2 # TopGit revert command
3 # Copyright (C) 2015 Kyle J. McKay <mackyle@gmail.com>
7 lf
="$(printf '\n.')" && lf
="${lf%?}"
8 tab
="$(printf '\t.')" && tab
="${tab%?}"
9 USAGE
="Usage: ${tgname:-tg} [...] revert (-f | -i | -n) [-q] [--tgish-only] [--no-deps] [--no-stash] [--exclude <ref>...] (<tagname> | --stash) [<ref>...]"
10 USAGE
="$USAGE$lf Or: ${tgname:-tg} [...] revert [-l] [--tgish-only] [(--deps | --rdeps)] [--exclude <ref>...] (<tagname> | --stash) [(--heads | <ref>...)]"
14 if [ "${1:-0}" != 0 ]; then
15 printf '%s\n' "$USAGE" >&2
17 printf '%s\n' "$USAGE"
36 while [ $# -gt 0 ]; do case "$1" in
71 [ -n "${1#--exclude=}" ] || die
"--exclude= requires a ref name"
72 case "${1#--exclude=}" in refs/*) rn="${1#--exclude=}";; *) rn="refs/heads/${1#--exclude=} refs/$topbases/${1#--exclude=}"; esac
73 exclude
="$exclude $rn";;
76 [ -n "$1" ] || die
"--exclude requires a ref name"
77 case "$1" in refs
/*) rn
="$1";; *) rn
="refs/heads/$1 refs/$topbases/$1"; esac
78 exclude
="$exclude $rn";;
83 --stash|
--stash"@{"*"}")
87 echo "Unknown option: $1" >&2
94 [ -z "$exclude" ] || exclude
="$exclude "
96 [ -z "$list" -o -z "$force$interact$dryrun$nodeps$nostash" ] || usage
1
97 [ -z "$force$interact$dryrun" -o -z "$list$deps$rdeps" ] || usage
1
98 [ -z "$deps" -o -z "$rdeps" ] || usage
1
99 [ -n "$list$force$interact$dryrun" ] || list
=1
100 [ -n "$1" ] ||
{ echo "Tag name required" >&2; usage
1; }
103 [ -n "$list" -o "$1" != "--heads" ] || usage
1
104 [ "$tagname" != "--stash" ] || tagname
=refs
/tgstash
105 case "$tagname" in --stash"@{"*"}")
106 strip
="${tagname#--stash??}"
108 tagname
="refs/tgstash@{$strip}"
111 case "$refname" in HEAD|refs
/*);;*)
112 suffix
="${refname%@*}"
113 suffix
="${refname#$suffix}"
114 refname
="${refname%$suffix}"
115 if reftest
="$(git rev-parse --revs-only --symbolic-full-name "$refname" -- 2>/dev/null)" &&
116 [ -n "$reftest" ]; then
117 refname
="$reftest$suffix"
119 if hash="$(git rev-parse --quiet --verify "$refname$suffix")"; then
122 refname
="refs/tags/$refname$suffix"
127 case "$refname" in refs
/tags
/*) tagname
="${refname#refs/tags/}";; *) reftype
=ref
; tagname
="$refname"; esac
128 git rev-parse
--verify --quiet "$refname^{tag}" -- >/dev
/null || die
"not annotated/signed tag: $refname"
129 tgf
="$(get_temp tag)"
130 trf
="$(get_temp refs)"
131 tagdataref
="$refname^{tag}"
133 git cat-file tag
"$tagdataref" >"$tgf" || die
"cannot read tag: $refname"
134 sed -ne '/^-----BEGIN TOPGIT REFS-----$/,/^-----END TOPGIT REFS-----$/p' <"$tgf" |
135 sed -ne "/^\\($octet20\\) \\(refs\/[^ $tab][^ $tab]*\\)\$/{s//\\2 \\1/;p;}" |
136 LC_ALL
=C
sort -u -b -k1,1 >"$trf"
139 # If it's a tag of a tag, dereference it and try again
140 read -r field tagtype
<<-EOT || break
141 $(sed -n '1,/^$/p' <"$tgf" | grep '^type [^ ][^ ]*$' || :)
143 [ "$tagtype" = "tag" ] ||
break
144 read -r field tagdataref
<<-EOT || break
145 $(sed -n '1,/^$/p' <"$tgf" | grep '^object [^ ][^ ]*$' || :)
147 [ -n "$tagdataref" ] ||
break
148 tagdataref
="$tagdataref^{tag}"
149 git rev-parse
--verify --quiet "$tagdataref" -- >/dev
/null ||
break
151 [ -s "$trf" ] || die
"$reftype $tagname does not contain a TOPGIT REFS section"
152 rcnt
=$
(( $
(wc -l <"$trf") ))
153 vcnt
=$
(( $
(cut
-d ' ' -f 2 <"$trf" | git cat-file
--batch-check='%(objectname)' |
grep -v ' missing$' |
wc -l) ))
154 [ "$rcnt" -eq "$vcnt" ] || die
"$reftime $tagname contains $rcnt ref(s) but only $vcnt are still valid"
155 cat "$trf" >"$tg_ref_cache"
160 [ $# -ne 0 -o -z "$rdeps$deps" ] ||
set -- --heads
161 [ $# -ne 1 -o -z "$deps" -o "$1" != "--heads" ] ||
{ deps
=; set --; }
162 if [ $# -eq 1 -a "$1" = "--heads" ]; then
163 srt
="$(get_temp sort)"
164 LC_ALL
=C
sort -b -k2,2 <"$trf" >"$srt"
166 git merge-base
--independent $
(cut
-d ' ' -f 2 <"$srt") |
167 LC_ALL
=C
sort -b -k1,1 |
168 join -2 2 -o 2.1 - "$srt" |
175 ref_exists
"refs/$topbases/${1#refs/heads/}"
178 ref_exists
"refs/heads/${1#refs/$topbases/}"
189 case "$b" in refs
/*) exp
=1; rn
="$b";; *) rn
="refs/heads/$b"; esac
190 ref_exists
"$rn" || die
"not present in tag data (try --list): $rn"
191 case " $refs " in *" $rn "*);;*)
192 refs
="${refs:+$refs }$rn"
193 if [ -z "$list" ] && [ -z "$nodeps" -o -z "$exp" ] && is_tgish
"$rn"; then
196 refs
="$refs refs/$topbases/${rn#refs/heads/}"
199 refs
="$refs refs/heads/${rn#refs/$topbases/}"
207 case "$exclude" in *" refs/heads/$_dep "*) return; esac
208 case " $seen_deps " in *" $_dep "*) return 0; esac
209 seen_deps
="${seen_deps:+$seen_deps }$_dep"
210 [ -z "$tgish" -o -n "$_dep_is_tgish" ] ||
return 0
211 printf 'refs/heads/%s\n' "$_dep"
212 [ -z "$_dep_is_tgish" ] ||
213 printf 'refs/%s/%s\n' "$topbases" "$_dep"
219 recurse_deps_exclude
=
220 while read _b
&& [ -n "$_b" ]; do
221 case "$exclude" in *" $_b "*) continue; esac
222 if ! is_tgish
"$_b"; then
223 [ -z "$tgish" ] ||
continue
227 case "$_b" in refs
/"$topbases"/*) _b
="refs/heads/${_b#refs/$topbases/}"; esac
228 _b
="${_b#refs/heads/}"
229 case " $recurse_deps_exclude " in *" $_b "*) continue; esac
231 _dep
="$_b"; _dep_is_tgish
=1; show_dep
232 recurse_deps show_dep
"$_b"
233 recurse_deps_exclude
="$recurse_deps_exclude $seen_deps"
239 case "$exclude" in *" refs/heads/$_dep "*) return; esac
240 [ -z "$tgish" -o -n "$_dep_is_tgish" ] ||
return 0
241 printf '%s %s\n' "$_depchain" "$(ref_exists_rev_short "refs
/heads
/$_dep")~$_dep"
249 while read _b
&& [ -n "$_b" ]; do
250 case "$exclude" in *" $_b "*) continue; esac
251 if ! is_tgish
"$_b"; then
252 [ -z "$tgish" ] ||
continue
253 [ -z "$showbreak" ] ||
echo
258 case "$_b" in refs
/"$topbases"/*) _b
="refs/heads/${_b#refs/$topbases/}"; esac
259 _b
="${_b#refs/heads/}"
260 case " $seen_deps " in *" $_b "*) continue; esac
261 seen_deps
="$seen_deps $_b"
262 [ -z "$showbreak" ] ||
echo
265 echo "$(ref_exists_rev_short "refs
/heads
/$_b")~$_b"
267 recurse_deps show_rdep
"$_b"
268 } |
sed -e 's/[^ ][^ ]*[ ]/ /g' -e 's/~/ /'
273 [ -z "$refs" ] ||
sed 'y/ /\n/' <<-EOT
278 if [ -n "$list" ]; then
279 if [ -z "$deps$rdeps" ]; then
280 while read -r name
rev; do
281 case "$exclude" in *" $name "*) continue; esac
282 [ -z "$refs" ] ||
case " $refs " in *" $name "*);;*) continue; esac
283 [ -z "$tgish" ] || is_tgish
"$name" ||
continue
284 printf '%s %s\n' "$(git rev-parse --verify --quiet --short "$rev" --)" "$name"
288 if [ -n "$deps" ]; then
289 refslist | show_deps | LC_ALL
=C
sort -u -b -k1,1 |
291 while read -r name
rev; do
292 printf '%s %s\n' "$(git rev-parse --verify --quiet --short "$rev" --)" "$name"
296 refslist | show_rdeps
299 insn
="$(get_temp isns)"
302 [ -n "$interact" ] ||
{ printf '%s' "$1"; return 0; }
303 git rev-parse
--verify --quiet --short "$1" --
306 if [ -n "$nodeps" -o -z "$refs" ]; then
307 while read -r name
rev; do
308 case "$exclude" in *" $name "*) continue; esac
309 [ -z "$refs" ] ||
case " $refs " in *" $name "*);;*) continue; esac
310 [ -z "$tgish" ] || is_tgish
"$name" ||
continue
311 printf 'revert %s %s\n' "$(get_short "$rev")" "$name"
312 done <"$trf" | LC_ALL
=C
sort -u -b -k3,3 >"$insn"
314 refslist | show_deps | LC_ALL
=C
sort -u -b -k1,1 |
316 while read -r name
rev; do
317 printf 'revert %s %s\n' "$(get_short "$rev")" "$name"
320 if [ -n "$interact" ]; then
321 count
=$
(( $
(wc -l <"$insn") ))
324 # Revert using $refname data ($count command(s))
327 # r, revert = revert ref to specified hash
329 # Note that changing the hash value shown here will have NO EFFECT.
331 # If you remove a line here THAT REVERT WILL BE SKIPPED.
333 # However, if you remove everything, the revert will be aborted.
335 run_editor
"$insn" ||
336 die
"there was a problem with the editor '$tg_editor'"
337 git stripspace
-s <"$insn" >"$insn"+
338 mv -f "$insn"+ "$insn"
339 [ -s "$insn" ] || die
"nothing to do"
340 while read -r op
hash ref
; do
341 [ "$op" = "r" -o "$op" = "revert" ] ||
342 die
"invalid op in instruction: $op $hash $ref"
343 case "$ref" in refs
/?
*);;*)
344 die
"invalid ref in instruction: $op $hash $ref"
347 die
"unknown ref in instruction: $op $hash $ref"
350 msg
="tgrevert: $reftype $tagname ($(( $(wc -l <"$insn") )) command(s))"
351 [ -n "$dryrun" -o -n "$nostash" ] ||
$tg tag
-q --none-ok -m "$msg" --stash
352 refwidth
="$(git config --get --int core.abbrev 2>/dev/null || :)"
353 [ -n "$refwidth" ] || refwidth
=7
354 [ $refwidth -ge 4 -a $refwidth -le 40 ] || refwidth
=7
355 nullref
="$(printf '%.*s' $refwidth "$nullsha")"
356 notewidth
=$
(( $refwidth + 4 + $refwidth ))
358 [ -n "$dryrun" ] || srh
="$(git symbolic-ref --quiet HEAD || :)"
359 cut
-d ' ' -f 3 <"$insn" | LC_ALL
=C
sort -u -b -k1,1 |
join - "$trf" |
360 while read -r name
rev; do
361 orig
="$(git rev-parse --verify --quiet "$name" -- || :)"
362 if [ -n "$logrefupdates" -o "$name" = "refs/tgstash" ]; then
363 case "$name" in refs
/heads
/*|refs
/"$topbases"/*|refs
/tgstash
)
364 mkdir
-p "$git_dir/logs/$(dirname "$name")" 2>/dev
/null ||
:
365 { >>"$git_dir/logs/$name" ||
:; } 2>/dev
/null
368 if [ "$rev" != "$orig" ]; then
369 [ -z "$dryrun" -a -n "$quiet" ] ||
370 origsh
="$(git rev-parse --verify --short --quiet "$name" -- || :)"
371 if [ -z "$dryrun" ]; then
372 if [ -n "$srh" ] && [ "$srh" = "$name" ]; then
373 [ -n "$quiet" ] ||
echo "Detaching HEAD to revert $name"
375 [ -n "$detachat" ] || detachat
="$(make_empty_commit)"
376 git update-ref
-m "tgrevert: detach HEAD to revert $name" --no-deref HEAD
"$detachat"
377 [ -n "$quiet" ] || git log
-n 1 --format=format
:'HEAD is now at %h... %s' HEAD
379 git update-ref
-m "$msg" "$name" "$rev"
381 if [ -n "$dryrun" -o -z "$quiet" ]; then
382 revsh
="$(git rev-parse --verify --short --quiet "$rev" -- || :)"
383 if [ -n "$origsh" ]; then
385 [ -z "$dryrun" ] || hdr
='-'
386 printf '%s %s -> %s %s\n' "$hdr" "$origsh" "$revsh" "$name"
389 [ -z "$dryrun" ] || hdr
='-'
390 printf '%s %s -> %s %s\n' "$hdr" "$nullref" "$revsh" "$name"
394 : #[ -z "$dryrun" -a -n "$quiet" ] || printf "* %-*s %s\n" $notewidth "[no change]" "$name"