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
8 names
= # Branch(es) to update
9 all
= # Update all branches
10 pattern
= # Branch selection filter for -a
11 current
= # Branch we are currently on
12 skipms
= # skip missing dependencies
13 stash
= # tgstash refs before changes
15 if [ "$(git config --get --bool topgit.autostash 2>/dev/null)" != "false" ]; then
16 # topgit.autostash is true
23 Usage: ${tgname:-tg} [...] update [--[no-]stash] [--skip-missing] ([<name>...] | -a [<pattern>...])
24 Or: ${tgname:-tg} --continue | -skip | --stop | --abort"
28 if [ "${1:-0}" != 0 ]; then
29 printf '%s\n' "$USAGE" >&2
31 printf '%s\n' "$USAGE"
36 state_dir
="$git_dir/tg-update"
43 [ -d "$state_dir" ] ||
return 1
44 [ -s "$state_dir/fullcmd" ] ||
return 1
45 [ -f "$state_dir/remote" ] ||
return 1
46 [ -f "$state_dir/skipms" ] ||
return 1
47 [ -f "$state_dir/all" ] ||
return 1
48 [ -s "$state_dir/current" ] ||
return 1
49 [ -s "$state_dir/stashhash" ] ||
return 1
50 [ -s "$state_dir/name" ] ||
return 1
51 [ -s "$state_dir/names" ] ||
return 1
52 [ -f "$state_dir/processed" ] ||
return 1
53 [ -f "$state_dir/mergeours" ] ||
return 1
54 [ -f "$state_dir/mergeours" ] ||
return 1
55 if [ -s "$state_dir/mergeours" ]; then
56 [ -s "$state_dir/mergetheirs" ] ||
return 1
58 ! [ -s "$state_dir/mergetheirs" ] ||
return 1
63 is_active || die
"programmer error"
64 IFS
= read -r fullcmd
<"$state_dir/fullcmd" && [ -n "$fullcmd" ]
65 IFS
= read -r base_remote
<"$state_dir/remote" ||
:
66 IFS
= read -r skipms
<"$state_dir/skipms" ||
:
67 IFS
= read -r all
<"$state_dir/all" ||
:
68 IFS
= read -r current
<"$state_dir/current" && [ -n "$current" ]
69 IFS
= read -r stashhash
<"$state_dir/stashhash" && [ -n "$stashhash" ]
70 IFS
= read -r name
<"$state_dir/name" && [ -n "$name" ]
71 IFS
= read -r names
<"$state_dir/names" && [ -n "$names" ]
72 IFS
= read -r processed
<"$state_dir/processed" ||
:
73 IFS
= read -r mergeours
<"$state_dir/mergeours" ||
:
74 IFS
= read -r mergetheirs
<"$state_dir/mergetheirs" ||
:
75 if [ -n "$mergeours" ] && [ -n "$mergetheirs" ]; then
76 headhash
="$(git rev-parse --quiet --verify HEAD --)" ||
:
77 if [ -n "$headhash" ]; then
78 parents
="$(git --no-pager log -n 1 --format='format:%P' "$headhash" -- 2>/dev/null)" ||
:
79 if [ "$parents" = "$mergeours $mergetheirs" ]; then
80 mergeresult
="$headhash"
83 if [ -z "$mergeresult" ]; then
92 ! [ -e "$state_dir" ] ||
rm -rf "$state_dir" >/dev
/null
2>&1 ||
:
97 ! is_active || isactive
=1
98 if [ -n "$isactive" ] ||
[ $# -eq 1 -a x
"$1" = x
"--abort" ]; then
99 [ $# -eq 1 ] && [ x
"$1" != x
"--status" ] ||
{ do_status
; exit 0; }
104 if [ -n "$isactive" ]; then
105 IFS
= read -r current
<"$state_dir/current" ||
:
106 IFS
= read -r stashhash
<"$state_dir/stashhash" ||
:
109 if [ -n "$isactive" ]; then
110 if [ -n "$stashhash" ]; then
111 $tg revert
-f -q -q --no-stash "$stashhash" >/dev
/null
2>&1 ||
:
113 if [ -n "$current" ]; then
114 info
"Ok, update aborted, returning to $current"
115 git checkout
-f -q "$current"
117 info
"Ok, update aborted. Now, you just need to"
118 info
"switch back to some sane branch using \`git$gitcdopt checkout\`."
121 info
"No update was active"
127 info
"Ok, update stopped. Now, you just need to"
128 info
"switch back to some sane branch using \`git$gitcdopt checkout\`."
133 if [ "$1" = "--skip" ]; then
134 info
"Ok, I will try to continue without updating this branch."
136 case " $processed " in *" $name "*);;*)
137 processed
="${processed:+$processed }$name"
140 # assume user fixed it
141 # we could be left on a detached HEAD if we were resolving
142 # a conflict while merging a base in, fix it with a checkout
143 git checkout
-q "$(strip_ref "$name")"
152 if [ -z "$restored" ]; then
153 while [ -n "$1" ]; do
157 [ -z "$names$pattern" ] || usage
1
170 if [ -z "$all" ]; then
171 names
="${names:+$names }$arg"
173 pattern
="${pattern:+$pattern }refs/$topbases/$(strip_ref "$arg")"
178 origpattern
="$pattern"
179 [ -z "$pattern" ] && pattern
="refs/$topbases"
182 current
="$(strip_ref "$
(git symbolic-ref
-q HEAD
)")" ||
:
183 [ -n "$all$names" ] || names
="HEAD"
184 if [ -z "$all" ]; then
187 while [ $# -gt 0 ]; do
188 name
="$(verify_topgit_branch "$1")"
189 case " $names " in *" $name "*);;*)
190 names
="${names:+$names }$name"
197 [ -n "$current" ] || die
"cannot return to detached HEAD; switch to another branch"
198 [ -n "$(git rev-parse --verify --quiet HEAD --)" ] ||
199 die
"cannot return to unborn branch; switch to another branch"
205 mkdir
-p "$state_dir"
206 printf '%s\n' "$fullcmd" >"$state_dir/fullcmd"
207 printf '%s\n' "$base_remote" >"$state_dir/remote"
208 printf '%s\n' "$skipms" >"$state_dir/skipms"
209 printf '%s\n' "$all" >"$state_dir/all"
210 printf '%s\n' "$current" >"$state_dir/current"
211 printf '%s\n' "$stashhash" >"$state_dir/stashhash"
212 printf '%s\n' "$name" >"$state_dir/name"
213 printf '%s\n' "$names" >"$state_dir/names"
214 printf '%s\n' "$processed" >"$state_dir/processed"
215 printf '%s\n' "$1" >"$state_dir/mergeours"
216 printf '%s\n' "$2" >"$state_dir/mergetheirs"
219 stash_now_if_requested
() {
220 [ -z "$TG_RECURSIVE" ] ||
return 0
221 [ -z "$stashhash" ] ||
return 0
222 ensure_ident_available
223 msg
="tgupdate: autostash before update"
224 if [ -n "$all" ]; then
225 msg
="$msg --all${origpattern:+ $origpattern}"
230 if [ -n "$stash" ]; then
231 $tg tag
-q -q -m "$msg" --stash "$@" &&
232 stashhash
="$(git rev-parse --quiet --verify refs/tgstash --)" &&
233 [ -n "$stashhash" ] &&
234 [ "$(git cat-file -t "$stashhash" -- 2>/dev/null)" = "tag" ] ||
235 die
"requested --stash failed"
237 $tg tag
--anonymous "$@" &&
238 stashhash
="$(git rev-parse --quiet --verify TG_STASH --)" &&
239 [ -n "$stashhash" ] &&
240 [ "$(git cat-file -t "$stashhash" -- 2>/dev/null)" = "tag" ] ||
241 die
"anonymous --stash failed"
249 TG_RECURSIVE
="[$1] $TG_RECURSIVE"
254 [ $_ret -eq 3 ] && exit 3
258 # If HEAD is a symref to "$1" detach it at its current value
259 detach_symref_head_on_branch
() {
260 _hsr
="$(git symbolic-ref -q HEAD --)" && [ -n "$_hsr" ] ||
return 0
261 _hrv
="$(git rev-parse --quiet --verify HEAD --)" && [ -n "$_hrv" ] ||
262 die
"cannot detach_symref_head_on_branch from unborn branch $_hsr"
263 git update-ref
--no-deref -m "detaching HEAD from $_hsr to safely update it" HEAD
"$_hrv"
266 # run git merge with the passed in arguments AND --no-stat
267 # return the exit status of git merge
268 # if the returned exit status is no error show a shortstat before
269 # returning assuming the merge was done into the previous HEAD
271 _oldhead
="$(git rev-parse --verify HEAD^0)"
273 git merge
$auhopt --no-stat "$@" || _ret
=$?
274 [ "$_ret" != "0" ] || git
--no-pager diff-tree
--shortstat "$_oldhead" HEAD^
0 --
278 # similar to git_merge but operates exclusively using a separate index and temp dir
279 # only trivial aggressive automatic (i.e. simple) merges are supported
281 # $1 => '' to discard result, 'refs/?*' to update the specified ref or a varname
282 # $2 => '-m' MUST be '-m'
283 # $3 => commit message AND, if $1 matches refs/?* the update-ref message
284 # $4 => commit-ish to merge as "ours"
285 # $5 => commit-ish to merge as "theirs"
286 # [$6...] => more commit-ishes to merge as "theirs" in octopus
288 # all merging is done in a separate index (or temporary files for simple merges)
289 # if successful the ref or var is updated with the result
290 # otherwise everything is left unchanged and a silent failure occurs
291 # if successful and $1 matches refs/?* it WILL BE UPDATED to a new commit using the
292 # message and appropriate parents AND HEAD WILL BE DETACHED first if it's a symref
294 # otherwise if $1 does not match refs/?* and is not empty the named variable will
295 # be set to contain the resulting commit from the merge
296 # the working tree and index ARE LEFT COMPLETELY UNTOUCHED no matter what
297 v_attempt_index_merge
() {
298 [ "$#" -ge 5 ] && [ "$2" = "-m" ] && [ -n "$3" ] && [ -n "$4" ] && [ -n "$5" ] ||
299 die
"programmer error: invalid arguments to v_attempt_index_merge: $*"
304 rh
="$(git rev-parse --quiet --verify "$_head^
0" --)" && [ -n "$rh" ] ||
return 1
312 if [ $# -gt 1 ]; then
313 ihl
="$(git merge-base --independent "$@
")" ||
return 1
315 [ $# -ge 1 ] && [ -n "$1" ] ||
return 1
317 [ $# -eq 1 ] || _octo
=1
318 mb
="$(git merge-base ${_octo:+--octopus} "$rh" "$@
")" && [ -n "$mb" ] ||
{
319 mb
="$(git hash-object -w -t tree --stdin < /dev/null)"
322 if [ -z "$_mt" ]; then
323 if [ -n "$_octo" ]; then
324 while [ $# -gt 1 ] && mbh
="$(git merge-base "$rh" "$1")" && [ -n "$mbh" ]; do
325 if [ "$rh" = "$mbh" ]; then
326 _mmsg
="Fast-forward (no commit created)"
329 elif [ "$1" = "$mbh" ]; then
335 if [ $# -eq 1 ]; then
337 mb
="$(git merge-base "$rh" "$1")" && [ -n "$mb" ] ||
return 1
340 if [ -z "$_octo" ]; then
341 r1
="$(git rev-parse --quiet --verify "$1^
0" --)" && [ -n "$r1" ] ||
return 1
343 if [ "$rh" = "$mb" ]; then
344 _mmsg
="Fast-forward (no commit created)"
347 elif [ "$r1" = "$mb" ]; then
348 [ -n "$_mmsg" ] || _mmsg
="Already up-to-date!"
355 if [ -z "$newc" ]; then
356 inew
="$tg_tmp_dir/index.$$"
357 ! [ -e "$inew" ] ||
rm -f "$inew"
358 itmp
="$tg_tmp_dir/output.$$"
359 imrg
="$tg_tmp_dir/auto.$$"
360 [ -z "$_octo" ] ||
>"$imrg"
365 if [ -n "$_parents" ]; then
366 if [ "$(git rev-list --count --max-count=1 "$1" --not "$_newrh" --)" = "0" ]; then
371 GIT_INDEX_FILE
="$inew" git read-tree
-m --aggressive -i "$mb" "$rh" "$1" ||
{ rm -f "$inew" "$imrg"; return 1; }
372 GIT_INDEX_FILE
="$inew" git ls-files
--unmerged --full-name --abbrev :/ >"$itmp" 2>&1 ||
{ rm -f "$inew" "$itmp" "$imrg"; return 1; }
373 ! [ -s "$itmp" ] ||
{
374 if ! GIT_INDEX_FILE
="$inew" TG_TMP_DIR
="$tg_tmp_dir" git merge-index
-q "$TG_INST_CMDDIR/tg--index-merge-one-file" -a >"$itmp" 2>&1; then
375 rm -f "$inew" "$itmp" "$imrg"
378 if [ -s "$itmp" ]; then
379 if [ -n "$_octo" ]; then
380 cat "$itmp" >>"$imrg"
388 newt
="$(GIT_INDEX_FILE="$inew" git write-tree)" && [ -n "$newt" ] ||
{ rm -f "$inew" "$imrg"; return 1; }
389 _parents
="${_parents:+$_parents }-p $1"
390 if [ $# -gt 1 ]; then
397 [ -z "$_octo" ] || LC_ALL
=C
sort -u <"$imrg"
398 rm -f "$inew" "$imrg"
399 newc
="$(git commit-tree -p "$orh" $_parents -m "$_msg" "$newt")" && [ -n "$newc" ] ||
return 1
400 _mmsg
="Merge made by the 'trivial aggressive$_auto${_octo:+ octopus}' strategy."
404 if [ -n "$_same" ]; then
406 if rv
="$(git rev-parse --quiet --verify "$_var" --)" && [ "$rv" = "$newc" ]; then
410 if [ -z "$_same" ]; then
411 detach_symref_head_on_branch
"$_var" ||
return 1
412 # git update-ref returns 0 even on failure :(
413 git update-ref
-m "$_msg" "$_var" "$newc" ||
return 1
417 eval "$_var="'"$newc"'
421 [ -n "$_nodt" ] || git
--no-pager diff-tree
--shortstat "$orh" "$newc" --
425 # shortcut that passes $3 as a preceding argument (which must match refs/?*)
426 attempt_index_merge
() {
427 case "$3" in refs
/?
*);;*)
428 die
"programmer error: invalid arguments to attempt_index_merge: $*"
430 v_attempt_index_merge
"$3" "$@"
435 [ -n "$1" ] ||
return 0
437 [ "$1" != "$on_base" ] ||
438 [ "$(git symbolic-ref -q HEAD)" != "refs/$topbases/$1" ]
446 # We are cacheable until the first change
450 ## First, take care of our base
452 _depcheck
="$(get_temp tg-depcheck)"
454 needs_update
"$_update_name" >"$_depcheck" ||
:
455 if [ -n "$missing_deps" ]; then
456 msg
="Some dependencies are missing: $missing_deps"
457 if [ -n "$skipms" ]; then
458 info
"$msg; skipping"
459 elif [ -z "$all" ]; then
462 info
"$msg; skipping branch $_update_name"
466 if [ -s "$_depcheck" ]; then
468 sed 's/ [^ ]* *$//' |
# last is $_update_name
469 sed 's/.* \([^ ]*\)$/+\1/' |
# only immediate dependencies
470 sed 's/^\([^+]\)/-\1/' |
# now each line is +branch or -branch (+ == recurse)
471 >"$_depcheck.ideps" \
472 uniq -s 1 # fold branch lines; + always comes before - and thus wins within uniq
474 stash_now_if_requested
476 while read -r depline
; do
478 action
="${depline%$dep}"
480 # We do not distinguish between dependencies out-of-date
481 # and base/remote out-of-date cases for $dep here,
482 # but thanks to needs_update returning : or refs/remotes/<remote>/<name>
483 # for the latter, we do correctly recurse here
486 if [ x
"$action" = x
+ ]; then
487 case " $missing_deps " in *" $dep "*)
488 info
"Skipping recursing to missing dependency: $dep"
491 info
"Recursing to $dep..."
492 recursive_update
"$dep" ||
exit 3
494 done <"$_depcheck.ideps"
496 # Create a list of all the fully qualified ref names that need
497 # to be merged into $_update_name's base. This will be done
498 # as an octopus merge if there are no conflicts.
502 while read -r dep
; do
509 d
="${dep#refs/heads/}"
510 deplist
="${deplist:+$deplist }$d"
511 deplines
="$deplines$d$lf"
515 deplist
="${deplist:+$deplist }$d"
516 deplines
="$deplines$d$lf"
521 set -- "$@" "refs/heads/$dep"
522 deplist
="${deplist:+$deplist }$dep"
523 deplines
="$deplines$dep$lf"
526 done <"$_depcheck.ideps"
528 # Make sure we end up on the correct base branch
530 if [ $# -ge 2 ]; then
531 info
"Updating $_update_name base with deps: $deplist"
533 msg
="tgupdate: octopus merge $# deps into $topbases/$_update_name$lf$lf$deplines"
534 if attempt_index_merge
-m "$msg" "refs/$topbases/$_update_name" "$@"; then
537 info
"Octopus merge failed; falling back to multiple 3-way merges"
541 for fulldep
in "$@"; do
542 # This will be either a proper topic branch
543 # or a remote base. (branch_needs_update() is called
544 # only on the _dependencies_, not our branch itself!)
548 dep
="${fulldep#refs/heads/}";;
550 dep
="${fulldep#refs/}";;
552 dep
="$fulldep";; # this should be a programmer error
555 info
"Updating $_update_name base with $dep changes..."
557 msg
="tgupdate: merge $dep into $topbases/$_update_name"
559 ! attempt_index_merge
-m "$msg" "refs/$topbases/$_update_name" "$fulldep^0" &&
561 # We need to switch to the base branch
562 # ...but only if we aren't there yet (from failed previous merge)
563 do_base_switch
"$_update_name" || die
"do_base_switch failed" &&
564 git_merge
-m "$msg" "$fulldep^0"
569 info
"Please commit merge resolution and call \`$tgdisplay update --continue\`"
570 info
"(use \`$tgdisplay status\` to see more options)"
575 info
"The base is up-to-date."
579 ## Second, update our head with the remote branch
582 merge_with
="refs/$topbases/$_update_name"
583 if has_remote
"$_update_name"; then
584 _rname
="refs/remotes/$base_remote/$_update_name"
585 if branch_contains
"refs/heads/$_update_name" "$_rname"; then
586 info
"The $_update_name head is up-to-date wrt. its remote branch."
588 stash_now_if_requested
589 info
"Reconciling $_update_name base with remote branch updates..."
591 msg
="tgupdate: merge ${_rname#refs/} onto $topbases/$_update_name"
595 if [ -n "$mergeresult" ]; then
596 checkours
="$(git rev-parse --verify --quiet "refs
/$topbases/$_update_name^
0" --)" ||
:
597 checktheirs
="$(git rev-parse --verify --quiet "$_rname^
0" --)" ||
:
598 if [ "$mergeours" = "$checkours" ] && [ "$mergetheirs" = "$checktheirs" ]; then
599 got_merge_with
="$mergeresult"
603 [ -z "$got_merge_with" ] &&
604 ! v_attempt_index_merge
"merge_with" -m "$msg" "refs/$topbases/$_update_name" "$_rname^0" &&
606 # *DETACH* our HEAD now!
607 git checkout
-q --detach "refs/$topbases/$_update_name" || die
"git checkout failed" &&
608 git_merge
-m "$msg" "$_rname^0" &&
609 merge_with
="$(git rev-parse --verify HEAD --)"
613 "$(git rev-parse --verify --quiet "refs
/$topbases/$_update_name^
0" --)" \
614 "$(git rev-parse --verify --quiet "$_rname^
0" --)"
615 info
"Please commit merge resolution and call \`$tgdisplay update --continue\`"
616 info
"(use \`$tgdisplay status\` to see more options)"
619 # Go back but remember we want to merge with this, not base
620 [ -z "$got_merge_with" ] || merge_with
="$got_merge_with"
621 plusextra
="${_rname#refs/}+"
626 ## Third, update our head with the base
628 if branch_contains
"refs/heads/$_update_name" "$merge_with"; then
629 info
"The $_update_name head is up-to-date wrt. the base."
632 stash_now_if_requested
633 info
"Updating $_update_name against new base..."
635 msg
="tgupdate: merge ${plusextra}$topbases/$_update_name into $_update_name"
637 ! attempt_index_merge
-m "$msg" "refs/heads/$_update_name" "$merge_with^0" &&
639 # Home, sweet home...
640 # (We want to always switch back, in case we were
641 # on the base from failed previous merge.)
642 git checkout
-q "$_update_name" || die
"git checkout failed" &&
643 git_merge
-m "$msg" "$merge_with^0"
647 info
"Please commit merge resolution and call \`$tgdisplay update --continue\`"
648 info
"(use \`$tgdisplay status\` to see more options)"
653 # We are "read-only" and cacheable until the first change
657 do_non_annihilated_branches_patterns
() {
658 while read -r _pat
&& [ -n "$_pat" ]; do
661 non_annihilated_branches
"$@"
664 do_non_annihilated_branches
() {
665 if [ -z "$pattern" ]; then
666 non_annihilated_branches
668 do_non_annihilated_branches_patterns
<<-EOT
669 $(sed 'y/ /\n/' <<-LIST
677 if [ -n "$all" ] && [ -z "$restored" ]; then
679 while read name
&& [ -n "$name" ]; do
680 case " $names " in *" $name "*);;*)
681 names
="${names:+$names }$name"
684 $(do_non_annihilated_branches)
688 for name
in $names; do
689 case " $processed " in *" $name "*) continue; esac
690 [ -z "$all" ] && case "$names" in *" "*) ! :; esac || info
"Proccessing $name..."
691 update_branch
"$name" ||
exit
692 processed
="${processed:+$processed }$name"
695 [ -z "$all" ] && case "$names" in *" "*) ! :; esac ||
696 info
"Returning to $current..."
697 git checkout
-q "$current"