git-mergetool--lib.sh 9.4 KB

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