tg-{revert,update}.sh: quiet confusing auto stash messages
[topgit/pro.git] / tg-update.sh
blobbebfce5fe0ee3ee6b99a14c029f1a57f5b378aeb
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
5 # All rights reserved.
6 # GPLv2
8 name= # Branch to update
9 all= # Update all branches
10 pattern= # Branch selection filter for -a
11 current= # Branch we are currently on
12 skip= # 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 while [ -n "$1" ]; do
23 arg="$1"; shift
24 case "$arg" in
25 -a|--all)
26 all=1;;
27 --skip)
28 skip=1;;
29 --stash)
30 stash=1;;
31 --no-stash)
32 stash=;;
33 -*)
34 echo "Usage: ${tgname:-tg} [...] update [--[no-]stash] [--skip] ([<name>] | -a [<pattern>...])" >&2
35 exit 1;;
37 if [ -z "$all" ]; then
38 [ -z "$name" ] || die "name already specified ($name)"
39 name="$arg"
40 else
41 pattern="${pattern:+$pattern }refs/$topbases/$(strip_ref "$arg")"
44 esac
45 done
46 origpattern="$pattern"
47 [ -z "$pattern" ] && pattern="refs/$topbases"
49 current="$(strip_ref "$(git symbolic-ref -q HEAD)")" || :
50 if [ -z "$all" ]; then
51 name="$(verify_topgit_branch "${name:-HEAD}")"
52 else
53 [ -n "$current" ] || die "cannot return to detached HEAD; switch to another branch"
54 [ -n "$(git rev-parse --verify --quiet HEAD --)" ] ||
55 die "cannot return to unborn branch; switch to another branch"
58 ensure_clean_tree
60 stash_now_if_requested() {
61 [ -z "$TG_RECURSIVE" ] || return 0
62 ensure_ident_available
63 [ -n "$stash" ] || return 0
64 msg="tgupdate: autostash before update"
65 if [ -n "$all" ]; then
66 msg="$msg --all${origpattern:+ $origpattern}"
67 stashb="--all"
68 else
69 msg="$msg $name"
70 stashb="$name"
72 $tg tag -q -q -m "$msg" --stash "$stashb" || die "requested --stash failed"
73 stash=
76 recursive_update() {
77 _ret=0
78 on_base=
80 TG_RECURSIVE="[$1] $TG_RECURSIVE"
81 PS1="[$1] $PS1"
82 export PS1
83 update_branch "$1"
84 ) || _ret=$?
85 [ $_ret -eq 3 ] && exit 3
86 return $_ret
89 # If HEAD is a symref to "$1" detach it at its current value
90 detach_symref_head_on_branch() {
91 _hsr="$(git symbolic-ref -q HEAD --)" && [ -n "$_hsr" ] || return 0
92 _hrv="$(git rev-parse --quiet --verify HEAD --)" && [ -n "$_hrv" ] ||
93 die "cannot detach_symref_head_on_branch from unborn branch $_hsr"
94 git update-ref --no-deref -m "detaching HEAD from $_hsr to safely update it" HEAD "$_hrv"
97 # run git merge with the passed in arguments AND --no-stat
98 # return the exit status of git merge
99 # if the returned exit status is no error show a shortstat before
100 # returning assuming the merge was done into the previous HEAD
101 git_merge() {
102 _oldhead="$(git rev-parse --verify HEAD^0)"
103 _ret=0
104 git merge $auhopt --no-stat "$@" || _ret=$?
105 [ "$_ret" != "0" ] || git --no-pager diff-tree --shortstat "$_oldhead" HEAD^0 --
106 return $_ret
109 # similar to git_merge but operates exclusively using a separate index and temp dir
110 # only trivial aggressive automatic (i.e. simple) merges are supported
112 # $1 => '' to discard result, 'refs/?*' to update the specified ref or a varname
113 # $2 => '-m' MUST be '-m'
114 # $3 => commit message AND, if $1 matches refs/?* the update-ref message
115 # $4 => commit-ish to merge as "ours"
116 # $5 => commit-ish to merge as "theirs"
118 # all merging is done in a separate index (or temporary files for simple merges)
119 # if successful the ref or var is updated with the result
120 # otherwise everything is left unchanged and a silent failure occurs
121 # if successful and $1 matches refs/?* it WILL BE UPDATED to a new commit using the
122 # message and appropriate parents AND HEAD WILL BE DETACHED first if it's a symref
123 # to the same ref
124 # otherwise if $1 does not match refs/?* and is not empty the named variable will
125 # be set to contain the resulting commit from the merge
126 # the working tree and index ARE LEFT COMPLETELY UNTOUCHED no matter what
127 v_attempt_index_merge() {
128 [ "$#" -eq 5 ] && [ "$2" = "-m" ] && [ -n "$3" ] && [ -n "$4" ] && [ -n "$5" ] ||
129 die "programmer error: invalid arguments to v_attempt_index_merge: $*"
130 _var="$1"
131 _msg="$3"
132 _head="$4"
133 shift 4
134 _mmsg=
135 newc=
136 _nodt=
137 _same=
138 rh="$(git rev-parse --quiet --verify "$_head^0" --)" && [ -n "$rh" ] || return 1
139 if mb="$(git merge-base "$_head" "$1")" && [ -n "$mb" ]; then
140 r1="$(git rev-parse --quiet --verify "$1^0" --)" && [ -n "$r1" ] || return 1
141 if [ "$rh" = "$mb" ]; then
142 _mmsg="Fast-forward (no commit created)"
143 newc="$r1"
144 _nodt=1
145 elif [ "$r1" = "$mb" ]; then
146 _mmsg="Already up-to-date!"
147 newc="$rh"
148 _nodt=1
149 _same=1
151 else
152 mb="$(git hash-object -w -t tree --stdin < /dev/null)"
154 if [ -z "$newc" ]; then
155 inew="$tg_tmp_dir/index.$$"
156 itmp="$tg_tmp_dir/output.$$"
157 ! [ -e "$inew" ] || rm -f "$inew"
158 GIT_INDEX_FILE="$inew" git read-tree -m --aggressive -i "$mb" "$_head" "$1" || { rm -f "$inew"; return 1; }
159 GIT_INDEX_FILE="$inew" git ls-files --unmerged --full-name --abbrev :/ >"$itmp" 2>&1 || { rm -f "$inew" "$itmp"; return 1; }
160 _auto=
161 ! [ -s "$itmp" ] || {
162 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
163 rm -f "$inew" "$itmp"
164 return 1
166 if [ -s "$itmp" ]; then
167 cat "$itmp"
168 _auto=" automatic"
171 rm -f "$itmp"
172 newt="$(GIT_INDEX_FILE="$inew" git write-tree)" && [ -n "$newt" ] || { rm -f "$inew"; return 1; }
173 rm -f "$inew"
174 newc="$(git commit-tree -p "$_head" -p "$1" -m "$_msg" "$newt")" && [ -n "$newc" ] || return 1
175 _mmsg="Merge made by the 'trivial aggressive$_auto' strategy."
177 case "$_var" in
178 refs/?*)
179 if [ -n "$_same" ]; then
180 _same=
181 if rv="$(git rev-parse --quiet --verify "$_var" --)" && [ "$rv" = "$newc" ]; then
182 _same=1
185 if [ -z "$_same" ] ; then
186 detach_symref_head_on_branch "$_head" || return 1
187 # git update-ref returns 0 even on failure :(
188 git update-ref -m "$_msg" "$_var" "$newc" || return 1
192 eval "$_var="'"$newc"'
194 esac
195 echo "$_mmsg"
196 [ -n "$_nodt" ] || git --no-pager diff-tree --shortstat "$rh" "$newc" --
197 return 0
200 # shortcut that passes $3 as a preceding argument (which must match refs/?*)
201 attempt_index_merge() {
202 case "$3" in refs/?*);;*)
203 die "programmer error: invalid arguments to attempt_index_merge: $*"
204 esac
205 v_attempt_index_merge "$3" "$@"
208 on_base=
209 do_base_switch() {
210 [ -n "$1" ] || return 0
212 [ "$1" != "$on_base" ] ||
213 [ "$(git symbolic-ref -q HEAD)" != "refs/$topbases/$1" ]
214 then
215 switch_to_base "$1"
216 on_base="$1"
220 update_branch() {
221 # We are cacheable until the first change
222 become_cacheable
224 _update_name="$1"
225 ## First, take care of our base
227 _depcheck="$(get_temp tg-depcheck)"
228 missing_deps=
229 needs_update "$_update_name" >"$_depcheck" || :
230 if [ -n "$missing_deps" ]; then
231 msg="Some dependencies are missing: $missing_deps"
232 if [ -n "$skip" ]; then
233 info "$msg; skipping"
234 elif [ -z "$all" ]; then
235 die "$msg"
236 else
237 info "$msg; skipping branch $_update_name"
238 return
241 if [ -s "$_depcheck" ]; then
242 <"$_depcheck" \
243 sed 's/ [^ ]* *$//' | # last is $_update_name
244 sed 's/.* \([^ ]*\)$/+\1/' | # only immediate dependencies
245 sed 's/^\([^+]\)/-\1/' | # now each line is +branch or -branch (+ == recurse)
246 >"$_depcheck.ideps" \
247 uniq -s 1 # fold branch lines; + always comes before - and thus wins within uniq
249 stash_now_if_requested
251 while read -r depline; do
252 dep="${depline#?}"
253 action="${depline%$dep}"
255 # We do not distinguish between dependencies out-of-date
256 # and base/remote out-of-date cases for $dep here,
257 # but thanks to needs_update returning : or refs/remotes/<remote>/<name>
258 # for the latter, we do correctly recurse here
259 # in both cases.
261 if [ x"$action" = x+ ]; then
262 case " $missing_deps " in *" $dep "*)
263 info "Skipping recursing to missing dependency: $dep"
264 continue
265 esac
266 info "Recursing to $dep..."
267 while ! recursive_update "$dep"; do
268 # The merge got stuck! Let the user fix it up.
269 info "You are in a subshell. If you abort the merge,"
270 info "use \`exit 1\` to abort the recursive update altogether."
271 info "Use \`exit 2\` to skip updating this branch and continue."
272 if "${SHELL:-@SHELL_PATH@}" -i </dev/tty; then
273 # assume user fixed it
274 # we could be left on a detached HEAD if we were resolving
275 # a conflict while merging a base in, fix it with a checkout
276 git checkout -q "$(strip_ref "$dep")"
277 continue
278 else
279 ret=$?
280 if [ $ret -eq 2 ]; then
281 info "Ok, I will try to continue without updating this branch."
282 break
283 else
284 info "Ok, you aborted the merge. Now, you just need to"
285 info "switch back to some sane branch using \`git$gitcdopt checkout\`."
286 exit 3
289 done
291 done <"$_depcheck.ideps"
293 # Create a list of all the fully qualified ref names that need
294 # to be merged into $_update_name's base. This could be done
295 # as an octopus merge if there are no conflicts...
296 set --
297 while read -r dep; do
298 dep="${dep#?}"
299 case "$dep" in
300 "refs"/*)
301 set -- "$@" "$dep";;
303 set -- "$@" "refs/heads/$dep";;
304 esac
305 done <"$_depcheck.ideps"
307 # Make sure we end up on the correct base branch
308 on_base=
310 for fulldep in "$@"; do
311 # This will be either a proper topic branch
312 # or a remote base. (branch_needs_update() is called
313 # only on the _dependencies_, not our branch itself!)
315 case "$fulldep" in
316 "refs/heads"/*)
317 dep="${fulldep#refs/heads/}";;
318 "refs"/*)
319 dep="${fulldep#refs/}";;
321 dep="$fulldep";; # this should be a programmer error
322 esac
324 info "Updating $_update_name base with $dep changes..."
325 become_non_cacheable
326 msg="tgupdate: merge $dep into $topbases/$_update_name"
328 ! attempt_index_merge -m "$msg" "refs/$topbases/$_update_name" "$fulldep^0" &&
330 # We need to switch to the base branch
331 # ...but only if we aren't there yet (from failed previous merge)
332 do_base_switch "$_update_name" || die "do_base_switch failed" &&
333 git_merge -m "$msg" "$fulldep^0"
335 then
336 if [ -z "$TG_RECURSIVE" ]; then
337 resume="\`$tgdisplay update${skip:+ --skip} $_update_name\` again"
338 else # subshell
339 resume='exit'
341 info "Please commit merge resolution and call $resume."
342 info "It is also safe to abort this operation using \`git$gitcdopt reset --hard\`,"
343 info "but please remember that you are on the base branch now;"
344 info "you will want to switch to some normal branch afterwards."
345 rm "$_depcheck"
346 exit 2
348 done
349 else
350 info "The base is up-to-date."
354 ## Second, update our head with the remote branch
356 plusextra=
357 merge_with="refs/$topbases/$_update_name"
358 if has_remote "$_update_name"; then
359 _rname="refs/remotes/$base_remote/$_update_name"
360 if branch_contains "refs/heads/$_update_name" "$_rname"; then
361 info "The $_update_name head is up-to-date wrt. its remote branch."
362 else
363 stash_now_if_requested
364 info "Reconciling $_update_name base with remote branch updates..."
365 become_non_cacheable
366 msg="tgupdate: merge ${_rname#refs/} onto $topbases/$_update_name"
368 ! v_attempt_index_merge "merge_with" -m "$msg" "refs/$topbases/$_update_name" "$_rname^0" &&
370 # *DETACH* our HEAD now!
371 git checkout -q --detach "refs/$topbases/$_update_name" || die "git checkout failed" &&
372 git_merge -m "$msg" "$_rname^0" &&
373 merge_with="$(git rev-parse --verify HEAD --)"
375 then
376 info "Oops, you will need to help me out here a bit."
377 info "Please commit merge resolution and call:"
378 info "git$gitcdopt checkout $_update_name && git$gitcdopt merge <commitid>"
379 info "It is also safe to abort this operation using: git$gitcdopt reset --hard $_update_name"
380 exit 4
382 # Go back but remember we want to merge with this, not base
383 plusextra="${_rname#refs/}+"
388 ## Third, update our head with the base
390 if branch_contains "refs/heads/$_update_name" "$merge_with"; then
391 info "The $_update_name head is up-to-date wrt. the base."
392 return 0
394 stash_now_if_requested
395 info "Updating $_update_name against new base..."
396 become_non_cacheable
397 msg="tgupdate: merge ${plusextra}$topbases/$_update_name into $_update_name"
399 ! attempt_index_merge -m "$msg" "refs/heads/$_update_name" "$merge_with^0" &&
401 # Home, sweet home...
402 # (We want to always switch back, in case we were
403 # on the base from failed previous merge.)
404 git checkout -q "$_update_name" || die "git checkout failed" &&
405 git_merge -m "$msg" "$merge_with^0"
407 then
408 if [ -z "$TG_RECURSIVE" ]; then
409 info "Please commit merge resolution. No need to do anything else"
410 info "You can abort this operation using \`git$gitcdopt reset --hard\` now"
411 info "and retry this merge later using \`$tgdisplay update${skip:+ --skip}\`."
412 else # subshell
413 info "Please commit merge resolution and call exit."
414 info "You can abort this operation using \`git$gitcdopt reset --hard\`."
416 exit 4
420 # We are "read-only" and cacheable until the first change
421 tg_read_only=1
422 v_create_ref_cache
424 [ -z "$all" ] && { update_branch "$name" && git checkout -q "$name"; exit; }
426 do_non_annihilated_branches_patterns() {
427 while read -r _pat && [ -n "$_pat" ]; do
428 set -- "$@" "$_pat"
429 done
430 non_annihilated_branches "$@"
433 do_non_annihilated_branches() {
434 if [ -z "$pattern" ]; then
435 non_annihilated_branches
436 else
437 do_non_annihilated_branches_patterns <<-EOT
438 $(sed 'y/ /\n/' <<-LIST
439 $pattern
440 LIST
446 while read name && [ -n "$name" ]; do
447 info "Proccessing $name..."
448 update_branch "$name" || exit
449 done <<-EOT
450 $(do_non_annihilated_branches)
453 info "Returning to $current..."
454 git checkout -q "$current"
455 # vim:noet