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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14142609/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-19 07:58:38  来源:igfitidea点击:

Git: discover which commits ever touched a range of lines

gitblame

提问by Joao Tavora

I'm having trouble figuring out how to use git blamefor getting the set of commits that evertouched a given range of lines. There are similar questions like this onebut the accepted answer doesn't bring me much further.

我无法弄清楚如何使用git blame来获取曾经触及给定范围的行的一组提交。还有像类似的问题这一个,但接受的答案没有给我带来更进一步。

Let's say I have a definition that starts on line 1000 of foo.rb. It's only only 5 lines long, but the number of commits that ever changed those lines is enormous. If I do

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

git blame foo.rb -L 1000,+5

I get references to (at most) five distinct commits that changed these lines, but I'm also interested in the commits "behind them".

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

Similarly,

相似地,

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

is almost what I want, but I can't specify line ranges to git rev-list

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

Can I pass a flag to git blameto get the list of commits that ever touched those five lines, or what's the quickest way to build a script that extracts such information? Let's ignore for the moment the possibility that the definition once had more or less than 5 lines.

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

回答by Matt McClure

Since Git 1.8.4, git loghas -Lto view the evolution of a range of lines.

自 Git 1.8.4 以来git log必须-L查看一系列行的演变。

For example, suppose you look at git blame's output:

例如,假设您查看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 "$@" &

And you want to know the history of what is now line 155.

你想知道现在第 155 行的历史。

Then:

然后:

((aa27064...))[mlm@macbook:~/w/mlm/git]
$ git log --topo-order --graph -u -L 155,155:git-web--browse.sh
* commit 81f42f11496b9117273939c98d270af273c8a463
| Author: Giuseppe Bilotta <[email protected]>
| 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 <[email protected]>
|     Signed-off-by: Junio C Hamano <[email protected]>
| 
| 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 <[email protected]>
| 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 <[email protected]>
|     Signed-off-by: Junio C Hamano <[email protected]>
| 
| 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 <[email protected]>
  Date:   Sat Feb 2 07:32:53 2008 +0100

      Rename 'git-help--browse.sh' to 'git-web--browse.sh'.

      Signed-off-by: Christian Couder <[email protected]>
      Signed-off-by: Junio C Hamano <[email protected]>

  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)

If you use this functionality frequently, you might find a git alias useful. To do that, put in your ~/.gitconfig:

如果您经常使用此功能,您可能会发现 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 ,${3:-}:""'" -

And now you can just do git follow git-web--browse.sh 155.

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

回答by Jonathan Wren

I think this is what you want:

我想这就是你想要的:

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[
-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.
]) { print
-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.
; h[
 ### 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=;
    range=""
    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}z.o" @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]="3[0;30m"
    colors[1]="3[0;34m"
    colors[2]="3[0;32m"
    colors[3]="3[0;33m"
    getline commits < "@changes"
    split(commits,commit,/\n/)
}
NR!=1 { print "" }
{
    thiscommit=gensub(/ .*/,"",1,commit[NR])
    printf "%s\n","3[0;31m"commit[NR]"3[0m"
    split(
git blame -L '/variable_name *= */',+1
,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%s3[0m\n",colors[color],line[n] } } EOD awk -f @pretty @blames | less -R }
]=1 } }'

That'll output the the revision number for each commit that has an edit to the lines you've chosen.

这将输出对您选择的行进行编辑的每个提交的修订号。

Here are the steps:

以下是步骤:

  1. The first part git rev-list HEAD -- foo.rboutputs all revisions in which the chosen file is edited.

  2. Each of those revisions then goes into the second part, which takes each one and puts it into git blame -l -L 1000,+5 $rev -- foo.rb | cut -d ' ' -f 1. This is a two-part command.

    1. git blame -l -L 1000,+5 $rev -- foo.rboutputs the blame for the chosen lines. By feeding it the revision number, we are telling it to start from that commit and go from there, rather than starting at the head.
    2. Since blame outputs a bunch of info we don't need, cut -d ' ' -f 1gives us the first column (the revision number) of the blame output.
  3. awk '{ if (!h[$0]) { print $0; h[$0]=1 } }'takes out non-adjacent duplicate lines while maintaining the order they appeared in. See http://jeetworks.org/node/94for more info about this command.
  1. 第一部分git rev-list HEAD -- foo.rb输出编辑所选文件的所有修订版本。

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

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

You could add a last step here to get prettier output. Pipe everything into xargs -L 1 git log --oneline -1and get the corresponding commit message for the list of revisions. I had a weird issue using this last step where I had to keep pressing next every few revisions that were output. I'm not sure why that was, which is why I didn't include it in my solution.

您可以在此处添加最后一步以获得更漂亮的输出。通过管道将所有内容导入xargs -L 1 git log --oneline -1并获取修订列表的相应提交消息。在使用这最后一步时,我遇到了一个奇怪的问题,我必须每输出几个修订版就一直按下一步。我不确定为什么会这样,这就是为什么我没有将它包含在我的解决方案中。

回答by FerCa

Not sure what you want to do, but maybe git log -Scan do the trick for you:

不确定您想做什么,但也许git log -S可以为您解决问题:

##代码##
##代码##

You can put in string the change (or part of the change) you are trying to follow and this will list the commits that ever touched this change.

您可以将您尝试遵循的更改(或更改的一部分)放入字符串中,这将列出曾经触及此更改的提交。

回答by jthill

I liked this puzzle, it's got its subtleties. Source this file, say init foo.rb 1000,1005and follow the instructions. When you're done, file @changeswill have the correct list of commits in topological order and @blameswill have the actual blame output from each.

我喜欢这个谜题,它有它的微妙之处。获取此文件,说出init foo.rb 1000,1005并按照说明进行操作。完成后,文件@changes将具有按拓扑顺序排列的正确提交列表,@blames并将具有每个的实际责任输出。

This is dramatically more complex than the accepted solution above. It produces output that will sometimes be more useful, and hard to reproduce, and it was fun to code.

这比上述公认的解决方案复杂得多。它产生的输出有时会更有用,并且难以重现,并且编码很有趣。

The problem with trying to track line-number ranges automatically while stepping backward through history is if a change hunk crosses line-numbered range boundaries you can't automatically determine where in that hunk the new range boundary should be, and you'll either have to include a big range for big additions and so accumulate (sometimes lots of) irrelevant changes, or drop into manual mode to be sure it's right (which of course gets you right back here), or accept extreme lossage at times.

尝试自动跟踪行号范围而在历史中倒退的问题是,如果更改块跨越了行号范围边界,您将无法自动确定新范围边界应位于该块中的哪个位置,您要么有包括大量添加的大范围,因此积累(有时是很多)不相关的更改,或者进入手动模式以确保它是正确的(这当然会让你回到这里),或者有时接受极端损失。

If you want your output to be exact, use the answer above with trustworthy regex ranges like `/^type function(/,/^}/', or use this, which isn't actually that bad, a couple seconds per step back in time.

如果您希望您的输出准确,请使用上面的答案与可信赖的正则表达式范围,例如`/^type function(/,/^}/',或者使用它,这实际上并没有那么糟糕,每退一步几秒钟及时。

In exchange for the extra complexity, it does produces the hitlist in topological sequence and it does at least (fairly successfully) try to ameliorate the pain at each step. It never runs a redundant blame, for instance, and update-ranges makes adjusting line numbers easier. And of course there's the reliability of having had to individually eyeball the hunks... :-P

作为额外的复杂性的交换,它确实以拓扑序列生成命中列表,并且它至少(相当成功地)尝试减轻每一步的痛苦。例如,它从不产生多余的责备,并且更新范围使调整行号变得更容易。当然还有不得不单独观察大块头的可靠性...... :-P

To run this on full auto, say { init foo.rb /^class foo/,/^end/; auto; } 2>&-

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

##代码##

回答by jitendrapurohit

Please refer to the answer posted here List all commits for a specific file. Its exactly what you need.

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

回答by Hyman

A few thoughts..

一些想法..

This sounds similar to this post, and it looks like you might get close with something like this:

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

##代码##

As long as you know the definition to match against (for the regex).

只要您知道要匹配的定义(对于正则表达式)。

There is a thread discussion here, about using tigand git gui(which apparently might handle this). I haven't tried this myself yet, so can't verify it (I'll give this a try later).

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