Git和文件重命名和替换 [英] Git and file renaming and replacing

查看:137
本文介绍了Git和文件重命名和替换的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

使用git重命名通常没有问题,但是我遇到了一个非常困难的问题,试图解决.

I don't generally have a problem with renaming with git, but I've run across a really difficult problem I'm trying to work out.

由于各种原因,我遇到了一个文件dir1/file的情况.由于很久以前的决定,它的位置完全错误,需要将其移至dir2/file.

For various reasons, I have a situation where we have a file dir1/file. Due to some long ago decisions, it's in completely the wrong place and needs to be moved to dir2/file.

但是,有很多代码需要更改,并且由于各种原因,我们必须将文件在新位置和旧位置保留一段时间.

However there's a lot of code that needs to be changed and for various reasons we have to keep the file in the new place and the old place for a while.

因此,自然的方法是这样做:

So, the natural(ish) approach would be to do this:

git mv dir1/file dir2/file
git commit -a

到目前为止一切顺利:

> git diff master --name-status --find-renames
R100 dir1/file dir2/file

那我们就做

ln -s ../dir2/file dir1/file
git commit -a

但这会发生

> git diff master --name-status --find-renames
A    dir2/file
T    dir1/file

如果有人更改了master上的dir1/file并且尝试将其拉出,我会被告知与dir1/file1存在合并冲突,并且dir2/file1保持不变.我从阅读其他帖子时认为git跟踪了内容,但似乎正在跟踪文件名和内容.并且完全缺少内容已移动的事实.

And if anyone changes dir1/file on master and I try to pull it I get told there's a merge conflict with dir1/file1 and dir2/file1 is left unchanged. I thought from reading other posts that git tracked content, but it seems to be tracking filenames as well as content. And completely missing the fact that the content has moved.

那么我到底如何才能知道我实际上已经重命名了一个文件,然后添加了一个刚好与旧文件同名的新文件呢?

So how on earth do I get git to recognise that I have in fact renamed a file and then added a new file which just happens to have the same name as the old one?

注意:我不希望将其作为多次推送来执行.像这样的几个文件会受到影响,有人并行更改其中一个文件的可能性非常高,并且无法保证他们能够进行拉取操作来获得重命名,然后进行拉取操作来获得软链接.

Note: I'd rather not do this as multiple pushes. There's several files like this that are affected and the chances that someone is doing changes to one of them in parallel are quite high and there's no guarantee they will be able to do the pull to get the rename and then the pull to get the soft link.

附加示例.我从python模块__init__.py中删除了一个功能,该功能本来不应该存在的,而__init__.py应该为空.这也没有被视为重命名.即使新文件的内容与原始__init__.py的99%相同,而新__init__.py的内容与旧的内容0%相同.一切正常,直到我添加了具有相同名称的文件.

Addition example. I was removing a function from a python module __init__.py which should never have been in there, the __init__.py should have been empty. This too is not spotted as a rename. Even though the contents of the new file are 99% identical to the original __init__.py and the contents of the new __init__.py are 0% identical to the old contents. Everything is fine till I add a file with the same name.

推荐答案

Git实际上会跟踪内容,而不是(或者我们应该说除"之外)名称. diff出错了,因为git diff(必须)试图映射名称并比较两个单独提交(或一个提交和当前工作目录,或一个提交和当前索引,等等)的内容,但是这些只是对比较两次提交"的主题.)

Git does, in fact, track content rather than—or rather, we should say "in addition to"—names. The diff goes wrong because git diff (necessarily) tries to map names and compare the contents of two separate commits (or one commit and the current working directory, or one commit and the current index, etc., but these are just variations on the theme of "compare two commits").

更具体地说, git diff比较树 1 T1 T2 时,假设默认情况下,重命名的唯一候选人 T1 中存在某些文件名但在 T2 中不存在的文件名,和 T2 中存在其他(不同)文件名,但 T1 中不存在.

More specifically, when git diff compares trees1 T1 and T2, it assumes by default that the only candidates for a rename are those where some file-name exists in T1 but not in T2, and some other (different) file name exists in T2 but not in T1.

因此,当您进行第一次提交时,您有两个提交(我们将其称为A和B),其中有两个树,其中A中的dir1/file1丢失",而dir2/file2出现在B中.这是重命名的候选对象-detection,并且由于文件内容是100%相同的,因此git很容易发现重命名并为您提供R100 diff输出.

Thus, when you make the first commit, you have two commits—let's call these A and B—with two trees where dir1/file1 "goes missing" from A and dir2/file2 appears in B. That's a candidate for rename-detection, and because the file contents are 100% identical, git easily spots the rename and gives you the R100 diff output.

