tg.sh: next version is 0.19.13
[topgit/pro.git] / tg-update.sh
blob1776d5284582654d13658846ec0219ffde85589f
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
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 v_ref_exists_rev baserev "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 v_strip_ref bname "$name"
264 git checkout -q $iowopt "$bname"
267 do_status
268 exit 1
269 esac
271 clear_state
273 if [ -z "$restored" ]; then
274 setautoupdate=1
275 [ "$(git config --get --bool topgit.setAutoUpdate 2>/dev/null)" != "false" ] ||
276 setautoupdate=
278 while [ -n "$1" ]; do
279 arg="$1"; shift
280 case "$arg" in
282 usage;;
283 -a|--all)
284 [ -z "$names$pattern" ] || usage 1
285 all=1;;
286 --skip-missing)
287 skipms=1;;
288 --stash)
289 stash=1;;
290 --no-stash)
291 stash=;;
292 --auto|--auto-update|--set-auto|--set-auto-update)
293 setautoupdate=1;;
294 --no-auto|--no-auto-update|--no-set-auto|--no-set-auto-update)
295 setautoupdate=;;
296 --quiet|-q)
297 quiet=1;;
298 --base)
299 basemode=1;;
300 --edit|-e)
301 editmode=1;;
302 --no-edit)
303 editmode=0;;
304 --no-commit)
305 basenc=1;;
306 --force|-f)
307 basefrc=1;;
308 -m|--message)
309 [ $# -gt 0 ] && [ -n "$1" ] || die "option $arg requires an argument"
310 basemsg="$1"
311 shift;;
312 -m?*)
313 basemsg="${1#-m}";;
314 --message=*)
315 basemsg="${1#--message=}";;
316 -F|--file)
317 [ $# -gt 0 ] && [ -n "$1" ] || die "option $arg requires an argument"
318 basefile="$1"
319 shift;;
320 -F?*)
321 basefile="${1#-F}";;
322 --file=*)
323 basefile="${1#--file=}"
324 [ -n "$basefile" ] || die "option --file= requires an argument"
326 -?*)
327 usage 1;;
329 break;;
333 if [ -z "$all" ]; then
334 namecnt=$(( $namecnt + 1 ))
335 [ "$namecnt" != "1" ] || name1="$arg"
336 [ "$namecnt" != "2" ] || name2="$arg"
337 names="${names:+$names }$arg"
338 else
339 v_strip_ref arg "$arg"
340 pattern="${pattern:+$pattern }refs/$topbases/$arg"
343 esac
344 done
345 ensure_work_tree
346 while [ $# -gt 0 ]; do
347 if [ -z "$all" ]; then
348 namecnt=$(( $namecnt + 1 ))
349 [ "$namecnt" != "1" ] || name1="$1"
350 [ "$namecnt" != "2" ] || name2="$1"
351 names="${names:+$names }$*"
352 else
353 v_strip_ref arg "$1"
354 pattern="${pattern:+$pattern }refs/$topbases/$arg"
356 shift
357 done
358 [ -n "$basemode" ] || [ -z "$editmode$basemsg$basefile$basenc$basefrc" ] || usage 1
359 [ -z "$basemode" ] || [ -z "$all$skipms" ] || usage 1
360 [ -z "$basemode" ] || [ -z "$basemsg" ] || [ -z "$basefile" ] || usage 1
361 [ -z "$basemode" ] || [ "$namecnt" -eq 2 ] || usage 1
363 current="$(git symbolic-ref -q HEAD)" || :
364 if [ -n "$current" ]; then
365 [ -n "$(git rev-parse --verify --quiet HEAD --)" ] ||
366 die "cannot return to unborn branch; switch to another branch"
367 else
368 current="$(git rev-parse --verify --quiet HEAD)" ||
369 die "cannot return to invalid HEAD; switch to another branch"
372 [ -z "$basemode" ] || do_base_mode "$name1" "$name2"
374 origpattern="$pattern"
375 [ -z "$pattern" ] && pattern="refs/$topbases"
377 processed=
378 [ -n "$all$names" ] || names="HEAD"
379 if [ -z "$all" ]; then
380 clean_names() {
381 names=
382 while [ $# -gt 0 ]; do
383 v_verify_topgit_branch name "$1"
384 case " $names " in *" $name "*);;*)
385 names="${names:+$names }$name"
386 esac
387 shift
388 done
390 clean_names $names
391 if [ "$namecnt" -eq 1 ]; then
392 case "$fullcmd" in *" @"|*" HEAD")
393 namecnt=0
394 fullcmd="${fullcmd% *}"
395 esac
397 [ "$namecnt" -ne 0 ] || fullcmd="$fullcmd $names"
399 ensure_clean_tree
402 save_state() {
403 mkdir -p "$state_dir"
404 printf '%s\n' "$fullcmd" >"$state_dir/fullcmd"
405 printf '%s\n' "$base_remote" >"$state_dir/remote"
406 printf '%s\n' "$skipms" >"$state_dir/skipms"
407 printf '%s\n' "$all" >"$state_dir/all"
408 printf '%s\n' "$current" >"$state_dir/current"
409 printf '%s\n' "$stashhash" >"$state_dir/stashhash"
410 printf '%s\n' "$name" >"$state_dir/name"
411 printf '%s\n' "$names" >"$state_dir/names"
412 printf '%s\n' "$processed" >"$state_dir/processed"
413 printf '%s\n' "$no_auto" >"$state_dir/no_auto"
414 printf '%s\n' "$setautoupdate" >"$state_dir/setautoupdate"
415 # this one is an external flag and needs to be zero length for false
416 printf '%s' "$merging_topfiles" >"$state_dir/merging_topfiles"
417 printf '%s\n' "$1" >"$state_dir/mergeours"
418 printf '%s\n' "$2" >"$state_dir/mergetheirs"
421 stash_now_if_requested() {
422 [ -z "$TG_RECURSIVE" ] || return 0
423 [ -z "$stashhash" ] || return 0
424 ensure_ident_available
425 msg="tgupdate: autostash before update"
426 if [ -n "$all" ]; then
427 msg="$msg --all${origpattern:+ $origpattern}"
428 else
429 msg="$msg $names"
431 set -- $names
432 if [ -n "$stash" ]; then
433 tg tag -q -q -m "$msg" --stash "$@" &&
434 stashhash="$(git rev-parse --quiet --verify refs/tgstash --)" &&
435 [ -n "$stashhash" ] &&
436 [ "$(git cat-file -t "$stashhash" 2>/dev/null)" = "tag" ] ||
437 die "requested --stash failed"
438 else
439 tg tag --anonymous "$@" &&
440 stashhash="$(git rev-parse --quiet --verify TG_STASH --)" &&
441 [ -n "$stashhash" ] &&
442 [ "$(git cat-file -t "$stashhash" 2>/dev/null)" = "tag" ] ||
443 die "anonymous --stash failed"
445 [ -z "$next_no_auto" ] || no_auto="$next_no_auto"
446 next_no_auto=
449 recursive_update() {
450 _ret=0
451 on_base=
453 if [ -n "$TG_RECURSIVE" ]; then
454 TG_RECURSIVE="==> [$1]${TG_RECURSIVE#==>}"
455 else
456 TG_RECURSIVE="==> [$1]$lf"
458 update_branch "$1"
459 ) || _ret=$?
460 [ $_ret -eq 3 ] && exit 3
461 return $_ret
464 # If HEAD is a symref to "$1" detach it at its current value
465 detach_symref_head_on_branch() {
466 _hsr="$(git symbolic-ref -q HEAD --)" && [ -n "$_hsr" ] || return 0
467 _hrv="$(git rev-parse --quiet --verify HEAD --)" && [ -n "$_hrv" ] ||
468 die "cannot detach_symref_head_on_branch from unborn branch $_hsr"
469 git update-ref --no-deref -m "detaching HEAD from $_hsr to safely update it" HEAD "$_hrv"
472 # git_topmerge will need this even on success and since it might otherwise
473 # be called many times do it just the once here and now
474 repotoplvl="$(git rev-parse --show-toplevel)" && [ -n "$repotoplvl" ] && [ -d "$repotoplvl" ] ||
475 die "git rev-parse --show-toplevel failed"
477 # Run an in-tree recursive merge but make sure we get the desired version of
478 # any .topdeps and .topmsg files. The $auhopt and --no-stat options are
479 # always implicitly in effect. If successful, a new commit is performed on HEAD.
481 # The "git merge-recursive" tool (and others) must be run to get the desired
482 # result. And --no-ff is always implicitly in effect as well.
484 # NOTE: [optional] arguments MUST appear in the order shown
485 # [optional] '-v' varname => optional variable to return original HEAD hash in
486 # [optional] '--merge', '--theirs' or '--remove' to alter .topfile handling
487 # [optional] '--name' <name-for-ours [--name <name-for-theirs>]
488 # $1 => '-m' MUST be '-m'
489 # $2 => commit message
490 # $3 => commit-ish to merge as "theirs"
491 git_topmerge()
493 _ovar=
494 [ "$1" != "-v" ] || [ $# -lt 2 ] || [ -z "$2" ] || { _ovar="$2"; shift 2; }
495 _mmode=
496 case "$1" in --theirs|--remove|--merge) _mmode="${1#--}"; shift; esac
497 _nameours=
498 _nametheirs=
499 if [ "$1" = "--name" ] && [ $# -ge 2 ]; then
500 _nameours="$2"
501 shift 2
502 if [ "$1" = "--name" ] && [ $# -ge 2 ]; then
503 _nametheirs="$2"
504 shift 2
507 : "${_nameours:=HEAD}"
508 [ "$#" -eq 3 ] && [ "$1" = "-m" ] && [ -n "$2" ] && [ -n "$3" ] ||
509 die "programmer error: invalid arguments to git_topmerge: $*"
510 _ours="$(git rev-parse --verify HEAD^0)" || die "git rev-parse failed"
511 _theirs="$(git rev-parse --verify "$3^0")" || die "git rev-parse failed"
512 [ -z "$_ovar" ] || eval "$_ovar="'"$_ours"'
513 eval "GITHEAD_$_ours="'"$_nameours"' && eval export "GITHEAD_$_ours"
514 if [ -n "$_nametheirs" ]; then
515 eval "GITHEAD_$_theirs="'"$_nametheirs"' && eval export "GITHEAD_$_theirs"
517 _mdriver='touch %A'
518 if [ "$_mmode" = "merge" ]; then
519 TG_L1="$_nameours" && export TG_L1
520 TG_L2="merged common ancestors" && export TG_L2
521 TG_L3="${_nametheirs:-$3}" && export TG_L3
522 _mdriver='git merge-file -L "$TG_L1" -L "$TG_L2" -L "$TG_L3" --marker-size=%L %A %O %B'
524 _msg="$2"
525 _mt=
526 _mb="$(git merge-base --all "$_ours" "$_theirs")" && [ -n "$_mb" ] ||
527 { _mt=1; _mb="$(git hash-object -w -t tree --stdin < /dev/null)"; }
528 # any .topdeps or .topmsg output needs to be stripped from stdout
529 tmpstdout="$tg_tmp_dir/stdout.$$"
530 _ret=0
531 git -c "merge.ours.driver=$_mdriver" merge-recursive \
532 $_mb -- "$_ours" "$_theirs" >"$tmpstdout" || _ret=$?
533 # success or failure is not relevant until after fixing up the
534 # .topdeps and .topmsg files and running rerere unless _ret >= 126
535 [ $_ret -lt 126 ] || return $_ret
536 if [ "$_mmode" = "merge" ]; then
537 cat "$tmpstdout"
538 else
539 case "$_mmode" in
540 theirs) _source="$_theirs";;
541 remove) _source="";;
542 *) _source="$_ours";;
543 esac
544 _newinfo=
545 [ -z "$_source" ] ||
546 _newinfo="$(git cat-file --batch-check="%(objecttype) %(objectname)$tab%(rest)" <<-EOT |
547 $_source:.topdeps .topdeps
548 $_source:.topmsg .topmsg
550 sed -n 's/^blob /100644 /p'
552 [ -z "$_newinfo" ] || _newinfo="$lf$_newinfo"
553 git update-index --index-info <<-EOT ||
554 0 $nullsha$tab.topdeps
555 0 $nullsha$tab.topmsg$_newinfo
557 die "git update-index failed"
558 if [ "$_mmode" = "remove" ] &&
559 { [ -e "$repotoplvl/.topdeps" ] || [ -e "$repotoplvl/.topmsg" ]; }
560 then
561 rm -r -f "$repotoplvl/.topdeps" "$repotoplvl/.topmsg" >/dev/null 2>&1 || :
562 else
563 for zapbad in "$repotoplvl/.topdeps" "$repotoplvl/.topmsg"; do
564 if [ -e "$zapbad" ] && { [ -L "$zapbad" ] || [ ! -f "$zapbad" ]; }; then
565 rm -r -f "$zapbad"
567 done
568 (cd "$repotoplvl" && git checkout-index -q -f -u -- .topdeps .topmsg) ||
569 die "git checkout-index failed"
571 # dump output without any .topdeps or .topmsg messages
572 sed -e '/ \.topdeps/d' -e '/ \.topmsg/d' <"$tmpstdout"
574 # rerere will be a nop unless rerere.enabled is true, but might complete the merge!
575 eval git "${setautoupdate:+-c rerere.autoupdate=1}" rerere || :
576 git ls-files --unmerged --full-name --abbrev :/ >"$tmpstdout" 2>&1 ||
577 die "git ls-files failed"
578 if [ -s "$tmpstdout" ]; then
579 [ "$_ret" != "0" ] || _ret=1
580 else
581 _ret=0
583 if [ $_ret -ne 0 ]; then
584 # merge failed, spit out message, enter "merge" mode and return
586 printf '%s\n\n# Conflicts:\n' "$_msg"
587 sed -n "/$tab/s/^[^$tab]*/#/p" <"$tmpstdout" | sort -u
588 } >"$git_dir/MERGE_MSG"
589 git update-ref MERGE_HEAD "$_theirs" || :
590 echo 'Automatic merge failed; fix conflicts and then commit the result.'
591 rm -f "$tmpstdout"
592 return $_ret
594 # commit time at last!
595 thetree="$(git write-tree)" || die "git write-tree failed"
596 # avoid an extra "already up-to-date" commit (can't happen if _mt though)
597 origtree=
598 [ -n "$_mt" ] || {
599 origtree="$(git rev-parse --quiet --verify "$_ours^{tree}" --)" &&
600 [ -n "$origtree" ]
601 } || die "git rev-parse failed"
602 if [ "$origtree" != "$thetree" ] || ! contained_by "$_theirs" "$_ours"; then
603 thecommit="$(git commit-tree -p "$_ours" -p "$_theirs" -m "$_msg" "$thetree")" &&
604 [ -n "$thecommit" ] || die "git commit-tree failed"
605 git update-ref -m "$_msg" HEAD "$thecommit" || die "git update-ref failed"
607 # mention how the merge was made
608 echo "Merge made by the 'recursive' strategy."
609 rm -f "$tmpstdout"
610 return 0
613 # run git_topmerge with the passed in arguments (it always does --no-stat)
614 # then return the exit status of git_topmerge
615 # if the returned exit status is no error show a shortstat before
616 # returning assuming the merge was done into the previous HEAD but exclude
617 # .topdeps and .topmsg info from the stat unless doing a --merge
618 # if the first argument is --merge or --theirs or --remove handle .topmsg/.topdeps
619 # as follows:
620 # (default) .topmsg and .topdeps always keep ours
621 # --merge a normal merge takes place
622 # --theirs .topmsg and .topdeps always keep theirs
623 # --remove .topmsg and .topdeps are removed from the result and working tree
624 # note this function should only be called after attempt_index_merge fails as
625 # it implicity always does --no-ff (except for --merge which will --ff)
626 git_merge() {
627 _ret=0
628 git_topmerge -v _oldhead "$@" || _ret=$?
629 _exclusions=
630 [ "$1" = "--merge" ] || _exclusions=":/ :!/.topdeps :!/.topmsg"
631 [ "$_ret" != "0" ] || git --no-pager diff-tree --shortstat "$_oldhead" HEAD^0 -- $_exclusions
632 return $_ret
635 # $1 => .topfile handling ([--]merge, [--]theirs, [--]remove or else do ours)
636 # $2 => current "HEAD"
637 # $3 => proposed fast-forward-to "HEAD"
638 # result is success if fast-forward satisfies $1
639 topff_ok() {
640 case "${1#--}" in
641 merge|theirs)
642 # merge and theirs will always be correct
644 remove)
645 # okay if both blobs are "missing" in $3
646 printf '%s\n' "$3:.topdeps" "$3:.topmsg" |
647 git cat-file --batch-check="%(objectname) %(objecttype)" |
649 read _tdo _tdt &&
650 read _tmo _tmt &&
651 [ "$_tdt" = "missing" ] &&
652 [ "$_tmt" = "missing" ]
653 } || return 1
656 # "ours"
657 # okay if both blobs are the same (same hash or missing)
658 printf '%s\n' "$2:.topdeps" "$2:.topmsg" "$3:.topdeps" "$3:.topmsg" |
659 git cat-file --batch-check="%(objectname) %(objecttype)" |
661 read _td1o _td1t &&
662 read _tm1o _tm1t &&
663 read _td2o _td2t &&
664 read _tm2o _tm2t &&
665 { [ "$_td1t" = "$_td2t" ] &&
666 { [ "$_td1o" = "$_td2o" ] ||
667 [ "$_td1t" = "missing" ]; }; } &&
668 { [ "$_tm1t" = "$_tm2t" ] &&
669 { [ "$_tm1o" = "$_tm2o" ] ||
670 [ "$_tm1t" = "missing" ]; }; }
671 } || return 1
673 esac
674 return 0
677 # similar to git_merge but operates exclusively using a separate index and temp dir
678 # only trivial aggressive automatic (i.e. simple) merges are supported
680 # [optional] '--no-auto' to suppress "automatic" merging, merge fails instead
681 # [optional] '--merge', '--theirs' or '--remove' to alter .topfile handling
682 # $1 => '' to discard result, 'refs/?*' to update the specified ref or a varname
683 # $2 => '-m' MUST be '-m'
684 # $3 => commit message AND, if $1 matches refs/?* the update-ref message
685 # $4 => commit-ish to merge as "ours"
686 # $5 => commit-ish to merge as "theirs"
687 # [$6...] => more commit-ishes to merge as "theirs" in octopus
689 # all merging is done in a separate index (or temporary files for simple merges)
690 # if successful the ref or var is updated with the result
691 # otherwise everything is left unchanged and a silent failure occurs
692 # if successful and $1 matches refs/?* it WILL BE UPDATED to a new commit using the
693 # message and appropriate parents AND HEAD WILL BE DETACHED first if it's a symref
694 # to the same ref
695 # otherwise if $1 does not match refs/?* and is not empty the named variable will
696 # be set to contain the resulting commit from the merge
697 # the working tree and index ARE LEFT COMPLETELY UNTOUCHED no matter what
698 v_attempt_index_merge() {
699 _noauto=
700 if [ "$1" = "--no-auto" ]; then
701 _noauto=1
702 shift
704 _exclusions=
705 [ "$1" = "--merge" ] || _exclusions=":/ :!/.topdeps :!/.topmsg"
706 _mstyle=
707 if [ "$1" = "--merge" ] || [ "$1" = "--theirs" ] || [ "$1" = "--remove" ]; then
708 _mmode="${1#--}"
709 shift
710 if [ "$_mmode" = "merge" ] || [ "$_mmode" = "theirs" ]; then
711 _mstyle="-top$_mmode"
714 [ "$#" -ge 5 ] && [ "$2" = "-m" ] && [ -n "$3" ] && [ -n "$4" ] && [ -n "$5" ] ||
715 die "programmer error: invalid arguments to v_attempt_index_merge: $*"
716 _var="$1"
717 _msg="$3"
718 _head="$4"
719 shift 4
720 rh="$(git rev-parse --quiet --verify "$_head^0" --)" && [ -n "$rh" ] || return 1
721 orh="$rh"
722 oth=
723 _mmsg=
724 newc=
725 _nodt=
726 _same=
727 _mt=
728 _octo=
729 if [ $# -gt 1 ]; then
730 if [ "$_mmode" = "merge" ] || [ "$_mmode" = "theirs" ]; then
731 die "programmer error: invalid octopus .topfile strategy to v_attempt_index_merge: --$_mode"
733 ihl="$(git merge-base --independent "$@")" || return 1
734 set -- $ihl
735 [ $# -ge 1 ] && [ -n "$1" ] || return 1
737 [ $# -eq 1 ] || _octo=1
738 mb="$(git merge-base ${_octo:+--octopus} "$rh" "$@")" && [ -n "$mb" ] || {
739 mb="$(git hash-object -w -t tree --stdin < /dev/null)"
740 _mt=1
742 if [ -z "$_mt" ]; then
743 if [ -n "$_octo" ]; then
744 while [ $# -gt 1 ] && mbh="$(git merge-base "$rh" "$1")" && [ -n "$mbh" ]; do
745 if [ "$rh" = "$mbh" ]; then
746 if topff_ok "$_mmode" "$rh" "$1"; then
747 _mmsg="Fast-forward (no commit created)"
748 rh="$1"
749 shift
750 else
751 break
753 elif [ "$1" = "$mbh" ]; then
754 shift
755 else
756 break;
758 done
759 if [ $# -eq 1 ]; then
760 _octo=
761 mb="$(git merge-base "$rh" "$1")" && [ -n "$mb" ] || return 1
764 if [ -z "$_octo" ]; then
765 r1="$(git rev-parse --quiet --verify "$1^0" --)" && [ -n "$r1" ] || return 1
766 oth="$r1"
767 set -- "$r1"
768 if [ "$rh" = "$mb" ]; then
769 if topff_ok "$_mmode" "$rh" "$r1"; then
770 _mmsg="Fast-forward (no commit created)"
771 newc="$r1"
772 _nodt=1
773 _mstyle=
775 elif [ "$r1" = "$mb" ]; then
776 [ -n "$_mmsg" ] || _mmsg="Already up-to-date!"
777 newc="$rh"
778 _nodt=1
779 _same=1
780 _mstyle=
784 if [ -z "$newc" ]; then
785 if [ "$_mmode" = "theirs" ] && [ -z "$oth" ]; then
786 oth="$(git rev-parse --quiet --verify "$1^0" --)" && [ -n "$oth" ] || return 1
787 set -- "$oth"
789 inew="$tg_tmp_dir/index.$$"
790 ! [ -e "$inew" ] || rm -f "$inew"
791 itmp="$tg_tmp_dir/output.$$"
792 imrg="$tg_tmp_dir/auto.$$"
793 [ -z "$_octo" ] || >"$imrg"
794 _auto=
795 _parents=
796 _newrh="$rh"
797 while :; do
798 if [ -n "$_parents" ]; then
799 if contained_by "$1" "$_newrh"; then
800 shift
801 continue
804 GIT_INDEX_FILE="$inew" git read-tree -m --aggressive -i "$mb" "$rh" "$1" || { rm -f "$inew" "$imrg"; return 1; }
805 GIT_INDEX_FILE="$inew" git ls-files --unmerged --full-name --abbrev :/ >"$itmp" 2>&1 || { rm -f "$inew" "$itmp" "$imrg"; return 1; }
806 ! [ -s "$itmp" ] || {
807 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
808 rm -f "$inew" "$itmp" "$imrg"
809 return 1
811 if [ -s "$itmp" ]; then
812 if [ -n "$_noauto" ]; then
813 rm -f "$inew" "$itmp" "$imrg"
814 return 1
816 if [ -n "$_octo" ]; then
817 cat "$itmp" >>"$imrg"
818 else
819 cat "$itmp"
821 _auto=" automatic"
824 _mstyle=
825 rm -f "$itmp"
826 _parents="${_parents:+$_parents }-p $1"
827 if [ $# -gt 1 ]; then
828 newt="$(GIT_INDEX_FILE="$inew" git write-tree)" && [ -n "$newt" ] || { rm -f "$inew" "$imrg"; return 1; }
829 rh="$newt"
830 shift
831 continue
833 break;
834 done
835 if [ "$_mmode" != "merge" ]; then
836 case "$_mmode" in
837 theirs) _source="$oth";;
838 remove) _source="";;
839 *) _source="$orh";;
840 esac
841 _newinfo=
842 [ -z "$_source" ] ||
843 _newinfo="$(git cat-file --batch-check="%(objecttype) %(objectname)$tab%(rest)" <<-EOT |
844 $_source:.topdeps .topdeps
845 $_source:.topmsg .topmsg
847 sed -n 's/^blob /100644 /p'
849 [ -z "$_newinfo" ] || _newinfo="$lf$_newinfo"
850 GIT_INDEX_FILE="$inew" git update-index --index-info <<-EOT || { rm -f "$inew" "$imrg"; return 1; }
851 0 $nullsha$tab.topdeps
852 0 $nullsha$tab.topmsg$_newinfo
855 newt="$(GIT_INDEX_FILE="$inew" git write-tree)" && [ -n "$newt" ] || { rm -f "$inew" "$imrg"; return 1; }
856 [ -z "$_octo" ] || sort -u <"$imrg"
857 rm -f "$inew" "$imrg"
858 newc="$(git commit-tree -p "$orh" $_parents -m "$_msg" "$newt")" && [ -n "$newc" ] || return 1
859 _mmsg="Merge made by the 'trivial aggressive$_auto${_octo:+ octopus}' strategy."
861 case "$_var" in
862 refs/?*)
863 if [ -n "$_same" ]; then
864 _same=
865 if rv="$(git rev-parse --quiet --verify "$_var" --)" && [ "$rv" = "$newc" ]; then
866 _same=1
869 if [ -z "$_same" ]; then
870 detach_symref_head_on_branch "$_var" || return 1
871 # git update-ref returns 0 even on failure :(
872 git update-ref -m "$_msg" "$_var" "$newc" || return 1
876 eval "$_var="'"$newc"'
878 esac
879 echo "$_mmsg"
880 [ -n "$_nodt" ] || git --no-pager diff-tree --shortstat "$orh" "$newc" -- $_exclusions
881 return 0
884 # shortcut that passes $3 as a preceding argument (which must match refs/?*)
885 attempt_index_merge() {
886 _noauto=
887 _mmode=
888 if [ "$1" = "--no-auto" ]; then
889 _noauto="$1"
890 shift
892 if [ "$1" = "--merge" ] || [ "$1" = "--theirs" ] || [ "$1" = "--remove" ]; then
893 _mmode="$1"
894 shift
896 case "$3" in refs/?*);;*)
897 die "programmer error: invalid arguments to attempt_index_merge: $*"
898 esac
899 v_attempt_index_merge $_noauto $_mmode "$3" "$@"
902 on_base=
903 do_base_switch() {
904 [ -n "$1" ] || return 0
906 [ "$1" != "$on_base" ] ||
907 [ "$(git symbolic-ref -q HEAD)" != "refs/$topbases/$1" ]
908 then
909 switch_to_base "$1"
910 on_base="$1"
914 update_branch_internal() {
915 # We are cacheable until the first change
916 become_cacheable
918 _update_name="$1"
919 ## First, take care of our base
921 _depcheck="$(get_temp tg-depcheck)"
922 missing_deps=
923 needs_update "$_update_name" >"$_depcheck" || :
924 if [ -n "$missing_deps" ]; then
925 msg="Some dependencies are missing: $missing_deps"
926 if [ -n "$skipms" ]; then
927 info "$msg; skipping"
928 elif [ -z "$all" ]; then
929 die "$msg"
930 else
931 info "$msg; skipping branch $_update_name"
932 return 0
935 # allow automatic simple merges by default until a failure occurs
936 no_auto=
937 if [ -s "$_depcheck" ]; then
938 # (1) last word is $_update_name, remove it
939 # (2) keep only immediate dependencies of a chain adding a leading '+'
940 # (3) one-level deep dependencies get a '-' prefix instead
941 <"$_depcheck" sed \
942 -e 's/ [^ ]* *$//; # (1)' \
943 -e 's/.* \([^ ]*\)$/+\1/; # (2)' \
944 -e 's/^\([^+]\)/-\1/; # (3)' |
945 # now each line is +branch or -branch (+ == recurse)
946 >"$_depcheck.ideps" \
947 uniq -s 1 # fold branch lines; + always comes before - and thus wins within uniq
949 stash_now_if_requested
951 while read -r depline; do
952 dep="${depline#?}"
953 action="${depline%$dep}"
955 # We do not distinguish between dependencies out-of-date
956 # and base/remote out-of-date cases for $dep here,
957 # but thanks to needs_update returning : or :refs/remotes/...
958 # for the latter, we do correctly recurse here
959 # in both cases.
961 if [ x"$action" = x+ ]; then
962 case " $missing_deps " in *" $dep "*)
963 info "Skipping recursing to missing dependency: $dep"
964 continue
965 esac
966 info "Recursing to $dep..."
967 recursive_update "$dep" || exit 3
969 done <"$_depcheck.ideps"
971 # Create a list of all the fully qualified ref names that need
972 # to be merged into $_update_name's base. This will be done
973 # as an octopus merge if there are no conflicts.
974 deplist=
975 deplines=
976 set --
977 while read -r dep; do
978 dep="${dep#?}"
979 case "$dep" in
981 d="${dep#?}"
982 set -- "$@" "$d"
983 case "$d" in
984 "refs/heads"/*)
985 d="${d#refs/heads/}"
986 deplist="${deplist:+$deplist }$d"
987 deplines="$deplines$d$lf"
990 d="${d#refs/}"
991 deplist="${deplist:+$deplist }$d"
992 deplines="$deplines$d$lf"
994 esac
997 set -- "$@" "refs/heads/$dep"
998 deplist="${deplist:+$deplist }$dep"
999 deplines="$deplines$dep$lf"
1001 esac
1002 done <"$_depcheck.ideps"
1004 # Make sure we end up on the correct base branch
1005 on_base=
1006 if [ $# -ge 2 ]; then
1007 info "Updating $_update_name base with deps: $deplist"
1008 become_non_cacheable
1009 msg="tgupdate: octopus merge $# deps into $_update_name base$lf$lf$deplines"
1010 if attempt_index_merge --remove -m "$msg" "refs/$topbases/$_update_name" "$@"; then
1011 set --
1012 else
1013 info "Octopus merge failed; falling back to multiple 3-way merges"
1014 no_auto="--no-auto"
1018 for fulldep in "$@"; do
1019 # This will be either a proper topic branch
1020 # or a remote base. (branch_needs_update() is called
1021 # only on the _dependencies_, not our branch itself!)
1023 case "$fulldep" in
1024 "refs/heads"/*)
1025 dep="${fulldep#refs/heads/}";;
1026 "refs"/*)
1027 dep="${fulldep#refs/}";;
1029 dep="$fulldep";; # this should be a programmer error
1030 esac
1032 info "Updating $_update_name base with $dep changes..."
1033 become_non_cacheable
1034 msg="tgupdate: merge $dep into $_update_name base"
1036 ! attempt_index_merge $no_auto --remove -m "$msg" "refs/$topbases/$_update_name" "$fulldep^0" &&
1038 # We need to switch to the base branch
1039 # ...but only if we aren't there yet (from failed previous merge)
1040 do_base_switch "$_update_name" || die "do_base_switch failed" &&
1041 git_merge --remove --name "$_update_name base" --name "$dep" -m "$msg" "$fulldep^0"
1043 then
1044 rm "$_depcheck"
1045 save_state
1046 unset TG_RECURSIVE
1047 info "Please commit merge resolution and call \`$tgdisplayac update --continue\`"
1048 info "(use \`$tgdisplayac status\` to see more options)"
1049 exit 3
1051 done
1052 else
1053 info "The base is up-to-date."
1057 ## Second, update our head with the remote branch
1059 plusextra=
1060 merge_with="refs/$topbases/$_update_name"
1061 brmmode=
1062 if has_remote "$_update_name"; then
1063 _rname="refs/remotes/$base_remote/$_update_name"
1064 if branch_contains "refs/heads/$_update_name" "$_rname"; then
1065 info "The $_update_name head is up-to-date wrt. its remote branch."
1066 else
1067 stash_now_if_requested
1068 info "Reconciling $_update_name base with remote branch updates..."
1069 become_non_cacheable
1070 msg="tgupdate: merge ${_rname#refs/} onto $_update_name base"
1071 checkours=
1072 checktheirs=
1073 got_merge_with=
1074 brmmode="--merge"
1075 if [ -n "$mergeresult" ]; then
1076 checkours="$(git rev-parse --verify --quiet "refs/$topbases/$_update_name^0" --)" || :
1077 checktheirs="$(git rev-parse --verify --quiet "$_rname^0" --)" || :
1078 if [ "$mergeours" = "$checkours" ] && [ "$mergetheirs" = "$checktheirs" ]; then
1079 got_merge_with="$mergeresult"
1083 [ -z "$got_merge_with" ] &&
1084 ! v_attempt_index_merge $no_auto --theirs "merge_with" -m "$msg" "refs/$topbases/$_update_name" "$_rname^0" &&
1086 # *DETACH* our HEAD now!
1087 no_auto="--no-auto"
1088 git checkout -q --detach $iowopt "refs/$topbases/$_update_name" || die "git checkout failed" &&
1089 git_merge --theirs --name "$_update_name base content" --name "${_rname#refs/}" -m "$msg" "$_rname^0" &&
1090 merge_with="$(git rev-parse --verify HEAD --)"
1092 then
1093 save_state \
1094 "$(git rev-parse --verify --quiet "refs/$topbases/$_update_name^0" --)" \
1095 "$(git rev-parse --verify --quiet "$_rname^0" --)"
1096 unset TG_RECURSIVE
1097 info "Please commit merge resolution and call \`$tgdisplayac update --continue\`"
1098 info "(use \`$tgdisplayac status\` to see more options)"
1099 exit 3
1101 # Go back but remember we want to merge with this, not base
1102 [ -z "$got_merge_with" ] || merge_with="$got_merge_with"
1103 plusextra="${_rname#refs/} + "
1108 ## Third, update our head with the base
1110 if branch_contains "refs/heads/$_update_name" "$merge_with"; then
1111 info "The $_update_name head is up-to-date wrt. the base."
1112 return 0
1114 stash_now_if_requested
1115 info "Updating $_update_name against ${plusextra}new base..."
1116 become_non_cacheable
1117 msg="tgupdate: merge ${plusextra}$_update_name base into $_update_name"
1118 b4deps=
1119 if [ -n "$brmmode" ] && [ "$base_remote" ]; then
1120 b4deps="$(git rev-parse --verify --quiet "refs/heads/$_update_name:.topdeps" --)" && [ -n "$b4deps" ] ||
1121 b4deps="$(git hash-object -t blob -w --stdin </dev/null)"
1124 ! attempt_index_merge $no_auto $brmmode -m "$msg" "refs/heads/$_update_name" "$merge_with^0" &&
1126 # Home, sweet home...
1127 # (We want to always switch back, in case we were
1128 # on the base from failed previous merge.)
1129 git checkout -q $iowopt "$_update_name" || die "git checkout failed" &&
1130 git_merge $brmmode --name "$_update_name" --name "${plusextra}$topbases/$_update_name" -m "$msg" "$merge_with^0"
1132 then
1133 no_auto=
1134 merging_topfiles="${brmmode:+1}"
1135 save_state
1136 unset TG_RECURSIVE
1137 info "Please commit merge resolution and call \`$tgdisplayac update --continue\`"
1138 info "(use \`$tgdisplayac status\` to see more options)"
1139 exit 3
1142 # Fourth, auto create locally any newly depended on branches we got from the remote
1144 _result=0
1145 if [ -n "$b4deps" ] &&
1146 l8rdeps="$(git rev-parse --verify --quiet "refs/heads/$_update_name:.topdeps" --)" &&
1147 [ -n "$l8rdeps" ] && [ "$b4deps" != "$l8rdeps" ]
1148 then
1149 _olddeps=
1150 while read -r newdep; do
1151 if [ -n "$newdep" ]; then
1152 if auto_create_local_remote "$newdep"; then
1153 _result=75
1154 else
1155 if ref_exists "refs/heads/$newdep"; then
1156 # maybe the line just moved around
1157 [ -n "$_olddeps" ] && [ -f "$_olddeps" ] || {
1158 _olddeps="$(get_temp b4deps)" &&
1159 git cat-file blob "$b4deps" >"$_olddeps"
1161 if awk -v "newdep=$newdep" '$0 == newdep {exit 1}' <"$_olddeps"; then
1162 # nope, it's a new head already existing locally
1163 _result=75
1165 else
1166 # helpfully check to see if there's such a remote branch
1167 _rntgb=
1168 ! ref_exists "refs/remotes/$base_remote/$newdep" || _rntgb=1
1169 # maybe a blocking local orphan base too
1170 _blocked=
1171 if [ -n "$_rntgb" ] &&
1172 ref_exists "refs/remotes/$base_remote/${topbases#heads/}/$newdep" &&
1173 ref_exists "refs/$topbases/$newdep"
1174 then
1175 _blocked=1
1177 # spew the flexibly adjustable warning
1178 warn "-------------------------------------"
1179 warn "MISSING DEPENDENCY MERGED FROM REMOTE"
1180 warn "-------------------------------------"
1181 warn "Local Branch: $_update_name"
1182 warn " Remote Name: $base_remote"
1183 warn " Dependency: $newdep"
1184 if [ -n "$_blocked" ]; then
1185 warn "Blocking Ref: refs/$topbases/$newdep"
1186 elif [ -n "$_rntgb" ]; then
1187 warn "Existing Ref: refs/remotes/$base_remote/$newdep"
1189 warn ""
1190 if [ -n "$_blocked" ]; then
1191 warn "There is no local branch by that name, but"
1192 warn "there IS a remote TopGit branch available by"
1193 warn "that name, but creation of a local version has"
1194 warn "been blocked by existence of the ref shown above."
1195 elif [ -n "$_rntgb" ]; then
1196 warn "There is no local branch or remote TopGit"
1197 warn "branch available by that name, but there is an"
1198 warn "existing non-TopGit remote branch ref shown above."
1199 warn "Non-TopGit branches are not set up automatically"
1200 warn "by TopGit and must be maintained manually."
1201 else
1202 warn "There is no local branch or remote branch"
1203 warn "(TopGit or otherwise) available by that name."
1205 warn "-------------------------------------"
1209 done <<-EOT
1210 $(git diff --ignore-space-at-eol "$b4deps" "$l8rdeps" -- | diff_added_lines)
1213 return $_result
1216 update_branch() {
1217 _ubicode=0
1218 _maxdeploop=3
1219 update_branch_internal "$@" || _ubicode=$?
1220 while [ "$_maxdeploop" -gt 0 ] && [ "$_ubicode" = "75" ]; do
1221 _maxdeploop="$(( $maxdeploop - 1 ))"
1222 info "Updating $1 again with newly added dependencies..."
1223 _ubicode=0
1224 update_branch_internal "$@" || _ubicode=$?
1225 done
1226 return $_ubicode
1229 # We are "read-only" and cacheable until the first change
1230 tg_read_only=1
1231 v_create_ref_cache
1233 do_non_annihilated_branches_patterns() {
1234 while read -r _pat && [ -n "$_pat" ]; do
1235 set -- "$@" "$_pat"
1236 done
1237 non_annihilated_branches "$@"
1240 do_non_annihilated_branches() {
1241 if [ -z "$pattern" ]; then
1242 non_annihilated_branches
1243 else
1244 do_non_annihilated_branches_patterns <<-EOT
1245 $(sed 'y/ /\n/' <<-LIST
1246 $pattern
1247 LIST
1253 if [ -n "$all" ] && [ -z "$restored" ]; then
1254 names=
1255 while read name && [ -n "$name" ]; do
1256 case " $names " in *" $name "*);;*)
1257 names="${names:+$names }$name"
1258 esac
1259 done <<-EOT
1260 $(do_non_annihilated_branches)
1264 for name in $names; do
1265 case " $processed " in *" $name "*) continue; esac
1266 [ -z "$all" ] && case "$names" in *" "*) ! :; esac || info "Proccessing $name..."
1267 update_branch "$name" || exit
1268 processed="${processed:+$processed }$name"
1269 done
1271 [ -z "$all" ] && case "$names" in *" "*) ! :; esac ||
1272 info "Returning to ${current#refs/heads/}..."
1273 checkout_symref_full "$current"
1274 ! [ -f "$git_dir/TGMERGE_MSG" ] || [ -e "$git_dir/MERGE_MSG" ] ||
1275 mv -f "$git_dir/TGMERGE_MSG" "$git_dir/MERGE_MSG" || :
1276 ec=$?
1277 tmpdir_cleanup || :
1278 git gc --auto || :
1279 exit $ec