Получить журнал фиксации для определенной строки в файле?

503

Есть ли способ заставить git выдать вам журнал коммитов только для коммитов, которые коснулись определенной строки в файле?

Как git blame, но git blameпокажет вам последний коммит, который коснулся конкретной строки.

Мне бы очень хотелось получить похожий журнал, а не список коммитов в любом месте файла, а только коммиты, которые коснулись определенной строки.

jrochkind
источник
2
Смотрите также: Git обвиняют - предыдущие коммиты
joeytwiddle

Ответы:

632

Смотрите также Git: узнайте, какие коммиты когда-либо касались ряда строк .


Начиная с Git 1.8.4 , git logприходится -Lпросматривать эволюцию ряда линий.

Например, предположим, вы смотрите на git blameвывод. Здесь -L 150,+11означает «только посмотрите на строки от 150 до 150 + 11»:

$ git blame -L 150,+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.

Затем используйте git log. Здесь -L 155,155:git-web--browse.shозначает «проследить эволюцию строк с 155 по 155 в названном файле git-web--browse.sh».

$ git log --pretty=short -u -L 155,155:git-web--browse.sh
commit 81f42f11496b9117273939c98d270af273c8a463
Author: Giuseppe Bilotta <giuseppe.bilotta@gmail.com>

    web--browse: support opera, seamonkey and elinks

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>

    web--browse: coding style

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>

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

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)
Мэтт МакКлюр
источник
2
'git log --topo-order --graph -u -L 155,155: git-web--browse.sh' - это привело к фатальной ошибке: 'недопустимое имя объекта 155,155'. Версия Git: 1.8.3.2. Какие-либо предложения?
BairDev
12
Обновление до Git 1.8.4 или новее.
Мэтт МакКлюр
4
Что делать, если я хочу знать «историю строки 155 в commitA» (вместо строки 155 в HEAD). Могу ли я просто использовать git log commitA-hash -L 155,155:file-name?
Ида
@ Flimm, у меня нет сильных предпочтений.
Мэтт МакКлюр
это работает хорошо, за исключением случаев, когда файл был перемещен / переименован ... кажется, что --follow не любит сочетаться с аргументами диапазона строк.
Майк Эллери,
67

Вы можете получить набор коммитов, используя кирку.

git log -S'the line from your file' -- path/to/your/file.txt

Это даст вам все коммиты, которые повлияли на этот текст в этом файле. Если файл был переименован в какой-то момент, вы можете добавить --follow-parent.

Если вы хотите проверять коммиты при каждом из этих правок, вы можете передать этот результат в git show:

git log ... | xargs -n 1 git show
Адам Димитрук
источник
5
Я не уверен, что вижу, как это помогает. Если текст был затронут, то строка больше не совпадает, поэтому кирка покажет вам только самые последние изменения. Затем вы должны будете делать git log -S'the previous version of the line'и так далее, точно так же, как и в конечном итоге git blame -L. И это будет намного медленнее, чем git blame, поскольку он должен искать текст везде, а не только в данном месте.
Каскабель
Вы можете использовать регулярное выражение там, чтобы сделать его более приемлемым. В настоящее время нет способа "перемещаться по патчам" назад во времени без каких-либо сложных сценариев. Я надеюсь, что Gitk получит эту функциональность в виде патча в будущем.
Адам Димитрук
3
почему это называется киркой ? :)
Ciprian Tomoiagă
43

Попробуйте использовать приведенную ниже команду, реализованную в Git 1.8.4.

git log -u -L <upperLimit>,<lowerLimit>:<path_to_filename>

Таким образом, в вашем случае upperLimitи lowerLimitявляется прикоснулсяline_number

Дополнительная информация - https://www.techpurohit.com/list-some-useful-git-commands

jitendrapurohit
источник
2
Я думаю, что --pretty=shortигнорируется при использовании -L. Пожалуйста, исправьте
Vic Seedoubleyew
14

Чрезвычайно простой способ сделать это - использовать vim-fugitive . Просто откройте файл в vim, выберите интересующую вас строку (и) и Vвведите

:Glog

Теперь вы можете использовать :cnextи, :cprevчтобы увидеть все ревизии файла, где эта строка изменена. В любой момент введите, :Gblameчтобы увидеть информацию о ша, авторе и дате.

Кори Кляйн
источник
13

Упрощение ответа @ Мэтта -

git blame -L14,15 -- <file_path>

Здесь вы получите вину за строки 14 to 15.

