一个诡异的 cherry-pick 问题

2017-05-24 11:19:17 +08:00
 aheadlead

大家好,我是 git 萌新(刚开始正儿八经用 git )

在学习 cherry-pick 的时候,发现 git 的 merge 看起来并不单是会对同名文件进行 merge。而看起来 git 自有一套机制去追踪文件的名字变化。

实际场景是我在两个分支上,分支 B 上的文件 2 是用 cp 命令,从分支 A 复制文件 1 过来的。而随后我对分支 A 上的文件 1 修改并 commit 之后,在分支 B 上 cherry-pick 分支 A 修改文件 1 的那个 commit。

居然神奇的修改了分支 B 上的文件 2 !

本小白不太理解为什么会改掉分支 B 上的文件 2

下面是一个完整的重现过程,希望朋友们指点一下本蒟蒻 感谢各位大哥

aheadlead@my-computer:~$ mkdir git-playground

aheadlead@my-computer:~$ cd git-playground/

aheadlead@my-computer:~/git-playground$ git init
初始化空的 Git 版本库于 /home/aheadlead/git-playground/.git/

aheadlead@my-computer:~/git-playground$ echo "README" > README

aheadlead@my-computer:~/git-playground$ git add README & git commit -m "add README"
[1] 19843
[master (根提交) 04cbbb4] add README
 1 file changed, 1 insertion(+)
 create mode 100644 README
[1]+  已完成               git add README

aheadlead@my-computer:~/git-playground$ git checkout -b A
切换到一个新分支 'A'

aheadlead@my-computer:~/git-playground$ printf "11111\n22222\n33333\n44444\n55555\n" > 1

aheadlead@my-computer:~/git-playground$ cat 1
11111
22222
33333
44444
55555

aheadlead@my-computer:~/git-playground$ git add 1 & git commit -m "add 1"
[1] 19871
[A 8162adc] add 1
 1 file changed, 5 insertions(+)
 create mode 100644 1
[1]+  已完成               git add 1

aheadlead@my-computer:~/git-playground$ git lg
* 8162adc - (2 秒钟前) add 1 - aheadlead (HEAD, A)
* 04cbbb4 - (2 分钟前) add README - aheadlead (master)

aheadlead@my-computer:~/git-playground$ cp 1 2

aheadlead@my-computer:~/git-playground$ git checkout master
A       2
切换到分支 'master'

aheadlead@my-computer:~/git-playground$ git checkout -b B
A       2
切换到一个新分支 'B'

aheadlead@my-computer:~/git-playground$ git add 2

aheadlead@my-computer:~/git-playground$ git commit -m "add 2"
[B be0cc6d] add 2
 1 file changed, 5 insertions(+)
 create mode 100644 2

aheadlead@my-computer:~/git-playground$ git lg
* be0cc6d - (2 秒钟前) add 2 - aheadlead (HEAD, B)
| * 8162adc - (2 分钟前) add 1 - aheadlead (A)
|/
* 04cbbb4 - (4 分钟前) add README - aheadlead (master)

aheadlead@my-computer:~/git-playground$ git checkout A
切换到分支 'A'

aheadlead@my-computer:~/git-playground$ sed 's/33333/?????/g' -i 1

aheadlead@my-computer:~/git-playground$ cat 1
11111
22222
?????
44444
55555

aheadlead@my-computer:~/git-playground$ git add 1

aheadlead@my-computer:~/git-playground$ git commit -m "replace ????? from 33333"
[A 76eda04] replace ????? from 33333
 1 file changed, 1 insertion(+), 1 deletion(-)

aheadlead@my-computer:~/git-playground$ git checkout B
切换到分支 'B'

aheadlead@my-computer:~/git-playground$ git lg
* 76eda04 - (10 秒钟前) replace ????? from 33333 - aheadlead (A)
* 8162adc - (3 分钟前) add 1 - aheadlead
| * be0cc6d - (66 秒钟前) add 2 - aheadlead (HEAD, B)
|/
* 04cbbb4 - (5 分钟前) add README - aheadlead (master)

aheadlead@my-computer:~/git-playground$ git cherry-pick 76ed
[B 5bee333] replace ????? from 33333
 1 file changed, 1 insertion(+), 1 deletion(-)

aheadlead@my-computer:~/git-playground$ ls
2  README

aheadlead@my-computer:~/git-playground$ cat 2
11111
22222
?????
44444
55555

aheadlead@my-computer:~/git-playground$
2586 次点击
所在节点    git
5 条回复
doctorlai
2017-05-24 21:34:57 +08:00
竟然是中文的.
SoloCompany
2017-05-25 01:54:40 +08:00
首先,git 是缺少文件复制 /移动跟踪功能的,但却能实现文件的复制 /移动 (参考 log — follow)
其次,没错,git 的跟踪完全就是靠猜,根据每次 commit 的变化, 文件相似度, 文件名重合度来猜是否发生了复制 /移动
最后,因为一切都是靠猜,cherry-pick 的时候,因为 B 分支不存在复制后的文件 2,就会尝试查找可能的文件进行合并,由于文件 2 在改动前内容和文件 1 完全相同,sha1 完全一样,在分支 A 上能匹配到文件 1,所以就会直接把文件 2 的改动应用到文件 1 上面
aheadlead
2017-05-25 09:07:17 +08:00
@SoloCompany 非常感谢您的回复

请问这个机制有没有特别的名字?
SoloCompany
2017-05-25 15:26:20 +08:00
@aheadlead 应该没有吧,你可以直接 man git-merge 文档, 如果你不希望 git 自动检测文件移动, 可以使用 -Xno-renames, 你甚至可以使用 -Xrename-threshold 来控制相似度阈值, 这本身是一个缺点 (不像 svn 那样有准确的 copy 跟踪), 但在大部分场景下应该可以很好的工作
aheadlead
2017-05-25 15:30:03 +08:00
@SoloCompany 非常感谢!

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/363424

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX