Сравнение различий при перебазировании в Git

Предположим, что я только что перебазировал ветку foo на master с конфликтами. Я хочу убедиться, что я случайно не повредил содержимое foo во время разрешения конфликта, внеся дополнительные изменения или потеряв изменения (кроме тех, которые подходят для разрешения конфликта). Я сделал это через:

diff -u <(git diff `git merge-base master foo@{1}` foo@{1}) \
        <(git diff `git merge-base master foo    ` foo    )

(обновление: или эквивалентный синтаксис ... для git-diff, о котором мне только что напомнили :)

diff -u <(git diff master...foo@{1}) <(git diff master...foo) | mate

Это показывает мне все изменения, которые произошли с master..foo, рассматриваемым как исправление, и это именно то, что я хочу проверить на предмет минимальности. Однако вызов сложен, и вывод не совсем прост для интерпретации.

Есть ли лучший способ выполнить эту задачу — предоставить ту же информацию, но с использованием лучшего метода или формата — или я должен просто взять все вышеперечисленное и заключить его в сценарий?


person Kevin Reid    schedule 17.09.2012    source источник
comment
Разве это не эквивалентно git diff foo@{1} foo?   -  person twalberg    schedule 17.09.2012
comment
@twalberg Нет; git diff foo@{1} foo показывает как мои разрешения конфликтов, так и изменения, внесенные в мастер, на который я перебазировался, а не только разрешения конфликтов.   -  person Kevin Reid    schedule 17.09.2012
comment
Ах, да... Я рассматривал только свой самый распространенный случай перебазирования, в котором база слияния источника и места назначения остается неизменной (переписывание/раздавливание ветки перед отправкой).   -  person twalberg    schedule 17.09.2012
comment
Связано: stackoverflow.com/questions/16304574/   -  person Ioannis Filippidis    schedule 09.09.2017
comment
Заголовок вопроса также имеет отношение к сравнению только по предмету коммитов, так что обнаруживаются почти эквивалентные коммиты (это не было бы даже с git cherry). В этом случае можно использовать git log со средством просмотра различий: stackoverflow.com/a/46127413/1959808   -  person Ioannis Filippidis    schedule 09.09.2017
comment
В Git 2.19 (3 ​​квартал 2018 г.) не забудьте git range-diff @{u} @{1} @. См. мой ответ ниже.   -  person VonC    schedule 22.08.2018


Ответы (6)


Что мы действительно хотим показать, так это конфликтный комбинированный diff, который генерируется, как если бы мы выполнили слияние, чтобы перейти от (a) к (b), где (a) ранее основывалось на (upstream-old), а (b) теперь на основе (upstream-новый).

Нам не нужен только прямой дифференциал.

Вместо этого мы можем по существу выполнить слияние, но заставить результирующее дерево быть $b^{tree}, которое, как мы уже знаем, является правильной «конечной» точкой того, что мы хотим.

Более или менее, давайте предположим, что у нас есть

newcommit -> the new version of the series
oldcommit -> the old version of the series
upstream -> the (new) version of the base of the series

Мы можем сгенерировать слияние через

git commit-tree newcommit^{tree} -p oldcommit -p upstream -m "message"

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

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

К сожалению, я не смог понять, как просто заставить «git diff» различаться таким же образом, не создавая дерево. Я не уверен, какие аргументы мы должны были бы передать, чтобы добраться до этой точки.

person Jake    schedule 21.10.2016
comment
Умная идея! Мне нужно будет посмотреть, насколько полезными будут результаты в следующий раз, когда у меня возникнет эта проблема. - person Kevin Reid; 22.10.2016
comment
У меня только что была возможность попробовать это. Я обнаружил предостережение, которое необходимо отметить: upstream удален файл, в котором были изменения (чтобы разделить его на несколько других файлов), и в diff, полученном с помощью этой техники, этот файл вообще не упоминается. , поэтому не будет показывать потерю этих изменений. - person Kevin Reid; 27.11.2016

Даже лучше, чем interdiff, теперь с Git 2.19 (3 ​​квартал 2018 г.) у вас есть git range-diff.
См. «Git diff — два непересекающихся диапазона ревизий< /а>"

Документация git range-diff включает следующий пример:

Если при перебазировании требуется разрешить конфликты слияния, сравните изменения, внесенные непосредственно после перебазирования, используя:

$ git range-diff @{u} @{1} @

