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>...])"
27 if [ "${1:-0}" != 0 ]; then
28 printf '%s\n' "$USAGE" >&2
30 printf '%s\n' "$USAGE"
39 [ -z "$names$pattern" ] || usage
1
52 if [ -z "$all" ]; then
53 names
="${names:+$names }$arg"
55 pattern
="${pattern:+$pattern }refs/$topbases/$(strip_ref "$arg")"
60 origpattern
="$pattern"
61 [ -z "$pattern" ] && pattern
="refs/$topbases"
63 current
="$(strip_ref "$
(git symbolic-ref
-q HEAD
)")" ||
:
64 [ -n "$all$names" ] || names
="HEAD"
65 if [ -z "$all" ]; then
68 while [ $# -gt 0 ]; do
69 name
="$(verify_topgit_branch "$1")"
70 case " $names " in *" $name "*);;*)
71 names
="${names:+$names }$name"
78 [ -n "$current" ] || die
"cannot return to detached HEAD; switch to another branch"
79 [ -n "$(git rev-parse --verify --quiet HEAD --)" ] ||
80 die
"cannot return to unborn branch; switch to another branch"
85 stash_now_if_requested
() {
86 [ -z "$TG_RECURSIVE" ] ||
return 0
87 ensure_ident_available
88 [ -n "$stash" ] ||
return 0
89 msg
="tgupdate: autostash before update"
90 if [ -n "$all" ]; then
91 msg
="$msg --all${origpattern:+ $origpattern}"
97 $tg tag
-q -q -m "$msg" --stash "$@" || die
"requested --stash failed"
105 TG_RECURSIVE
="[$1] $TG_RECURSIVE"
110 [ $_ret -eq 3 ] && exit 3
114 # If HEAD is a symref to "$1" detach it at its current value
115 detach_symref_head_on_branch
() {
116 _hsr
="$(git symbolic-ref -q HEAD --)" && [ -n "$_hsr" ] ||
return 0
117 _hrv
="$(git rev-parse --quiet --verify HEAD --)" && [ -n "$_hrv" ] ||
118 die
"cannot detach_symref_head_on_branch from unborn branch $_hsr"
119 git update-ref
--no-deref -m "detaching HEAD from $_hsr to safely update it" HEAD
"$_hrv"
122 # run git merge with the passed in arguments AND --no-stat
123 # return the exit status of git merge
124 # if the returned exit status is no error show a shortstat before
125 # returning assuming the merge was done into the previous HEAD
127 _oldhead
="$(git rev-parse --verify HEAD^0)"
129 git merge
$auhopt --no-stat "$@" || _ret
=$?
130 [ "$_ret" != "0" ] || git
--no-pager diff-tree
--shortstat "$_oldhead" HEAD^
0 --
134 # similar to git_merge but operates exclusively using a separate index and temp dir
135 # only trivial aggressive automatic (i.e. simple) merges are supported
137 # $1 => '' to discard result, 'refs/?*' to update the specified ref or a varname
138 # $2 => '-m' MUST be '-m'
139 # $3 => commit message AND, if $1 matches refs/?* the update-ref message
140 # $4 => commit-ish to merge as "ours"
141 # $5 => commit-ish to merge as "theirs"
142 # [$6...] => more commit-ishes to merge as "theirs" in octopus
144 # all merging is done in a separate index (or temporary files for simple merges)
145 # if successful the ref or var is updated with the result
146 # otherwise everything is left unchanged and a silent failure occurs
147 # if successful and $1 matches refs/?* it WILL BE UPDATED to a new commit using the
148 # message and appropriate parents AND HEAD WILL BE DETACHED first if it's a symref
150 # otherwise if $1 does not match refs/?* and is not empty the named variable will
151 # be set to contain the resulting commit from the merge
152 # the working tree and index ARE LEFT COMPLETELY UNTOUCHED no matter what
153 v_attempt_index_merge
() {
154 [ "$#" -ge 5 ] && [ "$2" = "-m" ] && [ -n "$3" ] && [ -n "$4" ] && [ -n "$5" ] ||
155 die
"programmer error: invalid arguments to v_attempt_index_merge: $*"
160 rh
="$(git rev-parse --quiet --verify "$_head^
0" --)" && [ -n "$rh" ] ||
return 1
168 if [ $# -gt 1 ]; then
169 ihl
="$(git merge-base --independent "$@
")" ||
return 1
171 [ $# -ge 1 ] && [ -n "$1" ] ||
return 1
173 [ $# -eq 1 ] || _octo
=1
174 mb
="$(git merge-base ${_octo:+--octopus} "$rh" "$@
")" && [ -n "$mb" ] ||
{
175 mb
="$(git hash-object -w -t tree --stdin < /dev/null)"
178 if [ -z "$_mt" ]; then
179 if [ -n "$_octo" ]; then
180 while [ $# -gt 1 ] && mbh
="$(git merge-base "$rh" "$1")" && [ -n "$mbh" ]; do
181 if [ "$rh" = "$mbh" ]; then
182 _mmsg
="Fast-forward (no commit created)"
185 elif [ "$1" = "$mbh" ]; then
191 if [ $# -eq 1 ]; then
193 mb
="$(git merge-base "$rh" "$1")" && [ -n "$mb" ] ||
return 1
196 if [ -z "$_octo" ]; then
197 r1
="$(git rev-parse --quiet --verify "$1^
0" --)" && [ -n "$r1" ] ||
return 1
199 if [ "$rh" = "$mb" ]; then
200 _mmsg
="Fast-forward (no commit created)"
203 elif [ "$r1" = "$mb" ]; then
204 [ -n "$_mmsg" ] || _mmsg
="Already up-to-date!"
211 if [ -z "$newc" ]; then
212 inew
="$tg_tmp_dir/index.$$"
213 ! [ -e "$inew" ] ||
rm -f "$inew"
214 itmp
="$tg_tmp_dir/output.$$"
215 imrg
="$tg_tmp_dir/auto.$$"
216 [ -z "$_octo" ] ||
>"$imrg"
221 if [ -n "$_parents" ]; then
222 if [ "$(git rev-list --count --max-count=1 "$1" --not "$_newrh" --)" = "0" ]; then
227 GIT_INDEX_FILE
="$inew" git read-tree
-m --aggressive -i "$mb" "$rh" "$1" ||
{ rm -f "$inew" "$imrg"; return 1; }
228 GIT_INDEX_FILE
="$inew" git ls-files
--unmerged --full-name --abbrev :/ >"$itmp" 2>&1 ||
{ rm -f "$inew" "$itmp" "$imrg"; return 1; }
229 ! [ -s "$itmp" ] ||
{
230 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
231 rm -f "$inew" "$itmp" "$imrg"
234 if [ -s "$itmp" ]; then
235 if [ -n "$_octo" ]; then
236 cat "$itmp" >>"$imrg"
244 newt
="$(GIT_INDEX_FILE="$inew" git write-tree)" && [ -n "$newt" ] ||
{ rm -f "$inew" "$imrg"; return 1; }
245 _parents
="${_parents:+$_parents }-p $1"
246 if [ $# -gt 1 ]; then
253 [ -z "$_octo" ] || LC_ALL
=C
sort -u <"$imrg"
254 rm -f "$inew" "$imrg"
255 newc
="$(git commit-tree -p "$orh" $_parents -m "$_msg" "$newt")" && [ -n "$newc" ] ||
return 1
256 _mmsg
="Merge made by the 'trivial aggressive$_auto${_octo:+ octopus}' strategy."
260 if [ -n "$_same" ]; then
262 if rv
="$(git rev-parse --quiet --verify "$_var" --)" && [ "$rv" = "$newc" ]; then
266 if [ -z "$_same" ]; then
267 detach_symref_head_on_branch
"$_var" ||
return 1
268 # git update-ref returns 0 even on failure :(
269 git update-ref
-m "$_msg" "$_var" "$newc" ||
return 1
273 eval "$_var="'"$newc"'
277 [ -n "$_nodt" ] || git
--no-pager diff-tree
--shortstat "$orh" "$newc" --
281 # shortcut that passes $3 as a preceding argument (which must match refs/?*)
282 attempt_index_merge
() {
283 case "$3" in refs
/?
*);;*)
284 die
"programmer error: invalid arguments to attempt_index_merge: $*"
286 v_attempt_index_merge
"$3" "$@"
291 [ -n "$1" ] ||
return 0
293 [ "$1" != "$on_base" ] ||
294 [ "$(git symbolic-ref -q HEAD)" != "refs/$topbases/$1" ]
302 # We are cacheable until the first change
306 ## First, take care of our base
308 _depcheck
="$(get_temp tg-depcheck)"
310 needs_update
"$_update_name" >"$_depcheck" ||
:
311 if [ -n "$missing_deps" ]; then
312 msg
="Some dependencies are missing: $missing_deps"
313 if [ -n "$skipms" ]; then
314 info
"$msg; skipping"
315 elif [ -z "$all" ]; then
318 info
"$msg; skipping branch $_update_name"
322 if [ -s "$_depcheck" ]; then
324 sed 's/ [^ ]* *$//' |
# last is $_update_name
325 sed 's/.* \([^ ]*\)$/+\1/' |
# only immediate dependencies
326 sed 's/^\([^+]\)/-\1/' |
# now each line is +branch or -branch (+ == recurse)
327 >"$_depcheck.ideps" \
328 uniq -s 1 # fold branch lines; + always comes before - and thus wins within uniq
330 stash_now_if_requested
332 while read -r depline
; do
334 action
="${depline%$dep}"
336 # We do not distinguish between dependencies out-of-date
337 # and base/remote out-of-date cases for $dep here,
338 # but thanks to needs_update returning : or refs/remotes/<remote>/<name>
339 # for the latter, we do correctly recurse here
342 if [ x
"$action" = x
+ ]; then
343 case " $missing_deps " in *" $dep "*)
344 info
"Skipping recursing to missing dependency: $dep"
347 info
"Recursing to $dep..."
348 while ! recursive_update
"$dep"; do
349 # The merge got stuck! Let the user fix it up.
350 info
"You are in a subshell. If you abort the merge,"
351 info
"use \`exit 1\` to abort the recursive update altogether."
352 info
"Use \`exit 2\` to skip updating this branch and continue."
353 if "${SHELL:-@SHELL_PATH@}" -i </dev
/tty
; then
354 # assume user fixed it
355 # we could be left on a detached HEAD if we were resolving
356 # a conflict while merging a base in, fix it with a checkout
357 git checkout
-q "$(strip_ref "$dep")"
361 if [ $ret -eq 2 ]; then
362 info
"Ok, I will try to continue without updating this branch."
365 info
"Ok, you aborted the merge. Now, you just need to"
366 info
"switch back to some sane branch using \`git$gitcdopt checkout\`."
372 done <"$_depcheck.ideps"
374 # Create a list of all the fully qualified ref names that need
375 # to be merged into $_update_name's base. This will be done
376 # as an octopus merge if there are no conflicts.
380 while read -r dep
; do
387 d
="${dep#refs/heads/}"
388 deplist
="${deplist:+$deplist }$d"
389 deplines
="$deplines$d$lf"
393 deplist
="${deplist:+$deplist }$d"
394 deplines
="$deplines$d$lf"
399 set -- "$@" "refs/heads/$dep"
400 deplist
="${deplist:+$deplist }$dep"
401 deplines
="$deplines$dep$lf"
404 done <"$_depcheck.ideps"
406 # Make sure we end up on the correct base branch
408 if [ $# -ge 2 ]; then
409 info
"Updating $_update_name base with deps: $deplist"
411 msg
="tgupdate: octopus merge $# deps into $topbases/$_update_name$lf$lf$deplines"
412 if attempt_index_merge
-m "$msg" "refs/$topbases/$_update_name" "$@"; then
415 info
"Octopus merge failed; falling back to multiple 3-way merges"
419 for fulldep
in "$@"; do
420 # This will be either a proper topic branch
421 # or a remote base. (branch_needs_update() is called
422 # only on the _dependencies_, not our branch itself!)
426 dep
="${fulldep#refs/heads/}";;
428 dep
="${fulldep#refs/}";;
430 dep
="$fulldep";; # this should be a programmer error
433 info
"Updating $_update_name base with $dep changes..."
435 msg
="tgupdate: merge $dep into $topbases/$_update_name"
437 ! attempt_index_merge
-m "$msg" "refs/$topbases/$_update_name" "$fulldep^0" &&
439 # We need to switch to the base branch
440 # ...but only if we aren't there yet (from failed previous merge)
441 do_base_switch
"$_update_name" || die
"do_base_switch failed" &&
442 git_merge
-m "$msg" "$fulldep^0"
445 if [ -z "$TG_RECURSIVE" ]; then
446 resume
="\`$tgdisplay update${skipms:+ --skip-missing} $_update_name\` again"
450 info
"Please commit merge resolution and call $resume."
451 info
"It is also safe to abort this operation using \`git$gitcdopt reset --hard\`,"
452 info
"but please remember that you are on the base branch now;"
453 info
"you will want to switch to some normal branch afterwards."
459 info
"The base is up-to-date."
463 ## Second, update our head with the remote branch
466 merge_with
="refs/$topbases/$_update_name"
467 if has_remote
"$_update_name"; then
468 _rname
="refs/remotes/$base_remote/$_update_name"
469 if branch_contains
"refs/heads/$_update_name" "$_rname"; then
470 info
"The $_update_name head is up-to-date wrt. its remote branch."
472 stash_now_if_requested
473 info
"Reconciling $_update_name base with remote branch updates..."
475 msg
="tgupdate: merge ${_rname#refs/} onto $topbases/$_update_name"
477 ! v_attempt_index_merge
"merge_with" -m "$msg" "refs/$topbases/$_update_name" "$_rname^0" &&
479 # *DETACH* our HEAD now!
480 git checkout
-q --detach "refs/$topbases/$_update_name" || die
"git checkout failed" &&
481 git_merge
-m "$msg" "$_rname^0" &&
482 merge_with
="$(git rev-parse --verify HEAD --)"
485 info
"Oops, you will need to help me out here a bit."
486 info
"Please commit merge resolution and call:"
487 info
"git$gitcdopt checkout $_update_name && git$gitcdopt merge <commitid>"
488 info
"It is also safe to abort this operation using: git$gitcdopt reset --hard $_update_name"
491 # Go back but remember we want to merge with this, not base
492 plusextra
="${_rname#refs/}+"
497 ## Third, update our head with the base
499 if branch_contains
"refs/heads/$_update_name" "$merge_with"; then
500 info
"The $_update_name head is up-to-date wrt. the base."
503 stash_now_if_requested
504 info
"Updating $_update_name against new base..."
506 msg
="tgupdate: merge ${plusextra}$topbases/$_update_name into $_update_name"
508 ! attempt_index_merge
-m "$msg" "refs/heads/$_update_name" "$merge_with^0" &&
510 # Home, sweet home...
511 # (We want to always switch back, in case we were
512 # on the base from failed previous merge.)
513 git checkout
-q "$_update_name" || die
"git checkout failed" &&
514 git_merge
-m "$msg" "$merge_with^0"
517 if [ -z "$TG_RECURSIVE" ]; then
518 info
"Please commit merge resolution. No need to do anything else"
519 info
"You can abort this operation using \`git$gitcdopt reset --hard\` now"
520 info
"and retry this merge later using \`$tgdisplay update${skipms:+ --skip-missing}\`."
522 info
"Please commit merge resolution and call exit."
523 info
"You can abort this operation using \`git$gitcdopt reset --hard\`."
529 # We are "read-only" and cacheable until the first change
533 if [ -z "$all" ]; then
534 for name
in $names; do
535 update_branch
"$name" ||
exit
537 case "$names" in *" "*)
538 info
"Returning to $current..."
540 git checkout
-q "$current"
544 do_non_annihilated_branches_patterns
() {
545 while read -r _pat
&& [ -n "$_pat" ]; do
548 non_annihilated_branches
"$@"
551 do_non_annihilated_branches
() {
552 if [ -z "$pattern" ]; then
553 non_annihilated_branches
555 do_non_annihilated_branches_patterns
<<-EOT
556 $(sed 'y/ /\n/' <<-LIST
564 while read name
&& [ -n "$name" ]; do
565 info
"Proccessing $name..."
566 update_branch
"$name" ||
exit
568 $(do_non_annihilated_branches)
571 info
"Returning to $current..."
572 git checkout
-q "$current"