Make git tag a builtin.
authorCarlos Rica <jasampler@gmail.com>
Thu, 19 Jul 2007 23:42:28 +0000 (01:42 +0200)
committerJunio C Hamano <gitster@pobox.com>
Fri, 20 Jul 2007 08:27:25 +0000 (01:27 -0700)
This replaces the script "git-tag.sh" with "builtin-tag.c".

The existing test suite for "git tag" guarantees the compatibility
with the features provided by the script version.

There are some minor changes in the behaviour of "git tag" here:
"git tag -v" now can get more than one tag to verify, like "git tag -d" does,
"git tag" with no arguments prints all tags, more like "git branch" does,
and "git tag -n" also prints all tags with annotations (without needing -l).
Tests and documentation were also updated to reflect these changes.

The program is currently calling the script "git verify-tag" for verify.
This can be changed porting it to C and calling its functions directly
from builtin-tag.c.

Signed-off-by: Carlos Rica <jasampler@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/git-tag.txt
Makefile
builtin-tag.c [new file with mode: 0644]
builtin.h
contrib/examples/git-tag.sh [new file with mode: 0755]
git-tag.sh [deleted file]
git.c
t/t7004-tag.sh
index aee2c1bdc785259c3f587323b557f5c575f3c797..119117f0bdc689a4da1c1ba4b8ae9a41fd8f05fd 100644 (file)
@@ -12,7 +12,7 @@ SYNOPSIS
 'git-tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]  <name> [<head>]
 'git-tag' -d <name>...
 'git-tag' [-n [<num>]] -l [<pattern>]
 'git-tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]  <name> [<head>]
 'git-tag' -d <name>...
 'git-tag' [-n [<num>]] -l [<pattern>]
-'git-tag' -v <name>
+'git-tag' -v <name>...
 
 DESCRIPTION
 -----------
 
 DESCRIPTION
 -----------
@@ -23,7 +23,7 @@ Unless `-f` is given, the tag must not yet exist in
 
 If one of `-a`, `-s`, or `-u <key-id>` is passed, the command
 creates a 'tag' object, and requires the tag message.  Unless
 
 If one of `-a`, `-s`, or `-u <key-id>` is passed, the command
 creates a 'tag' object, and requires the tag message.  Unless
-`-m <msg>` is given, an editor is started for the user to type
+`-m <msg>` or `-F <file>` is given, an editor is started for the user to type
 in the tag message.
 
 Otherwise just the SHA1 object name of the commit object is
 in the tag message.
 
 Otherwise just the SHA1 object name of the commit object is
@@ -59,15 +59,17 @@ OPTIONS
        Delete existing tags with the given names.
 
 -v::
        Delete existing tags with the given names.
 
 -v::
-       Verify the gpg signature of given the tag
+       Verify the gpg signature of the given tag names.
 
 -n <num>::
        <num> specifies how many lines from the annotation, if any,
        are printed when using -l.
        The default is not to print any annotation lines.
 
 -n <num>::
        <num> specifies how many lines from the annotation, if any,
        are printed when using -l.
        The default is not to print any annotation lines.
+       If no number is given to `-n`, only the first line is printed.
 
 -l <pattern>::
        List tags with names that match the given pattern (or all if no pattern is given).
 
 -l <pattern>::
        List tags with names that match the given pattern (or all if no pattern is given).
+       Typing "git tag" without arguments, also lists all tags.
 
 -m <msg>::
        Use the given tag message (instead of prompting)
 
 -m <msg>::
        Use the given tag message (instead of prompting)
index 73b487fba9dc699fd8d2061113b8e94b3d4fa698..8db664624594ad2b5d029d551ff48d86b1d3e989 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -206,7 +206,7 @@ SCRIPT_SH = \
        git-pull.sh git-rebase.sh git-rebase--interactive.sh \
        git-repack.sh git-request-pull.sh git-reset.sh \
        git-sh-setup.sh \
        git-pull.sh git-rebase.sh git-rebase--interactive.sh \
        git-repack.sh git-request-pull.sh git-reset.sh \
        git-sh-setup.sh \
-       git-tag.sh git-verify-tag.sh \
+       git-verify-tag.sh \
        git-am.sh \
        git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \
        git-merge-resolve.sh git-merge-ours.sh \
        git-am.sh \
        git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \
        git-merge-resolve.sh git-merge-ours.sh \
@@ -361,6 +361,7 @@ BUILTIN_OBJS = \
        builtin-show-branch.o \
        builtin-stripspace.o \
        builtin-symbolic-ref.o \
        builtin-show-branch.o \
        builtin-stripspace.o \
        builtin-symbolic-ref.o \
+       builtin-tag.o \
        builtin-tar-tree.o \
        builtin-unpack-objects.o \
        builtin-update-index.o \
        builtin-tar-tree.o \
        builtin-unpack-objects.o \
        builtin-update-index.o \
