Git:发现哪些提交触及了一系列的行

我很难搞清楚如何使用git blame来获取曾经触及给定范围的行的提交集。 有类似这样的问题,但接受的答案并没有让我更进一步。

假设我有一个从foo.rb第1000行开始的定义。 它只有5行,但是改变这些行的提交次数是巨大的。 如果我做

 git blame foo.rb -L 1000,+5 

我引用(最多)五个不同的提交,改变了这些线,但我也对“背后”的提交感兴趣。

同样的,

 git rev-list HEAD -- foo.rb | xargs git log --oneline 

几乎是我想要的,但我不能指定行范围git rev-list

我可以通过一个标志来git blame以获得曾经触及这五行的提交列表,或者build立一个提取这些信息的脚本的最快方法是什么? 让我们暂时忽略定义曾经多于或less于5行的可能性。

由于Git 1.8.4 , git log-L来查看一系列行的演变。

例如,假设你看看git blame的输出:

 ((aa27064...))[mlm@macbook:~/w/mlm/git] $ git blame -L150,+11 -- git-web--browse.sh a180055a git-web--browse.sh (Giuseppe Bilotta 2010-12-03 17:47:36 +0100 150) die "The browser $browser is not a180055a git-web--browse.sh (Giuseppe Bilotta 2010-12-03 17:47:36 +0100 151) fi 5d6491c7 git-browse-help.sh (Christian Couder 2007-12-02 06:07:55 +0100 152) fi 5d6491c7 git-browse-help.sh (Christian Couder 2007-12-02 06:07:55 +0100 153) 5d6491c7 git-browse-help.sh (Christian Couder 2007-12-02 06:07:55 +0100 154) case "$browser" in 81f42f11 git-web--browse.sh (Giuseppe Bilotta 2010-12-03 17:47:38 +0100 155) firefox|iceweasel|seamonkey|iceape) 5d6491c7 git-browse-help.sh (Christian Couder 2007-12-02 06:07:55 +0100 156) # Check version because firefox < 2.0 do 5d6491c7 git-browse-help.sh (Christian Couder 2007-12-02 06:07:55 +0100 157) vers=$(expr "$($browser_path -version)" 5d6491c7 git-browse-help.sh (Christian Couder 2007-12-02 06:07:55 +0100 158) NEWTAB='-new-tab' 5d6491c7 git-browse-help.sh (Christian Couder 2007-12-02 06:07:55 +0100 159) test "$vers" -lt 2 && NEWTAB='' a0685a4f git-web--browse.sh (Dmitry Potapov 2008-02-09 23:22:22 -0800 160) "$browser_path" $NEWTAB "$@" & 

而且你想知道现在155线的历史。

然后:

 ((aa27064...))[mlm@macbook:~/w/mlm/git] $ git log --topo-order --graph -u -L 155,155:git-web--browse.sh * commit 81f42f11496b9117273939c98d270af273c8a463 | Author: Giuseppe Bilotta <giuseppe.bilotta@gmail.com> | Date: Fri Dec 3 17:47:38 2010 +0100 | | web--browse: support opera, seamonkey and elinks | | The list of supported browsers is also updated in the documentation. | | Signed-off-by: Giuseppe Bilotta <giuseppe.bilotta@gmail.com> | Signed-off-by: Junio C Hamano <gitster@pobox.com> | | diff --git a/git-web--browse.sh b/git-web--browse.sh | --- a/git-web--browse.sh | +++ b/git-web--browse.sh | @@ -143,1 +143,1 @@ | -firefox|iceweasel) | +firefox|iceweasel|seamonkey|iceape) | * commit a180055a47c6793eaaba6289f623cff32644215b | Author: Giuseppe Bilotta <giuseppe.bilotta@gmail.com> | Date: Fri Dec 3 17:47:36 2010 +0100 | | web--browse: coding style | | Retab and deindent choices in case statements. | | Signed-off-by: Giuseppe Bilotta <giuseppe.bilotta@gmail.com> | Signed-off-by: Junio C Hamano <gitster@pobox.com> | | diff --git a/git-web--browse.sh b/git-web--browse.sh | --- a/git-web--browse.sh | +++ b/git-web--browse.sh | @@ -142,1 +142,1 @@ | - firefox|iceweasel) | +firefox|iceweasel) | * commit 5884f1fe96b33d9666a78e660042b1e3e5f9f4d9 Author: Christian Couder <chriscool@tuxfamily.org> Date: Sat Feb 2 07:32:53 2008 +0100 Rename 'git-help--browse.sh' to 'git-web--browse.sh'. Signed-off-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Junio C Hamano <gitster@pobox.com> diff --git a/git-web--browse.sh b/git-web--browse.sh --- /dev/null +++ b/git-web--browse.sh @@ -0,0 +127,1 @@ + firefox|iceweasel) 

如果你经常使用这个function,你可能会发现一个有用的git别名。 要做到这一点,把你的~/.gitconfig

 [alias] # Follow evolution of certain lines in a file # arg1=file, arg2=first line, arg3=last line or blank for just the first line follow = "!sh -c 'git log --topo-order -u -L $2,${3:-$2}:"$1"'" - 

现在你可以做git follow git-web--browse.sh 155

我想这是你想要的:

 git rev-list HEAD -- foo.rb | ( while read rev; do git blame -l -L 1000,+5 $rev -- foo.rb | cut -d ' ' -f 1 done; ) | awk '{ if (!h[$0]) { print $0; h[$0]=1 } }' 

这将输出每个提交的修订号码,该修改号码对您select的行进行了编辑。

这里是步骤:

  1. 第一部分git rev-list HEAD -- foo.rb输出所选文件被编辑的所有修订版本。

  2. 每一个版本都进入第二部分,每一部分都将每一个修改到git blame -l -L 1000,+5 $rev -- foo.rb | cut -d ' ' -f 1 git blame -l -L 1000,+5 $rev -- foo.rb | cut -d ' ' -f 1 。 这是一个由两部分组成的命令。

    1. git blame -l -L 1000,+5 $rev -- foo.rb输出所选行的责任。 通过给它提供修订号,我们告诉它从那个承诺开始,从那里开始,而不是从头开始。
    2. 由于责备输出了一大堆我们不需要的信息,因此我们cut -d ' ' -f 1给了我们非责任输出的第一列(修订号)。
  3. awk '{ if (!h[$0]) { print $0; h[$0]=1 } }' awk '{ if (!h[$0]) { print $0; h[$0]=1 } }'取出不相邻的重复行,同时保持它们出现的顺序。有关此命令的更多信息,请参阅http://jeetworks.org/node/94

你可以在这里添加最后一步来获得更漂亮的输出。 将一切都转换成xargs -L 1 git log --oneline -1并获取修订列表的相应提交信息。 我有一个奇怪的问题,使用这最后一步,我不得不继续按下输出每几个修订。 我不知道这是为什么,这就是为什么我没有把它包括在我的解决scheme。

不知道你想做什么,但也许git日志-S可以为你做的伎俩:

 -S<string> Look for differences that introduce or remove an instance of <string>. Note that this is different than the string simply appearing in diff output; see the pickaxe entry in gitdiffcore(7) for more details. 

您可以将string中的变化(或变化的一部分),你会试图遵循,这将列出曾经触及这个变化的提交。

我喜欢这个难题,它有微妙之处。 源文件,说init foo.rb 1000,1005并按照指示。 完成之后,文件@changes将按照拓扑顺序具有正确的提交列表,而@blames将具有每个实际的@blames输出。

这比上面接受的解决scheme要复杂得多。 它产生的输出有时候会更有用,而且很难再现,编码也很有趣。

尝试跟踪行号范围时,自动追溯历史logging的问题是,如果更改大块穿过行编号的范围边界,则不能自动确定新范围边界应在哪个位置,并且您将拥有包括一个大范围的大增加,并积累(有时很多)不相关的变化,或进入手动模式,以确保它是正确的(这当然可以让你回到这里),或有时接受极端的损失。

如果你希望你的输出是准确的,可以用`/ ^ type function(/,/ ^} /'等可信赖的正则expression式来回答上面的问题,或者使用这个,实际上并不是那么糟糕,及时。

为了交换额外的复杂性,它在拓扑序列中产生了命中列表,并且它至less(相当成功地)试图改善每一步的痛苦。 例如,它永远不会有多余的责任,更新范围使调整行号更容易。 当然还有个别眼球的可靠性: – P

要在全自动模式下运行,请说{ init foo.rb /^class foo/,/^end/; auto; } 2>&- { init foo.rb /^class foo/,/^end/; auto; } 2>&-

  ### functions here create random @-prefix files in the current directory ### # # git blame history for a range, finding every change to that range # throughout the available history. It's somewhat, ahh, "intended for # customization", is that enough of a warning? It works as advertised # but drops @-prefix temporary files in your current directory and # defines new commands # # Source this file in a subshell, it defines functions for your use. # If you have @-prefix files you care about, change all @ in this file # to something you don't have and source it again. # # init path/to/file [<start>,<end>] # range optional # update-ranges # check range boundaries for the next step # cycle [<start>,<end>] # range unchanged if not supplied # prettyblame # pretty colors, # blue="child commit doesn't have this line" # green="parent commit doesn't have this line" # brown=both # shhh # silence the pre-cycle blurb # # For regex ranges, you can _usually_ source this file and say `init # path/to/file /startpattern/,/endpattern/` and then cycle until it says 0 # commits remain in the checklist # # for line-number ranges, or regex ranges you think might be unworthy, you # need to check and possibly update the range before each cycle. File # @next is the next blame start-point revision text; and command # update-ranges will bring up vim with the current range V-selected. If # that looks good, `@M` is set up to quit even while selecting, so `@M` and # cycle. If it doesn't look good, 'o' and the arrow keys will make getting # good line numbers easy, or you can find better regex's. Either way, `@M` # out and say `cycle <start>,<end>` to update the ranges. init () { file=$1; range="$2" rm -f @changes git rev-list --topo-order HEAD -- "$file" \ | tee @checklist \ | cat -n | sort -k2 > @sequence git blame "-ln${range:+L$range}" -- "$file" > @latest || echo >@checklist check-cycle cp @latest @blames } update-latest-checklist() { # update $latest with the latest sha that actually touched our range, # and delete that and everything later than that from the checklist. latest=$( sed s,^^,, @latest \ | sort -uk1,1 \ | join -1 2 -o1.1,1.2 @sequence - \ | sort -unk1,1 \ | sed 1q \ | cut -d" " -f2 ) sed -i 1,/^$latest/d @checklist } shhh () { shhh=1; } check-cycle () { update-latest-checklist sed -n q1 @checklist || git log $latest~..$latest --format=%H\ %s | tee -a @changes next=`sed 1q @checklist` git cat-file -p `git rev-parse $next:"$file"` > @next test -z "$shh$shhh$shhhh" && { echo "A blame from the (next-)most recent alteration (id `git rev-parse --short $latest`) to '$file'" echo is in file @latest, save its contents where you like echo echo you will need to look in file @next to determine the correct next range, echo and say '`cycle its-start-line,its-end-line`' to continue echo the "update-ranges" function starts you out with the range selected } >&2 ncommits=`wc -l @checklist | cut -d\ -f1` echo $ncommits commits remain in the checklist >&2 return $((ncommits==0)) } update-ranges () { start="${range%,*}" end="${range#*,}" case "$start" in */*) startcmd="1G$start"$'\n' ;; *) startcmd="${start}G" ;; esac case "$end" in */*) endcmd="$end"$'\n' ;; [0-9]*) endcmd="${end}G" ;; +[0-9]*) endcmd="${end}j" ;; *) endcmd="echohl Search|echo "can\'t" get to '${end}'\"|echohl None" ;; esac vim -c 'set buftype=nofile|let @m=":|q'$'\n"' -c "norm!${startcmd}V${endcmd}zo" @next } cycle () { sed -n q1 @checklist && { echo "No more commits to check"; return 1; } range="${1:-$range}" git blame "-ln${range:+L$range}" $next -- "$file" >@latest || echo >@checklist echo >>@blames cat @latest >>@blames check-cycle } auto () { while cycle; do true; done } prettyblames () { cat >@pretty <<-\EOD BEGIN { RS="" colors[0]="\033[0;30m" colors[1]="\033[0;34m" colors[2]="\033[0;32m" colors[3]="\033[0;33m" getline commits < "@changes" split(commits,commit,/\n/) } NR!=1 { print "" } { thiscommit=gensub(/ .*/,"",1,commit[NR]) printf "%s\n","\033[0;31m"commit[NR]"\033[0m" split($0,line,/\n/) for ( n=1; n<=length(line); ++n ) { color=0 split(line[n],key,/[1-9][0-9]*)/) if ( NR!=1 && !seen[key[1]] ) color+=1 seen[key[1]]=1; linecommit = gensub(/ .*/,"",1,line[n]) if (linecommit==thiscommit) color+=2 printf "%s%s\033[0m\n",colors[color],line[n] } } EOD awk -f @pretty @blames | less -R } 

请参阅这里发布的答案列出特定文件的所有提交 。 它正是你所需要的。

一些想法..

这听起来类似于这个post ,看起来你可能会接近这样的事情:

 git blame -L '/variable_name *= */',+1 

只要你知道定义匹配(正则expression式)。

这里有一个线程讨论 ,关于使用tiggit gui (显然可以处理这个)。 我还没有尝试过,所以无法validation它(我会稍后再试一试)。