如何使用git log --follow <filename>工作?

时间:2017-05-11 05:10:29

标签: git

我试图为文件的历史记录选择一个ID - 我希望它是或者引用其详细信息git log --follow <filename>的“对象”。我想知道:

git如何知道一个文件在后续提交中是另一个文件的变体?当然,名称相同是一个强烈的提示,但它也跟踪提交时的重命名。它是否将计算结果保存在git log引用的位置(where?),或git log是否每次都重复这些计算? (这些是什么计算?)

理想情况下,我想使用nodegit访问或重新创建历史记录(提交/ blob shas列表)。

2 个答案:

答案 0 :(得分:6)

我和其他人都在其他地方(而不是链接)详细描述了这一点,例如this answerWhat's git's heuristic for assigning content modifications to file paths?或我对Git Diff of same files in two directories always result in "renamed"的回答。 git log --follow的详细信息与git diff的详细信息略有不同,因为git diff通常会处理整个树,其中包含左侧和右侧文件,但git log --follow仅适用于git diff有一条特定的路径。 1

在任何情况下,在比较两个特定提交时都会发生重命名跟踪。对于一般git log,他们任何两个提交 R (右侧)和 L (左侧 - 您选择两个) , 2 但对于git log --follow,他们特别是父母和孩子。为方便起见,我们将这些 P C 称为。使用diff_tree_sha1,Git运行一个后差异步骤(从-B调用;请参阅脚注),将所有内容修剪为一个文件。差异用 R = C L = P 完成。但是,一般情况下更容易描述,因此我们将从此开始。

通常,在比较 R L 时,Git:

  • 匹配具有相同完整路径名的所有树文件,然后
  • 将剩余的文件(路径)放入配对队列。

您可以使用-Bn/m(pair- b reaking)标志稍微修改一下,实际上需要两个可选整数(n)。只有 -C 整数才能进行重命名检测。 3 您也可以使用n标志对其进行修改;这只需要一个可选的 -M ,然后启用复制检测。在所有情况下,必须打开重命名检测。重命名检测通过n启用,同样需要一个可选的整数 git log --follow ,或者在git statusgit diff --stat等其他命令的情况下自动启用或合并后的n

在任何情况下,这里的整数 git diff <commit1> <commit2> 是所有这些不同选项的相似性(或不相似)度量值。这是我们了解重命名检测代码的地方。

