Code

Merge branch 'jk/maint-tag-show-fixes' into maint
authorJunio C Hamano <gitster@pobox.com>
Tue, 14 Feb 2012 07:31:27 +0000 (23:31 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 14 Feb 2012 07:31:27 +0000 (23:31 -0800)
* jk/maint-tag-show-fixes:
  tag: do not show non-tag contents with "-n"
  tag: die when listing missing or corrupt objects
  tag: fix output of "tag -n" when errors occur

Conflicts:
t/t7004-tag.sh

1  2 
builtin/tag.c
t/t7004-tag.sh

diff --combined builtin/tag.c
index 31f02e80f6b8fc22ed7ca7ae142c8d2f99084e8b,6ca53e3310c3c0fd9fadebb83e2442368e53bab5..03df16ac6e0e492483bf3695e0f6b2aa0a95978b
@@@ -14,7 -14,6 +14,7 @@@
  #include "parse-options.h"
  #include "diff.h"
  #include "revision.h"
 +#include "gpg-interface.h"
  
  static const char * const git_tag_usage[] = {
        "git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]",
@@@ -24,6 -23,8 +24,6 @@@
        NULL
  };
  
 -static char signingkey[1000];
 -
  struct tag_filter {
        const char **patterns;
        int lines;
@@@ -83,18 -84,51 +83,51 @@@ static int contains(struct commit *cand
        return contains_recurse(candidate, want);
  }
  
+ static void show_tag_lines(const unsigned char *sha1, int lines)
+ {
+       int i;
+       unsigned long size;
+       enum object_type type;
+       char *buf, *sp, *eol;
+       size_t len;
+       buf = read_sha1_file(sha1, &type, &size);
+       if (!buf)
+               die_errno("unable to read object %s", sha1_to_hex(sha1));
+       if (type != OBJ_COMMIT && type != OBJ_TAG)
+               goto free_return;
+       if (!size)
+               die("an empty %s object %s?",
+                   typename(type), sha1_to_hex(sha1));
+       /* skip header */
+       sp = strstr(buf, "\n\n");
+       if (!sp)
+               goto free_return;
+       /* only take up to "lines" lines, and strip the signature from a tag */
+       if (type == OBJ_TAG)
+               size = parse_signature(buf, size);
+       for (i = 0, sp += 2; i < lines && sp < buf + size; 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;
+       }
+ free_return:
+       free(buf);
+ }
  static int show_reference(const char *refname, const unsigned char *sha1,
                          int flag, void *cb_data)
  {
        struct tag_filter *filter = cb_data;
  
        if (match_pattern(filter->patterns, refname)) {
-               int i;
-               unsigned long size;
-               enum object_type type;
-               char *buf, *sp, *eol;
-               size_t len;
                if (filter->with_commit) {
                        struct commit *commit;
  
                        return 0;
                }
                printf("%-15s ", refname);
-               buf = read_sha1_file(sha1, &type, &size);
-               if (!buf || !size)
-                       return 0;
-               /* skip header */
-               sp = strstr(buf, "\n\n");
-               if (!sp) {
-                       free(buf);
-                       return 0;
-               }
-               /* only take up to "lines" lines, and strip the signature */
-               size = parse_signature(buf, size);
-               for (i = 0, sp += 2;
-                               i < filter->lines && sp < buf + size;
-                               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;
-               }
+               show_tag_lines(sha1, filter->lines);
                putchar('\n');
-               free(buf);
        }
  
        return 0;
@@@ -173,7 -182,7 +181,7 @@@ static int for_each_tag_name(const cha
                        had_error = 1;
                        continue;
                }
 -              if (!resolve_ref(ref, sha1, 1, NULL)) {
 +              if (read_ref(ref, sha1)) {
                        error(_("tag '%s' not found."), *p);
                        had_error = 1;
                        continue;
@@@ -207,29 -216,83 +215,29 @@@ static int verify_tag(const char *name
  
  static int do_sign(struct strbuf *buffer)
  {
 -      struct child_process gpg;
 -      const char *args[4];
 -      char *bracket;
 -      int len;
 -      int i, j;
 -
 -      if (!*signingkey) {
 -              if (strlcpy(signingkey, git_committer_info(IDENT_ERROR_ON_NO_NAME),
 -                              sizeof(signingkey)) > sizeof(signingkey) - 1)
 -                      return error(_("committer info too long."));
 -              bracket = strchr(signingkey, '>');
 -              if (bracket)
 -                      bracket[1] = '\0';
 -      }
 -
 -      /* When the username signingkey is bad, program could be terminated
 -       * because gpg exits without reading and then write gets SIGPIPE. */
 -      signal(SIGPIPE, SIG_IGN);
 -
 -      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."));
 -
 -      if (write_in_full(gpg.in, buffer->buf, buffer->len) != buffer->len) {
 -              close(gpg.in);
 -              close(gpg.out);
 -              finish_command(&gpg);
 -              return error(_("gpg did not accept the tag data"));
 -      }
 -      close(gpg.in);
 -      len = strbuf_read(buffer, gpg.out, 1024);
 -      close(gpg.out);
 -
 -      if (finish_command(&gpg) || !len || len < 0)
 -              return error(_("gpg failed to sign the tag"));
 -
 -      /* Strip CR from the line endings, in case we are on Windows. */
 -      for (i = j = 0; i < buffer->len; i++)
 -              if (buffer->buf[i] != '\r') {
 -                      if (i != j)
 -                              buffer->buf[j] = buffer->buf[i];
 -                      j++;
 -              }
 -      strbuf_setlen(buffer, j);
 -
 -      return 0;
 +      return sign_buffer(buffer, buffer, get_signing_key());
  }
  
  static const char tag_template[] =
        N_("\n"
        "#\n"
        "# Write a tag message\n"
 +      "# Lines starting with '#' will be ignored.\n"
        "#\n");
  
 -static void set_signingkey(const char *value)
 -{
 -      if (strlcpy(signingkey, value, sizeof(signingkey)) >= sizeof(signingkey))
 -              die(_("signing key value too long (%.10s...)"), value);
 -}
 +static const char tag_template_nocleanup[] =
 +      N_("\n"
 +      "#\n"
 +      "# Write a tag message\n"
 +      "# Lines starting with '#' will be kept; you may remove them"
 +      " yourself if you want to.\n"
 +      "#\n");
  
  static int git_tag_config(const char *var, const char *value, void *cb)
  {
 -      if (!strcmp(var, "user.signingkey")) {
 -              if (!value)
 -                      return config_error_nonbool(var);
 -              set_signingkey(value);
 -              return 0;
 -      }
 -
 +      int status = git_gpg_config(var, value, cb);
 +      if (status)
 +              return status;
        return git_default_config(var, value, cb);
  }
  
@@@ -264,18 -327,8 +272,18 @@@ static int build_tag_object(struct strb
        return 0;
  }
  
 +struct create_tag_options {
 +      unsigned int message_given:1;
 +      unsigned int sign;
 +      enum {
 +              CLEANUP_NONE,
 +              CLEANUP_SPACE,
 +              CLEANUP_ALL
 +      } cleanup_mode;
 +};
 +
  static void create_tag(const unsigned char *object, const char *tag,
 -                     struct strbuf *buf, int message, int sign,
 +                     struct strbuf *buf, struct create_tag_options *opt,
                       unsigned char *prev, unsigned char *result)
  {
        enum object_type type;
        if (header_len > sizeof(header_buf) - 1)
                die(_("tag header too big."));
  
 -      if (!message) {
 +      if (!opt->message_given) {
                int fd;
  
                /* write the template message before editing: */
  
                if (!is_null_sha1(prev))
                        write_tag_body(fd, prev);
 +              else if (opt->cleanup_mode == CLEANUP_ALL)
 +                      write_or_die(fd, _(tag_template),
 +                                      strlen(_(tag_template)));
                else
 -                      write_or_die(fd, _(tag_template), strlen(_(tag_template)));
 +                      write_or_die(fd, _(tag_template_nocleanup),
 +                                      strlen(_(tag_template_nocleanup)));
                close(fd);
  
                if (launch_editor(path, buf, NULL)) {
                }
        }
  
 -      stripspace(buf, 1);
 +      if (opt->cleanup_mode != CLEANUP_NONE)
 +              stripspace(buf, opt->cleanup_mode == CLEANUP_ALL);
  
 -      if (!message && !buf->len)
 +      if (!opt->message_given && !buf->len)
                die(_("no tag message?"));
  
        strbuf_insert(buf, 0, header_buf, header_len);
  
 -      if (build_tag_object(buf, sign, result) < 0) {
 +      if (build_tag_object(buf, opt->sign, result) < 0) {
                if (path)
                        fprintf(stderr, _("The tag message has been left in %s\n"),
                                path);
@@@ -367,12 -415,12 +375,12 @@@ static int parse_msg_arg(const struct o
  static int strbuf_check_tag_ref(struct strbuf *sb, const char *name)
  {
        if (name[0] == '-')
 -              return CHECK_REF_FORMAT_ERROR;
 +              return -1;
  
        strbuf_reset(sb);
        strbuf_addf(sb, "refs/tags/%s", name);
  
 -      return check_ref_format(sb->buf);
 +      return check_refname_format(sb->buf, 0);
  }
  
  int cmd_tag(int argc, const char **argv, const char *prefix)
        unsigned char object[20], prev[20];
        const char *object_ref, *tag;
        struct ref_lock *lock;
 -
 -      int annotate = 0, sign = 0, force = 0, lines = -1,
 -              list = 0, delete = 0, verify = 0;
 +      struct create_tag_options opt;
 +      char *cleanup_arg = NULL;
 +      int annotate = 0, force = 0, lines = -1, list = 0,
 +              delete = 0, verify = 0;
        const char *msgfile = NULL, *keyid = NULL;
        struct msg_arg msg = { 0, STRBUF_INIT };
        struct commit_list *with_commit = NULL;
        struct option options[] = {
 -              OPT_BOOLEAN('l', NULL, &list, "list tag names"),
 +              OPT_BOOLEAN('l', "list", &list, "list tag names"),
                { OPTION_INTEGER, 'n', NULL, &lines, "n",
                                "print <n> lines of each tag message",
                                PARSE_OPT_OPTARG, NULL, 1 },
 -              OPT_BOOLEAN('d', NULL, &delete, "delete tags"),
 -              OPT_BOOLEAN('v', NULL, &verify, "verify tags"),
 +              OPT_BOOLEAN('d', "delete", &delete, "delete tags"),
 +              OPT_BOOLEAN('v', "verify", &verify, "verify tags"),
  
                OPT_GROUP("Tag creation options"),
 -              OPT_BOOLEAN('a', NULL, &annotate,
 +              OPT_BOOLEAN('a', "annotate", &annotate,
                                        "annotated tag, needs a message"),
 -              OPT_CALLBACK('m', NULL, &msg, "message",
 +              OPT_CALLBACK('m', "message", &msg, "message",
                             "tag message", parse_msg_arg),
 -              OPT_FILENAME('F', NULL, &msgfile, "read message from file"),
 -              OPT_BOOLEAN('s', NULL, &sign, "annotated and GPG-signed tag"),
 -              OPT_STRING('u', NULL, &keyid, "key-id",
 +              OPT_FILENAME('F', "file", &msgfile, "read message from file"),
 +              OPT_BOOLEAN('s', "sign", &opt.sign, "annotated and GPG-signed tag"),
 +              OPT_STRING(0, "cleanup", &cleanup_arg, "mode",
 +                      "how to strip spaces and #comments from message"),
 +              OPT_STRING('u', "local-user", &keyid, "key-id",
                                        "use another key to sign the tag"),
                OPT__FORCE(&force, "replace the tag if exists"),
  
  
        git_config(git_tag_config, NULL);
  
 +      memset(&opt, 0, sizeof(opt));
 +
        argc = parse_options(argc, argv, prefix, options, git_tag_usage, 0);
  
        if (keyid) {
 -              sign = 1;
 -              set_signingkey(keyid);
 +              opt.sign = 1;
 +              set_signing_key(keyid);
        }
 -      if (sign)
 +      if (opt.sign)
                annotate = 1;
        if (argc == 0 && !(delete || verify))
                list = 1;
        if (strbuf_check_tag_ref(&ref, tag))
                die(_("'%s' is not a valid tag name."), tag);
  
 -      if (!resolve_ref(ref.buf, prev, 1, NULL))
 +      if (read_ref(ref.buf, prev))
                hashclr(prev);
        else if (!force)
                die(_("tag '%s' already exists"), tag);
  
 +      opt.message_given = msg.given || msgfile;
 +
 +      if (!cleanup_arg || !strcmp(cleanup_arg, "strip"))
 +              opt.cleanup_mode = CLEANUP_ALL;
 +      else if (!strcmp(cleanup_arg, "verbatim"))
 +              opt.cleanup_mode = CLEANUP_NONE;
 +      else if (!strcmp(cleanup_arg, "whitespace"))
 +              opt.cleanup_mode = CLEANUP_SPACE;
 +      else
 +              die(_("Invalid cleanup mode %s"), cleanup_arg);
 +
        if (annotate)
 -              create_tag(object, tag, &buf, msg.given || msgfile,
 -                         sign, prev, object);
 +              create_tag(object, tag, &buf, &opt, prev, object);
  
        lock = lock_any_ref_for_update(ref.buf, prev, 0);
        if (!lock)
diff --combined t/t7004-tag.sh
index e93ac73829f332cdbf53b05fcc611d4ea38c4c55,7687e62cc5c0366d69e1f6898a3181945c628799..4ef79aabc47a4ef2e9def65253edf6bcd8ce91a3
@@@ -8,7 -8,6 +8,7 @@@ test_description='git ta
  Tests for operations with tags.'
  
  . ./test-lib.sh
 +. "$TEST_DIRECTORY"/lib-gpg.sh
  
  # creating and listing lightweight tags:
  
@@@ -586,6 -585,37 +586,19 @@@ test_expect_success 
        test_cmp expect actual
  '
  
 -# subsequent tests require gpg; check if it is available
 -gpg --version >/dev/null 2>/dev/null
 -if [ $? -eq 127 ]; then
 -      say "# gpg not found - skipping tag signing and verification tests"
 -else
 -      # As said here: http://www.gnupg.org/documentation/faqs.html#q6.19
 -      # the gpg version 1.0.6 didn't parse trust packets correctly, so for
 -      # that version, creation of signed tags using the generated key fails.
 -      case "$(gpg --version)" in
 -      'gpg (GnuPG) 1.0.6'*)
 -              say "Skipping signed tag tests, because a bug in 1.0.6 version"
 -              ;;
 -      *)
 -              test_set_prereq GPG
 -              ;;
 -      esac
 -fi
 -
+ test_expect_success 'annotations for blobs are empty' '
+       blob=$(git hash-object -w --stdin <<-\EOF
+       Blob paragraph 1.
+       Blob paragraph 2.
+       EOF
+       ) &&
+       git tag tag-blob $blob &&
+       echo "tag-blob        " >expect &&
+       git tag -n1 -l tag-blob >actual &&
+       test_cmp expect actual
+ '
  # trying to verify annotated non-signed tags:
  
  test_expect_success GPG \
@@@ -608,6 -638,16 +621,6 @@@ test_expect_success GPG 
  
  # creating and verifying signed tags:
  
 -# key generation info: gpg --homedir t/t7004 --gen-key
 -# Type DSA and Elgamal, size 2048 bits, no expiration date.
 -# Name and email: C O Mitter <committer@example.com>
 -# No password given, to enable non-interactive operation.
 -
 -cp -R "$TEST_DIRECTORY"/t7004 ./gpghome
 -chmod 0700 gpghome
 -GNUPGHOME="$(pwd)/gpghome"
 -export GNUPGHOME
 -
  get_tag_header signed-tag $commit commit $time >expect
  echo 'A signed tag message' >>expect
  echo '-----BEGIN PGP SIGNATURE-----' >>expect