我移动了一些目录.
当我合并时,有许多冲突的文件,因为其他开发人员已经提交了他们的更改.两者都是合并工具,并git mergetool说该文件已在本地或远程删除.见图.
如何合并这些更改?
你永远不需要担心Git中的"保存历史".Git根本没有文件历史记录,只有提交历史记录.也就是说,每次提交"点"(包含散列ID)其父母或者对于合并,双方的父母,这是历史:提交E被提交之前D,同时提交D由提交之前C,等等上.只要你有提交,你就有了历史.
也就是说,Git 可以尝试使用合成一个特定文件的历史记录git log --follow.您指定了一个起始提交和一个路径名,并且Git检查commit-by-commit,以查看在将当前提交的父项与当前提交进行比较时是否重命名了该文件.这使用Git的重命名检测来识别a/b.txt提交L(左)中的文件c/d.txt与提交R(右)中的文件是"相同的文件" .
重命名检测有很多繁琐的旋钮,但在基本级别,它基本上是这样的:
a/b.txt已经消失并且c/d.txt是全新的,为什么,这是检测到的重命名的候选者.不成对的文件进入配对队列(一个用于L,一个用于R),Git哈希所有文件的内容.它已经具有内部Git哈希,因此它首先直接比较所有这些哈希值.如果文件完全不变,则它在L和R中具有相同的Git哈希ID(但名称不同),并且可以立即配对并从配对队列中删除.
现在,精确匹配被取出,Git尝试了漫长的缓慢.它需要一个未配对的L文件,并为每个R文件计算"相似性索引" .如果某个R文件足够相似 - 或者几个 - 它需要"最相似"的R文件并将其与L文件配对.如果没有足够相似的文件,则L文件保持未配对(从队列中取出)并被视为"从L中删除".最终,在未配对的L队列中没有文件,并且在未配对的R队列中保留了任何文件,这些文件被"添加"(R中的新文件).同时,所有配对文件都已重命名.
这意味着:当compare(git diff)将L提交到R时,如果两个文件足够相似,它们将被配对为重命名. 默认的相似性指数是50%,因此文件需要50%匹配(无论这意味着 - 相似性指数计算有些不透明),但Git 的完全匹配更容易,更快.
请注意,git log --follow启用重命名检测(仅在一个目标R文件上,因为我们正在通过日志向后工作,将父提交与仅在我们知道的子文件中的一个文件进行比较).自Git 2.9版以来git diff,git log -p现在都有自动重命名检测功能.在旧版本中,您必须使用该-M选项设置相似性阈值,或配置diff.renames为true,以获取git diff和git log -p重命名检测.
配对队列的最大长度也是如此.这已经翻了两倍,一次是在Git 1.5.6中,一次是在Git 1.7.5中.您可以自己控制它:它可以配置为diff.renameLimit和merge.renameLimit.当前的限制是400和1000.(如果你将它们设置为零,Git使用它自己的内部最大值,这可以扼杀大量的CPU时间 - 这就是为什么这两个限制首先存在的原因.如果你设置diff.renameLimit但不是merge.renameLimit,git merge使用您的差异设置.)
这导致了一个经验法则适用于git log --follow:如果可能,当您打算重命名某个文件或文件集时,请自行提交重命名步骤,而不更改任何文件内容. 如果可能,请保持重命名文件的数量相当小:例如,等于或低于400.您可以在多个步骤中提交更多重命名,一次400个.但是请记住,你正在git log --follow通过无意义的提交来克服你的历史记录的能力和速度:如果你需要重命名50000个文件,也许你应该这样做.
但这如何影响合并呢?好吧,git merge就像git log --follow,总是打开重命名检测.但哪个提交是L,哪个提交或提交是R?
每当你跑:
git merge <commit-specifier>
Run Code Online (Sandbox Code Playgroud)
Git必须找到当前(HEAD)提交和指定的其他提交之间的合并基础.(通常这只是git merge <branchname>.通过将分支名称解析为它所指向的提交来选择该另一个分支的提示提交.通过Git中"分支名称"的定义,这是该分支的提示提交,以便这样"只是工作".但是你可以通过哈希ID 指定任何提交,例如.)让我们调用这个合并基础提交B(对于base).我们已经知道我们自己的提交HEAD,虽然有些事情称之为"本地".让我们调用另一个提交O(用于其他),虽然有些东西称之为"远程"(这很愚蠢:Git中没有任何东西是远程的!).
然后,Git实际上是两个 git diff.一个比较B与HEAD,因此对于这个特定的差异,L是B而R是HEAD.根据我们上面看到的规则,Git将检测或未能检测到重命名.然后Git做另一个git diff,将B与O进行比较.Git将再次根据相同的规则检测或未能检测到重命名.
如果某个文件在B -vs-HEAD中重命名,Git会照常传播其内容.如果某个文件在B -vs- O中重命名,Git会照常传播其内容.如果在HEAD和O中将单个B文件F重命名为两个不同的名称,Git会在该文件上声明重命名/重命名冲突,并在工作树中保留这两个名称以供您清理.如果它只在一个差异中重命名- 它仍然在HEAD或O中被称为F,那么Git使用重命名的任何一方的新名称将文件存储在工作树中.在任何情况下,Git 都会像往常一样尝试组合两组变化(来自B -vs-HEAD和B -vs - O).1
当然,要让Git 检测重命名,文件的内容必须与往常一样.这对于Java文件(有时也是Python)来说尤其成问题,其中文件名嵌入在import语句中.如果一个模块主要由import语句组成,只有几行代码,重命名引起的更改将覆盖剩余的文件内容,文件甚至不会达到50%匹配.
有一个解决方案,虽然它有点难看.与拇指的规则git log --follow,我们可以承诺只是第一重命名,然后提交内容改变"修复所有进口"作为一个单独提交.然后,当我们合并时,我们可以做两个甚至三个合并:
git checkout ... # whatever branch we plan to merge into
git merge <hash> # merge with everything just before the Great Renaming
Run Code Online (Sandbox Code Playgroud)
由于没有重命名文件,因此这种合并也会像往常一样顺利或差劲.这是结果,以图表形式.请注意,我们提供给git merge命令的哈希值是commit的哈希值A,就在R此之前所有的重命名:
...--*--o--...--o--M <-- mainline
\ /
o--o--...-A--R--...--o <-- develop, with renames at R
Run Code Online (Sandbox Code Playgroud)
然后:
git merge <hash of R>
Run Code Online (Sandbox Code Playgroud)
由于每个文件的内容完全相同,名称相同,跨越另一个R提交 - 合并基础是提交A- 此处的效果仅仅是获取所有重命名.我们保留HEAD提交的文件内容M,但是来自的名称R.此合并应自动成功:
...--*--o--...--o--M--N <-- mainline
\ / /
o--o--...-A--R--...--o <-- develop, with renames at R
Run Code Online (Sandbox Code Playgroud)
现在我们可以git merge develop继续合并开发分支.
在许多情况下,我们不需要进行合并M,但如果我们需要N为所有重命名进行合并,那么无论如何都可能不是一个坏主意.其原因是,承诺R是没有的功能:它有进口错误的名称.R在二分期间必须跳过提交.这意味着合并N同样不起作用,必须在二分期间跳过.有M礼物可能会很好,因为M实际上可以奏效.
请注意,如果您执行此操作,则会扭曲/扭曲源代码,只是为了取悦您的版本控制系统.这不是一个好的情况.这可能是不太坏的比你的其他选择,但不要告诉自己这是好的.
1当存在重命名/重命名冲突时,我仍然需要查看文件的两个副本会发生什么.由于Git 在工作树中留下了这两个名称,两个名称是否包含相同的合并内容,以及任何冲突标记(如果需要)?也就是说,如果该文件被命名base.txt,现在命名为head.txt和other.txt,做的工作树版本head.txt和other.txt始终一致?
| 归档时间: |
|
| 查看次数: |
1785 次 |
| 最近记录: |