Может ли Git действительно отслеживать перемещение одной функции из одного файла в другой? Если да, то как?

Несколько раз я встречал утверждение, что если вы переместите одну функцию из одного файла в другой, Git сможет ее отследить. Например, в этой записи говорится: "Линус говорит, что если вы переместите функцию из одного файла в другой, Git расскажет вам историю этой единственной функции при перемещении ".

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

Насколько я понимаю, Git хранит содержимое каждого файла как Blob, и каждый Blob имеет глобально уникальную идентичность, которая возникает из хэша SHA его содержимого и размера. Затем Git представляет папки как деревья. Любая информация о имени файла принадлежит дереву, а не BLOB-объекту, поэтому переименование файла, например, отображается как изменение в Tree, а не в Blob.

Итак, если у меня есть файл с именем «foo» с 20 функциями в нем и файл с именем «bar» с 5 функциями в нем, и я перемещаю одну из функций из foo в bar (в результате получается 19 и 6 соответственно), как Git может определить, что я переместил эту функцию из одного файла в другой?

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

Если бы Git действительно заглядывал внутрь отдельных файлов и вычислял blob для каждой функции (что было бы безумно / невозможно, потому что вам нужно было бы знать, как анализировать любой возможный язык), тогда я мог понять, как это могло быть возможно.

Итак ... утверждение верно или нет? И если это правильно, то чего не хватает в моем понимании?


person Charlie Flowers    schedule 05.02.2011    source источник
comment
Я не думаю, что он отслеживает функции, а скорее фрагменты кода, поэтому, если у вас есть 30-строчная функция и разбита ее на две 15-строчные функции, он будет отслеживать это почти так же, как если бы вы переместили всю функцию . Кто-нибудь поправьте меня, если я ошибаюсь, пожалуйста.   -  person MatrixFrog    schedule 06.02.2011
comment
Насколько я понимаю (что вполне может быть неправильным, и поэтому я спрашиваю), каждый файл соответствует не более чем одному Blob. Таким образом, разделение одной функции на две меньшие функции в одном файле просто приведет к замене вашего старого Blob новым Blob. Если это верно, то на самом деле он не отслеживает фрагменты кода, потому что никогда не заглядывает внутрь файла. Другими словами, его наименьшая степень детализации - это один целый файл.   -  person Charlie Flowers    schedule 06.02.2011
comment
Интересная идея интегрировать GIT с языковыми парсерами. Я предполагаю, что мне понадобится эта функциональность, чтобы язык Delphi мог разделить один * .pas на несколько файлов * .pas, где каждый файл pas содержит один объект и реализацию или около того. И затем, надеюсь, обновите эти разделенные файлы с изменениями в исходном файле. Так что это может быть использовано в качестве скрытого отслеживания;) может извлечь выгоду из работы по локальной реструктуризации в случае, если основной обслуживающий персонал не хочет реструктурировать.   -  person Skybuck Flying    schedule 30.06.2018


Ответы (5)


Эта функция предоставляется через git blame -C <file>.

Параметр -C заставляет git пытаться найти совпадения между добавлением или удалением фрагментов текста в просматриваемом файле и файлами, измененными в тех же наборах изменений. Дополнительные -C -C или -C -C -C расширяют поиск.

Попробуйте сами в тестовом репозитории с git blame -C, и вы увидите, что блок кода, который вы только что переместили, происходит из исходного файла, которому он принадлежал.

На странице руководства git help blame:

Начало линий автоматически отслеживается при переименовании всего файла (в настоящее время нет возможности отключить отслеживание переименований). Чтобы следить за строками, перемещенными из одного файла в другой, или за строками, которые были скопированы и вставлены из другого файла, и т. Д., См. Параметры -C и -M.

