使用Git diff检测代码移动 + 如何使用diff选项

39

假设一个文件(1.c)包含三个函数和作者M和J所做的更改。 如果有人运行git blame 1.c,他将得到以下输出:

^869c699 (M 2012-09-25 14:05:31 -0600  1) 
de24af82 (J 2012-09-25 14:23:52 -0600  2) 
de24af82 (J 2012-09-25 14:23:52 -0600  3) 
de24af82 (J 2012-09-25 14:23:52 -0600  4) public int add(int x, int y)  {
de24af82 (J 2012-09-25 14:23:52 -0600  5)    int z = x+y;
de24af82 (J 2012-09-25 14:23:52 -0600  6)    return z;
de24af82 (J 2012-09-25 14:23:52 -0600  7) }  
de24af82 (J 2012-09-25 14:23:52 -0600  8) 
^869c699 (M 2012-09-25 14:05:31 -0600  9) public int multiplication(int y, int z){
^869c699 (M 2012-09-25 14:05:31 -0600 10)    int result = y*z;
^869c699 (M 2012-09-25 14:05:31 -0600 11)    return temp;
^869c699 (M 2012-09-25 14:05:31 -0600 12) }
^869c699 (M 2012-09-25 14:05:31 -0600 13) 
^869c699 (M 2012-09-25 14:05:31 -0600 14) public void main(){
de24af82 (J 2012-09-25 14:23:52 -0600 15)    //this is a comment
de24af82 (J 2012-09-25 14:23:52 -0600 16) }

现在,如果作者A改变了multiplication()add()函数的位置并提交了更改,git blame可以检测到代码的移动。请看下面的输出:

$ git blame  -C -M e4672cf82 1.c
^869c699 (M 2012-09-25 14:05:31 -0600  1) 
de24af82 (J 2012-09-25 14:23:52 -0600  2) 
de24af82 (J 2012-09-25 14:23:52 -0600  3) 
e4672cf8 (M 2012-09-25 14:26:39 -0600  4) 
de24af82 (J 2012-09-25 14:23:52 -0600  5) 
^869c699 (M 2012-09-25 14:05:31 -0600  6) public int multiplication(int y, int z){
^869c699 (M 2012-09-25 14:05:31 -0600  7)    int result = y*z;
^869c699 (M 2012-09-25 14:05:31 -0600  8)    return temp;
^869c699 (M 2012-09-25 14:05:31 -0600  9) }
^869c699 (M 2012-09-25 14:05:31 -0600 10) 
^869c699 (M 2012-09-25 14:05:31 -0600 11) public void main(){
de24af82 (J 2012-09-25 14:23:52 -0600 12)    //this is a comment
e4672cf8 (M 2012-09-25 14:26:39 -0600 13) }
de24af82 (J 2012-09-25 14:23:52 -0600 14) public int add(int x, int y){
de24af82 (J 2012-09-25 14:23:52 -0600 15)    int z = x+y;
de24af82 (J 2012-09-25 14:23:52 -0600 16)    return z;
e4672cf8 (M 2012-09-25 14:26:39 -0600 17) }

但是,如果我尝试在这两个版本之间运行git diff,它无法检测到函数更改其位置,并显示以下输出:

$ git diff -C -M de24af8..e4672cf82 1.c

diff --git a/1.c b/1.c
index 5b1fcba..56b4430 100644
--- a/1.c
+++ b/1.c
@@ -1,10 +1,7 @@



-public int add(int x, int y){
-       int z = x+y;
-       return z;
-}      
+

public int multiplication(int y, int z){
    int result = y*z;
@@ -13,4 +10,8 @@ public int multiplication(int y, int z){

 public void main(){
    //this is a comment
-}
\ No newline at end of file
+}
+public int add(int x, int y){
+       int z = x+y;
+       return z;
+}      
\ No newline at end of file

我的问题是:

  1. 我如何强制检测代码移动以获取diff输出?这是否可能?

  2. Git diff可以应用多个选项。例如--minimal--patience。我该如何在这里应用这些选项?我尝试了一个选项,但出现以下错误:

  3. $ git diff --minimal de24af8..e4672cf82 1.c
    usage: git diff <options> <rev>{0,2} -- <path>*
    
    可以有人提供/给出示例,如何正确添加这些选项?

3
由于Git在最近的版本中已经完全实现了您上面想要的功能,如果您改为接受的答案,将会减少未来读者的困惑:尝试一下这个,看看您是否同意。 - Inigo
3个回答

68

从Git 2.15开始,git diff现在支持使用--color-moved选项检测移动的行。甚至可以检测文件之间的移动。

显然,它适用于带有颜色的终端输出。据我所知,没有选项可以表示普通文本修补格式中的移动,但这是有道理的。

如果要使用默认行为,请尝试

git diff --color-moved

该命令还可以接受选项,目前支持的选项有nodefaultplainzebradimmed_zebra(使用git help diff获取最新选项及其说明)。例如:

git diff --color-moved=zebra

2
GitHub 有类似的东西吗? - Boris Yakubchik
1
有没有办法默认启用这个功能? - David Schumann
1
@DavidNathan 是的,使用 git config 命令来设置 diff.colorMoved。 - Inigo
4
谢谢!如果有人想知道,可以使用以下命令:git config diff.colorMoved true --global。该命令用于启用 Git 中移动文本块的颜色显示。 - David Schumann
2
@davidschumann 更正:git config --global diff.colorMoved true(--global 在选项名称之前)。 - Kyle Rogers
显示剩余9条评论

25
这是在其写作期间最好的答案,但是现在已经不再准确。在2017年,Git 2.15升级了它的diff以进行移动检测。如同在当前得票最多的答案中所解释的那样, 使用 git diff --color-moved

原始答案:

你在这里遇到的问题是,Git基本上会避开这样的高级diff处理。 Git允许配置外部diff和merge工具的原因是有帮助的。Beyond Compare和Araxis Merge都可以捕获此类移动,例如。

你要解决的一般性问题类是“结构化合并”:两个java源文件的结构差异

在这种情况下,使用git-format-patch可能比git-diff更好,因为前者提供了更多的提交信息,包括作者和提交消息,并且为您指定范围内的每个提交生成一个补丁文件。来源:'git format-patch'和'git diff'之间有什么区别? 如果您正在寻找有关检测代码移动的提示,那么有趣的是,代码移动的检测明确不是pickaxe的目标。请参阅此有趣的交流:http://gitster.livejournal.com/35628.html 如果您想检测谁交换了顺序,似乎您唯一的选择是执行以下操作:
 git log -S'public int multiplication(int y, int z){
    int result = y*z;
    return temp;
 }

 public void main(){
    //this is a comment
 }
 public int add(int x, int y)  {
    int z = x+y;
    return z;
 }'

您要查找的是git blame -M<num> -n,它与您所要求的内容非常相似:
-M|<num>|
       Detect moved or copied lines within a file. When a commit moves or
       copies a block of lines (e.g. the original file has A and then B,
       and the commit changes it to B and then A), the traditional blame
       algorithm notices only half of the movement and typically blames
       the lines that were moved up (i.e. B) to the parent and assigns
       blame to the lines that were moved down (i.e. A) to the child
       commit. With this option, both groups of lines are blamed on the
       parent by running extra passes of inspection.

       <num> is optional but it is the lower bound on the number of
       alphanumeric characters that git must detect as moving/copying
       within a file for it to associate those lines with the parent
       commit. The default value is 20.

-n, --show-number
       Show the line number in the original commit (Default: off).

1
谢谢答复。我们能否使用责备信息来确定版本中的哪一行来自先前版本中的哪一行? 有一个可用于责备的--porcelain选项。它提供了行映射信息,但输出对我来说似乎很令人困惑。我能否使用它来跟踪行位置?您能否就此事情给予解释。 - Muhammad Asaduzzaman
1
这里涉及到完全不同的概念。我会查看git blame信息,但瓷器最好被视为与厕所相关的东西。也就是说,瓷器是Git管道上“漂亮”的外观。瓷器命令都是你熟悉的命令:git add git tag git commit 管道命令是你不想混乱的危险命令。更多信息请参见:https://dev59.com/r2w05IYBdhLWcg3w3lkl和http://www.tin.org/bin/man.cgi?section=7&topic=git 值得一提的是,有不幸、令人困惑的例外情况。 - kayaker243
1
“--porcelain”在“git blame”和“git status”的上下文中是这些术语不幸混淆的两个例子。在这种情况下,“porcelain”是该命令的机器可读版本。 - kayaker243
2
这个答案非常有帮助。一个后续问题是为什么 GitHub 不使用更高级的 diff 工具。如果对某人进行 Pull Request,但 PR 只是移动了很多代码,这看起来会让审核者感到繁琐,而实际上代码可能变化很小。 - Tommy
1
这个答案曾经是正确的,但自从Git 2.15之后就不再正确了。这个更改也解决了你提出的问题,@tommy。 - Inigo
显示剩余2条评论

2
在这种情况下,我认为git diff并不关心检测代码移动;相反,它只是创建一个补丁,可以将旧文件转换为新文件,这就是您的git diff输出所显示的内容 - 函数从一个位置被删除并插入到另一个位置。可能有更简洁的方法来输出一系列编辑命令,将代码从一个位置移动到另一个位置,但我认为git在这里可能会犯错误 - 不能保证最终用户总是使用git apply或git am,因此补丁以可用于普通补丁的格式产生。请注意保留HTML标记。

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