Merge branch 'cb/mergetool'

* cb/mergetool:
  Add a very basic test script for git mergetool
  Teach git mergetool to use custom commands defined at config time
  Changed an internal variable of mergetool to support custom commands
  Tidy up git mergetool's backup file behaviour
This commit is contained in:
Junio C Hamano 2008-03-07 22:30:07 -08:00
commit 003b93cfb3
3 changed files with 162 additions and 76 deletions

View File

@ -749,8 +749,10 @@ merge.summary::
merge.tool::
Controls which merge resolution program is used by
linkgit:git-mergetool[1]. Valid values are: "kdiff3", "tkdiff",
"meld", "xxdiff", "emerge", "vimdiff", "gvimdiff", and "opendiff".
linkgit:git-mergetool[1]. Valid built-in values are: "kdiff3",
"tkdiff", "meld", "xxdiff", "emerge", "vimdiff", "gvimdiff", and
"opendiff". Any other value is treated is custom merge tool
and there must be a corresponing mergetool.<tool>.cmd option.
merge.verbosity::
Controls the amount of output shown by the recursive merge
@ -777,6 +779,31 @@ mergetool.<tool>.path::
Override the path for the given tool. This is useful in case
your tool is not in the PATH.
mergetool.<tool>.cmd::
Specify the command to invoke the specified merge tool. The
specified command is evaluated in shell with the following
variables available: 'BASE' is the name of a temporary file
containing the common base of the files to be merged, if available;
'LOCAL' is the name of a temporary file containing the contents of
the file on the current branch; 'REMOTE' is the name of a temporary
file containing the contents of the file from the branch being
merged; 'MERGED' contains the name of the file to which the merge
tool should write the results of a successful merge.
mergetool.<tool>.trustExitCode::
For a custom merge command, specify whether the exit code of
the merge command can be used to determine whether the merge was
successful. If this is not set to true then the merge target file
timestamp is checked and the merge assumed to have been successful
if the file has been updated, otherwise the user is prompted to
indicate the success of the merge.
mergetool.keepBackup::
After performing a merge, the original file with conflict markers
can be saved as a file with a `.orig` extension. If this variable
is set to `false` then this file is not preserved. Defaults to
`true` (i.e. keep the backup files).
pack.window::
The size of the window used by linkgit:git-pack-objects[1] when no
window size is given on the command line. Defaults to 10.

View File

