Знать, какие файлы были созданы, изменены или удалены в коммите - PullRequest
0 голосов
/ 16 марта 2019

В настоящее время я пытаюсь отобразить информацию о конкретном коммите в моем приложении.

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

Ответы [ 2 ]

1 голос
/ 16 марта 2019

TL; DR

Используйте -m --first-parent вместе с --name-status, чтобы получить то, что вы хотите, через слияния.Обратите внимание, что --first-parent изменяет способ git log обхода графика, если вы используете это с git log -p вместо git show.

Long

Вы упомянули git show напрямую:

... если я использую git show, полученная информация будет количеством удаленных или добавленных строк.

Здесь стоит указать как git show создает список различий.Это начинается с обзора того, что коммит является .

Поскольку книга Pro Git говорит , каждый коммит действует как снимок всехиз ваших исходных файлов.Другими словами, коммиты не говорят Внесите эти изменения в некоторые файлы, которые у вас уже есть .Вместо этого коммиты говорят: Если вы хотите этот коммит, вот файлы - все они целы.Распакуйте и отправляйтесь!

Проблема с хранением дельт или наборов изменений очевидна.Предположим, все, что я вам скажу, это файл изменений main.py, добавив эти три строки в середине .У тебя даже нет main.py.Как вы собираетесь добавить три строки в середине?

Проблемы с сохранением целых файлов также очевидны, конечно:

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

    Но это просто глупо, потому что мои 1000 коммитов, вероятно, содержат как минимум 300 копий этого файла, которые все одинаковы .Следующие 300, вероятно, также все те же и т. Д. - возможно, есть только четыре версии большого файла.И каждый сделанный коммит является постоянным (в основном - иногда возможно полностью удалить некоторые коммиты) и доступен только для чтения (полностью - ни один коммит не может быть изменен когда-либо ; в лучшем случае вы делаете замену и удаляетесовсем плохой).

    Я буквально не могу изменить копию файла, который я вставил, поэтому, если 300 фиксирует все, используйте эту версию файлаони могут просто поделиться этой версией файла.Это означает, что мои 1000 коммитов имеют только четыре копии файла размером 100 КБ, используя 400 КБ, а не 100 МБ, с коэффициентом сжатия 250.

    Git имеет дополнительные отстающиеТрюки, чтобы сжать это еще дальше.В общем, Git добавляет сжатие zlib deflate ко всему, и, в частности, Git также незаметно внедряет дельта-кодирование во время того, что Git называет процессом сборки мусора .Таким образом, каждый коммит имеет полную копию файла на логическом уровне, но (а) он сжат и (b) где-то глубоко в недрах Git, файл может быть внутренне delta -сжатые против других копий файла.Но вам не нужно знать что-либо из этого, чтобы использовать Git: на уровне "У меня есть фиксация" или "У меня нет фиксации", вы либо делаетеиметь коммит - в этом случае у вас do есть все его файлы - или у вас нет коммита, и вы даже не можете спросить, есть ли у вас его файлы.

  • Другое возражение более серьезное, потому что это проблема с выполнением работы.В частности, если коммит представляет собой снимок , как мы будем обрабатывать такие вещи, как проверки кода и выяснение, где какая-то ошибка была введена или исправлена?Как мы можем взять исправление, которое мы сделали, для одной версии программы и применить его к другой другой версии?

От снимка кдельта / changeset

Если выВы знакомы с инструментами, существовавшими до Git и многими другими системами управления хранением, вы знаете довольно древнюю команду Unix diff.Эта команда является, по крайней мере, источником вдохновения и, возможно, даже прямым предком git diff.Используя git diff, мы можем сравнить любые два коммита и заставить Git сообщить нам , что изменилось с коммита A на коммит H, например.

По сути, если мы скажем Git:

git diff hash1 hash2

Git просто извлекает коммит, идентифицируемый hash1 , а затем коммит, идентифицируемый hash2 , а затем переводит их,Вуаля, мы знаем, что изменилось между A (hash1) и H (hash2)!

Но подождите: каждый commit в Git не только сохраняет снимок из своих файлов он также хранит идентификатор хеша своего родительского коммита .Хэш-идентификатор каждого коммита представляет собой большую некрасивую строку букв и цифр, которая однозначно идентифицирует одного конкретного коммита .Ни один другой коммит не может иметь такой же хэш-идентификатор.Каждый другой коммит получает другой хэш-идентификатор.Идентификаторы хеша на самом деле являются криптографическими контрольными суммами содержимого коммитов, поэтому почему мы не можем ничего изменить, что мы зафиксировали: Git использует этот , чтобы сохранить метод криптографической контрольной суммы , чтобы уникальным образомидентифицируйте все, что может быть идентифицировано однозначно, как это. 1

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

A <-B <-C

Commit C имеет некоторый хэш-идентификатор.Commit C сохраняет снимок всех файлов.И в commit C хранится идентификатор хеша commit B.Поэтому, если мы знаем хеш-идентификатор C, мы можем найти его в гигантской базе данных Git «все коммиты / объекты в этом хранилище» - пока не этот гигант, есть только три коммита - и использовать это, чтобы найти B хеш-идентификатор, который мы можем найти в базе данных Git, чтобы найти A.

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

A--B--C   <-- master

name master позволяет нам найти commit C, который позволяет нам найти B, что позволяет нам найти A.Commit A имеет no parent - Git называет root commit - который сообщает нам, что цепочка заканчивается, и мы закончили.

Все этоэто довольно скучный способ добраться до того, что git show может показать нам, что мы изменили в коммите C.Это достигается путем просмотра сохраненного родительского хэша .Родитель C - B.Чтобы показать , что мы сделали в C, Git делает:

git diff <hash-of-B> <hash-of-C>

Мы уже знаем, что это по сути извлекает два коммита и сравнивает их.Теперь очевидно, что это сравнивает снимок в B со снимком в C - и, по определению, это то, что мы изменили .


1 Это включает в себя снимки файлов - так Git удается сохранить только четыре копии файла размером 100 КБ.Файл сводится к контрольной сумме, а контрольная сумма является именем версии содержимого, хранящейся в базе данных Git.Эти версии содержимого хранятся как объекты, которые Git называет объектами blob . имя уровня файловой системы файла, такое как big-file.dat, хранится в отдельном объекте, который Git вызывает дерево объект.

По сути,Сердцем Git-репозитория является коллекция объектов , хранящаяся в базе данных ключ-значение.Ключи - это хэш-идентификаторы, а значения - базовый коммит, дерево, блоб или объект четвертого типа, который Git вызывает аннотированный тег объект.Вам не нужно знать этот , чтобы использовать Git.Вам просто нужно знать, что коммиты имеют хеш-идентификаторы и что эти хеш-идентификаторы образуют своего рода сложную цепочку.Но это может помочь получить полное представление о том, что происходит.


Почему это не работает для слияний

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

...--H    <-- common-starting-point

Затем мы сделаем две новые ветви и сделаем один коммитна каждой ветви, так что теперь есть два новых коммитов I и J с новыми именами, указывающими на них:

       I   <-- branch1
      /
...--H    <-- common-starting-point
      \
       J   <-- branch2

Отсюда мы сделаем два more фиксирует (и останавливает рисование в имени common starting point) только для красивости и / или чтобы я мог вызвать коммит слияния M, например:: -)

       I--K   <-- branch1
      /
...--H
      \
       J--L   <-- branch2

Теперь мы делаем коммит слияния M, используя, например, git checkout branch1 && git merge branch2, что дает нам такой результат:

       I--K
      /    \
...--H      M   <-- branch1
      \    /
       J--L   <-- branch2

Обратите внимание, что name branch1 указывает на наш новый коммит M. Коммит M хранит снимок всех файлов, как и любой другой коммит. В нем есть что-то особенное.

Обычное правило добавления новых коммитов заключается в том, что новыйcommit указывает обратно на его непосредственного родителя.Для M это будет K - фиксация, на которую указывало имя branch1 непосредственно перед тем, как мы запустили git merge.Таким образом, M хранит хэш-идентификатор commit K.Но что делает M коммит слияния , так это то, что M хранит второго родителя тоже.Мы сказали Git слить коммиты K и L, поэтому M имеет K в качестве первого родителя , но затем имеет второго родителя L.

(Тот факт, что мы использовали git merge для создания M, и что git merge вернулся к фиксации H в порядке до make M, нигде не хранится. Я бы сказал, что это должно быть - по крайней мере, что-то об этом должно быть сохранено в коммите - потому что есть способы запустить git merge, которые изменяют его действие, напримериспользуя -X ours или --find-renames=<number>. Но Git не хранит это сейчас, и поскольку ни один из существующих коммитов не может когда-либо быть измененным , мы должны быть в состоянии обойтись безэту информацию. По большей части, мы можем.)

В любом случае, после того, как мы произвели слияние, у нас есть этот коммит M, который имеет небольшую особенность, потому что он имеет двое родителей вместо обычного.Мы называем это merge commit , который использует слово merge в качестве прилагательного, модифицирующего commit .Или, иногда, мы просто называем это слиянием , используя слово merge в качестве существительного.Вот почему я делаю большое различие между формой глагол , для слияния , то есть для вызова механизма слияния Git - например, путем запуска git merge - и существительным форма, слияние . Слияние - это вещь, а слияние - это действие, которое часто приводит к слиянию .

Итак, вернемся к git show: давайте сделаем git show show commit M.Обычный способ, которым git show показывает коммит - или, скорее, показывает, что мы сделали в коммите - это сделать:

git diff <hash-of-parent> <hash-of-commit>

Но коммит M не имеет a родитель.Совершить M имеет два родителей.Какой из них git show следует дать git diff?

git log -p и git show

Давайте таБыстрая поездка сюда. Команда git log имеет -p, чтобы показать каждый коммит как патч. То есть git log -p похоже на многократное выполнение git show: оно показывает сообщение журнала коммита, а затем превращает этот снимок в патч. Это именно то, что делает git show. Затем git log переходит к родителю коммита и показывает сообщение коммита и патч; затем идет к родителю родителя и так далее. Другими словами, с хорошей прямой линией коммитов H, затем G, затем F, затем ..., она идет назад по этой прямой линии, показывая H, затем G, затем F и так далее. .

Когда git log получает коммит слияния , как M, возникает две проблемы:

  • Как вы показываете слияние в виде патча? Это hard , а git log отвечает на этот вопрос простым ответом: Я не .

    Другими словами, git log -p просто не мешает показать патч. В любом случае, это ответ по умолчанию.

  • Учитывая, что у слияния M есть два родителя, какого родителя вы показываете следующим? Это тоже сложно, но git log отвечает на это, говоря: Я показываю обоих. Конечно, он должен выбрать один, чтобы пойти до другого, и здесь все может быть сложно. Так как сейчас нас не волнует git log, мы проигнорируем эту часть.

Команда git show не такая ленивая, как команда git log. Не нужно продолжать регистрироваться между обоими родителями, поэтому он готов усердно работать на show M в качестве патча . Но то, что он делает, немного странно.

Commit M - это слияние, вероятно, выполненное с помощью git merge. Если слияние прошло успешно - если не было конфликтов слияния - тогда Git принял все решения о , как сделать слияние. Так что в этом случае git show по умолчанию не показывает ничего . Но если было конфликтами слияния, то кто бы ни делал слияние, он должен был их разрешать. В этом случае git show показывает, где произошли конфликты слияния .

В этом случае Git создает то, что Git называет комбинированный diff . Мы берем слияние M и сравниваем его с родителем # 1, т.е. фиксируем K, выполняя обычный diff-файл-пара-коммитов. Некоторые файлы изменены в этом diff, а некоторые нет. Затем мы берем слияние M и сравниваем его с родителем # 2, т.е. фиксируем L. Некоторые файлы изменены в этом diff, а некоторые нет. Итак, теперь у нас есть два списка измененных файлов:

 M-vs-K       M-vs-L
--------     --------
README.md
main.py      main.py
             stuff.py

В файлах и имеется только один измененный файл, поэтому далее Git отбрасывает списки README.md и stuff.py diff. Теперь он готов к объединению списков различий для main.py.

Что делает этот шаг объединения, немного сложно описать (и не задокументировано). Использование -c приводит к получению неплотного результата, а использование --cc дает плотного результата (если только не произойдет переполнение очереди обнаружения переименования, в этом случае Git возвращается к -c и выдает предупреждение). Обратите внимание, что мы уже выбросили два из трех файлов - которые не меняются, независимо от того, является ли здесь плотный / неплотный - но теперь, в стандартном плотном режиме или режиме --cc, Git выбрасывает некоторые из Diff Hunks а также!

По сути, git diff --cc делает здесь попытку показать только те области, где требуется ручное объединение . Конечно, если вы использовали -X ours или -X theirs, слияние вручную на самом деле не требовалось - вместо этого Git просто занял «нашу» или «их» сторону, но git diff --cc все равно покажет этот дифференциал.