person JN Avila    schedule 19.05.2012
comment
В качестве теста я создал репо с тремя файлами и добавил строку в file1, а затем зафиксировал. Затем я переместил эту строку в файл2 и снова зафиксировал. Затем в file3 и зафиксировал. git blame -C10 file3 затем показал первую фиксацию, в которой эта строка была добавлена ​​в файл1, но я действительно хотел увидеть самую последнюю фиксацию, которая переместила эту строку (то есть фиксацию, которая переместила строку в файл2). любой способ добиться этого? Я получил некоторую полезную информацию, используя git log -S'my interesting line', но все еще не совсем то, что мне нужно. - person Johann; 26.04.2013
comment
@Johann, похоже, для этого подойдет обычный git blame. - person andrybak; 29.05.2017
comment
@andrybak Это 4 года спустя, поэтому я не помню, чего на самом деле пытался достичь. Но git blame будет показывать только самое последнее изменение в строке (независимо от того, было ли это движение или нет), где мой комментарий запрашивал самую последнюю фиксацию, которая переместила эту строку (предположительно, после того, как еще несколько коммитов изменили строку, были сделаны). - person Johann; 09.06.2017
comment
-CC и -CCC, похоже, не работают ... здесь, на git version 2.15.0.rc0, мне нужно передать изолированный -C переключатель несколько раз по отдельности, чтобы он имел задокументированный эффект. Документация kinda указывает на это, по крайней мере, неявно. Тем не менее, этот ответ и другие комментарии показывают, что это работало в прошлом. Хм. - person underscore_d; 17.10.2017
comment
Начиная с Git 2.15, я думаю, есть способ получше. - person Inigo; 05.12.2017

Начиная с Git 2.15, git diff теперь поддерживает обнаружение перемещенных строк с помощью --color-moved вариант. Он работает для перемещений по файлам.

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

Для поведения по умолчанию попробуйте

git diff --color-moved

Команда также принимает параметры, которыми в настоящее время являются no, default, plain, zebra и dimmed_zebra (используйте git help diff, чтобы получить последние параметры и их описания). Например:

git diff --color-moved=zebra

Что касается того, как это делается, вы можете почерпнуть некоторое понимание из эта электронная почта, отправленная автором функциональности.

person Inigo    schedule 09.11.2017
comment
Есть ли способ настроить git, чтобы он применял параметр --color-moved по умолчанию? - person Eugen Konkov; 06.01.2018
comment
@EugenKonkov Да, используйте git config, чтобы установить diff.colorMoved. - person Inigo; 07.01.2018

Часть этой функциональности находится в git gui blame (+ имя файла). Он показывает аннотацию строк файла, каждая из которых указывает, когда он был создан и когда последний раз изменялся. Для перемещения кода по файлу он показывает фиксацию исходного файла как создание, а фиксацию, в которой он был добавлен в текущий файл, как последнее изменение. Попробуй.

На самом деле я бы хотел передать git log в качестве аргумента диапазон номеров строк в дополнение к пути к файлу, а затем он показал бы историю этого блока кода. При правильной документации такого варианта нет. Да, судя по заявлению Линуса, я тоже думаю, что такая команда должна быть легко доступна.

person Paŭlo Ebermann    schedule 06.02.2011
comment
Я только сейчас впервые увидел вину gui. Хороший. Я начинаю думать, что, возможно, именно это имел в виду Линус. Не то, чтобы Git внутренне хранит информацию о том, что функция перемещена из одного файла в другой, но что, учитывая информацию, которую Git хранит хранит, вы можете определить, что функция переместилась (например, git gui blame или через diff, как я упоминал в вопросе). Если это так, то это будет означать, что я правильно понимаю, что все дело в коммитах, деревьях и больших двоичных объектах, а Git никогда не заглядывает внутрь файла. Но этого достаточно, чтобы вы могли обнаружить перемещение функции с помощью анализа. Возможно. - person Charlie Flowers; 06.02.2011
comment
Да, думаю, это все. Бэкэнд git теперь ничего не делает с содержимым файла (кроме, возможно, хранения его немного оптимизированного по размеру как diff), но инструменты внешнего интерфейса должны делать все. - person Paŭlo Ebermann; 06.02.2011
comment
Кажется, есть одна проблема ... как мне пройти по истории в хронологическом порядке? Это немного топовое ... - person ; 28.04.2014
comment
@AgentFriday вам может потребоваться установить это отдельно. Например, в Ubuntu он доступен в пакете git-gui. - person Paŭlo Ebermann; 21.01.2020

