当重命名文件并使用旧名称创建新文件时,什么工作流程可以缓解 git 合并冲突?
What workflow can mitigate git merge conflicts when a file is renamed and a new file is created with the old name?
问题
- 我有根分支
master
和 feature
从主分支。
master
有文件 A
feature
已将文件 A
移动到 B
并创建了一个新文件 A
master
中针对文件 A
的工作需要合并到 feature
分支文件 B
- 将
master
合并到 feature
会导致合并冲突。 master
文件 A
尝试合并到 feature
文件 A
,即使 master
A
和 feature
A
不再相关。
我如何告诉 git 将 master
A
合并到 feature
B
?
重现步数
在控制台中:
mkdir git_rename_demo
cd git_rename_demo
git init
echo "LineB1\nLineB2\nLineB3\nLineB4" > A.txt
git add A.txt
git commit -m "Add A"
git checkout -b rename_A_to_B
git mv A.txt B.txt
echo "LineA1\nLineA2\nLineA3" > A.txt
git add A.txt
git commit -m "Moved Old A to B and Added New A"
git checkout master
echo "LineB5\nLineB5" >> A.txt
git add A.txt
git commit -m "Added More LineBs to A"
git checkout rename_A_to_B
git merge master
场景
我有一个 master
分支和一个 feature
分支。 master
上的文件 A
包含执行 "A" 相关逻辑的代码。
在feature
分支上,发现文件A
作为文件名没有意义,因为代码与"B"逻辑更相关。同时,编写了与 feature
分支中的 "A" 逻辑相关的新代码。为解决此问题,文件 A
已重命名为文件 B
,这实际上意味着文件 A
中所有与原始 "B" 相关的逻辑已移至新文件 B
。所有新的 "A" 相关逻辑都添加到一个名为 A
的新文件中,有效地替换了旧的 A
文件。
在 master
分支上继续工作,向仍然存在的 A
文件添加更多 "B" 逻辑。这是 feature
分支上的文件,已重命名为 B
.
master
的工作需要合并到 feature
的时候到了,因为功能将继续与 master
分开开发,直到以后。将 master
合并到 feature
会导致上述冲突。我们需要继续允许从事 master
的开发人员针对 A
进行 "B" 相关工作,并能够将 A
文件工作合并到 feature
分支 B
文件,而不必每次都手动解决冲突。
我不确定您是否可以为此找到自动化解决方案。
但是您可以从 git diff 创建补丁并手动更新 git apply 在功能分支中。
TL;DR
没有很好的方法来处理这个问题,但您可以手动完成。见下文。
长
不要太担心 b运行ch 名称。不要担心提交;合并基于提交,而不是 b运行ch 名称。 B运行ch names 只是帮助你,Git, find 特定的提交。根据定义,b运行ch 名称始终包含 b运行ch 中 last 提交的原始提交哈希 ID。添加新提交包括:
通过名称检查 b运行ch,以便 Git 知道在步骤 3 中更新哪个 b运行ch 名称。这使提示b运行ch 的提交是 current 提交。特殊名称 HEAD
现在附加到 b运行ch 名称,因此,HEAD
select 是 current 提交,这是 b运行ch.
中的最后一次(或 tip)提交
以通常的方式进行新的提交。 Git 将创建提交,将其父项设置为当前提交。这个新提交将获得自己唯一的哈希 ID,不同于之前的每次提交,也不同于以后的每次提交。
此时,在进行了新提交后,Git 将 new 提交的哈希 ID 写入 b运行ch 名称: 附有 HEAD
的那个。所以现在 b运行ch 名称指向 b运行ch 中的 last(提示)提交。那个新的提交指向过去的提示; b运行ch 现在是一个更长的提交时间。
无论何时进行真正的合并(有一些伪合并),都会涉及三个提交:
- 合并基础,这是其他两个提交的最佳共同祖先;
- 你的当前提交(或
HEAD
),通常是b运行ch;1的提示和
- 在你的命令行中提交你 select:通常,另一个 b运行ch.
Git 自行查找合并基础提交。您只需命名另一个提交即可。如果您想提前查看哪个提交是合并基础,您可以 运行:
git merge-base --all other
其中 other
是您计划提供给 git merge
命令的任何内容,Git 从中选择第三次提交。
1另一种选择是你可能处于detached HEAD状态,其中特殊名称HEAD
只是包含提交本身的原始哈希 ID。无论哪种方式,HEAD
命名 当前提交 。当您在 b运行ch 上时,它只是 使用 b运行ch 名称。但通常当你 运行 合并时,你在 b运行ch 上。
简述合并的工作原理,这也是您的问题所在
想象一个简化的提交时间轴,后面的提交向右,我们用单个大写字母替换提交哈希,以便更容易讨论它们。我们可能有:
I--J <-- branch1 (HEAD)
/
...--G--H
\
K--L <-- branch2
此处名称 branch1
找到,或 指向 ,我们当前的提交,其哈希 ID 为 J
。名称 branch2
指向提交 L
。我们想将 b运行ch branch2
合并到 branch1
,这实际上意味着将提交 L
合并到提交 J
以生成新的合并提交 M
. Git 自动 找到最好的 shared 提交,即提交 H
:最后一次提交 both b运行ches.
为了将我们的更改与他们的更改合并,Git 必须弄清楚我们更改了什么,以及他们更改了什么。由于每个提交都包含所有文件的完整快照,这相对容易:Git 将提交 H
中的快照与我们 HEAD
提交 J
中的快照进行比较,以查看我们 发生了什么变化。而且,Git 还将比较 H
和 L
中的快照,以查看 它们 发生了什么变化。
您可以 运行 这两个 git diff
您自己的命令:
git diff --find-renames <hash-of-H> <hash-of-J> # what we changed
git diff --find-renames <hash-of-H> <hash-of-L> # what they changed
这就是您的问题所在: --find-renames
在第一个 git diff
中会 找到该文件合并基础中的 A
,大概在他们的提交中也称为 A
,现在在您的 HEAD 提交中称为 B
。所以 diff 会配对 "A in base, B in ours" 并比较内容。这就是 我们 改变的地方:内容改变了,文件的 name 也改变了。
第二个 git diff
会配对 "A in base, A in theirs" 看看他们改变了什么。当合并到 combine 更改时,一切都会起作用。合并后的更改为:
- 无论我们更改了什么,如果有的话,加上重命名文件;
- 无论他们改变了什么,如果有的话。
Git 会将这些组合更改应用于提交 H
中的快照,这将重命名文件。
但是对于Git首先要找到重命名,Git一定不能配对提交 H
中的文件 A
和 --ours
提交 (J
) 中名为 A
的文件。如果存在名为 A
的文件,Git 会自动假定 它是 "the same" 文件。
当您像这样手动 运行 git diff
时,您可以添加一个额外的选项来告诉 Git 不 假设。当 git merge
运行s git diff
本身时,它从不提供此选项。
请注意,对于所有其他文件,一切正常。假设在文件 F1 中,它在所有三个提交中都具有相同的名称,您更改了一些前面的行,并且他们更改了一些后面的行。 Git 将 both 更改应用到 H
的 F1 的副本,然后移动到下一个文件。
不过,在某些时候,Git 可能会命中某些文件——可能是 F2——你和他们在其中更改了 相同 行。 Git 现在会停下来让你收拾残局。
Git 给你留下了 四个 个文件,而不只是一个,里面乱七八糟:
- 在 Git 的 index.
中有来自合并库的文件 F2 的副本
- Git 的索引中有一份来自您提交的文件 F2 的副本。
- 在 Git 的索引中有一份文件 F2 的副本。
- 最后,Git 在工作树中合并这些更改的文件方面做出了最大努力。
工作树文件是您习惯使用的文件。它们就在那里,可供查看、编辑和编译,或者您对文件执行的任何操作。
Git 索引中的副本甚至很难看到,但您可以使用 git show
或 git checkout
将它们全部取出。 git mergetool
命令执行此操作,将名为 F 的文件提取到 F.BASE、F.LOCAL 和 F.REMOTE ... 然后尝试 运行 上的合并工具这三个文件产生合并文件F,然后删除这三个文件。所以 mergetool 几乎可以,但是它做的太多了。
您可以求助于 git show
方法:
git show :1:F > F.LOCAL # :1: means the merge base version
git show :2:F > F.OURS # :2: means our version, like `git checkout --ours`
git show :3:F > F.THEIRS # :3: means their version, like `git checkout --theirs`
但在这种情况下,Git 正在合并 错误的文件 ,所以这并没有直接帮助。不过,知道这一点很重要。
以上是关于哪里出了问题。这是您可以做的
你运行:
git merge other
Git 找到了三个提交:基础、HEAD
和其他。它 运行 两个 git diff
,从基地到 HEAD
看你做了什么,从基地到其他人看他们做了什么。
这两个差异中的一个应该找到重命名,但没有。然后它尝试将来自基础的 A 与来自本地/--ours
提交的 A 和来自其他/--theirs
/远程提交的 A 合并。
您可以提取 A 的三个版本,然后将它们合并 "by hand",有点像 git mergetool
所做的。但这里真正的技巧是 根本不需要 A.LOCAL,你想要 B.LOCAL 或 B.OURS 取决于你喜欢怎么称呼它.
您已经 B.LOCAL。它只是被称为B
。所以你想要的是:
- 提取文件
A
的合并基础版本:使用 git show :1:A > A.base
- 提取他们的文件版本
A
:使用git show :3:A > A.other
- 合并这三个文件,将合并结果写入文件
B
.
好的,前两个要点很简单。但是现在我们还是要合并三个文件。好吧,如果你有喜欢的合并工具,你可以直接使用它!如果没有,我们可以让 Git 执行它,与 Git 自动执行的方式相同,但我们控制输入文件。我们只要使用命令:
git merge-file B A.base A.other
将我们现有的 B
与他们的 A
和 A.other
合并。像往常一样可能存在合并冲突;它们现在将以通常的方式修复。
当然 Git 弄乱了我们的文件 A
,但我们也可以解决这个问题:
git checkout --ours A
从索引中提取 :2:A
到工作树中,然后是:
git add A
删除三个 :1:
、:2:
和 :3:
插槽条目,并将 A
的签出索引版本 2 作为到-提交文件 A
.
如果您必须经常这样做,您可以编写其中的一部分(除了解决任何实际冲突)。
问题
- 我有根分支
master
和feature
从主分支。 master
有文件A
feature
已将文件A
移动到B
并创建了一个新文件A
master
中针对文件A
的工作需要合并到feature
分支文件B
- 将
master
合并到feature
会导致合并冲突。master
文件A
尝试合并到feature
文件A
,即使master
A
和feature
A
不再相关。 我如何告诉 git 将master
A
合并到feature
B
?
重现步数
在控制台中:
mkdir git_rename_demo
cd git_rename_demo
git init
echo "LineB1\nLineB2\nLineB3\nLineB4" > A.txt
git add A.txt
git commit -m "Add A"
git checkout -b rename_A_to_B
git mv A.txt B.txt
echo "LineA1\nLineA2\nLineA3" > A.txt
git add A.txt
git commit -m "Moved Old A to B and Added New A"
git checkout master
echo "LineB5\nLineB5" >> A.txt
git add A.txt
git commit -m "Added More LineBs to A"
git checkout rename_A_to_B
git merge master
场景
我有一个 master
分支和一个 feature
分支。 master
上的文件 A
包含执行 "A" 相关逻辑的代码。
在feature
分支上,发现文件A
作为文件名没有意义,因为代码与"B"逻辑更相关。同时,编写了与 feature
分支中的 "A" 逻辑相关的新代码。为解决此问题,文件 A
已重命名为文件 B
,这实际上意味着文件 A
中所有与原始 "B" 相关的逻辑已移至新文件 B
。所有新的 "A" 相关逻辑都添加到一个名为 A
的新文件中,有效地替换了旧的 A
文件。
在 master
分支上继续工作,向仍然存在的 A
文件添加更多 "B" 逻辑。这是 feature
分支上的文件,已重命名为 B
.
master
的工作需要合并到 feature
的时候到了,因为功能将继续与 master
分开开发,直到以后。将 master
合并到 feature
会导致上述冲突。我们需要继续允许从事 master
的开发人员针对 A
进行 "B" 相关工作,并能够将 A
文件工作合并到 feature
分支 B
文件,而不必每次都手动解决冲突。
我不确定您是否可以为此找到自动化解决方案。 但是您可以从 git diff 创建补丁并手动更新 git apply 在功能分支中。
TL;DR
没有很好的方法来处理这个问题,但您可以手动完成。见下文。
长
不要太担心 b运行ch 名称。不要担心提交;合并基于提交,而不是 b运行ch 名称。 B运行ch names 只是帮助你,Git, find 特定的提交。根据定义,b运行ch 名称始终包含 b运行ch 中 last 提交的原始提交哈希 ID。添加新提交包括:
通过名称检查 b运行ch,以便 Git 知道在步骤 3 中更新哪个 b运行ch 名称。这使提示b运行ch 的提交是 current 提交。特殊名称
HEAD
现在附加到 b运行ch 名称,因此,HEAD
select 是 current 提交,这是 b运行ch. 中的最后一次(或 tip)提交
以通常的方式进行新的提交。 Git 将创建提交,将其父项设置为当前提交。这个新提交将获得自己唯一的哈希 ID,不同于之前的每次提交,也不同于以后的每次提交。
此时,在进行了新提交后,Git 将 new 提交的哈希 ID 写入 b运行ch 名称: 附有
HEAD
的那个。所以现在 b运行ch 名称指向 b运行ch 中的 last(提示)提交。那个新的提交指向过去的提示; b运行ch 现在是一个更长的提交时间。
无论何时进行真正的合并(有一些伪合并),都会涉及三个提交:
- 合并基础,这是其他两个提交的最佳共同祖先;
- 你的当前提交(或
HEAD
),通常是b运行ch;1的提示和 - 在你的命令行中提交你 select:通常,另一个 b运行ch.
Git 自行查找合并基础提交。您只需命名另一个提交即可。如果您想提前查看哪个提交是合并基础,您可以 运行:
git merge-base --all other
其中 other
是您计划提供给 git merge
命令的任何内容,Git 从中选择第三次提交。
1另一种选择是你可能处于detached HEAD状态,其中特殊名称HEAD
只是包含提交本身的原始哈希 ID。无论哪种方式,HEAD
命名 当前提交 。当您在 b运行ch 上时,它只是 使用 b运行ch 名称。但通常当你 运行 合并时,你在 b运行ch 上。
简述合并的工作原理,这也是您的问题所在
想象一个简化的提交时间轴,后面的提交向右,我们用单个大写字母替换提交哈希,以便更容易讨论它们。我们可能有:
I--J <-- branch1 (HEAD)
/
...--G--H
\
K--L <-- branch2
此处名称 branch1
找到,或 指向 ,我们当前的提交,其哈希 ID 为 J
。名称 branch2
指向提交 L
。我们想将 b运行ch branch2
合并到 branch1
,这实际上意味着将提交 L
合并到提交 J
以生成新的合并提交 M
. Git 自动 找到最好的 shared 提交,即提交 H
:最后一次提交 both b运行ches.
为了将我们的更改与他们的更改合并,Git 必须弄清楚我们更改了什么,以及他们更改了什么。由于每个提交都包含所有文件的完整快照,这相对容易:Git 将提交 H
中的快照与我们 HEAD
提交 J
中的快照进行比较,以查看我们 发生了什么变化。而且,Git 还将比较 H
和 L
中的快照,以查看 它们 发生了什么变化。
您可以 运行 这两个 git diff
您自己的命令:
git diff --find-renames <hash-of-H> <hash-of-J> # what we changed
git diff --find-renames <hash-of-H> <hash-of-L> # what they changed
这就是您的问题所在: --find-renames
在第一个 git diff
中会 找到该文件合并基础中的 A
,大概在他们的提交中也称为 A
,现在在您的 HEAD 提交中称为 B
。所以 diff 会配对 "A in base, B in ours" 并比较内容。这就是 我们 改变的地方:内容改变了,文件的 name 也改变了。
第二个 git diff
会配对 "A in base, A in theirs" 看看他们改变了什么。当合并到 combine 更改时,一切都会起作用。合并后的更改为:
- 无论我们更改了什么,如果有的话,加上重命名文件;
- 无论他们改变了什么,如果有的话。
Git 会将这些组合更改应用于提交 H
中的快照,这将重命名文件。
但是对于Git首先要找到重命名,Git一定不能配对提交 H
中的文件 A
和 --ours
提交 (J
) 中名为 A
的文件。如果存在名为 A
的文件,Git 会自动假定 它是 "the same" 文件。
当您像这样手动 运行 git diff
时,您可以添加一个额外的选项来告诉 Git 不 假设。当 git merge
运行s git diff
本身时,它从不提供此选项。
请注意,对于所有其他文件,一切正常。假设在文件 F1 中,它在所有三个提交中都具有相同的名称,您更改了一些前面的行,并且他们更改了一些后面的行。 Git 将 both 更改应用到 H
的 F1 的副本,然后移动到下一个文件。
不过,在某些时候,Git 可能会命中某些文件——可能是 F2——你和他们在其中更改了 相同 行。 Git 现在会停下来让你收拾残局。
Git 给你留下了 四个 个文件,而不只是一个,里面乱七八糟:
- 在 Git 的 index. 中有来自合并库的文件 F2 的副本
- Git 的索引中有一份来自您提交的文件 F2 的副本。
- 在 Git 的索引中有一份文件 F2 的副本。
- 最后,Git 在工作树中合并这些更改的文件方面做出了最大努力。
工作树文件是您习惯使用的文件。它们就在那里,可供查看、编辑和编译,或者您对文件执行的任何操作。
Git 索引中的副本甚至很难看到,但您可以使用 git show
或 git checkout
将它们全部取出。 git mergetool
命令执行此操作,将名为 F 的文件提取到 F.BASE、F.LOCAL 和 F.REMOTE ... 然后尝试 运行 上的合并工具这三个文件产生合并文件F,然后删除这三个文件。所以 mergetool 几乎可以,但是它做的太多了。
您可以求助于 git show
方法:
git show :1:F > F.LOCAL # :1: means the merge base version
git show :2:F > F.OURS # :2: means our version, like `git checkout --ours`
git show :3:F > F.THEIRS # :3: means their version, like `git checkout --theirs`
但在这种情况下,Git 正在合并 错误的文件 ,所以这并没有直接帮助。不过,知道这一点很重要。
以上是关于哪里出了问题。这是您可以做的
你运行:
git merge other
Git 找到了三个提交:基础、HEAD
和其他。它 运行 两个 git diff
,从基地到 HEAD
看你做了什么,从基地到其他人看他们做了什么。
这两个差异中的一个应该找到重命名,但没有。然后它尝试将来自基础的 A 与来自本地/--ours
提交的 A 和来自其他/--theirs
/远程提交的 A 合并。
您可以提取 A 的三个版本,然后将它们合并 "by hand",有点像 git mergetool
所做的。但这里真正的技巧是 根本不需要 A.LOCAL,你想要 B.LOCAL 或 B.OURS 取决于你喜欢怎么称呼它.
您已经 B.LOCAL。它只是被称为B
。所以你想要的是:
- 提取文件
A
的合并基础版本:使用git show :1:A > A.base
- 提取他们的文件版本
A
:使用git show :3:A > A.other
- 合并这三个文件,将合并结果写入文件
B
.
好的,前两个要点很简单。但是现在我们还是要合并三个文件。好吧,如果你有喜欢的合并工具,你可以直接使用它!如果没有,我们可以让 Git 执行它,与 Git 自动执行的方式相同,但我们控制输入文件。我们只要使用命令:
git merge-file B A.base A.other
将我们现有的 B
与他们的 A
和 A.other
合并。像往常一样可能存在合并冲突;它们现在将以通常的方式修复。
当然 Git 弄乱了我们的文件 A
,但我们也可以解决这个问题:
git checkout --ours A
从索引中提取 :2:A
到工作树中,然后是:
git add A
删除三个 :1:
、:2:
和 :3:
插槽条目,并将 A
的签出索引版本 2 作为到-提交文件 A
.
如果您必须经常这样做,您可以编写其中的一部分(除了解决任何实际冲突)。