2 # TopGit revert command
3 # Copyright (C) 2015 Kyle J. McKay <mackyle@gmail.com>
7 USAGE
="Usage: ${tgname:-tg} [...] revert (-f | -i | -n) [-q] [--tgish-only] [--no-deps] [--no-stash] [--exclude <ref>...] (<tagname> | --stash) [<ref>...]"
8 USAGE
="$USAGE$lf Or: ${tgname:-tg} [...] revert [-l] [--no-short] [--hash] [--tgish-only] [(--deps | --rdeps)] [--exclude <ref>...] (<tagname> | --stash) [(--[topgit-]heads] | <ref>...)]"
12 if [ "${1:-0}" != 0 ]; then
13 printf '%s\n' "$USAGE" >&2
15 printf '%s\n' "$USAGE"
37 while [ $# -gt 0 ]; do case "$1" in
47 --short|
--short=*|
--no-short)
78 [ -n "${1#--exclude=}" ] || die
"--exclude= requires a ref name"
79 case "${1#--exclude=}" in refs/*) rn="${1#--exclude=}";; *) rn="refs/heads/${1#--exclude=} refs/$topbases/${1#--exclude=}"; esac
80 exclude
="$exclude $rn";;
83 [ -n "$1" ] || die
"--exclude requires a ref name"
84 case "$1" in refs
/*) rn
="$1";; *) rn
="refs/heads/$1 refs/$topbases/$1"; esac
85 exclude
="$exclude $rn";;
90 --stash|
--stash"@{"*"}")
94 echo "Unknown option: $1" >&2
101 [ -z "$exclude" ] || exclude
="$exclude "
103 [ -z "$list$short$hashonly" ] ||
[ -z "$force$interact$dryrun$nodeps$nostash" ] || usage
1
104 [ -z "$force$interact$dryrun" ] ||
[ -z "$list$short$hashonly$deps$rdeps" ] || usage
1
105 [ -z "$deps" ] ||
[ -z "$rdeps" ] || usage
1
106 [ -n "$list$force$interact$dryrun" ] || list
=1
107 [ -z "$list" ] ||
[ -n "$short" ] ||
if [ -n "$hashonly" ]; then short
="--no-short"; else short
="--short"; fi
108 [ -n "$1" ] ||
{ echo "Tag name required" >&2; usage
1; }
111 [ "$1" != "--heads-independent" ] ||
{ shift; set -- --heads "$@"; }
112 if [ "$1" = "--topgit-heads" ]; then
115 set -- "--heads" "$@"
117 [ -n "$list" ] ||
[ "$1" != "--heads" ] || usage
1
118 [ "$tagname" != "--stash" ] || tagname
=refs
/tgstash
119 case "$tagname" in --stash"@{"*"}")
120 strip
="${tagname#--stash??}"
122 tagname
="refs/tgstash@{$strip}"
125 case "$refname" in HEAD|refs
/*);;*)
126 suffix
="${refname%@*}"
127 suffix
="${refname#$suffix}"
128 refname
="${refname%$suffix}"
129 if reftest
="$(git rev-parse --revs-only --symbolic-full-name "$refname" -- 2>/dev/null)" &&
130 [ -n "$reftest" ]; then
131 refname
="$reftest$suffix"
133 if hash="$(git rev-parse --quiet --verify "$refname$suffix")"; then
136 refname
="refs/tags/$refname$suffix"
141 case "$refname" in refs
/tags
/*) tagname
="${refname#refs/tags/}";; *) reftype
=ref
; tagname
="$refname"; esac
142 git rev-parse
--verify --quiet "$refname^{tag}" -- >/dev
/null || die
"not annotated/signed tag: $refname"
143 tgf
="$(get_temp tag)"
144 trf
="$(get_temp refs)"
145 tagdataref
="$refname^{tag}"
147 git cat-file tag
"$tagdataref" >"$tgf"t || die
"cannot read tag: $refname"
148 sed -ne '/^-----BEGIN TOPGIT REFS-----$/,/^-----END TOPGIT REFS-----$/p' <"$tgf"t |
149 sed -ne "/^\\($octet20\\) \\(refs\/[^ $tab][^ $tab]*\\)\$/{s//\\2 \\1/;p;}" |
150 sed -e "s,^refs/$oldbases/,refs/$topbases/,g" |
151 sort -u -b -k1,1 >"$trf"
154 # If it's a tag of a tag, dereference it and try again
155 read -r field tagtype
<<-EOT || break
156 $(sed -n '1,/^$/p' <"$tgf" | grep '^type [^ ][^ ]*$' || :)
158 [ "$tagtype" = "tag" ] ||
break
159 read -r field tagdataref
<<-EOT || break
160 $(sed -n '1,/^$/p' <"$tgf" | grep '^object [^ ][^ ]*$' || :)
162 [ -n "$tagdataref" ] ||
break
163 tagdataref
="$tagdataref^{tag}"
164 git rev-parse
--verify --quiet "$tagdataref" -- >/dev
/null ||
break
166 [ -s "$trf" ] || die
"$reftype $tagname does not contain a TOPGIT REFS section"
167 rcnt
=$
(( $
(wc -l <"$trf") ))
168 vcnt
=$
(( $
(cut
-d ' ' -f 2 <"$trf" | git cat-file
$gcfbopt --batch-check='%(objectname)' |
grep -v ' missing$' |
wc -l) ))
169 [ "$rcnt" -eq "$vcnt" ] || die
"$reftype $tagname contains $rcnt ref(s) but only $vcnt are still valid"
170 cat "$trf" >"$tg_ref_cache"
177 printf '%s\n' "$1" |
join - "$trf" | cut
-d ' ' -f 2 ||
:
182 topics
="$(get_temp topics)"
183 topics2
="$(get_temp topics)"
184 deplist
="$(get_temp deplist)"
186 sed -e '\,^refs/'"$topbasesrx"'/,!d' -e 's,^refs/'"$topbasesrx"'/,refs/heads/,' -e 's/ .*//'
187 while read -r oneref
; do
188 _rev
="$(get_recorded_ref "$oneref")" && [ -n "$_rev" ] ||
continue
189 printf '%s\n' "$oneref" >>"$topics2"
190 git cat-file blob
"$_rev:.topdeps" 2>/dev
/null |
awk '{print}'
191 done <"$topics" |
sed -e 's,^,refs/heads/,' |
sort -u >"$deplist"
193 join -v 1 "$topics" "$deplist"
198 srt
="$(get_temp sort)"
199 sort -b -k2,2 <"$trf" >"$srt"
200 git merge-base
--independent $
(cut
-d ' ' -f 2 <"$srt") |
202 join -2 2 -o 2.1 - "$srt" |
208 if [ -n "$headstopgit" ]; then
209 show_topgit_heads
"$@"
211 show_indep_heads
"$@"
215 [ $# -ne 0 ] ||
[ -z "$rdeps$deps" ] ||
{ set -- --heads; headstopgit
=1; }
216 [ $# -ne 1 ] ||
[ -z "$deps" ] ||
[ "$1" != "--heads" ] ||
{ deps
=; set --; }
217 if [ $# -eq 1 ] && [ "$1" = "--heads" ]; then
224 ref_exists
"refs/heads/${1#refs/$topbases/}"
227 ref_exists
"refs/$topbases/${1#refs/heads/}"
238 case "$b" in refs
/*) exp
=1; rn
="$b";; *) rn
="refs/heads/$b"; esac
239 ref_exists
"$rn" || die
"not present in tag data (try --list): $rn"
240 case " $refs " in *" $rn "*);;*)
241 refs
="${refs:+$refs }$rn"
242 if [ -z "$list" ] && { [ -z "$nodeps" ] ||
[ -z "$exp" ]; } && is_tgish
"$rn"; then
245 refs
="$refs refs/heads/${rn#refs/$topbases/}"
248 refs
="$refs refs/$topbases/${rn#refs/heads/}"
256 case "$exclude" in *" refs/heads/$_dep "*) return; esac
257 case " $seen_deps " in *" $_dep "*) return 0; esac
258 seen_deps
="${seen_deps:+$seen_deps }$_dep"
259 [ -z "$tgish" ] ||
[ -n "$_dep_is_tgish" ] ||
return 0
260 printf 'refs/heads/%s\n' "$_dep"
261 [ -z "$_dep_is_tgish" ] ||
262 printf 'refs/%s/%s\n' "$topbases" "$_dep"
268 recurse_deps_exclude
=
269 while read _b
&& [ -n "$_b" ]; do
270 case "$exclude" in *" $_b "*) continue; esac
271 if ! is_tgish
"$_b"; then
272 [ -z "$tgish" ] ||
continue
276 case "$_b" in refs
/"$topbases"/*) _b
="refs/heads/${_b#refs/$topbases/}"; esac
277 _b
="${_b#refs/heads/}"
278 case " $recurse_deps_exclude " in *" $_b "*) continue; esac
280 _dep
="$_b"; _dep_is_tgish
=1; show_dep
281 recurse_deps show_dep
"$_b"
282 recurse_deps_exclude
="$recurse_deps_exclude $seen_deps"
288 case "$exclude" in *" refs/heads/$_dep "*) return; esac
289 [ -z "$tgish" ] ||
[ -n "$_dep_is_tgish" ] ||
return 0
290 v_ref_exists_rev_short _depshort
"refs/heads/$_dep" $short
291 if [ -n "$hashonly" ]; then
292 printf '%s %s\n' "$_depchain" "$_depshort"
294 printf '%s %s\n' "$_depchain" "${_depshort}~$_dep"
303 [ "$short" != "--short" ] || v_get_core_abbrev _dummy
304 while read _b
&& [ -n "$_b" ]; do
305 case "$exclude" in *" $_b "*) continue; esac
306 if ! is_tgish
"$_b"; then
307 [ -z "$tgish" ] ||
continue
308 [ -z "$showbreak" ] ||
echo
310 v_ref_exists_rev_short _bshort
"refs/heads/$_b" $short
311 if [ -n "$hashonly" ]; then
312 printf '%s\n' "$_bshort"
314 printf '%s\n' "${_bshort}~$_b"
318 case "$_b" in refs
/"$topbases"/*) _b
="refs/heads/${_b#refs/$topbases/}"; esac
319 _b
="${_b#refs/heads/}"
320 case " $seen_deps " in *" $_b "*) continue; esac
321 seen_deps
="$seen_deps $_b"
322 [ -z "$showbreak" ] ||
echo
324 v_ref_exists_rev_short _bshort
"refs/heads/$_b" $short
326 if [ -n "$hashonly" ]; then
327 printf '%s\n' "$_bshort"
329 printf '%s\n' "${_bshort}~$_b"
332 recurse_deps show_rdep
"$_b"
333 } |
sed -e 's/[^ ][^ ]*[ ]/ /g' -e 's/~/ /'
338 [ -z "$refs" ] ||
sed 'y/ /\n/' <<-EOT
343 if [ -n "$list" ]; then
344 if [ -z "$deps$rdeps" ]; then
345 # accelerate showing everything in full
346 if [ -z "$refs$exclude" ] && [ z
"$short" = z
"--no-short" ]; then
347 if [ -n "$hashonly" ]; then
348 <"$trf" sed -n 's/^[^ ][^ ]* \([^ ][^ ]*\)$/\1/p'
350 <"$trf" sed -n 's/^\([^ ][^ ]*\) \([^ ][^ ]*\)$/\2 \1/p'
354 while read -r name
rev; do
355 case "$exclude" in *" $name "*) continue; esac
356 [ -z "$refs" ] ||
case " $refs " in *" $name "*);;*) continue; esac
357 [ -z "$tgish" ] || is_tgish
"$name" ||
continue
358 if [ -n "$hashonly" ]; then
359 printf '%s\n' "$(git rev-parse --verify --quiet $short "$rev" --)"
361 printf '%s %s\n' "$(git rev-parse --verify --quiet $short "$rev" --)" "$name"
366 if [ -n "$deps" ]; then
367 refslist | show_deps |
sort -u -b -k1,1 |
369 while read -r name
rev; do
370 if [ -n "$hashonly" ]; then
371 printf '%s\n' "$(git rev-parse --verify --quiet $short "$rev" --)"
373 printf '%s %s\n' "$(git rev-parse --verify --quiet $short "$rev" --)" "$name"
378 refslist | show_rdeps
381 insn
="$(get_temp isns)"
384 [ -n "$interact" ] ||
{ printf '%s' "$1"; return 0; }
385 git rev-parse
--verify --quiet --short "$1" --
388 if [ -n "$nodeps" ] ||
[ -z "$refs" ]; then
389 while read -r name
rev; do
390 case "$exclude" in *" $name "*) continue; esac
391 [ -z "$refs" ] ||
case " $refs " in *" $name "*);;*) continue; esac
392 [ -z "$tgish" ] || is_tgish
"$name" ||
continue
393 printf 'revert %s %s\n' "$(get_short "$rev")" "$name"
394 done <"$trf" |
sort -u -b -k3,3 >"$insn"
396 refslist | show_deps |
sort -u -b -k1,1 |
398 while read -r name
rev; do
399 printf 'revert %s %s\n' "$(get_short "$rev")" "$name"
402 if [ -n "$interact" ]; then
403 count
=$
(( $
(wc -l <"$insn") ))
406 # Revert using $refname data ($count command(s))
409 # r, revert = revert ref to specified hash
411 # Note that changing the hash value shown here will have NO EFFECT.
413 # If you remove a line here THAT REVERT WILL BE SKIPPED.
415 # However, if you remove everything, the revert will be aborted.
417 run_editor
"$insn" ||
418 die
"there was a problem with the editor '$tg_editor'"
419 git stripspace
-s <"$insn" >"$insn"+
420 mv -f "$insn"+ "$insn"
421 [ -s "$insn" ] || die
"nothing to do"
422 while read -r op
hash ref
; do
423 [ "$op" = "r" ] ||
[ "$op" = "revert" ] ||
424 die
"invalid op in instruction: $op $hash $ref"
425 case "$ref" in refs
/?
*);;*)
426 die
"invalid ref in instruction: $op $hash $ref"
429 die
"unknown ref in instruction: $op $hash $ref"
432 msg
="tgrevert: $reftype $tagname ($(( $(wc -l <"$insn") )) command(s))"
433 if [ -z "$dryrun" ]; then
434 if [ -z "$nostash" ]; then
435 tg tag
-q -q --allow-any --none-ok -m "$msg" --stash || die
"requested --stash failed"
437 tg tag
--allow-any --none-ok --anonymous || die
"anonymous --stash failed"
440 refwidth
="$(git config --get --int core.abbrev 2>/dev/null)" ||
:
441 [ -n "$refwidth" ] || refwidth
=7
442 [ $refwidth -ge 4 ] && [ $refwidth -le 40 ] || refwidth
=7
443 nullref
="$(printf '%.*s' $refwidth "$nullsha")"
444 notewidth
=$
(( $refwidth + 4 + $refwidth ))
446 [ -n "$dryrun" ] || srh
="$(git symbolic-ref --quiet HEAD)" ||
:
447 cut
-d ' ' -f 3 <"$insn" |
sort -u -b -k1,1 |
join - "$trf" |
448 while read -r name
rev; do
449 orig
="$(git rev-parse --verify --quiet "$name" --)" ||
:
451 if [ "$rev" != "$orig" ]; then
452 [ -z "$dryrun" ] && [ -n "$quiet" ] ||
453 origsh
="$(git rev-parse --verify --short --quiet "$name" --)" ||
:
454 if [ -z "$dryrun" ]; then
455 if [ -n "$srh" ] && [ "$srh" = "$name" ]; then
456 [ -n "$quiet" ] || warn
"detaching HEAD to revert $name"
458 [ -n "$detachat" ] || detachat
="$(make_empty_commit)"
459 git update-ref
-m "tgrevert: detach HEAD to revert $name" --no-deref HEAD
"$detachat" || die
"detach failed"
460 [ -n "$quiet" ] || warn
"$(git --no-pager log -n 1 --format=format:'HEAD is now at %h... %s' HEAD)"
462 git update-ref
-m "$msg" "$name" "$rev"
464 if [ -n "$dryrun" ] ||
[ -z "$quiet" ]; then
465 revsh
="$(git rev-parse --verify --short --quiet "$rev" --)" ||
:
466 if [ -n "$origsh" ]; then
468 [ -z "$dryrun" ] || hdr
='-'
469 printf '%s %s -> %s %s\n' "$hdr" "$origsh" "$revsh" "$name"
472 [ -z "$dryrun" ] || hdr
='-'
473 printf '%s %s -> %s %s\n' "$hdr" "$nullref" "$revsh" "$name"
477 : #[ -z "$dryrun" ] && [ -n "$quiet" ] || printf "* %-*s %s\n" $notewidth "[no change]" "$name"