В неплотном режиме git diff -c может показывать дополнительные возможности различий, хотя код для этого немного лаконичен, и я не уверен, что правильно прочитал его при быстром сканировании. Если вы хотите проверить это самостоятельно, вы можете найти этот код в Объединение-diff.cc .

Кеоднако, здесь мы рассмотрим - часть, которая задокументирована и имеет значение для первоначального вопроса, такова: Комбинированный diff нарочно игнорирует множество реальных различий, чтобы попытаться показать вам только что-тоСоответствующий.Это делает смелое и часто необоснованное предположение о том, что вы считаете уместным.Будьте осторожны с комбинированными разностями.

Обратите внимание, что комбинированные различия не возникают , когда вы даете git diff два коммиты для сравнения.Вы получаете комбинированный diff, запустив команду, которая автоматически выбирает родительские хеш-идентификаторы .Когда он попадает в слияние, он автоматически выбирает всех родителей, и - zap - вы получаете комбинированную разницу.

Что делает работает для слияний

Давайте на минуту вернемся к графику:

       I--K
      /    \
...--H      M   <-- branch1
      \    /
       J--L   <-- branch2

Большинство команд Git "show-a-commit-as-a-patch" различают родителя коммита против коммита.Но commit M - это merge с двумя родителями, поэтому эти команды либо вообще ничего не показывают , либо показывают комбинированный diff .Если это не то, что вам нужно, вам нужно взять под свой контроль.

Следовательно, если у вас есть какое-либо имя коммита или идентификатор хеша, например M (идентификатор хеша) или branch1 (имя), и выЧтобы увидеть, что изменилось между первым родителем M и M, вы можете сделать это:

git diff M^ M

или:

git diff branch1^ branch1

Здесь мы используем оператор hat-суффикса, чтобы сказать перейти к первому родителю .(Мы также можем использовать ~1, что означает один раз вернуться к первому родителю . Суффикс тильды предназначен для случаев, когда вы хотите вернуться к нескольким первичным родителям: вы можете написать branch1~2 длянапример, перейдите от M к K, а затем к I Для тех, кто застрял в оболочке, для которой требуется набрать ^^ вместо ^ - я понимаю, что это проблема в некоторых системах DOS / Windows- вы можете использовать ~ всегда, так как branch1~ означает branch1~1, что означает то же самое, что и branch1^.)

Обе git log и git show - которые разделяют большую часть их кода;в частности, они разделяют весь код, который вызывает для вас git diff - есть две интересные опции:

  • -m "разбивает" коммит слияния (m означает слияние).

    Как мы неоднократно видели здесь, слияние типа M имеет двух родителей.Использование опции -m указывает внутреннему разностному коду «разделить» слияние на две виртуальные фиксации.Вместо:

           I--K
          /    \
    ...--H      M
          \    /
           J--L
    

    код diff обрабатывает это как:

           I--K--M1
          /
    ...--H
          \
           J--L--M2
    

    только для целей сравнения.Два виртуальных коммита M1 и M2 используют снимок M, но имеют другое "имя".После такого разделения у них теперь есть один родитель каждый , и git show или git log могут выполнять git diff дважды .Первый git diff видит это как K против M1 и производит один diff, а второй git diff видит это как L против M2 и производит один diff.

    Теперь у вас есть две разницы, по одной для каждого родителя.(Если M - это слияние осьминога , с тремя или более родителями вы получите три или более различий - по одному на каждого родителя.)

  • --first-parentговорит git log или git show смотреть только на первый родительский элемент каждого слияния.Поскольку git show не просматривает график, это не оказывает на него никакого реального влияния, если только вы не включите -m для разделения слияния при диффузии.С git log он говорит Git идти от слияния обратно через только своего первого родителя, и добавление -m также влияет на листинг diff, если вы используете -p для его получения.

Это дает нам то, что работает для слияний:

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

  • Или мыing -m --first-parent заставляет git log -p или git show разделить слияние на два виртуальных слияния, а затем использовать только первый родительский при запуске внутреннего git diff для показа патча.

Если вы используете git log -p или git show или git diff с опцией --name-status, чтобы показывать только имена файлов и статус этого файла - A для добавленных, D для удаленных, M для модифицированных и т. д. - это приводит к поражению кода комбинированных различий, который, предполагая, что вы хотите знать, где были конфликты слияния , приводит к неправильному ответу на слияние.

1 голос
/ 16 марта 2019

Я полагаю, добавление опции --name-status покажет, что вы хотите.

Git docs: git-show

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