Так как -Loption ожидает Rangeв качестве параметра, мы не можем получить a Blameдля одной строки, используя -Loption` .

Ссылка

свопы
источник
10

Я не верю, что для этого есть что-то встроенное. Это усложняется тем фактом, что редко бывает, что одна строка меняется несколько раз без существенного изменения остальной части файла, так что вы, как правило, в конечном итоге сильно меняете номера строк.

Если вам повезло, что строка всегда имеет некоторую идентифицирующую характеристику, например, присваивание переменной, имя которой никогда не менялось, вы можете использовать выбор регулярного выражения для git blame -L. Например:

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

Но это находит только первый совпадение для этого регулярного выражения, поэтому, если у вас нет хорошего способа сопоставить строку, это не слишком полезно.

Вы могли бы взломать что-нибудь, я полагаю. У меня нет времени писать код только сейчас, но ... что-то в этом роде. Беги git blame -n -L $n,$n $file. Первое поле - это предыдущий коммит, а второе - номер строки в этом коммите, поскольку он мог измениться. Возьмите их и запустите git blame -n $n,$n $commit^ $file, т.е. то же самое, начиная с фиксации до последнего изменения файла.

(Обратите внимание, что это не удастся вам, если последний коммит, который изменил строку, был коммитом слияния. Основной способ это могло произойти, если строка была изменена как часть разрешения конфликта слияния.)

Редактировать: Я случайно встретил этот пост в списке рассылки с марта 2011 года, в котором упоминается об этом, tigи у git guiменя есть функция, которая поможет вам сделать это. Похоже, что функция была рассмотрена, но не завершена, для самого git.

Cascabel
источник
6

Это потребует git blameот каждой значимой ревизии показывать строку $LINEфайла $FILE:

git log --format=format:%H $FILE | xargs -L 1 git blame $FILE -L $LINE,$LINE

Как обычно, обвинение показывает номер редакции в начале каждой строки. Вы можете добавить

| sort | uniq -c

чтобы получить агрегированные результаты, что-то вроде списка коммитов, которые изменили эту строку. (Не совсем, если бы только код был перемещен, это могло бы показать один и тот же идентификатор коммита дважды для разного содержимого строки. Для более детального анализа вам нужно было бы выполнить запаздывающее сравнение git blameрезультатов для смежных коммитов. Кто-нибудь? )

krlmlr
источник
Опять же, я думаю, что это не работает, потому что он не отслеживает предыдущее местоположение этой линии. Так что, если линия была добавлена ​​2 коммитов назад, вы бы смотрели на другую строку
Vic Seedoubleyew
6

Вот решение, которое определяет псевдоним git, так что вы сможете использовать его следующим образом:

git rblame -M -n -L '/REGEX/,+1' FILE

Пример вывода:

00000000 18 (Not Committed Yet 2013-08-19 13:04:52 +0000 728) fooREGEXbar
15227b97 18 (User1 2013-07-11 18:51:26 +0000 728) fooREGEX
1748695d 23 (User2 2013-03-19 21:09:09 +0000 741) REGEXbar

Вы можете определить псевдоним в вашем .gitconfig или просто запустить следующую команду

git config alias.rblame !sh -c 'while line=$(git blame "$@" $commit 2>/dev/null); do commit=${line:0:8}^; [ 00000000^ == $commit ] && commit=$(git rev-parse HEAD); echo $line; done' dumb_param

Это уродливая однострочная строка, так что вот неясная эквивалентная функция bash:

git-rblame () {
    local commit line
    while line=$(git blame "$@" $commit 2>/dev/null); do
        commit="${line:0:8}^"
        if [ "00000000^" == "$commit" ]; then
            commit=$(git rev-parse HEAD)
        fi
        echo $line
    done
}

Решение кирки ( git log --pickaxe-regex -S'REGEX ' ) даст вам только добавление / удаление строк, а не другие изменения строки, содержащей регулярное выражение.

Ограничением этого решения является то, что git blame возвращает только 1-е совпадение с REGEX, поэтому, если существует несколько совпадений, рекурсия может «перепрыгнуть», чтобы следовать за другой строкой. Убедитесь, что проверили полный вывод истории, чтобы определить эти «скачки», а затем исправьте свой REGEX, чтобы игнорировать паразитные линии.

Наконец, вот альтернативная версия, которая запускает git show для каждого коммита, чтобы получить полный diff:

git config alias.rblameshow !sh -c 'while line=$(git blame "$@" $commit 2>/dev/null); do commit=${line:0:8}^; [ 00000000^ == $commit ] && commit=$(git rev-parse HEAD); git show $commit; done' dumb_param
Лукас Кимон
источник
2

Вы можете смешивать git blameи git logкоманды для получения сводки каждого коммита в команде git blame и добавлять их. Что-то вроде следующего скрипта bash + awk. Он добавляет сводку коммита как встроенный комментарий к коду.

git blame FILE_NAME | awk -F" " \
'{
   commit = substr($0, 0, 8);
   if (!a[commit]) {
     query = "git log --oneline -n 1 " commit " --";
     (query | getline a[commit]);
   }
   print $0 "  // " substr(a[commit], 9);
 }'

В одну строку:

git blame FILE_NAME | awk -F" " '{ commit = substr($0, 0, 8); if (!a[commit]) { query = "git log --oneline -n 1 " commit " --"; (query | getline a[commit]); } print $0 "  // " substr(a[commit], 9); }'
cserpell
источник
2

В моем случае номер строки сильно изменился с течением времени. Я также был на git 1.8.3, которая не поддерживает регулярные выражения в "git blame -L". (RHEL7 все еще имеет 1.8.3)

myfile=haproxy.cfg
git rev-list HEAD -- $myfile | while read i
do
    git diff -U0 ${i}^ $i $myfile | sed "s/^/$i /"
done | grep "<sometext>"

Один лайнер:

myfile=<myfile> ; git rev-list HEAD -- $myfile | while read i; do     git diff -U0 ${i}^ $i $myfile | sed "s/^/$i /"; done | grep "<sometext>"

Это, конечно, можно превратить в скрипт или функцию.

sastorsl
источник