Git - как просмотреть историю изменений метода / функции?

93

Итак, я нашел вопрос о том, как просмотреть историю изменений файла, но история изменений этого конкретного файла огромна, и меня действительно интересуют только изменения определенного метода. Итак, можно ли увидеть историю изменений только для этого конкретного метода?

Я знаю, что для этого потребуется git для анализа кода и что анализ будет отличаться для разных языков, но объявления методов / функций выглядят очень похоже на большинстве языков, поэтому я подумал, что, возможно, кто-то реализовал эту функцию.

Язык, с которым я сейчас работаю, - Objective-C, а SCM, который я сейчас использую, - git, но мне было бы интересно узнать, существует ли эта функция для какого-либо SCM / языка.

Эрик Б.
источник
1
Я видел такую ​​функциональность в предложении Git GSoG.
Vi.
Это то предложение, о котором вы говорили? lists-archives.org/git/…
Erik B
@lpapp, вопрос был задан 10 месяцами ранее, другой вопрос должен быть помечен как дурацкий (если они вообще дураки).
Dirty-flow
2
@lpapp Это два совершенно разных вопроса. Вы могли бы написать сценарий, который преобразовывает имя функции в диапазон строк, а затем использовать технику из этого вопроса, чтобы получить историю для этих строк, но сам по себе он не отвечает на этот вопрос.
Erik B

Ответы:

100

В последних версиях git logвыучили особую форму -Lпараметра:

-L: <имя функции>: <файл>

Проследите эволюцию диапазона строк, заданного "<start>,<end>"(или регулярным выражением имени функции <funcname>) внутри <file>. Вы не можете указывать какие-либо ограничители pathspec. В настоящее время это ограничено обходом, начиная с одной ревизии, т. Е. Вы можете указать только ноль или один положительный аргумент ревизии. Вы можете указать эту опцию более одного раза.
...
Если “:<funcname>”указано вместо <start>и <end>, это регулярное выражение, обозначающее диапазон от первой совпадающей строки имени функции <funcname>до следующей строки имени функции. “:<funcname>”выполняет поиск с конца предыдущего -Lдиапазона, если таковой имеется, в противном случае - с начала файла. “^:<funcname>”поиск с начала файла.

Другими словами: если вы попросите Git git log -L :myfunction:path/to/myfile.c, он с радостью распечатает историю изменений этой функции.

Eckes
источник
16
это может работать для objective-c из коробки, но если вы делаете это для других языков (например, Python, Ruby и т. д.), вам может потребоваться добавить соответствующую конфигурацию внутри файла .gitattributes, чтобы git распознал функцию / метод декларации на этом языке. Для python используйте * .py diff = python, для ruby ​​используйте * .rb diff = ruby
samaspin 09
1
Как git отслеживает функцию?
nn0p
@ nn0p Я предполагаю, что знаю синтаксис нескольких языков и, следовательно, знаю, как изолировать функцию и отслеживать ее изменения.
JasonGenX
4
Расширяя комментарий @ samaspin, для других языков вы можете обратиться к документации здесь: git-scm.com/docs/gitattributes#_generating_diff_text
edhgoose
Это не работает для таких языков, как Scala, Java и даже C, которые используют вложенные фигурные скобки (регулярные выражения не могут справиться с этим в целом), и даже начальная и конечная формы не работают, когда функция перемещается в файл. и изменен в том же коммите.
Робин Грин
16

Использование git gui blameтрудно использовать в сценариях, и в то время , git log -Gи git log --pickaxeкаждый может показать вам , когда определение метода появилось или исчезло, я не нашел способ заставить их перечислить все изменения , внесенные в тело вашего метода.

Тем не менее, вы можете использовать gitattributesи textconvсвойство кусочки решения , которое делает именно это. Хотя эти функции изначально предназначались для помощи в работе с двоичными файлами, они работают и здесь.

Ключ состоит в том, чтобы Git удалил из файла все строки, кроме тех, которые вам интересны, перед выполнением любых операций сравнения. Тогда git log, git diffи т. Д. Будут видеть только интересующую вас область.

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

  • Напишите короткий сценарий оболочки (или другую программу), который принимает один аргумент - имя исходного файла - и выводит только интересную часть этого файла (или ничего, если ни один из них не интересен). Например, вы можете использовать sedследующее:

    #!/bin/sh
    sed -n -e '/^int my_func(/,/^}/ p' "$1"
    
  • Определите textconvфильтр Git для вашего нового скрипта. (См. Дополнительную информацию на gitattributesстранице руководства .) Имя фильтра и расположение команды могут быть любыми, как вам угодно.

    $ git config diff.my_filter.textconv /path/to/my_script
    
  • Скажите Git использовать этот фильтр перед вычислением различий для рассматриваемого файла.

    $ echo "my_file diff=my_filter" >> .gitattributes
    
  • Теперь, если вы используете -G.(обратите внимание на .), чтобы перечислить все коммиты, которые производят видимые изменения при применении вашего фильтра, у вас будут именно те коммиты, которые вас интересуют. Любые другие параметры, использующие процедуры сравнения Git, такие как --patch, будут также получите это ограниченное представление.

    $ git log -G. --patch my_file
    
  • Вуаля!

Одно полезное улучшение, которое вы, возможно, захотите сделать, - это сделать так, чтобы ваш скрипт фильтра принимал имя метода в качестве первого аргумента (и файл в качестве второго). Это позволяет вам указать новый интересующий метод, просто вызвав его git config, а не редактировать скрипт. Например, вы можете сказать:

