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
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
34 echo "Usage: ${tgname:-tg} [...] update [--[no-]stash] [--skip] ([<name>] | -a [<pattern>...])" >&2
37 if [ -z "$all" ]; then
38 [ -z "$name" ] || die
"name already specified ($name)"
41 pattern
="${pattern:+$pattern }refs/$topbases/$(strip_ref "$arg")"
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}")"
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"
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}"
72 $tg tag
-q -q -m "$msg" --stash "$stashb" || die
"requested --stash failed"
80 TG_RECURSIVE
="[$1] $TG_RECURSIVE"
85 [ $_ret -eq 3 ] && exit 3
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
102 _oldhead
="$(git rev-parse --verify HEAD^0)"
104 git merge
$auhopt --no-stat "$@" || _ret
=$?
105 [ "$_ret" != "0" ] || git
--no-pager diff-tree
--shortstat "$_oldhead" HEAD^
0 --
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
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: $*"
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)"
145 elif [ "$r1" = "$mb" ]; then
146 _mmsg
="Already up-to-date!"
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; }
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"
166 if [ -s "$itmp" ]; then
172 newt
="$(GIT_INDEX_FILE="$inew" git write-tree)" && [ -n "$newt" ] ||
{ rm -f "$inew"; return 1; }
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."
179 if [ -n "$_same" ]; then
181 if rv
="$(git rev-parse --quiet --verify "$_var" --)" && [ "$rv" = "$newc" ]; then
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"'
196 [ -n "$_nodt" ] || git
--no-pager diff-tree
--shortstat "$rh" "$newc" --
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: $*"
205 v_attempt_index_merge
"$3" "$@"
210 [ -n "$1" ] ||
return 0
212 [ "$1" != "$on_base" ] ||
213 [ "$(git symbolic-ref -q HEAD)" != "refs/$topbases/$1" ]
221 # We are cacheable until the first change
225 ## First, take care of our base
227 _depcheck
="$(get_temp tg-depcheck)"
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
237 info
"$msg; skipping branch $_update_name"
241 if [ -s "$_depcheck" ]; then
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
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
261 if [ x
"$action" = x
+ ]; then
262 case " $missing_deps " in *" $dep "*)
263 info
"Skipping recursing to missing dependency: $dep"
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")"
280 if [ $ret -eq 2 ]; then
281 info
"Ok, I will try to continue without updating this branch."
284 info
"Ok, you aborted the merge. Now, you just need to"
285 info
"switch back to some sane branch using \`git$gitcdopt checkout\`."
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...
297 while read -r dep
; do
303 set -- "$@" "refs/heads/$dep";;
305 done <"$_depcheck.ideps"
307 # Make sure we end up on the correct base branch
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!)
317 dep
="${fulldep#refs/heads/}";;
319 dep
="${fulldep#refs/}";;
321 dep
="$fulldep";; # this should be a programmer error
324 info
"Updating $_update_name base with $dep changes..."
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"
336 if [ -z "$TG_RECURSIVE" ]; then
337 resume
="\`$tgdisplay update${skip:+ --skip} $_update_name\` again"
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."
350 info
"The base is up-to-date."
354 ## Second, update our head with the remote branch
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."
363 stash_now_if_requested
364 info
"Reconciling $_update_name base with remote branch updates..."
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 --)"
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"
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."
394 stash_now_if_requested
395 info
"Updating $_update_name against new base..."
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"
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}\`."
413 info
"Please commit merge resolution and call exit."
414 info
"You can abort this operation using \`git$gitcdopt reset --hard\`."
420 # We are "read-only" and cacheable until the first change
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
430 non_annihilated_branches
"$@"
433 do_non_annihilated_branches
() {
434 if [ -z "$pattern" ]; then
435 non_annihilated_branches
437 do_non_annihilated_branches_patterns
<<-EOT
438 $(sed 'y/ /\n/' <<-LIST
446 while read name
&& [ -n "$name" ]; do
447 info
"Proccessing $name..."
448 update_branch
"$name" ||
exit
450 $(do_non_annihilated_branches)
453 info
"Returning to $current..."
454 git checkout
-q "$current"