第九章:合并
1】Git是一个分布式版本库控制管理系统(Distributed Version Control System)DVCS。
2】一次合并会结合两个或多个历史提交分支。(多数情况是两个分支)
3】合并必须发生在同一个版本库中。
4】两个分支对同一文件同一行进行了修改,合并时就会出现错误。(手动修改)
1、合并的例子
1】先检出到目标分支,再把其他分支合并到目标分支。
2】合并会有两种: 1、没有冲突直接合并成功。 2、大量的冲突手动处理提交。
(1)为合并做准备
1】如果版本库中已经有了一个(脏的)工作目录或者索引。Git可能无法一次合并完成。
2】一般: 从干净工作目录和索引进行合并,当受合并操作的影响文件和工作目录的脏文件无关时(忽略)
3】重点:合并前尽量让工作目录和索引是干净的
(2)合并两个分支
1】合并的提交不修改相同文件的相同部分,就不会发生冲突。
2】git merge操作区分上下文。当前分支始终是目标分支。
3】git log --graph --pretty=oneline --abbrev-commit命令。查看提交图(与sourcetree相似)
4】git log --graph命令,是图形工具很好的替代品。
(3)有冲突的合并
1】当有冲突时,合并操作会报错。
2】通过git diff命令可以查看到冲突。(索引与工作目录的不同)
3】冲突解决后,两个分支的同一个文件的同一行内容还是会不同的。
(冲突解决后:只要不修改该行,该不同就不会冲突。因为只提交修改的,合并跟它无关)
4】分支B合并分支A时,就不会有冲突。而且这个不同也会消失了。(A并B之后做)
5】解决冲突时,可以使用当前分支内容,也可用被合并分支内容。甚至可以编写一个新内容。
2、处理合并冲突
(1)定位冲突文件
1】Git会对有问题的文件进行跟踪,并在索引中把他们标记为冲突的(conflicted)
或者未合并的(unmerged)
2】使用git status或者git ls-files -u命令显示工作树中仍然未合并的一组文件。
(2)检查冲突
1】当冲突出现时,通过第三方比较或合并标记,强调工作目录的每一个冲突文件的副本。
2】有冲突文件的样子。(合并标记,划定文件冲突部分的两个可能的版本)
3】三方合并标记线(<<<<<<< 、 ======= 和 >>>>>>>),它是自动生成的。
4】git diff显示针对两个父版本做修改。HEAD和MERGE_HEAD都只显示一方。
5】 git log的一些特殊选项可以帮助找到变更的确切来源和原因。
(3)Git是如何追踪冲突的
1】Git追踪合并冲突的所有信息
2】用git ls-files底层命令,查看索引如何存储(-s:查看所有文件, -u:只看冲突文件)
3】git cat-file -p + "SHA1",查看指定版本的文件内容。
4】用git diff的特殊语法比较文件的不同版本。(基础版本和合并版本的不同可以比较)
(4)结束解决冲突
1】 当我们处理完冲突后,只需要再次对它进行。add和commit操作就好。
2】在SHA1和路径名中间单独的0,代表无冲突文件的暂存编号是零。
3】不能对有冲突标记的文件进行add 和 commit操作,虽然不报错但是文件是错误的
4】.git/MERGE_MSG中保存着,合并信息。
5】查看一个合并提交时,要注意三件事。
(5)终止或重新启动合并
1】Git提供一个终止合并操作的命令。(git reset --hard HEAD)
2】合并操作之前,Git会把原始分支的HEAD保存在ORIG_HEAD。
因此(git reset --hard ORIG_HEAD 和 git reset --hard HEAD一样的效果)
3】如果在工作目录或者索引不干净的情况下启动合并,可能会丢失工作目录中任何未提交的修改。
4】脏的工作目录启动git merge(合并)请求,执行git reset --hard HEAD。
(合并之前的脏状态不会完全还原,重置甚至会弄丢工作目录区域的脏状态)
5】从Git 1.6.1版本,如果冲突解决方案搞砸了,(git checkout -m)回到解决之前的原始冲突状态。
3、合并策略
1】Git有特别复杂的DAG型历史和又长又难记的提交ID。
2】始终可以用 git merge-base 找到两个或者两个以上分支之间的合并基础。
(一组分支等效的合并基础可能不止一个)
3】合并基础:Bob获取Cal的最新改动。Bob向上找穿过Alice,到达她(Alice)第一次偏离Cal的A点。
A点就是Bob和Cal的合并基础。从Cal处合并,Bob需要在A到Q之间做出一系列变化。
三方把他们合并到他自己的树中,得到提交K。
4】Cal最新版本到Bob的最新版本的差异,得到Bob的所有修改。(Alice有了Bob的部分修改)
从Alice最后从Bob合并的版本J,但是K中来自Cal的修改,Alice已经包含了。(交叉合并)
5】因为修改在分支之间来回合并,所以会产生交叉合并。(修改只在一个方向上移动,合并就会很简单)
(1)退化合并
1】两种导致合并的常见退化情况。1、已经是最新的 和 2、快进的
2】以上两种情况,合并时都不引入一个和并提交。
3】快进的:HEAD已经在其他分支存在,Git简单地把其他分支的新提交钉在HEAD上。
(Git移动分支HEAD来指向最终的新提交。)
4】每一个新的合并如果都是一个新的提交,序列将永远不会收敛于一个稳定的状态。
(2)常规合并
1】以下合并策略都会产生一个最终提交,添加到当前分支中,表示合并的组合状态。
2】递归合并策略: 交叉合并是简单的例子。(合并需要对更早的提交进行另一个合并,所以叫递归)
3】章鱼合并: Git支持多个分支合并是因为通用性和高雅设计。
(3)特殊提交
1】两个特殊的合并策略: 我们的(ours)和子树(subtree)。
2】以下合并策略都会产生一个最终提交,添加到当前分支中,表示合并的组合状态。
(4)应用合并策略
1】Git会尝试使用尽可能简单和廉价的算法。
(会首先尝试使用"已经是最新的"和"快进"策略消除不重要的、简单的情况)
2】指定多个其他分支合并到当前分支中,Git只能尝试章鱼策略。
3】都失败后,Git会使用一个能可靠工作的默认策略。(最初,默认是resolve策略,现在是递归策略)
4】应用指定的策略进行合并(git merge -s resolve(策略名) 分支名)
5】Alice去合并Bob的交叉合并。使用了递归合并策略
6】使用 "我们的" 和 "子树"
(5)合并驱动程序
1】每一个合并策略都使用相关的合并驱动程序来解决和合并每个单独文件。
2】一个合并驱动程序接受三个临时文件名来代表共同祖先、目标分值版本和文件的其他分支版本。
(驱动程序通过修改目标分值来得到合并结果)
3】三个合并驱动程序
4】特定的文件会有特定的合并驱动程序(多数情况下: 文本用(text),二进制用(binary))
4、Git怎么看待合并
(1)合并和Git 的对象模型
1】在每个提交只有一个父提交的VSC中,合并会在当前分支创建一个新提交(包含另一个分支的修改)
2】单个父提交(VSC): A并B、B并A是两个操作。Git:合并A和B的所有修改到单个分支是一个操作
3】Git中,合并产生一个新的树对象(包含合并后的文件),该树对象只在目标分支上引入一个新提交对象
(2)压制合并
1】压制提交: 大多数系统,分支A的多个提交合并到分支B,产生一个差异。
把它当成一个单独的补丁打到my_branch,在历史中创建一个新元素
2】Git中,两分支平等。提交的整个历史记录两边都保存。
3】Git执行压制提交。(git merge 或者git pull 添加--squash选项。最好不要这么做)
(3)为什么不一个接一个地合并每一个变更
1】Git保持一个详细的历史记录,以便你在一个特定的时刻可以重新审视你的文件在过去的样子。
2】如果合并提交反映出从来没有真正存在过的文件版本,就会失去最初拥有的详细历史记录。(原因)