2013-01-03 30 views
52

Tôi đang gặp khó khăn trong việc tìm cách sử dụng git blame để nhận tập hợp cam kết bao giờ chạm vào một phạm vi nhất định. Có những câu hỏi tương tự như this one nhưng câu trả lời được chấp nhận không mang lại cho tôi nhiều hơn nữa.Git: khám phá những cam kết bao giờ chạm vào một loạt các dòng

Giả sử tôi có định nghĩa bắt đầu trên dòng 1000 của foo.rb. Nó chỉ dài 5 dòng, nhưng số lượng cam kết đã từng thay đổi những dòng đó là rất lớn. Nếu tôi làm

git blame foo.rb -L 1000,+5 

Tôi nhận được tham chiếu đến (nhiều nhất) năm cam kết riêng biệt đã thay đổi những dòng này, nhưng tôi cũng quan tâm đến các cam kết "phía sau".

Tương tự,

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

là gần như những gì tôi muốn, nhưng tôi không thể xác định dao động dòng để git rev-list

Tôi có thể vượt qua một lá cờ để git blame để có được danh sách các cam kết rằng bao giờ chạm vào những năm dòng, hoặc cách nhanh nhất để xây dựng kịch bản trích xuất thông tin đó là gì? Hãy bỏ qua cho thời điểm khả năng mà định nghĩa một lần có nhiều hơn hoặc ít hơn 5 dòng.

+0

Bạn có chắc chắn đây là những gì bạn muốn không? Xác định các thay đổi với số dòng chỉ hoạt động đối với một trạng thái nhất định của tệp. Nếu bạn muốn dòng 15 - 20 cho commit '12345' mã trên các dòng đó có thể nằm trên các dòng 55 - 60 cho commit' 12345^'. – asm

+0

Khá chắc chắn. Đây là lý do tại sao tôi cần phải viết một kịch bản xác định đó là tốt. Tuy nhiên, vì lý do đơn giản, định nghĩa chưa bao giờ chuyển trong tệp từ lần commit ban đầu trong repo. –

+5

có thể trùng lặp của [Lấy nhật ký cam kết cho một dòng cụ thể trong một tệp?] (Http://stackoverflow.com/questions/8435343/retrieve-the-commit-log-for-a-specific-line-in-a -file) –

Trả lời

52

Since Git 1.8.4, git log-L để xem sự phát triển của một loạt các dòng.

Ví dụ, giả sử bạn nhìn vào sản lượng git blame 's:

((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]" & 

Và bạn muốn biết lịch sử của ngày nay là dòng 155.

Sau đó:

((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) 

Nếu bạn sử dụng chức năng này thường xuyên, bạn có thể tìm thấy một bí danh git hữu ích. Để làm điều đó, đưa vào ~/.gitconfig của bạn:

[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"'" - 

Và bây giờ bạn chỉ có thể làm git follow git-web--browse.sh 155.

+0

Dấu hiệu '-' cuối cùng nào trong bí danh của bạn có nghĩa là gì? –

10

Không chắc chắn những gì bạn muốn làm, nhưng có lẽ git log -S có thể làm các trick cho bạn:

-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. 

Bạn có thể đặt trong chuỗi sự thay đổi (hoặc một phần của thay đổi) bạn đang cố gắng làm theo và điều này sẽ liệt kê các cam kết đã từng chạm vào thay đổi này.

+0

Xin lỗi, đó không phải là những gì Tôi là sau khi tất cả. nhưng +1 để thử anyway –

+3

+1 bởi vì nó giúp các nhà cung cấp dịch vụ có vấn đề tương tự như tiêu đề câu hỏi này – wim

0

Một vài suy nghĩ ..

này nghe có vẻ giống với this post, và có vẻ như bạn có thể nhận được gần gũi với một cái gì đó như thế này:

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

Chừng nào bạn biết định nghĩa để phù hợp với (cho regex).

Có một chủ đề discussion here, về cách sử dụng tiggit gui (có vẻ như có thể xử lý việc này). Tôi chưa thử điều này bản thân mình, vì vậy không thể xác minh nó (Tôi sẽ cung cấp cho một thử sau này).

19

Tôi nghĩ rằng đây là những gì bạn muốn:

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

đó sẽ ra các số phiên bản cho từng cam kết rằng có chỉnh sửa các dòng bạn đã chọn.

Sau đây là các bước sau:

  1. Phần đầu git rev-list HEAD -- foo.rb đầu ra tất cả các phiên bản, trong đó các tập tin được lựa chọn được chỉnh sửa.

  2. Mỗi trong số các sửa đổi đó sau đó đi vào phần thứ hai, mỗi phiên bản và đặt nó vào git blame -l -L 1000,+5 $rev -- foo.rb | cut -d ' ' -f 1. Đây là lệnh gồm hai phần.

    1. git blame -l -L 1000,+5 $rev -- foo.rb dẫn đến đổ lỗi cho các dòng đã chọn. Bằng cách cho nó ăn số sửa đổi, chúng tôi đang yêu cầu nó bắt đầu từ cam kết đó và đi từ đó, thay vì bắt đầu từ đầu.
    2. Vì đổ lỗi cho ra một loạt thông tin chúng tôi không cần, cut -d ' ' -f 1 cho chúng ta cột đầu tiên (số sửa đổi) của đầu ra đổ lỗi.
  3. awk '{ if (!h[$0]) { print $0; h[$0]=1 } }' đưa ra các dòng trùng lặp không liền kề trong khi duy trì thứ tự chúng xuất hiện. Xem http://jeetworks.org/node/94 để biết thêm thông tin về lệnh này.

Bạn có thể thêm bước cuối cùng vào đây để có được sản lượng đẹp hơn. Chia mọi thứ thành xargs -L 1 git log --oneline -1 và nhận thông báo cam kết tương ứng cho danh sách các bản sửa đổi. Tôi đã có một vấn đề kỳ lạ bằng cách sử dụng bước cuối cùng này, nơi tôi đã phải tiếp tục nhấn tiếp theo mỗi vài sửa đổi được sản lượng. Tôi không chắc tại sao đó là lý do tại sao tôi không đưa nó vào giải pháp của mình.

+1

Xin chúc mừng! Rất đẹp và súc tích, các bước tiếp theo sẽ là tính toán phạm vi dòng được cập nhật tự động. Nhưng đây là một khởi đầu tuyệt vời. Bạn có muốn giải quyết câu đố tiếp theo không? Tôi sẽ mở một tiền thưởng khác? :-) –

+0

Đó là một câu hỏi rất thú vị! Tôi, thật không may, rất bận rộn trong công việc tuần này, vì vậy tôi sẽ không có cơ hội để chơi với điều này. Mặc dù vậy, nó sẽ xuất hiện trên tâm trí của tôi và tôi sẽ quay lại nó vào tuần tới nếu có ai đó chưa giải quyết nó. –

1

Tôi thích câu đố này, nó có sự tinh tế của nó. Nguồn tập tin này, nói init foo.rb 1000,1005 và làm theo hướng dẫn. Khi bạn hoàn tất, tệp @changes sẽ có danh sách cam kết chính xác theo thứ tự cấu trúc liên kết và @blames sẽ có đầu ra đổ lỗi thực tế từ mỗi đơn hàng.

Điều này phức tạp hơn nhiều so với the accepted solution above. Nó tạo ra đầu ra đôi khi sẽ hữu ích hơn, và khó tái tạo, và thật thú vị khi viết mã.

Sự cố khi cố gắng tự động theo dõi phạm vi số dòng trong khi bước lùi qua lịch sử là một đoạn đường thay đổi vượt qua ranh giới phạm vi được đánh số dòng bạn không thể tự động xác định vị trí trong đoạn ranh giới phạm vi mới đó sẽ phải bao gồm một phạm vi lớn để bổ sung lớn và vì vậy tích lũy (đôi khi rất nhiều) thay đổi không liên quan, hoặc thả vào chế độ thủ công để đảm bảo nó đúng (tất nhiên là bạn quay lại ngay tại đây), hoặc chấp nhận tổn thất nghiêm trọng ở lần .

Nếu bạn muốn kết quả chính xác, hãy sử dụng câu trả lời ở trên với phạm vi regex đáng tin cậy như `/^type function (/,/^}/', hoặc sử dụng cái này, Để đổi lấy độ phức tạp thêm, nó tạo ra danh sách hit trong chuỗi tôpô và ít nhất nó (khá thành công) cố gắng cải thiện cơn đau ở mỗi bước. Ví dụ, và cập nhật phạm vi làm cho điều chỉnh số dòng dễ dàng hơn. Và tất nhiên có độ tin cậy của việc phải có nhãn cầu riêng từng hunks ... :-P

Để chạy tự động hoàn toàn, hãy nói { 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

Tôi nghĩ rằng đây là nó, nhưng phải kiểm tra, vì bạn không cung cấp ví dụ. Hy vọng bạn có thể nhận được tiền thưởng, nhưng nó kết thúc khá sớm và có một câu trả lời bình chọn 3 (hoàn toàn không trả lời thử thách!) –

+0

Xin lỗi, chỉ cần kiểm tra nó và nó không thực sự tự động và nó phụ thuộc vào vim đi với một câu trả lời đơn giản ở trên mà không có số dòng khác nhau trong việc xem xét nhưng là đơn giản hơn nhiều và hoạt động hoàn toàn tốt cho tuyên bố vấn đề. –

+0

@JoaoTavora Trên một cái nhìn thứ hai cập nhật danh sách kiểm tra hướng dẫn sử dụng bước trên (và tất cả sự phức tạp) là vô ích, danh sách kiểm tra ban đầu là đã đúng. Câu trả lời tôi nhận được sau khi sửa chữa cho rằng trông rất giống như của mình, ngoại trừ cho phép theo dõi trôi dạt. Hóa ra bạn có thể thực hiện một công việc khá hữu ích để tự động theo dõi, nhưng câu trả lời đúng là chỉ sử dụng ranh giới regexp - theo dõi dựa trên số dòng không được thả neo khi bổ sung vượt qua ranh giới phạm vi, bởi vì chỉ có regexps có hy vọng tự động tìm ranh giới mới trong các dòng được thêm vào. – jthill

1

Vui lòng tham khảo câu trả lời được đăng tại đây List all commits for a specific file. Chính xác những gì bạn cần.

+0

Được cấp, điều này trả lời câu hỏi của tôi, mặc dù tôi không nghĩ rằng nó có sẵn khi tôi đăng câu hỏi. Mặc dù, nó có đủ linh hoạt để theo dõi một cấu trúc ngôn ngữ cụ thể * di chuyển * trong lịch sử hay không. * di chuyển * theo nghĩa là phạm vi dòng bắt đầu và kết thúc không tĩnh. –

Các vấn đề liên quan