tg-update.sh: rename --skip to --skip-missing
[topgit/pro.git] / tg-update.sh
blobde35c5d46974103f46fdd32588d3eb6723be02d1
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 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
17 stash=1
20 ## Parse options
22 USAGE="\
23 Usage: ${tgname:-tg} [...] update [--[no-]stash] [--skip-missing] ([<name>...] | -a [<pattern>...])"
25 usage()
27 if [ "${1:-0}" != 0 ]; then
28 printf '%s\n' "$USAGE" >&2
29 else
30 printf '%s\n' "$USAGE"
32 exit ${1:-0}
35 while [ -n "$1" ]; do
36 arg="$1"; shift
37 case "$arg" in
38 -a|--all)
39 [ -z "$names$pattern" ] || usage 1
40 all=1;;
41 --skip-missing)
42 skipms=1;;
43 --stash)
44 stash=1;;
45 --no-stash)
46 stash=;;
47 -h)
48 usage;;
49 -*)
50 usage 1;;
52 if [ -z "$all" ]; then
53 names="${names:+$names }$arg"
54 else
55 pattern="${pattern:+$pattern }refs/$topbases/$(strip_ref "$arg")"
58 esac
59 done
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
66 clean_names() {
67 names=
68 while [ $# -gt 0 ]; do
69 name="$(verify_topgit_branch "$1")"
70 case " $names " in *" $name "*);;*)
71 names="${names:+$names }$name"
72 esac
73 shift
74 done
76 clean_names $names
77 else
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"
83 ensure_clean_tree
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}"
92 set -- "--all"
93 else
94 msg="$msg $names"
95 set -- $names
97 $tg tag -q -q -m "$msg" --stash "$@" || die "requested --stash failed"
98 stash=
101 recursive_update() {
102 _ret=0
103 on_base=
105 TG_RECURSIVE="[$1] $TG_RECURSIVE"
106 PS1="[$1] $PS1"
107 export PS1
108 update_branch "$1"
109 ) || _ret=$?
110 [ $_ret -eq 3 ] && exit 3
111 return $_ret
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
126 git_merge() {
127 _oldhead="$(git rev-parse --verify HEAD^0)"
128 _ret=0
129 git merge $auhopt --no-stat "$@" || _ret=$?
130 [ "$_ret" != "0" ] || git --no-pager diff-tree --shortstat "$_oldhead" HEAD^0 --
131 return $_ret
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
149 # to the same ref
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: $*"
156 _var="$1"
157 _msg="$3"
158 _head="$4"
159 shift 4
160 rh="$(git rev-parse --quiet --verify "$_head^0" --)" && [ -n "$rh" ] || return 1
161 orh="$rh"
162 _mmsg=
163 newc=
164 _nodt=
165 _same=
166 _mt=
167 _octo=
168 if [ $# -gt 1 ]; then
169 ihl="$(git merge-base --independent "$@")" || return 1
170 set -- $ihl
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)"
176 _mt=1
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)"
183 rh="$1"
184 shift
185 elif [ "$1" = "$mbh" ]; then
186 shift
187 else
188 break;
190 done
191 if [ $# -eq 1 ]; then
192 _octo=
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
198 set -- "$r1"
199 if [ "$rh" = "$mb" ]; then
200 _mmsg="Fast-forward (no commit created)"
201 newc="$r1"
202 _nodt=1
203 elif [ "$r1" = "$mb" ]; then
204 [ -n "$_mmsg" ] || _mmsg="Already up-to-date!"
205 newc="$rh"
206 _nodt=1
207 _same=1
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"
217 _auto=
218 _parents=
219 _newrh="$rh"
220 while :; do
221 if [ -n "$_parents" ]; then
222 if [ "$(git rev-list --count --max-count=1 "$1" --not "$_newrh" --)" = "0" ]; then
223 shift
224 continue
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"
232 return 1
234 if [ -s "$itmp" ]; then
235 if [ -n "$_octo" ]; then
236 cat "$itmp" >>"$imrg"
237 else
238 cat "$itmp"
240 _auto=" automatic"
243 rm -f "$itmp"
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
247 rh="$newt"
248 shift
249 continue
251 break;
252 done
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."
258 case "$_var" in
259 refs/?*)
260 if [ -n "$_same" ]; then
261 _same=
262 if rv="$(git rev-parse --quiet --verify "$_var" --)" && [ "$rv" = "$newc" ]; then
263 _same=1
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"'
275 esac
276 echo "$_mmsg"
277 [ -n "$_nodt" ] || git --no-pager diff-tree --shortstat "$orh" "$newc" --
278 return 0
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: $*"
285 esac
286 v_attempt_index_merge "$3" "$@"
289 on_base=
290 do_base_switch() {
291 [ -n "$1" ] || return 0
293 [ "$1" != "$on_base" ] ||
294 [ "$(git symbolic-ref -q HEAD)" != "refs/$topbases/$1" ]
295 then
296 switch_to_base "$1"
297 on_base="$1"
301 update_branch() {
302 # We are cacheable until the first change
303 become_cacheable
305 _update_name="$1"
306 ## First, take care of our base
308 _depcheck="$(get_temp tg-depcheck)"
309 missing_deps=
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
316 die "$msg"
317 else
318 info "$msg; skipping branch $_update_name"
319 return
322 if [ -s "$_depcheck" ]; then
323 <"$_depcheck" \
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
333 dep="${depline#?}"
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
340 # in both cases.
342 if [ x"$action" = x+ ]; then
343 case " $missing_deps " in *" $dep "*)
344 info "Skipping recursing to missing dependency: $dep"
345 continue
346 esac
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")"
358 continue
359 else
360 ret=$?
361 if [ $ret -eq 2 ]; then
362 info "Ok, I will try to continue without updating this branch."
363 break
364 else
365 info "Ok, you aborted the merge. Now, you just need to"
366 info "switch back to some sane branch using \`git$gitcdopt checkout\`."
367 exit 3
370 done
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.
377 deplist=
378 deplines=
379 set --
380 while read -r dep; do
381 dep="${dep#?}"
382 case "$dep" in
383 "refs"/*)
384 set -- "$@" "$dep"
385 case "$dep" in
386 "refs/heads"/*)
387 d="${dep#refs/heads/}"
388 deplist="${deplist:+$deplist }$d"
389 deplines="$deplines$d$lf"
392 d="${dep#refs/}"
393 deplist="${deplist:+$deplist }$d"
394 deplines="$deplines$d$lf"
396 esac
399 set -- "$@" "refs/heads/$dep"
400 deplist="${deplist:+$deplist }$dep"
401 deplines="$deplines$dep$lf"
403 esac
404 done <"$_depcheck.ideps"
406 # Make sure we end up on the correct base branch
407 on_base=
408 if [ $# -ge 2 ]; then
409 info "Updating $_update_name base with deps: $deplist"
410 become_non_cacheable
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
413 set --
414 else
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!)
424 case "$fulldep" in
425 "refs/heads"/*)
426 dep="${fulldep#refs/heads/}";;
427 "refs"/*)
428 dep="${fulldep#refs/}";;
430 dep="$fulldep";; # this should be a programmer error
431 esac
433 info "Updating $_update_name base with $dep changes..."
434 become_non_cacheable
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"
444 then
445 if [ -z "$TG_RECURSIVE" ]; then
446 resume="\`$tgdisplay update${skipms:+ --skip-missing} $_update_name\` again"
447 else # subshell
448 resume='exit'
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."
454 rm "$_depcheck"
455 exit 2
457 done
458 else
459 info "The base is up-to-date."
463 ## Second, update our head with the remote branch
465 plusextra=
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."
471 else
472 stash_now_if_requested
473 info "Reconciling $_update_name base with remote branch updates..."
474 become_non_cacheable
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 --)"
484 then
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"
489 exit 4
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."
501 return 0
503 stash_now_if_requested
504 info "Updating $_update_name against new base..."
505 become_non_cacheable
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"
516 then
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}\`."
521 else # subshell
522 info "Please commit merge resolution and call exit."
523 info "You can abort this operation using \`git$gitcdopt reset --hard\`."
525 exit 4
529 # We are "read-only" and cacheable until the first change
530 tg_read_only=1
531 v_create_ref_cache
533 if [ -z "$all" ]; then
534 for name in $names; do
535 update_branch "$name" || exit
536 done
537 case "$names" in *" "*)
538 info "Returning to $current..."
539 esac
540 git checkout -q "$current"
541 exit
544 do_non_annihilated_branches_patterns() {
545 while read -r _pat && [ -n "$_pat" ]; do
546 set -- "$@" "$_pat"
547 done
548 non_annihilated_branches "$@"
551 do_non_annihilated_branches() {
552 if [ -z "$pattern" ]; then
553 non_annihilated_branches
554 else
555 do_non_annihilated_branches_patterns <<-EOT
556 $(sed 'y/ /\n/' <<-LIST
557 $pattern
558 LIST
564 while read name && [ -n "$name" ]; do
565 info "Proccessing $name..."
566 update_branch "$name" || exit
567 done <<-EOT
568 $(do_non_annihilated_branches)
571 info "Returning to $current..."
572 git checkout -q "$current"
573 # vim:noet