2 # TopGit - A different patch queue manager
3 # Copyright (C) Petr Baudis <pasky@suse.cz> 2008
4 # Copyright (C) Kyle J. McKay <mackyle@gmail.com> 2015,2016,2017
8 names
= # Branch(es) to update
11 namecnt
=0 # how many names were seen
12 all
= # Update all branches
13 pattern
= # Branch selection filter for -a
14 current
= # Branch we are currently on
15 skipms
= # skip missing dependencies
16 stash
= # tgstash refs before changes
18 basemode
= # true if --base active
19 editmode
= # 0, 1 or empty to force none, force edit or default
20 basemsg
= # message for --base merge commit
21 basefile
= # message file for --base merge commit
22 basenc
= # --no-commit on merge
23 basefrc
= # --force non-ff update
24 setautoupdate
=1 # temporarily set rerere.autoUpdate to true
26 if [ "$(git config --get --bool topgit.autostash 2>/dev/null)" != "false" ]; then
27 # topgit.autostash is true (or unset)
34 Usage: ${tgname:-tg} [...] update [--[no-]stash] [--skip-missing] ([<name>...] | -a [<pattern>...])
35 Or: ${tgname:-tg} [...] update --base [-F <file> | -m <msg>] [--[no-]edit] [-f] <base-branch> <ref>
36 Or: ${tgname:-tg} [...] update --continue | -skip | --stop | --abort"
40 if [ "${1:-0}" != 0 ]; then
41 printf '%s\n' "$USAGE" >&2
43 printf '%s\n' "$USAGE"
48 # --base mode comes here with $1 set to <base-branch> and $2 set to <ref>
49 # and all options already parsed and validated into above-listed flags
50 # this function should exit after returning to "$current"
53 v_verify_topgit_branch tgbranch
"$1"
54 depcnt
="$(( $(git cat-file blob "refs
/heads
/$tgbranch:.topdeps
" 2>/dev/null | wc -l) ))"
55 if [ $depcnt -gt 0 ]; then
57 [ $depcnt -eq 1 ] || grammar
="dependencies"
58 die
"'$tgbranch' is not a TopGit [BASE] branch (it has $depcnt $grammar)"
60 newrev
="$(git rev-parse --verify "$2^
0" --)" && [ -n "$newrev" ] ||
61 die
"not a valid commit-ish: $2"
62 baserev
="$(ref_exists_rev "refs
/$topbases/$tgbranch")" && [ -n "$baserev" ] ||
63 die
"unable to get current base commit for branch '$tgbranch'"
64 if [ "$baserev" = "$newrev" ]; then
65 [ -n "$quiet" ] ||
echo "No change"
69 ! contained_by
"$newrev" "refs/heads/$tgbranch" || alreadymerged
=1
70 if [ -z "$basefrc" ] && ! contained_by
"$baserev" "$newrev"; then
71 die
"Refusing non-fast-forward update of base without --force"
74 # check that we can checkout the branch
76 [ -n "$alreadymerged" ] || git read-tree
-n -u -m "refs/heads/$tgbranch" ||
77 die
"git checkout \"$branch\" would fail"
79 # and make sure everything's clean and we know who we are
81 [ -n "$alreadymerged" ] || ensure_clean_tree
82 ensure_ident_available
84 # always auto stash even if it's just to the anonymous stash TG_STASH
86 stashmsg
="tgupdate: autostash before --base $tgbranch update"
87 if [ -n "$stash" ]; then
88 tg tag
-q -q -m "$stashmsg" --stash "$tgbranch" &&
89 stashhash
="$(git rev-parse --quiet --verify refs/tgstash --)" &&
90 [ -n "$stashhash" ] &&
91 [ "$(git cat-file -t "$stashhash" -- 2>/dev/null)" = "tag" ] ||
92 die
"requested --stash failed"
94 tg tag
--anonymous "$tgbranch" &&
95 stashhash
="$(git rev-parse --quiet --verify TG_STASH --)" &&
96 [ -n "$stashhash" ] &&
97 [ "$(git cat-file -t "$stashhash" -- 2>/dev/null)" = "tag" ] ||
98 die
"anonymous --stash failed"
101 # proceed with the update
103 git update-ref
-m "tg update --base $tgbranch $2" "refs/$topbases/$tgbranch" "$newrev" "$baserev" ||
104 die
"Unable to update base ref"
105 if [ -n "$alreadymerged" ]; then
106 [ -n "$quiet" ] ||
echo "Already contained in branch (base updated)"
109 git checkout
-q $iowopt "$tgbranch" || die
"git checkout failed"
111 [ -z "$basemsg" ] || msgopt
='-m "$basemsg"'
113 [ -z "$basefile" ] || fileopt
='-F "$basefile"'
114 if [ -n "$editmode" ]; then
115 if [ "$editmode" = "0" ]; then
121 if [ -z "$basemsg$basefile" ]; then
123 basemsg
="tg update --base $tgbranch $2"
124 msgopt
='-m "$basemsg"'
130 [ -z "$basenc" ] || ncopt
="--no-commit"
131 eval git merge
--no-ff --no-log --no-stat $auhopt $ncopt $editopt "$msgopt" "$fileopt" "refs/$topbases/$tgbranch" -- ||
exit
132 [ -n "$basenc" ] || checkout_symref_full
"$current"
136 state_dir
="$git_dir/tg-update"
145 [ -d "$state_dir" ] ||
return 1
146 [ -s "$state_dir/fullcmd" ] ||
return 1
147 [ -f "$state_dir/remote" ] ||
return 1
148 [ -f "$state_dir/skipms" ] ||
return 1
149 [ -f "$state_dir/all" ] ||
return 1
150 [ -s "$state_dir/current" ] ||
return 1
151 [ -s "$state_dir/stashhash" ] ||
return 1
152 [ -s "$state_dir/name" ] ||
return 1
153 [ -s "$state_dir/names" ] ||
return 1
154 [ -f "$state_dir/processed" ] ||
return 1
155 [ -f "$state_dir/no_auto" ] ||
return 1
156 [ -f "$state_dir/setautoupdate" ] ||
return 1
157 [ -f "$state_dir/merging_topfiles" ] ||
return 1
158 [ -f "$state_dir/mergeours" ] ||
return 1
159 [ -f "$state_dir/mergeours" ] ||
return 1
160 if [ -s "$state_dir/mergeours" ]; then
161 [ -s "$state_dir/mergetheirs" ] ||
return 1
163 ! [ -s "$state_dir/mergetheirs" ] ||
return 1
168 is_active || die
"programmer error"
169 IFS
= read -r fullcmd
<"$state_dir/fullcmd" && [ -n "$fullcmd" ]
170 IFS
= read -r base_remote
<"$state_dir/remote" ||
:
171 IFS
= read -r skipms
<"$state_dir/skipms" ||
:
172 IFS
= read -r all
<"$state_dir/all" ||
:
173 IFS
= read -r current
<"$state_dir/current" && [ -n "$current" ]
174 IFS
= read -r stashhash
<"$state_dir/stashhash" && [ -n "$stashhash" ]
175 IFS
= read -r name
<"$state_dir/name" && [ -n "$name" ]
176 IFS
= read -r names
<"$state_dir/names" && [ -n "$names" ]
177 IFS
= read -r processed
<"$state_dir/processed" ||
:
178 IFS
= read -r next_no_auto
<"$state_dir/no_auto" ||
:
179 IFS
= read -r setautoupdate
<"$state_dir/setautoupdate" ||
:
180 # merging_topfiles is for outside info but not to be restored
181 IFS
= read -r mergeours
<"$state_dir/mergeours" ||
:
182 IFS
= read -r mergetheirs
<"$state_dir/mergetheirs" ||
:
183 if [ -n "$mergeours" ] && [ -n "$mergetheirs" ]; then
184 headhash
="$(git rev-parse --quiet --verify HEAD --)" ||
:
185 if [ -n "$headhash" ]; then
186 parents
="$(git --no-pager log -n 1 --format='format:%P' "$headhash" -- 2>/dev/null)" ||
:
187 if [ "$parents" = "$mergeours $mergetheirs" ]; then
188 mergeresult
="$headhash"
191 if [ -z "$mergeresult" ]; then
200 ! [ -e "$state_dir" ] ||
rm -rf "$state_dir" >/dev
/null
2>&1 ||
:
205 ! is_active || isactive
=1
207 if [ -z "$isactive" ] && [ $# -eq 1 ]; then
208 case "$1" in --abort|
--stop|
--continue|
--skip) isactiveopt
=1; esac
210 if [ -n "$isactive" ] ||
[ -n "$isactiveopt" ]; then
211 [ $# -eq 1 ] && [ x
"$1" != x
"--status" ] ||
{ do_status
; exit 0; }
212 if [ -z "$isactive" ]; then
214 info
"No update is currently active"
221 IFS
= read -r current
<"$state_dir/current" ||
:
222 IFS
= read -r stashhash
<"$state_dir/stashhash" ||
:
224 if [ -n "$stashhash" ]; then
225 tg revert
-f -q -q --no-stash "$stashhash" >/dev
/null
2>&1 ||
:
227 if [ -n "$current" ]; then
228 info
"Ok, update aborted, returning to ${current#refs/heads/}"
229 checkout_symref_full
-f "$current"
231 info
"Ok, update aborted. Now, you just need to"
232 info
"switch back to some sane branch using \`git$gitcdopt checkout\`."
234 ! [ -f "$git_dir/TGMERGE_MSG" ] ||
[ -e "$git_dir/MERGE_MSG" ] ||
235 mv -f "$git_dir/TGMERGE_MSG" "$git_dir/MERGE_MSG" ||
:
240 info
"Ok, update stopped. Now, you just need to"
241 info
"switch back to some sane branch using \`git$gitcdopt checkout\`."
242 ! [ -f "$git_dir/TGMERGE_MSG" ] ||
[ -e "$git_dir/MERGE_MSG" ] ||
243 mv -f "$git_dir/TGMERGE_MSG" "$git_dir/MERGE_MSG" ||
:
248 if [ "$1" = "--skip" ]; then
249 info
"Ok, I will try to continue without updating this branch."
251 case " $processed " in *" $name "*);;*)
252 processed
="${processed:+$processed }$name"
255 # assume user fixed it
256 # we could be left on a detached HEAD if we were resolving
257 # a conflict while merging a base in, fix it with a checkout
258 git checkout
-q $iowopt "$(strip_ref "$name")"
267 if [ -z "$restored" ]; then
269 [ "$(git config --get --bool topgit.setAutoUpdate 2>/dev/null)" != "false" ] ||
272 while [ -n "$1" ]; do
278 [ -z "$names$pattern" ] || usage
1
286 --auto|
--auto-update|
--set-auto|
--set-auto-update)
288 --no-auto|
--no-auto-update|
--no-set-auto|
--no-set-auto-update)
303 [ $# -gt 0 ] && [ -n "$1" ] || die
"option -m requires an argument"
309 basemsg
="${1#--message=}";;
311 [ $# -gt 0 ] && [ -n "$1" ] || die
"option -F requires an argument"
317 basefile
="${1#--file=}"
318 [ -n "$basefile" ] || die
"option --file= requires an argument"
327 if [ -z "$all" ]; then
328 namecnt
=$
(( $namecnt + 1 ))
329 [ "$namecnt" != "1" ] || name1
="$arg"
330 [ "$namecnt" != "2" ] || name2
="$arg"
331 names
="${names:+$names }$arg"
333 pattern
="${pattern:+$pattern }refs/$topbases/$(strip_ref "$arg")"
338 while [ $# -gt 0 ]; do
339 if [ -z "$all" ]; then
340 namecnt
=$
(( $namecnt + 1 ))
341 [ "$namecnt" != "1" ] || name1
="$1"
342 [ "$namecnt" != "2" ] || name2
="$1"
343 names
="${names:+$names }$*"
345 pattern
="${pattern:+$pattern }refs/$topbases/$(strip_ref "$1")"
349 [ -n "$basemode" ] ||
[ -z "$editmode$basemsg$basefile$basenc$basefrc" ] || usage
1
350 [ -z "$basemode" ] ||
[ -z "$all$skipms" ] || usage
1
351 [ -z "$basemode" ] ||
[ -z "$basemsg" ] ||
[ -z "$basefile" ] || usage
1
352 [ -z "$basemode" ] ||
[ "$namecnt" -eq 2 ] || usage
1
354 current
="$(git symbolic-ref -q HEAD)" ||
:
355 if [ -n "$current" ]; then
356 [ -n "$(git rev-parse --verify --quiet HEAD --)" ] ||
357 die
"cannot return to unborn branch; switch to another branch"
359 current
="$(git rev-parse --verify --quiet HEAD)" ||
360 die
"cannot return to invalid HEAD; switch to another branch"
363 [ -z "$basemode" ] || do_base_mode
"$name1" "$name2"
365 origpattern
="$pattern"
366 [ -z "$pattern" ] && pattern
="refs/$topbases"
369 [ -n "$all$names" ] || names
="HEAD"
370 if [ -z "$all" ]; then
373 while [ $# -gt 0 ]; do
374 name
="$(verify_topgit_branch "$1")"
375 case " $names " in *" $name "*);;*)
376 names
="${names:+$names }$name"
387 mkdir
-p "$state_dir"
388 printf '%s\n' "$fullcmd" >"$state_dir/fullcmd"
389 printf '%s\n' "$base_remote" >"$state_dir/remote"
390 printf '%s\n' "$skipms" >"$state_dir/skipms"
391 printf '%s\n' "$all" >"$state_dir/all"
392 printf '%s\n' "$current" >"$state_dir/current"
393 printf '%s\n' "$stashhash" >"$state_dir/stashhash"
394 printf '%s\n' "$name" >"$state_dir/name"
395 printf '%s\n' "$names" >"$state_dir/names"
396 printf '%s\n' "$processed" >"$state_dir/processed"
397 printf '%s\n' "$no_auto" >"$state_dir/no_auto"
398 printf '%s\n' "$setautoupdate" >"$state_dir/setautoupdate"
399 # this one is an external flag and needs to be zero length for false
400 printf '%s' "$merging_topfiles" >"$state_dir/merging_topfiles"
401 printf '%s\n' "$1" >"$state_dir/mergeours"
402 printf '%s\n' "$2" >"$state_dir/mergetheirs"
405 stash_now_if_requested
() {
406 [ -z "$TG_RECURSIVE" ] ||
return 0
407 [ -z "$stashhash" ] ||
return 0
408 ensure_ident_available
409 msg
="tgupdate: autostash before update"
410 if [ -n "$all" ]; then
411 msg
="$msg --all${origpattern:+ $origpattern}"
416 if [ -n "$stash" ]; then
417 tg tag
-q -q -m "$msg" --stash "$@" &&
418 stashhash
="$(git rev-parse --quiet --verify refs/tgstash --)" &&
419 [ -n "$stashhash" ] &&
420 [ "$(git cat-file -t "$stashhash" -- 2>/dev/null)" = "tag" ] ||
421 die
"requested --stash failed"
423 tg tag
--anonymous "$@" &&
424 stashhash
="$(git rev-parse --quiet --verify TG_STASH --)" &&
425 [ -n "$stashhash" ] &&
426 [ "$(git cat-file -t "$stashhash" -- 2>/dev/null)" = "tag" ] ||
427 die
"anonymous --stash failed"
429 [ -z "$next_no_auto" ] || no_auto
="$next_no_auto"
437 if [ -n "$TG_RECURSIVE" ]; then
438 TG_RECURSIVE
="==> [$1]${TG_RECURSIVE#==>}"
440 TG_RECURSIVE
="==> [$1]$lf"
444 [ $_ret -eq 3 ] && exit 3
448 # If HEAD is a symref to "$1" detach it at its current value
449 detach_symref_head_on_branch
() {
450 _hsr
="$(git symbolic-ref -q HEAD --)" && [ -n "$_hsr" ] ||
return 0
451 _hrv
="$(git rev-parse --quiet --verify HEAD --)" && [ -n "$_hrv" ] ||
452 die
"cannot detach_symref_head_on_branch from unborn branch $_hsr"
453 git update-ref
--no-deref -m "detaching HEAD from $_hsr to safely update it" HEAD
"$_hrv"
456 # git_topmerge will need this even on success and since it might otherwise
457 # be called many times do it just the once here and now
458 repotoplvl
="$(git rev-parse --show-toplevel)" && [ -n "$repotoplvl" ] && [ -d "$repotoplvl" ] ||
459 die
"git rev-parse --show-toplevel failed"
461 # Run an in-tree recursive merge but make sure we get the desired version of
462 # any .topdeps and .topmsg files. The $auhopt and --no-stat options are
463 # always implicitly in effect. If successful, a new commit is performed on HEAD.
465 # The "git merge-recursive" tool (and others) must be run to get the desired
466 # result. And --no-ff is always implicitly in effect as well.
468 # NOTE: [optional] arguments MUST appear in the order shown
469 # [optional] '-v' varname => optional variable to return original HEAD hash in
470 # [optional] '--merge', '--theirs' or '--remove' to alter .topfile handling
471 # [optional] '--name' <name-for-ours [--name <name-for-theirs>]
472 # $1 => '-m' MUST be '-m'
473 # $2 => commit message
474 # $3 => commit-ish to merge as "theirs"
478 [ "$1" != "-v" ] ||
[ $# -lt 2 ] ||
[ -z "$2" ] ||
{ _ovar
="$2"; shift 2; }
480 case "$1" in --theirs|
--remove|
--merge) _mmode
="${1#--}"; shift; esac
483 if [ "$1" = "--name" ] && [ $# -ge 2 ]; then
486 if [ "$1" = "--name" ] && [ $# -ge 2 ]; then
491 : "${_nameours:=HEAD}"
492 [ "$#" -eq 3 ] && [ "$1" = "-m" ] && [ -n "$2" ] && [ -n "$3" ] ||
493 die
"programmer error: invalid arguments to git_topmerge: $*"
494 _ours
="$(git rev-parse --verify HEAD^0)" || die
"git rev-parse failed"
495 _theirs
="$(git rev-parse --verify "$3^
0")" || die
"git rev-parse failed"
496 [ -z "$_ovar" ] ||
eval "$_ovar="'"$_ours"'
497 eval "GITHEAD_$_ours="'"$_nameours"' && eval export "GITHEAD_$_ours"
498 if [ -n "$_nametheirs" ]; then
499 eval "GITHEAD_$_theirs="'"$_nametheirs"' && eval export "GITHEAD_$_theirs"
502 if [ "$_mmode" = "merge" ]; then
503 TG_L1
="$_nameours" && export TG_L1
504 TG_L2
="merged common ancestors" && export TG_L2
505 TG_L3
="${_nametheirs:-$3}" && export TG_L3
506 _mdriver
='git merge-file -L "$TG_L1" -L "$TG_L2" -L "$TG_L3" --marker-size=%L %A %O %B'
510 _mb
="$(git merge-base --all "$_ours" "$_theirs")" && [ -n "$_mb" ] ||
511 { _mt
=1; _mb
="$(git hash-object -w -t tree --stdin < /dev/null)"; }
512 # any .topdeps or .topmsg output needs to be stripped from stdout
513 tmpstdout
="$tg_tmp_dir/stdout.$$"
515 git
-c "merge.ours.driver=$_mdriver" merge-recursive \
516 $_mb -- "$_ours" "$_theirs" >"$tmpstdout" || _ret
=$?
517 # success or failure is not relevant until after fixing up the
518 # .topdeps and .topmsg files and running rerere unless _ret >= 126
519 [ $_ret -lt 126 ] ||
return $_ret
520 if [ "$_mmode" = "merge" ]; then
524 theirs
) _source
="$_theirs";;
526 *) _source
="$_ours";;
530 _newinfo
="$(git cat-file --batch-check="%(objecttype
) %(objectname
)$tab%(rest
)" <<-EOT |
531 $_source:.topdeps .topdeps
532 $_source:.topmsg .topmsg
534 sed -n 's/^blob /100644 /p'
536 [ -z "$_newinfo" ] || _newinfo
="$lf$_newinfo"
537 git update-index
--index-info <<-EOT ||
538 0 $nullsha$tab.topdeps
539 0 $nullsha$tab.topmsg$_newinfo
541 die
"git update-index failed"
542 if [ "$_mmode" = "remove" ] &&
543 { [ -e "$repotoplvl/.topdeps" ] ||
[ -e "$repotoplvl/.topmsg" ]; }
545 rm -r -f "$repotoplvl/.topdeps" "$repotoplvl/.topmsg" >/dev
/null
2>&1 ||
:
547 for zapbad
in "$repotoplvl/.topdeps" "$repotoplvl/.topmsg"; do
548 if [ -e "$zapbad" ] && { [ -L "$zapbad" ] ||
[ ! -f "$zapbad" ]; }; then
552 (cd "$repotoplvl" && git checkout-index
-q -f -u -- .topdeps .topmsg
) ||
553 die
"git checkout-index failed"
555 # dump output without any .topdeps or .topmsg messages
556 sed -e '/ \.topdeps/d' -e '/ \.topmsg/d' <"$tmpstdout"
558 # rerere will be a nop unless rerere.enabled is true, but might complete the merge!
559 eval git
"${setautoupdate:+-c rerere.autoupdate=1}" rerere ||
:
560 git ls-files
--unmerged --full-name --abbrev :/ >"$tmpstdout" 2>&1 ||
561 die
"git ls-files failed"
562 if [ -s "$tmpstdout" ]; then
563 [ "$_ret" != "0" ] || _ret
=1
567 if [ $_ret -ne 0 ]; then
568 # merge failed, spit out message, enter "merge" mode and return
570 printf '%s\n\n# Conflicts:\n' "$_msg"
571 sed -n "/$tab/s/^[^$tab]*/#/p" <"$tmpstdout" |
sort -u
572 } >"$git_dir/MERGE_MSG"
573 git update-ref MERGE_HEAD
"$_theirs" ||
:
574 echo 'Automatic merge failed; fix conflicts and then commit the result.'
578 # commit time at last!
579 thetree
="$(git write-tree)" || die
"git write-tree failed"
580 # avoid an extra "already up-to-date" commit (can't happen if _mt though)
582 [ -n "$_mt" ] || origtree
="$(git rev-parse --quiet --verify "$_ours^
{tree
}" --)" &&
583 [ -n "$origtree" ] || die
"git rev-parse failed"
584 if [ "$origtree" != "$thetree" ] ||
! contained_by
"$_theirs" "$_ours"; then
585 thecommit
="$(git commit-tree -p "$_ours" -p "$_theirs" -m "$_msg" "$thetree")" &&
586 [ -n "$thecommit" ] || die
"git commit-tree failed"
587 git update-ref
-m "$_msg" HEAD
"$thecommit" || die
"git update-ref failed"
589 # mention how the merge was made
590 echo "Merge made by the 'recursive' strategy."
595 # run git_topmerge with the passed in arguments (it always does --no-stat)
596 # then return the exit status of git_topmerge
597 # if the returned exit status is no error show a shortstat before
598 # returning assuming the merge was done into the previous HEAD but exclude
599 # .topdeps and .topmsg info from the stat unless doing a --merge
600 # if the first argument is --merge or --theirs or --remove handle .topmsg/.topdeps
602 # (default) .topmsg and .topdeps always keep ours
603 # --merge a normal merge takes place
604 # --theirs .topmsg and .topdeps always keep theirs
605 # --remove .topmsg and .topdeps are removed from the result and working tree
606 # note this function should only be called after attempt_index_merge fails as
607 # it implicity always does --no-ff (except for --merge which will --ff)
610 git_topmerge
-v _oldhead
"$@" || _ret
=$?
612 [ "$1" = "--merge" ] || _exclusions
=":/ :!/.topdeps :!/.topmsg"
613 [ "$_ret" != "0" ] || git
--no-pager diff-tree
--shortstat "$_oldhead" HEAD^
0 -- $_exclusions
617 # similar to git_merge but operates exclusively using a separate index and temp dir
618 # only trivial aggressive automatic (i.e. simple) merges are supported
620 # [optional] '--no-auto' to suppress "automatic" merging, merge fails instead
621 # [optional] '--merge', '--theirs' or '--remove' to alter .topfile handling
622 # $1 => '' to discard result, 'refs/?*' to update the specified ref or a varname
623 # $2 => '-m' MUST be '-m'
624 # $3 => commit message AND, if $1 matches refs/?* the update-ref message
625 # $4 => commit-ish to merge as "ours"
626 # $5 => commit-ish to merge as "theirs"
627 # [$6...] => more commit-ishes to merge as "theirs" in octopus
629 # all merging is done in a separate index (or temporary files for simple merges)
630 # if successful the ref or var is updated with the result
631 # otherwise everything is left unchanged and a silent failure occurs
632 # if successful and $1 matches refs/?* it WILL BE UPDATED to a new commit using the
633 # message and appropriate parents AND HEAD WILL BE DETACHED first if it's a symref
635 # otherwise if $1 does not match refs/?* and is not empty the named variable will
636 # be set to contain the resulting commit from the merge
637 # the working tree and index ARE LEFT COMPLETELY UNTOUCHED no matter what
638 v_attempt_index_merge
() {
640 if [ "$1" = "--no-auto" ]; then
645 [ "$1" = "--merge" ] || _exclusions
=":/ :!/.topdeps :!/.topmsg"
647 if [ "$1" = "--merge" ] ||
[ "$1" = "--theirs" ] ||
[ "$1" = "--remove" ]; then
650 if [ "$_mmode" = "merge" ] ||
[ "$_mmode" = "theirs" ]; then
651 _mstyle
="-top$_mmode"
654 [ "$#" -ge 5 ] && [ "$2" = "-m" ] && [ -n "$3" ] && [ -n "$4" ] && [ -n "$5" ] ||
655 die
"programmer error: invalid arguments to v_attempt_index_merge: $*"
660 rh
="$(git rev-parse --quiet --verify "$_head^
0" --)" && [ -n "$rh" ] ||
return 1
669 if [ $# -gt 1 ]; then
670 if [ "$_mmode" = "merge" ] ||
[ "$_mmode" = "theirs" ]; then
671 die
"programmer error: invalid octopus .topfile strategy to v_attempt_index_merge: --$_mode"
673 ihl
="$(git merge-base --independent "$@
")" ||
return 1
675 [ $# -ge 1 ] && [ -n "$1" ] ||
return 1
677 [ $# -eq 1 ] || _octo
=1
678 mb
="$(git merge-base ${_octo:+--octopus} "$rh" "$@
")" && [ -n "$mb" ] ||
{
679 mb
="$(git hash-object -w -t tree --stdin < /dev/null)"
682 if [ -z "$_mt" ]; then
683 if [ -n "$_octo" ]; then
684 while [ $# -gt 1 ] && mbh
="$(git merge-base "$rh" "$1")" && [ -n "$mbh" ]; do
685 if [ "$rh" = "$mbh" ]; then
686 _mmsg
="Fast-forward (no commit created)"
689 elif [ "$1" = "$mbh" ]; then
695 if [ $# -eq 1 ]; then
697 mb
="$(git merge-base "$rh" "$1")" && [ -n "$mb" ] ||
return 1
700 if [ -z "$_octo" ]; then
701 r1
="$(git rev-parse --quiet --verify "$1^
0" --)" && [ -n "$r1" ] ||
return 1
704 if [ "$rh" = "$mb" ]; then
705 _mmsg
="Fast-forward (no commit created)"
709 elif [ "$r1" = "$mb" ]; then
710 [ -n "$_mmsg" ] || _mmsg
="Already up-to-date!"
718 if [ -z "$newc" ]; then
719 if [ "$_mmode" = "theirs" ] && [ -z "$oth" ]; then
720 oth
="$(git rev-parse --quiet --verify "$1^
0" --)" && [ -n "$oth" ] ||
return 1
723 inew
="$tg_tmp_dir/index.$$"
724 ! [ -e "$inew" ] ||
rm -f "$inew"
725 itmp
="$tg_tmp_dir/output.$$"
726 imrg
="$tg_tmp_dir/auto.$$"
727 [ -z "$_octo" ] ||
>"$imrg"
732 if [ -n "$_parents" ]; then
733 if contained_by
"$1" "$_newrh"; then
738 GIT_INDEX_FILE
="$inew" git read-tree
-m --aggressive -i "$mb" "$rh" "$1" ||
{ rm -f "$inew" "$imrg"; return 1; }
739 GIT_INDEX_FILE
="$inew" git ls-files
--unmerged --full-name --abbrev :/ >"$itmp" 2>&1 ||
{ rm -f "$inew" "$itmp" "$imrg"; return 1; }
740 ! [ -s "$itmp" ] ||
{
741 if ! GIT_INDEX_FILE
="$inew" TG_TMP_DIR
="$tg_tmp_dir" git merge-index
-q "$TG_INST_CMDDIR/tg--index-merge-one-file$_mstyle" -a >"$itmp" 2>&1; then
742 rm -f "$inew" "$itmp" "$imrg"
745 if [ -s "$itmp" ]; then
746 if [ -n "$_noauto" ]; then
747 rm -f "$inew" "$itmp" "$imrg"
750 if [ -n "$_octo" ]; then
751 cat "$itmp" >>"$imrg"
760 _parents
="${_parents:+$_parents }-p $1"
761 if [ $# -gt 1 ]; then
762 newt
="$(GIT_INDEX_FILE="$inew" git write-tree)" && [ -n "$newt" ] ||
{ rm -f "$inew" "$imrg"; return 1; }
769 if [ "$_mmode" != "merge" ]; then
771 theirs
) _source
="$oth";;
777 _newinfo
="$(git cat-file --batch-check="%(objecttype
) %(objectname
)$tab%(rest
)" <<-EOT |
778 $_source:.topdeps .topdeps
779 $_source:.topmsg .topmsg
781 sed -n 's/^blob /100644 /p'
783 [ -z "$_newinfo" ] || _newinfo
="$lf$_newinfo"
784 GIT_INDEX_FILE
="$inew" git update-index
--index-info <<-EOT || { rm -f "$inew" "$imrg"; return 1; }
785 0 $nullsha$tab.topdeps
786 0 $nullsha$tab.topmsg$_newinfo
789 newt
="$(GIT_INDEX_FILE="$inew" git write-tree)" && [ -n "$newt" ] ||
{ rm -f "$inew" "$imrg"; return 1; }
790 [ -z "$_octo" ] ||
sort -u <"$imrg"
791 rm -f "$inew" "$imrg"
792 newc
="$(git commit-tree -p "$orh" $_parents -m "$_msg" "$newt")" && [ -n "$newc" ] ||
return 1
793 _mmsg
="Merge made by the 'trivial aggressive$_auto${_octo:+ octopus}' strategy."
797 if [ -n "$_same" ]; then
799 if rv
="$(git rev-parse --quiet --verify "$_var" --)" && [ "$rv" = "$newc" ]; then
803 if [ -z "$_same" ]; then
804 detach_symref_head_on_branch
"$_var" ||
return 1
805 # git update-ref returns 0 even on failure :(
806 git update-ref
-m "$_msg" "$_var" "$newc" ||
return 1
810 eval "$_var="'"$newc"'
814 [ -n "$_nodt" ] || git
--no-pager diff-tree
--shortstat "$orh" "$newc" -- $_exclusions
818 # shortcut that passes $3 as a preceding argument (which must match refs/?*)
819 attempt_index_merge
() {
822 if [ "$1" = "--no-auto" ]; then
826 if [ "$1" = "--merge" ] ||
[ "$1" = "--theirs" ] ||
[ "$1" = "--remove" ]; then
830 case "$3" in refs
/?
*);;*)
831 die
"programmer error: invalid arguments to attempt_index_merge: $*"
833 v_attempt_index_merge
$_noauto $_mmode "$3" "$@"
838 [ -n "$1" ] ||
return 0
840 [ "$1" != "$on_base" ] ||
841 [ "$(git symbolic-ref -q HEAD)" != "refs/$topbases/$1" ]
848 update_branch_internal
() {
849 # We are cacheable until the first change
853 ## First, take care of our base
855 _depcheck
="$(get_temp tg-depcheck)"
857 needs_update
"$_update_name" >"$_depcheck" ||
:
858 if [ -n "$missing_deps" ]; then
859 msg
="Some dependencies are missing: $missing_deps"
860 if [ -n "$skipms" ]; then
861 info
"$msg; skipping"
862 elif [ -z "$all" ]; then
865 info
"$msg; skipping branch $_update_name"
869 # allow automatic simple merges by default until a failure occurs
871 if [ -s "$_depcheck" ]; then
873 sed 's/ [^ ]* *$//' |
# last is $_update_name
874 sed 's/.* \([^ ]*\)$/+\1/' |
# only immediate dependencies
875 sed 's/^\([^+]\)/-\1/' |
# now each line is +branch or -branch (+ == recurse)
876 >"$_depcheck.ideps" \
877 uniq -s 1 # fold branch lines; + always comes before - and thus wins within uniq
879 stash_now_if_requested
881 while read -r depline
; do
883 action
="${depline%$dep}"
885 # We do not distinguish between dependencies out-of-date
886 # and base/remote out-of-date cases for $dep here,
887 # but thanks to needs_update returning : or refs/remotes/<remote>/<name>
888 # for the latter, we do correctly recurse here
891 if [ x
"$action" = x
+ ]; then
892 case " $missing_deps " in *" $dep "*)
893 info
"Skipping recursing to missing dependency: $dep"
896 info
"Recursing to $dep..."
897 recursive_update
"$dep" ||
exit 3
899 done <"$_depcheck.ideps"
901 # Create a list of all the fully qualified ref names that need
902 # to be merged into $_update_name's base. This will be done
903 # as an octopus merge if there are no conflicts.
907 while read -r dep
; do
914 d
="${dep#refs/heads/}"
915 deplist
="${deplist:+$deplist }$d"
916 deplines
="$deplines$d$lf"
920 deplist
="${deplist:+$deplist }$d"
921 deplines
="$deplines$d$lf"
926 set -- "$@" "refs/heads/$dep"
927 deplist
="${deplist:+$deplist }$dep"
928 deplines
="$deplines$dep$lf"
931 done <"$_depcheck.ideps"
933 # Make sure we end up on the correct base branch
935 if [ $# -ge 2 ]; then
936 info
"Updating $_update_name base with deps: $deplist"
938 msg
="tgupdate: octopus merge $# deps into $topbases/$_update_name$lf$lf$deplines"
939 if attempt_index_merge
--remove -m "$msg" "refs/$topbases/$_update_name" "$@"; then
942 info
"Octopus merge failed; falling back to multiple 3-way merges"
947 for fulldep
in "$@"; do
948 # This will be either a proper topic branch
949 # or a remote base. (branch_needs_update() is called
950 # only on the _dependencies_, not our branch itself!)
954 dep
="${fulldep#refs/heads/}";;
956 dep
="${fulldep#refs/}";;
958 dep
="$fulldep";; # this should be a programmer error
961 info
"Updating $_update_name base with $dep changes..."
963 msg
="tgupdate: merge $dep into $topbases/$_update_name"
965 ! attempt_index_merge
$no_auto --remove -m "$msg" "refs/$topbases/$_update_name" "$fulldep^0" &&
967 # We need to switch to the base branch
968 # ...but only if we aren't there yet (from failed previous merge)
969 do_base_switch
"$_update_name" || die
"do_base_switch failed" &&
970 git_merge
--remove --name "$_update_name base" --name "$dep" -m "$msg" "$fulldep^0"
976 info
"Please commit merge resolution and call \`$tgdisplayac update --continue\`"
977 info
"(use \`$tgdisplayac status\` to see more options)"
982 info
"The base is up-to-date."
986 ## Second, update our head with the remote branch
989 merge_with
="refs/$topbases/$_update_name"
991 if has_remote
"$_update_name"; then
992 _rname
="refs/remotes/$base_remote/$_update_name"
993 if branch_contains
"refs/heads/$_update_name" "$_rname"; then
994 info
"The $_update_name head is up-to-date wrt. its remote branch."
996 stash_now_if_requested
997 info
"Reconciling $_update_name base with remote branch updates..."
999 msg
="tgupdate: merge ${_rname#refs/} onto $topbases/$_update_name"
1004 if [ -n "$mergeresult" ]; then
1005 checkours
="$(git rev-parse --verify --quiet "refs
/$topbases/$_update_name^
0" --)" ||
:
1006 checktheirs
="$(git rev-parse --verify --quiet "$_rname^
0" --)" ||
:
1007 if [ "$mergeours" = "$checkours" ] && [ "$mergetheirs" = "$checktheirs" ]; then
1008 got_merge_with
="$mergeresult"
1012 [ -z "$got_merge_with" ] &&
1013 ! v_attempt_index_merge
$no_auto --theirs "merge_with" -m "$msg" "refs/$topbases/$_update_name" "$_rname^0" &&
1015 # *DETACH* our HEAD now!
1017 git checkout
-q --detach $iowopt "refs/$topbases/$_update_name" || die
"git checkout failed" &&
1018 git_merge
--theirs --name "$_update_name base content" --name "${_rname#refs/}" -m "$msg" "$_rname^0" &&
1019 merge_with
="$(git rev-parse --verify HEAD --)"
1023 "$(git rev-parse --verify --quiet "refs
/$topbases/$_update_name^
0" --)" \
1024 "$(git rev-parse --verify --quiet "$_rname^
0" --)"
1026 info
"Please commit merge resolution and call \`$tgdisplayac update --continue\`"
1027 info
"(use \`$tgdisplayac status\` to see more options)"
1030 # Go back but remember we want to merge with this, not base
1031 [ -z "$got_merge_with" ] || merge_with
="$got_merge_with"
1032 plusextra
="${_rname#refs/} + "
1037 ## Third, update our head with the base
1039 if branch_contains
"refs/heads/$_update_name" "$merge_with"; then
1040 info
"The $_update_name head is up-to-date wrt. the base."
1043 stash_now_if_requested
1044 info
"Updating $_update_name against ${plusextra}new base..."
1045 become_non_cacheable
1046 msg
="tgupdate: merge ${plusextra}$topbases/$_update_name into $_update_name"
1048 if [ -n "$brmmode" ] && [ "$base_remote" ]; then
1049 b4deps
="$(git rev-parse --verify --quiet "refs
/heads
/$_update_name:.topdeps
" --)" && [ -n "$b4deps" ] ||
1050 b4deps
="$(git hash-object -t blob -w --stdin </dev/null)"
1053 ! attempt_index_merge
$no_auto $brmmode -m "$msg" "refs/heads/$_update_name" "$merge_with^0" &&
1055 # Home, sweet home...
1056 # (We want to always switch back, in case we were
1057 # on the base from failed previous merge.)
1058 git checkout
-q $iowopt "$_update_name" || die
"git checkout failed" &&
1059 git_merge
$brmmode --name "$_update_name" --name "${plusextra}$topbases/$_update_name" -m "$msg" "$merge_with^0"
1063 merging_topfiles
="${brmmode:+1}"
1066 info
"Please commit merge resolution and call \`$tgdisplayac update --continue\`"
1067 info
"(use \`$tgdisplayac status\` to see more options)"
1071 # Fourth, auto create locally any newly depended on branches we got from the remote
1074 if [ -n "$b4deps" ] &&
1075 l8rdeps
="$(git rev-parse --verify --quiet "refs
/heads
/$_update_name:.topdeps
" --)" &&
1076 [ -n "$l8rdeps" ] && [ "$b4deps" != "$l8rdeps" ]
1079 while read -r newdep
; do
1080 if [ -n "$newdep" ]; then
1081 if auto_create_local_remote
"$newdep"; then
1084 if ref_exists
"refs/heads/$newdep"; then
1085 # maybe the line just moved around
1086 [ -n "$_olddeps" ] && [ -f "$_olddeps" ] ||
{
1087 _olddeps
="$(get_temp b4deps)" &&
1088 git cat-file blob
"$b4deps" >"$_olddeps"
1090 if awk -v "newdep=$newdep" '$0 == newdep {exit 1}' <"$_olddeps"; then
1091 # nope, it's a new head already existing locally
1095 # helpfully check to see if there's such a remote branch
1097 ! ref_exists
"refs/remotes/$base_remote/$newdep" || _rntgb
=1
1098 # maybe a locking local orphan base too
1100 if [ -n "$_rntgb" ] &&
1101 ref_exists
"refs/remotes/$base_remote/${topbases#heads/}$newdep" &&
1102 ref_exists
"refs/$topbases/$newdep"
1106 # spew the flexibly adjustable warning
1107 warn
"-------------------------------------"
1108 warn
"MISSING DEPENDENCY MERGED FROM REMOTE"
1109 warn
"-------------------------------------"
1110 warn
"Local Branch: $_update_name"
1111 warn
" Remote Name: $base_remote"
1112 warn
" Dependency: $newdep"
1113 if [ -n "$_blocked" ]; then
1114 warn
"Blocking Ref: refs/$topbases/$newdep"
1115 elif [ -n "$_rntgb" ]; then
1116 warn
"Existing Ref: refs/remotes/$base_remote/$newdep"
1119 if [ -n "$_blocked" ]; then
1120 warn
"There is no local branch by that name, but"
1121 warn
"there IS a remote TopGit branch available by"
1122 warn
"that name, but creation of a local version has"
1123 warn
"been blocked by existence of the ref shown above."
1124 elif [ -n "$_rntgb" ]; then
1125 warn
"There is no local branch or remote TopGit"
1126 warn
"branch available by that name, but there is an"
1127 warn
"existing non-TopGit remote branch ref shown above."
1128 warn
"Non-TopGit branches are not set up automatically"
1129 warn
"by TopGit and must be maintained manually."
1131 warn
"There is no local branch or remote branch"
1132 warn
"(TopGit or otherwise) available by that name."
1134 warn
"-------------------------------------"
1139 $(git diff "$b4deps" "$l8rdeps" -- | diff_added_lines)
1148 update_branch_internal
"$@" || _ubicode
=$?
1149 while [ "$_maxdeploop" -gt 0 ] && [ "$_ubicode" = "75" ]; do
1150 _maxdeploop
="$(( $maxdeploop - 1 ))"
1151 info
"Updating $1 again with newly added dependencies..."
1153 update_branch_internal
"$@" || _ubicode
=$?
1158 # We are "read-only" and cacheable until the first change
1162 do_non_annihilated_branches_patterns
() {
1163 while read -r _pat
&& [ -n "$_pat" ]; do
1166 non_annihilated_branches
"$@"
1169 do_non_annihilated_branches
() {
1170 if [ -z "$pattern" ]; then
1171 non_annihilated_branches
1173 do_non_annihilated_branches_patterns
<<-EOT
1174 $(sed 'y/ /\n/' <<-LIST
1182 if [ -n "$all" ] && [ -z "$restored" ]; then
1184 while read name
&& [ -n "$name" ]; do
1185 case " $names " in *" $name "*);;*)
1186 names
="${names:+$names }$name"
1189 $(do_non_annihilated_branches)
1193 for name
in $names; do
1194 case " $processed " in *" $name "*) continue; esac
1195 [ -z "$all" ] && case "$names" in *" "*) ! :; esac || info
"Proccessing $name..."
1196 update_branch
"$name" ||
exit
1197 processed
="${processed:+$processed }$name"
1200 [ -z "$all" ] && case "$names" in *" "*) ! :; esac ||
1201 info
"Returning to ${current#refs/heads/}..."
1202 checkout_symref_full
"$current"
1203 ! [ -f "$git_dir/TGMERGE_MSG" ] ||
[ -e "$git_dir/MERGE_MSG" ] ||
1204 mv -f "$git_dir/TGMERGE_MSG" "$git_dir/MERGE_MSG" ||
: