Git: узнайте, какие коммиты когда-либо касались диапазона линий
у меня возникли проблемы, выясняя, как использовать git blame
для получения набора коммитов, что когда-нибудь коснулся заданного диапазона строк. Есть подобные вопросы, как этот но принятый ответ не приносит мне много дальше.
Допустим, у меня есть определение, которое начинается на линии 1000 из foo.rb
. Это всего лишь 5 строк, но количество коммитов, которые когда-либо меняли эти строки, огромная. Если я это сделаю
git blame foo.rb -L 1000,+5
Я вам ссылки на (самое большее) пять различных коммитов, которые изменили эти строки, но меня также интересуют коммиты "за ними".
аналогично,
git rev-list HEAD -- foo.rb | xargs git log --oneline
это почти то, что я хочу, но я не могу указать диапазоны git rev-list
могу ли я передать флаг git blame
чтобы получить список коммитов, которые когда-либо касались этих пяти строк, или каков самый быстрый способ построить скрипт, который извлекает такую информацию? Давайте на данный момент проигнорируем возможность того, что после определения более или менее 5 строк.
6 ответов:
Начиная С Git 1.8.4,
git log
и-L
для просмотра эволюции ряда линий.например, предположим, что вы смотрите на С:
((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)
если вы часто используете эту функцию, вы можете найти полезный псевдоним 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:-}:""'" -
и теперь вы можете просто сделать
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[]) { print ; h[]=1 } }'
это выведет номер ревизии для каждой фиксации, которая имеет редактирование выбранных строк.
вот шаги:
первая часть
git rev-list HEAD -- foo.rb
выводит все ревизии, в которых редактируется выбранный файл.каждая из этих ревизий затем переходит во вторую часть, которая берет каждый из них и помещает его в
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
дает нам первый столбец (номер ревизии) вывода вины.awk '{ if (!h[]) { print ; h[]=1 } }'
вынимает несмежные повторяющиеся строки, сохраняя порядок, в котором они появились. Видеть http://jeetworks.org/node/94 для получения дополнительной информации об этой команде.вы можете добавить последний шаг здесь, чтобы получить более красивый выход. Труба все в
xargs -L 1 git log --oneline -1
и получить соответствующее сообщение фиксации для списка ревизий. У меня была странная проблема, используя этот последний шаг, где я должен был продолжать нажимать next каждые несколько ревизий, которые были выведены. Я не уверен, почему это было, поэтому я не включил его в свое решение.
Не уверен, что вы хотите сделать, но, возможно,git log-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.
вы можете поместить в строку изменение (или часть изменения) вы пытаетесь следовать, и это будет список коммитов, которые когда-либо касались этого изменения.
мне понравилась эта головоломка, в ней есть свои тонкости. Источник этого файла, скажем
init foo.rb 1000,1005
и следуйте инструкциям. Когда вы закончите, файл@changes
будет иметь правильный список коммитов в топологическом порядке и@blames
будет иметь фактический выход вины от каждого.Это значительно сложнее, чем принятое решение выше. Он производит вывод, который иногда будет более полезным и трудно воспроизводимым, и было весело кодировать.
проблема при попытке отслеживать диапазоны номеров строк автоматически, шагая назад по истории, если изменение пересекает границы диапазона с номерами строк, вы не можете автоматически определить, где в этом куске должна быть новая граница диапазона, и вам придется либо включить большой диапазон для больших дополнений и поэтому накапливать (иногда много) нерелевантных изменений, либо перейти в ручной режим, чтобы убедиться, что это правильно (что, конечно, возвращает вас сюда), либо принять экстремальные потери время от времени.
если вы хотите, чтобы ваш вывод был точным, используйте ответ выше с надежными диапазонами регулярных выражений, такими как `/^type function (/,/^}/', или используйте это, что на самом деле не так уж плохо, пару секунд на шаг назад во времени.
в обмен на дополнительную сложность, он создает список попаданий в топологической последовательности, и он по крайней мере (довольно успешно) пытается улучшить боль на каждом шаге. Например, он никогда не запускает избыточную вину, а update-ranges делает корректирующую линию цифры проще. И, конечно, есть надежность того, что пришлось индивидуально глазеть на куски... :- 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=; 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(,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 }
пожалуйста, обратитесь к ответу размещенные здесь Список всех коммитов для конкретного файла. Это именно то, что вам нужно.
несколько мыслей..
Это звучит похоже на этот пост, и похоже, что вы могли бы приблизиться к чему-то вроде этого:
git blame -L '/variable_name *= */',+1
пока вы знаете определение, чтобы соответствовать (для регулярного выражения).
есть какой-нить здесь, примерно через
tig
иgit gui
(который, по-видимому, может справиться с этим). Я еще не пробовал это сам, поэтому не могу проверить это (я дам это попробовать позже).