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,2018
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 # options validation guarantees that at most one of basemsg or basefile is set
112 [ -z "$basemsg" ] || msgopt
='-m "$basemsg"'
113 if [ -n "$basefile" ]; then
114 # git merge does not accept a -F <msgfile> option so we have to fake it
115 basefilemsg
="$(cat "$basefile")" || die
"could not read file '$basefile'"
116 msgopt
='-m "$basefilemsg"'
119 if [ -n "$editmode" ]; then
120 if [ "$editmode" = "0" ]; then
126 if [ -z "$basemsg$basefile" ]; then
127 [ -n "$editopt" ] || editopt
="--edit"
128 basemsg
="tg update --base $tgbranch $2"
129 msgopt
='-m "$basemsg"'
131 [ -n "$editopt" ] || editopt
="--no-edit"
134 [ -z "$basenc" ] || ncopt
="--no-commit"
135 eval git merge
--no-ff --no-log --no-stat $auhopt $ncopt $editopt "$msgopt" "refs/$topbases/$tgbranch" -- ||
exit
136 [ -n "$basenc" ] || checkout_symref_full
"$current"
140 state_dir
="$git_dir/tg-update"
149 [ -d "$state_dir" ] ||
return 1
150 [ -s "$state_dir/fullcmd" ] ||
return 1
151 [ -f "$state_dir/remote" ] ||
return 1
152 [ -f "$state_dir/skipms" ] ||
return 1
153 [ -f "$state_dir/all" ] ||
return 1
154 [ -s "$state_dir/current" ] ||
return 1
155 [ -s "$state_dir/stashhash" ] ||
return 1
156 [ -s "$state_dir/name" ] ||
return 1
157 [ -s "$state_dir/names" ] ||
return 1
158 [ -f "$state_dir/processed" ] ||
return 1
159 [ -f "$state_dir/no_auto" ] ||
return 1
160 [ -f "$state_dir/setautoupdate" ] ||
return 1
161 [ -f "$state_dir/merging_topfiles" ] ||
return 1
162 [ -f "$state_dir/mergeours" ] ||
return 1
163 [ -f "$state_dir/mergeours" ] ||
return 1
164 if [ -s "$state_dir/mergeours" ]; then
165 [ -s "$state_dir/mergetheirs" ] ||
return 1
167 ! [ -s "$state_dir/mergetheirs" ] ||
return 1
172 is_active || die
"programmer error"
173 IFS
= read -r fullcmd
<"$state_dir/fullcmd" && [ -n "$fullcmd" ]
174 IFS
= read -r base_remote
<"$state_dir/remote" ||
:
175 IFS
= read -r skipms
<"$state_dir/skipms" ||
:
176 IFS
= read -r all
<"$state_dir/all" ||
:
177 IFS
= read -r current
<"$state_dir/current" && [ -n "$current" ]
178 IFS
= read -r stashhash
<"$state_dir/stashhash" && [ -n "$stashhash" ]
179 IFS
= read -r name
<"$state_dir/name" && [ -n "$name" ]
180 IFS
= read -r names
<"$state_dir/names" && [ -n "$names" ]
181 IFS
= read -r processed
<"$state_dir/processed" ||
:
182 IFS
= read -r next_no_auto
<"$state_dir/no_auto" ||
:
183 IFS
= read -r setautoupdate
<"$state_dir/setautoupdate" ||
:
184 # merging_topfiles is for outside info but not to be restored
185 IFS
= read -r mergeours
<"$state_dir/mergeours" ||
:
186 IFS
= read -r mergetheirs
<"$state_dir/mergetheirs" ||
:
187 if [ -n "$mergeours" ] && [ -n "$mergetheirs" ]; then
188 headhash
="$(git rev-parse --quiet --verify HEAD --)" ||
:
189 if [ -n "$headhash" ]; then
190 parents
="$(git --no-pager log -n 1 --format='format:%P' "$headhash" -- 2>/dev/null)" ||
:
191 if [ "$parents" = "$mergeours $mergetheirs" ]; then
192 mergeresult
="$headhash"
195 if [ -z "$mergeresult" ]; then
204 ! [ -e "$state_dir" ] ||
rm -rf "$state_dir" >/dev
/null
2>&1 ||
:
209 ! is_active || isactive
=1
211 if [ -z "$isactive" ] && [ $# -eq 1 ]; then
212 case "$1" in --abort|
--stop|
--continue|
--skip) isactiveopt
=1; esac
214 if [ -n "$isactive" ] ||
[ -n "$isactiveopt" ]; then
215 [ $# -eq 1 ] && [ x
"$1" != x
"--status" ] ||
{ do_status
; exit 0; }
217 if [ -z "$isactive" ]; then
219 info
"No update is currently active"
226 IFS
= read -r current
<"$state_dir/current" ||
:
227 IFS
= read -r stashhash
<"$state_dir/stashhash" ||
:
229 if [ -n "$stashhash" ]; then
230 tg revert
-f -q -q --no-stash "$stashhash" >/dev
/null
2>&1 ||
:
232 if [ -n "$current" ]; then
233 info
"Ok, update aborted, returning to ${current#refs/heads/}"
234 checkout_symref_full
-f "$current"
236 info
"Ok, update aborted. Now, you just need to"
237 info
"switch back to some sane branch using \`git$gitcdopt checkout\`."
239 ! [ -f "$git_dir/TGMERGE_MSG" ] ||
[ -e "$git_dir/MERGE_MSG" ] ||
240 mv -f "$git_dir/TGMERGE_MSG" "$git_dir/MERGE_MSG" ||
:
245 info
"Ok, update stopped. Now, you just need to"
246 info
"switch back to some sane branch using \`git$gitcdopt checkout\`."
247 ! [ -f "$git_dir/TGMERGE_MSG" ] ||
[ -e "$git_dir/MERGE_MSG" ] ||
248 mv -f "$git_dir/TGMERGE_MSG" "$git_dir/MERGE_MSG" ||
:
253 if [ "$1" = "--skip" ]; then
254 info
"Ok, I will try to continue without updating this branch."
256 case " $processed " in *" $name "*);;*)
257 processed
="${processed:+$processed }$name"
260 # assume user fixed it
261 # we could be left on a detached HEAD if we were resolving
262 # a conflict while merging a base in, fix it with a checkout
263 git checkout
-q $iowopt "$(strip_ref "$name")"
272 if [ -z "$restored" ]; then
274 [ "$(git config --get --bool topgit.setAutoUpdate 2>/dev/null)" != "false" ] ||
277 while [ -n "$1" ]; do
283 [ -z "$names$pattern" ] || usage
1
291 --auto|
--auto-update|
--set-auto|
--set-auto-update)
293 --no-auto|
--no-auto-update|
--no-set-auto|
--no-set-auto-update)
308 [ $# -gt 0 ] && [ -n "$1" ] || die
"option $arg requires an argument"
314 basemsg
="${1#--message=}";;
316 [ $# -gt 0 ] && [ -n "$1" ] || die
"option $arg requires an argument"
322 basefile
="${1#--file=}"
323 [ -n "$basefile" ] || die
"option --file= requires an argument"
332 if [ -z "$all" ]; then
333 namecnt
=$
(( $namecnt + 1 ))
334 [ "$namecnt" != "1" ] || name1
="$arg"
335 [ "$namecnt" != "2" ] || name2
="$arg"
336 names
="${names:+$names }$arg"
338 pattern
="${pattern:+$pattern }refs/$topbases/$(strip_ref "$arg")"
344 while [ $# -gt 0 ]; do
345 if [ -z "$all" ]; then
346 namecnt
=$
(( $namecnt + 1 ))
347 [ "$namecnt" != "1" ] || name1
="$1"
348 [ "$namecnt" != "2" ] || name2
="$1"
349 names
="${names:+$names }$*"
351 pattern
="${pattern:+$pattern }refs/$topbases/$(strip_ref "$1")"
355 [ -n "$basemode" ] ||
[ -z "$editmode$basemsg$basefile$basenc$basefrc" ] || usage
1
356 [ -z "$basemode" ] ||
[ -z "$all$skipms" ] || usage
1
357 [ -z "$basemode" ] ||
[ -z "$basemsg" ] ||
[ -z "$basefile" ] || usage
1
358 [ -z "$basemode" ] ||
[ "$namecnt" -eq 2 ] || usage
1
360 current
="$(git symbolic-ref -q HEAD)" ||
:
361 if [ -n "$current" ]; then
362 [ -n "$(git rev-parse --verify --quiet HEAD --)" ] ||
363 die
"cannot return to unborn branch; switch to another branch"
365 current
="$(git rev-parse --verify --quiet HEAD)" ||
366 die
"cannot return to invalid HEAD; switch to another branch"
369 [ -z "$basemode" ] || do_base_mode
"$name1" "$name2"
371 origpattern
="$pattern"
372 [ -z "$pattern" ] && pattern
="refs/$topbases"
375 [ -n "$all$names" ] || names
="HEAD"
376 if [ -z "$all" ]; then
379 while [ $# -gt 0 ]; do
380 name
="$(verify_topgit_branch "$1")"
381 case " $names " in *" $name "*);;*)
382 names
="${names:+$names }$name"
388 if [ "$namecnt" -eq 1 ]; then
389 case "$fullcmd" in *" @"|
*" HEAD")
391 fullcmd
="${fullcmd% *}"
394 [ "$namecnt" -ne 0 ] || fullcmd
="$fullcmd $names"
400 mkdir
-p "$state_dir"
401 printf '%s\n' "$fullcmd" >"$state_dir/fullcmd"
402 printf '%s\n' "$base_remote" >"$state_dir/remote"
403 printf '%s\n' "$skipms" >"$state_dir/skipms"
404 printf '%s\n' "$all" >"$state_dir/all"
405 printf '%s\n' "$current" >"$state_dir/current"
406 printf '%s\n' "$stashhash" >"$state_dir/stashhash"
407 printf '%s\n' "$name" >"$state_dir/name"
408 printf '%s\n' "$names" >"$state_dir/names"
409 printf '%s\n' "$processed" >"$state_dir/processed"
410 printf '%s\n' "$no_auto" >"$state_dir/no_auto"
411 printf '%s\n' "$setautoupdate" >"$state_dir/setautoupdate"
412 # this one is an external flag and needs to be zero length for false
413 printf '%s' "$merging_topfiles" >"$state_dir/merging_topfiles"
414 printf '%s\n' "$1" >"$state_dir/mergeours"
415 printf '%s\n' "$2" >"$state_dir/mergetheirs"
418 stash_now_if_requested
() {
419 [ -z "$TG_RECURSIVE" ] ||
return 0
420 [ -z "$stashhash" ] ||
return 0
421 ensure_ident_available
422 msg
="tgupdate: autostash before update"
423 if [ -n "$all" ]; then
424 msg
="$msg --all${origpattern:+ $origpattern}"
429 if [ -n "$stash" ]; then
430 tg tag
-q -q -m "$msg" --stash "$@" &&
431 stashhash
="$(git rev-parse --quiet --verify refs/tgstash --)" &&
432 [ -n "$stashhash" ] &&
433 [ "$(git cat-file -t "$stashhash" -- 2>/dev/null)" = "tag" ] ||
434 die
"requested --stash failed"
436 tg tag
--anonymous "$@" &&
437 stashhash
="$(git rev-parse --quiet --verify TG_STASH --)" &&
438 [ -n "$stashhash" ] &&
439 [ "$(git cat-file -t "$stashhash" -- 2>/dev/null)" = "tag" ] ||
440 die
"anonymous --stash failed"
442 [ -z "$next_no_auto" ] || no_auto
="$next_no_auto"
450 if [ -n "$TG_RECURSIVE" ]; then
451 TG_RECURSIVE
="==> [$1]${TG_RECURSIVE#==>}"
453 TG_RECURSIVE
="==> [$1]$lf"
457 [ $_ret -eq 3 ] && exit 3
461 # If HEAD is a symref to "$1" detach it at its current value
462 detach_symref_head_on_branch
() {
463 _hsr
="$(git symbolic-ref -q HEAD --)" && [ -n "$_hsr" ] ||
return 0
464 _hrv
="$(git rev-parse --quiet --verify HEAD --)" && [ -n "$_hrv" ] ||
465 die
"cannot detach_symref_head_on_branch from unborn branch $_hsr"
466 git update-ref
--no-deref -m "detaching HEAD from $_hsr to safely update it" HEAD
"$_hrv"
469 # git_topmerge will need this even on success and since it might otherwise
470 # be called many times do it just the once here and now
471 repotoplvl
="$(git rev-parse --show-toplevel)" && [ -n "$repotoplvl" ] && [ -d "$repotoplvl" ] ||
472 die
"git rev-parse --show-toplevel failed"
474 # Run an in-tree recursive merge but make sure we get the desired version of
475 # any .topdeps and .topmsg files. The $auhopt and --no-stat options are
476 # always implicitly in effect. If successful, a new commit is performed on HEAD.
478 # The "git merge-recursive" tool (and others) must be run to get the desired
479 # result. And --no-ff is always implicitly in effect as well.
481 # NOTE: [optional] arguments MUST appear in the order shown
482 # [optional] '-v' varname => optional variable to return original HEAD hash in
483 # [optional] '--merge', '--theirs' or '--remove' to alter .topfile handling
484 # [optional] '--name' <name-for-ours [--name <name-for-theirs>]
485 # $1 => '-m' MUST be '-m'
486 # $2 => commit message
487 # $3 => commit-ish to merge as "theirs"
491 [ "$1" != "-v" ] ||
[ $# -lt 2 ] ||
[ -z "$2" ] ||
{ _ovar
="$2"; shift 2; }
493 case "$1" in --theirs|
--remove|
--merge) _mmode
="${1#--}"; shift; esac
496 if [ "$1" = "--name" ] && [ $# -ge 2 ]; then
499 if [ "$1" = "--name" ] && [ $# -ge 2 ]; then
504 : "${_nameours:=HEAD}"
505 [ "$#" -eq 3 ] && [ "$1" = "-m" ] && [ -n "$2" ] && [ -n "$3" ] ||
506 die
"programmer error: invalid arguments to git_topmerge: $*"
507 _ours
="$(git rev-parse --verify HEAD^0)" || die
"git rev-parse failed"
508 _theirs
="$(git rev-parse --verify "$3^
0")" || die
"git rev-parse failed"
509 [ -z "$_ovar" ] ||
eval "$_ovar="'"$_ours"'
510 eval "GITHEAD_$_ours="'"$_nameours"' && eval export "GITHEAD_$_ours"
511 if [ -n "$_nametheirs" ]; then
512 eval "GITHEAD_$_theirs="'"$_nametheirs"' && eval export "GITHEAD_$_theirs"
515 if [ "$_mmode" = "merge" ]; then
516 TG_L1
="$_nameours" && export TG_L1
517 TG_L2
="merged common ancestors" && export TG_L2
518 TG_L3
="${_nametheirs:-$3}" && export TG_L3
519 _mdriver
='git merge-file -L "$TG_L1" -L "$TG_L2" -L "$TG_L3" --marker-size=%L %A %O %B'
523 _mb
="$(git merge-base --all "$_ours" "$_theirs")" && [ -n "$_mb" ] ||
524 { _mt
=1; _mb
="$(git hash-object -w -t tree --stdin < /dev/null)"; }
525 # any .topdeps or .topmsg output needs to be stripped from stdout
526 tmpstdout
="$tg_tmp_dir/stdout.$$"
528 git
-c "merge.ours.driver=$_mdriver" merge-recursive \
529 $_mb -- "$_ours" "$_theirs" >"$tmpstdout" || _ret
=$?
530 # success or failure is not relevant until after fixing up the
531 # .topdeps and .topmsg files and running rerere unless _ret >= 126
532 [ $_ret -lt 126 ] ||
return $_ret
533 if [ "$_mmode" = "merge" ]; then
537 theirs
) _source
="$_theirs";;
539 *) _source
="$_ours";;
543 _newinfo
="$(git cat-file --batch-check="%(objecttype
) %(objectname
)$tab%(rest
)" <<-EOT |
544 $_source:.topdeps .topdeps
545 $_source:.topmsg .topmsg
547 sed -n 's/^blob /100644 /p'
549 [ -z "$_newinfo" ] || _newinfo
="$lf$_newinfo"
550 git update-index
--index-info <<-EOT ||
551 0 $nullsha$tab.topdeps
552 0 $nullsha$tab.topmsg$_newinfo
554 die
"git update-index failed"
555 if [ "$_mmode" = "remove" ] &&
556 { [ -e "$repotoplvl/.topdeps" ] ||
[ -e "$repotoplvl/.topmsg" ]; }
558 rm -r -f "$repotoplvl/.topdeps" "$repotoplvl/.topmsg" >/dev
/null
2>&1 ||
:
560 for zapbad
in "$repotoplvl/.topdeps" "$repotoplvl/.topmsg"; do
561 if [ -e "$zapbad" ] && { [ -L "$zapbad" ] ||
[ ! -f "$zapbad" ]; }; then
565 (cd "$repotoplvl" && git checkout-index
-q -f -u -- .topdeps .topmsg
) ||
566 die
"git checkout-index failed"
568 # dump output without any .topdeps or .topmsg messages
569 sed -e '/ \.topdeps/d' -e '/ \.topmsg/d' <"$tmpstdout"
571 # rerere will be a nop unless rerere.enabled is true, but might complete the merge!
572 eval git
"${setautoupdate:+-c rerere.autoupdate=1}" rerere ||
:
573 git ls-files
--unmerged --full-name --abbrev :/ >"$tmpstdout" 2>&1 ||
574 die
"git ls-files failed"
575 if [ -s "$tmpstdout" ]; then
576 [ "$_ret" != "0" ] || _ret
=1
580 if [ $_ret -ne 0 ]; then
581 # merge failed, spit out message, enter "merge" mode and return
583 printf '%s\n\n# Conflicts:\n' "$_msg"
584 sed -n "/$tab/s/^[^$tab]*/#/p" <"$tmpstdout" |
sort -u
585 } >"$git_dir/MERGE_MSG"
586 git update-ref MERGE_HEAD
"$_theirs" ||
:
587 echo 'Automatic merge failed; fix conflicts and then commit the result.'
591 # commit time at last!
592 thetree
="$(git write-tree)" || die
"git write-tree failed"
593 # avoid an extra "already up-to-date" commit (can't happen if _mt though)
596 origtree
="$(git rev-parse --quiet --verify "$_ours^
{tree
}" --)" &&
598 } || die
"git rev-parse failed"
599 if [ "$origtree" != "$thetree" ] ||
! contained_by
"$_theirs" "$_ours"; then
600 thecommit
="$(git commit-tree -p "$_ours" -p "$_theirs" -m "$_msg" "$thetree")" &&
601 [ -n "$thecommit" ] || die
"git commit-tree failed"
602 git update-ref
-m "$_msg" HEAD
"$thecommit" || die
"git update-ref failed"
604 # mention how the merge was made
605 echo "Merge made by the 'recursive' strategy."
610 # run git_topmerge with the passed in arguments (it always does --no-stat)
611 # then return the exit status of git_topmerge
612 # if the returned exit status is no error show a shortstat before
613 # returning assuming the merge was done into the previous HEAD but exclude
614 # .topdeps and .topmsg info from the stat unless doing a --merge
615 # if the first argument is --merge or --theirs or --remove handle .topmsg/.topdeps
617 # (default) .topmsg and .topdeps always keep ours
618 # --merge a normal merge takes place
619 # --theirs .topmsg and .topdeps always keep theirs
620 # --remove .topmsg and .topdeps are removed from the result and working tree
621 # note this function should only be called after attempt_index_merge fails as
622 # it implicity always does --no-ff (except for --merge which will --ff)
625 git_topmerge
-v _oldhead
"$@" || _ret
=$?
627 [ "$1" = "--merge" ] || _exclusions
=":/ :!/.topdeps :!/.topmsg"
628 [ "$_ret" != "0" ] || git
--no-pager diff-tree
--shortstat "$_oldhead" HEAD^
0 -- $_exclusions
632 # $1 => .topfile handling ([--]merge, [--]theirs, [--]remove or else do ours)
633 # $2 => current "HEAD"
634 # $3 => proposed fast-forward-to "HEAD"
635 # result is success if fast-forward satisfies $1
639 # merge and theirs will always be correct
642 # okay if both blobs are "missing" in $3
643 printf '%s\n' "$3:.topdeps" "$3:.topmsg" |
644 git cat-file
--batch-check="%(objectname) %(objecttype)" |
648 [ "$_tdt" = "missing" ] &&
649 [ "$_tmt" = "missing" ]
654 # okay if both blobs are the same (same hash or missing)
655 printf '%s\n' "$2:.topdeps" "$2:.topmsg" "$3:.topdeps" "$3:.topmsg" |
656 git cat-file
--batch-check="%(objectname) %(objecttype)" |
662 { [ "$_td1t" = "$_td2t" ] &&
663 { [ "$_td1o" = "$_td2o" ] ||
664 [ "$_td1t" = "missing" ]; }; } &&
665 { [ "$_tm1t" = "$_tm2t" ] &&
666 { [ "$_tm1o" = "$_tm2o" ] ||
667 [ "$_tm1t" = "missing" ]; }; }
674 # similar to git_merge but operates exclusively using a separate index and temp dir
675 # only trivial aggressive automatic (i.e. simple) merges are supported
677 # [optional] '--no-auto' to suppress "automatic" merging, merge fails instead
678 # [optional] '--merge', '--theirs' or '--remove' to alter .topfile handling
679 # $1 => '' to discard result, 'refs/?*' to update the specified ref or a varname
680 # $2 => '-m' MUST be '-m'
681 # $3 => commit message AND, if $1 matches refs/?* the update-ref message
682 # $4 => commit-ish to merge as "ours"
683 # $5 => commit-ish to merge as "theirs"
684 # [$6...] => more commit-ishes to merge as "theirs" in octopus
686 # all merging is done in a separate index (or temporary files for simple merges)
687 # if successful the ref or var is updated with the result
688 # otherwise everything is left unchanged and a silent failure occurs
689 # if successful and $1 matches refs/?* it WILL BE UPDATED to a new commit using the
690 # message and appropriate parents AND HEAD WILL BE DETACHED first if it's a symref
692 # otherwise if $1 does not match refs/?* and is not empty the named variable will
693 # be set to contain the resulting commit from the merge
694 # the working tree and index ARE LEFT COMPLETELY UNTOUCHED no matter what
695 v_attempt_index_merge
() {
697 if [ "$1" = "--no-auto" ]; then
702 [ "$1" = "--merge" ] || _exclusions
=":/ :!/.topdeps :!/.topmsg"
704 if [ "$1" = "--merge" ] ||
[ "$1" = "--theirs" ] ||
[ "$1" = "--remove" ]; then
707 if [ "$_mmode" = "merge" ] ||
[ "$_mmode" = "theirs" ]; then
708 _mstyle
="-top$_mmode"
711 [ "$#" -ge 5 ] && [ "$2" = "-m" ] && [ -n "$3" ] && [ -n "$4" ] && [ -n "$5" ] ||
712 die
"programmer error: invalid arguments to v_attempt_index_merge: $*"
717 rh
="$(git rev-parse --quiet --verify "$_head^
0" --)" && [ -n "$rh" ] ||
return 1
726 if [ $# -gt 1 ]; then
727 if [ "$_mmode" = "merge" ] ||
[ "$_mmode" = "theirs" ]; then
728 die
"programmer error: invalid octopus .topfile strategy to v_attempt_index_merge: --$_mode"
730 ihl
="$(git merge-base --independent "$@
")" ||
return 1
732 [ $# -ge 1 ] && [ -n "$1" ] ||
return 1
734 [ $# -eq 1 ] || _octo
=1
735 mb
="$(git merge-base ${_octo:+--octopus} "$rh" "$@
")" && [ -n "$mb" ] ||
{
736 mb
="$(git hash-object -w -t tree --stdin < /dev/null)"
739 if [ -z "$_mt" ]; then
740 if [ -n "$_octo" ]; then
741 while [ $# -gt 1 ] && mbh
="$(git merge-base "$rh" "$1")" && [ -n "$mbh" ]; do
742 if [ "$rh" = "$mbh" ]; then
743 if topff_ok
"$_mmode" "$rh" "$1"; then
744 _mmsg
="Fast-forward (no commit created)"
750 elif [ "$1" = "$mbh" ]; then
756 if [ $# -eq 1 ]; then
758 mb
="$(git merge-base "$rh" "$1")" && [ -n "$mb" ] ||
return 1
761 if [ -z "$_octo" ]; then
762 r1
="$(git rev-parse --quiet --verify "$1^
0" --)" && [ -n "$r1" ] ||
return 1
765 if [ "$rh" = "$mb" ]; then
766 if topff_ok
"$_mmode" "$rh" "$r1"; then
767 _mmsg
="Fast-forward (no commit created)"
772 elif [ "$r1" = "$mb" ]; then
773 [ -n "$_mmsg" ] || _mmsg
="Already up-to-date!"
781 if [ -z "$newc" ]; then
782 if [ "$_mmode" = "theirs" ] && [ -z "$oth" ]; then
783 oth
="$(git rev-parse --quiet --verify "$1^
0" --)" && [ -n "$oth" ] ||
return 1
786 inew
="$tg_tmp_dir/index.$$"
787 ! [ -e "$inew" ] ||
rm -f "$inew"
788 itmp
="$tg_tmp_dir/output.$$"
789 imrg
="$tg_tmp_dir/auto.$$"
790 [ -z "$_octo" ] ||
>"$imrg"
795 if [ -n "$_parents" ]; then
796 if contained_by
"$1" "$_newrh"; then
801 GIT_INDEX_FILE
="$inew" git read-tree
-m --aggressive -i "$mb" "$rh" "$1" ||
{ rm -f "$inew" "$imrg"; return 1; }
802 GIT_INDEX_FILE
="$inew" git ls-files
--unmerged --full-name --abbrev :/ >"$itmp" 2>&1 ||
{ rm -f "$inew" "$itmp" "$imrg"; return 1; }
803 ! [ -s "$itmp" ] ||
{
804 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
805 rm -f "$inew" "$itmp" "$imrg"
808 if [ -s "$itmp" ]; then
809 if [ -n "$_noauto" ]; then
810 rm -f "$inew" "$itmp" "$imrg"
813 if [ -n "$_octo" ]; then
814 cat "$itmp" >>"$imrg"
823 _parents
="${_parents:+$_parents }-p $1"
824 if [ $# -gt 1 ]; then
825 newt
="$(GIT_INDEX_FILE="$inew" git write-tree)" && [ -n "$newt" ] ||
{ rm -f "$inew" "$imrg"; return 1; }
832 if [ "$_mmode" != "merge" ]; then
834 theirs
) _source
="$oth";;
840 _newinfo
="$(git cat-file --batch-check="%(objecttype
) %(objectname
)$tab%(rest
)" <<-EOT |
841 $_source:.topdeps .topdeps
842 $_source:.topmsg .topmsg
844 sed -n 's/^blob /100644 /p'
846 [ -z "$_newinfo" ] || _newinfo
="$lf$_newinfo"
847 GIT_INDEX_FILE
="$inew" git update-index
--index-info <<-EOT || { rm -f "$inew" "$imrg"; return 1; }
848 0 $nullsha$tab.topdeps
849 0 $nullsha$tab.topmsg$_newinfo
852 newt
="$(GIT_INDEX_FILE="$inew" git write-tree)" && [ -n "$newt" ] ||
{ rm -f "$inew" "$imrg"; return 1; }
853 [ -z "$_octo" ] ||
sort -u <"$imrg"
854 rm -f "$inew" "$imrg"
855 newc
="$(git commit-tree -p "$orh" $_parents -m "$_msg" "$newt")" && [ -n "$newc" ] ||
return 1
856 _mmsg
="Merge made by the 'trivial aggressive$_auto${_octo:+ octopus}' strategy."
860 if [ -n "$_same" ]; then
862 if rv
="$(git rev-parse --quiet --verify "$_var" --)" && [ "$rv" = "$newc" ]; then
866 if [ -z "$_same" ]; then
867 detach_symref_head_on_branch
"$_var" ||
return 1
868 # git update-ref returns 0 even on failure :(
869 git update-ref
-m "$_msg" "$_var" "$newc" ||
return 1
873 eval "$_var="'"$newc"'
877 [ -n "$_nodt" ] || git
--no-pager diff-tree
--shortstat "$orh" "$newc" -- $_exclusions
881 # shortcut that passes $3 as a preceding argument (which must match refs/?*)
882 attempt_index_merge
() {
885 if [ "$1" = "--no-auto" ]; then
889 if [ "$1" = "--merge" ] ||
[ "$1" = "--theirs" ] ||
[ "$1" = "--remove" ]; then
893 case "$3" in refs
/?
*);;*)
894 die
"programmer error: invalid arguments to attempt_index_merge: $*"
896 v_attempt_index_merge
$_noauto $_mmode "$3" "$@"
901 [ -n "$1" ] ||
return 0
903 [ "$1" != "$on_base" ] ||
904 [ "$(git symbolic-ref -q HEAD)" != "refs/$topbases/$1" ]
911 update_branch_internal
() {
912 # We are cacheable until the first change
916 ## First, take care of our base
918 _depcheck
="$(get_temp tg-depcheck)"
920 needs_update
"$_update_name" >"$_depcheck" ||
:
921 if [ -n "$missing_deps" ]; then
922 msg
="Some dependencies are missing: $missing_deps"
923 if [ -n "$skipms" ]; then
924 info
"$msg; skipping"
925 elif [ -z "$all" ]; then
928 info
"$msg; skipping branch $_update_name"
932 # allow automatic simple merges by default until a failure occurs
934 if [ -s "$_depcheck" ]; then
935 # (1) last word is $_update_name, remove it
936 # (2) keep only immediate dependencies of a chain adding a leading '+'
937 # (3) one-level deep dependencies get a '-' prefix instead
939 -e 's/ [^ ]* *$//; # (1)' \
940 -e 's/.* \([^ ]*\)$/+\1/; # (2)' \
941 -e 's/^\([^+]\)/-\1/; # (3)' |
942 # now each line is +branch or -branch (+ == recurse)
943 >"$_depcheck.ideps" \
944 uniq -s 1 # fold branch lines; + always comes before - and thus wins within uniq
946 stash_now_if_requested
948 while read -r depline
; do
950 action
="${depline%$dep}"
952 # We do not distinguish between dependencies out-of-date
953 # and base/remote out-of-date cases for $dep here,
954 # but thanks to needs_update returning : or :refs/remotes/...
955 # for the latter, we do correctly recurse here
958 if [ x
"$action" = x
+ ]; then
959 case " $missing_deps " in *" $dep "*)
960 info
"Skipping recursing to missing dependency: $dep"
963 info
"Recursing to $dep..."
964 recursive_update
"$dep" ||
exit 3
966 done <"$_depcheck.ideps"
968 # Create a list of all the fully qualified ref names that need
969 # to be merged into $_update_name's base. This will be done
970 # as an octopus merge if there are no conflicts.
974 while read -r dep
; do
983 deplist
="${deplist:+$deplist }$d"
984 deplines
="$deplines$d$lf"
988 deplist
="${deplist:+$deplist }$d"
989 deplines
="$deplines$d$lf"
994 set -- "$@" "refs/heads/$dep"
995 deplist
="${deplist:+$deplist }$dep"
996 deplines
="$deplines$dep$lf"
999 done <"$_depcheck.ideps"
1001 # Make sure we end up on the correct base branch
1003 if [ $# -ge 2 ]; then
1004 info
"Updating $_update_name base with deps: $deplist"
1005 become_non_cacheable
1006 msg
="tgupdate: octopus merge $# deps into $_update_name base$lf$lf$deplines"
1007 if attempt_index_merge
--remove -m "$msg" "refs/$topbases/$_update_name" "$@"; then
1010 info
"Octopus merge failed; falling back to multiple 3-way merges"
1015 for fulldep
in "$@"; do
1016 # This will be either a proper topic branch
1017 # or a remote base. (branch_needs_update() is called
1018 # only on the _dependencies_, not our branch itself!)
1022 dep
="${fulldep#refs/heads/}";;
1024 dep
="${fulldep#refs/}";;
1026 dep
="$fulldep";; # this should be a programmer error
1029 info
"Updating $_update_name base with $dep changes..."
1030 become_non_cacheable
1031 msg
="tgupdate: merge $dep into $_update_name base"
1033 ! attempt_index_merge
$no_auto --remove -m "$msg" "refs/$topbases/$_update_name" "$fulldep^0" &&
1035 # We need to switch to the base branch
1036 # ...but only if we aren't there yet (from failed previous merge)
1037 do_base_switch
"$_update_name" || die
"do_base_switch failed" &&
1038 git_merge
--remove --name "$_update_name base" --name "$dep" -m "$msg" "$fulldep^0"
1044 info
"Please commit merge resolution and call \`$tgdisplayac update --continue\`"
1045 info
"(use \`$tgdisplayac status\` to see more options)"
1050 info
"The base is up-to-date."
1054 ## Second, update our head with the remote branch
1057 merge_with
="refs/$topbases/$_update_name"
1059 if has_remote
"$_update_name"; then
1060 _rname
="refs/remotes/$base_remote/$_update_name"
1061 if branch_contains
"refs/heads/$_update_name" "$_rname"; then
1062 info
"The $_update_name head is up-to-date wrt. its remote branch."
1064 stash_now_if_requested
1065 info
"Reconciling $_update_name base with remote branch updates..."
1066 become_non_cacheable
1067 msg
="tgupdate: merge ${_rname#refs/} onto $_update_name base"
1072 if [ -n "$mergeresult" ]; then
1073 checkours
="$(git rev-parse --verify --quiet "refs
/$topbases/$_update_name^
0" --)" ||
:
1074 checktheirs
="$(git rev-parse --verify --quiet "$_rname^
0" --)" ||
:
1075 if [ "$mergeours" = "$checkours" ] && [ "$mergetheirs" = "$checktheirs" ]; then
1076 got_merge_with
="$mergeresult"
1080 [ -z "$got_merge_with" ] &&
1081 ! v_attempt_index_merge
$no_auto --theirs "merge_with" -m "$msg" "refs/$topbases/$_update_name" "$_rname^0" &&
1083 # *DETACH* our HEAD now!
1085 git checkout
-q --detach $iowopt "refs/$topbases/$_update_name" || die
"git checkout failed" &&
1086 git_merge
--theirs --name "$_update_name base content" --name "${_rname#refs/}" -m "$msg" "$_rname^0" &&
1087 merge_with
="$(git rev-parse --verify HEAD --)"
1091 "$(git rev-parse --verify --quiet "refs
/$topbases/$_update_name^
0" --)" \
1092 "$(git rev-parse --verify --quiet "$_rname^
0" --)"
1094 info
"Please commit merge resolution and call \`$tgdisplayac update --continue\`"
1095 info
"(use \`$tgdisplayac status\` to see more options)"
1098 # Go back but remember we want to merge with this, not base
1099 [ -z "$got_merge_with" ] || merge_with
="$got_merge_with"
1100 plusextra
="${_rname#refs/} + "
1105 ## Third, update our head with the base
1107 if branch_contains
"refs/heads/$_update_name" "$merge_with"; then
1108 info
"The $_update_name head is up-to-date wrt. the base."
1111 stash_now_if_requested
1112 info
"Updating $_update_name against ${plusextra}new base..."
1113 become_non_cacheable
1114 msg
="tgupdate: merge ${plusextra}$_update_name base into $_update_name"
1116 if [ -n "$brmmode" ] && [ "$base_remote" ]; then
1117 b4deps
="$(git rev-parse --verify --quiet "refs
/heads
/$_update_name:.topdeps
" --)" && [ -n "$b4deps" ] ||
1118 b4deps
="$(git hash-object -t blob -w --stdin </dev/null)"
1121 ! attempt_index_merge
$no_auto $brmmode -m "$msg" "refs/heads/$_update_name" "$merge_with^0" &&
1123 # Home, sweet home...
1124 # (We want to always switch back, in case we were
1125 # on the base from failed previous merge.)
1126 git checkout
-q $iowopt "$_update_name" || die
"git checkout failed" &&
1127 git_merge
$brmmode --name "$_update_name" --name "${plusextra}$topbases/$_update_name" -m "$msg" "$merge_with^0"
1131 merging_topfiles
="${brmmode:+1}"
1134 info
"Please commit merge resolution and call \`$tgdisplayac update --continue\`"
1135 info
"(use \`$tgdisplayac status\` to see more options)"
1139 # Fourth, auto create locally any newly depended on branches we got from the remote
1142 if [ -n "$b4deps" ] &&
1143 l8rdeps
="$(git rev-parse --verify --quiet "refs
/heads
/$_update_name:.topdeps
" --)" &&
1144 [ -n "$l8rdeps" ] && [ "$b4deps" != "$l8rdeps" ]
1147 while read -r newdep
; do
1148 if [ -n "$newdep" ]; then
1149 if auto_create_local_remote
"$newdep"; then
1152 if ref_exists
"refs/heads/$newdep"; then
1153 # maybe the line just moved around
1154 [ -n "$_olddeps" ] && [ -f "$_olddeps" ] ||
{
1155 _olddeps
="$(get_temp b4deps)" &&
1156 git cat-file blob
"$b4deps" >"$_olddeps"
1158 if awk -v "newdep=$newdep" '$0 == newdep {exit 1}' <"$_olddeps"; then
1159 # nope, it's a new head already existing locally
1163 # helpfully check to see if there's such a remote branch
1165 ! ref_exists
"refs/remotes/$base_remote/$newdep" || _rntgb
=1
1166 # maybe a blocking local orphan base too
1168 if [ -n "$_rntgb" ] &&
1169 ref_exists
"refs/remotes/$base_remote/${topbases#heads/}/$newdep" &&
1170 ref_exists
"refs/$topbases/$newdep"
1174 # spew the flexibly adjustable warning
1175 warn
"-------------------------------------"
1176 warn
"MISSING DEPENDENCY MERGED FROM REMOTE"
1177 warn
"-------------------------------------"
1178 warn
"Local Branch: $_update_name"
1179 warn
" Remote Name: $base_remote"
1180 warn
" Dependency: $newdep"
1181 if [ -n "$_blocked" ]; then
1182 warn
"Blocking Ref: refs/$topbases/$newdep"
1183 elif [ -n "$_rntgb" ]; then
1184 warn
"Existing Ref: refs/remotes/$base_remote/$newdep"
1187 if [ -n "$_blocked" ]; then
1188 warn
"There is no local branch by that name, but"
1189 warn
"there IS a remote TopGit branch available by"
1190 warn
"that name, but creation of a local version has"
1191 warn
"been blocked by existence of the ref shown above."
1192 elif [ -n "$_rntgb" ]; then
1193 warn
"There is no local branch or remote TopGit"
1194 warn
"branch available by that name, but there is an"
1195 warn
"existing non-TopGit remote branch ref shown above."
1196 warn
"Non-TopGit branches are not set up automatically"
1197 warn
"by TopGit and must be maintained manually."
1199 warn
"There is no local branch or remote branch"
1200 warn
"(TopGit or otherwise) available by that name."
1202 warn
"-------------------------------------"
1207 $(git diff --ignore-space-at-eol "$b4deps" "$l8rdeps" -- | diff_added_lines)
1216 update_branch_internal
"$@" || _ubicode
=$?
1217 while [ "$_maxdeploop" -gt 0 ] && [ "$_ubicode" = "75" ]; do
1218 _maxdeploop
="$(( $maxdeploop - 1 ))"
1219 info
"Updating $1 again with newly added dependencies..."
1221 update_branch_internal
"$@" || _ubicode
=$?
1226 # We are "read-only" and cacheable until the first change
1230 do_non_annihilated_branches_patterns
() {
1231 while read -r _pat
&& [ -n "$_pat" ]; do
1234 non_annihilated_branches
"$@"
1237 do_non_annihilated_branches
() {
1238 if [ -z "$pattern" ]; then
1239 non_annihilated_branches
1241 do_non_annihilated_branches_patterns
<<-EOT
1242 $(sed 'y/ /\n/' <<-LIST
1250 if [ -n "$all" ] && [ -z "$restored" ]; then
1252 while read name
&& [ -n "$name" ]; do
1253 case " $names " in *" $name "*);;*)
1254 names
="${names:+$names }$name"
1257 $(do_non_annihilated_branches)
1261 for name
in $names; do
1262 case " $processed " in *" $name "*) continue; esac
1263 [ -z "$all" ] && case "$names" in *" "*) ! :; esac || info
"Proccessing $name..."
1264 update_branch
"$name" ||
exit
1265 processed
="${processed:+$processed }$name"
1268 [ -z "$all" ] && case "$names" in *" "*) ! :; esac ||
1269 info
"Returning to ${current#refs/heads/}..."
1270 checkout_symref_full
"$current"
1271 ! [ -f "$git_dir/TGMERGE_MSG" ] ||
[ -e "$git_dir/MERGE_MSG" ] ||
1272 mv -f "$git_dir/TGMERGE_MSG" "$git_dir/MERGE_MSG" ||
: