【发布时间】:2019-02-14 22:19:05
【问题描述】:
当我创建一个孤立分支时,我必须删除所有文件,以便分支完全为空。我希望能够在空分支上挑选另一个提交,但我收到一条错误消息,指出文件已在一个分支上修改,但在另一个分支上被删除。我想避免手动解决这些冲突,所以我最终可以自动化这个过程。有没有办法自动冲突以接受文件的修改版本?
【问题讨论】:
标签: git git-branch cherry-pick
当我创建一个孤立分支时,我必须删除所有文件,以便分支完全为空。我希望能够在空分支上挑选另一个提交,但我收到一条错误消息,指出文件已在一个分支上修改,但在另一个分支上被删除。我想避免手动解决这些冲突,所以我最终可以自动化这个过程。有没有办法自动冲突以接受文件的修改版本?
【问题讨论】:
标签: git git-branch cherry-pick
我完全不清楚你想要什么样的结果——即你的真正目标是什么。不过,无论这个目标是什么,这都可能是错误的实现方式。
当我创建一个孤立分支时,我必须删除所有文件,以便分支完全为空。
这是对的,但错了。 :-)
更具体地说,让我们来看看操作的部分,以及你需要的命令:
git checkout --orphan newbranch:这会修改您当前的 Git 状态,以便您的 index 和 work-tree 保持不变,但您位于分支 newbranch,实际上还不存在。
git commit:这会根据您的 index 中的任何内容创建一个新提交。1 您的工作树无关紧要。如果您在一个不存在的分支上,例如步骤 1 中的newbranch,则这具有创建分支的效果。您所在分支的尖端现在是刚刚创建的提交,而您刚刚所做的新提交的父级是分支上的先前提交——在步骤 1 产生的状态下,它是 no 提交,因此新的提交有 父级。
所以当我说它是正确的但错误的时候,我的意思是没有一个分支是永远空的。一个分支——或者更准确地说,一个分支name——指向一个特定的提交。该提交必须存在!如果该提交是根提交,则该提交有一些父级,或者没有父级。如果您使用 branch 这个词来表示松散指定的提交链,以一个特定的提交结束,那也永远不是 empty。您需要做的是清除 index,因为 Git 从索引中的任何内容进行新的提交。
大概,您想要的是创建一个新的提交,其中 没有文件。那不是一个空的分支!这是一个有一个提交的分支——新提交没有父提交——其中一个提交有一个空的树。另请参阅 Is git's semi-secret empty tree object reliable, and why is there not a symbolic name for it? 但这个空树对象或使用它的提交只有有限的用途。
1如果您不熟悉索引一词的这种用法——也称为暂存区或缓存——以及工作树,参见 What's the difference between HEAD, working tree and index, in Git? 更多关于 Git 和 Git 用户倾向于将称为 branch 的东西混为一谈的方式,另请参见 What exactly do we mean by "branch"?
我希望能够在空分支上挑选另一个提交,但我收到一条错误消息,指出文件已在一个分支上修改,但在另一个分支上被删除。
由于 empty branch 没有意义,我不得不在这里猜测你的意思:你已经完成了 git rm -r .; git commit 创建这个分支 newbranch 指向一个提交,其树是空树.您现在有一个带有一个根提交的孤立分支。如果你运行:
git checkout newbranch
Git 删除所有(跟踪的)文件,留下一个空索引。
如果你现在运行git cherry-pick <hash>,你会收到很多关于你描述的形式的抱怨。那是因为樱桃挑选是合并。让我们绘制部分 Git 提交图:
... <-F <-G <-H <-- master
I <-- newbranch (HEAD)
也就是说,名称master 指的是提交H——我们主分支的尖端。提交H 的父级——这里的每个大写字母代表一个实际的哈希ID——是提交G。 G 的父级是F,其父级在... 范围内。
提交 I 有 no 父级;这是一个根提交。我们的HEAD 附加到名称newbranch,指向提交I。
此时,运行 git cherry-pick <hash-of-G> 会产生这些抱怨,原因是它正在对提交进行三向合并 F 作为合并基础,I 作为 --ours,G 作为 @ 987654350@。三路合并包括运行 two git diffs,看看谁做了什么工作:
git diff --find-renames <hash-of-F> <hash-of-G>:这就是他们所做的。 Git 将F 中的快照与G 中的快照进行比较,以查看它们发生了什么变化。
git diff --find-renames <hash-of-F> <hash-of-I>:这就是我们所做的。 Git 将F 中的快照与I 中的快照进行比较,以查看 我们 发生了什么变化。但是我们使用空树创建了I,所以不同的是我们删除了每个文件!
合并引擎的工作现在是将我们的更改(删除所有文件)与其更改合并,无论这些更改是什么。对于他们没有更改的任何文件,Git 会接受我们的更改并删除该文件。对于他们确实更改的任何文件,Git 都会声明合并冲突。除非 F 和 G 中的树匹配,否则我们保证至少会发生一次合并冲突,并且樱桃采摘将停止并让您完成工作。
有没有办法自动冲突以接受文件的修改版本?
当然:使用git ls-files --stage 在索引中的各个暂存槽中查找条目。对于存在于阶段 1 和阶段 3 中的任何文件,即同时位于合并基础和 --theirs 提交中,在此示例中为 F 和 G,插槽 2 中将没有文件,因为我们删除了所有文件(在此示例中提交 I)。您的工作是用一个零级条目替换三个编号较高的临时插槽。您可能希望来自第 3 阶段的文件(来自 --theirs 提交)在暂存槽零中,并且有一种简单的方法可以得到它:git add 工作树中的文件副本。
在这种特殊的冲突情况下,“在我们的删除”和“在他们的修改”,Git 将修改后的文件留在工作树以及暂存槽 3 中。所以git add 将把工作树复制到插槽 0 并删除插槽 1 和 3 中的条目——在这种情况下,插槽 2 中没有任何内容可以删除。 (如果有,git add 会删除它,但如果有,现在工作树中的内容可能不合适。)
由于 每个 文件都是这种情况,您可以简单地运行 git add . 来准备索引,然后运行 git cherry-pick --continue 或 git commit 来完成挑选并获得一个新的提交:
... <-F <-G <-H <-- master
I <-J <-- newbranch (HEAD)
新提交 J 包含提交 G 中的每个文件的副本,该副本与提交 F 中的同一文件的副本不同。提交I 仍然持有空树,因此几乎没用。
如果 commit J 确实是想要的结果,那么有一个更简单的2 方法可以得到它:
F 和G 中的每个文件,如果文件匹配,则不执行任何操作,但如果它们不同,请更新临时索引以保存G 的名称和哈希ID。第 2 步是唯一困难的部分,但很容易自动化:
git diff-tree -r --find-renames -z | ...automation-program...
-r 使 git diff-tree 递归到子目录中。 --find-renames 和 -z 是可选的:--find-renames 启用重命名检测器(否则重命名的文件是 Deleted 从第一次提交和 Added 到第二次以不同的名称),并且 -z 安排差异数据以 ASCII-NUL 结尾而不是换行结尾,并避免需要“取消引用”困难的文件名(请参阅the RAW OUTPUT FORMAT section of the git diff-tree documentation)。您必须自己编写自动化程序,但它可以使用从git diff-tree 读取的模式和哈希信息,对每个缓存信息行调用git update-index --cacheinfo。
第 1 步包括导出一个环境变量 GIT_INDEX_FILE 指向一个不存在的临时文件的名称:Git 将在运行时创建它。第 3 步是运行git commit-tree,相当于git commit,然后使用git update-ref 创建或更新引用名称以引用新提交。您可以直接为J 选择父母(或根本不选择父母)。请参阅他们的文档以了解使用情况。
2嗯,计算更容易。与运行 git add . 相比,显然还有更多工作要做。
【讨论】: