{"thread":{"id":"8990","subject":"[PATCH 2/2] Make git-tag a builtin.","startedAt":"2007-07-11T18:54:13Z","lastAt":"2007-07-12T21:01:57Z","messageCount":3,"participants":["Carlos Rica","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"47060","messageId":"46952755.2050307@gmail.com","threadId":"8990","inReplyTo":null,"subject":"[PATCH 2/2] Make git-tag a builtin.","fromName":"Carlos Rica","fromEmail":"jasampler@gmail.com","sentAt":"2007-07-11T18:54:13Z","receivedAt":"2007-07-11T18:54:13Z","isPatch":true,"sender":{"key":"jasampler@gmail.com","avatar":null},"body":"This replaces the script \"git-tag.sh\" with \"builtin-tag.c\".\nIt is based in a previous work on it from Kristian Høgsberg.\n\nThere are some minor changes in the behaviour of \"git tag\" here:\n\"git tag -v\" now can get more than one tag to verify, like \"git tag -d\" did,\n\"git tag\" with no arguments prints all tags, more like \"git branch\" does, and\nthe template for the edited message adds an empty line, like in \"git commit\".\n\nThe program is now calling to the script \"git verify-tag\" for verify,\nsomething that should be changed porting it to C and calling its functions\ndirectly from builtin-tag.c.\n\nSigned-off-by: Carlos Rica <jasampler@gmail.com>\n---\n\n  As said, \"git verify-tag\" should be replaced with \"git tag\" for\n  verify tags, since both do the same job.  The builtin \"git mktag\"\n  is another candidate to be replaced with \"git tag\", however\n  it is now an essential part of the \"plumbing\" family doing\n  a neat thing not easily replaceable.\n\n  The function launch_editor is copied and modified from the initial work\n  from Kristian on builtin-commit.c, so we expect share that code on\n  both builtins when it is finnished.\n\n Makefile      |    3 +-\n builtin-tag.c |  430 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n builtin.h     |    1 +\n git-tag.sh    |  205 ---------------------------\n git.c         |    1 +\n 5 files changed, 434 insertions(+), 206 deletions(-)\n create mode 100644 builtin-tag.c\n delete mode 100755 git-tag.sh\n\ndiff --git a/Makefile b/Makefile\nindex d7541b4..f0da8f7 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -207,7 +207,7 @@ SCRIPT_SH = \\\n \tgit-pull.sh git-rebase.sh git-rebase--interactive.sh \\\n \tgit-repack.sh git-request-pull.sh git-reset.sh \\\n \tgit-sh-setup.sh \\\n-\tgit-tag.sh git-verify-tag.sh \\\n+\tgit-verify-tag.sh \\\n \tgit-am.sh \\\n \tgit-merge.sh git-merge-stupid.sh git-merge-octopus.sh \\\n \tgit-merge-resolve.sh git-merge-ours.sh \\\n@@ -376,6 +376,7 @@ BUILTIN_OBJS = \\\n \tbuiltin-show-branch.o \\\n \tbuiltin-stripspace.o \\\n \tbuiltin-symbolic-ref.o \\\n+\tbuiltin-tag.o \\\n \tbuiltin-tar-tree.o \\\n \tbuiltin-unpack-objects.o \\\n \tbuiltin-update-index.o \\\ndiff --git a/builtin-tag.c b/builtin-tag.c\nnew file mode 100644\nindex 0000000..1824379\n--- /dev/null\n+++ b/builtin-tag.c\n@@ -0,0 +1,430 @@\n+/*\n+ * Builtin \"git tag\"\n+ *\n+ * Copyright (c) 2007 Kristian HÃžgsberg <krh@redhat.com>,\n+ *                    Carlos Rica <jasampler@gmail.com>\n+ * Based on git-tag.sh and mktag.c by Linus Torvalds.\n+ */\n+\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"refs.h\"\n+#include \"tag.h\"\n+#include \"run-command.h\"\n+\n+static const char builtin_tag_usage[] =\n+  \"git-tag [-n [<num>]] -l [<pattern>] | [-a | -s | -u <key-id>] [-f | -d | -v] [-m <msg>] <tagname> [<head>]\";\n+\n+static char signingkey[1000];\n+static int lines;\n+\n+static void launch_editor(const char *path, char **buffer, unsigned long *len)\n+{\n+\tconst char *editor, *terminal;\n+\tstruct child_process child;\n+\tconst char *args[3];\n+\tint fd;\n+\n+\teditor = getenv(\"VISUAL\");\n+\tif (!editor)\n+\t\teditor = getenv(\"EDITOR\");\n+\n+\tterminal = getenv(\"TERM\");\n+\tif (!editor && (!terminal || !strcmp(terminal, \"dumb\"))) {\n+\t\tfprintf(stderr,\n+\t\t\"Terminal is dumb but no VISUAL nor EDITOR defined.\\n\"\n+\t\t\"Please supply the message using either -m or -F option.\\n\");\n+\t\texit(1);\n+\t}\n+\n+\tif (!editor)\n+\t\teditor = \"vi\";\n+\n+\tmemset(&child, 0, sizeof(child));\n+\tchild.argv = args;\n+\targs[0] = editor;\n+\targs[1] = path;\n+\targs[2] = NULL;\n+\n+\tif (run_command(&child))\n+\t\tdie(\"could not launch editor %s.\", editor);\n+\n+\tfd = open(path, O_RDONLY);\n+\tif (fd == -1)\n+\t\tdie(\"could not read %s.\", path);\n+\tif (read_pipe(fd, buffer, len))\n+\t\tdie(\"could not read message file '%s': %s\",\n+\t\t    path, strerror(errno));\n+\tclose(fd);\n+}\n+\n+#define PGP_SIGNATURE \"-----BEGIN PGP SIGNATURE-----\"\n+\n+static int show_reference(const char *refname, const unsigned char *sha1,\n+\t\t\t  int flag, void *cb_data)\n+{\n+\tconst char *pattern = cb_data;\n+\n+\tif (!fnmatch(pattern, refname, 0)) {\n+\t\tint i;\n+\t\tunsigned long size;\n+\t\tenum object_type type;\n+\t\tchar *buf, *sp, *eol;\n+\t\tsize_t len;\n+\n+\t\tif (!lines) {\n+\t\t\tprintf(\"%s\\n\", refname);\n+\t\t\treturn 0;\n+\t\t}\n+\t\tprintf(\"%-15s \", refname);\n+\n+\t\tsp = buf = read_sha1_file(sha1, &type, &size);\n+\t\tif (!buf || !size)\n+\t\t\treturn 0;\n+\t\t/* skip header */\n+\t\twhile (sp + 1 < buf + size &&\n+\t\t\t\t!(sp[0] == '\\n' && sp[1] == '\\n'))\n+\t\t\tsp++;\n+\t\t/* only take up to \"lines\" lines, and strip the signature */\n+\t\tfor (i = 0, sp += 2; i < lines && sp < buf + size &&\n+\t\t\t\tprefixcmp(sp, PGP_SIGNATURE \"\\n\");\n+\t\t\t\ti++) {\n+\t\t\tif (i)\n+\t\t\t\tprintf(\"\\n    \");\n+\t\t\teol = memchr(sp, '\\n', size - (sp - buf));\n+\t\t\tlen = eol ? eol - sp : size - (sp - buf);\n+\t\t\tfwrite(sp, len, 1, stdout);\n+\t\t\tif (!eol)\n+\t\t\t\tbreak;\n+\t\t\tsp = eol + 1;\n+\t\t}\n+\t\tputchar('\\n');\n+\t\tfree(buf);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int list_tags(const char *pattern)\n+{\n+\tchar *newpattern;\n+\n+\tif (pattern == NULL)\n+\t\tpattern = \"\";\n+\n+\t/* prepend/append * to the shell pattern: */\n+\tnewpattern = xmalloc(strlen(pattern) + 3);\n+\tsprintf(newpattern, \"*%s*\", pattern);\n+\n+\tfor_each_tag_ref(show_reference, (void *) newpattern);\n+\n+\tfree(newpattern);\n+\n+\treturn 0;\n+}\n+\n+\n+static int delete_tags(const char **argv)\n+{\n+\tconst char **p;\n+\tchar ref[PATH_MAX];\n+\tint had_error = 0;\n+\tunsigned char sha1[20];\n+\n+\tfor (p = argv; *p; p++) {\n+\t\tif (snprintf(ref, sizeof ref, \"refs/tags/%s\", *p) > sizeof ref)\n+\t\t\tdie(\"tag name '%s' too long.\", *p);\n+\t\tif (!resolve_ref(ref, sha1, 1, NULL)) {\n+\t\t\tfprintf(stderr, \"tag '%s' not found.\\n\", *p);\n+\t\t\thad_error = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (delete_ref(ref, sha1))\n+\t\t\thad_error = 1;\n+\t\telse\n+\t\t\tprintf(\"Deleted tag '%s'\\n\", *p);\n+\t}\n+\n+\treturn had_error;\n+}\n+\n+static int run_verify_tag_command(unsigned char *sha1)\n+{\n+\tint ret;\n+\tconst char *argv_verify_tag[] = {\"git-verify-tag\",\n+\t\t\t\t\t\"-v\", \"SHA1_HEX\", NULL};\n+\targv_verify_tag[2] = sha1_to_hex(sha1);\n+\n+\tret = run_command_v_opt(argv_verify_tag, 0);\n+\n+\tif (ret <= -10000)\n+\t\tdie(\"unable to run %s\\n\", argv_verify_tag[0]);\n+\treturn -ret;\n+}\n+\n+static int verify_tags(const char **argv)\n+{\n+\tconst char **p;\n+\tchar ref[PATH_MAX];\n+\tint had_error = 0;\n+\tunsigned char sha1[20];\n+\n+\tfor (p = argv; *p; p++) {\n+\t\tif (snprintf(ref, sizeof ref, \"refs/tags/%s\", *p) > sizeof ref)\n+\t\t\tdie(\"tag name '%s' too long.\", *p);\n+\n+\t\tif (!resolve_ref(ref, sha1, 1, NULL)) {\n+\t\t\tfprintf(stderr, \"tag '%s' not found.\\n\", *p);\n+\t\t\thad_error = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (run_verify_tag_command(sha1))\n+\t\t\thad_error = 1;\n+\t}\n+\n+\treturn had_error;\n+}\n+\n+static int do_sign(char *buffer, size_t size, size_t max)\n+{\n+\tstruct child_process gpg;\n+\tconst char *args[4];\n+\tchar *bracket;\n+\tint len;\n+\n+\tif (signingkey[0] == '\\0') {\n+\t\tstrlcpy(signingkey, git_committer_info(1), sizeof signingkey);\n+\t\tbracket = strchr(signingkey, '>');\n+\t\tif (bracket)\n+\t\t\tbracket[1] = '\\0';\n+\t}\n+\n+\tmemset(&gpg, 0, sizeof(gpg));\n+\tgpg.argv = args;\n+\tgpg.in = -1;\n+\tgpg.out = -1;\n+\targs[0] = \"gpg\";\n+\targs[1] = \"-bsau\";\n+\targs[2] = signingkey;\n+\targs[3] = NULL;\n+\n+\tif (start_command(&gpg))\n+\t\tdie(\"could not run gpg.\");\n+\n+\twrite_or_die(gpg.in, buffer, size);\n+\tclose(gpg.in);\n+\tgpg.close_in = 0;\n+\tlen = read_in_full(gpg.out, buffer + size, max - size);\n+\n+\tfinish_command(&gpg);\n+\n+\treturn size + len;\n+}\n+\n+static const char tag_template[] =\n+\t\"\\n\"\n+\t\"#\\n\"\n+\t\"# Write a tag message\\n\"\n+\t\"#\\n\";\n+\n+static int git_tag_config(const char *var, const char *value)\n+{\n+\tif (!strcmp(var, \"user.signingkey\")) {\n+\t\tif (!value)\n+\t\t\tdie(\"user.signingkey without value\");\n+\t\tstrlcpy(signingkey, value, sizeof signingkey);\n+\t\treturn 0;\n+\t}\n+\n+\treturn git_default_config(var, value);\n+}\n+\n+#define MAX_SIGNATURE_LENGTH 1024\n+/* message must be NULL or allocated, it will be reallocated and freed */\n+static void create_tag(const unsigned char *object, const char *tag,\n+\t\t       char *message, int sign, unsigned char *result)\n+{\n+\tenum object_type type;\n+\tchar header_buf[1024], *buffer;\n+\tint header_len, max_size;\n+\tunsigned long size;\n+\n+\ttype = sha1_object_info(object, NULL);\n+\tif (type <= 0)\n+\t    die(\"bad object type.\");\n+\n+\theader_len = snprintf(header_buf, sizeof header_buf,\n+\t\t\t  \"object %s\\n\"\n+\t\t\t  \"type %s\\n\"\n+\t\t\t  \"tag %s\\n\"\n+\t\t\t  \"tagger %s\\n\\n\",\n+\t\t\t  sha1_to_hex(object),\n+\t\t\t  typename(type),\n+\t\t\t  tag,\n+\t\t\t  git_committer_info(1));\n+\n+\tif (header_len >= sizeof header_buf)\n+\t\tdie(\"tag header too big.\");\n+\n+\tif (!message) {\n+\t\tchar *path;\n+\t\tint fd;\n+\n+\t\t/* write the template message before editing: */\n+\t\tpath = xstrdup(git_path(\"TAG_EDITMSG\"));\n+\t\tfd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);\n+\t\tif (fd < 0)\n+\t\t\tdie(\"could not create file %s.\", path);\n+\t\twrite_or_die(fd, tag_template, strlen(tag_template));\n+\t\tclose(fd);\n+\n+\t\tlaunch_editor(path, &buffer, &size);\n+\n+\t\tunlink(path);\n+\t\tfree(path);\n+\t}\n+\telse {\n+\t\tbuffer = message;\n+\t\tsize = strlen(message);\n+\t}\n+\n+\tsize = stripspace(buffer, size, 1);\n+\n+\tif (!message && !size)\n+\t\tdie(\"no tag message?\");\n+\n+\t/* insert the header and add the '\\n' if needed: */\n+\tmax_size = header_len + size + (sign ? MAX_SIGNATURE_LENGTH : 0) + 1;\n+\tbuffer = xrealloc(buffer, max_size);\n+\tif (size)\n+\t\tbuffer[size++] = '\\n';\n+\tmemmove(buffer + header_len, buffer, size);\n+\tmemcpy(buffer, header_buf, header_len);\n+\tsize += header_len;\n+\n+\tif (sign)\n+\t\tsize = do_sign(buffer, size, max_size);\n+\n+\tif (write_sha1_file(buffer, size, tag_type, result) < 0)\n+\t\tdie(\"unable to write tag file\");\n+\tfree(buffer);\n+}\n+\n+int cmd_tag(int argc, const char **argv, const char *prefix)\n+{\n+\tunsigned char object[20], prev[20];\n+\tint annotate = 0, sign = 0, force = 0;\n+\tchar *message = NULL;\n+\tchar ref[PATH_MAX];\n+\tconst char *object_ref, *tag;\n+\tint i, fd;\n+\tstruct ref_lock *lock;\n+\n+\tgit_config(git_tag_config);\n+\n+\tfor (i = 1; i < argc; i++) {\n+\t\tconst char *arg = argv[i];\n+\n+\t\tif (arg[0] != '-')\n+\t\t\tbreak;\n+\t\tif (!strcmp(arg, \"-a\")) {\n+\t\t\tannotate = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(arg, \"-s\")) {\n+\t\t\tannotate = 1;\n+\t\t\tsign = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(arg, \"-f\")) {\n+\t\t\tforce = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(arg, \"-n\")) {\n+\t\t\tif (i + 1 == argc || *argv[i + 1] == '-')\n+\t\t\t\t/* no argument */\n+\t\t\t\tlines = 1;\n+\t\t\telse\n+\t\t\t\tlines = isdigit(*argv[++i]) ?\n+\t\t\t\t\tatoi(argv[i]) : 1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(arg, \"-m\")) {\n+\t\t\tannotate = 1;\n+\t\t\ti++;\n+\t\t\tif  (i == argc)\n+\t\t\t\tdie(\"option -m needs an argument.\");\n+\t\t\tmessage = xstrdup(argv[i]);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(arg, \"-F\")) {\n+\t\t\tunsigned long len;\n+\t\t\tannotate = 1;\n+\t\t\ti++;\n+\t\t\tif  (i == argc)\n+\t\t\t\tdie(\"option -F needs an argument.\");\n+\n+\t\t\tfd = open(argv[i], O_RDONLY);\n+\t\t\tif (fd < 0)\n+\t\t\t\tdie(\"cannot open %s\", argv[1]);\n+\n+\t\t\tlen = 1024;\n+\t\t\tmessage = xmalloc(len);\n+\t\t\tif (read_pipe(fd, &message, &len))\n+\t\t\t\tdie(\"cannot read %s\", argv[1]);\n+\t\t\tmessage = xrealloc(message, len + 1);\n+\t\t\tmessage[len] = '\\0';\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(arg, \"-u\")) {\n+\t\t\tannotate = 1;\n+\t\t\tsign = 1;\n+\t\t\ti++;\n+\t\t\tif  (i == argc)\n+\t\t\t\tdie(\"option -u needs an argument.\");\n+\t\t\tstrlcpy(signingkey, argv[i], sizeof signingkey);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(arg, \"-l\")) {\n+\t\t\treturn list_tags(argv[i + 1]);\n+\t\t}\n+\t\tif (!strcmp(arg, \"-d\")) {\n+\t\t\treturn delete_tags(argv + i + 1);\n+\t\t}\n+\t\tif (!strcmp(arg, \"-v\")) {\n+\t\t\treturn verify_tags(argv + i + 1);\n+\t\t}\n+\t\tusage(builtin_tag_usage);\n+\t}\n+\n+\tif (i == argc)\n+\t\treturn list_tags(NULL);\n+\ttag = argv[i++];\n+\n+\tobject_ref = i < argc ? argv[i] : \"HEAD\";\n+\n+\tif (get_sha1(object_ref, object))\n+\t\tdie(\"Failed to resolve '%s' as a valid ref.\", object_ref);\n+\n+\tif (snprintf(ref, sizeof ref, \"refs/tags/%s\", tag) > sizeof ref)\n+\t\tdie(\"tag '%s' too long.\", tag);\n+\tif (check_ref_format(ref))\n+\t\tdie(\"'%s' is not a valid tag name.\", tag);\n+\n+\tif (!resolve_ref(ref, prev, 1, NULL))\n+\t\thashclr(prev);\n+\telse if (!force)\n+\t\tdie(\"tag '%s' already exists\", tag);\n+\n+\tif (annotate)\n+\t\tcreate_tag(object, tag, message, sign, object);\n+\n+\tlock = lock_any_ref_for_update(ref, prev, 0);\n+\tif (!lock)\n+\t\tdie(\"%s: cannot lock the ref\", ref);\n+\tif (write_ref_sha1(lock, object, NULL) < 0)\n+\t\tdie(\"%s: cannot update the ref\", ref);\n+\n+\treturn 0;\n+}\ndiff --git a/builtin.h b/builtin.h\nindex 4cc228d..ac7417f 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -70,6 +70,7 @@ extern int cmd_show(int argc, const char **argv, const char *prefix);\n extern int cmd_show_branch(int argc, const char **argv, const char *prefix);\n extern int cmd_stripspace(int argc, const char **argv, const char *prefix);\n extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);\n+extern int cmd_tag(int argc, const char **argv, const char *prefix);\n extern int cmd_tar_tree(int argc, const char **argv, const char *prefix);\n extern int cmd_unpack_objects(int argc, const char **argv, const char *prefix);\n extern int cmd_update_index(int argc, const char **argv, const char *prefix);\ndiff --git a/git-tag.sh b/git-tag.sh\ndeleted file mode 100755\nindex 1c25d88..0000000\n--- a/git-tag.sh\n+++ /dev/null\n@@ -1,205 +0,0 @@\n-#!/bin/sh\n-# Copyright (c) 2005 Linus Torvalds\n-\n-USAGE='[-n [<num>]] -l [<pattern>] | [-a | -s | -u <key-id>] [-f | -d | -v] [-m <msg>] <tagname> [<head>]'\n-SUBDIRECTORY_OK='Yes'\n-. git-sh-setup\n-\n-message_given=\n-annotate=\n-signed=\n-force=\n-message=\n-username=\n-list=\n-verify=\n-LINES=0\n-while case \"$#\" in 0) break ;; esac\n-do\n-    case \"$1\" in\n-    -a)\n-\tannotate=1\n-\tshift\n-\t;;\n-    -s)\n-\tannotate=1\n-\tsigned=1\n-\tshift\n-\t;;\n-    -f)\n-\tforce=1\n-\tshift\n-\t;;\n-    -n)\n-        case \"$#,$2\" in\n-\t1,* | *,-*)\n-\t\tLINES=1 \t# no argument\n-\t\t;;\n-\t*)\tshift\n-\t\tLINES=$(expr \"$1\" : '\\([0-9]*\\)')\n-\t\t[ -z \"$LINES\" ] && LINES=1 # 1 line is default when -n is used\n-\t\t;;\n-\tesac\n-\tshift\n-\t;;\n-    -l)\n-\tlist=1\n-\tshift\n-\tcase $# in\n-\t0)\tPATTERN=\n-\t\t;;\n-\t*)\n-\t\tPATTERN=\"$1\"\t# select tags by shell pattern, not re\n-\t\tshift\n-\t\t;;\n-\tesac\n-\tgit rev-parse --symbolic --tags | sort |\n-\t    while read TAG\n-\t    do\n-\t        case \"$TAG\" in\n-\t\t*$PATTERN*) ;;\n-\t\t*)\t    continue ;;\n-\t\tesac\n-\t\t[ \"$LINES\" -le 0 ] && { echo \"$TAG\"; continue ;}\n-\t\tOBJTYPE=$(git cat-file -t \"$TAG\")\n-\t\tcase $OBJTYPE in\n-\t\ttag)\n-\t\t\tANNOTATION=$(git cat-file tag \"$TAG\" |\n-\t\t\t\tsed -e '1,/^$/d' |\n-\t\t\t\tsed -n -e \"\n-\t\t\t\t\t/^-----BEGIN PGP SIGNATURE-----\\$/q\n-\t\t\t\t\t2,\\$s/^/    /\n-\t\t\t\t\tp\n-\t\t\t\t\t${LINES}q\n-\t\t\t\t\")\n-\t\t\tprintf \"%-15s %s\\n\" \"$TAG\" \"$ANNOTATION\"\n-\t\t\t;;\n-\t\t*)      echo \"$TAG\"\n-\t\t\t;;\n-\t\tesac\n-\t    done\n-\t;;\n-    -m)\n-\tannotate=1\n-\tshift\n-\tmessage=\"$1\"\n-\tif test \"$#\" = \"0\"; then\n-\t    die \"error: option -m needs an argument\"\n-\telse\n-\t    message=\"$1\"\n-\t    message_given=1\n-\t    shift\n-\tfi\n-\t;;\n-    -F)\n-\tannotate=1\n-\tshift\n-\tif test \"$#\" = \"0\"; then\n-\t    die \"error: option -F needs an argument\"\n-\telse\n-\t    message=\"$(cat \"$1\")\"\n-\t    message_given=1\n-\t    shift\n-\tfi\n-\t;;\n-    -u)\n-\tannotate=1\n-\tsigned=1\n-\tshift\n-\tif test \"$#\" = \"0\"; then\n-\t    die \"error: option -u needs an argument\"\n-\telse\n-\t    username=\"$1\"\n-\t    shift\n-\tfi\n-\t;;\n-    -d)\n-\tshift\n-\thad_error=0\n-\tfor tag\n-\tdo\n-\t\tcur=$(git show-ref --verify --hash -- \"refs/tags/$tag\") || {\n-\t\t\techo >&2 \"Seriously, what tag are you talking about?\"\n-\t\t\thad_error=1\n-\t\t\tcontinue\n-\t\t}\n-\t\tgit update-ref -m 'tag: delete' -d \"refs/tags/$tag\" \"$cur\" || {\n-\t\t\thad_error=1\n-\t\t\tcontinue\n-\t\t}\n-\t\techo \"Deleted tag $tag.\"\n-\tdone\n-\texit $had_error\n-\t;;\n-    -v)\n-\tshift\n-\ttag_name=\"$1\"\n-\ttag=$(git show-ref --verify --hash -- \"refs/tags/$tag_name\") ||\n-\t\tdie \"Seriously, what tag are you talking about?\"\n-\tgit-verify-tag -v \"$tag\"\n-\texit $?\n-\t;;\n-    -*)\n-        usage\n-\t;;\n-    *)\n-\tbreak\n-\t;;\n-    esac\n-done\n-\n-[ -n \"$list\" ] && exit 0\n-\n-name=\"$1\"\n-[ \"$name\" ] || usage\n-prev=0000000000000000000000000000000000000000\n-if git show-ref --verify --quiet -- \"refs/tags/$name\"\n-then\n-    test -n \"$force\" || die \"tag '$name' already exists\"\n-    prev=`git rev-parse \"refs/tags/$name\"`\n-fi\n-shift\n-git check-ref-format \"tags/$name\" ||\n-\tdie \"we do not like '$name' as a tag name.\"\n-\n-object=$(git rev-parse --verify --default HEAD \"$@\") || exit 1\n-type=$(git cat-file -t $object) || exit 1\n-tagger=$(git-var GIT_COMMITTER_IDENT) || exit 1\n-\n-test -n \"$username\" ||\n-\tusername=$(git repo-config user.signingkey) ||\n-\tusername=$(expr \"z$tagger\" : 'z\\(.*>\\)')\n-\n-trap 'rm -f \"$GIT_DIR\"/TAG_TMP* \"$GIT_DIR\"/TAG_FINALMSG \"$GIT_DIR\"/TAG_EDITMSG' 0\n-\n-if [ \"$annotate\" ]; then\n-    if [ -z \"$message_given\" ]; then\n-        ( echo \"#\"\n-          echo \"# Write a tag message\"\n-          echo \"#\" ) > \"$GIT_DIR\"/TAG_EDITMSG\n-        ${VISUAL:-${EDITOR:-vi}} \"$GIT_DIR\"/TAG_EDITMSG || exit\n-    else\n-        printf '%s\\n' \"$message\" >\"$GIT_DIR\"/TAG_EDITMSG\n-    fi\n-\n-    grep -v '^#' <\"$GIT_DIR\"/TAG_EDITMSG |\n-    git stripspace >\"$GIT_DIR\"/TAG_FINALMSG\n-\n-    [ -s \"$GIT_DIR\"/TAG_FINALMSG -o -n \"$message_given\" ] || {\n-\techo >&2 \"No tag message?\"\n-\texit 1\n-    }\n-\n-    ( printf 'object %s\\ntype %s\\ntag %s\\ntagger %s\\n\\n' \\\n-\t\"$object\" \"$type\" \"$name\" \"$tagger\";\n-      cat \"$GIT_DIR\"/TAG_FINALMSG ) >\"$GIT_DIR\"/TAG_TMP\n-    rm -f \"$GIT_DIR\"/TAG_TMP.asc \"$GIT_DIR\"/TAG_FINALMSG\n-    if [ \"$signed\" ]; then\n-\tgpg -bsa -u \"$username\" \"$GIT_DIR\"/TAG_TMP &&\n-\tcat \"$GIT_DIR\"/TAG_TMP.asc >>\"$GIT_DIR\"/TAG_TMP ||\n-\tdie \"failed to sign the tag with GPG.\"\n-    fi\n-    object=$(git-mktag < \"$GIT_DIR\"/TAG_TMP)\n-fi\n-\n-git update-ref \"refs/tags/$name\" \"$object\" \"$prev\"\ndiff --git a/git.c b/git.c\nindex a647f9c..eb9e5ca 100644\n--- a/git.c\n+++ b/git.c\n@@ -363,6 +363,7 @@ static void handle_internal_command(int argc, const char **argv)\n \t\t{ \"show\", cmd_show, RUN_SETUP | USE_PAGER },\n \t\t{ \"stripspace\", cmd_stripspace },\n \t\t{ \"symbolic-ref\", cmd_symbolic_ref, RUN_SETUP },\n+\t\t{ \"tag\", cmd_tag, RUN_SETUP },\n \t\t{ \"tar-tree\", cmd_tar_tree },\n \t\t{ \"unpack-objects\", cmd_unpack_objects, RUN_SETUP },\n \t\t{ \"update-index\", cmd_update_index, RUN_SETUP },\n-- \n1.5.0\n"},{"id":"47118","messageId":"7vps2yxjgq.fsf@assigned-by-dhcp.cox.net","threadId":"8990","inReplyTo":"46952755.2050307@gmail.com","subject":"Re: [PATCH 2/2] Make git-tag a builtin.","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2007-07-12T05:46:29Z","receivedAt":"2007-07-12T05:46:29Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Carlos Rica <jasampler@gmail.com> writes:\n\n> Mime-Version: 1.0\n> Content-Type: text/plain; charset=windows-1252\n\nHmmmmmm.....\n\n> This replaces the script \"git-tag.sh\" with \"builtin-tag.c\".\n> It is based in a previous work on it from Kristian Høgsberg.\n>\n> There are some minor changes in the behaviour of \"git tag\" here:\n> \"git tag -v\" now can get more than one tag to verify, like \"git tag -d\" did,\n> \"git tag\" with no arguments prints all tags, more like \"git branch\" does, and\n> the template for the edited message adds an empty line, like in \"git commit\".\n\nThese probably are good changes (except that \"no argument\" case\nmight be a bit annoying, but I guess it's Ok).  Is there any\nneed for documentation updates with these changes?\n\n> diff --git a/builtin-tag.c b/builtin-tag.c\n> new file mode 100644\n> index 0000000..1824379\n> --- /dev/null\n> +++ b/builtin-tag.c\n> @@ -0,0 +1,430 @@\n> +/*\n> + * Builtin \"git tag\"\n> + *\n> + * Copyright (c) 2007 Kristian H??gsberg <krh@redhat.com>,\n\nI do not know how the above would come out, but it surely was\nnot Høgsberg in the copy I received...\n\nWe probably should make sure our sources are in UTF-8; I suspect\nbuiltin-branch.c is not.\n\n> + *                    Carlos Rica <jasampler@gmail.com>\n> + * Based on git-tag.sh and mktag.c by Linus Torvalds.\n> + */\n> +\n> +#include \"cache.h\"\n> +#include \"builtin.h\"\n> +#include \"refs.h\"\n> +#include \"tag.h\"\n> +#include \"run-command.h\"\n> +\n> +static const char builtin_tag_usage[] =\n> +  \"git-tag [-n [<num>]] -l [<pattern>] | [-a | -s | -u <key-id>] [-f | -d | -v] [-m <msg>] <tagname> [<head>]\";\n> +\n> +static char signingkey[1000];\n> +static int lines;\n> +\n> +static void launch_editor(const char *path, char **buffer, unsigned long *len)\n> +{\n> +\tconst char *editor, *terminal;\n> +\tstruct child_process child;\n> +\tconst char *args[3];\n> +\tint fd;\n> +\n> +\teditor = getenv(\"VISUAL\");\n> +\tif (!editor)\n> +\t\teditor = getenv(\"EDITOR\");\n> +\n> +\tterminal = getenv(\"TERM\");\n> +\tif (!editor && (!terminal || !strcmp(terminal, \"dumb\"))) {\n> +\t\tfprintf(stderr,\n> +\t\t\"Terminal is dumb but no VISUAL nor EDITOR defined.\\n\"\n> +\t\t\"Please supply the message using either -m or -F option.\\n\");\n> +\t\texit(1);\n> +\t}\n\nAh, this is taken from git-commit.sh ;-) Does your \"git tag\"\nsupport the -F option (builtin_tag_usage[] does not seem to\nmention it)?  I wonder what happens when this function migrates\nto editor.c and a new program, other than git-tag and\ngit-commit, that is without -F nor -m options wants to call\nthis.\n\n> +\tif (!editor)\n> +\t\teditor = \"vi\";\n> +\n> +\tmemset(&child, 0, sizeof(child));\n> +\tchild.argv = args;\n> +\targs[0] = editor;\n> +\targs[1] = path;\n> +\targs[2] = NULL;\n> +\n> +\tif (run_command(&child))\n> +\t\tdie(\"could not launch editor %s.\", editor);\n\nThis message is not quite true, isn't it?  The editor might have\nbeen launched but exited with non-zero status.\n\n> +\tfd = open(path, O_RDONLY);\n> +\tif (fd == -1)\n> +\t\tdie(\"could not read %s.\", path);\n\nAnd this is \"could not open\", with probably strerror(errno) to\nbe helpful.\n\n> +\tif (read_pipe(fd, buffer, len))\n> +\t\tdie(\"could not read message file '%s': %s\",\n> +\t\t    path, strerror(errno));\n> +\tclose(fd);\n> +}\n> +\n> +#define PGP_SIGNATURE \"-----BEGIN PGP SIGNATURE-----\"\n> +\n> +static int show_reference(const char *refname, const unsigned char *sha1,\n> +\t\t\t  int flag, void *cb_data)\n> +{\n> ...\n> +}\n\nOk.\n\n> +static int list_tags(const char *pattern)\n> +{\n> ...\n> +}\n\nOk.\n\n> +static int delete_tags(const char **argv)\n> +{\n> +\tconst char **p;\n> +\tchar ref[PATH_MAX];\n> +\tint had_error = 0;\n> +\tunsigned char sha1[20];\n> +\n> +\tfor (p = argv; *p; p++) {\n> +\t\tif (snprintf(ref, sizeof ref, \"refs/tags/%s\", *p) > sizeof ref)\n> +\t\t\tdie(\"tag name '%s' too long.\", *p);\n\n\"sizeof foo\" may be proper ANSI C, but somehow many people seem\nto find \"sizeof(foo)\" much easier to read, including me. \n\nI think the overflow check is wrong.  snprintf() does not write\nmore than sizeof(ref) including the training NUL, but when its\noutput is truncated, it returns the number of bytes that would\nhave been written to the buffer, excluding the NUL termination.\nWhat this means is that if it returns sizeof(ref), it also is a\ncase of overflow.  Further, if C library is not C99, e.g. older\nglibc (say 2.0.6), it could return negative status indicating an\nerror condition upon truncated output.\n\nI suspect you inherited these problems from builtin-branch.c,\nbut we'd better fix them there as well.\n\n> +\t\tif (!resolve_ref(ref, sha1, 1, NULL)) {\n> +\t\t\tfprintf(stderr, \"tag '%s' not found.\\n\", *p);\n> +\t\t\thad_error = 1;\n> +\t\t\tcontinue;\n> +\t\t}\n\nWe might want to call error(\"tag '%s' not found.\") instead for\nlater libification...\n\n> ...\n> +}\n> +\n> +static int run_verify_tag_command(unsigned char *sha1)\n> +{\n> +\tint ret;\n> +\tconst char *argv_verify_tag[] = {\"git-verify-tag\",\n> +\t\t\t\t\t\"-v\", \"SHA1_HEX\", NULL};\n> +\targv_verify_tag[2] = sha1_to_hex(sha1);\n> +\n> +\tret = run_command_v_opt(argv_verify_tag, 0);\n> +\n> +\tif (ret <= -10000)\n> +\t\tdie(\"unable to run %s\\n\", argv_verify_tag[0]);\n> +\treturn -ret;\n> +}\n\nI wonder why you need to differentiate between ERR_RUN_COMMAND_*\nand non-zero exit status...  Also do you need to \"return -ret\",\ninstead of not negating?  In C programs error returns are often\nnegative and finish_command() follows that convention.\n\n> +static int verify_tags(const char **argv)\n> +{\n> ...\n> +}\n\nOk.\n\n> +static int do_sign(char *buffer, size_t size, size_t max)\n> +{\n> +\tstruct child_process gpg;\n> +\tconst char *args[4];\n> +\tchar *bracket;\n> +\tint len;\n> +\n> +\tif (signingkey[0] == '\\0') {\n> +\t\tstrlcpy(signingkey, git_committer_info(1), sizeof signingkey);\n> +\t\tbracket = strchr(signingkey, '>');\n> +\t\tif (bracket)\n> +\t\t\tbracket[1] = '\\0';\n> +\t}\n\nHmph.  Silently truncate with strlcpy instead of erroring out on\ninsanely long committer info?\n\n> +\tmemset(&gpg, 0, sizeof(gpg));\n> +\tgpg.argv = args;\n> +\tgpg.in = -1;\n> +\tgpg.out = -1;\n> +\targs[0] = \"gpg\";\n> +\targs[1] = \"-bsau\";\n> +\targs[2] = signingkey;\n> +\targs[3] = NULL;\n> +\n> +\tif (start_command(&gpg))\n> +\t\tdie(\"could not run gpg.\");\n> +\n> +\twrite_or_die(gpg.in, buffer, size);\n> +\tclose(gpg.in);\n> +\tgpg.close_in = 0;\n> +\tlen = read_in_full(gpg.out, buffer + size, max - size);\n\nBi-di pipes makes me nervous, but this case should be Ok. It\ncould try writing \"---BEGIN PGP SIGNATURE---\" before reading\nfrom you and get stuck because you are not reading from it but\nthat is only in theory.\n\nI wonder why our read_in_full and write_in_full do not return\nssize_t although that's how they count things internally.  At\nleast you could make do_sign() to return ssize_t, and change the\ndie() to \"return error()\" to have the caller deal with it, which\nwould be easier for people other than \"git tag -s\" to call this\nif they later want to.\n\nWhat happens when gpg gave more than you expect?  We get\ntruncated signature and I do not see any code to notice the\nbreakage...\n\n> +\n> +\tfinish_command(&gpg);\n> +\n> +\treturn size + len;\n> +}\n> +\n> +static const char tag_template[] =\n> +\t\"\\n\"\n> +\t\"#\\n\"\n> +\t\"# Write a tag message\\n\"\n> +\t\"#\\n\";\n> +\n> +static int git_tag_config(const char *var, const char *value)\n> +{\n> ...\n> +}\n\nOk, except for the same strlcpy comment above.\n\n> +#define MAX_SIGNATURE_LENGTH 1024\n> +/* message must be NULL or allocated, it will be reallocated and freed */\n> +static void create_tag(const unsigned char *object, const char *tag,\n> +\t\t       char *message, int sign, unsigned char *result)\n> +{\n> +\tenum object_type type;\n> +\tchar header_buf[1024], *buffer;\n> +\tint header_len, max_size;\n> +\tunsigned long size;\n> +\n> +\ttype = sha1_object_info(object, NULL);\n> +\tif (type <= 0)\n> +\t    die(\"bad object type.\");\n> +\n> +\theader_len = snprintf(header_buf, sizeof header_buf,\n> +\t\t\t  \"object %s\\n\"\n> +\t\t\t  \"type %s\\n\"\n> +\t\t\t  \"tag %s\\n\"\n> +\t\t\t  \"tagger %s\\n\\n\",\n> +\t\t\t  sha1_to_hex(object),\n> +\t\t\t  typename(type),\n> +\t\t\t  tag,\n> +\t\t\t  git_committer_info(1));\n> +\n> +\tif (header_len >= sizeof header_buf)\n> +\t\tdie(\"tag header too big.\");\n\nThis comparison '>=' is correct (i.e. not '>'), as opposed to\nthe one I commented way above.\n\n> +\tif (!message) {\n> ...\n> +\t}\n> +\telse {\n> +\t\tbuffer = message;\n> +\t\tsize = strlen(message);\n> +\t}\n> +\n> +\tsize = stripspace(buffer, size, 1);\n> +\n> +\tif (!message && !size)\n> +\t\tdie(\"no tag message?\");\n\nWhy check 'message' here?\n\n> ...\n> +\tfree(buffer);\n> +}\n\nThe rest is Ok.\n\n> +int cmd_tag(int argc, const char **argv, const char *prefix)\n> +{\n> ...\n> +\t\tif (!strcmp(arg, \"-F\")) {\n> +\t\t\tunsigned long len;\n> +\t\t\tannotate = 1;\n> +\t\t\ti++;\n> +\t\t\tif  (i == argc)\n> +\t\t\t\tdie(\"option -F needs an argument.\");\n> +\n> +\t\t\tfd = open(argv[i], O_RDONLY);\n> +\t\t\tif (fd < 0)\n> +\t\t\t\tdie(\"cannot open %s\", argv[1]);\n\nThe shell script version relies on the magic \"cat -\" to read\nfrom standard input upon \"git tag -F -\".  It is understandable\nthat both of you and Dscho missed it, though.\n\n> +\t\t\tlen = 1024;\n> +\t\t\tmessage = xmalloc(len);\n> +\t\t\tif (read_pipe(fd, &message, &len))\n> +\t\t\t\tdie(\"cannot read %s\", argv[1]);\n> +\t\t\tmessage = xrealloc(message, len + 1);\n> +\t\t\tmessage[len] = '\\0';\n> +\t\t\tcontinue;\n> +\t\t}\n\nWe might be better off if read_pipe() is renamed to read_fd()\nand made internally always NUL-terminate the buffer (but not\ncount that NUL as part of length).  I dunno.\n\n> +\t\tif (!strcmp(arg, \"-u\")) {\n> +\t\t\tannotate = 1;\n> +\t\t\tsign = 1;\n> +\t\t\ti++;\n> +\t\t\tif  (i == argc)\n> +\t\t\t\tdie(\"option -u needs an argument.\");\n> +\t\t\tstrlcpy(signingkey, argv[i], sizeof signingkey);\n> +\t\t\tcontinue;\n\nExtra space \"if  (expr\".  Return from strlcpy() not checked?\n\n> +\tif (snprintf(ref, sizeof ref, \"refs/tags/%s\", tag) > sizeof ref)\n> +\t\tdie(\"tag '%s' too long.\", tag);\n\nThis use of snprintf() and checking the overflow as error is\nmuch nicer, don't you think, instead of silently ignoring\ntruncation?\n\n> ...\n> +}\n\nThe remainder of cmd_tag() looks Ok.\n"},{"id":"47208","messageId":"1b46aba20707121401n12796827n77b7e1d1ccde330c@mail.gmail.com","threadId":"8990","inReplyTo":"7vps2yxjgq.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH 2/2] Make git-tag a builtin.","fromName":"Carlos Rica","fromEmail":"jasampler@gmail.com","sentAt":"2007-07-12T21:01:57Z","receivedAt":"2007-07-12T21:01:57Z","isPatch":true,"sender":{"key":"jasampler@gmail.com","avatar":null},"body":"Thank you for your review, Junio, I'm studing all of your suggestions.\n\n2007/7/12, Junio C Hamano <gitster@pobox.com>:\n> Carlos Rica <jasampler@gmail.com> writes:\n>\n> > Mime-Version: 1.0\n> > Content-Type: text/plain; charset=windows-1252\n>\n> Hmmmmmm.....\n\n> > diff --git a/builtin-tag.c b/builtin-tag.c\n> > new file mode 100644\n> > index 0000000..1824379\n> > --- /dev/null\n> > +++ b/builtin-tag.c\n> > @@ -0,0 +1,430 @@\n> > +/*\n> > + * Builtin \"git tag\"\n> > + *\n> > + * Copyright (c) 2007 Kristian H??gsberg <krh@redhat.com>,\n>\n> I do not know how the above would come out, but it surely was\n> not Høgsberg in the copy I received...\n\nI think it is my fault. My original builtin-tag.c file has the\nname encoded in UTF-8, and the patch too, except because\nI also added his name in the commit's message in Latin-1 encoding,\nand then, programs cannot recode or recognize both. Sorry.\n\n>\n> > + *                    Carlos Rica <jasampler@gmail.com>\n> > + * Based on git-tag.sh and mktag.c by Linus Torvalds.\n> > + */\n> > +\n> > +#include \"cache.h\"\n> > +#include \"builtin.h\"\n> > +#include \"refs.h\"\n> > +#include \"tag.h\"\n> > +#include \"run-command.h\"\n> > +\n> > +static const char builtin_tag_usage[] =\n> > +  \"git-tag [-n [<num>]] -l [<pattern>] | [-a | -s | -u <key-id>] [-f | -d | -v] [-m <msg>] <tagname> [<head>]\";\n> > +\n> > +static char signingkey[1000];\n> > +static int lines;\n> > +\n> > +static void launch_editor(const char *path, char **buffer, unsigned long *len)\n> > +{\n> > +     const char *editor, *terminal;\n> > +     struct child_process child;\n> > +     const char *args[3];\n> > +     int fd;\n> > +\n> > +     editor = getenv(\"VISUAL\");\n> > +     if (!editor)\n> > +             editor = getenv(\"EDITOR\");\n> > +\n> > +     terminal = getenv(\"TERM\");\n> > +     if (!editor && (!terminal || !strcmp(terminal, \"dumb\"))) {\n> > +             fprintf(stderr,\n> > +             \"Terminal is dumb but no VISUAL nor EDITOR defined.\\n\"\n> > +             \"Please supply the message using either -m or -F option.\\n\");\n> > +             exit(1);\n> > +     }\n>\n> Ah, this is taken from git-commit.sh ;-) Does your \"git tag\"\n> support the -F option (builtin_tag_usage[] does not seem to\n> mention it)?  I wonder what happens when this function migrates\n> to editor.c and a new program, other than git-tag and\n> git-commit, that is without -F nor -m options wants to call\n> this.\n\nI will include that -F option in the builtin_tag_usage and I will also find\nif documentation already has it.\n\nAs I said above, launch_editor is copied and modified from the builtin-commit.c\nthat Kristian is currently writing. Indeed, I removed every mention on\nit related\nspecifically to commits, so it will be difficult to share the code between both\nversions in the current state. Perhaps a raw function not printing out errors\n(and returning error codes instead) could be the only option to reuse it. I need\nto ask Kristian for this.\n\n> > +static int run_verify_tag_command(unsigned char *sha1)\n> > +{\n> > +     int ret;\n> > +     const char *argv_verify_tag[] = {\"git-verify-tag\",\n> > +                                     \"-v\", \"SHA1_HEX\", NULL};\n> > +     argv_verify_tag[2] = sha1_to_hex(sha1);\n> > +\n> > +     ret = run_command_v_opt(argv_verify_tag, 0);\n> > +\n> > +     if (ret <= -10000)\n> > +             die(\"unable to run %s\\n\", argv_verify_tag[0]);\n> > +     return -ret;\n> > +}\n>\n> I wonder why you need to differentiate between ERR_RUN_COMMAND_*\n> and non-zero exit status...  Also do you need to \"return -ret\",\n> instead of not negating?  In C programs error returns are often\n> negative and finish_command() follows that convention.\n\nThe point here was to mimic the behaviour of git-tag.sh,\nreturning the exit status returned by the called script: \"git-verify-tag.sh\".\nHowever, I didn't realize that git-verify tag.sh was exiting only with 1 or 0,\nso \"return ret;\" will also work since the call to the function is:\n   if (run_verify_tag_command(sha1))\n      had_error = 1;\n\nWhen die is called (because of an ERR_RUN_COMMAND_* error condition),\nit will return 128 to the system, and won't do more processing to other tags.\nDo yo mean that it would be better to remove this \"die\" call here?\n\n>\n> > +     if (!message) {\n> > ...\n> > +     }\n> > +     else {\n> > +             buffer = message;\n> > +             size = strlen(message);\n> > +     }\n> > +\n> > +     size = stripspace(buffer, size, 1);\n> > +\n> > +     if (!message && !size)\n> > +             die(\"no tag message?\");\n>\n> Why check 'message' here?\n\nBecause the behaviour of git-tag.sh here is not allow empty\nmessages when the editor is executed, but allow them when\n-m or -F options are given (message then will be not NULL).\n\n> > +int cmd_tag(int argc, const char **argv, const char *prefix)\n> > +{\n> > ...\n> > +             if (!strcmp(arg, \"-F\")) {\n> > +                     unsigned long len;\n> > +                     annotate = 1;\n> > +                     i++;\n> > +                     if  (i == argc)\n> > +                             die(\"option -F needs an argument.\");\n> > +\n> > +                     fd = open(argv[i], O_RDONLY);\n> > +                     if (fd < 0)\n> > +                             die(\"cannot open %s\", argv[1]);\n>\n> The shell script version relies on the magic \"cat -\" to read\n> from standard input upon \"git tag -F -\".  It is understandable\n> that both of you and Dscho missed it, though.\n\nIt should be easy to implement, I will do it.\n\n> > +                     len = 1024;\n> > +                     message = xmalloc(len);\n> > +                     if (read_pipe(fd, &message, &len))\n> > +                             die(\"cannot read %s\", argv[1]);\n> > +                     message = xrealloc(message, len + 1);\n> > +                     message[len] = '\\0';\n> > +                     continue;\n> > +             }\n>\n> We might be better off if read_pipe() is renamed to read_fd()\n> and made internally always NUL-terminate the buffer (but not\n> count that NUL as part of length).  I dunno.\n\nI saw an implementation for read_pipe named read_fd from Kristian:\nhttp://article.gmane.org/gmane.comp.version-control.git/51366\nbut it is only focused on allowing NULL and 0 in pointer and size\nparameters. It should be harmless NUL-terminating the buffer\nsince the routine is already allocating more memory than needed\nfor efficiency purposes.\n"}]}