1# git-mergetool--lib is a shell library for common merge tool functions 2 3: ${MERGE_TOOLS_DIR=$(git --exec-path)/mergetools} 4 5IFS=' 6' 7 8mode_ok () { 9 if diff_mode 10 then 11 can_diff 12 elif merge_mode 13 then 14 can_merge 15 else 16 false 17 fi 18} 19 20is_available () { 21 merge_tool_path=$(translate_merge_tool_path "$1") && 22 type "$merge_tool_path" >/dev/null 2>&1 23} 24 25list_config_tools () { 26 section=$1 27 line_prefix=${2:-} 28 29 git config --get-regexp $section'\..*\.cmd' | 30 while read -r key value 31 do 32 toolname=${key#$section.} 33 toolname=${toolname%.cmd} 34 35 printf "%s%s\n" "$line_prefix" "$toolname" 36 done 37} 38 39show_tool_names () { 40 condition=${1:-true} per_line_prefix=${2:-} preamble=${3:-} 41 not_found_msg=${4:-} 42 extra_content=${5:-} 43 44 shown_any= 45 ( cd "$MERGE_TOOLS_DIR" && ls ) | { 46 while read toolname 47 do 48 if setup_tool "$toolname" 2>/dev/null && 49 (eval "$condition" "$toolname") 50 then 51 if test -n "$preamble" 52 then 53 printf "%s\n" "$preamble" 54 preamble= 55 fi 56 shown_any=yes 57 printf "%s%s\n" "$per_line_prefix" "$toolname" 58 fi 59 done 60 61 if test -n "$extra_content" 62 then 63 if test -n "$preamble" 64 then 65 # Note: no '\n' here since we don't want a 66 # blank line if there is no initial content. 67 printf "%s" "$preamble" 68 preamble= 69 fi 70 shown_any=yes 71 printf "\n%s\n" "$extra_content" 72 fi 73 74 if test -n "$preamble" && test -n "$not_found_msg" 75 then 76 printf "%s\n" "$not_found_msg" 77 fi 78 79 test -n "$shown_any" 80 } 81} 82 83diff_mode() { 84 test "$TOOL_MODE" = diff 85} 86 87merge_mode() { 88 test "$TOOL_MODE" = merge 89} 90 91translate_merge_tool_path () { 92 echo "$1" 93} 94 95check_unchanged () { 96 if test "$MERGED" -nt "$BACKUP" 97 then 98 return 0 99 else 100 while true 101 do 102 echo "$MERGED seems unchanged." 103 printf "Was the merge successful [y/n]? " 104 read answer || return 1 105 case "$answer" in 106 y*|Y*) return 0 ;; 107 n*|N*) return 1 ;; 108 esac 109 done 110 fi 111} 112 113valid_tool () { 114 setup_tool "$1" && return 0 115 cmd=$(get_merge_tool_cmd "$1") 116 test -n "$cmd" 117} 118 119setup_user_tool () { 120 merge_tool_cmd=$(get_merge_tool_cmd "$tool") 121 test -n "$merge_tool_cmd" || return 1 122 123 diff_cmd () { 124 ( eval $merge_tool_cmd ) 125 } 126 127 merge_cmd () { 128 ( eval $merge_tool_cmd ) 129 } 130} 131 132setup_tool () { 133 tool="$1" 134 135 # Fallback definitions, to be overridden by tools. 136 can_merge () { 137 return 0 138 } 139 140 can_diff () { 141 return 0 142 } 143 144 diff_cmd () { 145 return 1 146 } 147 148 merge_cmd () { 149 return 1 150 } 151 152 translate_merge_tool_path () { 153 echo "$1" 154 } 155 156 # Most tools' exit codes cannot be trusted, so By default we ignore 157 # their exit code and check the merged file's modification time in 158 # check_unchanged() to determine whether or not the merge was 159 # successful. The return value from run_merge_cmd, by default, is 160 # determined by check_unchanged(). 161 # 162 # When a tool's exit code can be trusted then the return value from 163 # run_merge_cmd is simply the tool's exit code, and check_unchanged() 164 # is not called. 165 # 166 # The return value of exit_code_trustable() tells us whether or not we 167 # can trust the tool's exit code. 168 # 169 # User-defined and built-in tools default to false. 170 # Built-in tools advertise that their exit code is trustable by 171 # redefining exit_code_trustable() to true. 172 173 exit_code_trustable () { 174 false 175 } 176 177 178 if ! test -f "$MERGE_TOOLS_DIR/$tool" 179 then 180 setup_user_tool 181 return $? 182 fi 183 184 # Load the redefined functions 185 . "$MERGE_TOOLS_DIR/$tool" 186 # Now let the user override the default command for the tool. If 187 # they have not done so then this will return 1 which we ignore. 188 setup_user_tool 189 190 if merge_mode && ! can_merge 191 then 192 echo "error: '$tool' can not be used to resolve merges" >&2 193 return 1 194 elif diff_mode && ! can_diff 195 then 196 echo "error: '$tool' can only be used to resolve merges" >&2 197 return 1 198 fi 199 return 0 200} 201 202get_merge_tool_cmd () { 203 merge_tool="$1" 204 if diff_mode 205 then 206 git config "difftool.$merge_tool.cmd" || 207 git config "mergetool.$merge_tool.cmd" 208 else 209 git config "mergetool.$merge_tool.cmd" 210 fi 211} 212 213trust_exit_code () { 214 if git config --bool "mergetool.$1.trustExitCode" 215 then 216 :; # OK 217 elif exit_code_trustable 218 then 219 echo true 220 else 221 echo false 222 fi 223} 224 225 226# Entry point for running tools 227run_merge_tool () { 228 # If GIT_PREFIX is empty then we cannot use it in tools 229 # that expect to be able to chdir() to its value. 230 GIT_PREFIX=${GIT_PREFIX:-.} 231 export GIT_PREFIX 232 233 merge_tool_path=$(get_merge_tool_path "$1") || exit 234 base_present="$2" 235 236 # Bring tool-specific functions into scope 237 setup_tool "$1" || return 1 238 239 if merge_mode 240 then 241 run_merge_cmd "$1" 242 else 243 run_diff_cmd "$1" 244 fi 245} 246 247# Run a either a configured or built-in diff tool 248run_diff_cmd () { 249 diff_cmd "$1" 250} 251 252# Run a either a configured or built-in merge tool 253run_merge_cmd () { 254 mergetool_trust_exit_code=$(trust_exit_code "$1") 255 if test "$mergetool_trust_exit_code" = "true" 256 then 257 merge_cmd "$1" 258 else 259 touch "$BACKUP" 260 merge_cmd "$1" 261 check_unchanged 262 fi 263} 264 265list_merge_tool_candidates () { 266 if merge_mode 267 then 268 tools="tortoisemerge" 269 else 270 tools="kompare" 271 fi 272 if test -n "$DISPLAY" 273 then 274 if test -n "$GNOME_DESKTOP_SESSION_ID" 275 then 276 tools="meld opendiff kdiff3 tkdiff xxdiff $tools" 277 else 278 tools="opendiff kdiff3 tkdiff xxdiff meld $tools" 279 fi 280 tools="$tools gvimdiff diffuse diffmerge ecmerge" 281 tools="$tools p4merge araxis bc codecompare" 282 fi 283 case "${VISUAL:-$EDITOR}" in 284 *vim*) 285 tools="$tools vimdiff emerge" 286 ;; 287 *) 288 tools="$tools emerge vimdiff" 289 ;; 290 esac 291} 292 293show_tool_help () { 294 tool_opt="'git ${TOOL_MODE}tool --tool=<tool>'" 295 296 tab=' ' 297 LF=' 298' 299 any_shown=no 300 301 cmd_name=${TOOL_MODE}tool 302 config_tools=$({ 303 diff_mode && list_config_tools difftool "$tab$tab" 304 list_config_tools mergetool "$tab$tab" 305 } | sort) 306 extra_content= 307 if test -n "$config_tools" 308 then 309 extra_content="${tab}user-defined:${LF}$config_tools" 310 fi 311 312 show_tool_names 'mode_ok && is_available' "$tab$tab" \ 313 "$tool_opt may be set to one of the following:" \ 314 "No suitable tool for 'git $cmd_name --tool=<tool>' found." \ 315 "$extra_content" && 316 any_shown=yes 317 318 show_tool_names 'mode_ok && ! is_available' "$tab$tab" \ 319 "${LF}The following tools are valid, but not currently available:" && 320 any_shown=yes 321 322 if test "$any_shown" = yes 323 then 324 echo 325 echo "Some of the tools listed above only work in a windowed" 326 echo "environment. If run in a terminal-only session, they will fail." 327 fi 328 exit 0 329} 330 331guess_merge_tool () { 332 list_merge_tool_candidates 333 cat >&2 <<-EOF 334 335 This message is displayed because '$TOOL_MODE.tool' is not configured. 336 See 'git ${TOOL_MODE}tool --tool-help' or 'git help config' for more details. 337 'git ${TOOL_MODE}tool' will now attempt to use one of the following tools: 338 $tools 339 EOF 340 341 # Loop over each candidate and stop when a valid merge tool is found. 342 IFS=' ' 343 for tool in $tools 344 do 345 is_available "$tool" && echo "$tool" && return 0 346 done 347 348 echo >&2 "No known ${TOOL_MODE} tool is available." 349 return 1 350} 351 352get_configured_merge_tool () { 353 # Diff mode first tries diff.tool and falls back to merge.tool. 354 # Merge mode only checks merge.tool 355 if diff_mode 356 then 357 merge_tool=$(git config diff.tool || git config merge.tool) 358 else 359 merge_tool=$(git config merge.tool) 360 fi 361 if test -n "$merge_tool" && ! valid_tool "$merge_tool" 362 then 363 echo >&2 "git config option $TOOL_MODE.tool set to unknown tool: $merge_tool" 364 echo >&2 "Resetting to default..." 365 return 1 366 fi 367 echo "$merge_tool" 368} 369 370get_merge_tool_path () { 371 # A merge tool has been set, so verify that it's valid. 372 merge_tool="$1" 373 if ! valid_tool "$merge_tool" 374 then 375 echo >&2 "Unknown merge tool $merge_tool" 376 exit 1 377 fi 378 if diff_mode 379 then 380 merge_tool_path=$(git config difftool."$merge_tool".path || 381 git config mergetool."$merge_tool".path) 382 else 383 merge_tool_path=$(git config mergetool."$merge_tool".path) 384 fi 385 if test -z "$merge_tool_path" 386 then 387 merge_tool_path=$(translate_merge_tool_path "$merge_tool") 388 fi 389 if test -z "$(get_merge_tool_cmd "$merge_tool")" && 390 ! type "$merge_tool_path" >/dev/null 2>&1 391 then 392 echo >&2 "The $TOOL_MODE tool $merge_tool is not available as"\ 393 "'$merge_tool_path'" 394 exit 1 395 fi 396 echo "$merge_tool_path" 397} 398 399get_merge_tool () { 400 # Check if a merge tool has been configured 401 merge_tool=$(get_configured_merge_tool) 402 # Try to guess an appropriate merge tool if no tool has been set. 403 if test -z "$merge_tool" 404 then 405 merge_tool=$(guess_merge_tool) || exit 406 fi 407 echo "$merge_tool" 408} 409 410mergetool_find_win32_cmd () { 411 executable=$1 412 sub_directory=$2 413 414 # Use $executable if it exists in $PATH 415 if type -p "$executable" >/dev/null 2>&1 416 then 417 printf '%s' "$executable" 418 return 419 fi 420 421 # Look for executable in the typical locations 422 for directory in $(env | grep -Ei '^PROGRAM(FILES(\(X86\))?|W6432)=' | 423 cut -d '=' -f 2- | sort -u) 424 do 425 if test -n "$directory" && test -x "$directory/$sub_directory/$executable" 426 then 427 printf '%s' "$directory/$sub_directory/$executable" 428 return 429 fi 430 done 431 432 printf '%s' "$executable" 433}