tg.sh: fix recurse_deps pre-order traversal with remotes enabled
[topgit/pro.git] / tg-update.sh
blobaf5fbf368e477cb7dd462333b62fde96e650ca3b
1 #!/bin/sh
2 # TopGit - A different patch queue manager
3 # Copyright (C) Petr Baudis <pasky@suse.cz> 2008
4 # All rights reserved.
5 # GPLv2
7 name= # Branch to update
8 all= # Update all branches
9 pattern= # Branch selection filter for -a
10 current= # Branch we are currently on
11 skip= # skip missing dependencies
13 ## Parse options
15 while [ -n "$1" ]; do
16 arg="$1"; shift
17 case "$arg" in
18 -a|--all)
19 all=1;;
20 --skip)
21 skip=1;;
22 -*)
23 echo "Usage: ${tgname:-tg} [...] update [--skip] ([<name>] | -a [<pattern>...])" >&2
24 exit 1;;
26 if [ -z "$all" ]; then
27 [ -z "$name" ] || die "name already specified ($name)"
28 name="$arg"
29 else
30 pattern="$pattern refs/top-bases/${arg#refs/top-bases/}"
33 esac
34 done
35 [ -z "$pattern" ] && pattern=refs/top-bases
37 current="$(strip_ref "$(git symbolic-ref HEAD 2>/dev/null)")"
38 if [ -z "$all" ]; then
39 name="$(verify_topgit_branch "${name:-HEAD}")"
40 else
41 [ -n "$current" ] || die "cannot return to detached tree; switch to another branch"
44 ensure_clean_tree
46 recursive_update() {
47 $tg update ${skip:+--skip}
48 _ret=$?
49 [ $_ret -eq 3 ] && exit 3
50 return $_ret
53 update_branch() {
54 # We are cacheable until the first change
55 become_cacheable
57 _update_name="$1"
58 ## First, take care of our base
60 _depcheck="$(get_temp tg-depcheck)"
61 missing_deps=
62 needs_update "$_update_name" >"$_depcheck" || :
63 if [ -n "$missing_deps" ]; then
64 msg="Some dependencies are missing: $missing_deps"
65 if [ -n "$skip" ]; then
66 info "$msg; skipping"
67 elif [ -z "$all" ]; then
68 die "$msg"
69 else
70 info "$msg; skipping branch $_update_name"
71 return
74 if [ -s "$_depcheck" ]; then
75 # We need to switch to the base branch
76 # ...but only if we aren't there yet (from failed previous merge)
77 _HEAD="$(git symbolic-ref HEAD)"
78 if [ "$_HEAD" = "${_HEAD#refs/top-bases/}" ]; then
79 switch_to_base "$_update_name"
82 cat "$_depcheck" |
83 sed 's/ [^ ]* *$//' | # last is $_update_name
84 sed 's/.* \([^ ]*\)$/+\1/' | # only immediate dependencies
85 sed 's/^\([^+]\)/-\1/' | # now each line is +branch or -branch (+ == recurse)
86 uniq -s 1 | # fold branch lines; + always comes before - and thus wins within uniq
87 while read depline; do
88 action="$(echo "$depline" | cut -c 1)"
89 dep="$(echo "$depline" | cut -c 2-)"
90 become_non_cacheable
92 # We do not distinguish between dependencies out-of-date
93 # and base/remote out-of-date cases for $dep here,
94 # but thanks to needs_update returning : or %
95 # for the latter, we do correctly recurse here
96 # in both cases.
98 if [ x"$action" = x+ ]; then
99 case " $missing_deps " in *" $dep "*)
100 info "Skipping recursing to missing dependency: $dep"
101 continue
102 esac
103 info "Recursing to $dep..."
104 git checkout -q "$dep"
106 TG_RECURSIVE="[$dep] $TG_RECURSIVE"
107 PS1="[$dep] $PS1"
108 export TG_RECURSIVE
109 export PS1
110 while ! recursive_update; do
111 # The merge got stuck! Let the user fix it up.
112 info "You are in a subshell. If you abort the merge,"
113 info "use \`exit 1\` to abort the recursive update altogether."
114 info "Use \`exit 2\` to skip updating this branch and continue."
115 if "${SHELL:-@SHELL_PATH@}" -i </dev/tty; then
116 # assume user fixed it
117 # we could be left on a detached HEAD if we were resolving
118 # a conflict while merging a base in, fix it with a checkout
119 git checkout -q "$(strip_ref "$dep")"
120 continue
121 else
122 ret=$?
123 if [ $ret -eq 2 ]; then
124 info "Ok, I will try to continue without updating this branch."
125 break
126 else
127 info "Ok, you aborted the merge. Now, you just need to"
128 info "switch back to some sane branch using \`git$gitcdopt checkout\`."
129 exit 3
132 done
134 switch_to_base "$_update_name"
137 # This will be either a proper topic branch
138 # or a remote base. (branch_needs_update() is called
139 # only on the _dependencies_, not our branch itself!)
141 info "Updating base with $dep changes..."
142 if ! git merge "$dep^0"; then
143 if [ -z "$TG_RECURSIVE" ]; then
144 resume="\`$tgdisplay update${skip:+ --skip} $_update_name\` again"
145 else # subshell
146 resume='exit'
148 info "Please commit merge resolution and call $resume."
149 info "It is also safe to abort this operation using \`git$gitcdopt reset --hard\`,"
150 info "but please remember that you are on the base branch now;"
151 info "you will want to switch to some normal branch afterwards."
152 rm "$_depcheck"
153 exit 2
155 done
156 else
157 info "The base is up-to-date."
160 # Home, sweet home...
161 # (We want to always switch back, in case we were on the base from failed
162 # previous merge.)
163 git checkout -q "$_update_name"
165 merge_with="refs/top-bases/$_update_name"
168 ## Second, update our head with the remote branch
170 if has_remote "$_update_name"; then
171 _rname="refs/remotes/$base_remote/$_update_name"
172 if branch_contains "refs/heads/$_update_name" "$_rname"; then
173 info "The $_update_name head is up-to-date wrt. its remote branch."
174 else
175 info "Reconciling remote branch updates with $_update_name base..."
176 become_non_cacheable
177 # *DETACH* our HEAD now!
178 git checkout -q "refs/top-bases/$_update_name"
179 if ! git merge "$_rname^0"; then
180 info "Oops, you will need to help me out here a bit."
181 info "Please commit merge resolution and call:"
182 info "git$gitcdopt checkout $_update_name && git$gitcdopt merge <commitid>"
183 info "It is also safe to abort this operation using: git$gitcdopt reset --hard $_update_name"
184 exit 4
186 # Go back but remember we want to merge with this, not base
187 merge_with="$(git rev-parse --verify HEAD --)"
188 git checkout -q "$_update_name"
193 ## Third, update our head with the base
195 if branch_contains "refs/heads/$_update_name" "$merge_with"; then
196 info "The $_update_name head is up-to-date wrt. the base."
197 return 0
199 info "Updating $_update_name against new base..."
200 become_non_cacheable
201 if ! git merge "$merge_with^0"; then
202 if [ -z "$TG_RECURSIVE" ]; then
203 info "Please commit merge resolution. No need to do anything else"
204 info "You can abort this operation using \`git$gitcdopt reset --hard\` now"
205 info "and retry this merge later using \`$tgdisplay update${skip:+ --skip}\`."
206 else # subshell
207 info "Please commit merge resolution and call exit."
208 info "You can abort this operation using \`git$gitcdopt reset --hard\`."
210 exit 4
214 # We are "read-only" and cacheable until the first change
215 tg_read_only=1
216 create_ref_cache
218 [ -z "$all" ] && { update_branch $name; exit; }
220 non_annihilated_branches $pattern |
221 while read name; do
222 info "Proccessing $name..."
223 update_branch "$name" || exit
224 done
226 info "Returning to $current..."
227 git checkout -q "$current"
228 # vim:noet