Git学习笔记 #2 本地仓库使用
本文最后更新于:2022年5月19日 中午
接上文,本文在命令行的基础上介绍了常用指令的在本地的使用情景。
本文大部分内容参考了 RCY 同学的教程,部分参考了 廖雪峰教程-Git,菜鸟教程-Git,以及 Git 官网文档 Git-Documentation。
本地的 Git
预先准备
在正式的操作前,你需要先配置你的用户信息,这件事情通常在你的机器上只需要干一次即可,因为我们使用了全局配置。在任意⼀个目录打开你的终端,配置你的用户名和邮箱:
1 |
|
基本操作
在想用 Git 管理的项目路径下右键进入 Bash 终端,然后将当前目录初始化为 Git 仓库。
1 |
|
手动新建一个文件,随便写点什么,譬如一个 hello.md,此时文件存在于工作区。下面将其跟踪并提交:
1 |
|
注意到,add 后面是文件名,也可以是目录、通配符,因此也支持以下写法:
1 |
|
此外,为了体现三个区,我们可以使用一些指令来对比:
1 |
|
在工程中,通常我们不用命令行查看差异,而是用 VSCode、GitHub Desktop、GitLab 等可视化工具。
如果对于已暂存的文件后悔了,也可以取消暂存:
1 |
|
在完成了几次提交后,可以查看提交历史:
1 |
|
提交历史中,每个提交记录都有对应的 commit hash 值,唯一标识了这次提交,这是 Git 用 SHA-1 hash
生成的加密字符串。
注:如果 log 比较长或者窗口比较小,这会触发「导航」模式,很多人第⼀次见到可能不知所措,不会退出该页面,此时可以:
- 上下键移动或 Page Up / Down 翻页;
- 输入
\
接字符来全局查找 ; - 输入
q
退出,与其他系统中的导航模式类似; - 其他操作可以通过查询关键字「Linux less 导航」来查到。
分支 (Branch)
有了前面的知识,我们已经在脑海中想象⼀副快照变更图了,本节中我们将快照称作「结点」,若干结点组织成了版本树——Git 本身正是使用了红黑树对结点进行高效管理。
以下内容强烈推荐结合 Git 分支在线教程 来学习!
目前我们的结点树基本是串行的(除了回退、重新提交会导致分叉),那么所谓的并行开发如何体现呢?
注意到了反复有⼀个单词 main 出现在命令行,这即是 Git 默认的分支:主分支(旧版本叫 master)。main 即是这个分支的名字,也是一个指针,指向了该分支的最新结点。
和它⼀起的还有⼀个单词 HEAD,这表示头指针,指向当前所处的结点。在你做分支相关的操作前,会有 HEAD -> main -> 最新结点
,直到你将它们分开。
创建分支
与初始化仓库类似,创建分支也很方便:
1 |
|
注意:创建完后分支的即会指向当前所在的结点,因此当前最新结点同时被 main、test、HEAD 指向,即 HEAD -> main(test) -> 最新结点
。
对于已有的分支,也可以通过以下命令查看、切换:
1 |
|
切换分支后,可以看到出现在命令行右侧的 main 已经变成了 test。查看 log 也可发现 HEAD -> test(main) -> 最新结点
。
合并分支
如果此时我们已经在不同的分支提交了不同修改 C2 和 C3,那么如何将分支合并到一起,使得并行开发结果汇总呢?
1 |
|
对于分支的合并,Git 有专门的图形化输出命令来进行查看版本树(也可以用其他可视化工具):
1 |
|
为展示方便,这里使用在线教程里的图例:
可以看到,合并后产生了一个新结点 C4,该结点具有双父结点,指向原来的 C2 和 C3。需要注意的是,该结点属于 main 分支,是 main 分支的最新结点(被指针 main 所指),而 test 仍指向旧的 C2。
如果要把 test 分支也同步到新结点,只需要让 test 分支合并 C4,也就是合并当前的 main 分支:
1 |
|
而由于 main 分支的最新结点 C4 继承自 C2,此时的 Git 不会有任何操作,只是简单地将指针 test 移动到指针 main 所在位置,即快速前移(fast-forward):
多数情况下,我们会先用主分支合并从分支,如果之后需要再从分支继续开发,才会把从分支快速前移!
冲突的合并
如果 C2 和 C3 修改的代码不在同一文件的同一处,上述的 merge 是没有问题的,但是一旦发生冲突,git merge test
命令时就会提示错误。
此时如果我们用 git status
查看,会发现工作区里有一个新的状态「未合并的路径」,里面就是冲突的文件。打开该文件,会发现 Git 已经在里面标记出了双方修改的内容(用 VSCode 等 IDE 将看得更清楚)。
而我们只需要手动维护冲突,将该文件手动加入暂存区,最后再提交,就会生成一个新的结点,该结点无异于直接使用 git merge test
命令得到的。
变基 (Rebase)
一个来回穿插的版本树是有点凌乱的,此时不得不提到第二种合并分支的办法:变基 (rebase) 操作。Rebase 实际上就是取出从分支的提交记录,「复制」它们,然后在主分支逐个的放下去。
Rebase 的优势就是可以创造更线性的提交历史。如果两个分支没有冲突,如上文提到的第一种情况,直接 Merge 会出现一个新的结点(实际上该结点并没有做出实质的修改,反而使版本树变得冗长)。
此时如果我们用 Rebase 操作,则可以简化版本树:
1 |
|
注意,Rebase 操作通常是让从分支变基到主分支,这与 Merge 操作是相反的!
观察该图,我们可以发现 test 分支上的工作在 main 的最顶端,同时我们也得到了一个更线性的提交序列。
而提交记录 C2 依然存在(树上那个半透明的节点),而 C2’ 是我们 Rebase 到 main 分支上的 C2 的副本,它们具有不同的 hash 值,可以通过 log 查看。
之后我们也可以通过类似的操作,把 main 快速前移到最新的结点:
1 |
|
Rebase 操作在没有冲突时将非常舒适,可以避免没有意义的合并结点(尤其是涉及到远程仓库时),但是一旦发生了冲突,操作将十分繁琐!这里不再赘述,具体工程中如果遇到了请根据 Git 的自动提示逐步操作。
而对于 Rebase 后的从分支上的结点,就变成了所谓的「悬垂结点」,这些结点的访问将十分复杂。此外,如果这个从分支被废弃,我们也可以用以下指令将其删去:
1 |
|
分叉 (Branch Diverged)
分叉是分支的一种特殊情况,往往是因为某些「不友好」操作而产生,最终被废弃掉。对于单人操作的仓库,其产生原因可能是:
- Reset 后旧分支:版本回退后重新提交,这种情况下往往是要弃用旧分支。但如果旧的分支仍有需要保留的更改,则需要 Cherry-Pick 等操作。
- Rebase 后从分支:上文介绍到,Rebase 将创造更线性的主分支,但这样做的代价是从分支将被废弃,成为一个无用的分叉。
上述情形的发生往往可以人为进行控制,而对于多人操作的仓库,如果不同开发者同时对一个结点进行了更改,将会造成「不可控」的分叉,具体情形及解决方法将在下一节介绍。