THIS IS A TEST INSTANCE ONLY! REPOSITORIES CAN BE DELETED AT ANY TIME!

Git Source Code Mirror - This is a publish-only repository and all pull requests are ignored. Please follow Documentation/SubmittingPatches procedure for any of your improvements.
git
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

530 lines
10KB

  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. merge_file () {
  226. MERGED="$1"
  227. f=$(git ls-files -u -- "$MERGED")
  228. if test -z "$f"
  229. then
  230. if test ! -f "$MERGED"
  231. then
  232. echo "$MERGED: file not found"
  233. else
  234. echo "$MERGED: file does not need merging"
  235. fi
  236. return 1
  237. fi
  238. # extract file extension from the last path component
  239. case "${MERGED##*/}" in
  240. *.*)
  241. ext=.${MERGED##*.}
  242. BASE=${MERGED%"$ext"}
  243. ;;
  244. *)
  245. BASE=$MERGED
  246. ext=
  247. esac
  248. mergetool_tmpdir_init
  249. if test "$MERGETOOL_TMPDIR" != "."
  250. then
  251. # If we're using a temporary directory then write to the
  252. # top-level of that directory.
  253. BASE=${BASE##*/}
  254. fi
  255. BACKUP="$MERGETOOL_TMPDIR/${BASE}_BACKUP_$$$ext"
  256. LOCAL="$MERGETOOL_TMPDIR/${BASE}_LOCAL_$$$ext"
  257. REMOTE="$MERGETOOL_TMPDIR/${BASE}_REMOTE_$$$ext"
  258. BASE="$MERGETOOL_TMPDIR/${BASE}_BASE_$$$ext"
  259. base_mode= local_mode= remote_mode=
  260. # here, $IFS is just a LF
  261. for line in $f
  262. do
  263. mode=${line%% *} # 1st word
  264. sha1=${line#"$mode "}
  265. sha1=${sha1%% *} # 2nd word
  266. case "${line#$mode $sha1 }" in # remainder
  267. '1 '*)
  268. base_mode=$mode
  269. ;;
  270. '2 '*)
  271. local_mode=$mode local_sha1=$sha1
  272. ;;
  273. '3 '*)
  274. remote_mode=$mode remote_sha1=$sha1
  275. ;;
  276. esac
  277. done
  278. if is_submodule "$local_mode" || is_submodule "$remote_mode"
  279. then
  280. echo "Submodule merge conflict for '$MERGED':"
  281. describe_file "$local_mode" "local" "$local_sha1"
  282. describe_file "$remote_mode" "remote" "$remote_sha1"
  283. resolve_submodule_merge
  284. return
  285. fi
  286. if test -f "$MERGED"
  287. then
  288. mv -- "$MERGED" "$BACKUP"
  289. cp -- "$BACKUP" "$MERGED"
  290. fi
  291. # Create a parent directory to handle delete/delete conflicts
  292. # where the base's directory no longer exists.
  293. mkdir -p "$(dirname "$MERGED")"
  294. checkout_staged_file 1 "$MERGED" "$BASE"
  295. checkout_staged_file 2 "$MERGED" "$LOCAL"
  296. checkout_staged_file 3 "$MERGED" "$REMOTE"
  297. if test -z "$local_mode" || test -z "$remote_mode"
  298. then
  299. echo "Deleted merge conflict for '$MERGED':"
  300. describe_file "$local_mode" "local" "$LOCAL"
  301. describe_file "$remote_mode" "remote" "$REMOTE"
  302. resolve_deleted_merge
  303. status=$?
  304. rmdir -p "$(dirname "$MERGED")" 2>/dev/null
  305. return $status
  306. fi
  307. if is_symlink "$local_mode" || is_symlink "$remote_mode"
  308. then
  309. echo "Symbolic link merge conflict for '$MERGED':"
  310. describe_file "$local_mode" "local" "$LOCAL"
  311. describe_file "$remote_mode" "remote" "$REMOTE"
  312. resolve_symlink_merge
  313. return
  314. fi
  315. echo "Normal merge conflict for '$MERGED':"
  316. describe_file "$local_mode" "local" "$LOCAL"
  317. describe_file "$remote_mode" "remote" "$REMOTE"
  318. if test "$guessed_merge_tool" = true || test "$prompt" = true
  319. then
  320. printf "Hit return to start merge resolution tool (%s): " "$merge_tool"
  321. read ans || return 1
  322. fi
  323. if base_present
  324. then
  325. present=true
  326. else
  327. present=false
  328. fi
  329. if ! run_merge_tool "$merge_tool" "$present"
  330. then
  331. echo "merge of $MERGED failed" 1>&2
  332. mv -- "$BACKUP" "$MERGED"
  333. if test "$merge_keep_temporaries" = "false"
  334. then
  335. cleanup_temp_files
  336. fi
  337. return 1
  338. fi
  339. if test "$merge_keep_backup" = "true"
  340. then
  341. mv -- "$BACKUP" "$MERGED.orig"
  342. else
  343. rm -- "$BACKUP"
  344. fi
  345. git add -- "$MERGED"
  346. cleanup_temp_files
  347. return 0
  348. }
  349. prompt_after_failed_merge () {
  350. while true
  351. do
  352. printf "Continue merging other unresolved paths [y/n]? "
  353. read ans || return 1
  354. case "$ans" in
  355. [yY]*)
  356. return 0
  357. ;;
  358. [nN]*)
  359. return 1
  360. ;;
  361. esac
  362. done
  363. }
  364. print_noop_and_exit () {
  365. echo "No files need merging"
  366. exit 0
  367. }
  368. main () {
  369. prompt=$(git config --bool mergetool.prompt)
  370. GIT_MERGETOOL_GUI=false
  371. guessed_merge_tool=false
  372. orderfile=
  373. while test $# != 0
  374. do
  375. case "$1" in
  376. --tool-help=*)
  377. TOOL_MODE=${1#--tool-help=}
  378. show_tool_help
  379. ;;
  380. --tool-help)
  381. show_tool_help
  382. ;;
  383. -t|--tool*)
  384. case "$#,$1" in
  385. *,*=*)
  386. merge_tool=${1#*=}
  387. ;;
  388. 1,*)
  389. usage ;;
  390. *)
  391. merge_tool="$2"
  392. shift ;;
  393. esac
  394. ;;
  395. --no-gui)
  396. GIT_MERGETOOL_GUI=false
  397. ;;
  398. -g|--gui)
  399. GIT_MERGETOOL_GUI=true
  400. ;;
  401. -y|--no-prompt)
  402. prompt=false
  403. ;;
  404. --prompt)
  405. prompt=true
  406. ;;
  407. -O*)
  408. orderfile="${1#-O}"
  409. ;;
  410. --)
  411. shift
  412. break
  413. ;;
  414. -*)
  415. usage
  416. ;;
  417. *)
  418. break
  419. ;;
  420. esac
  421. shift
  422. done
  423. git_dir_init
  424. require_work_tree
  425. if test -z "$merge_tool"
  426. then
  427. if ! merge_tool=$(get_merge_tool)
  428. then
  429. guessed_merge_tool=true
  430. fi
  431. fi
  432. merge_keep_backup="$(git config --bool mergetool.keepBackup || echo true)"
  433. merge_keep_temporaries="$(git config --bool mergetool.keepTemporaries || echo false)"
  434. prefix=$(git rev-parse --show-prefix) || exit 1
  435. cd_to_toplevel
  436. if test -n "$orderfile"
  437. then
  438. orderfile=$(
  439. git rev-parse --prefix "$prefix" -- "$orderfile" |
  440. sed -e 1d
  441. )
  442. fi
  443. if test $# -eq 0 && test -e "$GIT_DIR/MERGE_RR"
  444. then
  445. set -- $(git rerere remaining)
  446. if test $# -eq 0
  447. then
  448. print_noop_and_exit
  449. fi
  450. elif test $# -ge 0
  451. then
  452. # rev-parse provides the -- needed for 'set'
  453. eval "set $(git rev-parse --sq --prefix "$prefix" -- "$@")"
  454. fi
  455. files=$(git -c core.quotePath=false \
  456. diff --name-only --diff-filter=U \
  457. ${orderfile:+"-O$orderfile"} -- "$@")
  458. if test -z "$files"
  459. then
  460. print_noop_and_exit
  461. fi
  462. printf "Merging:\n"
  463. printf "%s\n" "$files"
  464. rc=0
  465. set -- $files
  466. while test $# -ne 0
  467. do
  468. printf "\n"
  469. if ! merge_file "$1"
  470. then
  471. rc=1
  472. test $# -ne 1 && prompt_after_failed_merge || exit 1
  473. fi
  474. shift
  475. done
  476. exit $rc
  477. }
  478. main "$@"