it-swarm.com.ru

Как выполнить трехстороннее сравнение в 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 только подтянет две подсказки ветви. Я тоже хочу посмотреть базовый файл. Чтобы было ясно, я хочу иметь возможность выполнять это сравнение до, включая файлы без конфликтов слияния. (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:

Спасибо этот ответ и комментарий за вдохновение.

Создайте собственный драйвер слияния, который всегда возвращает «ложь», что создает конфликтующее состояние слияния без какого-либо автоматического слияния. Затем выполните diff, используя git mergetool. Затем прервите слияние, когда закончите.

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

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

    * merge=assert_conflict_states
    
  3. Выполните объединение, которое теперь не выполняет никакого автоматического объединения.

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

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

  6. Отменить шаг № 2.

Это (сорта) работает, за исключением того, что kdiff3 выполняет automerge при вызове, так что я до сих пор не могу увидеть предварительно объединенные различия. Однако это можно исправить, изменив файл драйвера Git kdiff3 (.../git-core/mergetools/kdiff3, убрав переключатель --auto).

Несмотря на это, у этого есть следующие проблемы остановки показа:

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

Информация для награды:

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

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

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

Любые умные идеи?

15
bitsmack

Я хочу, чтобы иметь возможность выполнять это 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 "$1" -d\\n git ls-tree -r $2 |
            sed "s/ [^ ]*//;s/\t/ $3\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 начинается с того, что находится в рабочем дереве, и не выполняет свое собственное автоматическое слияние. Похоже, kdiff3 хочет игнорировать рабочее дерево. Anyhoo, настроив его для запуска без --auto не выглядит слишком слишком хакерским:

    # 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.

8
jthill

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

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

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

2
Jan Hudec

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

#!/bin/bash

filename="$1"

if [ -z "$filename" ] ; then
    echo "Usage: $0 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="$1"
hash1=$2
hash2=$3

if [ -z "$filename" ] ; then
    echo "Usage: $0 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 объединит файл, но даст вам представление о возможных конфликтах.

1
Frédéric Marchal

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

1
Vampire