Skip to content

Commit

Permalink
Merge branch 'cb/mergetool'
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Junio C Hamano committed Mar 8, 2008
2 parents e3172d8 + 05e934b commit 003b93c
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 76 deletions.
31 changes: 29 additions & 2 deletions Documentation/config.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down
161 changes: 87 additions & 74 deletions git-mergetool.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
;;
Expand All @@ -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
;;
Expand All @@ -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
Expand All @@ -127,65 +127,53 @@ 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
return
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"
Expand All @@ -194,90 +182,101 @@ 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
"$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" "$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
}

Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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


Expand Down
Loading

0 comments on commit 003b93c

Please sign in to comment.