git на самом деле не отслеживает переименования вообще. Переименование - это просто удаление и добавление, вот и все. Любые инструменты, которые показывают переименования, реконструируют их из этой исторической информации.

Таким образом, функция отслеживания переименований - это простой вопрос анализа различий всех файлов в каждой фиксации постфактум. В этом нет ничего особенно невозможного; существующее отслеживание переименования уже обрабатывает «нечеткие» переименования, при которых в файл вносятся некоторые изменения, а также его переименование; это требует просмотра содержимого файлов. Это было бы простое расширение для поиска переименований функций.

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

person bdonlan    schedule 05.02.2011
comment
Я не имел в виду переименование функций. Скорее, я спрашиваю о случае перемещения подмножества текста одного файла из этого файла в другой файл. - person Charlie Flowers; 05.02.2011
comment
вы правы, но ваш комментарий неясен, и первые несколько слов предполагают (мне), что вы неправильно поняли Q, отредактируйте его или что-то в этом роде. по теме, git использует (system?) diff, и это все, что у него есть над этим, он может отслеживать переименование функций, но это не особенно умно. По сути, это просто разница в одну строку, и вы можете отслеживать это. - person Tomas Pruzina; 22.08.2012

Там git diff, который покажет вам, что определенные строки исчезли из foo и снова появились в bar. Если в той же фиксации в этих файлах нет других изменений, это изменение будет легко обнаружить.

Интеллектуальный git клиент сможет показать вам, как строки перемещаются из одного файла в другой. Среда IDE с поддержкой языка могла бы соотнести это изменение с конкретной функцией.

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

person 9000    schedule 05.02.2011
comment
Есть ли существующий клиент, который позволяет человеку отображать историю функции? - person William Pursell; 05.02.2011
comment
Уильям: вам следует попробовать git gui blame path / to / filename.ext или git blame -CCCw path / to / filename.ext (первый имеет довольно удобный графический интерфейс, а второй включает лучшую диагностику жестких перемещений и копий). К сожалению, я думаю, что нет возможности передать параметры -CCCw для git gui blame. - person Mikko Rantalainen; 01.09.2011
comment
На самом деле git gui blame можно использовать для получения результатов git blame -CCCw, используя git новее 1.5.3 и выбрав Do full copy detection из контекстного меню правой кнопки мыши после загрузки файла (я только что проверил исходный файл в / usr /share/git-gui/lib/blame.tcl). - person Mikko Rantalainen; 01.09.2011
comment
@MikkoRantalainen -CC или -CCC когда-нибудь работали? Сейчас они точно не кажутся (git версия 2.15.0.rc0) - person underscore_d; 17.10.2017
comment
@underscore_d Получаете ли вы какое-нибудь предупреждение? По-прежнему, похоже, работает с git version 2.7.4, а git help blame знает о -C: когда эта опция дается три раза, команда дополнительно ищет копии из других файлов в любом коммите. - person Mikko Rantalainen; 18.10.2017
comment
@MikkoRantalainen Да, но для этого требуется, чтобы параметр задавался несколько раз в виде дискретных переключателей, то есть -C -C или -C -C -C. Объединение C в одном аргументе не дает правильного эффекта для 2 или 3 C в моей версии. На данный момент -C задокументирован как принимающий необязательный числовой аргумент, так что, возможно, это не всегда было, и может быть то, что приводит к тому, что последовательные Cs не имеют желаемого эффекта (например, git пытается и не может интерпретировать 2-е C как число , и т.д.) - person underscore_d; 18.10.2017
comment
@underscore_d Я не уверен, что -CCC когда-либо работал правильно (возможно, он просто не смог вывести сообщение об ошибке в более старых версиях, хотя он анализировался таким же образом). Я согласен с тем, что с необязательным аргументом синтаксис -CCC может быть нестабильным в долгосрочной перспективе. Таким образом, вероятно, кто-то захочет использовать вместо этого git blame -C -C -C -w -- path/to/file.ext. - person Mikko Rantalainen; 19.10.2017