Можно ли настроить Git для использования моего настроенного diffftool с git add --patch
?
Я хотел бы выбрать изменения для добавления в индекс через свой собственный diffftool.
Можно ли настроить Git для использования моего настроенного diffftool с git add --patch
?
Я хотел бы выбрать изменения для добавления в индекс через свой собственный diffftool.
Нет, к сожалению.
Я полагаю, что я вижу, что работа - Git создает временный файл на основе того, что в настоящее время находится в индексе, передает его в diffftool вместе с копией текущей версии дерева работ (чтобы защитить вас от дальнейших изменений), позволяет использовать diffftool для перемещения некоторых изменений в индексную версию, а затем, как только вы сохраняете и завершаете, каскады любого контента в этой модифицированной версии индекса. Обратите внимание, что это потребует, чтобы diffftool также был немного редактором, и не все допустимые diffftools; некоторые из них предназначены только для просмотра различий. Обратите также внимание, что это в основном обходит все git add -p
. У вас не было бы никакого нормального интерфейса от него для перемещения между кусками, разбив куски и т.д. Дифтуол будет полностью отвечать за все это.
Если ваш diffftool достаточно полно, чтобы делать такие вещи, я полагаю, вы могли бы написать script, чтобы сделать это. Контур, без какой-либо защиты от ошибок, обработки особых случаев (двоичных файлов?) И полностью непроверенных:
#!/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
Возможно, существует множество способов улучшить это; жаль, что у меня нет времени быть осторожным и тщательным с этим прямо сейчас.
Вот мой скрипт для этого, который открывает kdiff3
для вас, чтобы выполнить слияние двух файлов. Если вам не нравится kdiff3
, укажите свои собственные значения для MERGETOOL
и MERGECMD
(но вы бы с ума сошли, если бы не любили kdiff3
).
Чтобы избежать неожиданностей, этот скрипт пытается имитировать git add -p
до аргументов и кодов ошибок. (Он обрабатывает как списки файлов и каталогов.)
Кроме того, он правильно обрабатывает различные угловые случаи, в том числе:
Ctrl+C
перед завершением (выйти рано)Пример использования:
$ ## 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 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 "[email protected]"
process_all "[email protected]"
}
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=( "[email protected]" )
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 "[email protected]"
Мне нравятся ответы от Каскабель и Стюарта, потому что они показывают, как написать сценарий для обложки, которая выполняет желаемую задачу. В качестве поставленного вопроса они используют git add
вместо git add --patch
, но все еще в духе вопроса. Другой альтернативой является использование git add --edit
которое имеет то преимущество, что ему вообще не нужно изменять рабочее дерево. Мой ответ логически небольшое изменение в сценарии Стюарта.
Проблема, которую я вижу с git add --patch
заключается в том, что он по своей сути интерактивен, что затрудняет его создание сценарием. Подход Стюарта состоит в том, чтобы использовать инструмент diff для определения желаемого полного содержимого индекса, а затем использовать git add
чтобы сделать это. Мой подход отличается тем, что вместо изменения рабочего дерева до и после вызова git add
я беру желаемый полный контент и превращаю его в патч, который можно применить к индексу, чтобы сделать его таким. Тогда для этого можно использовать EDITOR="mv \"$PATCH\"" git add --edit
. Это позволяет избежать изменений в рабочем дереве.
Чтобы использовать этот подход, начните со сценария Stuart и замените определение 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
чтобы поместить его рядом с другими. Но я помню, что некоторые сторонние программы сравнения позволяют скрывать общую начальную часть пути, поэтому этот подход может использовать эту функцию.
Спасибо Каскабель и Стюарт, за отличные ответы, и HaxElit за отличный вопрос.
Если vimdiff - это ваш difftool:
[diff]
tool = vimdiff
Вы также можете выполнить команду ниже, стоя на экране файла:
:!git add %
К сожалению, нет.
Единственный пользовательский интерфейс, который я знаю на данный момент, является частью git -gui при вызове
git gui citool
Другим пользовательским интерфейсом является интерфейс интерактивной консоли при вызове
git add -i
git diffftool позволяет использовать несколько различных параметров инструмента, но не интерфейс добавления.