$ git config diff.my_filter.textconv "/path/to/my_command other_func"

Конечно, сценарий фильтра может делать все, что угодно, принимать больше аргументов или что-то еще: есть большая гибкость, помимо того, что я показал здесь.

Пол Уиттакер
источник
1
У меня не получилось взять более одного аргумента, но добавление имени функции в hard отлично работает, и это действительно здорово!
qwertzguy 06
Великолепно, хотя мне интересно, насколько (не) удобно переключаться между множеством различных методов. Кроме того, знаете ли вы о программе, которая может извлечь целую C-подобную функцию?
nafg
12

В git log есть опция '-G', которую можно использовать для поиска всех различий.

-G Искать различия, добавленная или удаленная строка которых соответствует заданной <regex>.

Просто дайте ему правильное регулярное выражение имени функции, которая вам нужна. Например,

$ git log --oneline -G'^int commit_tree'
40d52ff make commit_tree a library function
81b50f3 Move 'builtin-*' into a 'builtin/' subdirectory
7b9c0a6 git-commit-tree: make it usable from other builtins
lht
источник
5
Я не запускал команду, но мне кажется, что эта команда будет показывать только те коммиты, которые касаются строки, соответствующей регулярному выражению, а это не весь метод / функцию.
Erik B
если вы хотите больше контекста, вы можете заменить его --onelineна-p
lfender6445
3
Но что, если в методе было внесено изменение на 20 строк?
nafg
+1. Для меня верхний ответ сработал, но показал только самую последнюю фиксацию. Возможно, из-за перебазирования или нескольких перемещений функции, не уверен. Путем поиска фактической строки кода вместо функции, в которой она была (хотя и однострочно), этот ответ позволил мне легко определить фиксацию, которую я искал. К счастью, я обычно пишу полезные сообщения о коммитах!
Dave S
11

Самое близкое, что вы можете сделать, - это определить положение вашей функции в файле (например, скажем, ваша функция i_am_buggyнаходится в строках 241-263 foo/bar.c), а затем запустить что-нибудь, чтобы добиться эффекта:

git log -p -L 200,300:foo/bar.c

Это откроет меньше (или эквивалентный пейджер). Теперь вы можете ввести /i_am_buggy(или эквивалент на пейджере) и приступить к внесению изменений.

Это может даже сработать, в зависимости от вашего стиля кода:

git log -p -L /int i_am_buggy\(/,+30:foo/bar.c

Это ограничивает поиск с первого попадания этого регулярного выражения (в идеале, объявления вашей функции) до тридцати строк после этого. Конечный аргумент также может быть регулярным выражением, хотя обнаружение этого с помощью регулярного выражения является более сомнительным предложением.

плохой
источник
Милая! К вашему сведению, это новое в Git v1.8.4. (Думаю, мне следует обновить.) Хотя было бы неплохо более точное решение ... например, если бы кто-то написал сценарий ответа Пола Уиттакера.
Грег Прайс,
@GregPrice Очевидно, границы поиска могут быть даже регулярными выражениями , так что вы можете иметь хотя бы более или менее точную начальную точку.
badp
Ух ты. Фактически: вместо того, чтобы писать свое собственное регулярное выражение, вы можете просто сказать -L ":int myfunc:foo/bar.c"и ограничить функцию с этим именем. Это фантастика - спасибо за указатель! Если бы только обнаружение функций было немного более надежным ...
Грег Прайс
3

Правильный способ - использовать, git log -L :function:path/to/fileкак описано в ответе eckes .

Но кроме того, если ваша функция очень длинная, вы можете захотеть видеть только изменения, внесенные различными фиксациями, а не целые функциональные строки, включая неизмененные, для каждой фиксации, которая может касаться только одной из этих строк. Вроде нормальный diff.

Обычно git logможно просматривать различия -p, но это не работает -L. Поэтому вам нужно grep git log -Lотображать только задействованные строки и заголовок коммитов / файлов, чтобы контекстуализировать их. Хитрость здесь в том, чтобы сопоставить только цветные линии терминала, добавив --colorпереключатель, с регулярным выражением. В заключение:

git log -L :function:path/to/file --color | grep --color=never -E -e "^(^[\[[0-9;]*[a-zA-Z])+" -3

Обратите внимание, что это ^[должно быть фактическим, буквальным ^[. Вы можете ввести их, нажав ^ V ^ [в bash, то есть Ctrl+ V, Ctrl+ [. Ссылка здесь .

Также последний -3переключатель позволяет печатать 3 строки контекста вывода до и после каждой совпадающей строки. Вы можете настроить его под свои нужды.

Hazzard17
источник
2

git blame показывает, кто последним изменил каждую строку файла; вы можете указать строки для проверки, чтобы избежать получения истории строк за пределами вашей функции.

Будет
источник
4
с помощью git gui blameвы можете перемещаться по более ранним версиям.
Vi.
0
  1. Показать историю функций, git log -L :<funcname>:<file>как показано в ответе eckes и git doc

    Если ничего не отображается, обратитесь к разделу «Определение настраиваемого заголовка фрагмента», чтобы добавить *.java diff=javaв .gitattributes файл что-то подобное для поддержки вашего языка.

  2. Показать историю функций между коммитами с помощью git log commit1..commit2 -L :functionName:filePath

  3. Показать историю перегруженных функций (может быть много функций с тем же именем, но с разными параметрами) с git log -L :sum\(double:filepath

Крис Руф
источник