使Git跨文件移动/重命名应用合并

使Git跨文件移动/重命名应用合并,git,git-merge,Git,Git Merge,我肯定我在这里做错了什么,但我不确定是什么 我有一个主机和一个分支,这两个分支最终会合并回来,但目前这两个分支的开发都在进行中 这意味着我会定期将master中的最新更改合并到branch中 问题是在分支中包含大量文件移动和重命名 我目前的程序是: 在分支中 将my control.html重命名为my control.js 阶段更改并提交-Git发现这是一个移动,而不是删除+添加 更新my control.js 提交my control.js更改 my control.js现在有了来自my

我肯定我在这里做错了什么,但我不确定是什么

我有一个
主机
和一个
分支
,这两个分支最终会合并回来,但目前这两个分支的开发都在进行中

这意味着我会定期将
master
中的最新更改合并到
branch

问题是在
分支中
包含大量文件移动和重命名

我目前的程序是:

  • 分支中
    • my control.html
      重命名为
      my control.js
    • 阶段更改并提交-Git发现这是一个
      移动
      ,而不是
      删除+添加
    • 更新
      my control.js
    • 提交
      my control.js
      更改
    • my control.js
      现在有了来自
      my control.html
  • master中
    • 更改
      my control.html
    • 提交更改
  • 返回分支机构
    • 合并来自
      master的更改
这就是问题发生的地方——有时我会得到我期望的对
my control.js
的更改,但大约有一半的时间我只是在
branch
中得到
my control.html

发生这种情况时,
my control.js
具有所有历史记录,
my control.html
具有所有历史记录,以及来自
master
的1或2次提交

  • 我做错了什么
  • 为什么这有时会发生,有时会起作用
  • 我能做些什么来修复它
  • 有没有办法告诉Git“不,这些更改应该应用于此文件”
背景:文件标识 这实际上都归结为我所说的文件标识,这是一个困难的问题,不仅在Git中,而且总体上是困难的:请参阅。然而,Git让它变得特别棘手,因为:

当这种情况发生时,
my control.js
拥有所有历史记录,
my control.html
拥有所有历史记录以及来自master的1或2次提交

Git没有文件历史记录。Git只有提交历史记录。更准确地说,提交是历史记录,文件与此无关。提交包含文件,但不以任何方式控制历史:提交是历史

我有更多关于这方面的信息,例如,我对的回答。如果您要求Git跨重命名跟踪文件,Git将使用其历史简化来仅显示与命名文件接触的提交,当其中一个“接触”是“Git检测到重命名”时,Git将在该点开始查找新名称,并停止查找旧名称。(或者,由于Git正在倒退,最好说它开始寻找旧名称,而不再寻找新名称。)

很明显,这种技术在合并时可能会失败,因为合并的一个分支可能有“错误”的名称。然而,不管怎样,历史简化通常只会导致合并的一个阶段

如果不使用
--follow
,而是使用
git log--path
或等效工具,git根本不需要检测重命名:它只是使用给定的路径简化历史

有点牵强的类比 我做错了什么

什么都没有,也许什么都没有。问题是Git有时可以,有时不能,在某一点上识别名为Bob的文件,在另一点上识别名为Robert的文件引用同一个人。它可以或不能正确识别文件对。鲍勃和罗伯特是同一个人吗

为什么这有时会发生,有时会起作用

这至少有一个可靠的答案:如果两个文件足够相似,Git可以识别它们,其他条件也适用。也就是说,您向Git显示两个快照,其中包含一些文件(“人”),并让Git猜测谁是谁以及谁移动了。如果在前一张照片中只有一个文件上有“鲍勃”的标签,在后一张照片中有一个文件上有“罗伯特”的标签,Git可能能够检测到他们是同一个人,只要他没有失去一条腿或多出一个头或诸如此类的东西。然而,如果两张照片上都有人戴着“鲍勃”和“罗伯特”的名字标签,Git会假设两个“鲍勃”是同一个人,两个“罗伯特”是同一个人,而且越早的鲍勃永远不是越晚的罗伯特,反之亦然

技术:
git merge
、提交图和
git diff--find重命名
让我们看看git merge的实际工作原理。要做到这一点,我们必须从两件事开始:提交图和
git diff--find renames

提交图是合并的关键。每个提交记录其父提交的原始散列ID(如果是普通提交,或者如果是合并提交,则记录其父提交的两个(或所有1个)散列ID)。通常,合并只有两个父级。让我们画一个提交图作为示例,并挑选几个具体的提交进行讨论。与其使用完整、大而难看的哈希ID,不如使用大写字母来指定特定的提交(圆点表示不太有趣的提交)。我们将有分支
branch
main
,它们在提交
B
时分开,但过去至少合并过一次:

          o--o---D--o--o--E   <-- branch
         /        \
...--o--B--o----C--M--o--o--F   <-- main
Git然后将这两组更改组合在一起,将组合的更改应用于保存在
B
中的快照,并执行结果合并提交
M

因为
M
是一个合并提交,所以它同时记住
C
D
。记住,当Git遍历由提交组成的历史时,无论何时从
M
向后移动,它都必须访问双亲

我们现在将运行
git checkout main;git合并分支
。也就是说,我们将选择commit
F
作为当前提交并询问
git diff --find-renames <hash-of-B> <hash-of-C>   # what we did, on main
git diff --find-renames <hash-of-B> <hash-of-D>   # what they did, on branch
git diff --find-renames <hash-of-D> <hash-of-F>   # what we did on main
git diff --find-renames <hash-of-D> <hash-of-E>   # what they did on branch
git diff -M10
git show $hash:$basepath > file.base
git show HEAD:file > file.ours
git show MERGE_HEAD:$theirpath > file.theirs