tg-revert.sh: convert any top-bases in TOPGIT REFS
[topgit/pro.git] / tg-update.sh
blobfcaf207115758c24bbe4ad4ade5250279b546517
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 tree; switch to another branch"
56 ensure_clean_tree
58 stash_now_if_requested() {
59 [ -z "$TG_RECURSIVE" ] || return 0
60 ensure_ident_available
61 [ -n "$stash" ] || return 0
62 msg="tgupdate: autostash before update"
63 if [ -n "$all" ]; then
64 msg="$msg --all${origpattern:+ $origpattern}"
65 stashb="--all"
66 else
67 msg="$msg $name"
68 stashb="$name"
70 $tg tag --quiet -m "$msg" --stash "$stashb" || die "requested --stash failed"
71 stash=
74 recursive_update() {
75 $tg update ${skip:+--skip}
76 _ret=$?
77 [ $_ret -eq 3 ] && exit 3
78 return $_ret
81 # run git merge with the passed in arguments AND --no-stat
82 # return the exit status of git merge
83 # if the returned exit status is no error show a shortstat before
84 # returning assuming the merge was done into the previous HEAD
85 git_merge() {
86 _oldhead="$(git rev-parse --verify HEAD^0)"
87 _ret=0
88 git merge $auhopt --no-stat "$@" || _ret=$?
89 [ "$_ret" != "0" ] || git --no-pager diff --shortstat "$_oldhead" HEAD^0 --
90 return $_ret
93 update_branch() {
94 # We are cacheable until the first change
95 become_cacheable
97 _update_name="$1"
98 ## First, take care of our base
100 _depcheck="$(get_temp tg-depcheck)"
101 missing_deps=
102 needs_update "$_update_name" >"$_depcheck" || :
103 if [ -n "$missing_deps" ]; then
104 msg="Some dependencies are missing: $missing_deps"
105 if [ -n "$skip" ]; then
106 info "$msg; skipping"
107 elif [ -z "$all" ]; then
108 die "$msg"
109 else
110 info "$msg; skipping branch $_update_name"
111 return
114 if [ -s "$_depcheck" ]; then
115 stash_now_if_requested
116 # We need to switch to the base branch
117 # ...but only if we aren't there yet (from failed previous merge)
118 _HEAD="$(git symbolic-ref -q HEAD || :)"
119 if [ "$_HEAD" = "${_HEAD#refs/$topbases/}" ]; then
120 switch_to_base "$_update_name"
123 cat "$_depcheck" |
124 sed 's/ [^ ]* *$//' | # last is $_update_name
125 sed 's/.* \([^ ]*\)$/+\1/' | # only immediate dependencies
126 sed 's/^\([^+]\)/-\1/' | # now each line is +branch or -branch (+ == recurse)
127 uniq -s 1 | # fold branch lines; + always comes before - and thus wins within uniq
128 while read depline; do
129 dep="${depline#?}"
130 action="${depline%$dep}"
131 become_non_cacheable
133 # We do not distinguish between dependencies out-of-date
134 # and base/remote out-of-date cases for $dep here,
135 # but thanks to needs_update returning : or refs/remotes/<remote>/<name>
136 # for the latter, we do correctly recurse here
137 # in both cases.
139 if [ x"$action" = x+ ]; then
140 case " $missing_deps " in *" $dep "*)
141 info "Skipping recursing to missing dependency: $dep"
142 continue
143 esac
144 info "Recursing to $dep..."
145 git checkout -q "$dep"
147 TG_RECURSIVE="[$dep] $TG_RECURSIVE"
148 PS1="[$dep] $PS1"
149 export TG_RECURSIVE
150 export PS1
151 while ! recursive_update; do
152 # The merge got stuck! Let the user fix it up.
153 info "You are in a subshell. If you abort the merge,"
154 info "use \`exit 1\` to abort the recursive update altogether."
155 info "Use \`exit 2\` to skip updating this branch and continue."
156 if "${SHELL:-@SHELL_PATH@}" -i </dev/tty; then
157 # assume user fixed it
158 # we could be left on a detached HEAD if we were resolving
159 # a conflict while merging a base in, fix it with a checkout
160 git checkout -q "$(strip_ref "$dep")"
161 continue
162 else
163 ret=$?
164 if [ $ret -eq 2 ]; then
165 info "Ok, I will try to continue without updating this branch."
166 break
167 else
168 info "Ok, you aborted the merge. Now, you just need to"
169 info "switch back to some sane branch using \`git$gitcdopt checkout\`."
170 exit 3
173 done
175 check_exit_code
176 switch_to_base "$_update_name"
179 # This will be either a proper topic branch
180 # or a remote base. (branch_needs_update() is called
181 # only on the _dependencies_, not our branch itself!)
183 info "Updating $_update_name base with $dep changes..."
184 case "$dep" in refs/*) fulldep="$dep";; *) fulldep="refs/heads/$dep"; esac
185 if ! git_merge -m "tgupdate: merge ${dep#refs/} into $topbases/$_update_name" "$fulldep^0"; then
186 if [ -z "$TG_RECURSIVE" ]; then
187 resume="\`$tgdisplay update${skip:+ --skip} $_update_name\` again"
188 else # subshell
189 resume='exit'
191 info "Please commit merge resolution and call $resume."
192 info "It is also safe to abort this operation using \`git$gitcdopt reset --hard\`,"
193 info "but please remember that you are on the base branch now;"
194 info "you will want to switch to some normal branch afterwards."
195 rm "$_depcheck"
196 exit 2
198 done
199 check_exit_code
200 else
201 info "The base is up-to-date."
204 # Home, sweet home...
205 # (We want to always switch back, in case we were on the base from failed
206 # previous merge.)
207 git checkout -q "$_update_name"
209 merge_with="refs/$topbases/$_update_name"
212 ## Second, update our head with the remote branch
214 plusextra=
215 if has_remote "$_update_name"; then
216 _rname="refs/remotes/$base_remote/$_update_name"
217 if branch_contains "refs/heads/$_update_name" "$_rname"; then
218 info "The $_update_name head is up-to-date wrt. its remote branch."
219 else
220 stash_now_if_requested
221 info "Reconciling $_update_name base with remote branch updates..."
222 become_non_cacheable
223 # *DETACH* our HEAD now!
224 git checkout -q --detach "refs/$topbases/$_update_name"
225 if ! git_merge -m "tgupdate: merge ${_rname#refs/} onto $topbases/$_update_name" "$_rname^0"; then
226 info "Oops, you will need to help me out here a bit."
227 info "Please commit merge resolution and call:"
228 info "git$gitcdopt checkout $_update_name && git$gitcdopt merge <commitid>"
229 info "It is also safe to abort this operation using: git$gitcdopt reset --hard $_update_name"
230 exit 4
232 # Go back but remember we want to merge with this, not base
233 merge_with="$(git rev-parse --verify HEAD --)"
234 plusextra="${_rname#refs/}+"
235 git checkout -q "$_update_name"
240 ## Third, update our head with the base
242 if branch_contains "refs/heads/$_update_name" "$merge_with"; then
243 info "The $_update_name head is up-to-date wrt. the base."
244 return 0
246 stash_now_if_requested
247 info "Updating $_update_name against new base..."
248 become_non_cacheable
249 if ! git_merge -m "tgupdate: merge ${plusextra}$topbases/$_update_name into $_update_name" "$merge_with^0"; then
250 if [ -z "$TG_RECURSIVE" ]; then
251 info "Please commit merge resolution. No need to do anything else"
252 info "You can abort this operation using \`git$gitcdopt reset --hard\` now"
253 info "and retry this merge later using \`$tgdisplay update${skip:+ --skip}\`."
254 else # subshell
255 info "Please commit merge resolution and call exit."
256 info "You can abort this operation using \`git$gitcdopt reset --hard\`."
258 exit 4
262 # We are "read-only" and cacheable until the first change
263 tg_read_only=1
264 create_ref_cache
266 [ -z "$all" ] && { update_branch $name; exit; }
268 do_non_annihilated_branches_patterns() {
269 while read -r _pat && [ -n "$_pat" ]; do
270 set -- "$@" "$_pat"
271 done
272 non_annihilated_branches "$@"
275 do_non_annihilated_branches() {
276 if [ -z "$pattern" ]; then
277 non_annihilated_branches
278 else
279 do_non_annihilated_branches_patterns <<-EOT
280 $(sed 'y/ /\n/' <<-LIST
281 $pattern
282 LIST
288 while read name && [ -n "$name" ]; do
289 info "Proccessing $name..."
290 update_branch "$name" || exit
291 done <<-EOT
292 $(do_non_annihilated_branches)
295 info "Returning to $current..."
296 git checkout -q "$current"
297 # vim:noet