git add --patch с difftool - PullRequest
       1

git add --patch с difftool

23 голосов
/ 26 января 2012

Можно ли настроить Git для использования моего сконфигурированного difftool с git add --patch?

Я бы хотел выбрать изменения для добавления в индекс через мой собственный difftool.

Ответы [ 5 ]

10 голосов
/ 26 января 2012

Нет, к сожалению.

Полагаю, я вижу, что работая - Git генерирует временный файл на основе того, что в данный момент находится в индексе, передает его в difftool вместе с копией текущей версии рабочего дерева (чтобы защитить вас от дальнейших изменений), позволяет вы используете difftool, чтобы переместить некоторые изменения в индексную версию, а затем, после того как вы сохраните и выйдете, добавят содержимое в эту модифицированную индексную версию. Обратите внимание, что для этого потребуется, чтобы difftool был немного редактором, а не все допустимые difftools; некоторые из них только для просмотра различий. Также обратите внимание, что это в основном обходит все из git add -p. У вас не будет нормального интерфейса для перемещения между ханками, разбиения хуков и так далее. Difftool будет полностью ответственен за все это.

Если ваш difftool достаточно полнофункциональный, чтобы делать подобные вещи, то, я полагаю, вы могли бы написать скрипт для этого. Схема, без какой-либо защиты от ошибок, обработки особых случаев (двоичных файлов?) И полностью непроверенная:

#!/bin/bash
tmpdir=$(mktemp -d)
git diff --name-only |
while read file; do
    cp "$file" $tmpdir
    # this has your changes in it
    work_tree_version="$tmpdir/$file"
    # this has the pristine version
    index_version=$(git checkout-index --temp "$file")
    # and now you bring changes from the work tree version into the index version,
    # within the difftool, and save the index version and quit when done
    my_difftool "$work_tree_version" "$index_version"

    # swap files around to run git add
    mv "$file" "$work_tree_version"
    mv "$index_version" "$file"
    git add "$file"
    mv "$work_tree_version" "$file"
    # you could also do this by calculating the diff and applying it directly to the index
    # git diff --no-index -- "$file" "$original_index_version" | git apply --cached

rm -r $tmpdir

Вероятно, есть много способов улучшить это; извините, у меня нет времени быть осторожным и тщательным с этим прямо сейчас.

3 голосов
/ 17 октября 2015

Вот мой скрипт для этого, который открывает kdiff3 для выполнения слияния из 2 файлов. Если вам не нравится kdiff3, укажите свои собственные значения для MERGETOOL и MERGECMD (но вы бы с ума сошли, если бы вам не понравился kdiff3).

Чтобы избежать неожиданностей, этот скрипт пытается имитировать git add -p до аргументов и кодов ошибок. (Он обрабатывает как списки файлов, так и каталоги.)

Кроме того, он правильно обрабатывает различные угловые случаи, в том числе:

  • Пользователь пытается запустить скрипт в не-git каталоге (прерывание с ошибкой)
  • Пользователь нажимает Ctrl+C перед тем, как закончить (выйти рано)
  • Пользователь отказывается сохранять результат слияния в difftool (тогда не используйте его, но переходите к следующему файлу)
  • В difftool произошла непредвиденная ошибка (остановка рано)

Пример использования:

$ ## With kdiff3 (default):
$ add-with-mergetool myfile1.txt
$ add-with-mergetool some-directory

$ ## ...or with custom mergetool:
$ export MERGETOOL='opendiff'
$ export MERGECMD='$MERGETOOL $LOCAL $REMOTE -merge $MERGED'
$ add-with-mergetool some-directory/*.py
#!/bin/bash
#
# add-with-mergetool
# Author: Stuart Berg (http://github.com/stuarteberg)
# 
# This little script is like 'git add --patch', except that 
# it launches a merge-tool to perform the merge.

# TODO: For now, this script hard-codes MERGETOOL and MERGECMD for kdiff3.
#       Modify those variables for your own tool if you wish.
#       In the future, it would be nice if we could somehow read  
#       MERGETOOL and MERGECMD from the user's git-config.

# Configure for kdiff3
# (and hide warnings on about modalSession, from kdiff3 on OSX)
MERGETOOL=${MERGETOOL-kdiff3}
MERGECMD=${MERGECMD-'"${MERGETOOL}" "${LOCAL}" "${REMOTE}" -o "${MERGED}"'\
                    2>&1 | grep -iv modalSession}

main() {
    check_for_errors "$@"
    process_all "$@"
}

check_for_errors() {
    which "${MERGETOOL}" > /dev/null
    if [[ $? == 1 ]]; then
        echo "Error: Can't find mergetool: '${MERGETOOL}'" 1>&2
        exit 1
    fi

    if [[ "$1" == "-h" ]]; then
        echo "Usage: $(basename $0) [<pathspec>...]" 1>&2
        exit 0
    fi

    # Exit early if we're not in a git repo
    git status > /dev/null || exit $?
}

process_all() {
    repo_toplevel=$(git rev-parse --show-toplevel)

    # If no args given, add everything (like 'git add -p')
    if [[ $# == 0 ]]; then
        set -- "$repo_toplevel"
    fi

    # For each given file/directory...
    args=( "$@" )
    for arg in "${args[@]}"
    do
        # Find the modified file(s)
        changed_files=( $(git diff --name-only -- "$arg") )
        (
            # Switch to toplevel, to easily handle 'git diff' output
            cd "$repo_toplevel"

            # For each modified file...
            for f in "${changed_files[@]}"
            do
                if [[ $startmsg_shown != "yes" ]]; then
                    echo "Starting $(basename $0).  Use Ctrl+C to stop early."
                    echo "To skip a file, quit ${MERGETOOL} without saving."
                    echo
                    startmsg_shown="yes"
                fi

                # This is where the magic happens.            
                patch_file_and_add "$f"
            done
        ) || exit $? # exit early if loop body failed
    done
}

# This helper function launches the mergetool for a single file,
#  and then adds it to the git index (if the user saved the new file).
patch_file_and_add() {
    f="$1"
    git show :"$f" > "$f.from_index" # Copy from the index
    (
        set -e
        trap "echo && exit 130" INT # Ctrl+C should trigger abnormal exit

        # Execute 2-file merge
        echo "Launching ${MERGETOOL} for '$f'."
        LOCAL="$f.from_index"
        REMOTE="$f"
        MERGED="$f.to_add"
        eval "${MERGECMD}"

        if [[ -e "$f.to_add" ]]; then
            mv "$f" "$f.from_working" # Backup original from working-tree
            mv "$f.to_add" "$f"       # Replace with patched version
            git add "$f"              # Add to the index
            mv "$f.from_working" "$f" # Restore the working-tree version
        fi
    )
    status=$?
    rm "$f.from_index" # Discard the old index version
    if [ $status == 130 ]; then
        echo "User interrupted." 1>&2
        exit $status
    elif [ $status != 0 ]; then
        echo "Error: Interactive add-patch stopped early!" 1>&2
        exit $status
    fi
}

main "$@"
2 голосов
/ 14 июня 2019

Если vimdiff является вашим difftool в .gitconfig:

[diff]
    tool = vimdiff

, вы также можете выполнить приведенную ниже команду, стоя на экране файла:

:!git add %
1 голос
/ 15 марта 2019

Мне нравятся ответы от Каскабель и Стюарта, потому что они показывают, как написать сценарий для обложки, которая выполняет желаемую задачу.Они используют git add вместо git add --patch в качестве поставленного вопроса, но все еще в духе вопроса.Другой альтернативой является использование git add --edit, которое имеет то преимущество, что ему вообще не нужно изменять рабочее дерево.Мой ответ логически - небольшое изменение в сценарии Стюарта.

Проблема, с которой я сталкиваюсь в git add --patch, заключается в том, что он по своей сути интерактивен, поэтому его трудно покрыть сценарием.Подход Стюарта состоит в том, чтобы использовать инструмент diff для определения желаемого полного содержимого индекса, а затем использовать git add, чтобы сделать это.Мой подход отличается тем, что вместо изменения рабочего дерева до и после вызова git add я беру желаемый полный контент и превращаю его в патч, который можно применить к индексу, чтобы сделать его таким.Тогда EDITOR="mv \"$PATCH\"" git add --edit можно использовать для этого.Это позволяет избежать изменений в рабочем дереве.

Чтобы использовать этот подход, начните со сценария Стюарта и замените определение patch_file_and_add следующим:

patch_file_and_add() {
    f="$1"
    base=$(basename "$f")
    dir=$(mktemp -d)
    mkdir "$dir/working"
    mkdir "$dir/index"
    mkdir "$dir/merged"
    LOCAL="$dir/working/$base"
    REMOTE="$dir/index/$base"
    MERGED="$dir/merged/$base"
    PATCH1="$dir/head.patch"
    PATCH="$dir/full.patch"
    git show :"$f" > "$REMOTE" # Copy from the index
    (
        set -e
        trap "echo && exit 130" INT # Ctrl+C should trigger abnormal exit

        # Execute 2-file merge
        echo "Launching ${MERGETOOL} for '$f'."
        cp "$f" "$LOCAL"
        eval "${MERGECMD}"

        if [[ -e "$MERGED" ]]; then
            git diff -- "$f" > "$PATCH1" 2> /dev/null
            git diff --staged -- "$f" >> "$PATCH1" 2> /dev/null
            # We need both of the above in case one is empty.
            head -4 "$PATCH1" > "$PATCH"
            diff --unified=7 "$REMOTE" "$MERGED" | tail -n +3 >> "$PATCH"
            # Now we have the patch we want to apply to the index.
            EDITOR="mv \"$PATCH\"" git add -e -- "$f"
        fi
        rm -rf "$dir"
    )
    status=$?
    if [ $status == 130 ]; then
        echo "User interrupted." 1>&2
        exit $status
    elif [ $status != 0 ]; then
        echo "Error: Interactive add-patch stopped early!" 1>&2
        exit $status
    fi
}

Строго говоря, LOCAL может бытьустановите $f вместо использования mv, чтобы поместить его рядом с остальными.Но я помню, что некоторые сторонние программы сравнения позволяют скрывать общую начальную часть пути, поэтому этот подход может воспользоваться этой возможностью.

Спасибо Cascabel и Stuart за отличные ответы, а HaxElit заотличный вопрос.

0 голосов
/ 27 января 2012

К сожалению, нет.

Единственный известный мне на данный момент пользовательский интерфейс - это часть git-gui, когда он вызывается как

git gui citool

Другой пользовательский интерфейс - это интерфейс интерактивной консоли, который вызывается как

git add -i

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

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