Как выполнить трехсторонний diff в Git без слияния?

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

я нашел много рекомендаций по SO (и несколько очень похожих вопросов (1, 2, 3) ) но я не нашел прямого ответа. Примечательно, что комментарий на ответ подразумевает, что то, чего я хочу, возможно, но это не сработало для меня. Надеюсь, что пользователь может перезвонить здесь:)

для фон, когда я выполняю слияния, я использую стиль конфликта "diff3":

git config --global merge.conflictstyle diff3

и git mergetool настроен на использование kdiff3.

при разрешении конфликтов слияния это показывает мне четыре файла:

  1. файл текущей ветви ($LOCAL)
  2. файл другой ветви ($REMOTE)
  3. файл, который является общим предком двух ветвей ($BASE)
  4. объединенный выходной файл ($MERGED)

однако, git difftool только потянет вверх две ветви советы. Я тоже хочу посмотреть базовый файл. чтобы быть ясным, я хочу иметь возможность выполнять этот diff до слияние, в том числе в файлах, без конфликтов слияния. (git mergetool показывает только три различия, если есть конфликты).


Частичное Решение #1:

С отдельным файлом я могу экспортировать три версии и вручную вызовите diff:

git show local_branch:filename > localfile
git show remote_branch:filename > remotefile
git show `git merge-base local_branch remote_branch`:filename > basefile

{kdiff3_path}/kdiff3.exe --L1 "Base" --L2 "Local" --L3 "Remote" -o "outputfile" basefile localfile remotefile &

есть две проблемы с этим:

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

Частичное Решение #2:

спасибо ответ и комментарий для вдохновение.

создайте пользовательский драйвер слияния, который всегда возвращает "false", что создает конфликтное состояние слияния без фактического автоматического слияния. Затем выполните изменения с помощью git mergetool. Затем прервите слияние, когда закончите.

  1. добавить .git/config:

    [merge "assert_conflict_states"]
        name = assert_conflict_states
        driver = false
    
  2. создать (или добавить) .git/info/attributes чтобы вызвать все слияния для использования нового драйвера:

    * merge=assert_conflict_states
    
  3. выполнить слияния, которые сейчас не automerging.

  4. сделайте разницу. В моем случае: git mergetool который вызывает трехстороннее слияние kdiff3.

  5. когда закончите, прервите слияние:git merge --abort.

  6. отменить Шаг #2.

это будет (сортировка) работать, за исключением того, что kdiff3 выполняет automerge при вызове, поэтому я все еще не могу см. предварительно объединенные различия. Я могу исправить это, изменив Файл драйвера ЖКТ акции kdiff3 (.../git-core/mergetools/kdiff3 на удалении --auto переключатель.

тем не менее, это имеет следующие проблемы с остановкой шоу:

  1. это работает только тогда, когда оба файла изменились! в случае, когда изменен только один файл, обновленный файл заменяет старый файл и слияние никогда не называл.
  2. я должен изменить драйвер git kdiff3,который вообще не переносится.
  3. я должен изменить attributes перед и после этого сравнения.
  4. и, конечно же, я надеялся сделать это без слияния:)

информация для "баунти":

согласно данным ответам, это невозможно со стандартным Git. Итак, теперь я ищу более нестандартное решение: как я могу настроить Git, чтобы это произошло?

вот один вывод: по-видимому, если только один из трех файлов изменился, этот новый файл используется в слиянии результат без фактического вызова драйвера слияния. Это означает, что мой пользовательский драйвер слияния "создание конфликтов" никогда не вызывается в этом случае. Если бы это было так, то мое "частичное решение № 2" фактически функционировало бы.

может ли это поведение быть изменено путем настройки файлов или конфигураций? Или, возможно, есть способ создать пользовательский драйвер diff? Я не готов начать играть с исходным кодом Git...

есть умные идеи?

4 ответов


Я хочу иметь возможность выполнять этот diff до слияние, в том числе в файлах, без конфликтов слияния.

вам просто нужно настроить индекс, как вам нравится, вам никогда не придется фиксировать результаты. Способ настроить именно то, что было задано, прямо diffs-since-base без подготовки к слиянию-это

git merge -s ours --no-ff --no-commit $your_other_tip