进行第二次提交时,将提交C与第三棵树一起添加.比较B和C可以正常工作:dir2/file都出现在两者中,而新的符号链接dir1/file仅出现在C中,并且这对的diff输出也很好.比较A和C时会出现问题:现在dir1/file1都出现在了两者中,而dir2/file2仅出现在C中,而git diff却没有意识到有重命名的候选对象.

When you make the second commit, you add commit C with a third tree. Comparing B and C works fine: dir2/file appears in both, and the new symlink dir1/file appears only in C, and the diff output from this pair is fine too. The problem comes in when comparing A and C: now dir1/file1 appears in both, while dir2/file2 is only in C, and git diff does not realize that there's a rename candidate.

有一个标志--find-copies-harder(或者您可以多次指定-C),这(并不奇怪)使复制/重命名检测代码的工作更加困难.在这种情况下,git将考虑以下可能性:外观未更改"(两个树中名称相同)的文件可能已被复制或重命名为外观新"的文件(在第二棵树中存在,但不在第一棵树中).默认情况下,此功能未启用,因为完全通用的版本在计算上非常耗时.

There is a flag, --find-copies-harder—or you may specify -C more than once—that (rather unsurprisingly) makes the copy/rename detection code work harder. In this case git will consider the possibility that a file that "appears unchanged" (has the same name in both trees) might have been copied or renamed to another file that "appears new" (exists in second tree but not in first). This is not enabled by default because the fully-general version is extremely computationally-intensive.

不幸的是,当计算git merge的差异集时,无法控制使用的差异选项. merge命令设置一些默认值(-M50%等),并执行多个差异,并且不允许您设置--find-copies-harder.因此,即使此方法适用于手动git diff,也无法解决您的合并冲突.

Unfortunately, there is no way to control the diff options used when computing diff-sets for git merge. The merge command sets some defaults (-M50%, etc.) and does several diffs, and does not let you set --find-copies-harder. So even if this works for a manual git diff, it won't solve your merge conflict.

请注意,进行合并时, 2 git仅计算两组差异:从merge-base 3 到当前的HEAD,以及从合并基础到合并的提交(git合并一个提交,而不是一个分支:当该提交是分支的尖端时,结果合并该分支的事实是一种故意的巧合").因此 可以将重命名作为一次提交,而将符号链接作为第二次提交,但是要使git merge看到"重命名,您还必须执行两个单独的git merge.这不是特别令人愉悦,但是要解决此问题,您必须使git的diff机制更智能,以便至少可以确定文件类型更改会为查找如果找到"重命名提供更大的机会.复制/重命名有点难".

Note that when you do a merge,2 git computes just two sets of diffs: that from the merge-base3 to the current HEAD, and that from the merge-base to the merged-in commit (git merges a commit, not a branch: the fact that the result merges that branch, when that commit is the tip of a branch, is a sort of "intentional coincidence"). So it is possible to make the rename as one commit, and the symlink as a second, but to get git merge to "see" the rename, you must also do two separate git merges. It's not particularly pleasing, but to fix this, you would have to make git's diff machinery smarter, so that it could at least figure out that a file-type-change makes for much greater chance of finding a rename if it "finds copies/renames a bit harder".

(请注意,将其添加到diff机制将同时解决两个问题-git diff看不到重命名,而git merge看不到重命名-全部一次.)

(Note that adding this to the diff machinery would fix both issues—git diff not seeing the rename, and git merge not seeing the rename—all at once.)

1 这里的树"是指完整的文件树,而不是git的tree对象.

1By "trees" here I mean full file trees, rather than git's tree objects.

2 更具体地说,双亲合并就是这种情况.章鱼合并的处理方式不同.我还没有深入研究章鱼合并的内幕,也无法对此多说什么.

2More specifically, this is the case for a two-parent merge. Octopus merges are handled differently. I have not dug into the innards of octopus merges and can't really say anything more about those.

3 如果存在多个合并基础,则合并基础取决于要合并的两个(或多个)提交,并且使用默认(recursive)策略使事情复杂化候选者,git计算一个虚拟合并基础",它不一定与任何实际提交相同.我无法在这里适当地解释这些细节:我知道一般的想法,但不了解git中的细节,无论如何,它很少重要,并且与您的问题没有直接关系.如果您想了解更多信息,请在此处中找到一个相当不错的示例. ,尽管该示例使用了一些类似Clearcase的术语.

3The merge-base depends on the two (or more) commits to be merged, and to complicate things, with the default (recursive) strategy, if there are multiple merge-base candidates, git computes a "virtual merge base", which is not necessarily the same as any actual commit. The details are not something I can explain properly here: I know the general idea but not the specifics within git, and in any case it's rarely important and not directly relevant to your issue. There's a fairly nice example here, if you want to read more, although the example uses some rather Clearcase-like terminology.

这篇关于Git和文件重命名和替换的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