Git

理论

区域组成

Git 本地有三个区域:

  • 工作区(workspace):就是你在电脑里能看到的目录。
  • 暂存区(staging area):一般存放在 .git 目录下的 index 文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)。
  • 版本库/本地仓库(local repository):工作目录有一个隐藏目录 .git,这个不算工作区,而是 Git 的版本库。

如果在加上远程的 git 仓库(remote repository),就可以分为四个工作区域。文件在这四个区域之间的转换关系如下:
pC8dmNR.png

  1. 当对工作目录被修改(或新增)的文件执行 git add 命令时,暂存区的目录树被更新,同时这些文件的内容被写入到对象库中的一个新的对象中,而该对象的 ID 被记录在暂存区的文件索引中。
  2. 当执行提交操作 git commit 时,暂存区的目录树写到版本库(对象库)中,当前分支会做相应的更新。
  3. 当执行 git reset HEAD 命令时,暂存区的目录树会被重写,被当前分支指向的目录树所替换,但是工作区不受影响。
  4. 当执行 git rm --cached <file> 命令时,会直接从暂存区删除文件,工作区则不做出改变。
  5. 当执行 git checkout . 或者 git checkout -- <file> 命令时,会用暂存区全部或指定的文件替换工作区的文件。这个操作很危险,会清除工作区中未添加到暂存区中的改动。
  6. 当执行 git checkout HEAD . 或者 git checkout HEAD <file> 命令时,会用 HEAD 指向的 master 分支中的全部或者部分文件替换暂存区和工作区中的文件。这个命令也是极具危险性的,因为不但会清除工作区中未提交的改动,也会清除暂存区中未提交的改动。

HEAD 是 Git 中用来引用当前快照的指针,指向当前分支的最后一次提交。
切换分支的原理就是将 HEAD 指向某一个分支的最后一次提交。

HEAD 指的是 .git/HEAD 文件,它存储着当前分支的名字:

1
ref: refs/heads/master

由此,我们可以得知当前所处于 master 分支。如果我们继续往下走,打开 refs/heads/master 文件:

1
7e136f508b982790db5686482075c60ee3ee4fed

这是 master 分支上最新提交的 commit id(版本号)。
我们在执行 git log 时会看到如下信息:

1
2
3
4
5
6
commit c15da881cddfec59648795fa77e37f2d18e69fcb
Author: Silence <xxxx@qq.com>
Date: Tue Jun 20 11:31:50 2023 +0800

# 或者
c15da88 update 111.txt

这里的 “c15da88” 或 “c15da881cddfec59648795fa77e37f2d18e69fcb” 就是每次 commit 的 commit id,同样也是 git 命令中确定 commit 版本号的依据,即 [HEAD] 的值。

创建仓库

初始化仓库

(1)使用当前目录作为 Git 仓库,我们只需使它初始化:

1
git init

该命令执行完后会在当前目录生成一个 .git 目录。
(2)使用指定目录作为 Git 仓库:

1
git init xxx

初始化后,会在 xxx 目录下会出现一个名为 .git 的目录,所有 Git 需要的数据和资源都存放在这个目录中。

克隆仓库
1
git clone https://gitee.com/ilenceszd/practice.git

执行该命令后,会在当前目录下创建一个名为 practice 的目录。
如果要自己定义要新建的项目目录名称,可以在上面的命令末尾指定新的名字:

1
git clone https://gitee.com/ilenceszd/practice.git myRepo

基本操作

(1)添加远程版本库

1
git remote add [shortname] [url]

shortname 是本地仓库为远程仓库起的别名,其指向远程仓库,如:

1
git remote add origin https://gitee.com/ilenceszd/practice.git

(2)添加文件到暂存区

1
2
3
4
5
6
7
8
9
10
11
# 添加目录下所有文件
git add .

# 添加一个或多个文件
git add 1.txt 2.txt 3.txt

# 添加目录
git add src/views

# 正则匹配
git add *.py

(3)将暂存区内容添加到本地仓库中

1
git commit -m [message]

message 可以是一些备注信息。
如果想在修改文件后不执行 git add 命令,直接来提交,可以执行以下命令:

1
git commit -am "xxx"

(4)提交到远程仓库

1
git push <远程主机名> <本地分支名>:<远程分支名>

