在分支之间进行 git 挑选和合并时出现意外行为



我很惊讶在 git 中挑选樱桃后所做的更改在合并时会过时。这是一个完整的示例。

以下是照常营业。

  1. 创建存储库
  2. 添加一个带有测试"rotums kanoner och krut"的文件
  3. 签出一个新分支,并添加文本行"mutors kanoner och krut">
  4. 查看 master 并使用 "mutors kanoner och krut" 挑选提交
Mac:git user1$ mkdir myrepo; cd myrepo; git init
Initialized empty Git repository in /Users/user1/tmp/git/myrepo/.git/
Mac:myrepo user1$ echo "rotums kanoner och krut" > rotum.txt
Mac:myrepo user1$ git add rotum.txt 
Mac:myrepo user1$ git commit -m "Added file"
[master (root-commit) 1044abb] Added file
1 file changed, 1 insertion(+)
create mode 100644 rotum.txt
Mac:myrepo user1$ git checkout -b mybranch
Switched to a new branch 'mybranch'
Mac:myrepo user1$ echo "mutors kanoner och krut" >> rotum.txt  
Mac:myrepo user1$ git commit -am "Added mutor"
[mybranch 19afeba] Added mutor
1 file changed, 1 insertion(+)
Mac:myrepo user1$ git checkout master
Switched to branch 'master'
Mac:myrepo user1$ git cherry-pick 19af
[master cce2ca5] Added mutor
Date: Wed May 19 16:12:04 2021 +0200
1 file changed, 1 insertion(+)
Mac:myrepo user1$ cat rotum.txt  
rotums kanoner och krut
mutors kanoner och krut

现在是发生意外行为的时候。

  1. 删除了添加并挑选的行(我通过覆盖文件来做到这一点,非常规方法,但在这种情况下很有用)。
  2. 然后我合并我的分支到主。我希望在 f63dc50 中所做的更改(删除一条线)会保留,但它神秘地消失了。"mutors kanoner och krut"这句话又回来了。
Mac:myrepo user1$ echo "rotums kanoner och krut" > rotum.txt  
Mac:myrepo user1$ cat rotum.txt  
rotums kanoner och krut
Mac:myrepo user1$ git commit -am "Removed mutor"
[master f63dc50] Removed mutor
1 file changed, 1 deletion(-)
Mac:myrepo user1$ git merge mybranch
Merge made by the 'recursive' strategy.
rotum.txt | 1 +
1 file changed, 1 insertion(+)
Mac:myrepo user1$ cat rotum.txt
rotums kanoner och krut
mutors kanoner och krut

这是预期行为还是错误?

附图:

Initial commit: create file
|    Add a line in file (cherry-pick b)
|    |    Remove line, return file to its state in a.
v    v    v
a----c----d  <- master

b <- mybranch
^
Add a line in file

mybranch合并到master时:

  • git 寻找关闭的共同祖先(又名合并基,图中的提交a.),
  • 它查看master(提交d),并看到与a.相比,文件保持不变
  • 它查看mybranch(提交b),并看到与a.相比,应该添加一行

因此,合并成功而没有冲突,并"引入"mybranch的更改。


您遇到这种情况是因为:

  1. git merge不检查mastermybranch历史记录中的中间提交,
  2. git cherry-pick创建新的、不相关的提交,并且提交图中不保留任何内容来指示"这些更改已包含在内",
  3. 碰巧可以组合更改而不会发生冲突(您的示例非常简单,但在实际情况中很可能会发生"无冲突")。

给出进一步的观点:

  • 如果您使用git rebase
git checkout mybranch
git rebase master

git merge不同,git rebase确实比较了提交列表,如果一个变基的提交引入了与目标分支中另一个提交完全相同的更改,则该提交将被丢弃。

在您的示例中:b不会重新应用,因为它引入了与master上已经c完全相同的更改。

  • 如果您合并了提交b而不是挑选:
merge 'mybranch'
v
a---c----d  <- master
 /
b <- mybranch

然后git merge mybranch会说already merged,并且不会重新应用提交b

这里的关键是 git 不会记录从一个分支到另一个分支精心挑选提交的事实;它只是根据您指定的提交创建一个新提交(这同样适用于"git rebase")。

就 git 而言,你有这些提交:

  • 创建文件的 1044abb
  • 19afeba 添加行
  • cce2ca5 添加行
  • F63DC50 去掉了线路

请注意,我没有将这些提交描述为"在"一个分支或另一个分支上,因为严格来说这在 git 中没有任何意义;分支指向提交,其他提交可以通过"父"指针访问。

在合并两个分支时:

  • 1044ABB、CCE2CA5 和 F63DC50 可从"主站"访问
  • 1044ABB和19afeba可从"MyBranch"访问
  • "master"中的文件只有一行
  • "mybranch"中的文件有两行

当你合并时,git 根据可从两个分支访问的最新提交来确定一个"合并基础";在本例中,即 1044abb。然后,它会查看该提交与要合并的两个分支之间的差异:

  • 在 1044ABB 和 F63DC50("主")之间,文件未更改
  • 在 1044ABB 和 19afeba ("mybranch") 之间,文件添加了一行

然后,它将这两个更改组合在一起,并应用它们来生成文件的新版本。由于合并的一方想要添加该行,而另一方不想执行任何操作,因此解决方法是添加该行。

结果是:

  • CCE2CA5、F63DC50 和 19AFEBA 均可从"主站"访问
  • 19afeba 可从"MyBranch"到达
  • 您已签出母版,其中又有该行

或者更简洁地说:您已经添加了该行,删除了它,然后再次添加它。

最新更新