简体   繁体   English

git 樱桃挑选并在分支之间合并时的意外行为

[英]Unexpected behaviour when git cherry-pick and merge between branches

I'm surprised that changes done after cherry-picking in git become obsolete when merging.我很惊讶在 git 中挑选后所做的更改在合并时变得过时。 Here is a full example.这是一个完整的例子。

The following is business as usual.以下是照常营业。

  1. Create a repo创建一个回购
  2. Add a file with test "rotums kanoner och krut"添加带有测试“rotums kanoner och krut”的文件
  3. Check out a new branch, and add the text line "mutors kanoner och krut"查看一个新分支,并添加文本行“mutors kanoner och krut”
  4. Check out master and cherry-pick the commit with "mutors kanoner och krut"查看 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

Now is when the unexpected behaviour occurs.现在是发生意外行为的时候。

  1. I remove the line that was added and cherry-picked (I do this by overriding the file, unconventional method, but useful in this case).我删除了添加和挑选的行(我通过覆盖文件来做到这一点,非常规方法,但在这种情况下很有用)。
  2. Then I merge mybranch to master.然后我将 mybranch 合并到 master。 I would expect the changes done in f63dc50, removal of a line, to remain, but it mysteriously vanishes.我希望在 f63dc50 中所做的更改(删除一条线)会保留下来,但它神秘地消失了。 The line "mutors kanoner och krut" is back. “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

Is this expected behaviour or a bug?这是预期的行为还是错误?

The key here is that git does not record the fact that a commit was cherry-picked from one branch to another;这里的关键是 git 没有记录提交是从一个分支到另一个分支的事实; it just creates a new commit based on the one you specify (the same applies to "git rebase").它只是根据您指定的提交创建一个新提交(同样适用于“git rebase”)。

As far as git is concerned, you have these commits:就 git 而言,您有以下提交:

  • 1044abb which creates the file 1044abb 创建文件
  • 19afeba which adds the line 19afeba 添加线
  • cce2ca5 which adds the line cce2ca5 添加该行
  • f63dc50 which removes the line f63dc50 删除该行

Note that I haven't described these commits as "on" one branch or the other, because strictly that has no meaning in git;请注意,我没有将这些提交描述为“在”一个分支或另一个分支上,因为严格来说,这在 git 中没有任何意义; a branch points at a commit, and other commits are reachable via "parent" pointers.分支指向提交,其他提交可通过“父”指针访问。

At the point when you merge the two branches:在合并两个分支时:

  • 1044abb, cce2ca5 and f63dc50 are reachable from "master" 1044abb、cce2ca5 和 f63dc50 可从“master”访问
  • 1044abb and 19afeba are reachable from "mybranch" 1044abb 和 19afeba 可从“mybranch”访问
  • the file in "master" only has one line “master”中的文件只有一行
  • the file in "mybranch" has two lines “mybranch”中的文件有两行

When you merge, git determines a "merge base" based on the most recent commit reachable from both branches;合并时,git 根据两个分支可访问的最新提交确定“合并基础”; in this case, that is 1044abb.在这种情况下,即 1044abb。 It then looks at the differences between that commit and the two branches being merged:然后它会查看该提交与被合并的两个分支之间的差异:

  • between 1044abb and f63dc50 ("master") the file is not changed在 1044abb 和 f63dc50 ("master") 之间文件没有改变
  • between 1044abb and 19afeba ("mybranch") the file has an added line在 1044abb 和 19afeba ("mybranch") 之间,文件添加了一行

It then combines these two changes, and applies them to produce the new version of the file.然后它结合这两个更改,并应用它们来生成文件的新版本。 Since one side of the merge wants to add the line, and the other wants to do nothing, the resolution is to add the line.由于合并的一侧要添加行,而另一侧什么都不做,因此解决方法是添加行。

The result is:结果是:

  • cce2ca5, f63dc50 and 19afeba are all reachable from "master" cce2ca5、f63dc50 和 19afeba 都可以从“master”访问
  • 19afeba is reachable from "mybranch" 19afeba 可从“mybranch”访问
  • you have master checked out, which has the line again你有主人签出,这又是一条线

Or to put it more succinctly: you've added the line, removed it, and then added it again.或者更简洁地说:您已经添加了该行,将其删除,然后再次添加它。

With a diagram:带图:

   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

When you merge mybranch into master :当您将mybranch合并到master时:

  • git looks for the closes common ancestor (aka the merge base, commit a. in the diagram), git 寻找关闭的共同祖先(又名合并基础,在图中提交a. ),
  • it looks at master (commit d ) and sees that, when compared to a.它查看master (commit d ) 并看到,与a. , the file is left unchanged ,文件保持不变
  • it looks at mybranch (commit b ) and sees that, when compared to a.它查看mybranch (提交b )并看到,与a. , a line should be added , 应添加一行

so the merge succeeds without conflicts, and "brings in" the changes from mybranch .因此合并成功而没有冲突,并“引入”来自mybranch的更改。


You reach this situation because:您遇到这种情况是因为:

  1. git merge does not inspect the intermediate commits in the history of master and mybranch , git merge不检查mastermybranch历史中的中间提交,
  2. git cherry-pick creates new, unrelated commits, and nothing is kept in the commit graph to indicate "these changes were already included", git cherry-pick创建新的、不相关的提交,并且提交图中没有保留任何内容以指示“这些更改已包含在内”,
  3. it so happens that the changes can be combined without conflicts (your example is really simple, but "no conlicts" may very well happen in actual situations).碰巧可以组合更改而不会发生冲突(您的示例非常简单,但是在实际情况下很可能会发生“没有冲突”)。

To give further perspective:提供进一步的视角:

  • if you use git rebase :如果您使用git rebase
git checkout mybranch
git rebase master

unlike git merge , git rebase does compare the list of commits, and if a rebased commit introduces the exact same changes as another commit in the target branch, that commit is dropped.git merge不同, git rebase确实比较了提交列表,并且如果重新定位的提交引入了与目标分支中的另一个提交完全相同的更改,则该提交将被删除。

In your example: b wouldn't be reapplied, because it introduces the exact same changes as c which is already on master .在您的示例中: b不会被重新应用,因为它引入了与已经在master上的c完全相同的更改。

  • if you had merged commit b instead of cherry-picking:如果您合并了提交b而不是挑选樱桃:
       merge 'mybranch'
       v
   a---c----d  <- master
    \ /
     b <- mybranch

then git merge mybranch would have said already merged , and wouldn't have re-applied commit b然后git merge mybranch会说already merged ,并且不会重新应用提交b

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM