$ pwd
/data/mdi2/classes
$ git blame -L22,+1 -- utils.js
99b7a802 mdi2/utils.js (user 2015-03-26 21:54:57 +0200 22) #comment
$ git blame -L22,+1 99b7a802^ -- utils.js
fatal: no such path mdi2/classes/utils.js in 99b7a802^
正如您所注意到的,该文件在该提交中的不同目录中
$ git blame -L22,+1 99b7a802^ -- ../utils.js
c5105267 (user 2007-04-10 08:00:20 +0000 22) #comment 2
尽管在文档上
The origin of lines is automatically followed across whole-file renames (currently there is no option to turn
the rename-following off)
责备不遵循重命名。为什么?
更新:简答
git blame
遵循重命名,但不遵循git blame COMMIT^ -- <filename>
但是,通过大量重命名和大量历史记录手动跟踪文件重命名太难了。我认为,必须修复此行为以静默地遵循git blame COMMIT^ -- <filename>
重命名。或者,至少,必须实施--follow
,所以我可以:git blame --follow COMMIT^ -- <filename>
UPDATE2:那是不可能的。请阅读下文。
来自邮件列表的答案 作者:Junio C Hamano
git blame
遵循重命名,但不遵循git blame COMMIT^ -- <filename>
假设您的版本 v1.0 中有文件 A 和文件 B。
六个月后,代码被重构了很多,你这样做了。不需要分别提供这两个文件的内容。 你有删除了 A 和 B,他们拥有的大部分内容现在都在文件 C 中。 那是当前状态。
git blame -C HEAD -- C
可以遵循两者的内容就好了,但如果你是允许说
git blame v1.0 -- C
这甚至意味着什么? C 根本不存在 v1.0。 你是要求遵循A的内容,还是B? 你怎么告诉你的意思是 A 而不是 B,当你在这个命令中告诉它 C 时?
"git blame"遵循内容移动,从不处理"重命名"任何特殊的方式,因为认为重命名是一件愚蠢的事情有点特别;-(
您告诉从哪些内容开始挖掘命令的方式从其命令行是给出起点提交(默认为HEAD,但您可以以提交^为例(及其中的路径起点。 因为告诉 C 到 Git 和 Git 没有任何意义然后神奇地让它猜到你在某些情况下的意思是 A,在某些情况下是指 B其他。 如果 v1.0 没有 C,唯一明智的做法是退出而不是猜测(并且不告诉用户如何猜测(。
git blame
确实遵循重命名(如果你给它--follow
,git log
也是如此(。 问题在于它遵循重命名的方式,这是一个不太彻底的黑客:当它一次退回一个提交(从每个子级到每个父级(时,它会进行差异 - 您可以手动进行的差异:
git diff -M SHA1^ SHA1
— 并检查此差异是否检测到重命名。1
就目前而言,这一切都很好,但这意味着要让git blame
检测到重命名,(a( git diff -M
必须能够检测到它(幸运的是,这里就是这种情况(,并且 - 这是导致您问题的原因 - 它必须跨重命名。
例如,假设提交图看起来有点像这样:
A <-- B <-- ... Q <-- R <-- S <-- T
其中每个大写字母代表一个提交。 进一步假设一个文件在提交R
中被重命名,因此在提交R
到T
中它有名称newname
而在提交A
到Q
它有名称oldname
。
如果运行git blame -- newname
,则序列从T
开始,比较S
和T
,比较R
和S
,比较Q
和R
。 当它比较Q
和R
时,git blame
会发现名称更改,并开始在提交Q
及更早的提交中查找oldname
,因此当它比较P
和Q
时,它会比较这两个提交中oldname
和oldname
的文件。
另一方面,如果您运行git blame R^ -- newname
(或git blame Q -- newname
(以使序列从提交Q
开始,则该提交中没有文件newname
,并且在比较P
和Q
时没有重命名,并且git blame
只是放弃。
诀窍是,如果你从文件具有以前名称的提交开始,你必须给 git 一个旧名称:
git blame R^ -- oldname
然后一切又开始了。
1在git diff
文档中,您将看到有一个-M
选项,用于控制git diff
如何检测重命名。 blame
代码对此进行了一些修改(实际上进行了两次传递,一次关闭了-M
,另一次打开了-M
(,并使用自己的(不同的(-M
选项用于不同的目的,但最终它使用相同的代码。
[编辑以添加对评论的回复(不适合作为评论本身(]:
是否有任何工具可以向我显示文件重命名,例如:git 重命名<文件名>SHA 日期旧名称->新名称文件名>
不完全是,但git diff -M
接近,可能足够接近。
我不确定您在这里所说的"SHA 日期"是什么意思,但git diff -M
允许您提供两个 SHA-1 并比较左右。 添加--name-status
以仅获取文件名和处置。 因此,git diff -M --name-status HEAD oldsha1
可能会报告要从HEAD
转换为oldsha1
,git 认为您应该R
命名文件,并将旧名称报告为"新"名称。 例如,在 git 存储库本身中,当前有一个名为 Documentation/giteveryday.txt
的文件,它的名称曾经略有不同:
$ git diff -M --name-status HEAD 992cb206
M .gitignore
M .mailmap
[...snip...]
M Documentation/diff-options.txt
R097 Documentation/giteveryday.txt Documentation/everyday.txt
D Documentation/everyday.txto
[...]
如果这是你关心的文件,你很好。 这里的两个问题是:
- 寻找SHA1:
992cb206
从何而来? 如果您已经拥有 SHA-1,这很容易;如果没有,git rev-list
是 SHA1 查找工具;阅读其文档; - 事实上,在每次提交进行一系列重命名之后,一次一次提交一次,就像
git blame
所做的那样,可能会产生与比较更晚的提交(HEAD
(与更早的提交(992cb206
或其他什么(完全不同的答案。 在这种情况下,结果相同,但这里的"相似性指数"是 97 分(满分 100 分(。 如果在某些中间步骤中对其进行更多修改,则相似性指数可能会降至50%以下......然而,如果我们在992cb206
后一点点比较修订版和992cb206
版(就像git blame
一样(,也许这两个文件之间的相似性指数可能会更高。
需要(和缺少(的是git rev-list
本身实现--follow
,以便所有在内部使用git rev-list
的命令 - 即,大多数命令在多个修订版上工作 - 都可以做到这一点。 在此过程中,如果它在另一个方向上工作会很好(目前--follow
只是从新到旧的,即,只要您不首先要求最旧的历史记录git log
就可以git blame
工作正常--reverse
(。
请参阅更新。现在,您可以关注重命名的文件。
最新的 git 有有趣的命令。在您的配置旁边添加:
[alias]
follow= "!sh -c 'git log --topo-order -u -L $2,${3:-$2}:"$1"'" -
现在,您可以:
$git follow <filename> <linefrom> [<lineto>]
您将看到每个提交都会更改<filename>
中的指定行。
您也可以对git log
命令的选项感兴趣--follow
:
继续列出重命名以外的文件历史记录(仅适用于单个文件(。
如果您对复制检测感兴趣,请使用-C
:
检测副本以及重命名。另请参阅--find-copyies-harder。如果指定了 n,则它与 -M 的含义相同。
-C
在同一提交中看起来会有不同的文件。如果要检测代码是从此提交中未更改的其他文件中获取的。然后,您应该提供--find-copies-harder
选项。
出于性能原因,缺省情况下,仅当副本的原始文件在同一变更集中被修改时,-C 选项才会查找副本。此标志使命令将未修改的文件检查为副本源的候选项。对于大型项目来说,这是一项非常昂贵的操作,因此请谨慎使用。提供多个 -C 选项具有相同的效果。
更新
我改进了这个别名:
[alias]
follow = "!bash -c '
if [[ $1 == "/"* ]]; then
FILE=$1;
else
FILE=${GIT_PREFIX}$1;
fi;
echo "git log --topo-order -u -L $2,${3:-$2}:\"$FILE\" $4 ";
git log -w -b -p --ignore-blank-lines --topo-order -u -L $2,${3:-$2}:"$FILE" $4;
' --"
作为参考,以下是此别名的位:
git 别名
GIT_PREFIX
设置为通过从原始当前目录运行git rev-parse --show-prefix
返回。
--topo-order
将按开发轨道对提交进行排序,而不是按日期排序,并可能混合两个不同的轨道。
-u
与-p
、--patch
相同,所以这将生成一个补丁
-L
线路范围
-w
与--ignore-all-space
相同,忽略空格差异
-b
忽略空间更改。
-p
,生成一个与-u
、--patch
相同的补丁
--ignore-blank-lines
现在,您可以跟踪指定范围的行是如何更改的:
git follow file_name.c 30 35
即使你可以继续关注不同的文件,从提交(@arg4(开始
git follow old_file_name.c 30 35 85ce061
85ce061 - 是文件重命名的提交
注意:不幸的是,git 没有考虑工作目录中的更改。因此,如果您对文件进行本地更改,则必须将其存储起来,然后才能follow
更改