Типичный вывод git range-diff будет выглядеть так:

------------
-:  ------- > 1:  0ddba11 Prepare for the inevitable!
1:  c0debee = 2:  cab005e Add a helpful message at the start
2:  f00dbal ! 3:  decafe1 Describe a bug
    @@ -1,3 +1,3 @@
     Author: A U Thor <[email protected]>

    -TODO: Describe a bug
    +Describe a bug
    @@ -324,5 +324,6
      This is expected.

    -+What is unexpected is that it will also crash.
    ++Unexpectedly, it also crashes. This is a bug, and the jury is
    ++still out there how to fix it best. See ticket #314 for details.

    Contact
3:  bedead < -:  ------- TO-UNDO
------------

В этом примере есть 3 старых и 3 новых коммита, где разработчик:

  • удалил 3-й,
  • добавил новый перед первыми двумя, и
  • изменил сообщение коммита 2-го коммита, а также его diff.

Когда вывод поступает на терминал, он по умолчанию имеет цветовую кодировку, как и обычный вывод git diff. Кроме того, первая строка (добавление фиксации) зеленая, последняя строка (удаление фиксации) — красная, вторая строка (с полным совпадением) — желтая, как и заголовок фиксации вывода git show, а третья строка окрашивает старый коммит красный, новый зеленый, а остальные похожи на заголовок коммита git show.


В Git 2.20 цвета лучше поддерживаются для нового вида (диапазона) различий.

См. коммит 2543a64, коммит 8d5ccb5, (17 августа 2018 г.) и коммит 4441067, зафиксировать f103a6f, фиксация 29ef759, фиксация 017ac45, коммит 9d1e16b, коммит 84120cc, коммит c5e64ca, Стефан Беллер (stefanbeller).
(объединено Хунио С. Хамано -- gitster -- в коммит 30035d1, 17 сентября 2018 г.)

range-diff: отступ специальных строк в зависимости от контекста

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

Если сделать отступ этих строк пробелом, они будут рассматриваться как контекст, что гораздо полезнее, например, в самой серии различий диапазонов:

git range-diff pr-1/dscho/branch-diff-v3...pr-1/dscho/branch-diff-v4

(из репозитория github.com/gitgitgadget/git)

[...]
    + diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt
    + new file mode 100644
    + --- /dev/null
    + +++ b/Documentation/git-range-diff.txt
    +@@
    ++git-range-diff(1)
[...]
    +
      diff --git a/Makefile b/Makefile
      --- a/Makefile
      +++ b/Makefile
[...]

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

Более поздние строки, указывающие на изменение Makefile, будут рассматриваться как контекст как во внешнем, так и во внутреннем различиях, так что эти строки останутся обычного цвета.

person VonC    schedule 21.08.2018

git-cherry ищет коммиты в одной ветке, которых нет в другой. Вы бы использовали его как:

git cherry -v OLDTIP TIP UPSTREAM

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

Аналогичного эффекта можно добиться, вернувшись к OLDTIP и выполнив git rebase -i TIP, поскольку при этом используется та же логика для заполнения списка коммитов.

person Thomas Leonard    schedule 26.09.2012
comment
Похоже, это не помогает при проверке изменений внутри файла. Меня интересуют изменения на уровне фрагментов, потерянные или поврежденные из-за плохого разрешения конфликтов, а не целые коммиты. - person Kevin Reid; 26.09.2012
comment
Идея здесь в том, чтобы уменьшить количество коммитов, которые вам нужно сравнить (в идеале до нуля). Это бесполезно, если у вас есть небольшое количество больших конфликтующих коммитов, но может помочь с большим количеством мелких коммитов. - person Thomas Leonard; 27.09.2012
comment
Ах, тогда я предполагаю, что по этому стандарту у меня небольшое количество крупных коммитов, потому что последующий вопрос, который пришел на ум, звучит так: «Если у меня есть коммиты для сравнения, как вы предлагаете мне сравнивать их, другие? чем выполнять diff-diff, с которого я начал?» - person Kevin Reid; 27.09.2012

Различия между мастером и перебазированным foo:

git diff master..foo

vs.

Отличия pre-rebase foo после отделения мастера (обратите внимание на три точки):

git diff master...foo@{1}

?

person user1338062    schedule 20.09.2012
comment
Спасибо, использование ... укорачивает команду, и я действительно использовал это раньше, поэтому должен был подумать об этом. Тем не менее, мне все еще остается явно сравнивать эти два различия. У вас есть предложение по этому поводу? - person Kevin Reid; 20.09.2012

