diff options
-rw-r--r-- | Documentation/config.txt | 31 | ||||
-rwxr-xr-x | git-mergetool.sh | 161 | ||||
-rw-r--r-- | t/t7610-mergetool.sh | 46 |
3 files changed, 162 insertions, 76 deletions
diff --git a/Documentation/config.txt b/Documentation/config.txt index 4027726f2e..29bc51603f 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -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. diff --git a/git-mergetool.sh b/git-mergetool.sh index cbbb707959..5c86f69229 100755 --- a/git-mergetool.sh +++ b/git-mergetool.sh @@ -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 diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh new file mode 100644 index 0000000000..6b0483f3e9 --- /dev/null +++ b/t/t7610-mergetool.sh @@ -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 |