小建议:不要这样做:
git cherry-pick $(git show-ref branch-a)
请这样做:
git cherry-pick branch-a
The cherry-pick 命令接受任何可以标识修订版本的内容,甚至是修订版本范围,如
gitrevisions 文档 中所述。
git show-ref
命令输出哈希 ID 和名称,因此它会尝试两次执行 cherry-pick 操作。(幸运的是,
git cherry-pick
足够聪明,可以消除多余的操作。)
解决冲突的高级视图
没有一种绝对正确的方法来解决冲突。您可以使用任何您喜欢的方法。这里有几个重要的事情需要记住:
Cherry-picking is essentially a three-way merge a la git merge
, except that instead of finding the actual merge base, Git just uses the parent of the commit you are cherry-picking as the merge base (and of course, the final commit is a regular non-merge commit).
Git doesn't track file name changes. Instead, when doing a merge operation—including a cherry-pick—Git in essence runs two git diff
commands:
git diff --find-renames <merge-base> HEAD # figure out what we did
git diff --find-renames <merge-base> <other> # figure out what they did
If the result is a merge conflict, Git leaves all three "interesting" files in the index—but one or two such files can be missing (as is the case here). The git status
command shows these as "unmerged", but you can find the full details using git ls-files --stage
. I'll show details in a moment.
Your job, at this point, is simply to arrange, in the index, the files you want in the final commit to be made as a result of the merge (or cherry-pick, in this case). These files need to be at stage zero, which is where normal, un-conflicted files live in the index. Any entries at stage 1 represent the version of the file in the merge base, any at stage 2 represent the version of the file in the HEAD
commit, and any at stage 3 represent the version of the file in the other commit you're merging with (or cherry-picking).
冲突本身
那么,让我们来看看 git diff --find-renames
所找到的内容,知道合并基础是被挑选的提交的父级。我们可以使用名称 branch-a
来确定挑选的提交。因此,它的父级是 branch-a^
(再次参见 gitrevisions 文档)。
$ git diff --find-renames branch-a^ HEAD
diff --git a/myFooFile b/myBarFile
similarity index 100%
rename from myFooFile
rename to myBarFile
这是“我们所更改的内容”:一次重命名操作。
$ git diff --find-renames branch-a^ branch-a
diff --git a/myBarFile b/myBarFile
new file mode 100644
index 0000000..5716ca5
--- /dev/null
+++ b/myBarFile
@@ -0,0 +1 @@
+bar
diff --git a/myFooFile b/myFooFile
deleted file mode 100644
index 257cc56..0000000
--- a/myFooFile
+++ /dev/null
@@ -1 +0,0 @@
-foo
这是“他们所做的更改”:删除一个文件,添加另一个。
现在Git负责将
myFooFile
重命名为
myBarFile
(内容为
foo
),同时删除
myFooFile
并创建带有内容
bar
的
myBarFile
。它不可能同时完成两个操作,因此我们会遇到冲突。与此同时,它可以执行我们所做的重命名,因此它只在工作区域中执行了该操作,使得
myBarFile
包含
foo
。
您想知道:
如何正确修复我们添加的cherry-pick冲突,并最终使
myBarFile
包含
bar
?
Git所需要的就是让您向工作区写入一个包含所需内容的
myBarFile
版本,然后调整索引以使其条目处于第零阶段。目前,索引中的内容是:
$ git ls-files --stage
100644 257cc5642cb1a054f08cc83f2d943e56fd3ebe99 2 myBarFile
并且myBarFile
的内容包含您不想要的内容。
注意:我知道在Git冲突解决期间可以使用git checkout branch-a -- myBarFile
...
是的:这会从由branch-a
标识的提交中提取myBarFile
的版本,将其写入阶段零的索引中,删除阶段2中的条目,并准备好提交所有内容。(作为奖励,它还将文件写入工作树,以便您可以看到它,尽管Git实际上在这一点上并不关心工作树版本。)因此,这是实现所需结果的一种好而快速的方法。我们可以看看另一种方法,虽然在某些情况下可能有用:
[但]我认为这不是Git的做法。
Git没有一个(单一)方法。Git是一种工具。按照您的喜好使用它。如果您喜欢在未触及索引的情况下调整工作树内容,则可以使用git show
提取文件的branch-a
版本:
$ git show branch-a:myBarFile > tmp
$ cat tmp
bar
$ mv tmp myBarFile
$ git add myBarFile
如果我们检查实际的暂存区,我们会发现:
$ git ls-files --stage
100644 5716ca5987cbf97d6bb54920bea6adde242d87e6 0 myBarFile
这就是我们想要的,因此现在我们可以使用 git cherry-pick --continue
或 git commit
完成此次 cherry-pick:
$ git cherry-pick --continue
此时,您首选的编辑器应打开一个包含主题
rm myFooFile add myBarFile 和其他数据(因为这是您要挑选的提交)的消息文件。将其写出如下:
[master 1837c17] rm myFooFile add myBarFile
Date: Fri Jun 15 22:21:48 2018 -0700
1 file changed, 1 insertion(+), 1 deletion(-)
"而git log --oneline
则显示你需要的内容:
"
1837c17 (HEAD -> master) rm myFooFile add myBarFile
a30b874 git mv myFooFile myBarFile
bde7df5 First commit add myFooFile
(注:我在我的配置中有log.decorate = auto
。)
README.TXT
和ReadMe.txt
一样(您可以在Linux框中拥有两个文件,但是当您在Windows或MacOS上检查此类提交时,Git无法执行它,您会得到一个混乱)。Git理论上可以为名称在操作系统级别冲突的文件制定新名称,但实际上并没有这样做。 - torekgit show :3:myBarFile
不起作用,我希望在这一步中能看到 bar。同样的原因吗? - Hugo ygit ls-files --stage
获得的输出。 (我认为在所有三个阶段中保留文件可能是合理的,但Git不这样做。) - torek