如果本地分支名与远程分支名相同,则可以省略冒号,如:

1
git push origin master

如果本地版本与远程版本有差异,但又要强制推送可以使用 –force 参数:

1
git push --force origin master

分支管理

查看分支
1
git branch
创建分支
1
git branch [branchname]
切换分支
1
git checkout [branchname]

当你切换分支的时候,Git 会用该分支最后提交(commit)的快照替换你的工作目录的内容。

创建新分支并立即切换到该分支下
1
git checkout -b [branchname]
删除分支
1
git branch -d [branchname]
合并分支
1
git merge [branchname]

将分支 branchname 合并到当前分支上。

查看提交历史

Git 提交历史一般常用两个命令:

  • git log - 查看历史提交记录。
  • git blame <file> - 以列表形式查看指定文件的历史修改记录。

查看历史提交记录:

1
git log

查看历史记录的简洁的版本:

1
git log --oneline

查看历史中什么时候出现了分支、合并:

1
git log --graph

查看历史中 commit 是和哪一个分支或标签关联的:

1
git log --decorate

逆向显示所有日志:

1
git log --reverse

查找指定用户的提交日志:

1
git log --author=Silence

文件状态

  • Untracked: 未跟踪。此文件在文件夹中,但没有加入 git 库,不参与版本控制,通过 git add 状态变为 Staged.
  • Unmodify: 文件已经入库,未修改。也就是版本库中的文件快照内容与文件夹中完全一致。这种类型的文件有两种去处,如果它被修改,而变为 Modified。如果使用 git rm 移出版本库,则变成 Untracked。
  • Modified:文件已修改。仅仅是修改,并没有进行其他的操作,这个文件也有两个去处,通过 git add 可进入暂存 Staged 状态。使用 git checkout 则丢弃修改过的内容返回到 Unmodify 状态。
  • Staged: 暂存状态。执行 git commit 则将修改同步到库中,这时库中的文件和本地文件又变为一致,文件为 Unmodify 状态。执行 git reset HEAD filename 取消暂存,文件状态为 Modified。

查看文件状态:

1
2
3
4
5
6
7
git status [filename]

# 查看文件状态当前的标志
git status [filename] -s

# 查看所有文件状态
git status

标签

当我们的项目开发到一定的阶段,需要发布一个版本,这时,我们就可以对最后一次 commit 打一个标签。
当我们对某一次 commit 打上标签之后,我们后面继续开发,想找到该次 commit 时,通过查找该标签就很容易找到这次提交的版本。
如果我们没有打标签,就只能查找 commit 提交时的哈希值来返回到指定的位置了。
所以标签的作用,是方便我们查阅某次 commit 的,比如我们发布一个新的版本时。
可以说,标签就是某次 commit 的版本号的别名。

本地仓库的标签

创建标签
1
2
3
4
git tag [tagName]

# 带注解的标签
git tag -a [tagName] -m [message]

如果我们忘了给某个提交打标签,又将它发布了,我们可以给它追加标签:

1
git tag -a [tagName] [HEAD]
查看标签
1
2
3
4
5
# 查看所有标签
git tag

# 查看某个标签的详细信息
git show [tagName]
删除标签
1
git tag -d [tagName]

远程仓库的标签

推送标签到远程仓库

我们在向远程仓库 push 时,不仅可以根据分支,也可以根据标签。
推送某个分支的时候,标签并不会被推送到远程仓库,所以我们必须显式的推送标签到远程仓库。

1
2
3
4
5
# 推送某个标签到远程仓库
git push [remote] [tagName]

# 推送所有标签到远程仓库
git push [remote] --tags
删除远程仓库的标签

同样,在删除本地的标签后,要想删除远程仓库的标签,也必须使用的显式的命令。

1
2
# 删除远程仓库中的某个标签
git push [remote] --delete [tagName]
拉取远程仓库的标签
1
git pull [remote] [tagName]

其他常用命令

查看本地 git 配置
1
2
3
4
5
6
7
8
#查看全部 config
git config -l

#查看系统 config
git config --system --list

#查看当前用户全局 config
git config --global --list

操作远程仓库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 显示所有远程仓库
git remote -v

# 显示某个远程仓库信息
git remote show [url]

# 查看远程仓库的分支
git branch -r

# 添加远程版本库
git remote add [shortname] [url]

# 删除远程仓库
git remote rm [name]

# 修改仓库名
git remote rename [old_name] [new_name]

git checkout

该指令主要用于切换分支,其原理为将 HEAD 指针更新为指向特定分支的最近的一次 commit。
那么,我们将 HEAD 指针指向历史 commit 呢?

1
git checkout [HEAD]

这会进入 HEAD 游离状态,在该状态下,可以进行任意提交,因为当你切换回分支时这些提交会被清除。
由于执行该操作后,工作区的文件会被更新,因此如果我们觉得该 commit 下的文件对我们有用,可以新建分支将其保留:

1
git switch -c [new-branch-name]

这也是该命令的主要应用场景。
此外,还可以实现用暂存区全部或指定的文件替换工作区的文件:

1
2
git checkout .
git checkout -- [file]

这个操作很危险,会清除工作区中未添加到暂存区中的改动。


git clone、git fetch 和 git pull
用法
1
2
3
4
5
6
git clone [远程仓库 url] [本地目录名]

# 将远程分支下载到本地分支,若本地分支不存在则会新建
git fetch [远程主机名] [远程分支名]:[本地分支名]

git pull [远程主机名] [远程分支名]:[本地分支名]
区别

git clone 下载的是整个远程库,本地无需 git init,且下载的 .git 文件夹里存放着与远程仓库一模一样的版本库记录。
git fetch 下载的是远程库的一个分支。
git pullgit fetchgit merge 的组合操作。


git reset

语法:

1
git reset [--soft | --mixed | --hard] [HEAD]
–mixed

该参数是默认参数,用于重置暂存区的文件与上一次的提交(commit)保持一致,工作区文件内容保持不变。
也就是说,它作用的周期为 git add 之后 git commit 之前,它会将此次向暂存区添加的文件清除。

–soft

该参数用于回退到某个版本,其作用周期为 git commit 之后 git push 之前。
比如我们在执行 git commit 之后,想要撤回此次提交,就可以使用该参数。那么在下一次提交并 git push 后,远程仓库的提交记录中没有上次的提交记录。
注意:回退版本之后的暂存区的文件依然与上一次提交(撤回的提交)保持一致。

–hard

该参数会撤销工作区中所有未提交的修改内容,将暂存区与工作区都回到上一次版本,并删除上一次版本之后的所有信息提交。
其作用周期为任何时候


git revert

语法:

1
git revert -n [HEAD]

该命令用于“反做”某一个版本,以达到撤销该版本的修改的目的。
比如我们 commit 了三个版本(版本一、版本二、 版本三),现在想要撤销版本二,但又不想影响撤销版本三的提交,就可以用该命令来反做版本二,生成新的版本四,这个版本四里会保留版本三的东西,但撤销了版本二的东西。
反做之后需执行 git commit 提交。


git cherry-pick

该指令可以将一个分支上的修改更新得到其它分支上。
语法:

1
git cherry-pick [HEAD]

例如在分支 first 和 second 上都有文件 xxx.txt。
现在修改 first 分支上 xxx.txt 文件中某一位置的内容,提交后记录 commit id 为 a78252f。
然后切换分支到 second,执行

1
git cherry-pick a78252f

就可以将修改的那一部分内容同步过来。
它还支持一次转移多个提交:

1
2
3
4
5
6
7
git cherry-pick [HEAD1] [HEAD2] [HEAD3]

# 转移从 [HEAD1] 到 [HEAD2] 的所有提交,不包括 [HEAD1]
git cherry-pick [HEAD1]..[HEAD2]

# 转移从 [HEAD1] 到 [HEAD2] 的所有提交,包括 [HEAD1]
git cherry-pick [HEAD1]^..[HEAD3]
–edit

自定义提交信息。

–no-commit

只更新工作区和暂存区,不自动提交。


如果操作过程中发生代码冲突,cherry-pick 会停下来,让用户决定如何操作。

–continue

解决代码冲突后,要先将修改的文件添加到暂存区,然后执行:

1
git cherry-pick --continue

来让 cherry-pick 过程继续执行下去。

–abort

发生冲突后,放弃合并,使工作区回到操作前的样子。