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