diff --git a/builtin-tag.c b/builtin-tag.c
new file mode 100644 (file)
index 0000000..507f510
--- /dev/null
@@ -0,0 +1,450 @@
+/*
+ * Builtin "git tag"
+ *
+ * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>,
+ *                    Carlos Rica <jasampler@gmail.com>
+ * Based on git-tag.sh and mktag.c by Linus Torvalds.
+ */
+
+#include "cache.h"
+#include "builtin.h"
+#include "refs.h"
+#include "tag.h"
+#include "run-command.h"
+
+static const char builtin_tag_usage[] =
+  "git-tag [-n [<num>]] -l [<pattern>] | [-a | -s | -u <key-id>] [-f | -d | -v] [-m <msg> | -F <file>] <tagname> [<head>]";
+
+static char signingkey[1000];
+
+static void launch_editor(const char *path, char **buffer, unsigned long *len)
+{
+       const char *editor, *terminal;
+       struct child_process child;
+       const char *args[3];
+       int fd;
+
+       editor = getenv("VISUAL");
+       if (!editor)
+               editor = getenv("EDITOR");
+
+       terminal = getenv("TERM");
+       if (!editor && (!terminal || !strcmp(terminal, "dumb"))) {
+               fprintf(stderr,
+               "Terminal is dumb but no VISUAL nor EDITOR defined.\n"
+               "Please supply the message using either -m or -F option.\n");
+               exit(1);
+       }
+
+       if (!editor)
+               editor = "vi";
+
+       memset(&child, 0, sizeof(child));
+       child.argv = args;
+       args[0] = editor;
+       args[1] = path;
+       args[2] = NULL;
+
+       if (run_command(&child))
+               die("There was a problem with the editor %s.", editor);
+
+       fd = open(path, O_RDONLY);
+       if (fd < 0)
+               die("could not open '%s': %s", path, strerror(errno));
+       if (read_fd(fd, buffer, len)) {
+               free(*buffer);
+               die("could not read message file '%s': %s",
+                                               path, strerror(errno));
+       }
+       close(fd);
+}
+
+struct tag_filter {
+       const char *pattern;
+       int lines;
+};
+
+#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
+
+static int show_reference(const char *refname, const unsigned char *sha1,
+                         int flag, void *cb_data)
+{
+       struct tag_filter *filter = cb_data;
+
+       if (!fnmatch(filter->pattern, refname, 0)) {
+               int i;
+               unsigned long size;
+               enum object_type type;
+               char *buf, *sp, *eol;
+               size_t len;
+
+               if (!filter->lines) {
+                       printf("%s\n", refname);
+                       return 0;
+               }
+               printf("%-15s ", refname);
+
+               sp = buf = read_sha1_file(sha1, &type, &size);
+               if (!buf || !size)
+                       return 0;
+               /* skip header */
+               while (sp + 1 < buf + size &&
+                               !(sp[0] == '\n' && sp[1] == '\n'))
+                       sp++;
+               /* only take up to "lines" lines, and strip the signature */
+               for (i = 0, sp += 2; i < filter->lines && sp < buf + size &&
+                               prefixcmp(sp, PGP_SIGNATURE "\n");
+                               i++) {
+                       if (i)
+                               printf("\n    ");
+                       eol = memchr(sp, '\n', size - (sp - buf));
+                       len = eol ? eol - sp : size - (sp - buf);
+                       fwrite(sp, len, 1, stdout);
+                       if (!eol)
+                               break;
+                       sp = eol + 1;
+               }
+               putchar('\n');
+               free(buf);
+       }
+
+       return 0;
+}
+
+static int list_tags(const char *pattern, int lines)
+{
+       struct tag_filter filter;
+       char *newpattern;
+
+       if (pattern == NULL)
+               pattern = "";
+
+       /* prepend/append * to the shell pattern: */
+       newpattern = xmalloc(strlen(pattern) + 3);
+       sprintf(newpattern, "*%s*", pattern);
+
+       filter.pattern = newpattern;
+       filter.lines = lines;
+
+       for_each_tag_ref(show_reference, (void *) &filter);
+
+       free(newpattern);
+
+       return 0;
+}
+
+typedef int (*func_tag)(const char *name, const char *ref,
+                               const unsigned char *sha1);
+
+static int do_tag_names(const char **argv, func_tag fn)
+{
+       const char **p;
+       char ref[PATH_MAX];
+       int had_error = 0;
+       unsigned char sha1[20];
+
+       for (p = argv; *p; p++) {
+               if (snprintf(ref, sizeof(ref), "refs/tags/%s", *p)
+                                       >= sizeof(ref)) {
+                       error("tag name too long: %.*s...", 50, *p);
+                       had_error = 1;
+                       continue;
+               }
+               if (!resolve_ref(ref, sha1, 1, NULL)) {
+                       error("tag '%s' not found.", *p);
+                       had_error = 1;
+                       continue;
+               }
+               if (fn(*p, ref, sha1))
+                       had_error = 1;
+       }
+       return had_error;
+}
+
+static int delete_tag(const char *name, const char *ref,
+                               const unsigned char *sha1)
+{
+       if (delete_ref(ref, sha1))
+               return 1;
+       printf("Deleted tag '%s'\n", name);
+       return 0;
+}
+
+static int verify_tag(const char *name, const char *ref,
+                               const unsigned char *sha1)
+{
+       const char *argv_verify_tag[] = {"git-verify-tag",
+                                       "-v", "SHA1_HEX", NULL};
+       argv_verify_tag[2] = sha1_to_hex(sha1);
+
+       if (run_command_v_opt(argv_verify_tag, 0))
+               return error("could not verify the tag '%s'", name);
+       return 0;
+}
+
+static ssize_t do_sign(char *buffer, size_t size, size_t max)
+{
+       struct child_process gpg;
+       const char *args[4];
+       char *bracket;
+       int len;
+
+       if (!*signingkey) {
+               if (strlcpy(signingkey, git_committer_info(1),
+                               sizeof(signingkey)) >= sizeof(signingkey))
+                       return error("committer info too long.");
+               bracket = strchr(signingkey, '>');
+               if (bracket)
+                       bracket[1] = '\0';
+       }
+
+       memset(&gpg, 0, sizeof(gpg));
+       gpg.argv = args;
+       gpg.in = -1;
+       gpg.out = -1;
+       args[0] = "gpg";
+       args[1] = "-bsau";
+       args[2] = signingkey;
+       args[3] = NULL;
+
+       if (start_command(&gpg))
+               return error("could not run gpg.");
+
+       write_or_die(gpg.in, buffer, size);
+       close(gpg.in);
+       gpg.close_in = 0;
+       len = read_in_full(gpg.out, buffer + size, max - size);
+
+       finish_command(&gpg);
+
+       if (len == max - size)
+               return error("could not read the entire signature from gpg.");
+
+       return size + len;
+}
+
+static const char tag_template[] =
+       "\n"
+       "#\n"
+       "# Write a tag message\n"
+       "#\n";
+
+static int git_tag_config(const char *var, const char *value)
+{
+       if (!strcmp(var, "user.signingkey")) {
+               if (!value)
+                       die("user.signingkey without value");
+               if (strlcpy(signingkey, value, sizeof(signingkey))
+                                               >= sizeof(signingkey))
+                       die("user.signingkey value too long");
+               return 0;
+       }
+
+       return git_default_config(var, value);
+}
+
+#define MAX_SIGNATURE_LENGTH 1024
+/* message must be NULL or allocated, it will be reallocated and freed */
+static void create_tag(const unsigned char *object, const char *tag,
+                      char *message, int sign, unsigned char *result)
+{
+       enum object_type type;
+       char header_buf[1024], *buffer;
+       int header_len, max_size;
+       unsigned long size;
+
+       type = sha1_object_info(object, NULL);
+       if (type <= 0)
+           die("bad object type.");
+
+       header_len = snprintf(header_buf, sizeof(header_buf),
+                         "object %s\n"
+                         "type %s\n"
+                         "tag %s\n"
+                         "tagger %s\n\n",
+                         sha1_to_hex(object),
+                         typename(type),
+                         tag,
+                         git_committer_info(1));
+
+       if (header_len >= sizeof(header_buf))
+               die("tag header too big.");
+
+       if (!message) {
+               char *path;
+               int fd;
+
+               /* write the template message before editing: */
+               path = xstrdup(git_path("TAG_EDITMSG"));
+               fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
+               if (fd < 0)
+                       die("could not create file '%s': %s",
+                                               path, strerror(errno));
+               write_or_die(fd, tag_template, strlen(tag_template));
+               close(fd);
+
+               launch_editor(path, &buffer, &size);
+
+               unlink(path);
+               free(path);
+       }
+       else {
+               buffer = message;
+               size = strlen(message);
+       }
+
+       size = stripspace(buffer, size, 1);
+
+       if (!message && !size)
+               die("no tag message?");
+
+       /* insert the header and add the '\n' if needed: */
+       max_size = header_len + size + (sign ? MAX_SIGNATURE_LENGTH : 0) + 1;
+       buffer = xrealloc(buffer, max_size);
+       if (size)
+               buffer[size++] = '\n';
+       memmove(buffer + header_len, buffer, size);
+       memcpy(buffer, header_buf, header_len);
+       size += header_len;
+
+       if (sign) {
+               size = do_sign(buffer, size, max_size);
+               if (size < 0)
+                       die("unable to sign the tag");
+       }
+
+       if (write_sha1_file(buffer, size, tag_type, result) < 0)
+               die("unable to write tag file");
+       free(buffer);
+}
+
+int cmd_tag(int argc, const char **argv, const char *prefix)
+{
+       unsigned char object[20], prev[20];
+       int annotate = 0, sign = 0, force = 0, lines = 0;
+       char *message = NULL;
+       char ref[PATH_MAX];
+       const char *object_ref, *tag;
+       int i;
+       struct ref_lock *lock;
+
+       git_config(git_tag_config);
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (arg[0] != '-')
+                       break;
+               if (!strcmp(arg, "-a")) {
+                       annotate = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-s")) {
+                       annotate = 1;
+                       sign = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-f")) {
+                       force = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-n")) {
+                       if (i + 1 == argc || *argv[i + 1] == '-')
+                               /* no argument */
+                               lines = 1;
+                       else
+                               lines = isdigit(*argv[++i]) ?
+                                       atoi(argv[i]) : 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-m")) {
+                       annotate = 1;
+                       i++;
+                       if (i == argc)
+                               die("option -m needs an argument.");
+                       message = xstrdup(argv[i]);
+                       continue;
+               }
+               if (!strcmp(arg, "-F")) {
+                       unsigned long len;
+                       int fd;
+
+                       annotate = 1;
+                       i++;
+                       if (i == argc)
+                               die("option -F needs an argument.");
+
+                       if (!strcmp(argv[i], "-"))
+                               fd = 0;
+                       else {
+                               fd = open(argv[i], O_RDONLY);
+                               if (fd < 0)
+                                       die("could not open '%s': %s",
+                                               argv[i], strerror(errno));
+                       }
+                       len = 1024;
+                       message = xmalloc(len);
+                       if (read_fd(fd, &message, &len)) {
+                               free(message);
+                               die("cannot read %s", argv[i]);
+                       }
+                       continue;
+               }
+               if (!strcmp(arg, "-u")) {
+                       annotate = 1;
+                       sign = 1;
+                       i++;
+                       if (i == argc)
+                               die("option -u needs an argument.");
+                       if (strlcpy(signingkey, argv[i], sizeof(signingkey))
+                                                       >= sizeof(signingkey))
+                               die("argument to option -u too long");
+                       continue;
+               }
+               if (!strcmp(arg, "-l")) {
+                       return list_tags(argv[i + 1], lines);
+               }
+               if (!strcmp(arg, "-d")) {
+                       return do_tag_names(argv + i + 1, delete_tag);
+               }
+               if (!strcmp(arg, "-v")) {
+                       return do_tag_names(argv + i + 1, verify_tag);
+               }
+               usage(builtin_tag_usage);
+       }
+
+       if (i == argc) {
+               if (annotate)
+                       usage(builtin_tag_usage);
+               return list_tags(NULL, lines);
+       }
+       tag = argv[i++];
+
+       object_ref = i < argc ? argv[i] : "HEAD";
+       if (i + 1 < argc)
+               die("too many params");
+
+       if (get_sha1(object_ref, object))
+               die("Failed to resolve '%s' as a valid ref.", object_ref);
+
+       if (snprintf(ref, sizeof(ref), "refs/tags/%s", tag) >= sizeof(ref))
+               die("tag name too long: %.*s...", 50, tag);
+       if (check_ref_format(ref))
+               die("'%s' is not a valid tag name.", tag);
+
+       if (!resolve_ref(ref, prev, 1, NULL))
+               hashclr(prev);
+       else if (!force)
+               die("tag '%s' already exists", tag);
+
+       if (annotate)
+               create_tag(object, tag, message, sign, object);
+
+       lock = lock_any_ref_for_update(ref, prev, 0);
+       if (!lock)
+               die("%s: cannot lock the ref", ref);
+       if (write_ref_sha1(lock, object, NULL) < 0)
+               die("%s: cannot update the ref", ref);
+
+       return 0;
+}
index 4cc228daceadd1aeb844a54bfe65237f67210c92..ac7417f7c55f2ee45ee9c2b669acf41357e85552 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -70,6 +70,7 @@ extern int cmd_show(int argc, const char **argv, const char *prefix);
 extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
 extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
 extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
 extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
 extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
 extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