Поскольку это перебазирование, а не слияние, вы можете сравнить foo с самим собой, но до слияния. Если я правильно помню, foo@{1} даст родителя текущего коммита для foo, и, возможно, это не то, что вы ищете.

Я думаю, вы можете сделать что-то вроде следующего (при условии, что вы не сделали git gc):

На ветке foo после перебазирования:

$ git reflog

Это покажет, как был перемещен HEAD вашей ветки. Вы должны увидеть некоторые записи, подобные этой (в зависимости от того, выполняете ли вы перебазирование в интерактивном режиме или нет):

64d3c9e HEAD@{15}: rebase -i (finish): returning to refs/heads/master
64d3c9e HEAD@{16}: rebase -i (fixup): Fixes external dependencies
049169e HEAD@{17}: rebase -i (fixup): updating HEAD
d4c2e69 HEAD@{18}: rebase -i (pick): Fixes external dependencies
049169e HEAD@{19}: rebase -i (fixup): Imports futures
e490bed HEAD@{20}: rebase -i (fixup): updating HEAD
...
etc
...

С нетерпением ждем последнего коммита foo перед слиянием. В зависимости от того, что вы сделали, это может быть сложно или нет. Вот почему я не предоставил сценарий сделай это.

Возьмите идентификатор фиксации, у которого есть последняя фиксация для foo перед перебазированием, а затем сравните с этим идентификатором фиксации. Скажем, идентификатор коммита: XXXX:

 git diff XXXX...foo

Может быть, это то, что вы хотите.

person manu    schedule 24.09.2012
comment
Как я ответил twalberg, это не исключает изменений в master, на которые я перебазировался, и поэтому очень шумно. (Кстати, копаться в журнале ссылок HEAD не обязательно; журнал ссылок branch будет содержать только состояния «до» и «после», а foo@{1} — это именно тот коммит, который вы предлагаете искать вручную. @ заглядывает в reflog, а не родословная.) - person Kevin Reid; 25.09.2012
comment
@KevinReid Ты прав, я перепутал @ с ~. Я не использую их часто. Извиняюсь. - person manu; 25.09.2012

Немного другой подход: у Git есть хорошая поддержка для отображения этого в случае слияний. то есть «что изменилось по отношению к родителю-1, что не может быть объяснено родителем-2?»

Есть несколько способов использовать эту поддержку для вашего rebase:

Опция 1:

  1. Сначала выполните одноразовое слияние (вместо перебазирования). Затем обычный дисплей слияния покажет, что изменилось в результате слияния. Проверьте, это то, что вы хотели.

  2. Вернитесь и перебазируйте и сравните перебазированный совет с результатом слияния — они должны быть идентичными.

Вариант 2 (если ребазинг проще слияния):

  1. Сделайте ребаз.
  2. Используйте прививку, чтобы она выглядела как слияние локально/временно.

Для этого создайте .git/info/grafts с одной строкой:

TIP UPSTREAM OLDTIP

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

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

Если вы действительно хотите сравнить два diff'а, вы можете взглянуть на interdiff(1), но я не уверен, насколько хорошо он справится с разными базовыми файлами.

person Thomas Leonard    schedule 26.09.2012
comment
Что касается варианта 1, в тех случаях, когда я хочу выполнить эту проверку, слияние, скорее всего, будет иметь другие конфликты, чем перебазирование, и в любом случае я хочу проверить, действительно ли перебазирование произошло. Что касается варианта 2, я попробовал его и обнаружил, что представление загромождено изменениями, внесенными в master, и это именно то, что я хочу скрыть. У меня нет interdiff, но на его справочной странице написано: «Оба различия должны относиться к одним и тем же файлам». - person Kevin Reid; 26.09.2012
comment
У меня есть аналогичный ответ для этого, который работает для серии и в основном просто использует дерево коммитов для создания слияния после перебазирования, предполагая, что результатом слияния должно быть то, что представляет собой дерево готовой перебазировки. Это просто позволяет нам по существу выполнить слияние из восходящего потока в v1 и заставить результат иметь дерево v2. Затем это можно передать в git-show, и он создаст прекрасный комбинированный формат, не полагаясь на прививки, и, таким образом, его можно автоматизировать. - person Jake; 21.10.2016