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