假设我们首先进行了基本的git diff <tree1> <tree2>diff_tree_sha1操作。这最终会调用builtin_diff_tree in builtin/diff.c,调用diffcore_break(我们稍后会再次查看),然后调用log_tree_diff_flush in log-tree.c。这几乎会立即调用diffcore_std in diff.c,如果正确(diffcore_renamediffcore_merge_broken或{-B,则运行-M-C-B函数选择{1}}和path/to/file选项。

这三个功能在配对队列上运行。如何设置配对队列?我会把它留给另一个部分,因为它很复杂。现在,假设配对队列已经path/to/filepath/to/file匹配,当 L R <中有path/to/L-only时/ em>,如果文件路径只出现在 L 或仅出现在 R中,则会出现未配对的path/to/R-onlydiffcore_merge

diffcore_break function is in diffcore-break.c。它的工作是找到已经配对的文件,其 dis 相似性索引(当比较 L R 版本时)高于某个阈值。如果是这样的话,就会破坏配对。 --find-copies-harder函数位于同一文件的正下方;如果两个人都没有找到一个更好的伴侣,它会重新加入一对破碎的对子。相异度指数计算与相似度计算类似,但不相同。 4

相似性检测

更有趣diffcore_rename function is in diffcore-rename.c。我们现在可以忽略它a special case shortcut for --follow。然后,它会查找exact renames,即blob哈希匹配的文件,即使它们的名称不相同。使用&#34;下一个文件&#34;有一些繁琐的位。如果多个 L 源与一些未配对的 R 目标具有相同的哈希值。

接下来,它checks how many unpaired entries there are,因为它将(实际上)执行num( L )倍num( R )比较要计算的文件他们的相似之处,这需要花费大量的时间和空间。它甚至会自动降级 * Idea here is very simple. * * Almost all data we are interested in are text, but sometimes we have * to deal with binary data. So we cut them into chunks delimited by * LF byte, or 64-byte sequence, whichever comes first, and hash them. * * For those chunks, if the source buffer has more instances of it * than the destination buffer, that means the difference are the * number of bytes not copied from source to destination. If the * counts are the same, everything was copied from source to * destination. If the destination has more, everything was copied, * and destination added more. * * We are doing an approximation so we do not really have to waste * memory by actually storing the sequence. We just hash them into * somewhere around 2^16 hashbuckets and count the occurrences. 案例,这个案例太难了#34;然后,对于每个可能的 L R 配对,它会计算similarity index and a name score

相似性索引代码位于estimate_similarity in diffcore-rename.c。它依赖于函数diffcore_count_changes in diffcore-delta.c,它说明了这一点(我直接从文件中复制它,因为它是核心指标之一):

score = (int)(src_copied * MAX_SCORE / max_size);

但这里有一个秘密:the similarity index ignores \r characters if the file is considered "not binary" and the \r is immediately followed by \n

final similarity index score是:

src_copied

其中max_size是源中发生的散列块数(64字节或最新行),然后再次出现在目标中,'\r'是大小,字节,无论哪个blob更大。 (此字节数不考虑已剥离的dir/oldbase字符。这些字符仅从正在进行哈希处理的64或更新行的块中删除。)

&#34;名称得分&#34;实际上只是1(相同的基本名称)或0(不同的基本名称),即如果 L 文件是differentdir/oldbase并且 R 文件是{1,则为1 {1}},但如果 L 文件为dir/oldbase R 文件为anything/newbase,则为0。当这两个文件同样相似时,这用于使Git优先于newdir/oldbase {/ 1}}。

创建配对队列

anything/newbase代码调用(通过一系列函数)ll_diff_tree_paths(两者都在diff_tree_sha1中;我只链接到此处的最终函数)。这是一个复杂且极其优化的代码(Git在这里花了很多时间),所以我们只是快速概述并忽略复杂性(见脚注2)。此代码部分地查看每个树中每个blob的完整路径名(这些是顶部注释中的P1,...,Pn项),部分位于每个blob的blob哈希值这些名字。对于具有相同名称​​和相同内容的文件,它不执行任何操作(tree-diff.c模式除外,在这种情况下,它将所有文件名排队)。对于具有相同名称和不同内容的文件,或者没有 L R 名称的文件,它会调用(通过函数指针,存储在{{ 1}},--find-copies-harderopt->pathchangeopt->change中最终归结为diff_changediff_addremove的内容。这些调用diff_queue,将文件对(如果文件是新的或删除的话,其中一个是虚拟的)放入配对队列。

因此,短版本(如果我们不使用opt->add_removediff.c),配对队列只有在没有原始来源时才会有未配对的文件对应于 R 中文件的 L 中的文件,或 L 中与源文件对应的 R 中没有目标文件>。使用-C时,它会列出每个源文件或每个已修改的源文件,以便可以扫描它们以获取副本(此处的选择取决于您是否使用--find-copies-harder)。

将此缩减为一个文件,-C

我们已在--find-copies-harder代码中注明了一个快捷方式:它会跳过所有 R 文件名,这些文件名不是我们关心的文件名。 --follow似乎有一些类似的黑客,虽然我不确定它们是否适用于此。代码也以不同的方式驱动,如脚注2所示。当我们将父 P 与子 C 进行差异,并在我们的配对队列中找到重命名时,我们然后在我们diffcore-rename.c中将我们用作限制的文件的名称作为限制:我们将 C 中的新名称替换为 P 的。然后我们像往常一样继续进行差异,所以下次我们比较 P - 和 - C 对时,我们正在寻找ll_diff_tree_paths而不是git log -- <path>。如果我们检测到oldpath已从newpath重命名,我们会像以前一样将该名称再次转换为该位置。

请注意,所有oldpathreallyoldpath-B机制在理论上适用 ,但快捷方式可能 - 它根本不适用我明白他们是否这样做 - 保持其中一些不起作用。

1 当使用-C时,Git使用一般的diffcore代码来运行对断开和复制检测。一般代码是调用想要进行简化的代码。查看调用try_to_follow_renames in tree-diff.c的函数diffcore_std in diff.c。这最终都会调用处理配对队列的diff_resolve_rename_copy。然后-M将结果修剪为一个有趣的文件;稍后通过diff_might_be_rename调用diff_tree_sha1进行测试。我认为这一切都来自log_tree_commit,来自cmd_log_walklog_show_early。这最后似乎是一个未经证实的黑客攻击,供一些GUI使用。

2 --follow中的树匹配实际上在输出右侧接受一次提交,并在输入左侧接受提交的列表 - 手侧,用于组合差异目的。这就是Git如何设法显示合并提交。虽然try_to_follow_renames如何使用合并提交,但有点不清楚。请参阅find_paths_generic in combine-diff.c,其中也会调用git diff。请注意,--follow hack是因为调用diff_tree_sha1而发生的,而这个组合差异合并处理代码会调用该函数一次每个父级。如果后面的名称将是但是,当它通过第二个父级时,已经更改了。也许这是一个错误。如果第二个父级决定新名称会导致另一个不同的重命名,会发生什么?从逻辑上讲,它应该按照拓扑顺序为每个父叉选择一个新名称,并考虑在叉子重新加入时以某种方式再次解析它们。

3 log --follow中的第二个 diff_tree_sha1 值告诉Git什么时候不运行真正的差异,而只是描述变化在 -renamed文件中&#34;删除所有原始行,将其替换为所有新行&#34;。这假设第一个m值最终打破配对,或者配对由于-Bn/m值重新粘合在一起,或粘合到不同的来源为-B副本。

4 有关详细信息,请参阅should_break。这也使用-M代码,但以不同的方式,使用&#34;添加&#34;计数。

答案 1 :(得分:1)

git log每次都重复计算。

决定基于文件内容。当一个文件消失,另一个文件出现时,Git会比较内容并决定文件是否重命名,如果内容相同或非常相似(按某种方式)。