【问题标题】:git merge: apply changes to code that moved to a different filegit merge:对移动到不同文件的代码应用更改
【发布时间】:2010-08-16 07:19:27
【问题描述】:

我现在正在尝试一个非常强大的 git merge 操作。我遇到的一个问题是我对分支中的某些代码进行了一些更改,但是我的同事将该代码移到了他分支中的一个新文件中。所以当我做git merge my_branch his_branch 时,git 并没有注意到新文件中的代码与旧文件中的代码相同,因此我的任何更改都不存在。

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

我知道 git 识别 blob,而不是文件,所以肯定有一种方法可以告诉它,“应用此提交中的确切代码更改,但不是它在哪里,而是它现在在这个新文件中的哪里”。

【问题讨论】:

  • 不完全一样,但这里有一个类似的问题,可能适用的好回答者:stackoverflow.com/questions/2701790/…
  • 没有一个答案描述 为什么 git 不能自动执行这样的合并。我认为它应该足够聪明,可以检测重命名并自动执行适当的合并?
  • 也许在问这个问题时这不是真的,但现代版本的 git(我使用的是 1.9.5)可以合并重命名和移动文件之间的更改。甚至还有一个 --rename-threshold 选项来调整所需的相似度。
  • @ToddOwen 如果您正在从上游分支进行香草变基,这将起作用,但如果您正在挑选或向后移植一系列可能的更改,您仍然会遇到麻烦不包括重命名文件的提交。
  • 如果你先做一个变基,这是否是个问题?

标签: git


【解决方案1】:

我遇到了类似的问题,我通过重新调整我的工作以匹配目标文件组织来解决它。这是可行的,因为 git 会跟踪文件 content,因此通过在重命名之上重新定位,可以根据需要应用更改。

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

您希望将在original.txt 上所做的所有本地更改、提交AB 应用到新文件copy.txt

 ---- 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,因为移动操作可能会导致与在主分支中提交 CP 的复制操作发生冲突。

你只需要再次变基你的工作,放弃移动操作,如下:

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

希望这很清楚。 这个答案来晚了,但这可能对其他人有用。

【讨论】:

  • 这是很多工作,但我认为它应该在原则上工作。不过,我认为合并比变基更好。
  • 这个解决方案只涉及基本的 git 命令,不像编辑补丁或使用patch(这也可能涉及大量工作),这就是为什么我认为展示它可能会很有趣。另外,请注意,就我而言,我花了更多时间来写答案而不是实际应用更改。您建议在哪一步使用合并?为什么?我看到的唯一区别是,通过变基,我做了一个临时提交,后来被丢弃(提交MV),这仅通过合并是不可能的。
  • 使用 rebase,你有更高的机会处理合并冲突,因为你处理每个提交,而使用合并你一次处理所有事情,这意味着 rebase 可能发生的一些变化合并将不存在。我要做的是合并,然后手动移动合并的文件。不过,也许我误解了你的答案。
  • 我添加了一些 ASCII 树来阐明这种方法。在这种情况下考虑合并与变基:我想做的是在我的分支中对“original.txt”进行所有更改并将它们应用于主分支中的“copy.txt”,因为出于某种原因,“original.txt”。 txt' 在某个时候被复制(而不是移动)到 'copy.txt' 中。在那个副本之后,'original.txt' 也可能在 master 分支上进化了。如果我直接合并,我对 original.txt 的本地更改将应用​​于 master 分支中修改后的 original.txt,这将很难合并。问候。
  • 不管怎样,我相信这个解决方案会奏效(虽然幸好我现在没有机会尝试它),所以现在我将它标记为答案。跨度>
【解决方案2】:

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

除此之外,它会自动工作的唯一方法是如果 git 的重命名检测可以找出旧文件和新文件是同一件事 - 听起来它们不是你的情况,只是一个块其中。确实,git 使用的是 blob,而不是文件,但 blob 只是整个文件的内容,没有附加文件名和元数据。因此,如果您在两个文件之间移动了一段代码,它们实际上并不是同一个 blob - blob 的其余内容是不同的,只是共同的块。

【讨论】:

  • 嗯,这是迄今为止最好的答案。我不知道如何让git format-patch 为提交工作。如果我这样做git format-patch SHA1,它会为整个历史生成一大堆补丁文件。但我想git show SHA1 > diff.patch 也可以。
  • @asmeurer:使用-1 选项。 format-patch 的正常操作模式是一个修订范围,例如origin/master..master,因此您可以轻松准备补丁系列。
  • 其实还有一个注意事项。 git applygit am 太挑剔了,因为他们想要相同的行号。但我目前正在成功使用 UNIX patch 命令。
  • 谢谢。似乎当一个人处理大量移动(以及在他们的分支上修改)的文件时,这是唯一合理(而且相当快)的方式。
【解决方案3】:

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

  • 由于您意识到已重命名和编辑的“已删除文件”而导致合并失败后:

    1. 您中止了合并。
    2. 在您的分支上提交重命名的文件。
    3. 然后再次合并。

演练:

创建文件.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(+)

尝试合并master:

$ 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

【讨论】:

  • 有趣。下次遇到此问题时,我必须尝试一下。
  • 在该解决方案中,在我看来,第一步,即中止的合并,仅对找出哪些文件被远程重命名和编辑有用。如果你提前知道他们。您可以跳过该步骤,基本上,解决方案只是在本地手动重命名文件,然后像往常一样合并并解决冲突。
  • 谢谢。我的情况是我用 git mv 移动了 B.txt -> C.txtA.txt -> B.txt,而 git 无法自动正确匹配合并冲突(在旧 B.txt 和新 B.txt 之间出现合并冲突)。通过使用这种方法,合并冲突现在在正确的文件之间。
  • 这仅在整个文件被移动时有效,但 git 通常应该自动检测到这种情况。棘手的情况是仅移动文件的一部分。
【解决方案4】:

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

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

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

但由于 git 跟踪文件的移动,git blamegit log 等仍将在这些文件上工作,因为移动文件的提交并没有改变它们。所以我看不出这种方法有什么缺点,而且很容易理解。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-06-08
    • 2013-05-14
    • 2011-06-20
    • 2011-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多