Git merge

Git merge

参考文档:

https://marsishandsome.github.io/2019/07/Three_Way_Merge

https://git-scm.com/docs/merge-strategies

https://stackoverflow.com/questions/56889406/how-does-git-compare-two-files-while-merging

Git merge的目标是合并changes,但是Git并不存储changes。Git存储的是snapshots。从每个commit可以都可以获取到整个文件的,git可以从这个commit中获取整个文件。进行比较及合并。

一、Three-way merge

Git如何帮助我们进行merge操作的

Two-way merge

Two-way merge解决的问题是:如何把两个文件进行合并。

举例:假设你和另外一个人同时修改了一个文件,这时merge算法看到了这两个文件,如下图:
在这里插入图片描述
merging算法发现两个文件大部分都是一样,只有30行不一样,

  • Yours的版本里内容是:Print("hello")
  • Mine的版本里内容是:Print("bye")

但是merging算法怎么知道是你修改了30行还是另外一个人修改了?可能会有以下几种情况:

  1. Mine版本没有修改,Yours版本修改了内容(从Print("bye") 修改 Print("hello"))
  2. Yours版本没有修改,Mine版本修改了内容(从Print("hello") 修改 Print("bye")
  3. YoursMine都修改了内容,(Yours???修改成Print("hello")Mine???修改成Print("bye")
  4. YoursMine都增加了一行

对于一个merge算法来说,该怎么处理上述4中情况呢?

  1. Mine版本没有修改,Yours版本修改了内容 => 应该选Yours版本
  2. Yours版本没有修改,Mine版本修改了内容 => 应该选Mine版本
  3. YoursMine都修改了内容 => 需要手动解决冲突
  4. YoursMine都增加了一行 => 需要手动解决冲突

由于缺乏必要的信息,Two-way merge根本无法帮助我们解决冲突,TA只能帮助我们发现冲突,需要手动解决冲突。

如果让merging算法知道更多一些信息,merging算法是否可以帮助我们自动解决一些简单的冲突呢?下面来看一下Three-way merge算法。

Three-way merge

Three-way merge是在Two-way merge的基础上又增加了一个信息,即两个需要合并的文件修改前的版本。如下图所示,merge算法现在知道三个信息:

  1. Mine:需要合并的一个文件
  2. Yours:另一个需要合并的文件
  3. Base:两个文件修改前的版本

在这里插入图片描述

这时merging算法发现:

  • 修改前的Base版本里的内容是:Print("bye")
  • Yours的版本里内容是:Print("hello")
  • Mine的版本里内容是:Print("bye")

说明Yours对这一行做了修改,而Mine对这行没有做修改,因此对YoursMine进行merge后的结果应该采用Yours的修改,于是就变成Print("hello")

这就是Three-way merge的大致原理。

Three-way merge 冲突案例

我们来看一个更加复杂的案例,如下图:

在这里插入图片描述

按行对比两个文件后,merging算法发现有3个地方不一样,分别是:

  1. 30行:上文描述的冲突案例
  2. 51行:有一个for循环被同时修改
  3. 70行:Mine的版本里面新增了一行

在这里插入图片描述

我们来看一下这三种冲突改怎么解决:

  1. 30行:只有Yours修改了,因此使用Yours的版本
  2. 51行:YoursMine都修改了,需要手工解决冲突
  3. 70行:Mine新增了一行,因此使用Mine的版本

使用Three-way merge进行merge

我们来看下git是如何使用Three-way merge来进行git merge操作的。

先来看下git merge在官网的定义:

git-merge - Join two or more development histories together

即把两个或两个以上的开发历史进行合并。

这样讲比较抽象,来看一个简单的例子,假设我们有2个branch:

  • main:master branch
  • task001:我们正在开发的branch

第一次Merge:main -> task001

我们在task001上开发了一段时间,需要把main上的修改合并到task001,这时可以运行

$ git checkout task001
$ git merge main

在这里插入图片描述

merge后结果如下

在这里插入图片描述

merge的过程其实就是使用Three-way merge,其中

  1. Base = commit 1
  2. Mine = commit 4
  3. Yours = commit 3

merge后会生成一个新的merge节点commit 5,并且commit 5会同时依赖commit 3commit 4

使用Three-way merge进行cherry-pick

cherry-pick 在官网的定义如下:

git-cherry-pick - Apply the changes introduced by some existing commits

即把已经有的commit apply到其他分支,git cherry-pick其实也是使用Three-way merge,其中:

  1. Mine = 执行cherry-pick时所在的branch的HEAD
  2. Yours = 被cherry-pick的那个commit
  3. Base = 被cherry-pick的那个commit的前一个commit

这样讲比较抽象,举个例子:

E <-- master
|
D
| C <-- foo_feature(*)
|/
B
|
A

假设我们目前在foo_feature分支,运行git cherry-pick D,这时Three-way merge的参数:

  • Mine = C
  • Yours = D
  • Base = B

假设我们目前在foo_feature分支,运行git cherry-pick E,这时Three-way merge的参数:

  • Mine = C
  • Yours = E
  • Base = D

使用Three-way merge进行rebase

rebase官方定义如下:

git-rebase - Reapply commits on top of another base tip

即使用其他分支作为基础,重新apply当前分支所有的commit,git rebase的过程可以看做是不断的做git cherry-pick,举个例子:

E <-- master
|
|   F <-- foo_feature(*)
D  /
| C
|/
B
|
A

foo_feature branch运行下面运行git rebase master命令,就会变成下面的样子:

E <-- master
|
|       F <-- foo_feature(*)
|      /
|     C
D    /
|   E
|  /
| D
|/
B
|
A

相当于运行了下面几个命令:

git checkout master
git checkout -b foo_feature_rebased
git cherry-pick C
git cherry-pick F

二、Merge commit

https://stackoverflow.com/questions/40986518/how-to-git-show-the-diffs-for-a-merge-commit

定义:

合并提交(merge commit)是当两个分支的共同祖先不是最新提交时,Git创建一个新的提交来合并这两个分支的更改。

在 Git 中,当你执行 git merge 命令将一个分支合并到另一个分支时,Git 会创建一个特殊的提交,称为 “merge commit”。这个提交记录了两个分支合并的点,它有两个父提交:一个是你当前所在的分支的最新提交,另一个是你合并进来的分支的最新提交。

选择策略:

当你希望在 Git 历史中保留明确的合并点时,或者需要合并来自不同分支的更改,而这些更改不能简单地通过移动 HEAD 指针来完成时,使用合并提交是合适的。

Merge commit的内容:

通常包括

  1. 标题(Title)
    • 通常以 “Merge” 开头,后面跟着被合并分支的名称。
    • 例如:“Merge branch ‘feature-branch’ into ‘main’”
  2. 作者(Author)
    • 合并提交的作者通常是执行 git merge 命令的用户。
  3. 提交者(Committer)
    • 通常与作者相同,但有时可能会不同,尤其是在使用 git merge --no-ff 选项时。
  4. 日期(Date)
    • 合并提交的日期和时间。
  5. 消息(Message)
    • 通常包含更多关于合并的详细信息,可能包括被合并分支的提交范围和合并的原因。
  6. 父提交(Parent Commits)
    • 合并提交有两个父提交,分别指向两个分支的最新提交。
  7. 变更内容(Changes)
    • 合并提交可能包含文件的变更,这些变更是两个分支差异的结果。

注意:

  • 合并提交是 Git 历史的一部分,并且会永久存储在仓库中。
  • 使用 git merge --no-ff 选项可以确保即使在快进合并的情况下也会创建一个合并提交。
  • 合并提交可以帮助理解项目的历史和分支的合并点,但有时也可能使 Git 历史变得复杂。

查看 Merge Commit

git log --oneline --decorate --graph --all

这个命令会显示一个简化的 Git 历史图,包括所有的分支和合并提交。合并提交通常会有以下格式:

e43b5c2 (HEAD -> main, tag: v1.0, origin/main) Merge branch 'feature-branch'

在这个例子中:

  • e43b5c2 是合并提交的 SHA-1 哈希值。
  • HEAD -> main 表示当前 HEAD 指向 main 分支。
  • tag: v1.0, origin/main 表示合并提交也被标记为版本 v1.0,并且与远程 originmain 分支同步。

查看合并提交的详细内容:

git show e43b5c2

查看合并提交的diff:

git show -m c05f017
git show --first-parent c05f017
git diff c05f017^ c05f017

由于普通commit只有一个parent,所以git show可以明确的找到比较的对象。但是由于merge commit有两个parents,这时就需要指定parent。

c05f017和c05f0171都是代表第一个parent,c05f017^2代表第二个parent。

回退Merge commit

在这里插入图片描述

非merge commit 通过git revert xxx 命令即可产生一个回退commit。

但是merge类commit使用git revert xxx 命令,此时会报错commit is a merge but no -m option was given.,这是因为当前的merge commit其实包含了两个子commit,也就是当时合并的两个commit,因此在执行git revert 的时候会失败,需要选择回滚具体的哪一路的提交。

  1. 分析log, 确认要回退的commit

我们通过git log xxx 可以看到当前commit下的Merge: f2fe8c9 6103926,第二个id:6103926就是我要回滚的commitid。

具体要回退哪一路commit,可以通过git log --oneline --decorate --graph --all 图形化查看。

  1. 运行回退命令
git revert -m 1 xxx

注意:xxx为merge commit的commitid。

因此在执行git revert 的时候会失败,需要选择回滚具体的哪一路的提交。

  1. 分析log, 确认要回退的commit

我们通过git log xxx 可以看到当前commit下的Merge: f2fe8c9 6103926,第二个id:6103926就是我要回滚的commitid。

具体要回退哪一路commit,可以通过git log --oneline --decorate --graph --all 图形化查看。

  1. 运行回退命令
git revert -m 1 xxx

注意:xxx为merge commit的commitid。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值