tg.sh: make "tg help st" work
[topgit/pro.git] / tg-update.sh
blob2b14a7e865394648adcacfa969e3911970cee753
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
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 # --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"
51 do_base_mode()
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
56 grammar="dependency"
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"
66 exit 0
68 alreadymerged=
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"
93 else
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)"
107 exit 0
109 git checkout -q $iowopt "$tgbranch" || die "git checkout failed"
110 msgopt=
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"'
118 editopt=
119 if [ -n "$editmode" ]; then
120 if [ "$editmode" = "0" ]; then
121 editopt="--no-edit"
122 else
123 editopt="--edit"
126 if [ -z "$basemsg$basefile" ]; then
127 [ -n "$editopt" ] || editopt="--edit"
128 basemsg="tg update --base $tgbranch $2"
129 msgopt='-m "$basemsg"'
130 else
131 [ -n "$editopt" ] || editopt="--no-edit"
133 ncopt=
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"
137 exit
140 state_dir="$git_dir/tg-update"
141 mergeours=
142 mergetheirs=
143 mergeresult=
144 stashhash=
145 next_no_auto=
146 merging_topfiles=
148 is_active() {
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
166 else
167 ! [ -s "$state_dir/mergetheirs" ] || return 1
171 restore_state() {
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
196 mergeours=
197 mergetheirs=
200 restored=1
203 clear_state() {
204 ! [ -e "$state_dir" ] || rm -rf "$state_dir" >/dev/null 2>&1 || :
207 restarted=
208 isactive=
209 ! is_active || isactive=1
210 isactiveopt=
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; }
216 ensure_work_tree
217 if [ -z "$isactive" ]; then
218 clear_state
219 info "No update is currently active"
220 exit 0
222 case "$1" in
223 --abort)
224 current=
225 stashhash=
226 IFS= read -r current <"$state_dir/current" || :
227 IFS= read -r stashhash <"$state_dir/stashhash" || :
228 clear_state
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"
235 else
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" || :
241 exit 0
243 --stop)
244 clear_state
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" || :
249 exit 0
251 --continue|--skip)
252 restore_state
253 if [ "$1" = "--skip" ]; then
254 info "Ok, I will try to continue without updating this branch."
255 git reset --hard -q
256 case " $processed " in *" $name "*);;*)
257 processed="${processed:+$processed }$name"
258 esac
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")"
266 do_status
267 exit 1
268 esac
270 clear_state
272 if [ -z "$restored" ]; then
273 setautoupdate=1
274 [ "$(git config --get --bool topgit.setAutoUpdate 2>/dev/null)" != "false" ] ||
275 setautoupdate=
277 while [ -n "$1" ]; do
278 arg="$1"; shift
279 case "$arg" in
281 usage;;
282 -a|--all)
283 [ -z "$names$pattern" ] || usage 1
284 all=1;;
285 --skip-missing)
286 skipms=1;;
287 --stash)
288 stash=1;;
289 --no-stash)
290 stash=;;
291 --auto|--auto-update|--set-auto|--set-auto-update)
292 setautoupdate=1;;
293 --no-auto|--no-auto-update|--no-set-auto|--no-set-auto-update)
294 setautoupdate=;;
295 --quiet|-q)
296 quiet=1;;
297 --base)
298 basemode=1;;
299 --edit|-e)
300 editmode=1;;
301 --no-edit)
302 editmode=0;;
303 --no-commit)
304 basenc=1;;
305 --force|-f)
306 basefrc=1;;
307 -m|--message)
308 [ $# -gt 0 ] && [ -n "$1" ] || die "option $arg requires an argument"
309 basemsg="$1"
310 shift;;
311 -m?*)
312 basemsg="${1#-m}";;
313 --message=*)
314 basemsg="${1#--message=}";;
315 -F|--file)
316 [ $# -gt 0 ] && [ -n "$1" ] || die "option $arg requires an argument"
317 basefile="$1"
318 shift;;
319 -F?*)
320 basefile="${1#-F}";;
321 --file=*)
322 basefile="${1#--file=}"
323 [ -n "$basefile" ] || die "option --file= requires an argument"
325 -?*)
326 usage 1;;
328 break;;
332 if [ -z "$all" ]; then
333 namecnt=$(( $namecnt + 1 ))
334 [ "$namecnt" != "1" ] || name1="$arg"
335 [ "$namecnt" != "2" ] || name2="$arg"
336 names="${names:+$names }$arg"
337 else
338 pattern="${pattern:+$pattern }refs/$topbases/$(strip_ref "$arg")"
341 esac
342 done
343 ensure_work_tree
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 }$*"
350 else
351 pattern="${pattern:+$pattern }refs/$topbases/$(strip_ref "$1")"
353 shift
354 done
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"
364 else
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"
374 processed=
375 [ -n "$all$names" ] || names="HEAD"
376 if [ -z "$all" ]; then
377 clean_names() {
378 names=
379 while [ $# -gt 0 ]; do
380 name="$(verify_topgit_branch "$1")"
381 case " $names " in *" $name "*);;*)
382 names="${names:+$names }$name"
383 esac
384 shift
385 done
387 clean_names $names
389 ensure_clean_tree
392 save_state() {
393 mkdir -p "$state_dir"
394 printf '%s\n' "$fullcmd" >"$state_dir/fullcmd"
395 printf '%s\n' "$base_remote" >"$state_dir/remote"
396 printf '%s\n' "$skipms" >"$state_dir/skipms"
397 printf '%s\n' "$all" >"$state_dir/all"
398 printf '%s\n' "$current" >"$state_dir/current"
399 printf '%s\n' "$stashhash" >"$state_dir/stashhash"
400 printf '%s\n' "$name" >"$state_dir/name"
401 printf '%s\n' "$names" >"$state_dir/names"
402 printf '%s\n' "$processed" >"$state_dir/processed"
403 printf '%s\n' "$no_auto" >"$state_dir/no_auto"
404 printf '%s\n' "$setautoupdate" >"$state_dir/setautoupdate"
405 # this one is an external flag and needs to be zero length for false
406 printf '%s' "$merging_topfiles" >"$state_dir/merging_topfiles"
407 printf '%s\n' "$1" >"$state_dir/mergeours"
408 printf '%s\n' "$2" >"$state_dir/mergetheirs"
411 stash_now_if_requested() {
412 [ -z "$TG_RECURSIVE" ] || return 0
413 [ -z "$stashhash" ] || return 0
414 ensure_ident_available
415 msg="tgupdate: autostash before update"
416 if [ -n "$all" ]; then
417 msg="$msg --all${origpattern:+ $origpattern}"
418 else
419 msg="$msg $names"
421 set -- $names
422 if [ -n "$stash" ]; then
423 tg tag -q -q -m "$msg" --stash "$@" &&
424 stashhash="$(git rev-parse --quiet --verify refs/tgstash --)" &&
425 [ -n "$stashhash" ] &&
426 [ "$(git cat-file -t "$stashhash" -- 2>/dev/null)" = "tag" ] ||
427 die "requested --stash failed"
428 else
429 tg tag --anonymous "$@" &&
430 stashhash="$(git rev-parse --quiet --verify TG_STASH --)" &&
431 [ -n "$stashhash" ] &&
432 [ "$(git cat-file -t "$stashhash" -- 2>/dev/null)" = "tag" ] ||
433 die "anonymous --stash failed"
435 [ -z "$next_no_auto" ] || no_auto="$next_no_auto"
436 next_no_auto=
439 recursive_update() {
440 _ret=0
441 on_base=
443 if [ -n "$TG_RECURSIVE" ]; then
444 TG_RECURSIVE="==> [$1]${TG_RECURSIVE#==>}"
445 else
446 TG_RECURSIVE="==> [$1]$lf"
448 update_branch "$1"
449 ) || _ret=$?
450 [ $_ret -eq 3 ] && exit 3
451 return $_ret
454 # If HEAD is a symref to "$1" detach it at its current value
455 detach_symref_head_on_branch() {
456 _hsr="$(git symbolic-ref -q HEAD --)" && [ -n "$_hsr" ] || return 0
457 _hrv="$(git rev-parse --quiet --verify HEAD --)" && [ -n "$_hrv" ] ||
458 die "cannot detach_symref_head_on_branch from unborn branch $_hsr"
459 git update-ref --no-deref -m "detaching HEAD from $_hsr to safely update it" HEAD "$_hrv"
462 # git_topmerge will need this even on success and since it might otherwise
463 # be called many times do it just the once here and now
464 repotoplvl="$(git rev-parse --show-toplevel)" && [ -n "$repotoplvl" ] && [ -d "$repotoplvl" ] ||
465 die "git rev-parse --show-toplevel failed"
467 # Run an in-tree recursive merge but make sure we get the desired version of
468 # any .topdeps and .topmsg files. The $auhopt and --no-stat options are
469 # always implicitly in effect. If successful, a new commit is performed on HEAD.
471 # The "git merge-recursive" tool (and others) must be run to get the desired
472 # result. And --no-ff is always implicitly in effect as well.
474 # NOTE: [optional] arguments MUST appear in the order shown
475 # [optional] '-v' varname => optional variable to return original HEAD hash in
476 # [optional] '--merge', '--theirs' or '--remove' to alter .topfile handling
477 # [optional] '--name' <name-for-ours [--name <name-for-theirs>]
478 # $1 => '-m' MUST be '-m'
479 # $2 => commit message
480 # $3 => commit-ish to merge as "theirs"
481 git_topmerge()
483 _ovar=
484 [ "$1" != "-v" ] || [ $# -lt 2 ] || [ -z "$2" ] || { _ovar="$2"; shift 2; }
485 _mmode=
486 case "$1" in --theirs|--remove|--merge) _mmode="${1#--}"; shift; esac
487 _nameours=
488 _nametheirs=
489 if [ "$1" = "--name" ] && [ $# -ge 2 ]; then
490 _nameours="$2"
491 shift 2
492 if [ "$1" = "--name" ] && [ $# -ge 2 ]; then
493 _nametheirs="$2"
494 shift 2
497 : "${_nameours:=HEAD}"
498 [ "$#" -eq 3 ] && [ "$1" = "-m" ] && [ -n "$2" ] && [ -n "$3" ] ||
499 die "programmer error: invalid arguments to git_topmerge: $*"
500 _ours="$(git rev-parse --verify HEAD^0)" || die "git rev-parse failed"
501 _theirs="$(git rev-parse --verify "$3^0")" || die "git rev-parse failed"
502 [ -z "$_ovar" ] || eval "$_ovar="'"$_ours"'
503 eval "GITHEAD_$_ours="'"$_nameours"' && eval export "GITHEAD_$_ours"
504 if [ -n "$_nametheirs" ]; then
505 eval "GITHEAD_$_theirs="'"$_nametheirs"' && eval export "GITHEAD_$_theirs"
507 _mdriver='touch %A'
508 if [ "$_mmode" = "merge" ]; then
509 TG_L1="$_nameours" && export TG_L1
510 TG_L2="merged common ancestors" && export TG_L2
511 TG_L3="${_nametheirs:-$3}" && export TG_L3
512 _mdriver='git merge-file -L "$TG_L1" -L "$TG_L2" -L "$TG_L3" --marker-size=%L %A %O %B'
514 _msg="$2"
515 _mt=
516 _mb="$(git merge-base --all "$_ours" "$_theirs")" && [ -n "$_mb" ] ||
517 { _mt=1; _mb="$(git hash-object -w -t tree --stdin < /dev/null)"; }
518 # any .topdeps or .topmsg output needs to be stripped from stdout
519 tmpstdout="$tg_tmp_dir/stdout.$$"
520 _ret=0
521 git -c "merge.ours.driver=$_mdriver" merge-recursive \
522 $_mb -- "$_ours" "$_theirs" >"$tmpstdout" || _ret=$?
523 # success or failure is not relevant until after fixing up the
524 # .topdeps and .topmsg files and running rerere unless _ret >= 126
525 [ $_ret -lt 126 ] || return $_ret
526 if [ "$_mmode" = "merge" ]; then
527 cat "$tmpstdout"
528 else
529 case "$_mmode" in
530 theirs) _source="$_theirs";;
531 remove) _source="";;
532 *) _source="$_ours";;
533 esac
534 _newinfo=
535 [ -z "$_source" ] ||
536 _newinfo="$(git cat-file --batch-check="%(objecttype) %(objectname)$tab%(rest)" <<-EOT |
537 $_source:.topdeps .topdeps
538 $_source:.topmsg .topmsg
540 sed -n 's/^blob /100644 /p'
542 [ -z "$_newinfo" ] || _newinfo="$lf$_newinfo"
543 git update-index --index-info <<-EOT ||
544 0 $nullsha$tab.topdeps
545 0 $nullsha$tab.topmsg$_newinfo
547 die "git update-index failed"
548 if [ "$_mmode" = "remove" ] &&
549 { [ -e "$repotoplvl/.topdeps" ] || [ -e "$repotoplvl/.topmsg" ]; }
550 then
551 rm -r -f "$repotoplvl/.topdeps" "$repotoplvl/.topmsg" >/dev/null 2>&1 || :
552 else
553 for zapbad in "$repotoplvl/.topdeps" "$repotoplvl/.topmsg"; do
554 if [ -e "$zapbad" ] && { [ -L "$zapbad" ] || [ ! -f "$zapbad" ]; }; then
555 rm -r -f "$zapbad"
557 done
558 (cd "$repotoplvl" && git checkout-index -q -f -u -- .topdeps .topmsg) ||
559 die "git checkout-index failed"
561 # dump output without any .topdeps or .topmsg messages
562 sed -e '/ \.topdeps/d' -e '/ \.topmsg/d' <"$tmpstdout"
564 # rerere will be a nop unless rerere.enabled is true, but might complete the merge!
565 eval git "${setautoupdate:+-c rerere.autoupdate=1}" rerere || :
566 git ls-files --unmerged --full-name --abbrev :/ >"$tmpstdout" 2>&1 ||
567 die "git ls-files failed"
568 if [ -s "$tmpstdout" ]; then
569 [ "$_ret" != "0" ] || _ret=1
570 else
571 _ret=0
573 if [ $_ret -ne 0 ]; then
574 # merge failed, spit out message, enter "merge" mode and return
576 printf '%s\n\n# Conflicts:\n' "$_msg"
577 sed -n "/$tab/s/^[^$tab]*/#/p" <"$tmpstdout" | sort -u
578 } >"$git_dir/MERGE_MSG"
579 git update-ref MERGE_HEAD "$_theirs" || :
580 echo 'Automatic merge failed; fix conflicts and then commit the result.'
581 rm -f "$tmpstdout"
582 return $_ret
584 # commit time at last!
585 thetree="$(git write-tree)" || die "git write-tree failed"
586 # avoid an extra "already up-to-date" commit (can't happen if _mt though)
587 origtree=
588 [ -n "$_mt" ] || origtree="$(git rev-parse --quiet --verify "$_ours^{tree}" --)" &&
589 [ -n "$origtree" ] || die "git rev-parse failed"
590 if [ "$origtree" != "$thetree" ] || ! contained_by "$_theirs" "$_ours"; then
591 thecommit="$(git commit-tree -p "$_ours" -p "$_theirs" -m "$_msg" "$thetree")" &&
592 [ -n "$thecommit" ] || die "git commit-tree failed"
593 git update-ref -m "$_msg" HEAD "$thecommit" || die "git update-ref failed"
595 # mention how the merge was made
596 echo "Merge made by the 'recursive' strategy."
597 rm -f "$tmpstdout"
598 return 0
601 # run git_topmerge with the passed in arguments (it always does --no-stat)
602 # then return the exit status of git_topmerge
603 # if the returned exit status is no error show a shortstat before
604 # returning assuming the merge was done into the previous HEAD but exclude
605 # .topdeps and .topmsg info from the stat unless doing a --merge
606 # if the first argument is --merge or --theirs or --remove handle .topmsg/.topdeps
607 # as follows:
608 # (default) .topmsg and .topdeps always keep ours
609 # --merge a normal merge takes place
610 # --theirs .topmsg and .topdeps always keep theirs
611 # --remove .topmsg and .topdeps are removed from the result and working tree
612 # note this function should only be called after attempt_index_merge fails as
613 # it implicity always does --no-ff (except for --merge which will --ff)
614 git_merge() {
615 _ret=0
616 git_topmerge -v _oldhead "$@" || _ret=$?
617 _exclusions=
618 [ "$1" = "--merge" ] || _exclusions=":/ :!/.topdeps :!/.topmsg"
619 [ "$_ret" != "0" ] || git --no-pager diff-tree --shortstat "$_oldhead" HEAD^0 -- $_exclusions
620 return $_ret
623 # $1 => .topfile handling ([--]merge, [--]theirs, [--]remove or else do ours)
624 # $2 => current "HEAD"
625 # $3 => proposed fast-forward-to "HEAD"
626 # result is success if fast-forward satisfies $1
627 topff_ok() {
628 case "${1#--}" in
629 merge|theirs)
630 # merge and theirs will always be correct
632 remove)
633 # okay if both blobs are "missing" in $3
634 printf '%s\n' "$3:.topdeps" "$3:.topmsg" |
635 git cat-file --batch-check="%(objectname) %(objecttype)" |
637 read _tdo _tdt &&
638 read _tmo _tmt &&
639 [ "$_tdt" = "missing" ] &&
640 [ "$_tmt" = "missing" ]
641 } || return 1
644 # "ours"
645 # okay if both blobs are the same (same hash or missing)
646 printf '%s\n' "$2:.topdeps" "$2:.topmsg" "$3:.topdeps" "$3:.topmsg" |
647 git cat-file --batch-check="%(objectname) %(objecttype)" |
649 read _td1o _td1t &&
650 read _tm1o _tm1t &&
651 read _td2o _td2t &&
652 read _tm2o _tm2t &&
653 { [ "$_td1t" = "$_td2t" ] &&
654 { [ "$_td1o" = "$_td2o" ] ||
655 [ "$_td1t" = "missing" ]; }; } &&
656 { [ "$_tm1t" = "$_tm2t" ] &&
657 { [ "$_tm1o" = "$_tm2o" ] ||
658 [ "$_tm1t" = "missing" ]; }; }
659 } || return 1
661 esac
662 return 0
665 # similar to git_merge but operates exclusively using a separate index and temp dir
666 # only trivial aggressive automatic (i.e. simple) merges are supported
668 # [optional] '--no-auto' to suppress "automatic" merging, merge fails instead
669 # [optional] '--merge', '--theirs' or '--remove' to alter .topfile handling
670 # $1 => '' to discard result, 'refs/?*' to update the specified ref or a varname
671 # $2 => '-m' MUST be '-m'
672 # $3 => commit message AND, if $1 matches refs/?* the update-ref message
673 # $4 => commit-ish to merge as "ours"
674 # $5 => commit-ish to merge as "theirs"
675 # [$6...] => more commit-ishes to merge as "theirs" in octopus
677 # all merging is done in a separate index (or temporary files for simple merges)
678 # if successful the ref or var is updated with the result
679 # otherwise everything is left unchanged and a silent failure occurs
680 # if successful and $1 matches refs/?* it WILL BE UPDATED to a new commit using the
681 # message and appropriate parents AND HEAD WILL BE DETACHED first if it's a symref
682 # to the same ref
683 # otherwise if $1 does not match refs/?* and is not empty the named variable will
684 # be set to contain the resulting commit from the merge
685 # the working tree and index ARE LEFT COMPLETELY UNTOUCHED no matter what
686 v_attempt_index_merge() {
687 _noauto=
688 if [ "$1" = "--no-auto" ]; then
689 _noauto=1
690 shift
692 _exclusions=
693 [ "$1" = "--merge" ] || _exclusions=":/ :!/.topdeps :!/.topmsg"
694 _mstyle=
695 if [ "$1" = "--merge" ] || [ "$1" = "--theirs" ] || [ "$1" = "--remove" ]; then
696 _mmode="${1#--}"
697 shift
698 if [ "$_mmode" = "merge" ] || [ "$_mmode" = "theirs" ]; then
699 _mstyle="-top$_mmode"
702 [ "$#" -ge 5 ] && [ "$2" = "-m" ] && [ -n "$3" ] && [ -n "$4" ] && [ -n "$5" ] ||
703 die "programmer error: invalid arguments to v_attempt_index_merge: $*"
704 _var="$1"
705 _msg="$3"
706 _head="$4"
707 shift 4
708 rh="$(git rev-parse --quiet --verify "$_head^0" --)" && [ -n "$rh" ] || return 1
709 orh="$rh"
710 oth=
711 _mmsg=
712 newc=
713 _nodt=
714 _same=
715 _mt=
716 _octo=
717 if [ $# -gt 1 ]; then
718 if [ "$_mmode" = "merge" ] || [ "$_mmode" = "theirs" ]; then
719 die "programmer error: invalid octopus .topfile strategy to v_attempt_index_merge: --$_mode"
721 ihl="$(git merge-base --independent "$@")" || return 1
722 set -- $ihl
723 [ $# -ge 1 ] && [ -n "$1" ] || return 1
725 [ $# -eq 1 ] || _octo=1
726 mb="$(git merge-base ${_octo:+--octopus} "$rh" "$@")" && [ -n "$mb" ] || {
727 mb="$(git hash-object -w -t tree --stdin < /dev/null)"
728 _mt=1
730 if [ -z "$_mt" ]; then
731 if [ -n "$_octo" ]; then
732 while [ $# -gt 1 ] && mbh="$(git merge-base "$rh" "$1")" && [ -n "$mbh" ]; do
733 if [ "$rh" = "$mbh" ]; then
734 if topff_ok "$_mmode" "$rh" "$1"; then
735 _mmsg="Fast-forward (no commit created)"
736 rh="$1"
737 shift
738 else
739 break
741 elif [ "$1" = "$mbh" ]; then
742 shift
743 else
744 break;
746 done
747 if [ $# -eq 1 ]; then
748 _octo=
749 mb="$(git merge-base "$rh" "$1")" && [ -n "$mb" ] || return 1
752 if [ -z "$_octo" ]; then
753 r1="$(git rev-parse --quiet --verify "$1^0" --)" && [ -n "$r1" ] || return 1
754 oth="$r1"
755 set -- "$r1"
756 if [ "$rh" = "$mb" ]; then
757 if topff_ok "$_mmode" "$rh" "$r1"; then
758 _mmsg="Fast-forward (no commit created)"
759 newc="$r1"
760 _nodt=1
761 _mstyle=
763 elif [ "$r1" = "$mb" ]; then
764 [ -n "$_mmsg" ] || _mmsg="Already up-to-date!"
765 newc="$rh"
766 _nodt=1
767 _same=1
768 _mstyle=
772 if [ -z "$newc" ]; then
773 if [ "$_mmode" = "theirs" ] && [ -z "$oth" ]; then
774 oth="$(git rev-parse --quiet --verify "$1^0" --)" && [ -n "$oth" ] || return 1
775 set -- "$oth"
777 inew="$tg_tmp_dir/index.$$"
778 ! [ -e "$inew" ] || rm -f "$inew"
779 itmp="$tg_tmp_dir/output.$$"
780 imrg="$tg_tmp_dir/auto.$$"
781 [ -z "$_octo" ] || >"$imrg"
782 _auto=
783 _parents=
784 _newrh="$rh"
785 while :; do
786 if [ -n "$_parents" ]; then
787 if contained_by "$1" "$_newrh"; then
788 shift
789 continue
792 GIT_INDEX_FILE="$inew" git read-tree -m --aggressive -i "$mb" "$rh" "$1" || { rm -f "$inew" "$imrg"; return 1; }
793 GIT_INDEX_FILE="$inew" git ls-files --unmerged --full-name --abbrev :/ >"$itmp" 2>&1 || { rm -f "$inew" "$itmp" "$imrg"; return 1; }
794 ! [ -s "$itmp" ] || {
795 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
796 rm -f "$inew" "$itmp" "$imrg"
797 return 1
799 if [ -s "$itmp" ]; then
800 if [ -n "$_noauto" ]; then
801 rm -f "$inew" "$itmp" "$imrg"
802 return 1
804 if [ -n "$_octo" ]; then
805 cat "$itmp" >>"$imrg"
806 else
807 cat "$itmp"
809 _auto=" automatic"
812 _mstyle=
813 rm -f "$itmp"
814 _parents="${_parents:+$_parents }-p $1"
815 if [ $# -gt 1 ]; then
816 newt="$(GIT_INDEX_FILE="$inew" git write-tree)" && [ -n "$newt" ] || { rm -f "$inew" "$imrg"; return 1; }
817 rh="$newt"
818 shift
819 continue
821 break;
822 done
823 if [ "$_mmode" != "merge" ]; then
824 case "$_mmode" in
825 theirs) _source="$oth";;
826 remove) _source="";;
827 *) _source="$orh";;
828 esac
829 _newinfo=
830 [ -z "$_source" ] ||
831 _newinfo="$(git cat-file --batch-check="%(objecttype) %(objectname)$tab%(rest)" <<-EOT |
832 $_source:.topdeps .topdeps
833 $_source:.topmsg .topmsg
835 sed -n 's/^blob /100644 /p'
837 [ -z "$_newinfo" ] || _newinfo="$lf$_newinfo"
838 GIT_INDEX_FILE="$inew" git update-index --index-info <<-EOT || { rm -f "$inew" "$imrg"; return 1; }
839 0 $nullsha$tab.topdeps
840 0 $nullsha$tab.topmsg$_newinfo
843 newt="$(GIT_INDEX_FILE="$inew" git write-tree)" && [ -n "$newt" ] || { rm -f "$inew" "$imrg"; return 1; }
844 [ -z "$_octo" ] || sort -u <"$imrg"
845 rm -f "$inew" "$imrg"
846 newc="$(git commit-tree -p "$orh" $_parents -m "$_msg" "$newt")" && [ -n "$newc" ] || return 1
847 _mmsg="Merge made by the 'trivial aggressive$_auto${_octo:+ octopus}' strategy."
849 case "$_var" in
850 refs/?*)
851 if [ -n "$_same" ]; then
852 _same=
853 if rv="$(git rev-parse --quiet --verify "$_var" --)" && [ "$rv" = "$newc" ]; then
854 _same=1
857 if [ -z "$_same" ]; then
858 detach_symref_head_on_branch "$_var" || return 1
859 # git update-ref returns 0 even on failure :(
860 git update-ref -m "$_msg" "$_var" "$newc" || return 1
864 eval "$_var="'"$newc"'
866 esac
867 echo "$_mmsg"
868 [ -n "$_nodt" ] || git --no-pager diff-tree --shortstat "$orh" "$newc" -- $_exclusions
869 return 0
872 # shortcut that passes $3 as a preceding argument (which must match refs/?*)
873 attempt_index_merge() {
874 _noauto=
875 _mmode=
876 if [ "$1" = "--no-auto" ]; then
877 _noauto="$1"
878 shift
880 if [ "$1" = "--merge" ] || [ "$1" = "--theirs" ] || [ "$1" = "--remove" ]; then
881 _mmode="$1"
882 shift
884 case "$3" in refs/?*);;*)
885 die "programmer error: invalid arguments to attempt_index_merge: $*"
886 esac
887 v_attempt_index_merge $_noauto $_mmode "$3" "$@"
890 on_base=
891 do_base_switch() {
892 [ -n "$1" ] || return 0
894 [ "$1" != "$on_base" ] ||
895 [ "$(git symbolic-ref -q HEAD)" != "refs/$topbases/$1" ]
896 then
897 switch_to_base "$1"
898 on_base="$1"
902 update_branch_internal() {
903 # We are cacheable until the first change
904 become_cacheable
906 _update_name="$1"
907 ## First, take care of our base
909 _depcheck="$(get_temp tg-depcheck)"
910 missing_deps=
911 needs_update "$_update_name" >"$_depcheck" || :
912 if [ -n "$missing_deps" ]; then
913 msg="Some dependencies are missing: $missing_deps"
914 if [ -n "$skipms" ]; then
915 info "$msg; skipping"
916 elif [ -z "$all" ]; then
917 die "$msg"
918 else
919 info "$msg; skipping branch $_update_name"
920 return 0
923 # allow automatic simple merges by default until a failure occurs
924 no_auto=
925 if [ -s "$_depcheck" ]; then
926 # (1) last word is $_update_name, remove it
927 # (2) keep only immediate dependencies of a chain adding a leading '+'
928 # (3) one-level deep dependencies get a '-' prefix instead
929 <"$_depcheck" sed \
930 -e 's/ [^ ]* *$//; # (1)' \
931 -e 's/.* \([^ ]*\)$/+\1/; # (2)' \
932 -e 's/^\([^+]\)/-\1/; # (3)' |
933 # now each line is +branch or -branch (+ == recurse)
934 >"$_depcheck.ideps" \
935 uniq -s 1 # fold branch lines; + always comes before - and thus wins within uniq
937 stash_now_if_requested
939 while read -r depline; do
940 dep="${depline#?}"
941 action="${depline%$dep}"
943 # We do not distinguish between dependencies out-of-date
944 # and base/remote out-of-date cases for $dep here,
945 # but thanks to needs_update returning : or :refs/remotes/...
946 # for the latter, we do correctly recurse here
947 # in both cases.
949 if [ x"$action" = x+ ]; then
950 case " $missing_deps " in *" $dep "*)
951 info "Skipping recursing to missing dependency: $dep"
952 continue
953 esac
954 info "Recursing to $dep..."
955 recursive_update "$dep" || exit 3
957 done <"$_depcheck.ideps"
959 # Create a list of all the fully qualified ref names that need
960 # to be merged into $_update_name's base. This will be done
961 # as an octopus merge if there are no conflicts.
962 deplist=
963 deplines=
964 set --
965 while read -r dep; do
966 dep="${dep#?}"
967 case "$dep" in
969 d="${dep#?}"
970 set -- "$@" "$d"
971 case "$d" in
972 "refs/heads"/*)
973 d="${d#refs/heads/}"
974 deplist="${deplist:+$deplist }$d"
975 deplines="$deplines$d$lf"
978 d="${d#refs/}"
979 deplist="${deplist:+$deplist }$d"
980 deplines="$deplines$d$lf"
982 esac
985 set -- "$@" "refs/heads/$dep"
986 deplist="${deplist:+$deplist }$dep"
987 deplines="$deplines$dep$lf"
989 esac
990 done <"$_depcheck.ideps"
992 # Make sure we end up on the correct base branch
993 on_base=
994 if [ $# -ge 2 ]; then
995 info "Updating $_update_name base with deps: $deplist"
996 become_non_cacheable
997 msg="tgupdate: octopus merge $# deps into $_update_name base$lf$lf$deplines"
998 if attempt_index_merge --remove -m "$msg" "refs/$topbases/$_update_name" "$@"; then
999 set --
1000 else
1001 info "Octopus merge failed; falling back to multiple 3-way merges"
1002 no_auto="--no-auto"
1006 for fulldep in "$@"; do
1007 # This will be either a proper topic branch
1008 # or a remote base. (branch_needs_update() is called
1009 # only on the _dependencies_, not our branch itself!)
1011 case "$fulldep" in
1012 "refs/heads"/*)
1013 dep="${fulldep#refs/heads/}";;
1014 "refs"/*)
1015 dep="${fulldep#refs/}";;
1017 dep="$fulldep";; # this should be a programmer error
1018 esac
1020 info "Updating $_update_name base with $dep changes..."
1021 become_non_cacheable
1022 msg="tgupdate: merge $dep into $_update_name base"
1024 ! attempt_index_merge $no_auto --remove -m "$msg" "refs/$topbases/$_update_name" "$fulldep^0" &&
1026 # We need to switch to the base branch
1027 # ...but only if we aren't there yet (from failed previous merge)
1028 do_base_switch "$_update_name" || die "do_base_switch failed" &&
1029 git_merge --remove --name "$_update_name base" --name "$dep" -m "$msg" "$fulldep^0"
1031 then
1032 rm "$_depcheck"
1033 save_state
1034 unset TG_RECURSIVE
1035 info "Please commit merge resolution and call \`$tgdisplayac update --continue\`"
1036 info "(use \`$tgdisplayac status\` to see more options)"
1037 exit 3
1039 done
1040 else
1041 info "The base is up-to-date."
1045 ## Second, update our head with the remote branch
1047 plusextra=
1048 merge_with="refs/$topbases/$_update_name"
1049 brmmode=
1050 if has_remote "$_update_name"; then
1051 _rname="refs/remotes/$base_remote/$_update_name"
1052 if branch_contains "refs/heads/$_update_name" "$_rname"; then
1053 info "The $_update_name head is up-to-date wrt. its remote branch."
1054 else
1055 stash_now_if_requested
1056 info "Reconciling $_update_name base with remote branch updates..."
1057 become_non_cacheable
1058 msg="tgupdate: merge ${_rname#refs/} onto $_update_name base"
1059 checkours=
1060 checktheirs=
1061 got_merge_with=
1062 brmmode="--merge"
1063 if [ -n "$mergeresult" ]; then
1064 checkours="$(git rev-parse --verify --quiet "refs/$topbases/$_update_name^0" --)" || :
1065 checktheirs="$(git rev-parse --verify --quiet "$_rname^0" --)" || :
1066 if [ "$mergeours" = "$checkours" ] && [ "$mergetheirs" = "$checktheirs" ]; then
1067 got_merge_with="$mergeresult"
1071 [ -z "$got_merge_with" ] &&
1072 ! v_attempt_index_merge $no_auto --theirs "merge_with" -m "$msg" "refs/$topbases/$_update_name" "$_rname^0" &&
1074 # *DETACH* our HEAD now!
1075 no_auto="--no-auto"
1076 git checkout -q --detach $iowopt "refs/$topbases/$_update_name" || die "git checkout failed" &&
1077 git_merge --theirs --name "$_update_name base content" --name "${_rname#refs/}" -m "$msg" "$_rname^0" &&
1078 merge_with="$(git rev-parse --verify HEAD --)"
1080 then
1081 save_state \
1082 "$(git rev-parse --verify --quiet "refs/$topbases/$_update_name^0" --)" \
1083 "$(git rev-parse --verify --quiet "$_rname^0" --)"
1084 unset TG_RECURSIVE
1085 info "Please commit merge resolution and call \`$tgdisplayac update --continue\`"
1086 info "(use \`$tgdisplayac status\` to see more options)"
1087 exit 3
1089 # Go back but remember we want to merge with this, not base
1090 [ -z "$got_merge_with" ] || merge_with="$got_merge_with"
1091 plusextra="${_rname#refs/} + "
1096 ## Third, update our head with the base
1098 if branch_contains "refs/heads/$_update_name" "$merge_with"; then
1099 info "The $_update_name head is up-to-date wrt. the base."
1100 return 0
1102 stash_now_if_requested
1103 info "Updating $_update_name against ${plusextra}new base..."
1104 become_non_cacheable
1105 msg="tgupdate: merge ${plusextra}$_update_name base into $_update_name"
1106 b4deps=
1107 if [ -n "$brmmode" ] && [ "$base_remote" ]; then
1108 b4deps="$(git rev-parse --verify --quiet "refs/heads/$_update_name:.topdeps" --)" && [ -n "$b4deps" ] ||
1109 b4deps="$(git hash-object -t blob -w --stdin </dev/null)"
1112 ! attempt_index_merge $no_auto $brmmode -m "$msg" "refs/heads/$_update_name" "$merge_with^0" &&
1114 # Home, sweet home...
1115 # (We want to always switch back, in case we were
1116 # on the base from failed previous merge.)
1117 git checkout -q $iowopt "$_update_name" || die "git checkout failed" &&
1118 git_merge $brmmode --name "$_update_name" --name "${plusextra}$topbases/$_update_name" -m "$msg" "$merge_with^0"
1120 then
1121 no_auto=
1122 merging_topfiles="${brmmode:+1}"
1123 save_state
1124 unset TG_RECURSIVE
1125 info "Please commit merge resolution and call \`$tgdisplayac update --continue\`"
1126 info "(use \`$tgdisplayac status\` to see more options)"
1127 exit 3
1130 # Fourth, auto create locally any newly depended on branches we got from the remote
1132 _result=0
1133 if [ -n "$b4deps" ] &&
1134 l8rdeps="$(git rev-parse --verify --quiet "refs/heads/$_update_name:.topdeps" --)" &&
1135 [ -n "$l8rdeps" ] && [ "$b4deps" != "$l8rdeps" ]
1136 then
1137 _olddeps=
1138 while read -r newdep; do
1139 if [ -n "$newdep" ]; then
1140 if auto_create_local_remote "$newdep"; then
1141 _result=75
1142 else
1143 if ref_exists "refs/heads/$newdep"; then
1144 # maybe the line just moved around
1145 [ -n "$_olddeps" ] && [ -f "$_olddeps" ] || {
1146 _olddeps="$(get_temp b4deps)" &&
1147 git cat-file blob "$b4deps" >"$_olddeps"
1149 if awk -v "newdep=$newdep" '$0 == newdep {exit 1}' <"$_olddeps"; then
1150 # nope, it's a new head already existing locally
1151 _result=75
1153 else
1154 # helpfully check to see if there's such a remote branch
1155 _rntgb=
1156 ! ref_exists "refs/remotes/$base_remote/$newdep" || _rntgb=1
1157 # maybe a blocking local orphan base too
1158 _blocked=
1159 if [ -n "$_rntgb" ] &&
1160 ref_exists "refs/remotes/$base_remote/${topbases#heads/}/$newdep" &&
1161 ref_exists "refs/$topbases/$newdep"
1162 then
1163 _blocked=1
1165 # spew the flexibly adjustable warning
1166 warn "-------------------------------------"
1167 warn "MISSING DEPENDENCY MERGED FROM REMOTE"
1168 warn "-------------------------------------"
1169 warn "Local Branch: $_update_name"
1170 warn " Remote Name: $base_remote"
1171 warn " Dependency: $newdep"
1172 if [ -n "$_blocked" ]; then
1173 warn "Blocking Ref: refs/$topbases/$newdep"
1174 elif [ -n "$_rntgb" ]; then
1175 warn "Existing Ref: refs/remotes/$base_remote/$newdep"
1177 warn ""
1178 if [ -n "$_blocked" ]; then
1179 warn "There is no local branch by that name, but"
1180 warn "there IS a remote TopGit branch available by"
1181 warn "that name, but creation of a local version has"
1182 warn "been blocked by existence of the ref shown above."
1183 elif [ -n "$_rntgb" ]; then
1184 warn "There is no local branch or remote TopGit"
1185 warn "branch available by that name, but there is an"
1186 warn "existing non-TopGit remote branch ref shown above."
1187 warn "Non-TopGit branches are not set up automatically"
1188 warn "by TopGit and must be maintained manually."
1189 else
1190 warn "There is no local branch or remote branch"
1191 warn "(TopGit or otherwise) available by that name."
1193 warn "-------------------------------------"
1197 done <<-EOT
1198 $(git diff --ignore-space-at-eol "$b4deps" "$l8rdeps" -- | diff_added_lines)
1201 return $_result
1204 update_branch() {
1205 _ubicode=0
1206 _maxdeploop=3
1207 update_branch_internal "$@" || _ubicode=$?
1208 while [ "$_maxdeploop" -gt 0 ] && [ "$_ubicode" = "75" ]; do
1209 _maxdeploop="$(( $maxdeploop - 1 ))"
1210 info "Updating $1 again with newly added dependencies..."
1211 _ubicode=0
1212 update_branch_internal "$@" || _ubicode=$?
1213 done
1214 return $_ubicode
1217 # We are "read-only" and cacheable until the first change
1218 tg_read_only=1
1219 v_create_ref_cache
1221 do_non_annihilated_branches_patterns() {
1222 while read -r _pat && [ -n "$_pat" ]; do
1223 set -- "$@" "$_pat"
1224 done
1225 non_annihilated_branches "$@"
1228 do_non_annihilated_branches() {
1229 if [ -z "$pattern" ]; then
1230 non_annihilated_branches
1231 else
1232 do_non_annihilated_branches_patterns <<-EOT
1233 $(sed 'y/ /\n/' <<-LIST
1234 $pattern
1235 LIST
1241 if [ -n "$all" ] && [ -z "$restored" ]; then
1242 names=
1243 while read name && [ -n "$name" ]; do
1244 case " $names " in *" $name "*);;*)
1245 names="${names:+$names }$name"
1246 esac
1247 done <<-EOT
1248 $(do_non_annihilated_branches)
1252 for name in $names; do
1253 case " $processed " in *" $name "*) continue; esac
1254 [ -z "$all" ] && case "$names" in *" "*) ! :; esac || info "Proccessing $name..."
1255 update_branch "$name" || exit
1256 processed="${processed:+$processed }$name"
1257 done
1259 [ -z "$all" ] && case "$names" in *" "*) ! :; esac ||
1260 info "Returning to ${current#refs/heads/}..."
1261 checkout_symref_full "$current"
1262 ! [ -f "$git_dir/TGMERGE_MSG" ] || [ -e "$git_dir/MERGE_MSG" ] ||
1263 mv -f "$git_dir/TGMERGE_MSG" "$git_dir/MERGE_MSG" || :