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 | awk 'END {print NR}')"
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; }
213 if [ -z "$isactive" ]; then
215 info
"No update is currently active"
222 IFS
= read -r current
<"$state_dir/current" ||
:
223 IFS
= read -r stashhash
<"$state_dir/stashhash" ||
:
225 if [ -n "$stashhash" ]; then
226 tg revert
-f -q -q --no-stash "$stashhash" >/dev
/null
2>&1 ||
:
228 if [ -n "$current" ]; then
229 info
"Ok, update aborted, returning to ${current#refs/heads/}"
230 checkout_symref_full
-f "$current"
232 info
"Ok, update aborted. Now, you just need to"
233 info
"switch back to some sane branch using \`git$gitcdopt checkout\`."
235 ! [ -f "$git_dir/TGMERGE_MSG" ] ||
[ -e "$git_dir/MERGE_MSG" ] ||
236 mv -f "$git_dir/TGMERGE_MSG" "$git_dir/MERGE_MSG" ||
:
241 info
"Ok, update stopped. Now, you just need to"
242 info
"switch back to some sane branch using \`git$gitcdopt checkout\`."
243 ! [ -f "$git_dir/TGMERGE_MSG" ] ||
[ -e "$git_dir/MERGE_MSG" ] ||
244 mv -f "$git_dir/TGMERGE_MSG" "$git_dir/MERGE_MSG" ||
:
249 if [ "$1" = "--skip" ]; then
250 info
"Ok, I will try to continue without updating this branch."
252 case " $processed " in *" $name "*);;*)
253 processed
="${processed:+$processed }$name"
256 # assume user fixed it
257 # we could be left on a detached HEAD if we were resolving
258 # a conflict while merging a base in, fix it with a checkout
259 git checkout
-q $iowopt "$(strip_ref "$name")"
268 if [ -z "$restored" ]; then
270 [ "$(git config --get --bool topgit.setAutoUpdate 2>/dev/null)" != "false" ] ||
273 while [ -n "$1" ]; do
279 [ -z "$names$pattern" ] || usage
1
287 --auto|
--auto-update|
--set-auto|
--set-auto-update)
289 --no-auto|
--no-auto-update|
--no-set-auto|
--no-set-auto-update)
304 [ $# -gt 0 ] && [ -n "$1" ] || die
"option -m requires an argument"
310 basemsg
="${1#--message=}";;
312 [ $# -gt 0 ] && [ -n "$1" ] || die
"option -F requires an argument"
318 basefile
="${1#--file=}"
319 [ -n "$basefile" ] || die
"option --file= requires an argument"
328 if [ -z "$all" ]; then
329 namecnt
=$
(( $namecnt + 1 ))
330 [ "$namecnt" != "1" ] || name1
="$arg"
331 [ "$namecnt" != "2" ] || name2
="$arg"
332 names
="${names:+$names }$arg"
334 pattern
="${pattern:+$pattern }refs/$topbases/$(strip_ref "$arg")"
340 while [ $# -gt 0 ]; do
341 if [ -z "$all" ]; then
342 namecnt
=$
(( $namecnt + 1 ))
343 [ "$namecnt" != "1" ] || name1
="$1"
344 [ "$namecnt" != "2" ] || name2
="$1"
345 names
="${names:+$names }$*"
347 pattern
="${pattern:+$pattern }refs/$topbases/$(strip_ref "$1")"
351 [ -n "$basemode" ] ||
[ -z "$editmode$basemsg$basefile$basenc$basefrc" ] || usage
1
352 [ -z "$basemode" ] ||
[ -z "$all$skipms" ] || usage
1
353 [ -z "$basemode" ] ||
[ -z "$basemsg" ] ||
[ -z "$basefile" ] || usage
1
354 [ -z "$basemode" ] ||
[ "$namecnt" -eq 2 ] || usage
1
356 current
="$(git symbolic-ref -q HEAD)" ||
:
357 if [ -n "$current" ]; then
358 [ -n "$(git rev-parse --verify --quiet HEAD --)" ] ||
359 die
"cannot return to unborn branch; switch to another branch"
361 current
="$(git rev-parse --verify --quiet HEAD)" ||
362 die
"cannot return to invalid HEAD; switch to another branch"
365 [ -z "$basemode" ] || do_base_mode
"$name1" "$name2"
367 origpattern
="$pattern"
368 [ -z "$pattern" ] && pattern
="refs/$topbases"
371 [ -n "$all$names" ] || names
="HEAD"
372 if [ -z "$all" ]; then
375 while [ $# -gt 0 ]; do
376 name
="$(verify_topgit_branch "$1")"
377 case " $names " in *" $name "*);;*)
378 names
="${names:+$names }$name"
389 mkdir
-p "$state_dir"
390 printf '%s\n' "$fullcmd" >"$state_dir/fullcmd"
391 printf '%s\n' "$base_remote" >"$state_dir/remote"
392 printf '%s\n' "$skipms" >"$state_dir/skipms"
393 printf '%s\n' "$all" >"$state_dir/all"
394 printf '%s\n' "$current" >"$state_dir/current"
395 printf '%s\n' "$stashhash" >"$state_dir/stashhash"
396 printf '%s\n' "$name" >"$state_dir/name"
397 printf '%s\n' "$names" >"$state_dir/names"
398 printf '%s\n' "$processed" >"$state_dir/processed"
399 printf '%s\n' "$no_auto" >"$state_dir/no_auto"
400 printf '%s\n' "$setautoupdate" >"$state_dir/setautoupdate"
401 # this one is an external flag and needs to be zero length for false
402 printf '%s' "$merging_topfiles" >"$state_dir/merging_topfiles"
403 printf '%s\n' "$1" >"$state_dir/mergeours"
404 printf '%s\n' "$2" >"$state_dir/mergetheirs"
407 stash_now_if_requested
() {
408 [ -z "$TG_RECURSIVE" ] ||
return 0
409 [ -z "$stashhash" ] ||
return 0
410 ensure_ident_available
411 msg
="tgupdate: autostash before update"
412 if [ -n "$all" ]; then
413 msg
="$msg --all${origpattern:+ $origpattern}"
418 if [ -n "$stash" ]; then
419 tg tag
-q -q -m "$msg" --stash "$@" &&
420 stashhash
="$(git rev-parse --quiet --verify refs/tgstash --)" &&
421 [ -n "$stashhash" ] &&
422 [ "$(git cat-file -t "$stashhash" -- 2>/dev/null)" = "tag" ] ||
423 die
"requested --stash failed"
425 tg tag
--anonymous "$@" &&
426 stashhash
="$(git rev-parse --quiet --verify TG_STASH --)" &&
427 [ -n "$stashhash" ] &&
428 [ "$(git cat-file -t "$stashhash" -- 2>/dev/null)" = "tag" ] ||
429 die
"anonymous --stash failed"
431 [ -z "$next_no_auto" ] || no_auto
="$next_no_auto"
439 if [ -n "$TG_RECURSIVE" ]; then
440 TG_RECURSIVE
="==> [$1]${TG_RECURSIVE#==>}"
442 TG_RECURSIVE
="==> [$1]$lf"
446 [ $_ret -eq 3 ] && exit 3
450 # If HEAD is a symref to "$1" detach it at its current value
451 detach_symref_head_on_branch
() {
452 _hsr
="$(git symbolic-ref -q HEAD --)" && [ -n "$_hsr" ] ||
return 0
453 _hrv
="$(git rev-parse --quiet --verify HEAD --)" && [ -n "$_hrv" ] ||
454 die
"cannot detach_symref_head_on_branch from unborn branch $_hsr"
455 git update-ref
--no-deref -m "detaching HEAD from $_hsr to safely update it" HEAD
"$_hrv"
458 # git_topmerge will need this even on success and since it might otherwise
459 # be called many times do it just the once here and now
460 repotoplvl
="$(git rev-parse --show-toplevel)" && [ -n "$repotoplvl" ] && [ -d "$repotoplvl" ] ||
461 die
"git rev-parse --show-toplevel failed"
463 # Run an in-tree recursive merge but make sure we get the desired version of
464 # any .topdeps and .topmsg files. The $auhopt and --no-stat options are
465 # always implicitly in effect. If successful, a new commit is performed on HEAD.
467 # The "git merge-recursive" tool (and others) must be run to get the desired
468 # result. And --no-ff is always implicitly in effect as well.
470 # NOTE: [optional] arguments MUST appear in the order shown
471 # [optional] '-v' varname => optional variable to return original HEAD hash in
472 # [optional] '--merge', '--theirs' or '--remove' to alter .topfile handling
473 # [optional] '--name' <name-for-ours [--name <name-for-theirs>]
474 # $1 => '-m' MUST be '-m'
475 # $2 => commit message
476 # $3 => commit-ish to merge as "theirs"
480 [ "$1" != "-v" ] ||
[ $# -lt 2 ] ||
[ -z "$2" ] ||
{ _ovar
="$2"; shift 2; }
482 case "$1" in --theirs|
--remove|
--merge) _mmode
="${1#--}"; shift; esac
485 if [ "$1" = "--name" ] && [ $# -ge 2 ]; then
488 if [ "$1" = "--name" ] && [ $# -ge 2 ]; then
493 : "${_nameours:=HEAD}"
494 [ "$#" -eq 3 ] && [ "$1" = "-m" ] && [ -n "$2" ] && [ -n "$3" ] ||
495 die
"programmer error: invalid arguments to git_topmerge: $*"
496 _ours
="$(git rev-parse --verify HEAD^0)" || die
"git rev-parse failed"
497 _theirs
="$(git rev-parse --verify "$3^
0")" || die
"git rev-parse failed"
498 [ -z "$_ovar" ] ||
eval "$_ovar="'"$_ours"'
499 eval "GITHEAD_$_ours="'"$_nameours"' && eval export "GITHEAD_$_ours"
500 if [ -n "$_nametheirs" ]; then
501 eval "GITHEAD_$_theirs="'"$_nametheirs"' && eval export "GITHEAD_$_theirs"
504 if [ "$_mmode" = "merge" ]; then
505 TG_L1
="$_nameours" && export TG_L1
506 TG_L2
="merged common ancestors" && export TG_L2
507 TG_L3
="${_nametheirs:-$3}" && export TG_L3
508 _mdriver
='git merge-file -L "$TG_L1" -L "$TG_L2" -L "$TG_L3" --marker-size=%L %A %O %B'
512 _mb
="$(git merge-base --all "$_ours" "$_theirs")" && [ -n "$_mb" ] ||
513 { _mt
=1; _mb
="$(git hash-object -w -t tree --stdin < /dev/null)"; }
514 # any .topdeps or .topmsg output needs to be stripped from stdout
515 tmpstdout
="$tg_tmp_dir/stdout.$$"
517 git
-c "merge.ours.driver=$_mdriver" merge-recursive \
518 $_mb -- "$_ours" "$_theirs" >"$tmpstdout" || _ret
=$?
519 # success or failure is not relevant until after fixing up the
520 # .topdeps and .topmsg files and running rerere unless _ret >= 126
521 [ $_ret -lt 126 ] ||
return $_ret
522 if [ "$_mmode" = "merge" ]; then
526 theirs
) _source
="$_theirs";;
528 *) _source
="$_ours";;
532 _newinfo
="$(git cat-file --batch-check="%(objecttype
) %(objectname
)$tab%(rest
)" <<-EOT |
533 $_source:.topdeps .topdeps
534 $_source:.topmsg .topmsg
536 sed -n 's/^blob /100644 /p'
538 [ -z "$_newinfo" ] || _newinfo
="$lf$_newinfo"
539 git update-index
--index-info <<-EOT ||
540 0 $nullsha$tab.topdeps
541 0 $nullsha$tab.topmsg$_newinfo
543 die
"git update-index failed"
544 if [ "$_mmode" = "remove" ] &&
545 { [ -e "$repotoplvl/.topdeps" ] ||
[ -e "$repotoplvl/.topmsg" ]; }
547 rm -r -f "$repotoplvl/.topdeps" "$repotoplvl/.topmsg" >/dev
/null
2>&1 ||
:
549 for zapbad
in "$repotoplvl/.topdeps" "$repotoplvl/.topmsg"; do
550 if [ -e "$zapbad" ] && { [ -L "$zapbad" ] ||
[ ! -f "$zapbad" ]; }; then
554 (cd "$repotoplvl" && git checkout-index
-q -f -u -- .topdeps .topmsg
) ||
555 die
"git checkout-index failed"
557 # dump output without any .topdeps or .topmsg messages
558 sed -e '/ \.topdeps/d' -e '/ \.topmsg/d' <"$tmpstdout"
560 # rerere will be a nop unless rerere.enabled is true, but might complete the merge!
561 eval git
"${setautoupdate:+-c rerere.autoupdate=1}" rerere ||
:
562 git ls-files
--unmerged --full-name --abbrev :/ >"$tmpstdout" 2>&1 ||
563 die
"git ls-files failed"
564 if [ -s "$tmpstdout" ]; then
565 [ "$_ret" != "0" ] || _ret
=1
569 if [ $_ret -ne 0 ]; then
570 # merge failed, spit out message, enter "merge" mode and return
572 printf '%s\n\n# Conflicts:\n' "$_msg"
573 sed -n "/$tab/s/^[^$tab]*/#/p" <"$tmpstdout" |
sort -u
574 } >"$git_dir/MERGE_MSG"
575 git update-ref MERGE_HEAD
"$_theirs" ||
:
576 echo 'Automatic merge failed; fix conflicts and then commit the result.'
580 # commit time at last!
581 thetree
="$(git write-tree)" || die
"git write-tree failed"
582 # avoid an extra "already up-to-date" commit (can't happen if _mt though)
584 [ -n "$_mt" ] || origtree
="$(git rev-parse --quiet --verify "$_ours^
{tree
}" --)" &&
585 [ -n "$origtree" ] || die
"git rev-parse failed"
586 if [ "$origtree" != "$thetree" ] ||
! contained_by
"$_theirs" "$_ours"; then
587 thecommit
="$(git commit-tree -p "$_ours" -p "$_theirs" -m "$_msg" "$thetree")" &&
588 [ -n "$thecommit" ] || die
"git commit-tree failed"
589 git update-ref
-m "$_msg" HEAD
"$thecommit" || die
"git update-ref failed"
591 # mention how the merge was made
592 echo "Merge made by the 'recursive' strategy."
597 # run git_topmerge with the passed in arguments (it always does --no-stat)
598 # then return the exit status of git_topmerge
599 # if the returned exit status is no error show a shortstat before
600 # returning assuming the merge was done into the previous HEAD but exclude
601 # .topdeps and .topmsg info from the stat unless doing a --merge
602 # if the first argument is --merge or --theirs or --remove handle .topmsg/.topdeps
604 # (default) .topmsg and .topdeps always keep ours
605 # --merge a normal merge takes place
606 # --theirs .topmsg and .topdeps always keep theirs
607 # --remove .topmsg and .topdeps are removed from the result and working tree
608 # note this function should only be called after attempt_index_merge fails as
609 # it implicity always does --no-ff (except for --merge which will --ff)
612 git_topmerge
-v _oldhead
"$@" || _ret
=$?
614 [ "$1" = "--merge" ] || _exclusions
=":/ :!/.topdeps :!/.topmsg"
615 [ "$_ret" != "0" ] || git
--no-pager diff-tree
--shortstat "$_oldhead" HEAD^
0 -- $_exclusions
619 # $1 => .topfile handling ([--]merge, [--]theirs, [--]remove or else do ours)
620 # $2 => current "HEAD"
621 # $3 => proposed fast-forward-to "HEAD"
622 # result is success if fast-forward satisfies $1
626 # merge and theirs will always be correct
629 # okay if both blobs are "missing" in $3
630 printf '%s\n' "$3:.topdeps" "$3:.topmsg" |
631 git cat-file
--batch-check="%(objectname) %(objecttype)" |
635 [ "$_tdt" = "missing" ] &&
636 [ "$_tmt" = "missing" ]
641 # okay if both blobs are the same (same hash or missing)
642 printf '%s\n' "$2:.topdeps" "$2:.topmsg" "$3:.topdeps" "$3:.topmsg" |
643 git cat-file
--batch-check="%(objectname) %(objecttype)" |
649 { [ "$_td1t" = "$_td2t" ] &&
650 { [ "$_td1o" = "$_td2o" ] ||
651 [ "$_td1t" = "missing" ]; }; } &&
652 { [ "$_tm1t" = "$_tm2t" ] &&
653 { [ "$_tm1o" = "$_tm2o" ] ||
654 [ "$_tm1t" = "missing" ]; }; }
661 # similar to git_merge but operates exclusively using a separate index and temp dir
662 # only trivial aggressive automatic (i.e. simple) merges are supported
664 # [optional] '--no-auto' to suppress "automatic" merging, merge fails instead
665 # [optional] '--merge', '--theirs' or '--remove' to alter .topfile handling
666 # $1 => '' to discard result, 'refs/?*' to update the specified ref or a varname
667 # $2 => '-m' MUST be '-m'
668 # $3 => commit message AND, if $1 matches refs/?* the update-ref message
669 # $4 => commit-ish to merge as "ours"
670 # $5 => commit-ish to merge as "theirs"
671 # [$6...] => more commit-ishes to merge as "theirs" in octopus
673 # all merging is done in a separate index (or temporary files for simple merges)
674 # if successful the ref or var is updated with the result
675 # otherwise everything is left unchanged and a silent failure occurs
676 # if successful and $1 matches refs/?* it WILL BE UPDATED to a new commit using the
677 # message and appropriate parents AND HEAD WILL BE DETACHED first if it's a symref
679 # otherwise if $1 does not match refs/?* and is not empty the named variable will
680 # be set to contain the resulting commit from the merge
681 # the working tree and index ARE LEFT COMPLETELY UNTOUCHED no matter what
682 v_attempt_index_merge
() {
684 if [ "$1" = "--no-auto" ]; then
689 [ "$1" = "--merge" ] || _exclusions
=":/ :!/.topdeps :!/.topmsg"
691 if [ "$1" = "--merge" ] ||
[ "$1" = "--theirs" ] ||
[ "$1" = "--remove" ]; then
694 if [ "$_mmode" = "merge" ] ||
[ "$_mmode" = "theirs" ]; then
695 _mstyle
="-top$_mmode"
698 [ "$#" -ge 5 ] && [ "$2" = "-m" ] && [ -n "$3" ] && [ -n "$4" ] && [ -n "$5" ] ||
699 die
"programmer error: invalid arguments to v_attempt_index_merge: $*"
704 rh
="$(git rev-parse --quiet --verify "$_head^
0" --)" && [ -n "$rh" ] ||
return 1
713 if [ $# -gt 1 ]; then
714 if [ "$_mmode" = "merge" ] ||
[ "$_mmode" = "theirs" ]; then
715 die
"programmer error: invalid octopus .topfile strategy to v_attempt_index_merge: --$_mode"
717 ihl
="$(git merge-base --independent "$@
")" ||
return 1
719 [ $# -ge 1 ] && [ -n "$1" ] ||
return 1
721 [ $# -eq 1 ] || _octo
=1
722 mb
="$(git merge-base ${_octo:+--octopus} "$rh" "$@
")" && [ -n "$mb" ] ||
{
723 mb
="$(git hash-object -w -t tree --stdin < /dev/null)"
726 if [ -z "$_mt" ]; then
727 if [ -n "$_octo" ]; then
728 while [ $# -gt 1 ] && mbh
="$(git merge-base "$rh" "$1")" && [ -n "$mbh" ]; do
729 if [ "$rh" = "$mbh" ]; then
730 if topff_ok
"$_mmode" "$rh" "$1"; then
731 _mmsg
="Fast-forward (no commit created)"
737 elif [ "$1" = "$mbh" ]; then
743 if [ $# -eq 1 ]; then
745 mb
="$(git merge-base "$rh" "$1")" && [ -n "$mb" ] ||
return 1
748 if [ -z "$_octo" ]; then
749 r1
="$(git rev-parse --quiet --verify "$1^
0" --)" && [ -n "$r1" ] ||
return 1
752 if [ "$rh" = "$mb" ]; then
753 if topff_ok
"$_mmode" "$rh" "$r1"; then
754 _mmsg
="Fast-forward (no commit created)"
759 elif [ "$r1" = "$mb" ]; then
760 [ -n "$_mmsg" ] || _mmsg
="Already up-to-date!"
768 if [ -z "$newc" ]; then
769 if [ "$_mmode" = "theirs" ] && [ -z "$oth" ]; then
770 oth
="$(git rev-parse --quiet --verify "$1^
0" --)" && [ -n "$oth" ] ||
return 1
773 inew
="$tg_tmp_dir/index.$$"
774 ! [ -e "$inew" ] ||
rm -f "$inew"
775 itmp
="$tg_tmp_dir/output.$$"
776 imrg
="$tg_tmp_dir/auto.$$"
777 [ -z "$_octo" ] ||
>"$imrg"
782 if [ -n "$_parents" ]; then
783 if contained_by
"$1" "$_newrh"; then
788 GIT_INDEX_FILE
="$inew" git read-tree
-m --aggressive -i "$mb" "$rh" "$1" ||
{ rm -f "$inew" "$imrg"; return 1; }
789 GIT_INDEX_FILE
="$inew" git ls-files
--unmerged --full-name --abbrev :/ >"$itmp" 2>&1 ||
{ rm -f "$inew" "$itmp" "$imrg"; return 1; }
790 ! [ -s "$itmp" ] ||
{
791 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
792 rm -f "$inew" "$itmp" "$imrg"
795 if [ -s "$itmp" ]; then
796 if [ -n "$_noauto" ]; then
797 rm -f "$inew" "$itmp" "$imrg"
800 if [ -n "$_octo" ]; then
801 cat "$itmp" >>"$imrg"
810 _parents
="${_parents:+$_parents }-p $1"
811 if [ $# -gt 1 ]; then
812 newt
="$(GIT_INDEX_FILE="$inew" git write-tree)" && [ -n "$newt" ] ||
{ rm -f "$inew" "$imrg"; return 1; }
819 if [ "$_mmode" != "merge" ]; then
821 theirs
) _source
="$oth";;
827 _newinfo
="$(git cat-file --batch-check="%(objecttype
) %(objectname
)$tab%(rest
)" <<-EOT |
828 $_source:.topdeps .topdeps
829 $_source:.topmsg .topmsg
831 sed -n 's/^blob /100644 /p'
833 [ -z "$_newinfo" ] || _newinfo
="$lf$_newinfo"
834 GIT_INDEX_FILE
="$inew" git update-index
--index-info <<-EOT || { rm -f "$inew" "$imrg"; return 1; }
835 0 $nullsha$tab.topdeps
836 0 $nullsha$tab.topmsg$_newinfo
839 newt
="$(GIT_INDEX_FILE="$inew" git write-tree)" && [ -n "$newt" ] ||
{ rm -f "$inew" "$imrg"; return 1; }
840 [ -z "$_octo" ] ||
sort -u <"$imrg"
841 rm -f "$inew" "$imrg"
842 newc
="$(git commit-tree -p "$orh" $_parents -m "$_msg" "$newt")" && [ -n "$newc" ] ||
return 1
843 _mmsg
="Merge made by the 'trivial aggressive$_auto${_octo:+ octopus}' strategy."
847 if [ -n "$_same" ]; then
849 if rv
="$(git rev-parse --quiet --verify "$_var" --)" && [ "$rv" = "$newc" ]; then
853 if [ -z "$_same" ]; then
854 detach_symref_head_on_branch
"$_var" ||
return 1
855 # git update-ref returns 0 even on failure :(
856 git update-ref
-m "$_msg" "$_var" "$newc" ||
return 1
860 eval "$_var="'"$newc"'
864 [ -n "$_nodt" ] || git
--no-pager diff-tree
--shortstat "$orh" "$newc" -- $_exclusions
868 # shortcut that passes $3 as a preceding argument (which must match refs/?*)
869 attempt_index_merge
() {
872 if [ "$1" = "--no-auto" ]; then
876 if [ "$1" = "--merge" ] ||
[ "$1" = "--theirs" ] ||
[ "$1" = "--remove" ]; then
880 case "$3" in refs
/?
*);;*)
881 die
"programmer error: invalid arguments to attempt_index_merge: $*"
883 v_attempt_index_merge
$_noauto $_mmode "$3" "$@"
888 [ -n "$1" ] ||
return 0
890 [ "$1" != "$on_base" ] ||
891 [ "$(git symbolic-ref -q HEAD)" != "refs/$topbases/$1" ]
898 update_branch_internal
() {
899 # We are cacheable until the first change
903 ## First, take care of our base
905 _depcheck
="$(get_temp tg-depcheck)"
907 needs_update
"$_update_name" >"$_depcheck" ||
:
908 if [ -n "$missing_deps" ]; then
909 msg
="Some dependencies are missing: $missing_deps"
910 if [ -n "$skipms" ]; then
911 info
"$msg; skipping"
912 elif [ -z "$all" ]; then
915 info
"$msg; skipping branch $_update_name"
919 # allow automatic simple merges by default until a failure occurs
921 if [ -s "$_depcheck" ]; then
923 sed 's/ [^ ]* *$//' |
# last is $_update_name
924 sed 's/.* \([^ ]*\)$/+\1/' |
# only immediate dependencies
925 sed 's/^\([^+]\)/-\1/' |
# now each line is +branch or -branch (+ == recurse)
926 >"$_depcheck.ideps" \
927 uniq -s 1 # fold branch lines; + always comes before - and thus wins within uniq
929 stash_now_if_requested
931 while read -r depline
; do
933 action
="${depline%$dep}"
935 # We do not distinguish between dependencies out-of-date
936 # and base/remote out-of-date cases for $dep here,
937 # but thanks to needs_update returning : or refs/remotes/<remote>/<name>
938 # for the latter, we do correctly recurse here
941 if [ x
"$action" = x
+ ]; then
942 case " $missing_deps " in *" $dep "*)
943 info
"Skipping recursing to missing dependency: $dep"
946 info
"Recursing to $dep..."
947 recursive_update
"$dep" ||
exit 3
949 done <"$_depcheck.ideps"
951 # Create a list of all the fully qualified ref names that need
952 # to be merged into $_update_name's base. This will be done
953 # as an octopus merge if there are no conflicts.
957 while read -r dep
; do
964 d
="${dep#refs/heads/}"
965 deplist
="${deplist:+$deplist }$d"
966 deplines
="$deplines$d$lf"
970 deplist
="${deplist:+$deplist }$d"
971 deplines
="$deplines$d$lf"
976 set -- "$@" "refs/heads/$dep"
977 deplist
="${deplist:+$deplist }$dep"
978 deplines
="$deplines$dep$lf"
981 done <"$_depcheck.ideps"
983 # Make sure we end up on the correct base branch
985 if [ $# -ge 2 ]; then
986 info
"Updating $_update_name base with deps: $deplist"
988 msg
="tgupdate: octopus merge $# deps into $_update_name base$lf$lf$deplines"
989 if attempt_index_merge
--remove -m "$msg" "refs/$topbases/$_update_name" "$@"; then
992 info
"Octopus merge failed; falling back to multiple 3-way merges"
997 for fulldep
in "$@"; do
998 # This will be either a proper topic branch
999 # or a remote base. (branch_needs_update() is called
1000 # only on the _dependencies_, not our branch itself!)
1004 dep
="${fulldep#refs/heads/}";;
1006 dep
="${fulldep#refs/}";;
1008 dep
="$fulldep";; # this should be a programmer error
1011 info
"Updating $_update_name base with $dep changes..."
1012 become_non_cacheable
1013 msg
="tgupdate: merge $dep into $_update_name base"
1015 ! attempt_index_merge
$no_auto --remove -m "$msg" "refs/$topbases/$_update_name" "$fulldep^0" &&
1017 # We need to switch to the base branch
1018 # ...but only if we aren't there yet (from failed previous merge)
1019 do_base_switch
"$_update_name" || die
"do_base_switch failed" &&
1020 git_merge
--remove --name "$_update_name base" --name "$dep" -m "$msg" "$fulldep^0"
1026 info
"Please commit merge resolution and call \`$tgdisplayac update --continue\`"
1027 info
"(use \`$tgdisplayac status\` to see more options)"
1032 info
"The base is up-to-date."
1036 ## Second, update our head with the remote branch
1039 merge_with
="refs/$topbases/$_update_name"
1041 if has_remote
"$_update_name"; then
1042 _rname
="refs/remotes/$base_remote/$_update_name"
1043 if branch_contains
"refs/heads/$_update_name" "$_rname"; then
1044 info
"The $_update_name head is up-to-date wrt. its remote branch."
1046 stash_now_if_requested
1047 info
"Reconciling $_update_name base with remote branch updates..."
1048 become_non_cacheable
1049 msg
="tgupdate: merge ${_rname#refs/} onto $_update_name base"
1054 if [ -n "$mergeresult" ]; then
1055 checkours
="$(git rev-parse --verify --quiet "refs
/$topbases/$_update_name^
0" --)" ||
:
1056 checktheirs
="$(git rev-parse --verify --quiet "$_rname^
0" --)" ||
:
1057 if [ "$mergeours" = "$checkours" ] && [ "$mergetheirs" = "$checktheirs" ]; then
1058 got_merge_with
="$mergeresult"
1062 [ -z "$got_merge_with" ] &&
1063 ! v_attempt_index_merge
$no_auto --theirs "merge_with" -m "$msg" "refs/$topbases/$_update_name" "$_rname^0" &&
1065 # *DETACH* our HEAD now!
1067 git checkout
-q --detach $iowopt "refs/$topbases/$_update_name" || die
"git checkout failed" &&
1068 git_merge
--theirs --name "$_update_name base content" --name "${_rname#refs/}" -m "$msg" "$_rname^0" &&
1069 merge_with
="$(git rev-parse --verify HEAD --)"
1073 "$(git rev-parse --verify --quiet "refs
/$topbases/$_update_name^
0" --)" \
1074 "$(git rev-parse --verify --quiet "$_rname^
0" --)"
1076 info
"Please commit merge resolution and call \`$tgdisplayac update --continue\`"
1077 info
"(use \`$tgdisplayac status\` to see more options)"
1080 # Go back but remember we want to merge with this, not base
1081 [ -z "$got_merge_with" ] || merge_with
="$got_merge_with"
1082 plusextra
="${_rname#refs/} + "
1087 ## Third, update our head with the base
1089 if branch_contains
"refs/heads/$_update_name" "$merge_with"; then
1090 info
"The $_update_name head is up-to-date wrt. the base."
1093 stash_now_if_requested
1094 info
"Updating $_update_name against ${plusextra}new base..."
1095 become_non_cacheable
1096 msg
="tgupdate: merge ${plusextra}$_update_name base into $_update_name"
1098 if [ -n "$brmmode" ] && [ "$base_remote" ]; then
1099 b4deps
="$(git rev-parse --verify --quiet "refs
/heads
/$_update_name:.topdeps
" --)" && [ -n "$b4deps" ] ||
1100 b4deps
="$(git hash-object -t blob -w --stdin </dev/null)"
1103 ! attempt_index_merge
$no_auto $brmmode -m "$msg" "refs/heads/$_update_name" "$merge_with^0" &&
1105 # Home, sweet home...
1106 # (We want to always switch back, in case we were
1107 # on the base from failed previous merge.)
1108 git checkout
-q $iowopt "$_update_name" || die
"git checkout failed" &&
1109 git_merge
$brmmode --name "$_update_name" --name "${plusextra}$topbases/$_update_name" -m "$msg" "$merge_with^0"
1113 merging_topfiles
="${brmmode:+1}"
1116 info
"Please commit merge resolution and call \`$tgdisplayac update --continue\`"
1117 info
"(use \`$tgdisplayac status\` to see more options)"
1121 # Fourth, auto create locally any newly depended on branches we got from the remote
1124 if [ -n "$b4deps" ] &&
1125 l8rdeps
="$(git rev-parse --verify --quiet "refs
/heads
/$_update_name:.topdeps
" --)" &&
1126 [ -n "$l8rdeps" ] && [ "$b4deps" != "$l8rdeps" ]
1129 while read -r newdep
; do
1130 if [ -n "$newdep" ]; then
1131 if auto_create_local_remote
"$newdep"; then
1134 if ref_exists
"refs/heads/$newdep"; then
1135 # maybe the line just moved around
1136 [ -n "$_olddeps" ] && [ -f "$_olddeps" ] ||
{
1137 _olddeps
="$(get_temp b4deps)" &&
1138 git cat-file blob
"$b4deps" >"$_olddeps"
1140 if awk -v "newdep=$newdep" '$0 == newdep {exit 1}' <"$_olddeps"; then
1141 # nope, it's a new head already existing locally
1145 # helpfully check to see if there's such a remote branch
1147 ! ref_exists
"refs/remotes/$base_remote/$newdep" || _rntgb
=1
1148 # maybe a locking local orphan base too
1150 if [ -n "$_rntgb" ] &&
1151 ref_exists
"refs/remotes/$base_remote/${topbases#heads/}$newdep" &&
1152 ref_exists
"refs/$topbases/$newdep"
1156 # spew the flexibly adjustable warning
1157 warn
"-------------------------------------"
1158 warn
"MISSING DEPENDENCY MERGED FROM REMOTE"
1159 warn
"-------------------------------------"
1160 warn
"Local Branch: $_update_name"
1161 warn
" Remote Name: $base_remote"
1162 warn
" Dependency: $newdep"
1163 if [ -n "$_blocked" ]; then
1164 warn
"Blocking Ref: refs/$topbases/$newdep"
1165 elif [ -n "$_rntgb" ]; then
1166 warn
"Existing Ref: refs/remotes/$base_remote/$newdep"
1169 if [ -n "$_blocked" ]; then
1170 warn
"There is no local branch by that name, but"
1171 warn
"there IS a remote TopGit branch available by"
1172 warn
"that name, but creation of a local version has"
1173 warn
"been blocked by existence of the ref shown above."
1174 elif [ -n "$_rntgb" ]; then
1175 warn
"There is no local branch or remote TopGit"
1176 warn
"branch available by that name, but there is an"
1177 warn
"existing non-TopGit remote branch ref shown above."
1178 warn
"Non-TopGit branches are not set up automatically"
1179 warn
"by TopGit and must be maintained manually."
1181 warn
"There is no local branch or remote branch"
1182 warn
"(TopGit or otherwise) available by that name."
1184 warn
"-------------------------------------"
1189 $(git diff --ignore-space-at-eol "$b4deps" "$l8rdeps" -- | diff_added_lines)
1198 update_branch_internal
"$@" || _ubicode
=$?
1199 while [ "$_maxdeploop" -gt 0 ] && [ "$_ubicode" = "75" ]; do
1200 _maxdeploop
="$(( $maxdeploop - 1 ))"
1201 info
"Updating $1 again with newly added dependencies..."
1203 update_branch_internal
"$@" || _ubicode
=$?
1208 # We are "read-only" and cacheable until the first change
1212 do_non_annihilated_branches_patterns
() {
1213 while read -r _pat
&& [ -n "$_pat" ]; do
1216 non_annihilated_branches
"$@"
1219 do_non_annihilated_branches
() {
1220 if [ -z "$pattern" ]; then
1221 non_annihilated_branches
1223 do_non_annihilated_branches_patterns
<<-EOT
1224 $(sed 'y/ /\n/' <<-LIST
1232 if [ -n "$all" ] && [ -z "$restored" ]; then
1234 while read name
&& [ -n "$name" ]; do
1235 case " $names " in *" $name "*);;*)
1236 names
="${names:+$names }$name"
1239 $(do_non_annihilated_branches)
1243 for name
in $names; do
1244 case " $processed " in *" $name "*) continue; esac
1245 [ -z "$all" ] && case "$names" in *" "*) ! :; esac || info
"Proccessing $name..."
1246 update_branch
"$name" ||
exit
1247 processed
="${processed:+$processed }$name"
1250 [ -z "$all" ] && case "$names" in *" "*) ! :; esac ||
1251 info
"Returning to ${current#refs/heads/}..."
1252 checkout_symref_full
"$current"
1253 ! [ -f "$git_dir/TGMERGE_MSG" ] ||
[ -e "$git_dir/MERGE_MSG" ] ||
1254 mv -f "$git_dir/TGMERGE_MSG" "$git_dir/MERGE_MSG" ||
: