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,2019,2021
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 # Remove any currently untracked files that also appear in
49 # $1's tree AND have an identical blob hash. Never fail, but
50 # instead simply ignore any operations with problems.
51 clear_matching_untracked
() {
52 _cmutree
="$(git rev-parse --verify "$1^
{tree
}" 2>/dev/null)" &&
53 [ -n "$_cmutree" ] ||
return 0
54 _idxtree
="$(git write-tree 2>/dev/null)" &&
55 [ -n "$_idxtree" ] ||
return 0
56 v_get_show_toplevel _tplvl
57 # Save the list of untracked files in a temp file, then feed the
58 # "A" file lines from diff-tree index to the tree to an awk script
59 # along with the list of untracked files and let it spit out
60 # a list of blob matches which includes the file mode of the match
61 # and then remove any untracked files with a matching hash and mode.
62 # Due to limitations of awk, files with '\n' in their names are skipped.
63 _utfl
="$(get_temp untracked)" ||
return 0
65 command -v readlink
>/dev
/null
2>&1 || _hvrl
=
66 git status
--porcelain -z |
tr '\n\000' '\177\n' |
awk '!/^\?\? ./||/\177/{next}{print}' >"$_utfl" ||
:
69 git diff-tree
--raw --ignore-submodules=all
--no-renames -r -z --diff-filter=A
"$_idxtree" "$_cmutree" |
70 tr '\n\000' '\177\n' |
paste - - |
awk -v u
="$_utfl" '
72 function exitnow(c) {x=c;exit x}
75 while ((e=(getline l<u))>0) {
76 if(l!~/^\?\? ./||l~/\177/)continue
81 BEGIN {if(u=="")exitnow(2);init()}
84 if($1!=":000000"||($2!="100644"&&$2!="100755"&&$2!="120000")||
85 $3!~/^0+$/||$4!~/^[0-9a-f][0-9a-f][0-9a-f][0-9a-f]+$/||
87 t=$0;sub(/^[^\t]*\t/,"",t)
88 if(t!=""&&f[t])print $4" "$2" "t
91 while read -r _uthsh _utmod _utnam
&& [ -n "$_utnam" ] && [ -n "$_uthsh" ]; do
92 case "$_utmod" in "100644"|
"100755"|
"120000");;*) continue; esac
94 [ -L "$_tplvl/$_utnam" ] ||
95 [ -f "$_tplvl/$_utnam" ]
98 "100644") test ! -L "$_tplvl/$_utnam" &&
99 test ! -x "$_tplvl/$_utnam" ||
continue;;
100 "100755") test ! -L "$_tplvl/$_utnam" &&
101 test -x "$_tplvl/$_utnam" ||
continue;;
102 "120000") test -n "$_hvrl" &&
103 test -L "$_tplvl/$_utnam" ||
continue;;
107 "100644"|
"100755") _flhsh
="$(git hash-object -t blob -- "$_tplvl/$_utnam")";;
108 "120000") _flhsh
="$(readlink -n "$_tplvl/$_utnam" 2>/dev/null |
109 git hash-object -t blob --stdin)";;
112 [ "$_flhsh" = "$_uthsh" ] ||
continue
113 rm -f "$_tplvl/$_utnam" >/dev
/null
2>&1 ||
:
120 # --base mode comes here with $1 set to <base-branch> and $2 set to <ref>
121 # and all options already parsed and validated into above-listed flags
122 # this function should exit after returning to "$current"
125 v_verify_topgit_branch tgbranch
"$1"
126 depcnt
="$(git cat-file blob "refs
/heads
/$tgbranch:.topdeps
" 2>/dev/null | awk 'END {print NR}')"
127 if [ $depcnt -gt 0 ]; then
129 [ $depcnt -eq 1 ] || grammar
="dependencies"
130 die
"'$tgbranch' is not a TopGit [BASE] branch (it has $depcnt $grammar)"
132 newrev
="$(git rev-parse --verify "$2^
0" --)" && [ -n "$newrev" ] ||
133 die
"not a valid commit-ish: $2"
134 v_ref_exists_rev baserev
"refs/$topbases/$tgbranch" && [ -n "$baserev" ] ||
135 die
"unable to get current base commit for branch '$tgbranch'"
136 if [ "$baserev" = "$newrev" ]; then
137 [ -n "$quiet" ] ||
echo "No change"
140 if [ -z "$basefrc" ] && ! contained_by
"$baserev" "$newrev"; then
141 die
"Refusing non-fast-forward update of base without --force"
144 # make sure the new base commit does not have any .topdeps or .topmsg files
146 newrevt
="$(git rev-parse --verify "$newrev^
{tree
}" --)" && [ -n "$newrevt" ] || die
"$2 disappeared"
147 v_pretty_tree newptree
"$newrev" -r || die
"v_pretty_tree ... $2 -r (via git mktree) failed"
148 if [ "$newrevt" != "$newptree" ]; then
149 ensure_ident_available
150 bmsg
="tg create $tgbranch base"
151 newrev
="$(git commit-tree -p "$newrev" -m "$bmsg" "$newptree")" || die
"git commit-tree failed"
154 # check that we can checkout the branch
157 ! contained_by
"$newrev" "refs/heads/$tgbranch" || alreadymerged
=1
158 [ -n "$alreadymerged" ] || git read-tree
-n -u -m "refs/heads/$tgbranch" ||
159 die
"git checkout \"$branch\" would fail"
161 # and make sure everything's clean and we know who we are
163 [ -n "$alreadymerged" ] || ensure_clean_tree
164 ensure_ident_available
166 # collect the message from the user unless "$alreadymerged" or --no-edit
168 if [ -z "$alreadymerged" ]; then
170 [ -z "$basenc" ] || ncopt
="--no-commit"
172 # options validation guarantees that at most one of basemsg or basefile is set
173 [ -z "$basemsg" ] || msgopt
='-m "$basemsg"'
174 if [ -n "$basefile" ]; then
175 # git merge does not accept a -F <msgfile> option so we have to fake it
176 basefilemsg
="$(cat "$basefile")" || die
"could not read file '$basefile'"
177 msgopt
='-m "$basefilemsg"'
180 if [ -n "$editmode" ]; then
181 if [ "$editmode" = "0" ]; then
187 if [ -z "$basemsg$basefile" ]; then
188 [ -n "$editopt" ] || editopt
="--edit"
189 basemsg
="tg update --base $tgbranch $2"
190 msgopt
='-m "$basemsg"'
192 [ -n "$editopt" ] || editopt
="--no-edit"
194 if [ "$editopt" = "--edit" ] ||
[ "$ncopt" = "--no-commit" ]; then
195 if [ -n "$basefile" ]; then
196 eval 'printf "%s\n"' "${msgopt#-m }" >"$git_dir/MERGE_MSG"
198 eval 'printf "%s\n"' "${msgopt#-m }" | git stripspace
>"$git_dir/MERGE_MSG"
200 if [ "$ncopt" != "--no-commit" ]; then
201 cat <<-\EOT >>"$git_dir/MERGE_MSG"
203 # Please enter a commit message to explain why this merge is necessary,
204 # especially if it merges an updated upstream into a topic branch.
206 # Lines starting with '#' will be ignored, and an empty message aborts
209 run_editor "$git_dir/MERGE_MSG" ||
210 die "there was a problem with the editor '$tg_editor'"
211 git -c core.commentchar='#' stripspace -s <"$git_dir/MERGE_MSG" >"$git_dir/MERGE_MSG"+
212 mv -f "$git_dir/MERGE_MSG"+ "$git_dir/MERGE_MSG"
213 if [ ! -s "$git_dir/MERGE_MSG" ]; then
214 # Restore the (unannotated) MERGE_MSG even though we are aborting
215 if [ -n "$basefile" ]; then
216 eval 'printf "%s\n"' "${msgopt#-m }" >"$git_dir/MERGE_MSG"
218 eval 'printf "%s\n"' "${msgopt#-m }" |
219 git stripspace >"$git_dir/MERGE_MSG"
221 die "Aborting update due to empty commit message."
223 mergemsg="$(cat "$git_dir/MERGE_MSG")" || die "could not read file '$git_dir/MERGE_MSG'"
224 msgopt='-m "$mergemsg"'
229 # always auto stash even if it's just to the anonymous stash TG_STASH
231 stashmsg="tgupdate: autostash before --base $tgbranch update"
232 if [ -n "$stash" ]; then
233 tg tag -q -q -m "$stashmsg" --stash "$tgbranch" &&
234 stashhash="$(git rev-parse --quiet --verify refs/tgstash --)" &&
235 [ -n "$stashhash" ] &&
236 [ "$(git cat-file -t "$stashhash" 2>/dev/null)" = "tag" ] ||
237 die "requested --stash failed"
239 tg tag --anonymous "$tgbranch" &&
240 stashhash="$(git rev-parse --quiet --verify TG_STASH --)" &&
241 [ -n "$stashhash" ] &&
242 [ "$(git cat-file -t "$stashhash" 2>/dev/null)" = "tag" ] ||
243 die "anonymous --stash failed"
246 # proceed with the update
248 git update-ref -m "tg update --base $tgbranch $2" "refs/$topbases/$tgbranch" "$newrev" "$baserev" ||
249 die "Unable to update base ref"
250 if [ -n "$alreadymerged" ]; then
251 [ -n "$quiet" ] || echo "Already contained in branch (base updated)"
254 git checkout -q $iowopt "$tgbranch" || die "git checkout failed"
255 eval git_topmerge $ncopt "$msgopt" "refs/$topbases/$tgbranch" || exit
257 if [ -z "$basenc" ]; then
258 clear_matching_untracked "$current"
259 checkout_symref_full "$current" || ec=$?
261 if [ "${ec:-0}" != "0" ]; then
262 info "Unable to switch to ${current#refs/heads/}"
264 git rev-parse -q --verify HEAD >/dev/null 2>&1 &&
265 ! git symbolic-ref -q HEAD >/dev/null 2>&1
267 info "HEAD is currently detached"
268 info "Use 'git checkout ${current#refs/heads/}' to reattach"
275 state_dir="$git_dir/tg-state"
284 [ -d "$state_dir" ] || return 1
285 [ -s "$state_dir/state" ] || return 1
286 read -r _astate <"$state_dir/state" &&
287 [ "$_astate" = "update" ] || return 1
288 [ -s "$state_dir/fullcmd" ] || return 1
289 [ -f "$state_dir/remote" ] || return 1
290 [ -f "$state_dir/skipms" ] || return 1
291 [ -f "$state_dir/all" ] || return 1
292 [ -s "$state_dir/current" ] || return 1
293 [ -s "$state_dir/stashhash" ] || return 1
294 [ -s "$state_dir/name" ] || return 1
295 [ -s "$state_dir/names" ] || return 1
296 [ -f "$state_dir/processed" ] || return 1
297 [ -f "$state_dir/no_auto" ] || return 1
298 [ -f "$state_dir/setautoupdate" ] || return 1
299 [ -f "$state_dir/merging_topfiles" ] || return 1
300 [ -f "$state_dir/mergeours" ] || return 1
301 [ -f "$state_dir/mergeours" ] || return 1
302 if [ -s "$state_dir/mergeours" ]; then
303 [ -s "$state_dir/mergetheirs" ] || return 1
305 ! [ -s "$state_dir/mergetheirs" ] || return 1
310 is_active || die "programmer error"
311 IFS= read -r fullcmd <"$state_dir/fullcmd" && [ -n "$fullcmd" ]
312 IFS= read -r base_remote <"$state_dir/remote" || :
313 IFS= read -r skipms <"$state_dir/skipms" || :
314 IFS= read -r all <"$state_dir/all" || :
315 IFS= read -r current <"$state_dir/current" && [ -n "$current" ]
316 IFS= read -r stashhash <"$state_dir/stashhash" && [ -n "$stashhash" ]
317 IFS= read -r name <"$state_dir/name" && [ -n "$name" ]
318 IFS= read -r names <"$state_dir/names" && [ -n "$names" ]
319 IFS= read -r processed <"$state_dir/processed" || :
320 IFS= read -r next_no_auto <"$state_dir/no_auto" || :
321 IFS= read -r setautoupdate <"$state_dir/setautoupdate" || :
322 # merging_topfiles is for outside info but not to be restored
323 IFS= read -r mergeours <"$state_dir/mergeours" || :
324 IFS= read -r mergetheirs <"$state_dir/mergetheirs" || :
325 if [ -n "$mergeours" ] && [ -n "$mergetheirs" ]; then
326 headhash="$(git rev-parse --quiet --verify HEAD --)" || :
327 if [ -n "$headhash" ]; then
328 parents="$(git --no-pager log -n 1 --format='format:%P' "$headhash" -- 2>/dev/null)" || :
329 if [ "$parents" = "$mergeours $mergetheirs" ]; then
330 mergeresult="$headhash"
333 if [ -z "$mergeresult" ]; then
342 ! [ -e "$state_dir" ] || rm -rf "$state_dir" >/dev/null 2>&1 || :
347 ! is_active || isactive=1
349 if [ -z "$isactive" ] && [ $# -eq 1 ]; then
350 case "$1" in --abort|--stop|--continue|--skip) isactiveopt=1; esac
352 if [ -n "$isactive" ] || [ -n "$isactiveopt" ]; then
353 [ $# -eq 1 ] && [ x"$1" != x"--status" ] || { do_status; exit 0; }
355 if [ -z "$isactive" ]; then
357 info "No update is currently active"
364 IFS= read -r current <"$state_dir/current" || :
365 IFS= read -r stashhash <"$state_dir/stashhash" || :
367 if [ -n "$stashhash" ]; then
368 tg revert -f -q -q --no-stash "$stashhash" >/dev/null 2>&1 || :
370 if [ -n "$current" ]; then
371 info "Ok, update aborted, returning to ${current#refs/heads/}"
372 checkout_symref_full -f "$current"
374 info "Ok, update aborted. Now, you just need to"
375 info "switch back to some sane branch using \`git$gitcdopt checkout\`."
377 ! [ -f "$git_dir/TGMERGE_MSG" ] || [ -e "$git_dir/MERGE_MSG" ] ||
378 mv -f "$git_dir/TGMERGE_MSG" "$git_dir/MERGE_MSG" || :
383 info "Ok, update stopped. Now, you just need to"
384 info "switch back to some sane branch using \`git$gitcdopt checkout\`."
385 ! [ -f "$git_dir/TGMERGE_MSG" ] || [ -e "$git_dir/MERGE_MSG" ] ||
386 mv -f "$git_dir/TGMERGE_MSG" "$git_dir/MERGE_MSG" || :
391 if [ "$1" = "--skip" ]; then
392 info "Ok, I will try to continue without updating this branch."
394 case " $processed " in *" $name "*);;*)
395 processed="${processed:+$processed }$name"
398 # assume user fixed it
399 # we could be left on a detached HEAD if we were resolving
400 # a conflict while merging a base in, fix it with a checkout
401 v_strip_ref bname "$name"
402 git checkout -q $iowopt "$bname"
411 if [ -z "$restored" ]; then
413 [ "$(git config --get --bool topgit.setAutoUpdate 2>/dev/null)" != "false" ] ||
416 while [ -n "$1" ]; do
422 [ -z "$names$pattern" ] || usage 1
430 --auto|--auto-update|--set-auto|--set-auto-update)
432 --no-auto|--no-auto-update|--no-set-auto|--no-set-auto-update)
447 [ $# -gt 0 ] && [ -n "$1" ] || die "option $arg requires an argument"
453 basemsg="${1#--message=}";;
455 [ $# -gt 0 ] && [ -n "$1" ] || die "option $arg requires an argument"
461 basefile="${1#--file=}"
462 [ -n "$basefile" ] || die "option --file= requires an argument"
471 if [ -z "$all" ]; then
472 namecnt
=$
(( $namecnt + 1 ))
473 [ "$namecnt" != "1" ] || name1
="$arg"
474 [ "$namecnt" != "2" ] || name2
="$arg"
475 names
="${names:+$names }$arg"
477 v_strip_ref arg
"$arg"
478 pattern
="${pattern:+$pattern }refs/$topbases/$arg"
484 while [ $# -gt 0 ]; do
485 if [ -z "$all" ]; then
486 namecnt
=$
(( $namecnt + 1 ))
487 [ "$namecnt" != "1" ] || name1
="$1"
488 [ "$namecnt" != "2" ] || name2
="$1"
489 names
="${names:+$names }$*"
492 pattern
="${pattern:+$pattern }refs/$topbases/$arg"
496 [ -n "$basemode" ] ||
[ -z "$editmode$basemsg$basefile$basenc$basefrc" ] || usage
1
497 [ -z "$basemode" ] ||
[ -z "$all$skipms" ] || usage
1
498 [ -z "$basemode" ] ||
[ -z "$basemsg" ] ||
[ -z "$basefile" ] || usage
1
499 [ -z "$basemode" ] ||
[ "$namecnt" -eq 2 ] || usage
1
501 current
="$(git symbolic-ref -q HEAD)" ||
:
502 if [ -n "$current" ]; then
503 [ -n "$(git rev-parse --verify --quiet HEAD --)" ] ||
504 die
"cannot return to unborn branch; switch to another branch"
506 current
="$(git rev-parse --verify --quiet HEAD)" ||
507 die
"cannot return to invalid HEAD; switch to another branch"
510 [ -z "$basemode" ] || do_base_mode
"$name1" "$name2"
512 origpattern
="$pattern"
513 [ -z "$pattern" ] && pattern
="refs/$topbases"
516 [ -n "$all$names" ] || names
="HEAD"
517 if [ -z "$all" ]; then
520 while [ $# -gt 0 ]; do
521 v_verify_topgit_branch name
"$1"
522 case " $names " in *" $name "*);;*)
523 names
="${names:+$names }$name"
529 if [ "$namecnt" -eq 1 ]; then
530 case "$fullcmd" in *" @"|
*" HEAD")
532 fullcmd
="${fullcmd% *}"
535 [ "$namecnt" -ne 0 ] || fullcmd
="$fullcmd $names"
541 mkdir
-p "$state_dir"
543 printf '%s\n' "$fullcmd" >"$state_dir/fullcmd"
544 printf '%s\n' "$base_remote" >"$state_dir/remote"
545 printf '%s\n' "$skipms" >"$state_dir/skipms"
546 printf '%s\n' "$all" >"$state_dir/all"
547 printf '%s\n' "$current" >"$state_dir/current"
548 printf '%s\n' "$stashhash" >"$state_dir/stashhash"
549 printf '%s\n' "$name" >"$state_dir/name"
550 printf '%s\n' "$names" >"$state_dir/names"
551 printf '%s\n' "$processed" >"$state_dir/processed"
552 printf '%s\n' "$no_auto" >"$state_dir/no_auto"
553 printf '%s\n' "$setautoupdate" >"$state_dir/setautoupdate"
554 # this one is an external flag and needs to be zero length for false
555 printf '%s' "$merging_topfiles" >"$state_dir/merging_topfiles"
556 printf '%s\n' "$1" >"$state_dir/mergeours"
557 printf '%s\n' "$2" >"$state_dir/mergetheirs"
558 # state not valid until $state_dir/state contains "update"
559 echo update
>"$state_dir/state"
562 stash_now_if_requested
() {
563 [ -z "$TG_RECURSIVE" ] ||
return 0
564 [ -z "$stashhash" ] ||
return 0
565 ensure_ident_available
566 msg
="tgupdate: autostash before update"
567 if [ -n "$all" ]; then
568 msg
="$msg --all${origpattern:+ $origpattern}"
573 if [ -n "$stash" ]; then
574 tg tag
-q -q -m "$msg" --stash "$@" &&
575 stashhash
="$(git rev-parse --quiet --verify refs/tgstash --)" &&
576 [ -n "$stashhash" ] &&
577 [ "$(git cat-file -t "$stashhash" 2>/dev/null)" = "tag" ] ||
578 die
"requested --stash failed"
580 tg tag
--anonymous "$@" &&
581 stashhash
="$(git rev-parse --quiet --verify TG_STASH --)" &&
582 [ -n "$stashhash" ] &&
583 [ "$(git cat-file -t "$stashhash" 2>/dev/null)" = "tag" ] ||
584 die
"anonymous --stash failed"
586 [ -z "$next_no_auto" ] || no_auto
="$next_no_auto"
594 if [ -n "$TG_RECURSIVE" ]; then
595 TG_RECURSIVE
="==> [$1]${TG_RECURSIVE#==>}"
597 TG_RECURSIVE
="==> [$1]$lf"
601 [ $_ret -eq 3 ] && exit 3
607 [ -n "$1" ] ||
return 0
609 [ "$1" != "$on_base" ] ||
610 [ "$(git symbolic-ref -q HEAD)" != "refs/$topbases/$1" ]
617 # $1 varname to hold space-separated+surrounded result(s) (can be empty)
618 # $2 is "our" full ref name
619 # $3 is "their" full ref name
620 # return status is 0 if any removed deps found, non-0 otherwise in which
621 # case $1 will always be set to the empty string
622 v_get_removed_deps
() {
624 [ -z "$1" ] ||
eval "$1="
626 v_attempt_index_merge_file _newdepsblob \
627 "refs/heads/$_update_name" "$_rname" .topdeps
629 rm -f "$tg_tmp_dir/tgblob.$$"
630 git cat-file blob
"refs/heads/$_update_name:.topdeps" >"$tg_tmp_dir/tgblob.$$" 2>/dev
/null ||
:
631 __gonedeps
="$(git cat-file blob "$_newdepsblob" 2>/dev/null |
632 awk -v td="$tg_tmp_dir/tgblob.$$
" '
634 function exitnow(c) {x=c;exit x}
635 END {if(x!="")exit x}
636 function init(e,l,a) {
637 while ((e=(getline l<td))>0) {
638 if (split(l,a," ") == 1) t[a[1]] = 1
642 BEGIN {if(td=="")exitnow(2);init()}
655 ')" && __goneec
=0 || __goneec
=$?
656 [ -z "$1" ] ||
[ "$__goneec" != "0" ] ||
eval "$1=\"\$__gonedeps\""
661 update_branch_internal
() {
662 # We are cacheable until the first change
669 if has_remote
"$_update_name"; then
670 _rname
="refs/remotes/$base_remote/$_update_name"
671 if branch_contains
"refs/heads/$_update_name" "$_rname"; then
674 # Check for removed dependencies
675 v_get_removed_deps _removedeps
"refs/heads/$_update_name" "$_rname" ||
:
680 ## First, take care of our base
682 _depcheck
="$(get_temp tg-depcheck)"
684 needs_update
"$_update_name" >"$_depcheck" ||
:
685 if [ -n "$missing_deps" ]; then
686 msg
="Some dependencies are missing: $missing_deps"
687 if [ -n "$skipms" ]; then
688 info
"$msg; skipping"
689 elif [ -z "$all" ]; then
692 info
"$msg; skipping branch $_update_name"
696 # allow automatic simple merges by default until a failure occurs
698 if [ -s "$_depcheck" ]; then
699 # (1) last word is $_update_name, remove it
700 # (2) keep only immediate dependencies of a chain adding a leading '+'
701 # (3) one-level deep dependencies get a '-' prefix instead
703 -e 's/ [^ ]* *$//; # (1)' \
704 -e 's/.* \([^ ]*\)$/+\1/; # (2)' \
705 -e 's/^\([^+]\)/-\1/; # (3)' |
706 # now each line is +branch or -branch (+ == recurse)
707 >"$_depcheck.ideps" \
708 uniq -s 1 # fold branch lines; + always comes before - and thus wins within uniq
710 stash_now_if_requested
712 while read -r depline
; do
714 action
="${depline%$dep}"
716 # We do not distinguish between dependencies out-of-date
717 # and base/remote out-of-date cases for $dep here,
718 # but thanks to needs_update returning : or :refs/remotes/...
719 # for the latter, we do correctly recurse here
722 if [ x
"$action" = x
+ ]; then
723 case " $missing_deps " in *" $dep "*)
724 info
"Skipping recursing to missing dependency: $dep"
727 case " $_removedeps " in *" $dep "*)
728 info
"Skipping recursing to remote-removed dependency: $dep"
731 info
"Recursing to $dep..."
732 recursive_update
"$dep" ||
exit 3
734 done <"$_depcheck.ideps"
736 # Create a list of all the fully qualified ref names that need
737 # to be merged into $_update_name's base. This will be done
738 # as an octopus merge if there are no conflicts.
742 while read -r dep
; do
744 case " $missing_deps " in *" $dep "*)
745 # message already shown above
748 case " $_removedeps " in *" $dep "*)
749 # message already shown above
759 deplist
="${deplist:+$deplist }$d"
760 deplines
="$deplines$d$lf"
764 deplist
="${deplist:+$deplist }$d"
765 deplines
="$deplines$d$lf"
770 set -- "$@" "refs/heads/$dep"
771 deplist
="${deplist:+$deplist }$dep"
772 deplines
="$deplines$dep$lf"
775 done <"$_depcheck.ideps"
777 # Make sure we end up on the correct base branch
779 if [ $# -ge 2 ]; then
780 info
"Updating $_update_name base with deps: $deplist"
782 msg
="tgupdate: octopus merge $# deps into $_update_name base$lf$lf$deplines"
783 if attempt_index_merge
--remove -m "$msg" "refs/$topbases/$_update_name" "$@"; then
786 info
"Octopus merge failed; falling back to multiple 3-way merges"
791 for fulldep
in "$@"; do
792 # This will be either a proper topic branch
793 # or a remote base. (branch_needs_update() is called
794 # only on the _dependencies_, not our branch itself!)
798 dep
="${fulldep#refs/heads/}";;
800 dep
="${fulldep#refs/}";;
802 dep
="$fulldep";; # this should be a programmer error
805 info
"Updating $_update_name base with $dep changes..."
807 msg
="tgupdate: merge $dep into $_update_name base"
809 ! attempt_index_merge
$no_auto --remove -m "$msg" "refs/$topbases/$_update_name" "$fulldep^0" &&
811 # We need to switch to the base branch
812 # ...but only if we aren't there yet (from failed previous merge)
813 do_base_switch
"$_update_name" || die
"do_base_switch failed" &&
814 git_merge
--remove --name "$_update_name base" --name "$dep" -m "$msg" "$fulldep^0"
820 info
"Please commit merge resolution and call \`$tgdisplayac update --continue\`"
821 info
"(use \`$tgdisplayac status\` to see more options)"
826 info
"The base is up-to-date."
830 ## Second, update our head with the remote branch
833 merge_with
="refs/$topbases/$_update_name"
835 if [ -n "$_rname" ]; then
836 if [ -n "$_bcrname" ]; then
837 info
"The $_update_name head is up-to-date wrt. its remote branch."
839 stash_now_if_requested
840 info
"Reconciling $_update_name base with remote branch updates..."
842 msg
="tgupdate: merge ${_rname#refs/} onto $_update_name base"
847 if [ -n "$mergeresult" ]; then
848 checkours
="$(git rev-parse --verify --quiet "refs
/$topbases/$_update_name^
0" --)" ||
:
849 checktheirs
="$(git rev-parse --verify --quiet "$_rname^
0" --)" ||
:
850 if [ "$mergeours" = "$checkours" ] && [ "$mergetheirs" = "$checktheirs" ]; then
851 got_merge_with
="$mergeresult"
855 [ -z "$got_merge_with" ] &&
856 ! v_attempt_index_merge
$no_auto --theirs "merge_with" -m "$msg" "refs/$topbases/$_update_name" "$_rname^0" &&
858 # *DETACH* our HEAD now!
860 git checkout
-q --detach $iowopt "refs/$topbases/$_update_name" || die
"git checkout failed" &&
861 git_merge
--theirs --name "$_update_name base content" --name "${_rname#refs/}" -m "$msg" "$_rname^0" &&
862 merge_with
="$(git rev-parse --verify HEAD --)"
866 "$(git rev-parse --verify --quiet "refs
/$topbases/$_update_name^
0" --)" \
867 "$(git rev-parse --verify --quiet "$_rname^
0" --)"
869 info
"Please commit merge resolution and call \`$tgdisplayac update --continue\`"
870 info
"(use \`$tgdisplayac status\` to see more options)"
873 # Go back but remember we want to merge with this, not base
874 [ -z "$got_merge_with" ] || merge_with
="$got_merge_with"
875 plusextra
="${_rname#refs/} + "
880 ## Third, update our head with the base
882 if branch_contains
"refs/heads/$_update_name" "$merge_with"; then
883 info
"The $_update_name head is up-to-date wrt. the base."
886 stash_now_if_requested
887 info
"Updating $_update_name against ${plusextra}new base..."
889 msg
="tgupdate: merge ${plusextra}$_update_name base into $_update_name"
891 if [ -n "$brmmode" ] && [ "$base_remote" ]; then
892 b4deps
="$(git rev-parse --verify --quiet "refs
/heads
/$_update_name:.topdeps
" --)" && [ -n "$b4deps" ] ||
893 b4deps
="$(git hash-object -t blob -w --stdin </dev/null)"
896 ! attempt_index_merge
$no_auto $brmmode -m "$msg" "refs/heads/$_update_name" "$merge_with^0" &&
898 # Home, sweet home...
899 # (We want to always switch back, in case we were
900 # on the base from failed previous merge.)
901 git checkout
-q $iowopt "$_update_name" || die
"git checkout failed" &&
902 git_merge
$brmmode --name "$_update_name" --name "${plusextra}$topbases/$_update_name" -m "$msg" "$merge_with^0"
906 merging_topfiles
="${brmmode:+1}"
909 info
"Please commit merge resolution and call \`$tgdisplayac update --continue\`"
910 info
"(use \`$tgdisplayac status\` to see more options)"
914 # Fourth, auto create locally any newly depended on branches we got from the remote
917 if [ -n "$b4deps" ] &&
918 l8rdeps
="$(git rev-parse --verify --quiet "refs
/heads
/$_update_name:.topdeps
" --)" &&
919 [ -n "$l8rdeps" ] && [ "$b4deps" != "$l8rdeps" ]
922 while read -r newdep
; do
923 if [ -n "$newdep" ]; then
924 if auto_create_local_remote
"$newdep"; then
927 if ref_exists
"refs/heads/$newdep"; then
928 # maybe the line just moved around
929 [ -n "$_olddeps" ] && [ -f "$_olddeps" ] ||
{
930 _olddeps
="$(get_temp b4deps)" &&
931 git cat-file blob
"$b4deps" >"$_olddeps"
933 if awk -v "newdep=$newdep" '$0 == newdep {exit 1}' <"$_olddeps"; then
934 # nope, it's a new head already existing locally
938 # helpfully check to see if there's such a remote branch
940 ! ref_exists
"refs/remotes/$base_remote/$newdep" || _rntgb
=1
941 # maybe a blocking local orphan base too
943 if [ -n "$_rntgb" ] &&
944 ref_exists
"refs/remotes/$base_remote/${topbases#heads/}/$newdep" &&
945 ref_exists
"refs/$topbases/$newdep"
949 # spew the flexibly adjustable warning
950 warn
"-------------------------------------"
951 warn
"MISSING DEPENDENCY MERGED FROM REMOTE"
952 warn
"-------------------------------------"
953 warn
"Local Branch: $_update_name"
954 warn
" Remote Name: $base_remote"
955 warn
" Dependency: $newdep"
956 if [ -n "$_blocked" ]; then
957 warn
"Blocking Ref: refs/$topbases/$newdep"
958 elif [ -n "$_rntgb" ]; then
959 warn
"Existing Ref: refs/remotes/$base_remote/$newdep"
962 if [ -n "$_blocked" ]; then
963 warn
"There is no local branch by that name, but"
964 warn
"there IS a remote TopGit branch available by"
965 warn
"that name, but creation of a local version has"
966 warn
"been blocked by existence of the ref shown above."
967 elif [ -n "$_rntgb" ]; then
968 warn
"There is no local branch or remote TopGit"
969 warn
"branch available by that name, but there is an"
970 warn
"existing non-TopGit remote branch ref shown above."
971 warn
"Non-TopGit branches are not set up automatically"
972 warn
"by TopGit and must be maintained manually."
974 warn
"There is no local branch or remote branch"
975 warn
"(TopGit or otherwise) available by that name."
977 warn
"-------------------------------------"
982 $(git diff --ignore-space-at-eol "$b4deps" "$l8rdeps" -- | diff_added_lines)
991 update_branch_internal
"$@" || _ubicode
=$?
992 while [ "$_maxdeploop" -gt 0 ] && [ "$_ubicode" = "75" ]; do
993 _maxdeploop
="$(( $maxdeploop - 1 ))"
994 info
"Updating $1 again with newly added dependencies..."
996 update_branch_internal
"$@" || _ubicode
=$?
1001 # We are "read-only" and cacheable until the first change
1005 do_non_annihilated_branches_patterns
() {
1006 while read -r _pat
&& [ -n "$_pat" ]; do
1009 non_annihilated_branches
"$@"
1012 do_non_annihilated_branches
() {
1013 if [ -z "$pattern" ]; then
1014 non_annihilated_branches
1016 do_non_annihilated_branches_patterns
<<-EOT
1017 $(sed 'y/ /\n/' <<-LIST
1025 if [ -n "$all" ] && [ -z "$restored" ]; then
1027 while read name
&& [ -n "$name" ]; do
1028 case " $names " in *" $name "*);;*)
1029 names
="${names:+$names }$name"
1032 $(do_non_annihilated_branches)
1036 for name
in $names; do
1037 case " $processed " in *" $name "*) continue; esac
1038 [ -z "$all" ] && case "$names" in *" "*) ! :; esac || info
"Proccessing $name..."
1039 update_branch
"$name" ||
exit
1040 processed
="${processed:+$processed }$name"
1043 [ -z "$all" ] && case "$names" in *" "*) ! :; esac ||
1044 info
"Returning to ${current#refs/heads/}..."
1046 clear_matching_untracked
"$current"
1047 checkout_symref_full
"$current" || ec
=$?
1048 ! [ -f "$git_dir/TGMERGE_MSG" ] ||
[ -e "$git_dir/MERGE_MSG" ] ||
1049 mv -f "$git_dir/TGMERGE_MSG" "$git_dir/MERGE_MSG" ||
:
1052 if [ "${ec:-0}" != "0" ]; then
1053 info
"Unable to switch to ${current#refs/heads/}"
1055 git rev-parse
-q --verify HEAD
>/dev
/null
2>&1 &&
1056 ! git symbolic-ref
-q HEAD
>/dev
/null
2>&1
1058 info
"HEAD is currently detached"
1059 info
"Use 'git checkout ${current#refs/heads/}' to reattach"