4

I've read a lot about how git cleverly will detect when a file is renamed or moved, and you shouldn't have to (or can't) do anything to help it with that kind of task. Unfortunately we have a lot of issues in our project where the git history will consist of a single line after a file has been moved. Here is a pattern taken from a recent example I've seen:

Original class:

package a.b.c.d.e.f.X;

[four imports]

public interface A extends B {
  void f(String id, int index) throws MyException;

  @Annotation
  class AImpl extends C implements A {
    @OtherAnnotation
    public void f(String id, int index)
      throws MyException {
        Map<String, Object> inputs = new HashMap<String, Object>();
        inputs.put("a", id);
        inputs.put("b", index);
        execute(inputs);
    }

    @Override
    public String g() {
      return "xxxx.yyyy";
    }
  }
}

Class after being moved:

package a.b.c.d.e.f.Y;

[six imports, two new compared to original]

public interface A extends B {
  void f(MyIdObject idobject, int index) throws MyException;

  @Annotation
  class AImpl extends D implements A {
    @OtherAnnotation
    public void f(@ParameterAnnotation([some stuff]) MyIdObject idobject, int index) throws MyException {
        Map<String, Object> inputs = new HashMap<>();
        inputs.put("c", idobject.getId());
        inputs.put("b", index);
        execute(inputs);
    }

    @Override
    public String g() {
      return "yyyy";
    }
  }
}

To summarize: class file was moved to another directory/package (differing by one level - see the package names), imports were added, a parameter was changed in the method that is declared and implemented here, a linebreak was removed in the method header, a string was changed inside the method, the HashMap instantiation was changed to use diamond notation, the return value in g() was changed.

Most identifiers and strings have been changed to protect the innocent - they're usually something like 10-20 characters. I haven't compiled this, so sorry if there are any mistakes - I hope you get the idea.

As this was part of some framework refactoring, there were a bunch of classes that were changed following this pattern, in the same commit, differing by string contents, amount of strings put in the hashmap, and the "f" method name.

So I think I can see what git is struggling with here - even though this particular rename is obvious by itself, when it's mixed in with other renames of files that have similar contents, git can't be sure which is which? Shouldn't the filenames (that weren't changed) be hint enough?

My impression is that, since git is supposed to handle this perfectly, there is no way to tweak git into handling this in a better way? Was there something we could have done when we refactored to make it work better? Is there something we can do now?

Especially: I want to be able to see the diff for a file in each commit, including the one where the rename was done. I am used to loading the git history in IntelliJ, and he double clicking one commit, which will show me changes were done in that commit. I get that it may not be possible to get what I want in IntelliJ, but how would I do it on the command line or elsewhere, for a file that has had its history interrupted like this?

Things that we probably don't want to do: 1) one commit for each rename, 2) one commit for renaming + another for content changing.

If you're curious about the Java design send me a PM - let's keep this thread about git. :)

4

1 回答 1

2

尝试使用这些附加git log --follow <filename> 选项

-M[]

--查找重命名[=]

如果生成差异,检测并报告每次提交的重命名。有关在遍历历史记录时跨重命名跟踪文件,请参阅 --follow。如果指定了n,它是相似性指数的阈值(即添加/删除量与文件大小相比)。例如,-M90% 表示如果 90% 以上的文件没有更改,git 应该将删除/添加对视为重命名。如果没有 % 符号,则该数字将被读取为分数,前面有一个小数点。即,-M5 变为0.5,因此与-M50% 相同。同样,-M05 与 -M5% 相同。要将检测限制为精确重命名,请使用 -M100%。

-C[]

--查找副本[=]

检测副本以及重命名。另请参见 --find-copies-harder。如果指定了 n,则它与 -M 的含义相同。

--查找副本更难

出于性能原因,默认情况下,-C 选项仅在副本的原始文件在同一变更集中进行了修改时才查找副本。此标志使命令检查未修改的文件作为副本源的候选者。对于大型项目,这是一项非常昂贵的操作,因此请谨慎使用。提供多个 -C 选项具有相同的效果。

于 2013-06-17T11:35:49.993 回答