#!/bin/sh # git-difftool-helper is a GIT_EXTERNAL_DIFF-compatible diff tool launcher. # It supports kdiff3, kompare, tkdiff, xxdiff, meld, opendiff, # emerge, ecmerge, vimdiff, gvimdiff, and custom user-configurable tools. # This script is typically launched by using the 'git difftool' # convenience command. # # Copyright (c) 2009 David Aguilar # Set GIT_DIFFTOOL_NO_PROMPT to bypass the per-file prompt. should_prompt () { ! test -n "$GIT_DIFFTOOL_NO_PROMPT" } # Should we keep the backup .orig file? keep_backup_mode="$(git config --bool merge.keepBackup || echo true)" keep_backup () { test "$keep_backup_mode" = "true" } # This function manages the backup .orig file. # A backup $MERGED.orig file is created if changes are detected. cleanup_temp_files () { if test -n "$MERGED"; then if keep_backup && test "$MERGED" -nt "$BACKUP"; then test -f "$BACKUP" && mv -- "$BACKUP" "$MERGED.orig" else rm -f -- "$BACKUP" fi fi } # This is called when users Ctrl-C out of git-difftool-helper sigint_handler () { cleanup_temp_files exit 1 } # This function prepares temporary files and launches the appropriate # merge tool. launch_merge_tool () { # Merged is the filename as it appears in the work tree # Local is the contents of a/filename # Remote is the contents of b/filename # Custom merge tool commands might use $BASE so we provide it MERGED="$1" LOCAL="$2" REMOTE="$3" BASE="$1" ext="$$$(expr "$MERGED" : '.*\(\.[^/]*\)$')" BACKUP="$MERGED.BACKUP.$ext" # Create and ensure that we clean up $BACKUP test -f "$MERGED" && cp -- "$MERGED" "$BACKUP" trap sigint_handler INT # $LOCAL and $REMOTE are temporary files so prompt # the user with the real $MERGED name before launching $merge_tool. if should_prompt; then printf "\nViewing: '$MERGED'\n" printf "Hit return to launch '%s': " "$merge_tool" read ans fi # Run the appropriate merge tool command case "$merge_tool" in kdiff3) basename=$(basename "$MERGED") "$merge_tool_path" --auto \ --L1 "$basename (A)" \ --L2 "$basename (B)" \ "$LOCAL" "$REMOTE" \ > /dev/null 2>&1 ;; kompare) "$merge_tool_path" "$LOCAL" "$REMOTE" ;; tkdiff) "$merge_tool_path" "$LOCAL" "$REMOTE" ;; meld) "$merge_tool_path" "$LOCAL" "$REMOTE" ;; vimdiff) "$merge_tool_path" -d -c "wincmd l" "$LOCAL" "$REMOTE" ;; gvimdiff) "$merge_tool_path" -d -c "wincmd l" -f "$LOCAL" "$REMOTE" ;; xxdiff) "$merge_tool_path" \ -R 'Accel.Search: "Ctrl+F"' \ -R 'Accel.SearchForward: "Ctrl-G"' \ "$LOCAL" "$REMOTE" ;; opendiff) "$merge_tool_path" "$LOCAL" "$REMOTE" | cat ;; ecmerge) "$merge_tool_path" "$LOCAL" "$REMOTE" \ --default --mode=merge2 --to="$MERGED" ;; emerge) "$merge_tool_path" -f emerge-files-command \ "$LOCAL" "$REMOTE" "$(basename "$MERGED")" ;; *) if test -n "$merge_tool_cmd"; then ( eval $merge_tool_cmd ) fi ;; esac cleanup_temp_files } # Verifies that (difftool|mergetool)..cmd exists valid_custom_tool() { merge_tool_cmd="$(git config difftool.$1.cmd)" test -z "$merge_tool_cmd" && merge_tool_cmd="$(git config mergetool.$1.cmd)" test -n "$merge_tool_cmd" } # Verifies that the chosen merge tool is properly setup. # Built-in merge tools are always valid. valid_tool() { case "$1" in kdiff3 | kompare | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | ecmerge) ;; # happy *) if ! valid_custom_tool "$1" then return 1 fi ;; esac } # Sets up the merge_tool_path variable. # This handles the difftool..path configuration. # This also falls back to mergetool defaults. init_merge_tool_path() { merge_tool_path=$(git config difftool."$1".path) test -z "$merge_tool_path" && merge_tool_path=$(git config mergetool."$1".path) if test -z "$merge_tool_path"; then case "$1" in vimdiff) merge_tool_path=vim ;; gvimdiff) merge_tool_path=gvim ;; emerge) merge_tool_path=emacs ;; *) merge_tool_path="$1" ;; esac fi } # Allow GIT_DIFF_TOOL and GIT_MERGE_TOOL to provide default values test -n "$GIT_MERGE_TOOL" && merge_tool="$GIT_MERGE_TOOL" test -n "$GIT_DIFF_TOOL" && merge_tool="$GIT_DIFF_TOOL" # If merge tool was not specified then use the diff.tool # configuration variable. If that's invalid then reset merge_tool. # Fallback to merge.tool. if test -z "$merge_tool"; then merge_tool=$(git config diff.tool) test -z "$merge_tool" && merge_tool=$(git config merge.tool) if test -n "$merge_tool" && ! valid_tool "$merge_tool"; then echo >&2 "git config option diff.tool set to unknown tool: $merge_tool" echo >&2 "Resetting to default..." unset merge_tool fi fi # Try to guess an appropriate merge tool if no tool has been set. if test -z "$merge_tool"; then # We have a $DISPLAY so try some common UNIX merge tools if test -n "$DISPLAY"; then # If gnome then prefer meld, otherwise, prefer kdiff3 or kompare if test -n "$GNOME_DESKTOP_SESSION_ID" ; then merge_tool_candidates="meld kdiff3 kompare tkdiff xxdiff gvimdiff" else merge_tool_candidates="kdiff3 kompare tkdiff xxdiff meld gvimdiff" fi fi if echo "${VISUAL:-$EDITOR}" | grep 'emacs' > /dev/null 2>&1; then # $EDITOR is emacs so add emerge as a candidate merge_tool_candidates="$merge_tool_candidates emerge opendiff vimdiff" elif echo "${VISUAL:-$EDITOR}" | grep 'vim' > /dev/null 2>&1; then # $EDITOR is vim so add vimdiff as a candidate merge_tool_candidates="$merge_tool_candidates vimdiff opendiff emerge" else merge_tool_candidates="$merge_tool_candidates opendiff emerge vimdiff" fi echo "merge tool candidates: $merge_tool_candidates" # Loop over each candidate and stop when a valid merge tool is found. for i in $merge_tool_candidates do init_merge_tool_path $i if type "$merge_tool_path" > /dev/null 2>&1; then merge_tool=$i break fi done if test -z "$merge_tool" ; then echo "No known merge resolution program available." exit 1 fi else # A merge tool has been set, so verify that it's valid. if ! valid_tool "$merge_tool"; then echo >&2 "Unknown merge tool $merge_tool" exit 1 fi init_merge_tool_path "$merge_tool" 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 fi # Launch the merge tool on each path provided by 'git diff' while test $# -gt 6 do launch_merge_tool "$1" "$2" "$5" shift 7 done