Git merge: 对移动到不同文件的代码应用更改

我现在正在尝试一个相当强壮的 Git 合并策略。我遇到的一个问题是,我对分支中的一些代码进行了一些更改,但是我的同事将这些代码移动到了分支中的一个新文件中。因此,当我使用 git merge my_branch his_branch时,git 没有注意到新文件中的代码与旧文件中的代码相同,因此没有任何更改。

将我的更改再次应用到新文件中的代码的最简单的方法是什么。我不会有太多的问题找出哪些提交需要重新应用(我可以只使用 git log --stat)。但据我所知,没有办法让 git 将更改重新应用到新文件中。我现在看到的最简单的事情是手动重新应用更改,这看起来不像是一个好主意。

我知道 git 识别的是 blobs,而不是文件,所以肯定有办法告诉它,“从这个提交中应用这个确切的代码更改,除了它在这个新文件中的位置,而不是它之前的位置”。

62057 次浏览

您总是可以使用 git diff(或 git format-patch)来生成补丁,然后手动编辑补丁中的文件名,并使用 git apply(或 git am)应用它。

除此之外,它能够自动工作的唯一方法就是 git 的重命名检测能够识别出旧文件和新文件是相同的——这听起来好像它们并不在你的案例中,只是其中的一部分。的确,git 使用 blob,而不是文件,但是 blob 只是整个文件的内容,没有附加文件名和元数据。因此,如果有一块代码在两个文件之间移动,它们实际上并不是同一个 blob —— blob 的其余部分的内容是不同的,只是相同的块。

我也遇到过类似的问题,我通过重新调整工作基础以匹配目标文件组织来解决这个问题。这是因为 git 跟踪文件 内容,所以通过在重命名的基础上重新命名,可以根据需要应用更改。

更准确地说,假设您在分支(local分支)上修改了 original.txt,但是在主分支上,original.txt被复制到另一个分支(比如 copy.txt)。 此副本已在我们命名为提交 CP的提交中完成。

您希望将所有本地更改应用于新文件 copy.txt,提交以下在 original.txt上进行的 AB

 ---- X -----CP------ (master)
\
`--A---B--- (local)
 

在使用 git branch move X进行更改的起始点创建一个一次性分支 move。也就是说,将 move分支放在提交 X处,即您希望合并的提交之前的那个分支; 最有可能的情况是,这个分支是您实现更改的分支。正如用户 @ digory doo在下面所写的,您可以执行 git merge-base master local来查找 X

 ---- X (move)-----CP----- (master)
\
`--A---B--- (local)
 

在此分支上,发出以下重命名命令:

git mv original.txt copy.txt

这将重命名文件。请注意,此时在您的树中还不存在 copy.txt
提交您的更改(我们将此提交命名为 MV)。

        ,--MV (move)
/
---- X -----CP----- (master)
\
`--A---B--- (local)
 

你现在可以在 move之上重新定位你的工作:

git rebase move local

这应该没有问题,您的更改将应用于本地分支中的 copy.txt

        ,--MV (move)---A'---B'--- (local)
/
---- X -----CP----- (master)
 

现在,您不一定希望或需要在主分支的历史记录中提交 MV,因为 move 操作可能会导致与主分支中提交 CP时的复制操作发生冲突。

您只需再次调整工作基础,放弃 move 操作,如下所示:

git rebase move local --onto CP

... 其中 CP是在另一个分支中引入 copy.txt的提交。 这将 copy.txt上的所有更改重新建立在 CP提交之上。 现在,您的 local分支就像您总是修改 copy.txt而不是 original.txt一样,您可以继续与其他分支合并。

                ,--A''---B''-- (local)
/
-----X-------CP----- (master)
 

重要的是,这些更改应用于 CP,否则 copy.txt将不存在,并且这些更改将应用于 original.txt

希望你听清楚了。 这个答案来得比较晚,但是对于其他人可能有用。

这里是一个 合并解决方案,遇到合并冲突与重命名和编辑,并解决它与 mergetool 识别正确的3合并源文件。

  • 合并失败后,因为“删除的文件”,你意识到被重命名和编辑:
  1. 你终止合并。
  2. 提交分支上的重命名文件。
  3. 再次合并。

参观:

创建一个 file.txt:

$ git init
Initialized empty Git repository in /tmp/git-rename-and-modify-test/.git/


$ echo "A file." > file.txt
$ git add file.txt
$ git commit -am "file.txt added."
[master (root-commit) 401b10d] file.txt added.
1 file changed, 1 insertion(+)
create mode 100644 file.txt

创建一个分支,以便稍后进行编辑:

$ git branch branch-with-edits
Branch branch-with-edits set up to track local branch master.

在 master 上创建重命名和编辑:

$ git mv file.txt renamed-and-edited.txt
$ echo "edits on master" >> renamed-and-edited.txt
$ git commit -am "file.txt + edits -> renamed-and-edited.txt."
[master def790f] file.txt + edits -> renamed-and-edited.txt.
2 files changed, 2 insertions(+), 1 deletion(-)
delete mode 100644 file.txt
create mode 100644 renamed-and-edited.txt

切换到分支,并在那里进行编辑:

$ git checkout branch-with-edits
Switched to branch 'branch-with-edits'
Your branch is behind 'master' by 1 commit, and can be fast-forwarded.
(use "git pull" to update your local branch)
$
$ echo "edits on branch" >> file.txt
$ git commit -am "file.txt edited on branch."
[branch-with-edits 2c4760e] file.txt edited on branch.
1 file changed, 1 insertion(+)

尝试合并主机:

$ git merge master
CONFLICT (modify/delete): file.txt deleted in master and modified in HEAD. Version HEAD of file.txt left in tree.
Automatic merge failed; fix conflicts and then commit the result.

注意,这个冲突很难解决,而且文件被重命名了:

$ git merge --abort
$ git mv file.txt renamed-and-edited.txt
$ git commit -am "Preparing for merge; Human noticed renames files were edited."
[branch-with-edits ca506da] Preparing for merge; Human noticed renames files were edited.
1 file changed, 0 insertions(+), 0 deletions(-)
rename file.txt => renamed-and-edited.txt (100%)

再试一次合并:

$ git merge master
Auto-merging renamed-and-edited.txt
CONFLICT (add/add): Merge conflict in renamed-and-edited.txt
Recorded preimage for 'renamed-and-edited.txt'
Automatic merge failed; fix conflicts and then commit the result.

很好! 合并导致了一个可以用 mergetool 解决的“正常”冲突:

$ git mergetool
Merging:
renamed-and-edited.txt


Normal merge conflict for 'renamed-and-edited.txt':
{local}: created file
{remote}: created file
$ git commit
Recorded resolution for 'renamed-and-edited.txt'.
[branch-with-edits 2264483] Merge branch 'master' into branch-with-edits

我对这个问题的快速解决方案(在我的案例中,它不是一个单一的文件,而是一个完整的目录结构)是:

  • 将“ my _ Branch”中的文件移动到位于“ his _ Branch”中的位置(git rm/git add)
  • git commit -m "moved to original location for merging"
  • git merge his_branch(这次没有冲突!)
  • 将文件移动到我想要的位置(gitrm/gitadd)
  • git commit -m "moved back to final location after merge"

在历史记录中,您将有两个额外的提交。

但是由于 git 跟踪文件的移动,git blamegit log等仍将处理这些文件,因为移动文件的提交没有改变它们。所以我没有看到这个方法的任何缺点,而且它非常容易理解。