tg.sh: quote problem alternate directory paths
[topgit/pro.git] / tg-migrate-bases.sh
blob5f5d6e05f69b4f5d5209291028f8577baa8489c7
1 #!/bin/sh
3 # tg--migrate-bases -- migrate from old top-bases to new {top-bases}
4 # Copyright (C) 2017 Kyle J. McKay
5 # All rights reserved.
6 # License GPLv2+
8 USAGE="\
9 Usage: ${tgname:-tg} [...] migrate-bases (--dry-run | --force) [--no-remotes | --remotes-only]"
11 usage()
13 if [ "${1:-0}" != 0 ]; then
14 printf '%s\n' "$USAGE" >&2
15 else
16 printf '%s\n' "$USAGE"
18 exit ${1:-0}
21 ## Parse options
23 dryrun=
24 force=
25 noremotes=
26 remotesonly=
27 reverse=
28 orphans=1
30 while [ $# -gt 0 ]; do case "$1" in
31 -h|--help)
32 usage
34 -n|--dry-run|--dryrun)
35 dryrun=1
37 -f|--force)
38 force=1
40 --no-remotes)
41 noremotes=1
43 --remotes-only)
44 remotesonly=1
46 --reverse)
47 reverse=1
49 --orphans|--orphan)
50 orphans=1
52 --no-orphans|--no-orphan)
53 orphans=
55 -?*)
56 echo "Unknown option: $1" >&2
57 usage 1
59 esac; shift; done
61 [ "$dryrun$force" = "1" ] || usage 1
62 [ "$noremotes$remotesonly" != "11" ] || usage 1
63 [ $# -eq 0 ] || usage 1
65 remotes=
66 [ -n "$noremotes" ] || remotes="$(git remote)" || :
68 if [ -z "$reverse" ]; then
69 oldbases="top-bases"
70 oldbasesrx="top-bases"
71 newbases="heads/{top-bases}"
72 newbasesrx="heads/[{]top-bases[}]"
73 else
74 oldbases="heads/{top-bases}"
75 oldbasesrx="heads/[{]top-bases[}]"
76 newbases="top-bases"
77 newbasesrx="top-bases"
80 refpats=
81 [ -n "$remotesonly" ] || refpats="refs/$oldbases"
82 [ -z "$remotes" ] || refpats="$refpats$(printf " refs/remotes/%s/${oldbases#heads/}" $remotes)"
83 [ -n "$refpats" ] || exit 0
85 topbraces="{top-bases}"
86 not_orphan_base() {
87 _check=
88 case "$1" in
89 "refs/top-bases"/[!/]*)
90 _check="refs/heads/${1#refs/top-bases/}"
92 "refs/heads/{top-bases}"/[!/]*)
93 _check="refs/heads/${1#refs/heads/$topbraces/}"
95 "refs/remotes"/[!/]*)
96 _rb="${1#refs/remotes/}"
97 _rn="${_rb%%/*}"
98 _rr="${_rb#*/}"
99 case "$_rr" in
100 "top-bases"/[!/]*)
101 _check="refs/remotes/$_rn/${_rr#top-bases/}"
103 "{top-bases}"/[!/]*)
104 _check="refs/remotes/$_rn/${_rr#$topbraces/}"
106 esac
108 esac
109 [ -n "$_check" ] || return 1
110 git rev-parse --verify --quiet "$_check" -- >/dev/null
113 v_transform_base() {
114 _newb=
115 case "$2" in
116 "refs/top-bases"/[!/]*)
117 _newb="refs/heads/{top-bases}/${2#refs/top-bases/}"
118 _wasrevdir=
119 _wasremote=
121 "refs/heads/{top-bases}"/[!/]*)
122 _newb="refs/top-bases/${2#refs/heads/$topbraces/}"
123 _wasremote=
124 _wasrevdir=1
126 "refs/remotes"/[!/]*)
127 _rb="${2#refs/remotes/}"
128 _rn="${_rb%%/*}"
129 _rr="${_rb#*/}"
130 case "$_rr" in
131 "top-bases"/[!/]*)
132 _newb="refs/remotes/$_rn/{top-bases}/${_rr#top-bases/}"
133 _wasrevdir=
134 _wasremote=1
136 "{top-bases}"/[!/]*)
137 _newb="refs/remotes/$_rn/top-bases/${_rr#$topbraces/}"
138 _wasrevdir=1
139 _wasremote=1
141 esac
143 esac
144 if [ -n "$_newb" ] && [ -n "$1" ]; then
145 eval "$1="'"$_newb"'
146 return 0
148 [ -z "$1" ] || eval "$1="
149 return 1
152 symrefhead="$(git symbolic-ref HEAD 2>/dev/null)" || :
153 if [ "$(cd "$git_dir" && pwd -P)" != "$(cd "$git_common_dir" && pwd -P)" ]; then
154 symrefmain="$(git --git-dir="$git_common_dir" symbolic-ref HEAD 2>/dev/null)" || :
155 else
156 symrefmain=
158 headnote=
159 mainnote=
160 if [ -n "$symrefhead" ] && v_transform_base newrefhead "$symrefhead"; then
161 headnote=" [HEAD]"
162 symrefheadrmt="$_wasremote"
163 symrefheadrev="$_wasrevdir"
165 if [ -n "$symrefmain" ]; then
166 if [ "$symrefhead" = "$symrefmain" ]; then
167 if [ -n "$headnote" ]; then
168 headnote=" [HEAD, main]"
169 mainnote="$headnote"
170 newrefmain="$newrefhead"
171 symrefmainrmt="$symrefheadrmt"
172 symrefmainrev="$symrefheadrev"
174 elif v_transform_base newrefmain "$symrefmain"; then
175 mainnote=" [main]"
176 symrefmainrmt="$_wasremote"
177 symrefmainrev="$_wasrevdir"
181 for r in $remotes; do
182 nv="+refs/$newbases/*:refs/remotes/$r/${newbases#heads/}/*"
183 if rf="$(git config --get-all "remote.$r.fetch" \
184 "\\+?refs/(top-bases|heads/[{]top-bases[}])/\\*:refs/remotes/$r/(top-bases|[{]top-bases[}])/\\*")" &&
185 [ "$rf" != "$nv" ]; then
186 echo "remote.$r.fetch:"
187 printf ' %s\n' $rf
188 printf ' -> %s\n' "$nv"
189 if [ -n "$force" ]; then
190 git config --replace-all "remote.$r.fetch" "$nv" \
191 "\\+?refs/(top-bases|heads/[{]top-bases[}])/\\*:refs/remotes/$r/(top-bases|[{]top-bases[}])/\\*"
193 elif [ "$rf" != "$nv" ] && rf="$(git config --get-all "remote.$r.fetch" "\\+?refs/(top-bases|heads/[{]top-bases[}])/.*")"; then
194 echo "remote.$r.fetch may need manual updates of:"
195 printf ' %s\n' $rf
197 done
199 sawhead=
200 sawmain=
201 while read -r rn rt rh && [ -n "$rn" ] && [ -n "$rt" ] && [ -n "$rh" ]; do
202 if [ -z "$orphans" ] && ! not_orphan_base "$rn"; then
203 echo "skipping orphan base (use --orphans): $rn" >&2
204 continue
206 if [ "$rt" = "tree" ] || [ "$rt" = "blob" ]; then
207 echo "ignoring base with type $rt: $rn" >&2
208 continue
210 if [ "$rt" = "tag" ]; then
211 rnc="$(git rev-parse --verify --quiet "$rh^0" -- 2>/dev/null)" || :
212 if [ -z "$rnc" ]; then
213 echo "ignoring base with type tag of non-commit: $rn" >&2
214 continue
216 echo "warning: resolving base with type tag to commit: $rn" >&2
217 rh="$rnc"
219 v_transform_base newb "$rn" || die "unexpected non-bases ref: $rn"
220 newbrev="$(git rev-parse --verify --quiet "$newb" --)" || :
221 newbtype=
222 [ -z "$newbrev" ] || newbtype="$(git cat-file -t "$newbrev")"
223 if [ "$newbtype" = "tree" ] || [ "$newbtype" = "blob" ]; then
224 echo "warning: $rn" >&2
225 echo " refusing to update existing ref:" >&2
226 echo " $newb" >&2
227 echo " of type $newbtype" >&2
228 continue
230 if [ "$newbtype" = "tag" ]; then
231 newbrev="$(git rev-parse --verify --quiet "$newbrev^0" -- 2>/dev/null)" || :
232 if [ -z "$newbrev" ]; then
233 echo "warning: $rn" >&2
234 echo " refusing to update existing ref:" >&2
235 echo " $newb" >&2
236 echo " of type tag of non-commit" >&2
237 continue
239 echo "warning: $rn" >&2
240 echo " treating existing ref:" >&2
241 echo " $newb" >&2
242 echo " of type tag as the tagged commit" >&2
244 if [ -n "$newbrev" ] && [ "$newbrev" != "rh" ]; then
245 mb="$(git merge-base "$newbrev" "$rh" 2>/dev/null)" || :
246 if [ "$mb" = "$newbrev" ]; then
247 echo "warning: $rn" >&2
248 echo " ignoring existing ref:" >&2
249 echo " $newb" >&2
250 echo " since it's contained in $rn" >&2
251 elif [ "$mb" = "$rh" ]; then
252 echo "warning: $rn" >&2
253 echo " using existing value of ref:" >&2
254 echo " $newb" >&2
255 echo " since it contains $rn" >&2
256 rh="$newbrev"
257 else
258 rd="$(git --no-pager log -n 1 --format='format:%ct' "$rh" --)"
259 newbdt="$(git --no-pager log -n 1 --format='format:%ct' "$newbrev" --)"
260 if [ "$rd" -ge "$newbdt" ]; then
261 echo "warning: $rn" >&2
262 echo " ignoring existing diverged ref:" >&2
263 echo " $newb" >&2
264 echo " since it's got an older committer timestamp" >&2
265 else
266 echo "warning: $rn" >&2
267 echo " using existing value of diverged ref:" >&2
268 echo " $newb" >&2
269 echo " since it's got a newer committer timestamp" >&2
270 rh="$newbrev"
274 note=
275 if [ "$rn" = "$symrefhead" ] && [ "$newb" = "$newrefhead" ]; then
276 note="$headnote"
277 sawhead=1
278 elif [ "$rn" = "$symrefmain" ] && [ "$newb" = "$newrefmain" ]; then
279 note="$mainnote"
280 sawmain=1
282 printf 'update: %s%s\n -> %s\n' "$rn" "$note" "$newb"
283 if [ -n "$force" ]; then
284 git update-ref "$newb" "$rh"
285 if [ "$(git rev-parse --quiet --verify "$newb" --)" = "$rh" ] && [ "$newb" != "$rn" ]; then
286 git update-ref -d "$rn"
288 if [ "$rn" = "$symrefhead" ]; then
289 git symbolic-ref HEAD "$newrefhead"
291 if [ "$rn" = "$symrefmain" ]; then
292 git --git-dir="$git_common_dir" symbolic-ref HEAD "$newrefmain"
295 done <<EOT
296 $(git for-each-ref --format='%(refname) %(objecttype) %(objectname)' $refpats)
299 # [ -n "$1" ] => remote update
300 # [ -n "$2" ] => reverse update
301 doing_update_type()
303 if [ -n "$1" ]; then
304 [ -z "$noremotes" ] || return 1
305 else
306 [ -z "$remotesonly" ] || return 1
308 if [ -n "$2" ]; then
309 [ -n "$reverse" ] || return 1
310 else
311 [ -z "$reverse" ] || return 1
313 return 0
316 # In case just the HEAD symref needs updating
317 if [ -z "$sawhead" ] && [ -n "$headnote" ] && doing_update_type "$symrefheadrmt" "$symrefheadrev"; then
318 [ "$symrefhead" != "$symrefmain" ] || sawmain=1
319 printf 'update [symref only]: %s%s\n -> %s\n' "$symrefhead" "$headnote" "$newrefhead"
320 [ -z "$force" ] || git symbolic-ref HEAD "$newrefhead"
321 [ -z "$force" ] || [ "$symrefhead" != "$symrefmain" ] ||
322 git --git-dir="$git_common_dir" symbolic-ref HEAD "$newrefmain"
324 if [ -z "$sawmain" ] && [ -n "$mainnote" ] && doing_update_type "$symrefmainrmt" "$symrefmainrev"; then
325 printf 'update [symref only]: %s%s\n -> %s\n' "$symrefmain" "$mainnote" "$newrefmain"
326 git --git-dir="$git_common_dir" symbolic-ref HEAD "$newrefmain"
329 exit 0