@ -34,7 +34,7 @@ base_present () {
cleanup_temp_files () {
if test "$1" = --save-backup ; then
mv -- "$BACKUP" "$path.orig"
mv -- "$BACKUP" "$MERGED.orig"
rm -f -- "$LOCAL" "$REMOTE" "$BASE"
else
rm -f -- "$LOCAL" "$REMOTE" "$BASE" "$BACKUP"
@ -67,14 +67,14 @@ resolve_symlink_merge () {
read ans
case "$ans" in
[lL]*)
git checkout-index -f --stage=2 -- "$path"
git add -- "$path"
git checkout-index -f --stage=2 -- "$MERGED"
git add -- "$MERGED"
cleanup_temp_files --save-backup
return
;;
[rR]*)
git checkout-index -f --stage=3 -- "$path"
git add -- "$path"
git checkout-index -f --stage=3 -- "$MERGED"
git add -- "$MERGED"
cleanup_temp_files --save-backup
return
;;
@ -95,12 +95,12 @@ resolve_deleted_merge () {
read ans
case "$ans" in
[mMcC]*)
git add -- "$path"
git add -- "$MERGED"
cleanup_temp_files --save-backup
return
;;
[dD]*)
git rm -- "$path" > /dev/null
git rm -- "$MERGED" > /dev/null
cleanup_temp_files
return
;;
@ -112,11 +112,11 @@ resolve_deleted_merge () {
}
check_unchanged () {
if test "$path" -nt "$BACKUP" ; then
if test "$MERGED" -nt "$BACKUP" ; then
status=0;
else
while true; do
echo "$path seems unchanged."
echo "$MERGED seems unchanged."
printf "Was the merge successful? [y/n] "
read answer < /dev/tty
case "$answer" in
@ -127,50 +127,38 @@ check_unchanged () {
fi
}
save_backup () {
if test "$status" -eq 0; then
mv -- "$BACKUP" "$path.orig"
fi
}
remove_backup () {
if test "$status" -eq 0; then
rm "$BACKUP"
fi
}
merge_file () {
path="$1"
MERGED="$1"
f=`git ls-files -u -- "$path"`
f=`git ls-files -u -- "$MERGED"`
if test -z "$f" ; then
if test ! -f "$path" ; then
echo "$path: file not found"
if test ! -f "$MERGED" ; then
echo "$MERGED: file not found"
else
echo "$path: file does not need merging"
echo "$MERGED: file does not need merging"
fi
exit 1
fi
ext="$$$(expr "$path" : '.*\(\.[^/]*\)$')"
BACKUP="$path.BACKUP.$ext"
LOCAL="$path.LOCAL.$ext"
REMOTE="$path.REMOTE.$ext"
BASE="$path.BASE.$ext"
ext="$$$(expr "$MERGED" : '.*\(\.[^/]*\)$')"
BACKUP="$MERGED.BACKUP.$ext"
LOCAL="$MERGED.LOCAL.$ext"
REMOTE="$MERGED.REMOTE.$ext"
BASE="$MERGED.BASE.$ext"
mv -- "$path" "$BACKUP"
cp -- "$BACKUP" "$path"
mv -- "$MERGED" "$BACKUP"
cp -- "$BACKUP" "$MERGED"
base_mode=`git ls-files -u -- "$path" | awk '{if ($3==1) print $1;}'`
local_mode=`git ls-files -u -- "$path" | awk '{if ($3==2) print $1;}'`
remote_mode=`git ls-files -u -- "$path" | awk '{if ($3==3) print $1;}'`
base_mode=`git ls-files -u -- "$MERGED" | awk '{if ($3==1) print $1;}'`
local_mode=`git ls-files -u -- "$MERGED" | awk '{if ($3==2) print $1;}'`
remote_mode=`git ls-files -u -- "$MERGED" | awk '{if ($3==3) print $1;}'`
base_present && git cat-file blob ":1:$prefix$path" >"$BASE" 2>/dev/null
local_present && git cat-file blob ":2:$prefix$path" >"$LOCAL" 2>/dev/null
remote_present && git cat-file blob ":3:$prefix$path" >"$REMOTE" 2>/dev/null
base_present && git cat-file blob ":1:$prefix$MERGED" >"$BASE" 2>/dev/null
local_present && git cat-file blob ":2:$prefix$MERGED" >"$LOCAL" 2>/dev/null
remote_present && git cat-file blob ":3:$prefix$MERGED" >"$REMOTE" 2>/dev/null
if test -z "$local_mode" -o -z "$remote_mode"; then
echo "Deleted merge conflict for '$path':"
echo "Deleted merge conflict for '$MERGED':"
describe_file "$local_mode" "local" "$LOCAL"
describe_file "$remote_mode" "remote" "$REMOTE"
resolve_deleted_merge
@ -178,14 +166,14 @@ merge_file () {
fi
if is_symlink "$local_mode" || is_symlink "$remote_mode"; then
echo "Symbolic link merge conflict for '$path':"
echo "Symbolic link merge conflict for '$MERGED':"
describe_file "$local_mode" "local" "$LOCAL"
describe_file "$remote_mode" "remote" "$REMOTE"
resolve_symlink_merge
return
fi
echo "Normal merge conflict for '$path':"
echo "Normal merge conflict for '$MERGED':"
describe_file "$local_mode" "local" "$LOCAL"
describe_file "$remote_mode" "remote" "$REMOTE"
printf "Hit return to start merge resolution tool (%s): " "$merge_tool"
@ -194,36 +182,32 @@ merge_file () {
case "$merge_tool" in
kdiff3)
if base_present ; then
("$merge_tool_path" --auto --L1 "$path (Base)" --L2 "$path (Local)" --L3 "$path (Remote)" \
-o "$path" -- "$BASE" "$LOCAL" "$REMOTE" > /dev/null 2>&1)
("$merge_tool_path" --auto --L1 "$MERGED (Base)" --L2 "$MERGED (Local)" --L3 "$MERGED (Remote)" \
-o "$MERGED" -- "$BASE" "$LOCAL" "$REMOTE" > /dev/null 2>&1)
else
("$merge_tool_path" --auto --L1 "$path (Local)" --L2 "$path (Remote)" \
-o "$path" -- "$LOCAL" "$REMOTE" > /dev/null 2>&1)
("$merge_tool_path" --auto --L1 "$MERGED (Local)" --L2 "$MERGED (Remote)" \
-o "$MERGED" -- "$LOCAL" "$REMOTE" > /dev/null 2>&1)
fi
status=$?
remove_backup
;;
tkdiff)
if base_present ; then
"$merge_tool_path" -a "$BASE" -o "$path" -- "$LOCAL" "$REMOTE"
"$merge_tool_path" -a "$BASE" -o "$MERGED" -- "$LOCAL" "$REMOTE"
else
"$merge_tool_path" -o "$path" -- "$LOCAL" "$REMOTE"
"$merge_tool_path" -o "$MERGED" -- "$LOCAL" "$REMOTE"
fi
status=$?
save_backup
;;
meld|vimdiff)
touch "$BACKUP"
"$merge_tool_path" -- "$LOCAL" "$path" "$REMOTE"
"$merge_tool_path" -- "$LOCAL" "$MERGED" "$REMOTE"
check_unchanged
save_backup
;;
gvimdiff)
touch "$BACKUP"
"$merge_tool_path" -f -- "$LOCAL" "$path" "$REMOTE"
check_unchanged
save_backup
;;
touch "$BACKUP"
"$merge_tool_path" -f -- "$LOCAL" "$MERGED" "$REMOTE"
check_unchanged
;;
xxdiff)
touch "$BACKUP"
if base_present ; then
@ -231,53 +215,68 @@ merge_file () {
-R 'Accel.SaveAsMerged: "Ctrl-S"' \
-R 'Accel.Search: "Ctrl+F"' \
-R 'Accel.SearchForward: "Ctrl-G"' \
--merged-file "$path" -- "$LOCAL" "$BASE" "$REMOTE"
--merged-file "$MERGED" -- "$LOCAL" "$BASE" "$REMOTE"
else
"$merge_tool_path" -X --show-merged-pane \
-R 'Accel.SaveAsMerged: "Ctrl-S"' \
-R 'Accel.Search: "Ctrl+F"' \
-R 'Accel.SearchForward: "Ctrl-G"' \
--merged-file "$path" -- "$LOCAL" "$REMOTE"
--merged-file "$MERGED" -- "$LOCAL" "$REMOTE"
fi
check_unchanged
save_backup
;;
opendiff)
touch "$BACKUP"
if base_present; then
"$merge_tool_path" "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$path" | cat
"$merge_tool_path" "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$MERGED" | cat
else
"$merge_tool_path" "$LOCAL" "$REMOTE" -merge "$path" | cat
"$merge_tool_path" "$LOCAL" "$REMOTE" -merge "$MERGED" | cat
fi
check_unchanged
save_backup
;;
ecmerge)
touch "$BACKUP"
if base_present; then
"$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" --mode=merge3 --to="$path"
"$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" --mode=merge3 --to="$MERGED"
else
"$merge_tool_path" "$LOCAL" "$REMOTE" --mode=merge2 --to="$path"
"$merge_tool_path" "$LOCAL" "$REMOTE" --mode=merge2 --to="$MERGED"
fi
check_unchanged
save_backup
;;
emerge)
if base_present ; then
"$merge_tool_path" -f emerge-files-with-ancestor-command "$LOCAL" "$REMOTE" "$BASE" "$(basename "$path")"
"$merge_tool_path" -f emerge-files-with-ancestor-command "$LOCAL" "$REMOTE" "$BASE" "$(basename "$MERGED")"
else
"$merge_tool_path" -f emerge-files-command "$LOCAL" "$REMOTE" "$(basename "$path")"
"$merge_tool_path" -f emerge-files-command "$LOCAL" "$REMOTE" "$(basename "$MERGED")"
fi
status=$?
save_backup
;;
*)
if test -n "$merge_tool_cmd"; then
if test "$merge_tool_trust_exit_code" = "false"; then
touch "$BACKUP"
( eval $merge_tool_cmd )
check_unchanged
else
( eval $merge_tool_cmd )
status=$?
fi
fi
;;
esac
if test "$status" -ne 0; then
echo "merge of $path failed" 1>&2
mv -- "$BACKUP" "$path"
echo "merge of $MERGED failed" 1>&2
mv -- "$BACKUP" "$MERGED"
exit 1
fi
git add -- "$path"
if test "$merge_keep_backup" = "true"; then
mv -- "$BACKUP" "$MERGED.orig"
else
rm -- "$BACKUP"
fi
git add -- "$MERGED"
cleanup_temp_files
}
@ -309,12 +308,20 @@ do
shift
done
valid_custom_tool()
{
merge_tool_cmd="$(git config mergetool.$1.cmd)"
test -n "$merge_tool_cmd"
}
valid_tool() {
case "$1" in
kdiff3 | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | ecmerge)
;; # happy
*)
return 1
if ! valid_custom_tool "$1"; then
return 1
fi
;;
esac
}
@ -380,10 +387,16 @@ else
init_merge_tool_path "$merge_tool"
if ! type "$merge_tool_path" > /dev/null 2>&1; then
merge_keep_backup="$(git config --bool merge.keepBackup || echo true)"
if test -z "$merge_tool_cmd" && ! type "$merge_tool_path" > /dev/null 2>&1; then
echo "The merge tool $merge_tool is not available as '$merge_tool_path'"
exit 1
fi
if ! test -z "$merge_tool_cmd"; then
merge_tool_trust_exit_code="$(git config --bool mergetool.$merge_tool.trustExitCode || echo false)"
fi
fi

