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