tg-update.sh: reduce git merge spew
[topgit/pro.git] / tg-tag.sh
blob184be790a7171ec41661ea67a47798231ea5941a
1 #!/bin/sh
2 # TopGit tag command
3 # Copyright (C) 2015 Kyle J. McKay <mackyle@gmail.com>
4 # All rights reserved.
5 # GPLv2
7 lf="$(printf '\n.')" && lf="${lf%?}"
8 tab="$(printf '\t.')" && tab="${tab%?}"
9 USAGE="Usage: ${tgname:-tg} [...] tag [-s | -u <key-id>] [-f] [-q] [--no-edit] [-m <msg> | -F <file>] (<tagname> | --refs) [<branch>...]"
10 USAGE="$USAGE$lf Or: ${tgname:-tg} [...] tag (-g | --reflog) [--reflog-message | --commit-message] [--no-type] [-n <number> | -number] [<tagname>]"
11 USAGE="$USAGE$lf Or: ${tgname:-tg} [...] tag (--clear | --delete) <tagname>"
12 USAGE="$USAGE$lf Or: ${tgname:-tg} [...] tag --drop <tagname>@{n}"
14 usage()
16 if [ "${1:-0}" != 0 ]; then
17 printf '%s\n' "$USAGE" >&2
18 else
19 printf '%s\n' "$USAGE"
21 exit ${1:-0}
24 ## Parse options
26 signed=
27 keyid=
28 force=
29 msg=
30 msgfile=
31 noedit=
32 defnoedit=
33 refsonly=
34 maxcount=
35 reflog=
36 outofdateok=
37 anyrefok=
38 defbranch=HEAD
39 stash=
40 reflogmsg=
41 notype=
42 setreflogmsg=
43 quiet=
44 noneok=
45 clear=
46 delete=
47 drop=
49 is_numeric()
51 [ -n "$1" ] || return 1
52 while [ -n "$1" ]; do
53 case "$1" in
54 [0-9]*)
55 set -- "${1#?}";;
57 break;;
58 esac
59 done
60 [ -z "$1" ]
63 while [ $# -gt 0 ]; do case "$1" in
64 -h|--help)
65 usage
67 -q|--quiet)
68 quiet=1
70 --none-ok)
71 noneok=1
73 --clear)
74 clear=1
76 --delete)
77 delete=1
79 --drop)
80 drop=1
82 -g|--reflog|--walk-reflogs)
83 reflog=1
85 --reflog-message)
86 reflogmsg=1
87 setreflogmsg=1
89 --no-reflog-message|--commit-message)
90 reflogmsg=
91 setreflogmsg=1
93 --no-type)
94 notype=1
96 -s|--sign)
97 signed=1
99 -u|--local-user|--local-user=*)
100 case "$1" in --local-user=*)
101 x="$1"
102 shift
103 set -- --local-user "${x#--local-user=}" "$@"
104 esac
105 if [ $# -lt 2 ]; then
106 echo "The $1 option requires an argument" >&2
107 usage 1
109 shift
110 keyid="$1"
112 -f|--force)
113 force=1
115 --no-edit)
116 noedit=1
118 --edit)
119 noedit=0
121 --allow-outdated)
122 outofdateok=1
124 --allow-any)
125 anyrefok=1
127 --refs|--refs-only)
128 refsonly=1
130 -m|--message|--message=*)
131 case "$1" in --message=*)
132 x="$1"
133 shift
134 set -- --message "${x#--message=}" "$@"
135 esac
136 if [ $# -lt 2 ]; then
137 echo "The $1 option requires an argument" >&2
138 usage 1
140 shift
141 msg="$1"
143 -F|--file|--file=*)
144 case "$1" in --file=*)
145 x="$1"
146 shift
147 set -- --file "${x#--file=}" "$@"
148 esac
149 if [ $# -lt 2 ]; then
150 echo "The $1 option requires an argument" >&2
151 usage 1
153 shift
154 msgfile="$1"
156 -n|--max-count|--max-count=*|-[1-9]*)
157 case "$1" in --max-count=*)
158 x="$1"
159 shift
160 set -- --max-count "${x#--max-count=}" "$@"
161 esac
162 case "$1" in -[1-9]*)
163 x="${1#-}"
164 shift
165 set -- -n "$x" "$@"
166 esac
167 if [ $# -lt 2 ]; then
168 echo "The $1 option requires an argument" >&2
169 usage 1
171 shift
172 maxcount="$1"
175 shift
176 break
178 --all)
179 break
181 --stash|--stash"@{"*"}")
182 if [ -n "$reflog" ]; then
183 case "$2" in -[1-9]*)
184 x1="$1"
185 x2="$2"
186 shift
187 shift
188 set -- "$x2" "$x1" "$@"
189 continue
190 esac
192 stash=1
193 defbranch=--all
194 break
196 -?*)
197 echo "Unknown option: $1" >&2
198 usage 1
201 if [ -n "$reflog" ]; then
202 case "$2" in -[1-9]*)
203 x1="$1"
204 x2="$2"
205 shift
206 shift
207 set -- "$x2" "$x1" "$@"
208 continue
209 esac
211 break
213 esac; shift; done
215 [ -z "$stash" -o -n "$reflog$drop$clear$delete" ] || { outofdateok=1; force=1; defnoedit=1; }
216 [ -n "$noedit" ] || noedit="$defnoedit"
217 [ "$noedit" != "0" ] || noedit=
218 [ -z "$reflog" -o -z "$drop$clear$delete$signed$keyid$force$msg$msgfile$noedit$refsonly$outofdateok" ] || usage 1
219 [ -n "$reflog" -o -z "$setreflogmsg$notype$maxcount" ] || usage 1
220 [ -z "$drop$clear$delete" -o -z "$setreflogmsg$notype$maxcount$signed$keyid$force$msg$msgfile$noedit$refsonly$outofdateok" ] || usage 1
221 [ -z "$reflog$drop$clear$delete" -o "$reflog$drop$clear$delete" = "1" ] || usage 1
222 [ -z "$maxcount" ] || is_numeric "$maxcount" || die "invalid count: $maxcount"
223 [ -z "$maxcount" ] || [ $maxcount -gt 0 ] || die "invalid count: $maxcount"
224 [ -z "$msg" -o -z "$msgfile" ] || die "only one -F or -m option is allowed."
225 [ -z "$refsonly" ] || set -- refs..only "$@"
226 [ $# -gt 0 -o -z "$reflog" ] || set -- --stash
227 [ -n "$1" ] || { echo "Tag name required" >&2; usage 1; }
228 tagname="$1"
229 shift
230 [ "$tagname" != "--stash" ] || tagname=refs/tgstash
231 case "$tagname" in --stash"@{"*"}")
232 strip="${tagname#--stash??}"
233 strip="${strip%?}"
234 tagname="refs/tgstash@{$strip}"
235 esac
236 refname="$tagname"
237 sfx=
238 case "$refname" in [!@]*"@{"*"}")
239 sfx="$refname"
240 refname="${refname%@*}"
241 sfx="${sfx#$refname}"
242 esac
243 case "$refname" in HEAD|refs/*) :;; *)
244 if reftest="$(git rev-parse --revs-only --symbolic-full-name "$refname" -- 2>/dev/null)" && \
245 [ -n "$reftest" ]; then
246 if [ -n "$reflog$drop$clear$delete" ]; then
247 refname="$reftest"
248 else
249 case "$reftest" in
250 refs/tags/*|refs/tgstash)
251 refname="$reftest"
254 refname="refs/tags/$refname"
255 esac
257 else
258 refname="refs/tags/$refname"
260 esac
261 refname="$refname$sfx"
262 reftype=tag
263 case "$refname" in refs/tags/*) tagname="${refname#refs/tags/}";; *) reftype=ref; tagname="$refname"; esac
264 [ -z "$reflog$drop$clear$delete" -o $# -eq 0 ] || usage 1
265 if [ -n "$drop$clear$delete" ]; then
266 if [ -n "$sfx" ]; then
267 [ -z "$clear$delete" ] || die "invalid ref name ($sfx suffix not allowed): $refname"
268 else
269 [ -z "$drop" ] || die "invalid reflog name (@{n} suffix required): $refname"
271 old="$(git rev-parse --verify --quiet --short "${refname%$sfx}" --)" || die "no such ref: ${refname%$sfx}"
272 if [ -n "$delete" ]; then
273 git update-ref -d "$refname" || die "git update-ref -d failed"
274 printf "Deleted $reftype '%s' (was %s)\n" "$tagname" "$old"
275 exit 0
276 elif [ -n "$clear" ]; then
277 [ -f "$git_dir/logs/$refname" ] || die "no reflog found for: $refname"
278 [ -s "$git_dir/logs/$refname" ] || die "empty reflog found for: $refname"
279 cp -p "$git_dir/logs/$refname" "$git_dir/logs/$refname^-+" || die "cp failed"
280 sed -n '$p' <"$git_dir/logs/$refname^-+" >"$git_dir/logs/$refname" || die "reflog clear failed"
281 rm -f "$git_dir/logs/$refname^-+"
282 printf "Cleared $reftype '%s' reflog to single @{0} entry\n" "$tagname"
283 exit 0
284 else
285 old="$(git rev-parse --verify --short "$refname" --)" || exit 1
286 git reflog delete --rewrite --updateref "$refname" || die "reflog drop failed"
287 printf "Dropped $reftype '%s' reflog entry (was %s)\n" "$tagname" "$old"
288 exit 0
291 if [ -n "$reflog" ]; then
292 [ "$tagname" != "refs/tgstash" -o -n "$setreflogmsg" ] || reflogmsg=1
293 git rev-parse --verify --quiet "$refname" -- >/dev/null || \
294 die "no such ref: $refname"
295 [ -s "$git_dir/logs/$refname" ] || \
296 die "no reflog present for $reftype: $tagname"
297 showref="$(git rev-parse --revs-only --abbrev-ref=strict "$refname" --)"
298 hashcolor=
299 resetcolor=
300 if git config --get-colorbool color.tgtag; then
301 metacolor="$(git config --get-color color.tgtag.meta)"
302 [ -n "$metacolor" ] || metacolor="$(git config --get-color color.diff.meta "bold")"
303 hashcolor="$(git config --get-color color.tgtag.commit)"
304 [ -n "$hashcolor" ] || hashcolor="$(git config --get-color color.diff.commit "yellow")"
305 datecolor="$(git config --get-color color.tgtag.date "bold blue")"
306 timecolor="$(git config --get-color color.tgtag.time "green")"
307 resetcolor="$(git config --get-color "" reset)"
309 setup_strftime
310 output()
312 sed 's/[^ ][^ ]* //' <"$git_dir/logs/$refname" | \
313 awk '{a[i++]=$0} END {for (j=i-1; j>=0;) print a[j--]}' | \
314 git cat-file --batch-check='%(objectname) %(objecttype) %(rest)' | \
316 stashnum=-1
317 lastdate=
318 while read -r newrev type rest; do
319 stashnum=$(( $stashnum + 1 ))
320 [ "$type" != "missing" ] || continue
321 IFS="$tab" read -r cmmttr msg <<-~EOT~
322 $rest
323 ~EOT~
324 ne="${cmmttr% *}"
325 ne="${ne% *}"
326 es="${cmmttr#$ne}"
327 es="${es% *}"
328 es="${es# }"
329 obj="$(git rev-parse --verify --quiet --short "$newrev" --)"
330 extra=
331 [ "$type" = "tag" -o -n "$notype" ] || \
332 extra="$hashcolor($metacolor$type$resetcolor$hashcolor)$resetcolor "
333 if [ -z "$reflogmsg" -o -z "$msg" ]; then
334 objmsg=
335 if [ "$type" = "tag" ]; then
336 objmsg="$(git cat-file tag "$obj" | \
337 sed '1,/^$/d' | sed '/^$/,$d')"
338 elif [ "$type" = "commit" ]; then
339 objmsg="$(git log -n 1 --format='format:%s' "$obj" --)"
341 [ -z "$objmsg" ] || msg="$objmsg"
343 read newdate newtime <<-EOT
344 $(strftime "%Y-%m-%d %H:%M:%S" "$es")
346 if [ "$lastdate" != "$newdate" ]; then
347 printf '%s=== %s ===%s\n' "$datecolor" "$newdate" "$resetcolor"
348 lastdate="$newdate"
350 printf '%s %s %s%s@{%s}: %s\n' "$hashcolor$obj$reseutcolor" \
351 "$timecolor$newtime$resetcolor" \
352 "$extra" "$showref" "$stashnum" "$msg"
353 if [ -n "$maxcount" ]; then
354 maxcount=$(( $maxcount - 1 ))
355 [ $maxcount -gt 0 ] || break
357 done
360 page output
361 exit 0
363 [ -z "$signed" -o "$reftype" = "tag" ] || die "signed tags must be under refs/tags"
364 [ $# -gt 0 ] || set -- $defbranch
365 all=
366 if [ $# -eq 1 ] && [ "$1" = "--all" ]; then
367 set -- $(git for-each-ref --format="%(refname)" refs/top-bases)
368 outofdateok=1
369 all=1
370 if [ $# -eq 0 ]; then
371 if [ -n "$quiet" -a -n "$noneok" ]; then
372 exit 0
373 else
374 die "no TopGit branches found"
378 branches=
379 allrefs=
380 extrarefs=
381 tgbranches=
382 tgcount=0
383 othercount=0
384 ignore=
385 newlist=
386 while read -r obj typ ref && [ -n "$obj" -a -n "$typ" ]; do
387 [ -n "$ref" -o "$typ" != "missing" ] || die "no such ref: ${obj%???}"
388 case " $ignore " in *" $ref "*) continue; esac
389 if [ "$typ" != "commit" -a "$typ" != "tag" ]; then
390 [ -n "$anyrefok" ] || die "not a committish (is a '$typ') ref: $ref"
391 warn "ignoring non-committish (is a '$typ') ref: $ref"
392 ignore="${ignore:+$ignore }$ref"
393 continue
395 case " $newlist " in *" $ref "*) :;; *)
396 newlist="${newlist:+$newlist }$ref"
397 esac
398 if [ "$typ" = "tag" ]; then
399 warn "storing as lightweight tag instead of 'tag' object: $ref"
400 ignore="${ignore:+$ignore }$ref"
402 done <<-EOT
404 printf '%s\n' "$@" | sed 's/^\(.*\)$/\1^{} \1/'
405 printf '%s\n' "$@" | sed 's/^\(.*\)$/\1 \1/'
407 git cat-file --batch-check='%(objectname) %(objecttype) %(rest)' 2>/dev/null ||
410 set -- $newlist
411 for b; do
412 sfn="$b"
413 [ -n "$all" ] ||
414 sfn="$(git rev-parse --revs-only --symbolic-full-name "$b" -- 2>/dev/null || :)"
415 [ -n "$sfn" ] || {
416 [ -n "$anyrefok" ] || die "no such symbolic ref name: $b"
417 fullhash="$(git rev-parse --verify --quiet "$b" --)" || die "no such ref: $b"
418 case " $extrarefs " in *" $b "*) :;; *)
419 warn "including non-symbolic ref only in parents calculation: $b"
420 extrarefs="${extrarefs:+$extrarefs }$fullhash"
421 esac
422 continue
424 case "$sfn" in
425 refs/heads/*)
426 added=
427 tgish=1
428 ref_exists "refs/top-bases/${sfn#refs/heads/}" || tgish=
429 [ -n "$anyrefok" ] || [ -n "$tgish" ] ||
430 die "not a TopGit branch: ${sfn#refs/heads/} (use --allow-any option)"
431 case " $allrefs " in *" $b "*) :;; *)
432 allrefs="${allrefs:+$allrefs }$sfn"
433 esac
434 case " $branches " in *" ${sfn#refs/heads/} "*) :;; *)
435 branches="${branches:+$branches }${sfn#refs/heads/}"
436 added=1
437 esac
438 if [ -n "$tgish" ]; then
439 case " $allrefs " in *" refs/top-bases/${sfn#refs/heads/} "*) :;; *)
440 allrefs="${allrefs:+$allrefs }refs/top-bases/${sfn#refs/heads/}"
441 esac
442 case " $tgbranches " in *" ${sfn#refs/heads/} "*) :;; *)
443 tgbranches="${tgbranches:+$tgbranches }${sfn#refs/heads/}"
444 added=1
445 esac
446 [ -z "$added" ] || tgcount=$(( $tgcount + 1 ))
447 else
448 [ -z "$added" ] || othercount=$(( $othercount + 1 ))
451 refs/top-bases/*)
452 added=
453 tgish=1
454 ref_exists "refs/heads/${sfn#refs/top-bases/}" || tgish=
455 [ -n "$anyrefok" ] || [ -n "$tgish" ] ||
456 warn "including TopGit base that's missing its head: $sfn"
457 case " $allrefs " in *" $sfn "*) :;; *)
458 allrefs="${allrefs:+$allrefs }$sfn"
459 esac
460 case " $branches " in *" ${sfn#refs/top-bases/} "*) :;; *)
461 branches="${branches:+$branches }${sfn#refs/top-bases/}"
462 added=1
463 esac
464 if [ -n "$tgish" ]; then
465 case " $allrefs " in *" refs/heads/${sfn#refs/top-bases/} "*) :;; *)
466 allrefs="${allrefs:+$allrefs }refs/heads/${sfn#refs/top-bases/}"
467 esac
468 case " $tgbranches " in *" ${sfn#refs/top-bases/} "*) :;; *)
469 tgbranches="${tgbranches:+$tgbranches }${sfn#refs/top-bases/}"
470 added=1
471 esac
472 [ -z "$added" ] || tgcount=$(( $tgcount + 1 ))
473 else
474 [ -z "$added" ] || othercount=$(( $othercount + 1 ))
478 [ -n "$anyrefok" ] || die "refusing to include without --allow-any: $sfn"
479 case " $allrefs " in *" $sfn "*) :;; *)
480 allrefs="${allrefs:+$allrefs }$sfn"
481 esac
482 case " $branches " in *" ${sfn#refs/} "*) :;; *)
483 branches="${branches:+$branches }${sfn#refs/}"
484 othercount=$(( $othercount + 1 ))
485 esac
487 esac
488 done
490 [ -n "$force" ] || \
491 ! git rev-parse --verify --quiet "$refname" -- >/dev/null ||
492 die "$reftype '$tagname' already exists"
494 desc="tg branch"
495 descpl="tg branches"
496 if [ $othercount -gt 0 ]; then
497 if [ $tgcount -eq 0 ]; then
498 desc="ref"
499 descpl="refs"
500 else
501 descpl="$descpl and refs"
505 get_dep() {
506 case " $seen_deps " in *" $_dep "*) return 0; esac
507 seen_deps="${seen_deps:+$seen_deps }$_dep"
508 printf 'refs/heads/%s\n' "$_dep"
509 [ -z "$_dep_is_tgish" ] || printf 'refs/top-bases/%s\n' "$_dep"
512 get_deps_internal()
514 no_remotes=1
515 recurse_deps_exclude=
516 for _b; do
517 case " $recurse_deps_exclude " in *" $_b "*) continue; esac
518 seen_deps=
519 _dep="$_b"; _dep_is_tgish=1; get_dep
520 recurse_deps get_dep "$_b"
521 recurse_deps_exclude="$recurse_deps_exclude $seen_deps"
522 done
525 get_deps()
527 get_deps_internal "$@" | LC_ALL=C sort -u
530 out_of_date=
531 if [ -n "$outofdateok" ]; then
532 if [ -n "$tgbranches" ]; then
533 while read -r dep && [ -n "$dep" ]; do
534 case " $allrefs " in *" $dep "*) :;; *)
535 ! ref_exists "$dep" ||
536 allrefs="${allrefs:+$allrefs }$dep"
537 esac
538 done <<-EOT
539 $(get_deps $tgbranches)
542 else
543 for b in $tgbranches; do
544 if ! needs_update "$b" >/dev/null; then
545 out_of_date=1
546 echo "branch not up-to-date: $b"
548 done
550 [ -z "$out_of_date" ] || die "all branches to be tagged must be up-to-date"
552 get_refs()
554 printf '%s\n' '-----BEGIN TOPGIT REFS-----'
556 printf '%s\n' $allrefs
557 [ -n "$outofdateok" ] || get_deps $tgbranches
558 } | LC_ALL=C sort -u | sed 's/^\(.*\)$/\1^0 \1/' |
559 git cat-file --batch-check='%(objectname) %(rest)' 2>/dev/null |
560 grep -v ' missing$' || :
561 printf '%s\n' '-----END TOPGIT REFS-----'
564 if [ -n "$refsonly" ]; then
565 get_refs
566 exit 0
569 stripcomments=
570 if [ -n "$msgfile" ]; then
571 if [ "$msgfile" = "-" ]; then
572 git stripspace >"$git_dir/TAG_EDITMSG"
573 else
574 git stripspace <"$msgfile" >"$git_dir/TAG_EDITMSG"
576 elif [ -n "$msg" ]; then
577 printf '%s\n' "$msg" | git stripspace >"$git_dir/TAG_EDITMSG"
578 else
579 case "$branches" in
580 *" "*)
581 if [ ${#branches} -le 60 ]; then
582 printf '%s\n' "tag $descpl $branches"
583 printf '%s\n' "$updmsg"
584 else
585 printf '%s\n' "tag $(( $(printf '%s' "$branches" | wc -w) )) $descpl" ""
586 for b in $branches; do
587 printf '%s\n' "$b"
588 done
592 printf '%s\n' "tag $desc $branches"
594 esac | git stripspace >"$git_dir/TAG_EDITMSG"
595 if [ -z "$noedit" ]; then
597 cat <<EOT
599 # Please enter a message for tg tag:
600 # $tagname
601 # Lines starting with '#' will be ignored.
603 # $descpl to be tagged:
606 for b in $branches; do
607 printf '%s\n' "# $b"
608 done
609 } >>"$git_dir/TAG_EDITMSG"
610 stripcomments=1
611 run_editor "$git_dir/TAG_EDITMSG" || \
612 die "there was a problem with the editor '$tg_editor'"
615 git stripspace ${stripcomments:+ --strip-comments} \
616 <"$git_dir/TAG_EDITMSG" >"$git_dir/TGTAG_FINALMSG"
617 [ -s "$git_dir/TGTAG_FINALMSG" ] || die "no tag message?"
618 echo "" >>"$git_dir/TGTAG_FINALMSG"
619 get_refs >>"$git_dir/TGTAG_FINALMSG"
621 tagtarget=
622 case "$allrefs${extrarefs:+ $extrarefs}" in
623 *" "*)
624 parents="$(git merge-base --independent \
625 $(printf '%s^0 ' $allrefs $extrarefs))" || \
626 die "failed: git merge-base --independent"
627 if [ $(printf '%s\n' "$parents" | wc -l) -eq 1 ]; then
628 tagtarget="$parents"
629 else
630 mttree="$(git hash-object -t tree -w --stdin </dev/null)"
631 tagtarget="$(printf '%s\n' "tg tag branch consolidation" "" $branches | \
632 git commit-tree $mttree $(printf -- '-p %s ' $parents))"
636 tagtarget="$allrefs^0"
638 esac
640 if [ -n "$logrefupdates" -o "$refname" = "refs/tgstash" ]; then
641 mkdir -p "$git_dir/logs/$(dirname "$refname")" 2>/dev/null || :
642 { >>"$git_dir/logs/$refname" || :; } 2>/dev/null
644 if [ "$reftype" = "tag" -a -n "$signed" ]; then
645 [ -z "$quiet" ] || exec >/dev/null
646 git tag -F "$git_dir/TGTAG_FINALMSG" ${signed:+-s} ${force:+-f} \
647 ${keyid:+-u} ${keyid} "$tagname" "$tagtarget"
648 else
649 obj="$(git rev-parse --verify --quiet "$tagtarget" --)" || \
650 die "invalid object name: $tagtarget"
651 typ="$(git cat-file -t "$tagtarget" 2>/dev/null)" || \
652 die "invalid object name: $tagtarget"
653 id="$(git var GIT_COMMITTER_IDENT 2>/dev/null)" || \
654 die "could not get GIT_COMMITTER_IDENT"
655 newtag="$({
656 printf '%s\n' "object $obj" "type $typ" "tag $tagname" \
657 "tagger $id" ""
658 cat "$git_dir/TGTAG_FINALMSG"
659 } | git mktag)" || die "git mktag failed"
660 old="$(git rev-parse --verify --short --quiet "$refname" -- || :)"
661 updmsg=
662 case "$branches" in
663 *" "*)
664 if [ ${#branches} -le 100 ]; then
665 updmsg="$(printf '%s\n' "tgtag: $branches")"
666 else
667 updmsg="$(printf '%s\n' "tgtag: $(( $(printf '%s' "$branches" | wc -w) )) ${descpl#tg }")"
671 updmsg="$(printf '%s\n' "tgtag: $branches")"
673 esac
674 git update-ref -m "$updmsg" "$refname" "$newtag"
675 [ -z "$old" -o -n "$quiet" ] || printf "Updated $reftype '%s' (was %s)\n" "$tagname" "$old"
677 rm -f "$git_dir/TAG_EDITMSG" "$git_dir/TGTAG_FINALMSG"