46
t/t7610-mergetool.sh Normal file
View File

@ -0,0 +1,46 @@
#!/bin/sh
#
# Copyright (c) 2008 Charles Bailey
#
test_description='git-mergetool
Testing basic merge tool invocation'
. ./test-lib.sh
test_expect_success 'setup' '
echo master >file1 &&
git add file1 &&
git commit -m "added file1" &&
git checkout -b branch1 master &&
echo branch1 change >file1 &&
echo branch1 newfile >file2 &&
git add file1 file2 &&
git commit -m "branch1 changes" &&
git checkout -b branch2 master &&
echo branch2 change >file1 &&
echo branch2 newfile >file2 &&
git add file1 file2 &&
git commit -m "branch2 changes" &&
git checkout master &&
echo master updated >file1 &&
echo master new >file2 &&
git add file1 file2 &&
git commit -m "master updates"
'
test_expect_success 'custom mergetool' '
git config merge.tool mytool &&
git config mergetool.mytool.cmd "cat \"\$REMOTE\" >\"\$MERGED\"" &&
git config mergetool.mytool.trustExitCode true &&
git checkout branch1 &&
! git merge master >/dev/null 2>&1 &&
( yes "" | git mergetool file1>/dev/null 2>&1 ) &&
( yes "" | git mergetool file2>/dev/null 2>&1 ) &&
test "$(cat file1)" = "master updated" &&
test "$(cat file2)" = "master new" &&
git commit -m "branch1 resolved with mergetool"
'
test_done