logo

oasis-root

Compiled tree of Oasis Linux based on own branch at <https://hacktivis.me/git/oasis/> git clone https://anongit.hacktivis.me/git/oasis-root.git

git-mergetool (11693B)


  1. #!/bin/sh
  2. #
  3. # This program resolves merge conflicts in git
  4. #
  5. # Copyright (c) 2006 Theodore Y. Ts'o
  6. # Copyright (c) 2009-2016 David Aguilar
  7. #
  8. # This file is licensed under the GPL v2, or a later version
  9. # at the discretion of Junio C Hamano.
  10. #
  11. USAGE='[--tool=tool] [--tool-help] [-y|--no-prompt|--prompt] [-g|--gui|--no-gui] [-O<orderfile>] [file to merge] ...'
  12. SUBDIRECTORY_OK=Yes
  13. NONGIT_OK=Yes
  14. OPTIONS_SPEC=
  15. TOOL_MODE=merge
  16. . git-sh-setup
  17. . git-mergetool--lib
  18. # Returns true if the mode reflects a symlink
  19. is_symlink () {
  20. test "$1" = 120000
  21. }
  22. is_submodule () {
  23. test "$1" = 160000
  24. }
  25. local_present () {
  26. test -n "$local_mode"
  27. }
  28. remote_present () {
  29. test -n "$remote_mode"
  30. }
  31. base_present () {
  32. test -n "$base_mode"
  33. }
  34. mergetool_tmpdir_init () {
  35. if test "$(git config --bool mergetool.writeToTemp)" != true
  36. then
  37. MERGETOOL_TMPDIR=.
  38. return 0
  39. fi
  40. if MERGETOOL_TMPDIR=$(mktemp -d -t "git-mergetool-XXXXXX" 2>/dev/null)
  41. then
  42. return 0
  43. fi
  44. die "error: mktemp is needed when 'mergetool.writeToTemp' is true"
  45. }
  46. cleanup_temp_files () {
  47. if test "$1" = --save-backup
  48. then
  49. rm -rf -- "$MERGED.orig"
  50. test -e "$BACKUP" && mv -- "$BACKUP" "$MERGED.orig"
  51. rm -f -- "$LOCAL" "$REMOTE" "$BASE"
  52. else
  53. rm -f -- "$LOCAL" "$REMOTE" "$BASE" "$BACKUP"
  54. fi
  55. if test "$MERGETOOL_TMPDIR" != "."
  56. then
  57. rmdir "$MERGETOOL_TMPDIR"
  58. fi
  59. }
  60. describe_file () {
  61. mode="$1"
  62. branch="$2"
  63. file="$3"
  64. printf " {%s}: " "$branch"
  65. if test -z "$mode"
  66. then
  67. echo "deleted"
  68. elif is_symlink "$mode"
  69. then
  70. echo "a symbolic link -> '$(cat "$file")'"
  71. elif is_submodule "$mode"
  72. then
  73. echo "submodule commit $file"
  74. elif base_present
  75. then
  76. echo "modified file"
  77. else
  78. echo "created file"
  79. fi
  80. }
  81. resolve_symlink_merge () {
  82. while true
  83. do
  84. printf "Use (l)ocal or (r)emote, or (a)bort? "
  85. read ans || return 1
  86. case "$ans" in
  87. [lL]*)
  88. git checkout-index -f --stage=2 -- "$MERGED"
  89. git add -- "$MERGED"
  90. cleanup_temp_files --save-backup
  91. return 0
  92. ;;
  93. [rR]*)
  94. git checkout-index -f --stage=3 -- "$MERGED"
  95. git add -- "$MERGED"
  96. cleanup_temp_files --save-backup
  97. return 0
  98. ;;
  99. [aA]*)
  100. return 1
  101. ;;
  102. esac
  103. done
  104. }
  105. resolve_deleted_merge () {
  106. while true
  107. do
  108. if base_present
  109. then
  110. printf "Use (m)odified or (d)eleted file, or (a)bort? "
  111. else
  112. printf "Use (c)reated or (d)eleted file, or (a)bort? "
  113. fi
  114. read ans || return 1
  115. case "$ans" in
  116. [mMcC]*)
  117. git add -- "$MERGED"
  118. if test "$merge_keep_backup" = "true"
  119. then
  120. cleanup_temp_files --save-backup
  121. else
  122. cleanup_temp_files
  123. fi
  124. return 0
  125. ;;
  126. [dD]*)
  127. git rm -- "$MERGED" > /dev/null
  128. cleanup_temp_files
  129. return 0
  130. ;;
  131. [aA]*)
  132. if test "$merge_keep_temporaries" = "false"
  133. then
  134. cleanup_temp_files
  135. fi
  136. return 1
  137. ;;
  138. esac
  139. done
  140. }
  141. resolve_submodule_merge () {
  142. while true
  143. do
  144. printf "Use (l)ocal or (r)emote, or (a)bort? "
  145. read ans || return 1
  146. case "$ans" in
  147. [lL]*)
  148. if ! local_present
  149. then
  150. if test -n "$(git ls-tree HEAD -- "$MERGED")"
  151. then
  152. # Local isn't present, but it's a subdirectory
  153. git ls-tree --full-name -r HEAD -- "$MERGED" |
  154. git update-index --index-info || exit $?
  155. else
  156. test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
  157. git update-index --force-remove "$MERGED"
  158. cleanup_temp_files --save-backup
  159. fi
  160. elif is_submodule "$local_mode"
  161. then
  162. stage_submodule "$MERGED" "$local_sha1"
  163. else
  164. git checkout-index -f --stage=2 -- "$MERGED"
  165. git add -- "$MERGED"
  166. fi
  167. return 0
  168. ;;
  169. [rR]*)
  170. if ! remote_present
  171. then
  172. if test -n "$(git ls-tree MERGE_HEAD -- "$MERGED")"
  173. then
  174. # Remote isn't present, but it's a subdirectory
  175. git ls-tree --full-name -r MERGE_HEAD -- "$MERGED" |
  176. git update-index --index-info || exit $?
  177. else
  178. test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
  179. git update-index --force-remove "$MERGED"
  180. fi
  181. elif is_submodule "$remote_mode"
  182. then
  183. ! is_submodule "$local_mode" &&
  184. test -e "$MERGED" &&
  185. mv -- "$MERGED" "$BACKUP"
  186. stage_submodule "$MERGED" "$remote_sha1"
  187. else
  188. test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
  189. git checkout-index -f --stage=3 -- "$MERGED"
  190. git add -- "$MERGED"
  191. fi
  192. cleanup_temp_files --save-backup
  193. return 0
  194. ;;
  195. [aA]*)
  196. return 1
  197. ;;
  198. esac
  199. done
  200. }
  201. stage_submodule () {
  202. path="$1"
  203. submodule_sha1="$2"
  204. mkdir -p "$path" ||
  205. die "fatal: unable to create directory for module at $path"
  206. # Find $path relative to work tree
  207. work_tree_root=$(cd_to_toplevel && pwd)
  208. work_rel_path=$(cd "$path" &&
  209. GIT_WORK_TREE="${work_tree_root}" git rev-parse --show-prefix
  210. )
  211. test -n "$work_rel_path" ||
  212. die "fatal: unable to get path of module $path relative to work tree"
  213. git update-index --add --replace --cacheinfo 160000 "$submodule_sha1" "${work_rel_path%/}" || die
  214. }
  215. checkout_staged_file () {
  216. tmpfile="$(git checkout-index --temp --stage="$1" "$2" 2>/dev/null)" &&
  217. tmpfile=${tmpfile%%' '*}
  218. if test $? -eq 0 && test -n "$tmpfile"
  219. then
  220. mv -- "$(git rev-parse --show-cdup)$tmpfile" "$3"
  221. else
  222. >"$3"
  223. fi
  224. }
  225. hide_resolved () {
  226. git merge-file --ours -q -p "$LOCAL" "$BASE" "$REMOTE" >"$LCONFL"
  227. git merge-file --theirs -q -p "$LOCAL" "$BASE" "$REMOTE" >"$RCONFL"
  228. mv -- "$LCONFL" "$LOCAL"
  229. mv -- "$RCONFL" "$REMOTE"
  230. }
  231. merge_file () {
  232. MERGED="$1"
  233. f=$(git ls-files -u -- "$MERGED")
  234. if test -z "$f"
  235. then
  236. if test ! -f "$MERGED"
  237. then
  238. echo "$MERGED: file not found"
  239. else
  240. echo "$MERGED: file does not need merging"
  241. fi
  242. return 1
  243. fi
  244. # extract file extension from the last path component
  245. case "${MERGED##*/}" in
  246. *.*)
  247. ext=.${MERGED##*.}
  248. BASE=${MERGED%"$ext"}
  249. ;;
  250. *)
  251. BASE=$MERGED
  252. ext=
  253. esac
  254. initialize_merge_tool "$merge_tool" || return
  255. mergetool_tmpdir_init
  256. if test "$MERGETOOL_TMPDIR" != "."
  257. then
  258. # If we're using a temporary directory then write to the
  259. # top-level of that directory.
  260. BASE=${BASE##*/}
  261. fi
  262. BACKUP="$MERGETOOL_TMPDIR/${BASE}_BACKUP_$$$ext"
  263. LOCAL="$MERGETOOL_TMPDIR/${BASE}_LOCAL_$$$ext"
  264. LCONFL="$MERGETOOL_TMPDIR/${BASE}_LOCAL_LCONFL_$$$ext"
  265. REMOTE="$MERGETOOL_TMPDIR/${BASE}_REMOTE_$$$ext"
  266. RCONFL="$MERGETOOL_TMPDIR/${BASE}_REMOTE_RCONFL_$$$ext"
  267. BASE="$MERGETOOL_TMPDIR/${BASE}_BASE_$$$ext"
  268. base_mode= local_mode= remote_mode=
  269. # here, $IFS is just a LF
  270. for line in $f
  271. do
  272. mode=${line%% *} # 1st word
  273. sha1=${line#"$mode "}
  274. sha1=${sha1%% *} # 2nd word
  275. case "${line#$mode $sha1 }" in # remainder
  276. '1 '*)
  277. base_mode=$mode
  278. ;;
  279. '2 '*)
  280. local_mode=$mode local_sha1=$sha1
  281. ;;
  282. '3 '*)
  283. remote_mode=$mode remote_sha1=$sha1
  284. ;;
  285. esac
  286. done
  287. if is_submodule "$local_mode" || is_submodule "$remote_mode"
  288. then
  289. echo "Submodule merge conflict for '$MERGED':"
  290. describe_file "$local_mode" "local" "$local_sha1"
  291. describe_file "$remote_mode" "remote" "$remote_sha1"
  292. resolve_submodule_merge
  293. return
  294. fi
  295. if test -f "$MERGED"
  296. then
  297. mv -- "$MERGED" "$BACKUP"
  298. cp -- "$BACKUP" "$MERGED"
  299. fi
  300. # Create a parent directory to handle delete/delete conflicts
  301. # where the base's directory no longer exists.
  302. mkdir -p "$(dirname "$MERGED")"
  303. checkout_staged_file 1 "$MERGED" "$BASE"
  304. checkout_staged_file 2 "$MERGED" "$LOCAL"
  305. checkout_staged_file 3 "$MERGED" "$REMOTE"
  306. # hideResolved preferences hierarchy.
  307. global_config="mergetool.hideResolved"
  308. tool_config="mergetool.${merge_tool}.hideResolved"
  309. if enabled=$(git config --type=bool "$tool_config")
  310. then
  311. # The user has a specific preference for a specific tool and no
  312. # other preferences should override that.
  313. : ;
  314. elif enabled=$(git config --type=bool "$global_config")
  315. then
  316. # The user has a general preference for all tools.
  317. #
  318. # 'true' means the user likes the feature so we should use it
  319. # where possible but tool authors can still override.
  320. #
  321. # 'false' means the user doesn't like the feature so we should
  322. # not use it anywhere.
  323. if test "$enabled" = true && hide_resolved_enabled
  324. then
  325. enabled=true
  326. else
  327. enabled=false
  328. fi
  329. else
  330. # The user does not have a preference. Default to disabled.
  331. enabled=false
  332. fi
  333. if test "$enabled" = true
  334. then
  335. hide_resolved
  336. fi
  337. if test -z "$local_mode" || test -z "$remote_mode"
  338. then
  339. echo "Deleted merge conflict for '$MERGED':"
  340. describe_file "$local_mode" "local" "$LOCAL"
  341. describe_file "$remote_mode" "remote" "$REMOTE"
  342. resolve_deleted_merge
  343. status=$?
  344. rmdir -p "$(dirname "$MERGED")" 2>/dev/null
  345. return $status
  346. fi
  347. if is_symlink "$local_mode" || is_symlink "$remote_mode"
  348. then
  349. echo "Symbolic link merge conflict for '$MERGED':"
  350. describe_file "$local_mode" "local" "$LOCAL"
  351. describe_file "$remote_mode" "remote" "$REMOTE"
  352. resolve_symlink_merge
  353. return
  354. fi
  355. echo "Normal merge conflict for '$MERGED':"
  356. describe_file "$local_mode" "local" "$LOCAL"
  357. describe_file "$remote_mode" "remote" "$REMOTE"
  358. if test "$guessed_merge_tool" = true || test "$prompt" = true
  359. then
  360. printf "Hit return to start merge resolution tool (%s): " "$merge_tool"
  361. read ans || return 1
  362. fi
  363. if base_present
  364. then
  365. present=true
  366. else
  367. present=false
  368. fi
  369. if ! run_merge_tool "$merge_tool" "$present"
  370. then
  371. echo "merge of $MERGED failed" 1>&2
  372. mv -- "$BACKUP" "$MERGED"
  373. if test "$merge_keep_temporaries" = "false"
  374. then
  375. cleanup_temp_files
  376. fi
  377. return 1
  378. fi
  379. if test "$merge_keep_backup" = "true"
  380. then
  381. mv -- "$BACKUP" "$MERGED.orig"
  382. else
  383. rm -- "$BACKUP"
  384. fi
  385. git add -- "$MERGED"
  386. cleanup_temp_files
  387. return 0
  388. }
  389. prompt_after_failed_merge () {
  390. while true
  391. do
  392. printf "Continue merging other unresolved paths [y/n]? "
  393. read ans || return 1
  394. case "$ans" in
  395. [yY]*)
  396. return 0
  397. ;;
  398. [nN]*)
  399. return 1
  400. ;;
  401. esac
  402. done
  403. }
  404. print_noop_and_exit () {
  405. echo "No files need merging"
  406. exit 0
  407. }
  408. main () {
  409. prompt=$(git config --bool mergetool.prompt)
  410. GIT_MERGETOOL_GUI=
  411. guessed_merge_tool=false
  412. orderfile=
  413. while test $# != 0
  414. do
  415. case "$1" in
  416. --tool-help=*)
  417. TOOL_MODE=${1#--tool-help=}
  418. show_tool_help
  419. ;;
  420. --tool-help)
  421. show_tool_help
  422. ;;
  423. -t|--tool*)
  424. case "$#,$1" in
  425. *,*=*)
  426. merge_tool=${1#*=}
  427. ;;
  428. 1,*)
  429. usage ;;
  430. *)
  431. merge_tool="$2"
  432. shift ;;
  433. esac
  434. ;;
  435. --no-gui)
  436. GIT_MERGETOOL_GUI=false
  437. ;;
  438. -g|--gui)
  439. GIT_MERGETOOL_GUI=true
  440. ;;
  441. -y|--no-prompt)
  442. prompt=false
  443. ;;
  444. --prompt)
  445. prompt=true
  446. ;;
  447. -O*)
  448. orderfile="${1#-O}"
  449. ;;
  450. --)
  451. shift
  452. break
  453. ;;
  454. -*)
  455. usage
  456. ;;
  457. *)
  458. break
  459. ;;
  460. esac
  461. shift
  462. done
  463. git_dir_init
  464. require_work_tree
  465. if test -z "$merge_tool"
  466. then
  467. merge_tool=$(get_merge_tool)
  468. subshell_exit_status=$?
  469. if test $subshell_exit_status = 1
  470. then
  471. guessed_merge_tool=true
  472. elif test $subshell_exit_status -gt 1
  473. then
  474. exit $subshell_exit_status
  475. fi
  476. fi
  477. merge_keep_backup="$(git config --bool mergetool.keepBackup || echo true)"
  478. merge_keep_temporaries="$(git config --bool mergetool.keepTemporaries || echo false)"
  479. prefix=$(git rev-parse --show-prefix) || exit 1
  480. cd_to_toplevel
  481. if test -n "$orderfile"
  482. then
  483. orderfile=$(
  484. git rev-parse --prefix "$prefix" -- "$orderfile" |
  485. sed -e 1d
  486. )
  487. fi
  488. if test $# -eq 0 && test -e "$GIT_DIR/MERGE_RR"
  489. then
  490. set -- $(git rerere remaining)
  491. if test $# -eq 0
  492. then
  493. print_noop_and_exit
  494. fi
  495. elif test $# -ge 0
  496. then
  497. # rev-parse provides the -- needed for 'set'
  498. eval "set $(git rev-parse --sq --prefix "$prefix" -- "$@")"
  499. fi
  500. files=$(git -c core.quotePath=false \
  501. diff --name-only --diff-filter=U \
  502. ${orderfile:+"-O$orderfile"} -- "$@")
  503. if test -z "$files"
  504. then
  505. print_noop_and_exit
  506. fi
  507. printf "Merging:\n"
  508. printf "%s\n" "$files"
  509. rc=0
  510. set -- $files
  511. while test $# -ne 0
  512. do
  513. printf "\n"
  514. if ! merge_file "$1"
  515. then
  516. rc=1
  517. test $# -ne 1 && prompt_after_failed_merge || exit 1
  518. fi
  519. shift
  520. done
  521. exit $rc
  522. }
  523. main "$@"