Git: получение пути к файлу при заданном хэше коммита - PullRequest
2 голосов
/ 27 марта 2020

Скажем, у меня есть хранилище, где у меня есть файл, a.txt, который в какой-то момент был переименован в b.txt. Скажем, это происходило долгое время go в истории, или, может быть, несколько раз, поэтому я не совсем уверен, что имя файла было при определенной фиксации.

Если я запускаю git log на b.txt с флагом --follow это выглядит примерно так:

$ git log --name-status --pretty=oneline --abbrev-commit --follow -- 'b.txt'

45c5d11 (HEAD -> master) Edit to 'b.txt'
M       b.txt
cb4ce19 Renaming 'a.txt' to 'b.txt'
R100    a.txt   b.txt
13973ff Edit to 'a.txt'
M       a.txt
9620e34 Adding 'a.txt'
A       a.txt

Именно то, что я хочу! Но когда я пытаюсь show файл при коммите, когда он был назван как-то еще, я получаю эту ошибку:

$ git show 9620e34:'b.txt'
fatal: path 'b.txt' exists on disk, but not in '9620e34'

Итак, есть ли:

  • А) Как заставить show следовать за переименованием файлов, как log? Или
  • B) Команда, возвращающая путь к определенному файлу при наличии коммита ha sh?

1 Ответ

4 голосов
/ 27 марта 2020

Короткий ответ - просто "нет". :-) По сути, вы должны запустить git log --follow и найти изменение имени самостоятельно, если вообще можете его найти, а затем вручную использовать правильное имя.

Настоящая проблема здесь заключается в том, что каждый коммит является полным, но независимым снимком всех ваших файлов:

  • Commit 9620e34 содержит один файл, a.txt. Это все, что у него есть. У него нет b.txt. (Ну, у него могут быть другие файлы, которые не являются ни a.txt, ни b.txt; из вышеизложенного я не могу сказать, так ли это.)

  • Commit 13973ff имеет полную (но другую) копию a.txt.

  • Commit cb4ce19 имеет полную копию b.txt и a.txt вообще не существует. Копия b.txt в cb4ce19 соответствует 100%, копия a.txt в 13973ff, но это два разных имени файла.

  • Последний коммит, 45c5d11, имеет полную (но различную) копию b.txt.

Когда вы используете git log, git log выполняет проход по коммитам, один коммит в время, начинающееся там, где вы сейчас находитесь (или где вы указываете) и работающее в обратном направлении. При желании он может сравнивать каждый коммит со своим (единственным) родителем, 1 с родительским коммитом слева и дочерним коммитом справа. Сравнение двух снимков - diff - выдает в качестве выходных данных рецепт: Выполните эти действия, и вы измените файлы левого снимка так, чтобы они соответствовали файлам правого снимка. Обычно это довольно близко к тому, что кто-то на самом деле делал, или даже к тому, что он делал. Иногда это совсем не то, что они делали, но он выдает тот же результат .

С --name-status, git log печатает только имя файла и букву состояния, а не полный рецепт. 2 Если в рецепте указано «переименовать этот файл», вы получите R букву и индекс сходства (в процентах): 100 означает совпадение файлов, 100% идентичные слева и справа стороны. Этот тип соответствия намного быстрее, чем приблизительный, поэтому, когда вы делаете переименование, не изменяя также и содержимое файла, как в этом случае, будет приятно Git. (Иногда было бы неплохо, если бы Git было к вам лучше.)

Добавление опции --follow включает Git обнаружение переименования механизм. Он может включаться или не включаться для других операций - вы можете настроить git diff для управления этим, например, при запуске git diff без указания -M, чтобы включить его вручную, но это гарантирует, что он включен для этого особый вид git log. Для поиска переименований необходимо запустить детектор переименования. Детектор переименования является вычислительно дорогим (хотя и гораздо менее подходящим для 100% совпадений), поэтому git log операции, которые ему не нужны, обычно вообще не используют его.

Однако реализация --follow , довольно неряшливо: внутренне Git отбирает каждый коммит до одного интересующего файла в дочернем коммите, а затем сравнивает этот файл с его аналогом в родительском коммите. Если файл не в родительском файле, Git вызывает детектор переименования для этого одного файла (а не для всех файлов, как обычно), чтобы проверить, может ли он найти соответствующий -content-but-Different-name в родительском файле. 3 Если это так, он просто меняет имя , которое ищет, сообщая вам об этом переименовании. С этого момента, помните, git log работает в обратном направлении - он начинает искать старое имя вместо нового.

Когда вы используете git show, вы указываете напрямую к некоторому коммиту . Он не попадает туда, начиная с последнего коммита и работая в обратном направлении, как это делает git log. Он начинается прямо с коммита, который вы называете. У этого коммита есть родительский идентификатор ha sh (хранится в метаданных коммита), поэтому git show может git diff родитель и потомок, если вы используете его как git show <hash>; эта фиксация имеет моментальный снимок, и git show может извлечь и отобразить один файл, если вы используете его как git show <hash>:<path>. Но он не пошел назад от коммита, в котором вы сейчас находитесь, к указанному вами коммиту, поэтому он не знает, может ли приведенный здесь path соответствовать какому-то другому, path в указанном вами коммите.

Git, вероятно, должна иметь команду для отслеживания имени файла: вы бы указали ему необязательную начальную точку, по умолчанию HEAD, a имя пути и целевой коммит; и это даст результат выполнения эквивалента git log --follow для этого имени и обнаружения переименований до достижения цели, или выдаст ошибку - или, возможно, просто выведет исходный путь - если он никогда не достигнет этого целевого коммита. Это была бы полезная сантехническая команда. (Вероятно, он также должен иметь несколько путей, и детектор переименования git log должен быть расширен и использован для реализации этой команды.) Но он не существует.


1 Когда git log достигает коммита слияния , который, по определению, является одним с двумя или более родителями, git log обычно просто вообще не выполняет никакого сравнения. При использовании --follow он несколько меняет стратегии, но --follow плохо работает при слияниях.

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

3 При просмотре слияний я не уверен что детектор переименования делает. У него есть все родители, но код здесь очень короток и труден для подражания. В любом случае есть только одна глобальная переменная, содержащая имя, за которым следует подпись, поэтому, если имя отличается у двух разных родителей, код буквально не может обработать это правильно, для любого разумного определения «правильно».

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...