README_DOCS.rst: mention --paginate and -P options
[topgit/pro.git] / tg-update.sh
blob911d27880087630d6de835c61b5e6cca432faf8e
1 #!/bin/sh
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
5 # All rights reserved.
6 # GPLv2
8 names= # Branch(es) to update
9 name1= # first name
10 name2= # second name
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
17 quiet= # be quieter
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)
28 stash=1
31 ## Parse options
33 USAGE="\
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"
38 usage()
40 if [ "${1:-0}" != 0 ]; then
41 printf '%s\n' "$USAGE" >&2
42 else
43 printf '%s\n' "$USAGE"
45 exit ${1:-0}
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
64 _hvrl=1
65 command -v readlink >/dev/null 2>&1 || _hvrl=
66 git status --porcelain -z | tr '\n\000' '\177\n' | awk '!/^\?\? ./||/\177/{next}{print}' >"$_utfl" || :
67 saveIFS="$IFS"
68 IFS=" "
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" '
71 BEGIN {x=""}
72 function exitnow(c) {x=c;exit x}
73 END {if(x!="")exit x}
74 function init(e,l) {
75 while ((e=(getline l<u))>0) {
76 if(l!~/^\?\? ./||l~/\177/)continue
77 f[substr(l,4)]=1
79 close(u)
81 BEGIN {if(u=="")exitnow(2);init()}
82 NF<5||/\177/{next}
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]+$/||
86 $5!="A")next
87 t=$0;sub(/^[^\t]*\t/,"",t)
88 if(t!=""&&f[t])print $4" "$2" "t
90 ' |
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" ]
96 then
97 case "$_utmod" in
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;;
104 *) ! :;;
105 esac &&
106 case "$_utmod" in
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)";;
110 *) ! :;;
111 esac &&
112 [ "$_flhsh" = "$_uthsh" ] || continue
113 rm -f "$_tplvl/$_utnam" >/dev/null 2>&1 || :
115 done || :
116 IFS="$saveIFS"
117 return 0
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"
123 do_base_mode()
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
128 grammar="dependency"
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"
138 exit 0
140 alreadymerged=
141 ! contained_by "$newrev" "refs/heads/$tgbranch" || alreadymerged=1
142 if [ -z "$basefrc" ] && ! contained_by "$baserev" "$newrev"; then
143 die "Refusing non-fast-forward update of base without --force"
146 # check that we can checkout the branch
148 [ -n "$alreadymerged" ] || git read-tree -n -u -m "refs/heads/$tgbranch" ||
149 die "git checkout \"$branch\" would fail"
151 # and make sure everything's clean and we know who we are
153 [ -n "$alreadymerged" ] || ensure_clean_tree
154 ensure_ident_available
156 # always auto stash even if it's just to the anonymous stash TG_STASH
158 stashmsg="tgupdate: autostash before --base $tgbranch update"
159 if [ -n "$stash" ]; then
160 tg tag -q -q -m "$stashmsg" --stash "$tgbranch" &&
161 stashhash="$(git rev-parse --quiet --verify refs/tgstash --)" &&
162 [ -n "$stashhash" ] &&
163 [ "$(git cat-file -t "$stashhash" 2>/dev/null)" = "tag" ] ||
164 die "requested --stash failed"
165 else
166 tg tag --anonymous "$tgbranch" &&
167 stashhash="$(git rev-parse --quiet --verify TG_STASH --)" &&
168 [ -n "$stashhash" ] &&
169 [ "$(git cat-file -t "$stashhash" 2>/dev/null)" = "tag" ] ||
170 die "anonymous --stash failed"
173 # proceed with the update
175 git update-ref -m "tg update --base $tgbranch $2" "refs/$topbases/$tgbranch" "$newrev" "$baserev" ||
176 die "Unable to update base ref"
177 if [ -n "$alreadymerged" ]; then
178 [ -n "$quiet" ] || echo "Already contained in branch (base updated)"
179 exit 0
181 git checkout -q $iowopt "$tgbranch" || die "git checkout failed"
182 msgopt=
183 # options validation guarantees that at most one of basemsg or basefile is set
184 [ -z "$basemsg" ] || msgopt='-m "$basemsg"'
185 if [ -n "$basefile" ]; then
186 # git merge does not accept a -F <msgfile> option so we have to fake it
187 basefilemsg="$(cat "$basefile")" || die "could not read file '$basefile'"
188 msgopt='-m "$basefilemsg"'
190 editopt=
191 if [ -n "$editmode" ]; then
192 if [ "$editmode" = "0" ]; then
193 editopt="--no-edit"
194 else
195 editopt="--edit"
198 if [ -z "$basemsg$basefile" ]; then
199 [ -n "$editopt" ] || editopt="--edit"
200 basemsg="tg update --base $tgbranch $2"
201 msgopt='-m "$basemsg"'
202 else
203 [ -n "$editopt" ] || editopt="--no-edit"
205 ncopt=
206 [ -z "$basenc" ] || ncopt="--no-commit"
207 eval git merge --no-ff --no-log --no-stat $auhopt $ncopt $editopt "$msgopt" "refs/$topbases/$tgbranch" -- || exit
208 ec=0
209 if [ -z "$basenc" ]; then
210 clear_matching_untracked "$current"
211 checkout_symref_full "$current" || ec=$?
212 tmpdir_cleanup || :
213 if [ "${ec:-0}" != "0" ]; then
214 info "Unable to switch to ${current#refs/heads/}"
216 git rev-parse -q --verify HEAD >/dev/null 2>&1 &&
217 ! git symbolic-ref -q HEAD >/dev/null 2>&1
218 then
219 info "HEAD is currently detached"
220 info "Use 'git checkout ${current#refs/heads/}' to reattach"
224 exit ${ec:-0}
227 state_dir="$git_dir/tg-update"
228 mergeours=
229 mergetheirs=
230 mergeresult=
231 stashhash=
232 next_no_auto=
233 merging_topfiles=
235 is_active() {
236 [ -d "$state_dir" ] || return 1
237 [ -s "$state_dir/fullcmd" ] || return 1
238 [ -f "$state_dir/remote" ] || return 1
239 [ -f "$state_dir/skipms" ] || return 1
240 [ -f "$state_dir/all" ] || return 1
241 [ -s "$state_dir/current" ] || return 1
242 [ -s "$state_dir/stashhash" ] || return 1
243 [ -s "$state_dir/name" ] || return 1
244 [ -s "$state_dir/names" ] || return 1
245 [ -f "$state_dir/processed" ] || return 1
246 [ -f "$state_dir/no_auto" ] || return 1
247 [ -f "$state_dir/setautoupdate" ] || return 1
248 [ -f "$state_dir/merging_topfiles" ] || return 1
249 [ -f "$state_dir/mergeours" ] || return 1
250 [ -f "$state_dir/mergeours" ] || return 1
251 if [ -s "$state_dir/mergeours" ]; then
252 [ -s "$state_dir/mergetheirs" ] || return 1
253 else
254 ! [ -s "$state_dir/mergetheirs" ] || return 1
258 restore_state() {
259 is_active || die "programmer error"
260 IFS= read -r fullcmd <"$state_dir/fullcmd" && [ -n "$fullcmd" ]
261 IFS= read -r base_remote <"$state_dir/remote" || :
262 IFS= read -r skipms <"$state_dir/skipms" || :
263 IFS= read -r all <"$state_dir/all" || :
264 IFS= read -r current <"$state_dir/current" && [ -n "$current" ]
265 IFS= read -r stashhash <"$state_dir/stashhash" && [ -n "$stashhash" ]
266 IFS= read -r name <"$state_dir/name" && [ -n "$name" ]
267 IFS= read -r names <"$state_dir/names" && [ -n "$names" ]
268 IFS= read -r processed <"$state_dir/processed" || :
269 IFS= read -r next_no_auto <"$state_dir/no_auto" || :
270 IFS= read -r setautoupdate <"$state_dir/setautoupdate" || :
271 # merging_topfiles is for outside info but not to be restored
272 IFS= read -r mergeours <"$state_dir/mergeours" || :
273 IFS= read -r mergetheirs <"$state_dir/mergetheirs" || :
274 if [ -n "$mergeours" ] && [ -n "$mergetheirs" ]; then
275 headhash="$(git rev-parse --quiet --verify HEAD --)" || :
276 if [ -n "$headhash" ]; then
277 parents="$(git --no-pager log -n 1 --format='format:%P' "$headhash" -- 2>/dev/null)" || :
278 if [ "$parents" = "$mergeours $mergetheirs" ]; then
279 mergeresult="$headhash"
282 if [ -z "$mergeresult" ]; then
283 mergeours=
284 mergetheirs=
287 restored=1
290 clear_state() {
291 ! [ -e "$state_dir" ] || rm -rf "$state_dir" >/dev/null 2>&1 || :
294 restarted=
295 isactive=
296 ! is_active || isactive=1
297 isactiveopt=
298 if [ -z "$isactive" ] && [ $# -eq 1 ]; then
299 case "$1" in --abort|--stop|--continue|--skip) isactiveopt=1; esac
301 if [ -n "$isactive" ] || [ -n "$isactiveopt" ]; then
302 [ $# -eq 1 ] && [ x"$1" != x"--status" ] || { do_status; exit 0; }
303 ensure_work_tree
304 if [ -z "$isactive" ]; then
305 clear_state
306 info "No update is currently active"
307 exit 0
309 case "$1" in
310 --abort)
311 current=
312 stashhash=
313 IFS= read -r current <"$state_dir/current" || :
314 IFS= read -r stashhash <"$state_dir/stashhash" || :
315 clear_state
316 if [ -n "$stashhash" ]; then
317 tg revert -f -q -q --no-stash "$stashhash" >/dev/null 2>&1 || :
319 if [ -n "$current" ]; then
320 info "Ok, update aborted, returning to ${current#refs/heads/}"
321 checkout_symref_full -f "$current"
322 else
323 info "Ok, update aborted. Now, you just need to"
324 info "switch back to some sane branch using \`git$gitcdopt checkout\`."
326 ! [ -f "$git_dir/TGMERGE_MSG" ] || [ -e "$git_dir/MERGE_MSG" ] ||
327 mv -f "$git_dir/TGMERGE_MSG" "$git_dir/MERGE_MSG" || :
328 exit 0
330 --stop)
331 clear_state
332 info "Ok, update stopped. Now, you just need to"
333 info "switch back to some sane branch using \`git$gitcdopt checkout\`."
334 ! [ -f "$git_dir/TGMERGE_MSG" ] || [ -e "$git_dir/MERGE_MSG" ] ||
335 mv -f "$git_dir/TGMERGE_MSG" "$git_dir/MERGE_MSG" || :
336 exit 0
338 --continue|--skip)
339 restore_state
340 if [ "$1" = "--skip" ]; then
341 info "Ok, I will try to continue without updating this branch."
342 git reset --hard -q
343 case " $processed " in *" $name "*);;*)
344 processed="${processed:+$processed }$name"
345 esac
347 # assume user fixed it
348 # we could be left on a detached HEAD if we were resolving
349 # a conflict while merging a base in, fix it with a checkout
350 v_strip_ref bname "$name"
351 git checkout -q $iowopt "$bname"
354 do_status
355 exit 1
356 esac
358 clear_state
360 if [ -z "$restored" ]; then
361 setautoupdate=1
362 [ "$(git config --get --bool topgit.setAutoUpdate 2>/dev/null)" != "false" ] ||
363 setautoupdate=
365 while [ -n "$1" ]; do
366 arg="$1"; shift
367 case "$arg" in
369 usage;;
370 -a|--all)
371 [ -z "$names$pattern" ] || usage 1
372 all=1;;
373 --skip-missing)
374 skipms=1;;
375 --stash)
376 stash=1;;
377 --no-stash)
378 stash=;;
379 --auto|--auto-update|--set-auto|--set-auto-update)
380 setautoupdate=1;;
381 --no-auto|--no-auto-update|--no-set-auto|--no-set-auto-update)
382 setautoupdate=;;
383 --quiet|-q)
384 quiet=1;;
385 --base)
386 basemode=1;;
387 --edit|-e)
388 editmode=1;;
389 --no-edit)
390 editmode=0;;
391 --no-commit)
392 basenc=1;;
393 --force|-f)
394 basefrc=1;;
395 -m|--message)
396 [ $# -gt 0 ] && [ -n "$1" ] || die "option $arg requires an argument"
397 basemsg="$1"
398 shift;;
399 -m?*)
400 basemsg="${1#-m}";;
401 --message=*)
402 basemsg="${1#--message=}";;
403 -F|--file)
404 [ $# -gt 0 ] && [ -n "$1" ] || die "option $arg requires an argument"
405 basefile="$1"
406 shift;;
407 -F?*)
408 basefile="${1#-F}";;
409 --file=*)
410 basefile="${1#--file=}"
411 [ -n "$basefile" ] || die "option --file= requires an argument"
413 -?*)
414 usage 1;;
416 break;;
420 if [ -z "$all" ]; then
421 namecnt=$(( $namecnt + 1 ))
422 [ "$namecnt" != "1" ] || name1="$arg"
423 [ "$namecnt" != "2" ] || name2="$arg"
424 names="${names:+$names }$arg"
425 else
426 v_strip_ref arg "$arg"
427 pattern="${pattern:+$pattern }refs/$topbases/$arg"
430 esac
431 done
432 ensure_work_tree
433 while [ $# -gt 0 ]; do
434 if [ -z "$all" ]; then
435 namecnt=$(( $namecnt + 1 ))
436 [ "$namecnt" != "1" ] || name1="$1"
437 [ "$namecnt" != "2" ] || name2="$1"
438 names="${names:+$names }$*"
439 else
440 v_strip_ref arg "$1"
441 pattern="${pattern:+$pattern }refs/$topbases/$arg"
443 shift
444 done
445 [ -n "$basemode" ] || [ -z "$editmode$basemsg$basefile$basenc$basefrc" ] || usage 1
446 [ -z "$basemode" ] || [ -z "$all$skipms" ] || usage 1
447 [ -z "$basemode" ] || [ -z "$basemsg" ] || [ -z "$basefile" ] || usage 1
448 [ -z "$basemode" ] || [ "$namecnt" -eq 2 ] || usage 1
450 current="$(git symbolic-ref -q HEAD)" || :
451 if [ -n "$current" ]; then
452 [ -n "$(git rev-parse --verify --quiet HEAD --)" ] ||
453 die "cannot return to unborn branch; switch to another branch"
454 else
455 current="$(git rev-parse --verify --quiet HEAD)" ||
456 die "cannot return to invalid HEAD; switch to another branch"
459 [ -z "$basemode" ] || do_base_mode "$name1" "$name2"
461 origpattern="$pattern"
462 [ -z "$pattern" ] && pattern="refs/$topbases"
464 processed=
465 [ -n "$all$names" ] || names="HEAD"
466 if [ -z "$all" ]; then
467 clean_names() {
468 names=
469 while [ $# -gt 0 ]; do
470 v_verify_topgit_branch name "$1"
471 case " $names " in *" $name "*);;*)
472 names="${names:+$names }$name"
473 esac
474 shift
475 done
477 clean_names $names
478 if [ "$namecnt" -eq 1 ]; then
479 case "$fullcmd" in *" @"|*" HEAD")
480 namecnt=0
481 fullcmd="${fullcmd% *}"
482 esac
484 [ "$namecnt" -ne 0 ] || fullcmd="$fullcmd $names"
486 ensure_clean_tree
489 save_state() {
490 mkdir -p "$state_dir"
491 printf '%s\n' "$fullcmd" >"$state_dir/fullcmd"
492 printf '%s\n' "$base_remote" >"$state_dir/remote"
493 printf '%s\n' "$skipms" >"$state_dir/skipms"
494 printf '%s\n' "$all" >"$state_dir/all"
495 printf '%s\n' "$current" >"$state_dir/current"
496 printf '%s\n' "$stashhash" >"$state_dir/stashhash"
497 printf '%s\n' "$name" >"$state_dir/name"
498 printf '%s\n' "$names" >"$state_dir/names"
499 printf '%s\n' "$processed" >"$state_dir/processed"
500 printf '%s\n' "$no_auto" >"$state_dir/no_auto"
501 printf '%s\n' "$setautoupdate" >"$state_dir/setautoupdate"
502 # this one is an external flag and needs to be zero length for false
503 printf '%s' "$merging_topfiles" >"$state_dir/merging_topfiles"
504 printf '%s\n' "$1" >"$state_dir/mergeours"
505 printf '%s\n' "$2" >"$state_dir/mergetheirs"
508 stash_now_if_requested() {
509 [ -z "$TG_RECURSIVE" ] || return 0
510 [ -z "$stashhash" ] || return 0
511 ensure_ident_available
512 msg="tgupdate: autostash before update"
513 if [ -n "$all" ]; then
514 msg="$msg --all${origpattern:+ $origpattern}"
515 else
516 msg="$msg $names"
518 set -- $names
519 if [ -n "$stash" ]; then
520 tg tag -q -q -m "$msg" --stash "$@" &&
521 stashhash="$(git rev-parse --quiet --verify refs/tgstash --)" &&
522 [ -n "$stashhash" ] &&
523 [ "$(git cat-file -t "$stashhash" 2>/dev/null)" = "tag" ] ||
524 die "requested --stash failed"
525 else
526 tg tag --anonymous "$@" &&
527 stashhash="$(git rev-parse --quiet --verify TG_STASH --)" &&
528 [ -n "$stashhash" ] &&
529 [ "$(git cat-file -t "$stashhash" 2>/dev/null)" = "tag" ] ||
530 die "anonymous --stash failed"
532 [ -z "$next_no_auto" ] || no_auto="$next_no_auto"
533 next_no_auto=
536 recursive_update() {
537 _ret=0
538 on_base=
540 if [ -n "$TG_RECURSIVE" ]; then
541 TG_RECURSIVE="==> [$1]${TG_RECURSIVE#==>}"
542 else
543 TG_RECURSIVE="==> [$1]$lf"
545 update_branch "$1"
546 ) || _ret=$?
547 [ $_ret -eq 3 ] && exit 3
548 return $_ret
551 on_base=
552 do_base_switch() {
553 [ -n "$1" ] || return 0
555 [ "$1" != "$on_base" ] ||
556 [ "$(git symbolic-ref -q HEAD)" != "refs/$topbases/$1" ]
557 then
558 switch_to_base "$1"
559 on_base="$1"
563 update_branch_internal() {
564 # We are cacheable until the first change
565 become_cacheable
567 _update_name="$1"
568 ## First, take care of our base
570 _depcheck="$(get_temp tg-depcheck)"
571 missing_deps=
572 needs_update "$_update_name" >"$_depcheck" || :
573 if [ -n "$missing_deps" ]; then
574 msg="Some dependencies are missing: $missing_deps"
575 if [ -n "$skipms" ]; then
576 info "$msg; skipping"
577 elif [ -z "$all" ]; then
578 die "$msg"
579 else
580 info "$msg; skipping branch $_update_name"
581 return 0
584 # allow automatic simple merges by default until a failure occurs
585 no_auto=
586 if [ -s "$_depcheck" ]; then
587 # (1) last word is $_update_name, remove it
588 # (2) keep only immediate dependencies of a chain adding a leading '+'
589 # (3) one-level deep dependencies get a '-' prefix instead
590 <"$_depcheck" sed \
591 -e 's/ [^ ]* *$//; # (1)' \
592 -e 's/.* \([^ ]*\)$/+\1/; # (2)' \
593 -e 's/^\([^+]\)/-\1/; # (3)' |
594 # now each line is +branch or -branch (+ == recurse)
595 >"$_depcheck.ideps" \
596 uniq -s 1 # fold branch lines; + always comes before - and thus wins within uniq
598 stash_now_if_requested
600 while read -r depline; do
601 dep="${depline#?}"
602 action="${depline%$dep}"
604 # We do not distinguish between dependencies out-of-date
605 # and base/remote out-of-date cases for $dep here,
606 # but thanks to needs_update returning : or :refs/remotes/...
607 # for the latter, we do correctly recurse here
608 # in both cases.
610 if [ x"$action" = x+ ]; then
611 case " $missing_deps " in *" $dep "*)
612 info "Skipping recursing to missing dependency: $dep"
613 continue
614 esac
615 info "Recursing to $dep..."
616 recursive_update "$dep" || exit 3
618 done <"$_depcheck.ideps"
620 # Create a list of all the fully qualified ref names that need
621 # to be merged into $_update_name's base. This will be done
622 # as an octopus merge if there are no conflicts.
623 deplist=
624 deplines=
625 set --
626 while read -r dep; do
627 dep="${dep#?}"
628 case "$dep" in
630 d="${dep#?}"
631 set -- "$@" "$d"
632 case "$d" in
633 "refs/heads"/*)
634 d="${d#refs/heads/}"
635 deplist="${deplist:+$deplist }$d"
636 deplines="$deplines$d$lf"
639 d="${d#refs/}"
640 deplist="${deplist:+$deplist }$d"
641 deplines="$deplines$d$lf"
643 esac
646 set -- "$@" "refs/heads/$dep"
647 deplist="${deplist:+$deplist }$dep"
648 deplines="$deplines$dep$lf"
650 esac
651 done <"$_depcheck.ideps"
653 # Make sure we end up on the correct base branch
654 on_base=
655 if [ $# -ge 2 ]; then
656 info "Updating $_update_name base with deps: $deplist"
657 become_non_cacheable
658 msg="tgupdate: octopus merge $# deps into $_update_name base$lf$lf$deplines"
659 if attempt_index_merge --remove -m "$msg" "refs/$topbases/$_update_name" "$@"; then
660 set --
661 else
662 info "Octopus merge failed; falling back to multiple 3-way merges"
663 no_auto="--no-auto"
667 for fulldep in "$@"; do
668 # This will be either a proper topic branch
669 # or a remote base. (branch_needs_update() is called
670 # only on the _dependencies_, not our branch itself!)
672 case "$fulldep" in
673 "refs/heads"/*)
674 dep="${fulldep#refs/heads/}";;
675 "refs"/*)
676 dep="${fulldep#refs/}";;
678 dep="$fulldep";; # this should be a programmer error
679 esac
681 info "Updating $_update_name base with $dep changes..."
682 become_non_cacheable
683 msg="tgupdate: merge $dep into $_update_name base"
685 ! attempt_index_merge $no_auto --remove -m "$msg" "refs/$topbases/$_update_name" "$fulldep^0" &&
687 # We need to switch to the base branch
688 # ...but only if we aren't there yet (from failed previous merge)
689 do_base_switch "$_update_name" || die "do_base_switch failed" &&
690 git_merge --remove --name "$_update_name base" --name "$dep" -m "$msg" "$fulldep^0"
692 then
693 rm "$_depcheck"
694 save_state
695 unset TG_RECURSIVE
696 info "Please commit merge resolution and call \`$tgdisplayac update --continue\`"
697 info "(use \`$tgdisplayac status\` to see more options)"
698 exit 3
700 done
701 else
702 info "The base is up-to-date."
706 ## Second, update our head with the remote branch
708 plusextra=
709 merge_with="refs/$topbases/$_update_name"
710 brmmode=
711 if has_remote "$_update_name"; then
712 _rname="refs/remotes/$base_remote/$_update_name"
713 if branch_contains "refs/heads/$_update_name" "$_rname"; then
714 info "The $_update_name head is up-to-date wrt. its remote branch."
715 else
716 stash_now_if_requested
717 info "Reconciling $_update_name base with remote branch updates..."
718 become_non_cacheable
719 msg="tgupdate: merge ${_rname#refs/} onto $_update_name base"
720 checkours=
721 checktheirs=
722 got_merge_with=
723 brmmode="--merge"
724 if [ -n "$mergeresult" ]; then
725 checkours="$(git rev-parse --verify --quiet "refs/$topbases/$_update_name^0" --)" || :
726 checktheirs="$(git rev-parse --verify --quiet "$_rname^0" --)" || :
727 if [ "$mergeours" = "$checkours" ] && [ "$mergetheirs" = "$checktheirs" ]; then
728 got_merge_with="$mergeresult"
732 [ -z "$got_merge_with" ] &&
733 ! v_attempt_index_merge $no_auto --theirs "merge_with" -m "$msg" "refs/$topbases/$_update_name" "$_rname^0" &&
735 # *DETACH* our HEAD now!
736 no_auto="--no-auto"
737 git checkout -q --detach $iowopt "refs/$topbases/$_update_name" || die "git checkout failed" &&
738 git_merge --theirs --name "$_update_name base content" --name "${_rname#refs/}" -m "$msg" "$_rname^0" &&
739 merge_with="$(git rev-parse --verify HEAD --)"
741 then
742 save_state \
743 "$(git rev-parse --verify --quiet "refs/$topbases/$_update_name^0" --)" \
744 "$(git rev-parse --verify --quiet "$_rname^0" --)"
745 unset TG_RECURSIVE
746 info "Please commit merge resolution and call \`$tgdisplayac update --continue\`"
747 info "(use \`$tgdisplayac status\` to see more options)"
748 exit 3
750 # Go back but remember we want to merge with this, not base
751 [ -z "$got_merge_with" ] || merge_with="$got_merge_with"
752 plusextra="${_rname#refs/} + "
757 ## Third, update our head with the base
759 if branch_contains "refs/heads/$_update_name" "$merge_with"; then
760 info "The $_update_name head is up-to-date wrt. the base."
761 return 0
763 stash_now_if_requested
764 info "Updating $_update_name against ${plusextra}new base..."
765 become_non_cacheable
766 msg="tgupdate: merge ${plusextra}$_update_name base into $_update_name"
767 b4deps=
768 if [ -n "$brmmode" ] && [ "$base_remote" ]; then
769 b4deps="$(git rev-parse --verify --quiet "refs/heads/$_update_name:.topdeps" --)" && [ -n "$b4deps" ] ||
770 b4deps="$(git hash-object -t blob -w --stdin </dev/null)"
773 ! attempt_index_merge $no_auto $brmmode -m "$msg" "refs/heads/$_update_name" "$merge_with^0" &&
775 # Home, sweet home...
776 # (We want to always switch back, in case we were
777 # on the base from failed previous merge.)
778 git checkout -q $iowopt "$_update_name" || die "git checkout failed" &&
779 git_merge $brmmode --name "$_update_name" --name "${plusextra}$topbases/$_update_name" -m "$msg" "$merge_with^0"
781 then
782 no_auto=
783 merging_topfiles="${brmmode:+1}"
784 save_state
785 unset TG_RECURSIVE
786 info "Please commit merge resolution and call \`$tgdisplayac update --continue\`"
787 info "(use \`$tgdisplayac status\` to see more options)"
788 exit 3
791 # Fourth, auto create locally any newly depended on branches we got from the remote
793 _result=0
794 if [ -n "$b4deps" ] &&
795 l8rdeps="$(git rev-parse --verify --quiet "refs/heads/$_update_name:.topdeps" --)" &&
796 [ -n "$l8rdeps" ] && [ "$b4deps" != "$l8rdeps" ]
797 then
798 _olddeps=
799 while read -r newdep; do
800 if [ -n "$newdep" ]; then
801 if auto_create_local_remote "$newdep"; then
802 _result=75
803 else
804 if ref_exists "refs/heads/$newdep"; then
805 # maybe the line just moved around
806 [ -n "$_olddeps" ] && [ -f "$_olddeps" ] || {
807 _olddeps="$(get_temp b4deps)" &&
808 git cat-file blob "$b4deps" >"$_olddeps"
810 if awk -v "newdep=$newdep" '$0 == newdep {exit 1}' <"$_olddeps"; then
811 # nope, it's a new head already existing locally
812 _result=75
814 else
815 # helpfully check to see if there's such a remote branch
816 _rntgb=
817 ! ref_exists "refs/remotes/$base_remote/$newdep" || _rntgb=1
818 # maybe a blocking local orphan base too
819 _blocked=
820 if [ -n "$_rntgb" ] &&
821 ref_exists "refs/remotes/$base_remote/${topbases#heads/}/$newdep" &&
822 ref_exists "refs/$topbases/$newdep"
823 then
824 _blocked=1
826 # spew the flexibly adjustable warning
827 warn "-------------------------------------"
828 warn "MISSING DEPENDENCY MERGED FROM REMOTE"
829 warn "-------------------------------------"
830 warn "Local Branch: $_update_name"
831 warn " Remote Name: $base_remote"
832 warn " Dependency: $newdep"
833 if [ -n "$_blocked" ]; then
834 warn "Blocking Ref: refs/$topbases/$newdep"
835 elif [ -n "$_rntgb" ]; then
836 warn "Existing Ref: refs/remotes/$base_remote/$newdep"
838 warn ""
839 if [ -n "$_blocked" ]; then
840 warn "There is no local branch by that name, but"
841 warn "there IS a remote TopGit branch available by"
842 warn "that name, but creation of a local version has"
843 warn "been blocked by existence of the ref shown above."
844 elif [ -n "$_rntgb" ]; then
845 warn "There is no local branch or remote TopGit"
846 warn "branch available by that name, but there is an"
847 warn "existing non-TopGit remote branch ref shown above."
848 warn "Non-TopGit branches are not set up automatically"
849 warn "by TopGit and must be maintained manually."
850 else
851 warn "There is no local branch or remote branch"
852 warn "(TopGit or otherwise) available by that name."
854 warn "-------------------------------------"
858 done <<-EOT
859 $(git diff --ignore-space-at-eol "$b4deps" "$l8rdeps" -- | diff_added_lines)
862 return $_result
865 update_branch() {
866 _ubicode=0
867 _maxdeploop=3
868 update_branch_internal "$@" || _ubicode=$?
869 while [ "$_maxdeploop" -gt 0 ] && [ "$_ubicode" = "75" ]; do
870 _maxdeploop="$(( $maxdeploop - 1 ))"
871 info "Updating $1 again with newly added dependencies..."
872 _ubicode=0
873 update_branch_internal "$@" || _ubicode=$?
874 done
875 return $_ubicode
878 # We are "read-only" and cacheable until the first change
879 tg_read_only=1
880 v_create_ref_cache
882 do_non_annihilated_branches_patterns() {
883 while read -r _pat && [ -n "$_pat" ]; do
884 set -- "$@" "$_pat"
885 done
886 non_annihilated_branches "$@"
889 do_non_annihilated_branches() {
890 if [ -z "$pattern" ]; then
891 non_annihilated_branches
892 else
893 do_non_annihilated_branches_patterns <<-EOT
894 $(sed 'y/ /\n/' <<-LIST
895 $pattern
896 LIST
902 if [ -n "$all" ] && [ -z "$restored" ]; then
903 names=
904 while read name && [ -n "$name" ]; do
905 case " $names " in *" $name "*);;*)
906 names="${names:+$names }$name"
907 esac
908 done <<-EOT
909 $(do_non_annihilated_branches)
913 for name in $names; do
914 case " $processed " in *" $name "*) continue; esac
915 [ -z "$all" ] && case "$names" in *" "*) ! :; esac || info "Proccessing $name..."
916 update_branch "$name" || exit
917 processed="${processed:+$processed }$name"
918 done
920 [ -z "$all" ] && case "$names" in *" "*) ! :; esac ||
921 info "Returning to ${current#refs/heads/}..."
922 ec=0
923 clear_matching_untracked "$current"
924 checkout_symref_full "$current" || ec=$?
925 ! [ -f "$git_dir/TGMERGE_MSG" ] || [ -e "$git_dir/MERGE_MSG" ] ||
926 mv -f "$git_dir/TGMERGE_MSG" "$git_dir/MERGE_MSG" || :
927 tmpdir_cleanup || :
928 git gc --auto || :
929 if [ "${ec:-0}" != "0" ]; then
930 info "Unable to switch to ${current#refs/heads/}"
932 git rev-parse -q --verify HEAD >/dev/null 2>&1 &&
933 ! git symbolic-ref -q HEAD >/dev/null 2>&1
934 then
935 info "HEAD is currently detached"
936 info "Use 'git checkout ${current#refs/heads/}' to reattach"
939 exit ${ec:-0}