如何在git中使用“hg mv --after”命令?

3

我在git之外重命名了一些文件并对它们进行了大量修改。 当我运行git diff-index -M --name-status HEAD时,我只看到4个文件被重命名/移动,而实际上应该更多。

我知道可以使用hg mv --after在Mercurial中事后记录移动。 我找不到在git中的等价物。 有吗?


我不明白...你的意思是类似于 git log 这样的东西吗? - Hackerman
1
hg mv --after X Y 允许您告诉Mercurial文件X被重命名为Y,而不是保留X被删除和Y被添加的记录。请注意,即使文件X不再存在于工作目录中,它仍然可以工作。 - mark
2个回答

8
作为Dietrich Epp已经回答的那样,你不仅不必这么做,而且实际上你是不能这样做的。从评论中我看到你仍然在 struggling with this concept。回到"前一步"并谈谈关于版本控制系统(VCS)理论的一般概念可能会有所帮助。
像许多(大多数?)其他VCS一样,Mercurial有一个强烈的文件标识概念。这就是为什么你必须使用,如果你忘记了这个命令,可以使用:Mercurial知道某个路径下的文件是在某个提交中首次引入的,并从此时起,通过记录任何重命名操作来跟踪该文件的标识
其他版本控制系统通过其他机制实现相同的功能,例如将路径名映射到对象标识符(类Unix的“inode号”或ClearCase的“OID”)。文件的历史记录以某种方式附加到此对象ID上。虽然Mercurial不使用数字OID / inode号,但它通过记录重命名(以及复制和删除)的方式跟踪文件,从而达到相同的结果。
Git放弃了这个概念。Linus声明你不需要它,因此他根本不会去做。除了存在于任何提交中或不存在之外,没有任何文件具有任何身份。提交C0中的文件foo与提交Cn(其中n≠ 0)中的文件foo完全无关...除非您(或Git)事后决定它们应该相关。(见下面的附注。) git diff 命令根据文件名相同和/或足够相似来判断两个不同提交中的两个文件是否相关。否则它们是无关的。它会根据您传递给 git diff 的选项,在运行 git diff 时即时决定这一点。因此,如果使用不同的选项运行两次 git diff,将得到不同的答案。文件 f1f2 可能相关(重命名或复制)或相关(f1 删除,f2 创建)。
如果您对版本控制系统非常熟悉,您可能会立即提出反对意见:“这难道不会干扰增量压缩吗?”答案是肯定的,但是 Git 不进行增量压缩。
(等等,什么?)
好吧,让我们稍微调整一下。Git 目前还没有进行增量压缩。
在存储库中的“松散对象”级别上,Git根本不进行增量压缩。它只将文件内容减少为单个哈希值,并声明所有哈希到相同值的文件同一个文件。内容的哈希内容的标识(其名称)。因此,如果提交C0中的f1与提交Cn中的f2在内容上相同,则该内容仅存储一次,作为以其哈希命名的对象(即使n = 0!)。
后来,Git 创建“打包文件”,并确实进行增量压缩,但它这样做是通过挑选其他对象进行分组,以便在其中进行有效的增量压缩。在对象选择方面有很多技巧和魔法,并且 Git 实际上会瞥一眼文件名,但至少原则上,它只是在整个存储库中遍历所有历史记录,并说:“啊,如果我将提交 9999999 和内部树形对象 1234567 以及提交 abcdef2 中的文件 blah 进行增量压缩,我可以得到好的结果,所以我就这么做了!”

附注:文件历史记录

这导致 Git 中的文件实际上没有历史记录。由于不存在真正的文件标识,你拥有提交历史但没有文件历史。Git 会通过将每个提交与某些先前的提交进行比较并有时声明具有不同路径名称的两个文件相关来为你合成历史记录。例如,使用 "git log --follow" 命令就可以做到这一点。但总的来说,这是一件难事,而且 "git log --follow" 的效果相对较差:你只能沿着一条路径名逐个回溯到一系列提交中的一个提交,并尝试发现重命名操作。该代码仅在从新提交到旧提交时有效,因此你无法使用 "git log --follow --reverse where/did/this/get/renamed/to" 命令。也就是说,如果你知道某个路径名下曾经存在某个文件,并想知道它是否仍然存在,Git 并不擅长提供答案。(基本上,你需要使用 "git log --raw" 并搜索原始名称上的 "R" 状态。如果找到了这样的状态,那么可能还需要使用新名称重复操作,直到找到每个重命名操作,从 "我当时知道的文件" 到 "今天似乎与之相关的文件"。)

+1 这是非常详细的回答。不幸的是,只能标记一个作为答案。我希望我知道这样决定背后的理由。表面上看起来很奇怪。 - mark
这很不寻常,至少在我所知道的版本控制系统中,Git是唯一采用这种方法的(到目前为止),尽管我没有调查过DARCS和bzr。 - torek

4

摘要:不用担心,只需正常提交文件即可,因为这是你唯一的选择。

以下是Git如何将重命名从X到Y视为一个过程。

Commit #1:
  - File named "X" with contents "Hello, world!"
Commit #2:
  - File named "Y" with contents "Hello, world!"

请注意,Git实际上并不关心你是否重命名了文件或者创建了一个内容相同的新文件,对于Git来说,这两种情况是没有任何区别的。
Git不记录文件在仓库中的移动操作,也没有基于每个文件的历史记录。Git只将整个仓库的快照作为一张图形记录下来。简单使用git addgit commit命令就可以了,此处不需要执行其他操作。如果你使用git mv命令,就跟使用git rmgit add的效果是一样的。
当你使用git statusgit log命令时,如果新增的文件和删除的文件足够相似,则它们会以移动的形式显示出来。Git不会利用仓库中的额外信息,如果你修改了移动的文件,系统会将其视为单独的添加和删除操作。Git通过比较新文件的内容与旧文件的内容来实现这一点。这发生在数据提交后,当你要求查看差异时 默认的相似度阈值是50%。如果希望查看文件之间低于50%相似度的重命名操作,请向git dif-index命令传递一个更低的百分比参数。例如,要跟踪重命名操作,即使文件差异达到75%,请使用以下命令:
git diff-index -M25%

嗯,有趣。版本历史记录呢?如果git认为这是一个新文件,那么它就是全新的版本历史记录,不是吗? - mark
还是不太明白。假设代码库有提交记录 C0、C1、C2。在 C3 中,我删除了文件 X 并添加了文件 Y。那么文件 Y 的历史记录只有 C3,对吗?但如果我将文件 X 重命名为 Y,那么历史记录应该是 C0、C1、C2 和 C3。我有什么遗漏吗? - mark
这是100%不正确的。Git数据库中文件Y的历史记录在C3中。无论您做什么,都不会改变这个事实,这就是Git的工作方式。 - Dietrich Epp
1
让我澄清一下:Y没有历史记录。历史记录不存在。无论您是移动文件、创建新文件还是施展魔法,对于Git来说都无关紧要,因为在之前的提交中没有名为Y的文件存在,所以Y对于Git来说是新的。当您使用git loggit diffgit status等命令时,Git会尝试猜测文件Y的历史记录,但实际上文件Y从未有过任何记录的历史。 - Dietrich Epp
1
@mark: 我认为这里的主要问题是你仍然认为文件有历史记录。尽管从你的角度来看这可能是正确的,但就 Git 而言,这是绝对错误的。Git 根本不记录文件的历史记录。即使你保留了相同名称的文件, Git 仍然 不会为该文件记录历史记录。Git 只为整个仓库作为单个单位记录历史记录。使用整个仓库的历史记录,如果你要求 Git 这样做,Git 将尝试猜测并为单个文件提供一个合理的历史记录。 - Dietrich Epp
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接