Git: 在未检测到的重命名中手动合并更改
Git: Manually merge changes in an undetected rename
这道题使用了Git 2.7.0.windows.1
,所以一些git命令可能已经过时了。
如果我的 git merge
命令没有检测到重命名的文件,我如何告诉 git 手动合并两个应该是单个文件的文件的更改,而不启动合并并使用较低的重命名阈值?
复制步骤:
git init
echo "//Hello world!" > hw.h
git add . && git commit -m "Initial commit"
git checkout -b someBranch
mv hw.h hw.hpp
echo "//Foobar" > hw.hpp
git add . && git commit -m "Change hw to HPP & change content"
git checkout master
echo "//Boofar" > hw.h
git add . && git commit -m "Change content of hw"
git merge -X rename-threshold=100% someBranch
您会遇到合并冲突,但不会有冲突的块。也就是说,您应该得到的唯一 conflict/error 是:
CONFLICT (modify/delete): hw.h deleted in branchB and modified in HEAD. Version HEAD of hw.h left in tree.
Automatic merge failed; fix conflicts and then commit the result.
而git status --porcelain
会显示:
UD hw.h
A hw.hpp
通常,合并时,最好将检测重命名的阈值设置得足够低,以便检测到重命名。 Some people recommend 5%,例如。就我而言,我正在进行大规模合并(>1000 个文件和大约 900 万行代码),并且我必须将重命名阈值提高到足够高以避免任何 "false positives";实际上降低了百分之一,我得到了大量错误检测的重命名(我知道,重复的代码很糟糕)。根据我最终使用的值,我只得到了一小部分遗漏的重命名,这似乎是一个更好的选择。
TL;DR 降低重命名阈值不是我的选择;我如何在不重新开始合并的情况下告诉 git 将 hw.h
和 hw.hpp
视为单个文件(有冲突),而不是如上所示的两个文件?
这方面的工具有点笨拙,但它们 在那里。
您需要确保合并本身在提交之前停止。在您的情况下,这是自动发生的。对于更棘手的合并,Git 认为它正确执行但不是,您可以添加 --no-commit
,但这会影响接下来的几个步骤。我们暂时忽略这个问题。
接下来,您需要获取相关文件的所有三个版本。由于 Git 因冲突而停止,我们的状态很好:三个版本都可以通过索引访问。记得我们关心的三个版本是merge base、--ours
和--theirs
.
如果 Git 正确检测到重命名,所有三个版本将在索引中以 单一 名称命名。既然没有,他们就不是:我们需要两个名字。 (在 "Git thinks it did the merge correctly" 情况下,文件的合并基础版本根本不在索引中,我们必须以其他方式检索它。)您的情况下的两个名称是 hw.h
和hw.hpp
,所以现在我们这样做:
$ git show :1:hw.h > hw.h.base # extract base version
$ git show :2:hw.h > hw.h # extract ours
$ mv hw.hpp hw.h.theirs # move theirs into place
(重命名不是绝对必要的,只是为了帮助保持一切正常和漂亮的插图。)
现在我们要合并一个文件 git merge-file
:
$ git merge-file hw.h hw.h.base hw.h.theirs
这会使用您配置的 merge.conflictStyle
,以便合并文件中的内容看起来与您预期的一样,只是冲突行上的标签略有不同。我设置了 diff3
,所以我得到:
$ cat hw.h
<<<<<<< hw.h
//Boofar
||||||| hw.h.base
//Hello world!
=======
//Foobar
>>>>>>> hw.h.theirs
您现在可以照常解决此问题,rm
额外的 .base
和 .theirs
文件,git add
最终结果,git rm --cached hw.hpp
,以及 git commit
。 (由您决定何时 git rm --cached hw.hpp
:在提交前的 任何 时间点执行此操作是安全的,但一旦完成,您将无法再获得 "theirs"来自索引;见下文。)
请注意,"ours" 和 "theirs" 版本也可通过 git show HEAD:path
和 git show MERGE_HEAD:path
获得。要获得没有索引的基础版本,我们必须 运行 git merge-base HEAD MERGE_HEAD
找到它的哈希 ID(然后假设还有一个合并基础 1), 和 git show <hash>:path
。如果 Git 认为它已正确完成合并,这就是我们必须做的。
另请注意,如果您真的想要——我想这只有在您想要使用您拥有的其他需要它的工具时才是正确的——您可以使用 git update-index
来随机播放索引中的条目,将 hw.hpp
移动到 hw.h
的插槽 3 中,以便它 确实 显示为 "theirs",并以这种方式显示在 git status
。对于这个特定的例子:
$ printf '100644 bbda177a6ecfe285153467ff8fd332de5ecfb2f8 3\thw.h' |
git update-index --index-info
此处的散列来自 git ls-files --stage
,是 hw.hpp
的散列。 (您需要第二个步骤来删除 hw.hpp
索引条目。)
1使用git merge-base --all
找到所有合并碱基。如果不止一个,你可以任意选择一个(这就是-s resolve
所做的),或者尝试将所有合并基地合并成一个虚拟合并基地。要合并两个合并基础,您找到他们自己的合并基础,并使用该合并基础合并两个基础,就好像它们是分支提示一样。根据需要递归和迭代——这就是 Git 对默认 -s recursive
策略所做的——直到你有一个文件的单一合并基础版本。
这道题使用了Git 2.7.0.windows.1
,所以一些git命令可能已经过时了。
如果我的 git merge
命令没有检测到重命名的文件,我如何告诉 git 手动合并两个应该是单个文件的文件的更改,而不启动合并并使用较低的重命名阈值?
复制步骤:
git init
echo "//Hello world!" > hw.h
git add . && git commit -m "Initial commit"
git checkout -b someBranch
mv hw.h hw.hpp
echo "//Foobar" > hw.hpp
git add . && git commit -m "Change hw to HPP & change content"
git checkout master
echo "//Boofar" > hw.h
git add . && git commit -m "Change content of hw"
git merge -X rename-threshold=100% someBranch
您会遇到合并冲突,但不会有冲突的块。也就是说,您应该得到的唯一 conflict/error 是:
CONFLICT (modify/delete): hw.h deleted in branchB and modified in HEAD. Version HEAD of hw.h left in tree.
Automatic merge failed; fix conflicts and then commit the result.
而git status --porcelain
会显示:
UD hw.h
A hw.hpp
通常,合并时,最好将检测重命名的阈值设置得足够低,以便检测到重命名。 Some people recommend 5%,例如。就我而言,我正在进行大规模合并(>1000 个文件和大约 900 万行代码),并且我必须将重命名阈值提高到足够高以避免任何 "false positives";实际上降低了百分之一,我得到了大量错误检测的重命名(我知道,重复的代码很糟糕)。根据我最终使用的值,我只得到了一小部分遗漏的重命名,这似乎是一个更好的选择。
TL;DR 降低重命名阈值不是我的选择;我如何在不重新开始合并的情况下告诉 git 将 hw.h
和 hw.hpp
视为单个文件(有冲突),而不是如上所示的两个文件?
这方面的工具有点笨拙,但它们 在那里。
您需要确保合并本身在提交之前停止。在您的情况下,这是自动发生的。对于更棘手的合并,Git 认为它正确执行但不是,您可以添加 --no-commit
,但这会影响接下来的几个步骤。我们暂时忽略这个问题。
接下来,您需要获取相关文件的所有三个版本。由于 Git 因冲突而停止,我们的状态很好:三个版本都可以通过索引访问。记得我们关心的三个版本是merge base、--ours
和--theirs
.
如果 Git 正确检测到重命名,所有三个版本将在索引中以 单一 名称命名。既然没有,他们就不是:我们需要两个名字。 (在 "Git thinks it did the merge correctly" 情况下,文件的合并基础版本根本不在索引中,我们必须以其他方式检索它。)您的情况下的两个名称是 hw.h
和hw.hpp
,所以现在我们这样做:
$ git show :1:hw.h > hw.h.base # extract base version
$ git show :2:hw.h > hw.h # extract ours
$ mv hw.hpp hw.h.theirs # move theirs into place
(重命名不是绝对必要的,只是为了帮助保持一切正常和漂亮的插图。)
现在我们要合并一个文件 git merge-file
:
$ git merge-file hw.h hw.h.base hw.h.theirs
这会使用您配置的 merge.conflictStyle
,以便合并文件中的内容看起来与您预期的一样,只是冲突行上的标签略有不同。我设置了 diff3
,所以我得到:
$ cat hw.h
<<<<<<< hw.h
//Boofar
||||||| hw.h.base
//Hello world!
=======
//Foobar
>>>>>>> hw.h.theirs
您现在可以照常解决此问题,rm
额外的 .base
和 .theirs
文件,git add
最终结果,git rm --cached hw.hpp
,以及 git commit
。 (由您决定何时 git rm --cached hw.hpp
:在提交前的 任何 时间点执行此操作是安全的,但一旦完成,您将无法再获得 "theirs"来自索引;见下文。)
请注意,"ours" 和 "theirs" 版本也可通过 git show HEAD:path
和 git show MERGE_HEAD:path
获得。要获得没有索引的基础版本,我们必须 运行 git merge-base HEAD MERGE_HEAD
找到它的哈希 ID(然后假设还有一个合并基础 1), 和 git show <hash>:path
。如果 Git 认为它已正确完成合并,这就是我们必须做的。
另请注意,如果您真的想要——我想这只有在您想要使用您拥有的其他需要它的工具时才是正确的——您可以使用 git update-index
来随机播放索引中的条目,将 hw.hpp
移动到 hw.h
的插槽 3 中,以便它 确实 显示为 "theirs",并以这种方式显示在 git status
。对于这个特定的例子:
$ printf '100644 bbda177a6ecfe285153467ff8fd332de5ecfb2f8 3\thw.h' |
git update-index --index-info
此处的散列来自 git ls-files --stage
,是 hw.hpp
的散列。 (您需要第二个步骤来删除 hw.hpp
索引条目。)
1使用git merge-base --all
找到所有合并碱基。如果不止一个,你可以任意选择一个(这就是-s resolve
所做的),或者尝试将所有合并基地合并成一个虚拟合并基地。要合并两个合并基础,您找到他们自己的合并基础,并使用该合并基础合并两个基础,就好像它们是分支提示一样。根据需要递归和迭代——这就是 Git 对默认 -s recursive
策略所做的——直到你有一个文件的单一合并基础版本。