+extern int cmd_tag(int argc, const char **argv, const char *prefix);
 extern int cmd_tar_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_unpack_objects(int argc, const char **argv, const char *prefix);
 extern int cmd_update_index(int argc, const char **argv, const char *prefix);
 extern int cmd_tar_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_unpack_objects(int argc, const char **argv, const char *prefix);
 extern int cmd_update_index(int argc, const char **argv, const char *prefix);
diff --git a/contrib/examples/git-tag.sh b/contrib/examples/git-tag.sh
new file mode 100755 (executable)
index 0000000..5ee3f50
--- /dev/null
@@ -0,0 +1,205 @@
+#!/bin/sh
+# Copyright (c) 2005 Linus Torvalds
+
+USAGE='[-n [<num>]] -l [<pattern>] | [-a | -s | -u <key-id>] [-f | -d | -v] [-m <msg>] <tagname> [<head>]'
+SUBDIRECTORY_OK='Yes'
+. git-sh-setup
+
+message_given=
+annotate=
+signed=
+force=
+message=
+username=
+list=
+verify=
+LINES=0
+while case "$#" in 0) break ;; esac
+do
+    case "$1" in
+    -a)
+       annotate=1
+       shift
+       ;;
+    -s)
+       annotate=1
+       signed=1
+       shift
+       ;;
+    -f)
+       force=1
+       shift
+       ;;
+    -n)
+        case "$#,$2" in
+       1,* | *,-*)
+               LINES=1         # no argument
+               ;;
+       *)      shift
+               LINES=$(expr "$1" : '\([0-9]*\)')
+               [ -z "$LINES" ] && LINES=1 # 1 line is default when -n is used
+               ;;
+       esac
+       shift
+       ;;
+    -l)
+       list=1
+       shift
+       case $# in
+       0)      PATTERN=
+               ;;
+       *)
+               PATTERN="$1"    # select tags by shell pattern, not re
+               shift
+               ;;
+       esac
+       git rev-parse --symbolic --tags | sort |
+           while read TAG
+           do
+               case "$TAG" in
+               *$PATTERN*) ;;
+               *)          continue ;;
+               esac
+               [ "$LINES" -le 0 ] && { echo "$TAG"; continue ;}
+               OBJTYPE=$(git cat-file -t "$TAG")
+               case $OBJTYPE in
+               tag)
+                       ANNOTATION=$(git cat-file tag "$TAG" |
+                               sed -e '1,/^$/d' |
+                               sed -n -e "
+                                       /^-----BEGIN PGP SIGNATURE-----\$/q
+                                       2,\$s/^/    /
+                                       p
+                                       ${LINES}q
+                               ")
+                       printf "%-15s %s\n" "$TAG" "$ANNOTATION"
+                       ;;
+               *)      echo "$TAG"
+                       ;;
+               esac
+           done
+       ;;
+    -m)
+       annotate=1
+       shift
+       message="$1"
+       if test "$#" = "0"; then
+           die "error: option -m needs an argument"
+       else
+           message="$1"
+           message_given=1
+           shift
+       fi
+       ;;
+    -F)
+       annotate=1
+       shift
+       if test "$#" = "0"; then
+           die "error: option -F needs an argument"
+       else
+           message="$(cat "$1")"
+           message_given=1
+           shift
+       fi
+       ;;
+    -u)
+       annotate=1
+       signed=1
+       shift
+       if test "$#" = "0"; then
+           die "error: option -u needs an argument"
+       else
+           username="$1"
+           shift
+       fi
+       ;;
+    -d)
+       shift
+       had_error=0
+       for tag
+       do
+               cur=$(git show-ref --verify --hash -- "refs/tags/$tag") || {
+                       echo >&2 "Seriously, what tag are you talking about?"
+                       had_error=1
+                       continue
+               }
+               git update-ref -m 'tag: delete' -d "refs/tags/$tag" "$cur" || {
+                       had_error=1
+                       continue
+               }
+               echo "Deleted tag $tag."
+       done
+       exit $had_error
+       ;;
+    -v)
+       shift
+       tag_name="$1"
+       tag=$(git show-ref --verify --hash -- "refs/tags/$tag_name") ||
+               die "Seriously, what tag are you talking about?"
+       git-verify-tag -v "$tag"
+       exit $?
+       ;;
+    -*)
+        usage
+       ;;
+    *)
+       break
+       ;;
+    esac
+done
+
+[ -n "$list" ] && exit 0
+
+name="$1"
+[ "$name" ] || usage
+prev=0000000000000000000000000000000000000000
+if git show-ref --verify --quiet -- "refs/tags/$name"
+then
+    test -n "$force" || die "tag '$name' already exists"
+    prev=`git rev-parse "refs/tags/$name"`
+fi
+shift
+git check-ref-format "tags/$name" ||
+       die "we do not like '$name' as a tag name."
+
+object=$(git rev-parse --verify --default HEAD "$@") || exit 1
+type=$(git cat-file -t $object) || exit 1
+tagger=$(git-var GIT_COMMITTER_IDENT) || exit 1
+
+test -n "$username" ||
+       username=$(git repo-config user.signingkey) ||
+       username=$(expr "z$tagger" : 'z\(.*>\)')
+
+trap 'rm -f "$GIT_DIR"/TAG_TMP* "$GIT_DIR"/TAG_FINALMSG "$GIT_DIR"/TAG_EDITMSG' 0
+
+if [ "$annotate" ]; then
+    if [ -z "$message_given" ]; then
+        ( echo "#"
+          echo "# Write a tag message"
+          echo "#" ) > "$GIT_DIR"/TAG_EDITMSG
+        git_editor "$GIT_DIR"/TAG_EDITMSG || exit
+    else
+        printf '%s\n' "$message" >"$GIT_DIR"/TAG_EDITMSG
+    fi
+
+    grep -v '^#' <"$GIT_DIR"/TAG_EDITMSG |
+    git stripspace >"$GIT_DIR"/TAG_FINALMSG
+
+    [ -s "$GIT_DIR"/TAG_FINALMSG -o -n "$message_given" ] || {
+       echo >&2 "No tag message?"
+       exit 1
+    }
+
+    ( printf 'object %s\ntype %s\ntag %s\ntagger %s\n\n' \
+       "$object" "$type" "$name" "$tagger";
+      cat "$GIT_DIR"/TAG_FINALMSG ) >"$GIT_DIR"/TAG_TMP
+    rm -f "$GIT_DIR"/TAG_TMP.asc "$GIT_DIR"/TAG_FINALMSG
+    if [ "$signed" ]; then
+       gpg -bsa -u "$username" "$GIT_DIR"/TAG_TMP &&
+       cat "$GIT_DIR"/TAG_TMP.asc >>"$GIT_DIR"/TAG_TMP ||
+       die "failed to sign the tag with GPG."
+    fi
+    object=$(git-mktag < "$GIT_DIR"/TAG_TMP)
+fi
+
+git update-ref "refs/tags/$name" "$object" "$prev"
diff --git a/git-tag.sh b/git-tag.sh
deleted file mode 100755 (executable)
index 5ee3f50..0000000
+++ /dev/null
@@ -1,205 +0,0 @@
-#!/bin/sh
-# Copyright (c) 2005 Linus Torvalds
-
-USAGE='[-n [<num>]] -l [<pattern>] | [-a | -s | -u <key-id>] [-f | -d | -v] [-m <msg>] <tagname> [<head>]'
-SUBDIRECTORY_OK='Yes'
-. git-sh-setup
-
-message_given=
-annotate=
-signed=
-force=
-message=
-username=
-list=
-verify=
-LINES=0
-while case "$#" in 0) break ;; esac
-do
-    case "$1" in
-    -a)
-       annotate=1
-       shift
-       ;;
-    -s)
-       annotate=1
-       signed=1
-       shift
-       ;;
-    -f)
-       force=1
-       shift
-       ;;
-    -n)
-        case "$#,$2" in
-       1,* | *,-*)
-               LINES=1         # no argument
-               ;;
-       *)      shift
-               LINES=$(expr "$1" : '\([0-9]*\)')
-               [ -z "$LINES" ] && LINES=1 # 1 line is default when -n is used
-               ;;
-       esac
-       shift
-       ;;
-    -l)
-       list=1
-       shift
-       case $# in
-       0)      PATTERN=
-               ;;
-       *)
-               PATTERN="$1"    # select tags by shell pattern, not re
-               shift
-               ;;
-       esac
-       git rev-parse --symbolic --tags | sort |
-           while read TAG
-           do
-               case "$TAG" in
-               *$PATTERN*) ;;
-               *)          continue ;;
-               esac
-               [ "$LINES" -le 0 ] && { echo "$TAG"; continue ;}
-               OBJTYPE=$(git cat-file -t "$TAG")
-               case $OBJTYPE in
-               tag)
-                       ANNOTATION=$(git cat-file tag "$TAG" |
-                               sed -e '1,/^$/d' |
-                               sed -n -e "
-                                       /^-----BEGIN PGP SIGNATURE-----\$/q
-                                       2,\$s/^/    /
-                                       p
-                                       ${LINES}q
-                               ")
-                       printf "%-15s %s\n" "$TAG" "$ANNOTATION"
-                       ;;
-               *)      echo "$TAG"
-                       ;;
-               esac
-           done
-       ;;
-    -m)
-       annotate=1
-       shift
-       message="$1"
-       if test "$#" = "0"; then
-           die "error: option -m needs an argument"
-       else
-           message="$1"
-           message_given=1
-           shift
-       fi
-       ;;
-    -F)
-       annotate=1
-       shift
-       if test "$#" = "0"; then
-           die "error: option -F needs an argument"
-       else
-           message="$(cat "$1")"
-           message_given=1
-           shift
-       fi
-       ;;
-    -u)
-       annotate=1
-       signed=1
-       shift
-       if test "$#" = "0"; then
-           die "error: option -u needs an argument"
-       else
-           username="$1"
-           shift
-       fi
-       ;;
-    -d)
-       shift
-       had_error=0
-       for tag
-       do
-               cur=$(git show-ref --verify --hash -- "refs/tags/$tag") || {
-                       echo >&2 "Seriously, what tag are you talking about?"
-                       had_error=1
-                       continue
-               }
-               git update-ref -m 'tag: delete' -d "refs/tags/$tag" "$cur" || {
-                       had_error=1
-                       continue
-               }
-               echo "Deleted tag $tag."
-       done
-       exit $had_error
-       ;;
-    -v)
-       shift
-       tag_name="$1"
-       tag=$(git show-ref --verify --hash -- "refs/tags/$tag_name") ||
-               die "Seriously, what tag are you talking about?"
-       git-verify-tag -v "$tag"
-       exit $?
-       ;;
-    -*)
-        usage
-       ;;
-    *)
-       break
-       ;;
-    esac
-done
-
-[ -n "$list" ] && exit 0
-
-name="$1"
-[ "$name" ] || usage
-prev=0000000000000000000000000000000000000000
-if git show-ref --verify --quiet -- "refs/tags/$name"
-then
-    test -n "$force" || die "tag '$name' already exists"
-    prev=`git rev-parse "refs/tags/$name"`
-fi
-shift
-git check-ref-format "tags/$name" ||
-       die "we do not like '$name' as a tag name."
-
-object=$(git rev-parse --verify --default HEAD "$@") || exit 1
-type=$(git cat-file -t $object) || exit 1
-tagger=$(git-var GIT_COMMITTER_IDENT) || exit 1
-
-test -n "$username" ||
-       username=$(git repo-config user.signingkey) ||
-       username=$(expr "z$tagger" : 'z\(.*>\)')
-
-trap 'rm -f "$GIT_DIR"/TAG_TMP* "$GIT_DIR"/TAG_FINALMSG "$GIT_DIR"/TAG_EDITMSG' 0
-
-if [ "$annotate" ]; then
-    if [ -z "$message_given" ]; then
-        ( echo "#"
-          echo "# Write a tag message"
-          echo "#" ) > "$GIT_DIR"/TAG_EDITMSG
-        git_editor "$GIT_DIR"/TAG_EDITMSG || exit
-    else
-        printf '%s\n' "$message" >"$GIT_DIR"/TAG_EDITMSG
-    fi
-
-    grep -v '^#' <"$GIT_DIR"/TAG_EDITMSG |
-    git stripspace >"$GIT_DIR"/TAG_FINALMSG
-
-    [ -s "$GIT_DIR"/TAG_FINALMSG -o -n "$message_given" ] || {
-       echo >&2 "No tag message?"
-       exit 1
-    }
-
-    ( printf 'object %s\ntype %s\ntag %s\ntagger %s\n\n' \
-       "$object" "$type" "$name" "$tagger";
-      cat "$GIT_DIR"/TAG_FINALMSG ) >"$GIT_DIR"/TAG_TMP
-    rm -f "$GIT_DIR"/TAG_TMP.asc "$GIT_DIR"/TAG_FINALMSG
-    if [ "$signed" ]; then
-       gpg -bsa -u "$username" "$GIT_DIR"/TAG_TMP &&
-       cat "$GIT_DIR"/TAG_TMP.asc >>"$GIT_DIR"/TAG_TMP ||
-       die "failed to sign the tag with GPG."
-    fi
-    object=$(git-mktag < "$GIT_DIR"/TAG_TMP)
-fi
-
-git update-ref "refs/tags/$name" "$object" "$prev"
diff --git a/git.c b/git.c
index a647f9c61e28e74a6294afcd8b01528e12d3c24d..eb9e5ca97258ad04fee0b63d01151cee1845cb7e 100644 (file)
--- a/git.c
+++ b/git.c
@@ -363,6 +363,7 @@ static void handle_internal_command(int argc, const char **argv)
                { "show", cmd_show, RUN_SETUP | USE_PAGER },
                { "stripspace", cmd_stripspace },
                { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
                { "show", cmd_show, RUN_SETUP | USE_PAGER },
                { "stripspace", cmd_stripspace },
                { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
+               { "tag", cmd_tag, RUN_SETUP },
                { "tar-tree", cmd_tar_tree },
                { "unpack-objects", cmd_unpack_objects, RUN_SETUP },
                { "update-index", cmd_update_index, RUN_SETUP },
                { "tar-tree", cmd_tar_tree },
                { "unpack-objects", cmd_unpack_objects, RUN_SETUP },
                { "update-index", cmd_update_index, RUN_SETUP },
index 17de2a90e68665d428fa772acc6e9e72b333ce80..a0be1646194ef7248184bfaf764bb8cc3b0eadb8 100755 (executable)
@@ -5,7 +5,7 @@
 
 test_description='git-tag
 
 
 test_description='git-tag
 
-Basic tests for operations with tags.'
+Tests for operations with tags.'
 
 . ./test-lib.sh
 
 
 . ./test-lib.sh
 
@@ -16,11 +16,15 @@ tag_exists () {
 }
 
 # todo: git tag -l now returns always zero, when fixed, change this test
 }
 
 # todo: git tag -l now returns always zero, when fixed, change this test
-test_expect_success 'listing all tags in an empty tree should succeed' \
-       'git tag -l'
+test_expect_success 'listing all tags in an empty tree should succeed' '
+       git tag -l &&
+       git tag
+'
 
 
-test_expect_success 'listing all tags in an empty tree should output nothing' \
-       'test `git-tag -l | wc -l` -eq 0'
+test_expect_success 'listing all tags in an empty tree should output nothing' '
+       test `git-tag -l | wc -l` -eq 0 &&
+       test `git-tag | wc -l` -eq 0
+'
 
 test_expect_failure 'looking for a tag in an empty tree should fail' \
        'tag_exists mytag'
 
 test_expect_failure 'looking for a tag in an empty tree should fail' \
        'tag_exists mytag'
@@ -49,11 +53,15 @@ test_expect_success 'creating a tag using default HEAD should succeed' '
        git tag mytag
 '
 
        git tag mytag
 '
 
-test_expect_success 'listing all tags if one exists should succeed' \
-       'git-tag -l'
+test_expect_success 'listing all tags if one exists should succeed' '
+       git-tag -l &&
+       git-tag
+'
 
 
-test_expect_success 'listing all tags if one exists should output that tag' \
-       'test `git-tag -l` = mytag'
+test_expect_success 'listing all tags if one exists should output that tag' '
+       test `git-tag -l` = mytag &&
+       test `git-tag` = mytag
+'
 
 # pattern matching:
 
 
 # pattern matching:
 
@@ -165,6 +173,8 @@ test_expect_success 'listing all tags should print them ordered' '
        git tag v1.0 &&
        git tag t210 &&
        git tag -l > actual &&
        git tag v1.0 &&
        git tag t210 &&
        git tag -l > actual &&
+       git diff expect actual &&
+       git tag > actual &&
        git diff expect actual
 '
 
        git diff expect actual
 '
 
@@ -264,6 +274,10 @@ test_expect_failure \
        'trying to verify a non-annotated and non-signed tag should fail' \
        'git-tag -v non-annotated-tag'
 
        'trying to verify a non-annotated and non-signed tag should fail' \
        'git-tag -v non-annotated-tag'
 
+test_expect_failure \
+       'trying to verify many non-annotated or unknown tags, should fail' \
+       'git-tag -v unknown-tag1 non-annotated-tag unknown-tag2'
+
 # creating annotated tags:
 
 get_tag_msg () {
 # creating annotated tags:
 
 get_tag_msg () {
@@ -306,6 +320,18 @@ test_expect_success \
        git diff expect actual
 '
 
        git diff expect actual
 '
 
+cat >inputmsg <<EOF
+A message from the
+standard input
+EOF
+get_tag_header stdin-annotated-tag $commit commit $time >expect
+cat inputmsg >>expect
+test_expect_success 'creating an annotated tag with -F - should succeed' '
+       git-tag -F - stdin-annotated-tag <inputmsg &&
+       get_tag_msg stdin-annotated-tag >actual &&
+       git diff expect actual
+'
+
 # blank and empty messages:
 
 get_tag_header empty-annotated-tag $commit commit $time >expect
 # blank and empty messages:
 
 get_tag_header empty-annotated-tag $commit commit $time >expect
@@ -551,6 +577,12 @@ test_expect_success \
        ! git-tag -v file-annotated-tag
 '
 
        ! git-tag -v file-annotated-tag
 '
 
+test_expect_success \
+       'trying to verify two annotated non-signed tags should fail' '
+       tag_exists annotated-tag file-annotated-tag &&
+       ! git-tag -v annotated-tag file-annotated-tag
+'
+
 # creating and verifying signed tags:
 
 gpg --version >/dev/null
 # creating and verifying signed tags:
 
 gpg --version >/dev/null
@@ -589,9 +621,47 @@ test_expect_success 'creating a signed tag with -m message should succeed' '
        git diff expect actual
 '
 
        git diff expect actual
 '
 
+cat >sigmsgfile <<EOF
+Another signed tag
+message in a file.
+EOF
+get_tag_header file-signed-tag $commit commit $time >expect
+cat sigmsgfile >>expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+       'creating a signed tag with -F messagefile should succeed' '
+       git-tag -s -F sigmsgfile file-signed-tag &&
+       get_tag_msg file-signed-tag >actual &&
+       git diff expect actual
+'
+
+cat >siginputmsg <<EOF
+A signed tag message from
+the standard input
+EOF
+get_tag_header stdin-signed-tag $commit commit $time >expect
+cat siginputmsg >>expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success 'creating a signed tag with -F - should succeed' '
+       git-tag -s -F - stdin-signed-tag <siginputmsg &&
+       get_tag_msg stdin-signed-tag >actual &&
+       git diff expect actual
+'
+
 test_expect_success 'verifying a signed tag should succeed' \
        'git-tag -v signed-tag'
 
 test_expect_success 'verifying a signed tag should succeed' \
        'git-tag -v signed-tag'
 
+test_expect_success 'verifying two signed tags in one command should succeed' \
+       'git-tag -v signed-tag file-signed-tag'
+
+test_expect_success \
+       'verifying many signed and non-signed tags should fail' '
+       ! git-tag -v signed-tag annotated-tag &&
+       ! git-tag -v file-annotated-tag file-signed-tag &&
+       ! git-tag -v annotated-tag file-signed-tag file-annotated-tag &&
+       ! git-tag -v signed-tag annotated-tag file-signed-tag
+'
+
 test_expect_success 'verifying a forged tag should fail' '
        forged=$(git cat-file tag signed-tag |
                sed -e "s/signed-tag/forged-tag/" |
 test_expect_success 'verifying a forged tag should fail' '
        forged=$(git cat-file tag signed-tag |
                sed -e "s/signed-tag/forged-tag/" |