3 # tg--migrate-bases -- migrate from old top-bases to new {top-bases}
4 # Copyright (C) 2017,2021 Kyle J. McKay
9 Usage: ${tgname:-tg} [...] migrate-bases (--dry-run | --force) [--no-remotes | --remotes-only]"
13 if [ "${1:-0}" != 0 ]; then
14 printf '%s\n' "$USAGE" >&2
16 printf '%s\n' "$USAGE"
30 while [ $# -gt 0 ]; do case "$1" in
34 -n|
--dry-run|
--dryrun)
52 --no-orphans|
--no-orphan)
56 echo "Unknown option: $1" >&2
61 [ "$dryrun$force" = "1" ] || usage
1
62 [ "$noremotes$remotesonly" != "11" ] || usage
1
63 [ $# -eq 0 ] || usage
1
66 [ -n "$noremotes" ] || remotes
="$(git remote)" ||
:
68 if [ -z "$reverse" ]; then
70 oldbasesrx
="top-bases"
71 newbases
="heads/{top-bases}"
72 newbasesrx
="heads/[{]top-bases[}]"
74 oldbases
="heads/{top-bases}"
75 oldbasesrx
="heads/[{]top-bases[}]"
77 newbasesrx
="top-bases"
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}"
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/}"
96 _rb
="${1#refs/remotes/}"
101 _check
="refs/remotes/$_rn/${_rr#top-bases/}"
104 _check
="refs/remotes/$_rn/${_rr#$topbraces/}"
109 [ -n "$_check" ] ||
return 1
110 git rev-parse
--verify --quiet "$_check^0" -- >/dev
/null
116 "refs/top-bases"/[!/]*)
117 _newb
="refs/heads/{top-bases}/${2#refs/top-bases/}"
121 "refs/heads/{top-bases}"/[!/]*)
122 _newb
="refs/top-bases/${2#refs/heads/$topbraces/}"
126 "refs/remotes"/[!/]*)
127 _rb
="${2#refs/remotes/}"
132 _newb
="refs/remotes/$_rn/{top-bases}/${_rr#top-bases/}"
137 _newb
="refs/remotes/$_rn/top-bases/${_rr#$topbraces/}"
144 if [ -n "$_newb" ] && [ -n "$1" ]; then
148 [ -z "$1" ] ||
eval "$1="
152 for r
in $remotes; do
153 nv
="+refs/$newbases/*:refs/remotes/$r/${newbases#heads/}/*"
154 if rf
="$(git config --get-all "remote.
$r.fetch
" \
155 "\\+?refs
/(top-bases|
heads
/[{]top-bases
[}])/\\*:refs
/remotes
/$r/(top-bases|
[{]top-bases
[}])/\\*")" &&
156 [ "$rf" != "$nv" ]; then
157 echo "remote.$r.fetch:"
159 printf ' -> %s\n' "$nv"
160 if [ -n "$force" ]; then
161 git config
--replace-all "remote.$r.fetch" "$nv" \
162 "\\+?refs/(top-bases|heads/[{]top-bases[}])/\\*:refs/remotes/$r/(top-bases|[{]top-bases[}])/\\*"
164 elif [ "$rf" != "$nv" ] && rf
="$(git config --get-all "remote.
$r.fetch
" "\\+?refs
/(top-bases|
heads
/[{]top-bases
[}])/.
*")"; then
165 echo "remote.$r.fetch may need manual updates of:"
170 while read -r rn rt rh
&& [ -n "$rn" ] && [ -n "$rt" ] && [ -n "$rh" ]; do
171 if [ -z "$orphans" ] && ! not_orphan_base
"$rn"; then
172 echo "skipping orphan base (use --orphans): $rn" >&2
175 if [ "$rt" = "tree" ] ||
[ "$rt" = "blob" ]; then
176 echo "ignoring base with type $rt: $rn" >&2
179 if [ "$rt" = "tag" ]; then
180 rnc
="$(git rev-parse --verify --quiet "$rh^
0" -- 2>/dev/null)" ||
:
181 if [ -z "$rnc" ]; then
182 echo "ignoring base with type tag of non-commit: $rn" >&2
185 echo "warning: resolving base with type tag to commit: $rn" >&2
188 v_transform_base newb
"$rn" || die
"unexpected non-bases ref: $rn"
189 newbrev
="$(git rev-parse --verify --quiet "$newb^
0" --)" ||
:
191 [ -z "$newbrev" ] || newbtype
="$(git cat-file -t "$newbrev")"
192 if [ "$newbtype" = "tree" ] ||
[ "$newbtype" = "blob" ]; then
193 echo "warning: $rn" >&2
194 echo " refusing to update existing ref:" >&2
196 echo " of type $newbtype" >&2
199 if [ "$newbtype" = "tag" ]; then
200 newbrev
="$(git rev-parse --verify --quiet "$newbrev^
0" -- 2>/dev/null)" ||
:
201 if [ -z "$newbrev" ]; then
202 echo "warning: $rn" >&2
203 echo " refusing to update existing ref:" >&2
205 echo " of type tag of non-commit" >&2
208 echo "warning: $rn" >&2
209 echo " treating existing ref:" >&2
211 echo " of type tag as the tagged commit" >&2
213 if [ -n "$newbrev" ] && [ "$newbrev" != "rh" ]; then
214 mb
="$(git merge-base "$newbrev" "$rh" 2>/dev/null)" ||
:
215 if [ "$mb" = "$newbrev" ]; then
216 echo "warning: $rn" >&2
217 echo " ignoring existing ref:" >&2
219 echo " since it's contained in $rn" >&2
220 elif [ "$mb" = "$rh" ]; then
221 echo "warning: $rn" >&2
222 echo " using existing value of ref:" >&2
224 echo " since it contains $rn" >&2
227 rd
="$(git --no-pager log -n 1 --format='format:%ct' "$rh" --)"
228 newbdt
="$(git --no-pager log -n 1 --format='format:%ct' "$newbrev" --)"
229 if [ "$rd" -ge "$newbdt" ]; then
230 echo "warning: $rn" >&2
231 echo " ignoring existing diverged ref:" >&2
233 echo " since it's got an older committer timestamp" >&2
235 echo "warning: $rn" >&2
236 echo " using existing value of diverged ref:" >&2
238 echo " since it's got a newer committer timestamp" >&2
243 printf 'update: %s\n -> %s\n' "$rn" "$newb"
244 if [ -n "$force" ]; then
245 git update-ref
"$newb" "$rh"
246 if [ "$(git rev-parse --quiet --verify "$newb^
0" --)" = "$rh" ] && [ "$newb" != "$rn" ]; then
247 git update-ref
-d "$rn"
251 $(git for-each-ref --format='%(refname) %(objecttype) %(objectname)' $refpats)
254 maindir
="$(cd "$git_common_dir" && pwd -P)"
255 if [ "$git_dir" = "$git_common_dir" ]; then
258 headdir
="$(cd "$git_dir" && pwd -P)"
261 posix_find_worktrees_HEAD
() (
262 # neither -mindepth nor -maxdepth are POSIX, grrrr
263 # however, POSIX has adopted -path
264 # $1 is the starting directory and the output lines,
265 # if any, will all start with "./"
267 exec find .
-path './HEAD' -prune -o -path '*/*/*/*' -prune -o -name HEAD
-type f
-print
270 # note that [ -n "$iowopt" ] will be true if linked worktrees are available
274 [ -d "$maindir/worktrees" ] &&
275 [ $
(( $
(posix_find_worktrees_HEAD
"$maindir/worktrees" |
wc -l) )) -gt 0 ]
280 # $1 => --git-dir to process (or try to anyway)
282 process_one_symref
() {
283 case "$processed" in *" $1 "*) return 0; esac
284 processed
="$processed$1 "
285 hsymref
="$(git --git-dir="$1" symbolic-ref --quiet HEAD -- 2>/dev/null)" &&
286 [ -n "$hsymref" ] ||
return 0
287 v_transform_base xsymref
"$hsymref" ||
return 0
288 [ -z "$_wasrevdir" ] ||
[ -n "$reverse" ] ||
return 0
289 [ -n "$_wasrevdir" ] ||
[ -z "$reverse" ] ||
return 0
290 [ -z "$_wasremote" ] ||
[ -z "$noremotes" ] ||
return 0
291 [ -n "$_wasremote" ] ||
[ -z "$remotesonly" ] ||
return 0
294 [ "$1" != "$headdir" ] ||
[ -z "$maybehaslw" ] || appellation
="$appelation ="
295 if [ -n "$maybehaslw" ]; then
296 if [ "$1" = "$maindir" ]; then
301 appellation
="${appellation:+$appellation }($workname)"
303 [ -z "$appellation" ] || appellation
=" [$appellation]"
304 printf 'symref: %s%s\n -> %s\n' "$hsymref" "$appellation" "$xsymref"
305 if [ -n "$force" ]; then
306 git
--git-dir="$1" symbolic-ref HEAD
"$xsymref" --
310 # always do our HEAD first
311 process_one_symref
"$headdir"
312 # then the main HEAD (it will automatically be ignored if a duplicate)
313 process_one_symref
"$maindir"
314 # then each worktree (again duplicates will be automatically ignored)
315 if [ -n "$maybehaslw" ]; then
318 wktree
="${wktree#./}" &&
321 process_one_symref
"$maindir/worktrees/${wktree%/HEAD}"
323 $(posix_find_worktrees_HEAD "$maindir/worktrees")