2013-01-03 45 views
52

我無法弄清楚如何使用git blame獲取有史以來觸及給定範圍的行的提交集。有類似的問題,如this one,但接受的答案並沒有帶來更多的進展。Git:發現哪些提交觸及了一系列行

假設我有一個從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吃出碰過這些提交的清單五行,或者建立提取這些信息的腳本的最快方法是什麼?讓我們暫時忽略定義曾經多於或少於5行的可能性。

+0

您確定這是您想要的嗎?使用行號識別更改僅適用於文件的給定狀態。如果你需要第15-20行用於提交'12345',那麼這些行上的代碼可能在第55-60行提交'12345 ^'。 – asm

+0

很確定。這就是爲什麼我需要編寫一個腳本來識別它。爲了簡單起見,我們仍然假定定義從來沒有在文件中從最初的提交回滾中移動。 –

+5

[檢索文件中特定行的提交日誌?](http://stackoverflow.com/questions/8435343/retrieve-the-commit-log-for-a-specific-line-in-a -file) –

回答

52

Since Git 1.8.4git log具有-L可以觀看範圍內的行的演變。

例如,假設你看git blame的輸出:

((aa27064...))[[email protected]:~/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 "[email protected]" & 

你想知道什麼是現在的155線的歷史。

然後:

((aa27064...))[[email protected]:~/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) 

如果您經常使用此功能,您可能會發現一個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

+0

你的別名最後一個'-'是什麼意思? –

10

不知道你想幹什麼,但也許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. 

你可以把字符串的變化(或部分改變)你試圖遵循,這將列出曾經觸及這一改變的提交。

+0

對不起,這不是什麼我一直都在。但無論如何嘗試+1 +1 –

+3

+1因爲它有助於Google相似的問題,因爲這個問題標題 – wim

0

的一點想法..

這聽起來類似this post,它看起來像你可能有這樣的親近:

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

,您知道,對陣的定義,只要(正則表達式)。

有一個線程discussion here,關於使用tiggit gui(這顯然會處理這個問題)。我還沒有嘗試過,所以無法驗證它(稍後我會試試這個)。

19

我想這是你想要什麼:

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 } }' 

那將輸出的每個版本號提交的是有一個編輯給您所選擇的線路。

步驟如下:

  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. 由於責備輸出了一堆我們不需要的信息,cut -d ' ' -f 1給了我們第一列(修訂號)的責備輸出。
  3. awk '{ if (!h[$0]) { print $0; h[$0]=1 } }'取出不相鄰的重複行,同時保持它們出現的順序。有關此命令的更多信息,請參閱http://jeetworks.org/node/94

您可以在此添加最後一步以獲得更漂亮的輸出。將所有內容都轉換爲xargs -L 1 git log --oneline -1,並獲取修訂列表的相應提交消息。我有一個奇怪的問題,使用這最後一步,我不得不繼續按下輸出的幾個修訂版。我不確定這是爲什麼,這就是爲什麼我沒有將其納入我的解決方案。

+1

恭喜!非常好和簡潔,接下來的步驟是自動計算更新的線路範圍。但這是一個很好的開始。你會對解決下一個難題感興趣嗎?我可以開一個賞金嗎? :-) –

+0

這是一個非常有趣的問題!不幸的是,本週我工作很忙,所以我沒有機會玩這個遊戲。不過,我會記住,如果其他人到那時還沒有解決問題,我會在下週再回來。 –

1

我喜歡這個謎題,它有它的微妙之處。源文件,說init foo.rb 1000,1005並按照說明。完成之後,文件@changes將按照拓撲順序具有正確的提交列表,並且@blames將具有來自每個提交的實際責任輸出。

這比the accepted solution above複雜得多。它產生的輸出有時更有用,難以重現,而且編碼很有趣。

嘗試跟蹤歷史行數時自動跟蹤行號範圍的問題是,如果更改大塊穿過行號範圍的邊界,則無法自動確定新範圍邊界應位於該塊的位置,而您我們必須包括一個大範圍的大型增加項,並且積累(有時候很多)不相關的變化,或者進入手動模式以確保它是正確的(這當然會讓你回到這裏),或者有時會接受極度的損失。

如果你想要你的輸出是準確的,使用上面的答案與可信賴的正則表達式範圍,如`/^type function(/,/ ^} /',或者使用它,這實際上並不壞,一對夫婦每秒退後一步

爲了換取額外的複雜性,它在拓撲序列中產生了hitlist,它至少(相當成功地)試圖改善每一步的痛苦。 ,例如,更新範圍使得調整行號更容易,當然還有可能性不得不單獨對眼睛的視線進行調整:-P

要在全自動狀態下運行此功能,請說{ 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}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]="\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 
} 
+0

我認爲這是它,但必須測試,因爲你沒有提供任何示例。希望你能得到賞金,但它很快就會結束,並有一個答案投票3(儘管它根本沒有回答挑戰!) –

+0

對不起,只是檢查它,它不是真正的自動,它取決於vim我會在上面考慮一個簡單的答案,不考慮不同的行號,但是要簡單得多並且對於問題陳述非常有效。 –

+0

@JoaoTavora第二次看上面的手冊清單更新步驟(以及所有的複雜性)是無用的,初始清單已經是正確的。我糾正後得到的答案看起來很像他,除了允許跟蹤漂移。事實證明,你可以做一個相當有用的工作自動跟蹤,但正確的答案是隻使用正則表達式邊界 - 基於行號的跟蹤來自添加跨越範圍邊界時unmoored,因爲只有正則表達式有任何希望自動在追加的行中找到新的邊界。 – jthill

1

請參考這裏發佈的答案List all commits for a specific file。它正是你需要的。

+0

當然,這回答我的問題,但我不認爲它是可用的時候我發佈的問題。儘管如此,它是否足夠靈活地跟蹤跨歷史的*移動*語言特定結構。 *移動*的意思是起始和結束行範圍不是靜態的。 –