A.txt
和 B.txt
。是否可以将这两个文件合并成第三个文件A+B.txt
,同时删除原始的A.txt
和B.txt
,并提交所有更改,以使历史记录得以保留?也就是说,如果我运行命令git log --follow A+B.txt
,我会知道内容来源于A.txt
和B.txt
文件吗?我尝试过将文件分别放在两个不同的分支中,然后将它们合并成一个新文件(同时删除旧文件),但没有成功。fruits
和veggies
。
The naïve way of combining the files would be to do it in a single commit, but you'll lose line history on one of the files (or both)
You could tweak the
git blame
algorithms with options like-M
and-C
to get it to try harder, but in practice, you don’t often have control over those options (eg. the git blame may be performed on a server)The trick is to use a
merge
with two forked branches
- In one branch, we rename
veggies
toproduce
.- In the other branch, we rename
fruits
toproduce
.
git checkout -b rename-veggies git mv veggies produce git commit -m "rename veggies to produce"
git checkout - git mv fruits produce git commit -m "rename fruits to produce"
Then merge the first into the second
git merge -m "combine fruits and veggies" rename-veggies
This will generate a merge conflict - that's okay - now take the changes from each branch's Produce file and combine into one - here's a simple concatenation (but resolve the merge conflict however you please):
cat "produce~HEAD" "produce~rename-veggies" >produce git add produce git merge --continue
The resulting
produce
file was created by a merge, so git knows to look in both parents of the merge to learn what happened.And that’s where it sees that each parent contributed half of the file, and it also sees that the files in each branch were themselves created via renames of other files, so it can chase the history back into both of the original files.
Each line should be correctly attributed to the person who introduced it in the original file, whether it’s fruits or veggies. People investigating the produce file get a more accurate history of who last touched each line of the file.
For best results, your rename commit should be a pure rename. Resist the temptation to edit the file’s contents at the same time you rename it. A pure rename ensure that git’s rename detection will find the match. If you edit the file in the same commit as the rename, then whether the rename is detected as such will depend on git’s “similar files” heuristic.
查看完整文章以获得完整的逐步分解和更多解释
最初,我认为这可能是使用情况之一,需要使用git merge-file
这样的工具来执行以下操作:
>produce echo #empty
git merge-file fruits produce veggies --union -p > produce
git rm fruits veggies
git add produce
git commit -m "combine fruits and veggies"
git blame -C40
。要在TortoiseGitBlame窗口中实现这一点,可以通过设置“检测移动或复制的行”=“来自修改的文件”来完成。 - jifbgit log --follow
却存在。这是自相矛盾的:如果文件历史记录不存在,那么git log --follow
如何生成文件历史记录呢?(但如果需要通过git blame
获取合并文件的有用合成line历史记录,请参见KyleMit的答案。)git log --follow
是有欺骗性的。它并没有真正找到文件的历史记录。它通过更改正在查找的文件(单个)名称来查找历史记录并构建子历史记录。它逐个提交地查看每个提交,并针对其父提交运行(加速,限制)git diff --find-renames
。如果差异指出父提交中的文件X.txt
被重命名为子提交中的A.txt
,并且您正在运行git log --follow A.txt
,则git log
中的代码现在开始查找X.txt
。
由于没有代码可以同时查找多个文件,因此您无法让这个特定的欺骗方式适应您所需的情况,即从查找一个特定文件转变为查找多个文件。(实际上有两个问题。一个是由于内部实现相当有限,2git log --follow
只能一次查看一个文件。另一个问题是重命名检测不包括“组合检测”:Git将执行复制查找的“分割检测”,启用了--find-copies
和--find-copies-harder
。后者非常计算密集,而且两者在这里都是朝着错误的方向工作,尽管简单地通过反转差异的顺序就可以使它做正确的事情。)
1这意味着--follow
默认情况下根本不查看合并差异。另请参见`git log --follow --graph`跳过提交。
2也称为“廉价的黑客”。
git log
在这里没有什么用处。 但它确实使得合并提交和其后的提交的git blame
更加有用。 blame(或annotate)命令从commit历史中合成了line history,这使它能够更好地完成工作。 - torekRaymond Chen所写并由KyleMit引用的文章是最好的答案。以下是一种解决方案,最终只保留了一半的行历史记录,但我将其保留供参考/教育。
不要合并分支,只需使用cherry-pick
拉取提交即可。这仍会导致冲突需要解决,但结果将是一个单独的提交而没有合并提交,并且未来操作的历史记录更简单(以一个文件的行历史为代价)。
git checkout -b temp
git mv A.txt AB.txt
git commit -am "moving B to AB"
git switch main
git mv B.txt AB.txt
git commit -am "moving A to AB"
git cherry-pick temp
解决冲突
git add AB.txt
git cherry-pick --continue
AB.txt将保留责任历史记录
git blame AB.txt
A.txt
重命名为A+B.txt
,将B.txt
的更改内容加入其中并删除B.txt
,最后提交更改。 - Derek