полный рулон, в котором git только устанавливает родителей для того, что вы в конечном итоге решите совершить в результате, но это вероятно, лучше сделать это с нормальным слиянием, все еще имея возможность попасть туда и изучить все,

git merge --no-ff --no-commit $your_other_tip

выбрать начальную точку, а затем

  1. принудительно объединить визит для всех записей, которые показывают любые изменения в либо совет:

    #!/bin/sh
    
    git checkout -m .
    
    # identify paths that show changes in either tip but were automerged
    scratch=`mktemp -t`
    sort <<EOD | uniq -u >"$scratch"
    $(  # paths that show changes at either tip:
        (   git diff --name-only  ...MERGE_HEAD
            git diff --name-only  MERGE_HEAD...
        ) | sort -u )
    $(  # paths that already show a conflict:
        git ls-files -u | cut -f2- )
    EOD
    
    # un-automerge them: strip the resolved-content entry and explicitly 
    # add the base/ours/theirs content entries
    git update-index --force-remove --stdin <"$scratch"
    stage_paths_from () {
            xargs -a "" -d\n git ls-tree -r  |
            sed "s/ [^ ]*//;s/\t/ \t/" |
            git update-index --index-info
    }
    stage_paths_from "$scratch" $(git merge-base @ MERGE_HEAD) 1 
    stage_paths_from "$scratch" @ 2
    stage_paths_from "$scratch" MERGE_HEAD 3
    
  2. ... если бы вы использовали vimdiff, Шаг 2 был бы просто git mergetool. vimdiff начинается с того, что находится в рабочем дереве, и не делает свой собственный automerge. Похоже, kdiff3 хочет игнорировать worktree. В любом случае, настройка его для запуска без ... авто не выглядит слишком слишком hacky:

    # one-time setup:
    wip=~/libexec/my-git-mergetools
    mkdir -p "$wip"
    cp -a "$(git --exec-path)/mergetools/kdiff3" "$wip"
    sed -si 's/--auto //g' "$wip"/kdiff3
    

    и тогда вы можете

    MERGE_TOOLS_DIR=~/libexec/my-git-mergetools git mergetool
    

отступление от этого просто обычное git merge --abort или git reset --hard.


Я не думаю, что это возможно.

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

  2. Git позволяет легко вернуться в предыдущее состояние. Так что спрячьте свои изменения, если у вас есть, попробуйте объединить, а затем --abort или reset когда вы уже достаточно и не нужно результат больше.


насколько я помню, это невозможно. Вы можете отличить mergebase от local_branch и mergebase против remote_branch как описано в ответе на которую вы ссылаетесь. Но я думаю, что еще нет возможности получить 3-стороннее слияние, как вы просили со стандартной командой git. Вы можете запросить в списке рассылки Git, чтобы эта функция была добавлена.


Я использую следующий грубый скрипт bash и meld, чтобы увидеть, что было изменено после слияние двух ветвей:

#!/bin/bash

filename=""

if [ -z "$filename" ] ; then
    echo "Usage:  filename"
    exit 1
fi

if [ ! -f "$filename" ] ; then
    echo "No file named \"$filename\""
    exit 1
fi

hashes=$(git log --merges -n1 --parents --format="%P")

hash1=${hashes% *}
hash2=${hashes#* }
if [ -z "$hash1" || -z "$hash2" ] ; then
    echo "Current commit isn't a merge of two branches"
    exit 1
fi

meld <(git show $hash1:"$filename") "$filename" <(git show $hash2:"$filename")

вероятно, его можно взломать, чтобы увидеть различия между файлом в текущем каталоге и двумя ветвями:

!/bin/bash

filename=""
hash1=
hash2=

if [ -z "$filename" ] ; then
    echo "Usage:  filename hash1 hash2"
    exit 1
fi

if [ ! -f "$filename" ] ; then
    echo "No file named \"$filename\""
    exit 1
fi

if [ -z "$hash1" || -z "$hash2" ] ; then
    echo "Missing hashes to compare"
    exit 1
fi

meld <(git show $hash1:"$filename") "$filename" <(git show $hash2:"$filename")

Я не тестировал этот сценарий. Он не покажет вам, как git объединит файл, но он даст вам представление о том, где потенциальные конфликты.