{"thread":{"id":"9111","subject":"[PATCH] Make git tag a builtin.","startedAt":"2007-07-19T23:42:28Z","lastAt":"2007-07-20T10:15:32Z","messageCount":4,"participants":["Carlos Rica","Johannes Schindelin","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"47908","messageId":"469FF6E4.9010501@gmail.com","threadId":"9111","inReplyTo":null,"subject":"[PATCH] Make git tag a builtin.","fromName":"Carlos Rica","fromEmail":"jasampler@gmail.com","sentAt":"2007-07-19T23:42:28Z","receivedAt":"2007-07-19T23:42:28Z","isPatch":true,"sender":{"key":"jasampler@gmail.com","avatar":null},"body":"This replaces the script \"git-tag.sh\" with \"builtin-tag.c\".\n\nThe existing test suite for \"git tag\" guarantees the compatibility\nwith the features provided by the script version.\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\" does,\n\"git tag\" with no arguments prints all tags, more like \"git branch\" does,\nand \"git tag -n\" also prints all tags with annotations (without needing -l).\nTests and documentation were also updated to reflect these changes.\n\nThe program is currently calling the script \"git verify-tag\" for verify.\nThis can be changed porting it to C and calling its functions directly\nfrom builtin-tag.c.\n\nSigned-off-by: Carlos Rica <jasampler@gmail.com>\n---\n\n   This patch is a resend including the suggestions and corrections\n   commented by Junio in the previous proposal for builtin-tag.c:\n   http://article.gmane.org/gmane.comp.version-control.git/52246\n\n   In this version, a bit of refactoring was also done grouping the common\n   code between the previous functions delete_tags() and verify_tags().\n\n   The function launch_editor will be moved to another file\n   (editor.c is the suggested name), along with other tools\n   for editing and reading text files, in order to share its code\n   wiht other builtins like the upcoming builtin-commit.c from Kristian.\n\n Documentation/git-tag.txt                 |    8 +-\n Makefile                                  |    3 +-\n builtin-tag.c                             |  450 +++++++++++++++++++++++++++++\n builtin.h                                 |    1 +\n git-tag.sh => contrib/examples/git-tag.sh |    0\n git.c                                     |    1 +\n t/t7004-tag.sh                            |   88 +++++-\n 7 files changed, 538 insertions(+), 13 deletions(-)\n create mode 100644 builtin-tag.c\n rename git-tag.sh => contrib/examples/git-tag.sh (100%)\n\ndiff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt\nindex aee2c1b..119117f 100644\n--- a/Documentation/git-tag.txt\n+++ b/Documentation/git-tag.txt\n@@ -12,7 +12,7 @@ SYNOPSIS\n 'git-tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]  <name> [<head>]\n 'git-tag' -d <name>...\n 'git-tag' [-n [<num>]] -l [<pattern>]\n-'git-tag' -v <name>\n+'git-tag' -v <name>...\n\n DESCRIPTION\n -----------\n@@ -23,7 +23,7 @@ Unless `-f` is given, the tag must not yet exist in\n\n If one of `-a`, `-s`, or `-u <key-id>` is passed, the command\n creates a 'tag' object, and requires the tag message.  Unless\n-`-m <msg>` is given, an editor is started for the user to type\n+`-m <msg>` or `-F <file>` is given, an editor is started for the user to type\n in the tag message.\n\n Otherwise just the SHA1 object name of the commit object is\n@@ -59,15 +59,17 @@ OPTIONS\n \tDelete existing tags with the given names.\n\n -v::\n-\tVerify the gpg signature of given the tag\n+\tVerify the gpg signature of the given tag names.\n\n -n <num>::\n \t<num> specifies how many lines from the annotation, if any,\n \tare printed when using -l.\n \tThe default is not to print any annotation lines.\n+\tIf no number is given to `-n`, only the first line is printed.\n\n -l <pattern>::\n \tList tags with names that match the given pattern (or all if no pattern is given).\n+\tTyping \"git tag\" without arguments, also lists all tags.\n\n -m <msg>::\n \tUse the given tag message (instead of prompting)\ndiff --git a/Makefile b/Makefile\nindex 73b487f..8db6646 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -206,7 +206,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@@ -361,6 +361,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..507f510\n--- /dev/null\n+++ b/builtin-tag.c\n@@ -0,0 +1,450 @@\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> | -F <file>] <tagname> [<head>]\";\n+\n+static char signingkey[1000];\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(\"There was a problem with the editor %s.\", editor);\n+\n+\tfd = open(path, O_RDONLY);\n+\tif (fd < 0)\n+\t\tdie(\"could not open '%s': %s\", path, strerror(errno));\n+\tif (read_fd(fd, buffer, len)) {\n+\t\tfree(*buffer);\n+\t\tdie(\"could not read message file '%s': %s\",\n+\t\t\t\t\t\tpath, strerror(errno));\n+\t}\n+\tclose(fd);\n+}\n+\n+struct tag_filter {\n+\tconst char *pattern;\n+\tint lines;\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+\tstruct tag_filter *filter = cb_data;\n+\n+\tif (!fnmatch(filter->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 (!filter->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 < filter->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, int lines)\n+{\n+\tstruct tag_filter filter;\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+\tfilter.pattern = newpattern;\n+\tfilter.lines = lines;\n+\n+\tfor_each_tag_ref(show_reference, (void *) &filter);\n+\n+\tfree(newpattern);\n+\n+\treturn 0;\n+}\n+\n+typedef int (*func_tag)(const char *name, const char *ref,\n+\t\t\t\tconst unsigned char *sha1);\n+\n+static int do_tag_names(const char **argv, func_tag fn)\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)\n+\t\t\t\t\t>= sizeof(ref)) {\n+\t\t\terror(\"tag name too long: %.*s...\", 50, *p);\n+\t\t\thad_error = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!resolve_ref(ref, sha1, 1, NULL)) {\n+\t\t\terror(\"tag '%s' not found.\", *p);\n+\t\t\thad_error = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (fn(*p, ref, sha1))\n+\t\t\thad_error = 1;\n+\t}\n+\treturn had_error;\n+}\n+\n+static int delete_tag(const char *name, const char *ref,\n+\t\t\t\tconst unsigned char *sha1)\n+{\n+\tif (delete_ref(ref, sha1))\n+\t\treturn 1;\n+\tprintf(\"Deleted tag '%s'\\n\", name);\n+\treturn 0;\n+}\n+\n+static int verify_tag(const char *name, const char *ref,\n+\t\t\t\tconst unsigned char *sha1)\n+{\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+\tif (run_command_v_opt(argv_verify_tag, 0))\n+\t\treturn error(\"could not verify the tag '%s'\", name);\n+\treturn 0;\n+}\n+\n+static ssize_t 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) {\n+\t\tif (strlcpy(signingkey, git_committer_info(1),\n+\t\t\t\tsizeof(signingkey)) >= sizeof(signingkey))\n+\t\t\treturn error(\"committer info too long.\");\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\treturn error(\"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+\tif (len == max - size)\n+\t\treturn error(\"could not read the entire signature from 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\tif (strlcpy(signingkey, value, sizeof(signingkey))\n+\t\t\t\t\t\t>= sizeof(signingkey))\n+\t\t\tdie(\"user.signingkey value too long\");\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': %s\",\n+\t\t\t\t\t\tpath, strerror(errno));\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+\t\tif (size < 0)\n+\t\t\tdie(\"unable to sign the tag\");\n+\t}\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, lines = 0;\n+\tchar *message = NULL;\n+\tchar ref[PATH_MAX];\n+\tconst char *object_ref, *tag;\n+\tint i;\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\tint fd;\n+\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\tif (!strcmp(argv[i], \"-\"))\n+\t\t\t\tfd = 0;\n+\t\t\telse {\n+\t\t\t\tfd = open(argv[i], O_RDONLY);\n+\t\t\t\tif (fd < 0)\n+\t\t\t\t\tdie(\"could not open '%s': %s\",\n+\t\t\t\t\t\targv[i], strerror(errno));\n+\t\t\t}\n+\t\t\tlen = 1024;\n+\t\t\tmessage = xmalloc(len);\n+\t\t\tif (read_fd(fd, &message, &len)) {\n+\t\t\t\tfree(message);\n+\t\t\t\tdie(\"cannot read %s\", argv[i]);\n+\t\t\t}\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\tif (strlcpy(signingkey, argv[i], sizeof(signingkey))\n+\t\t\t\t\t\t\t>= sizeof(signingkey))\n+\t\t\t\tdie(\"argument to option -u too long\");\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(arg, \"-l\")) {\n+\t\t\treturn list_tags(argv[i + 1], lines);\n+\t\t}\n+\t\tif (!strcmp(arg, \"-d\")) {\n+\t\t\treturn do_tag_names(argv + i + 1, delete_tag);\n+\t\t}\n+\t\tif (!strcmp(arg, \"-v\")) {\n+\t\t\treturn do_tag_names(argv + i + 1, verify_tag);\n+\t\t}\n+\t\tusage(builtin_tag_usage);\n+\t}\n+\n+\tif (i == argc) {\n+\t\tif (annotate)\n+\t\t\tusage(builtin_tag_usage);\n+\t\treturn list_tags(NULL, lines);\n+\t}\n+\ttag = argv[i++];\n+\n+\tobject_ref = i < argc ? argv[i] : \"HEAD\";\n+\tif (i + 1 < argc)\n+\t\tdie(\"too many params\");\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 name too long: %.*s...\", 50, 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/contrib/examples/git-tag.sh\nsimilarity index 100%\nrename from git-tag.sh\nrename to contrib/examples/git-tag.sh\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 },\ndiff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\nindex 17de2a9..a0be164 100755\n--- a/t/t7004-tag.sh\n+++ b/t/t7004-tag.sh\n@@ -5,7 +5,7 @@\n\n test_description='git-tag\n\n-Basic tests for operations with tags.'\n+Tests for operations with tags.'\n\n . ./test-lib.sh\n\n@@ -16,11 +16,15 @@ tag_exists () {\n }\n\n # todo: git tag -l now returns always zero, when fixed, change this test\n-test_expect_success 'listing all tags in an empty tree should succeed' \\\n-\t'git tag -l'\n+test_expect_success 'listing all tags in an empty tree should succeed' '\n+\tgit tag -l &&\n+\tgit tag\n+'\n\n-test_expect_success 'listing all tags in an empty tree should output nothing' \\\n-\t'test `git-tag -l | wc -l` -eq 0'\n+test_expect_success 'listing all tags in an empty tree should output nothing' '\n+\ttest `git-tag -l | wc -l` -eq 0 &&\n+\ttest `git-tag | wc -l` -eq 0\n+'\n\n test_expect_failure 'looking for a tag in an empty tree should fail' \\\n \t'tag_exists mytag'\n@@ -49,11 +53,15 @@ test_expect_success 'creating a tag using default HEAD should succeed' '\n \tgit tag mytag\n '\n\n-test_expect_success 'listing all tags if one exists should succeed' \\\n-\t'git-tag -l'\n+test_expect_success 'listing all tags if one exists should succeed' '\n+\tgit-tag -l &&\n+\tgit-tag\n+'\n\n-test_expect_success 'listing all tags if one exists should output that tag' \\\n-\t'test `git-tag -l` = mytag'\n+test_expect_success 'listing all tags if one exists should output that tag' '\n+\ttest `git-tag -l` = mytag &&\n+\ttest `git-tag` = mytag\n+'\n\n # pattern matching:\n\n@@ -165,6 +173,8 @@ test_expect_success 'listing all tags should print them ordered' '\n \tgit tag v1.0 &&\n \tgit tag t210 &&\n \tgit tag -l > actual &&\n+\tgit diff expect actual &&\n+\tgit tag > actual &&\n \tgit diff expect actual\n '\n\n@@ -264,6 +274,10 @@ test_expect_failure \\\n \t'trying to verify a non-annotated and non-signed tag should fail' \\\n \t'git-tag -v non-annotated-tag'\n\n+test_expect_failure \\\n+\t'trying to verify many non-annotated or unknown tags, should fail' \\\n+\t'git-tag -v unknown-tag1 non-annotated-tag unknown-tag2'\n+\n # creating annotated tags:\n\n get_tag_msg () {\n@@ -306,6 +320,18 @@ test_expect_success \\\n \tgit diff expect actual\n '\n\n+cat >inputmsg <<EOF\n+A message from the\n+standard input\n+EOF\n+get_tag_header stdin-annotated-tag $commit commit $time >expect\n+cat inputmsg >>expect\n+test_expect_success 'creating an annotated tag with -F - should succeed' '\n+\tgit-tag -F - stdin-annotated-tag <inputmsg &&\n+\tget_tag_msg stdin-annotated-tag >actual &&\n+\tgit diff expect actual\n+'\n+\n # blank and empty messages:\n\n get_tag_header empty-annotated-tag $commit commit $time >expect\n@@ -551,6 +577,12 @@ test_expect_success \\\n \t! git-tag -v file-annotated-tag\n '\n\n+test_expect_success \\\n+\t'trying to verify two annotated non-signed tags should fail' '\n+\ttag_exists annotated-tag file-annotated-tag &&\n+\t! git-tag -v annotated-tag file-annotated-tag\n+'\n+\n # creating and verifying signed tags:\n\n gpg --version >/dev/null\n@@ -589,9 +621,47 @@ test_expect_success 'creating a signed tag with -m message should succeed' '\n \tgit diff expect actual\n '\n\n+cat >sigmsgfile <<EOF\n+Another signed tag\n+message in a file.\n+EOF\n+get_tag_header file-signed-tag $commit commit $time >expect\n+cat sigmsgfile >>expect\n+echo '-----BEGIN PGP SIGNATURE-----' >>expect\n+test_expect_success \\\n+\t'creating a signed tag with -F messagefile should succeed' '\n+\tgit-tag -s -F sigmsgfile file-signed-tag &&\n+\tget_tag_msg file-signed-tag >actual &&\n+\tgit diff expect actual\n+'\n+\n+cat >siginputmsg <<EOF\n+A signed tag message from\n+the standard input\n+EOF\n+get_tag_header stdin-signed-tag $commit commit $time >expect\n+cat siginputmsg >>expect\n+echo '-----BEGIN PGP SIGNATURE-----' >>expect\n+test_expect_success 'creating a signed tag with -F - should succeed' '\n+\tgit-tag -s -F - stdin-signed-tag <siginputmsg &&\n+\tget_tag_msg stdin-signed-tag >actual &&\n+\tgit diff expect actual\n+'\n+\n test_expect_success 'verifying a signed tag should succeed' \\\n \t'git-tag -v signed-tag'\n\n+test_expect_success 'verifying two signed tags in one command should succeed' \\\n+\t'git-tag -v signed-tag file-signed-tag'\n+\n+test_expect_success \\\n+\t'verifying many signed and non-signed tags should fail' '\n+\t! git-tag -v signed-tag annotated-tag &&\n+\t! git-tag -v file-annotated-tag file-signed-tag &&\n+\t! git-tag -v annotated-tag file-signed-tag file-annotated-tag &&\n+\t! git-tag -v signed-tag annotated-tag file-signed-tag\n+'\n+\n test_expect_success 'verifying a forged tag should fail' '\n \tforged=$(git cat-file tag signed-tag |\n \t\tsed -e \"s/signed-tag/forged-tag/\" |\n-- \n1.5.0\n"},{"id":"47945","messageId":"Pine.LNX.4.64.0707200928050.14781@racer.site","threadId":"9111","inReplyTo":"469FF6E4.9010501@gmail.com","subject":"Re: [PATCH] Make git tag a builtin.","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-07-20T08:53:31Z","receivedAt":"2007-07-20T08:53:31Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Fri, 20 Jul 2007, Carlos Rica wrote:\n\n> diff --git a/builtin-tag.c b/builtin-tag.c\n> new file mode 100644\n> index 0000000..507f510\n> --- /dev/null\n> +++ b/builtin-tag.c\n> @@ -0,0 +1,450 @@\n> +/*\n> + * Builtin \"git tag\"\n> + *\n> + * Copyright (c) 2007 Kristian Hv/gsberg <krh@redhat.com>,\n\nIn case of encoding problems, I suggest uploading it to repo.or.cz, so it \ncan be pulled.  Uploading has more advantages, such as visibility and \na free backup, too...\n\n> +typedef int (*func_tag)(const char *name, const char *ref,\n> +\t\t\t\tconst unsigned char *sha1);\n> +\n> +static int do_tag_names(const char **argv, func_tag fn)\n\nNeat!  Just a minor style nit here, though: in refs.h, we have something \nsimilar, and I would have expected to read \"each_tag_name_fn\" and \n\"for_each_tag_name\" instead of \"func_tag\" and \"do_tag_names\", \nrespectively.  Hmm?\n\n> +static ssize_t 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) {\n> +\t\tif (strlcpy(signingkey, git_committer_info(1),\n> +\t\t\t\tsizeof(signingkey)) >= sizeof(signingkey))\n\nsizeof(signingkey) - 1?\n\n> +\tlen = read_in_full(gpg.out, buffer + size, max - size);\n> +\n> +\tfinish_command(&gpg);\n> +\n> +\tif (len == max - size)\n> +\t\treturn error(\"could not read the entire signature from gpg.\");\n\nMaybe at a later stage, and if this turns out to be not sufficient to \nbegin with, we might consider not using a buffer, but writing \nincrementally into an object right away.  Think \"git hash-object -w \n--stdin\".\n\nBut at the moment, this is not a problem, so let's keep it as-is.\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\tif (strlcpy(signingkey, value, sizeof(signingkey))\n> +\t\t\t\t\t\t>= sizeof(signingkey))\n\nsizeof(signingkey) - 1?\n\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\nMaybe use OBJ_NONE instead of 0?  Dunno.\n\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\nsizeof(header_buf) - 1?\n\n> +\tmemmove(buffer + header_len, buffer, size);\n\nJust two ideas for the future (it is not really important now, so we \nshould not do it now):\n\n- Instead of memmove()ing, we could teach read_fd() to take an offset, \n  too.\n\n- launch_editor() could learn to write the buffer itself (IMHO it is more \n  common to write a buffer to the file before editing it, than editing an \n  existing file).\n\nBut as I said, this is just something to keep in mind, not to change now.\n\n> +\t\tif (!strcmp(arg, \"-F\")) {\n> +\t\t\tunsigned long len;\n> +\t\t\tint fd;\n> +\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\tif (!strcmp(argv[i], \"-\"))\n> +\t\t\t\tfd = 0;\n> +\t\t\telse {\n> +\t\t\t\tfd = open(argv[i], O_RDONLY);\n> +\t\t\t\tif (fd < 0)\n> +\t\t\t\t\tdie(\"could not open '%s': %s\",\n> +\t\t\t\t\t\targv[i], strerror(errno));\n> +\t\t\t}\n> +\t\t\tlen = 1024;\n> +\t\t\tmessage = xmalloc(len);\n> +\t\t\tif (read_fd(fd, &message, &len)) {\n\nWith your recent sanification of read_fd(), you can initialise len and \nmessage to 0 and NULL, respectively.\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\tif (strlcpy(signingkey, argv[i], sizeof(signingkey))\n> +\t\t\t\t\t\t\t>= sizeof(signingkey))\n\nsizeof(signingkey) - 1?\n\n> +\t\t\t\tdie(\"argument to option -u too long\");\n> +\t\t\tcontinue;\n> +\t\t}\n> +\t\tif (!strcmp(arg, \"-l\")) {\n> +\t\t\treturn list_tags(argv[i + 1], lines);\n> +\t\t}\n> +\t\tif (!strcmp(arg, \"-d\")) {\n> +\t\t\treturn do_tag_names(argv + i + 1, delete_tag);\n> +\t\t}\n> +\t\tif (!strcmp(arg, \"-v\")) {\n> +\t\t\treturn do_tag_names(argv + i + 1, verify_tag);\n> +\t\t}\n\nPlease lose the \"{..}\" around single lines.\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\nsizeof(ref) - 1?\n\n> diff --git a/git-tag.sh b/contrib/examples/git-tag.sh\n> similarity index 100%\n> rename from git-tag.sh\n> rename to contrib/examples/git-tag.sh\n\nThat should make Nico and Junio happy.\n\nThese are my last minor nits.  If nobody else has objections, let's put \nthis ship to sea.\n\nCiao,\nDscho\n"},{"id":"47946","messageId":"7v1wf3zbhj.fsf@assigned-by-dhcp.cox.net","threadId":"9111","inReplyTo":"469FF6E4.9010501@gmail.com","subject":"Re: [PATCH] Make git tag a builtin.","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2007-07-20T09:10:48Z","receivedAt":"2007-07-20T09:10:48Z","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> This replaces the script \"git-tag.sh\" with \"builtin-tag.c\".\n\nThanks.  Will queue in 'next', and perhaps with a few nit fixups\nto merge as the first thing after 1.5.3.\n\n> diff --git a/builtin-tag.c b/builtin-tag.c\n> new file mode 100644\n> index 0000000..507f510\n> --- /dev/null\n> +++ b/builtin-tag.c\n> @@ -0,0 +1,450 @@\n> +/*\n> + * Builtin \"git tag\"\n> + *\n> + * Copyright (c) 2007 Kristian H??gsberg <krh@redhat.com>,\n\nI've fixed up the name here in my copy.\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\nlaunch-editor() would need adjustment for GIT_EDITOR and\ncore.editor which should be straightforward.\n\n> +struct tag_filter {\n> +\tconst char *pattern;\n> +\tint lines;\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> +\tstruct tag_filter *filter = cb_data;\n> +\n> +\tif (!fnmatch(filter->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 (!filter->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\nTheoretically, I can create millions of lightweight tags, all of\nthem pointing at a zero-length blob object (or an empty tree\nobject) and kill you with memory leak here ;-).\n\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 < filter->lines && sp < buf + size &&\n> +\t\t\t\tprefixcmp(sp, PGP_SIGNATURE \"\\n\");\n> +\t\t\t\ti++) {\n\nMinor nit; I would have split this to four physical lines, like:\n\n\t\tfor (i = 0, sp += 2;\n\t\t\ti < filter->lines && sp < buf + size &&\n\t\t\tprefixcmp(sp, PGP_SIGNATURE \"\\n\");\n\t\t\ti++) {\n\nThe places that semicolons appear are more significant gaps when\nreading the code.\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\tint fd;\n> +\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\tif (!strcmp(argv[i], \"-\"))\n> +\t\t\t\tfd = 0;\n> +\t\t\telse {\n> +\t\t\t\tfd = open(argv[i], O_RDONLY);\n> +\t\t\t\tif (fd < 0)\n> +\t\t\t\t\tdie(\"could not open '%s': %s\",\n> +\t\t\t\t\t\targv[i], strerror(errno));\n> +\t\t\t}\n> +\t\t\tlen = 1024;\n> +\t\t\tmessage = xmalloc(len);\n\nYou cannot anticipate how many bytes the user will type (or\npipe-in), but when you opened the file you could fstat() to see\nhow many bytes you would need to hold the contents of that\nfile.  Even in stdin case fstat(fd) could tell you the size, but\nI am not sure how to tell if the st_size is reliable.  But for\nthe purposes of \"git tag\", 1k buffer that grows on demand is\nprobably cheaper than a fstat() syscall.\n\n> +\t\t\tif (read_fd(fd, &message, &len)) {\n> +\t\t\t\tfree(message);\n> +\t\t\t\tdie(\"cannot read %s\", argv[i]);\n> +\t\t\t}\n> +\t\t\tcontinue;\n\nWe seem to leak fd here, but the user is doing something insane\nif he gives more than one -F anyway, so it probably is Ok, as\nlong as we have some sanity checks.  Should we barf on \"git tag\n-m foo -F bar\"?  What should we do with \"git tag -m one -m two\"?\n\nDoes the test suite check any of these conditions?\n\n> +\tif (snprintf(ref, sizeof(ref), \"refs/tags/%s\", tag) >= sizeof(ref))\n> +\t\tdie(\"tag name too long: %.*s...\", 50, tag);\n\nCute and considerate ;-).\n\n> diff --git a/git-tag.sh b/contrib/examples/git-tag.sh\n> similarity index 100%\n> rename from git-tag.sh\n> rename to contrib/examples/git-tag.sh\n\nThis actually caught my attention for completely different\nreason.  If this were removal of git-tag.sh, this would have\nconflicted when applied on top of Adam's GIT_EDITOR/core.editor\npatch.  With a rename, however, there is no preimage/context\nshown, so it just cleanly applied.\n\nAlso Johannes's final minor nits.\n"},{"id":"47949","messageId":"Pine.LNX.4.64.0707201110460.14781@racer.site","threadId":"9111","inReplyTo":"7v1wf3zbhj.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH] Make git tag a builtin.","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-07-20T10:15:32Z","receivedAt":"2007-07-20T10:15:32Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Fri, 20 Jul 2007, Junio C Hamano wrote:\n\n> Carlos Rica <jasampler@gmail.com> writes:\n> \n> > This replaces the script \"git-tag.sh\" with \"builtin-tag.c\".\n> \n> Thanks.  Will queue in 'next', and perhaps with a few nit fixups\n> to merge as the first thing after 1.5.3.\n\nNice!\n\n> launch-editor() would need adjustment for GIT_EDITOR and core.editor \n> which should be straightforward.\n\nYes.  As a separate commit?  After a couple of revisions, I lost track of \nwhat I looked at, and what not...  So it would make it easier for me to \nhave these changes after the big builtin-tag commit.\n\n> > +\t\tsp = buf = read_sha1_file(sha1, &type, &size);\n> > +\t\tif (!buf || !size)\n> > +\t\t\treturn 0;\n> \n> Theoretically, I can create millions of lightweight tags, all of\n> them pointing at a zero-length blob object (or an empty tree\n> object) and kill you with memory leak here ;-).\n\nYes ;-)  Would we really want to say\n\n\tif (!buf)\n\t\treturn 0;\n\tif (!size) {\n\t\tfree(buf);\n\t\treturn 0;\n\t}\n\nhere?  IMHO that is overkill.\n\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 < filter->lines && sp < buf + size &&\n> > +\t\t\t\tprefixcmp(sp, PGP_SIGNATURE \"\\n\");\n> > +\t\t\t\ti++) {\n> \n> Minor nit; I would have split this to four physical lines, like:\n> \n> \t\tfor (i = 0, sp += 2;\n> \t\t\ti < filter->lines && sp < buf + size &&\n> \t\t\tprefixcmp(sp, PGP_SIGNATURE \"\\n\");\n> \t\t\ti++) {\n> \n> The places that semicolons appear are more significant gaps when\n> reading the code.\n\nI am to blame for that.  That code was outlined by me.\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\tint fd;\n> > +\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\tif (!strcmp(argv[i], \"-\"))\n> > +\t\t\t\tfd = 0;\n> > +\t\t\telse {\n> > +\t\t\t\tfd = open(argv[i], O_RDONLY);\n> > +\t\t\t\tif (fd < 0)\n> > +\t\t\t\t\tdie(\"could not open '%s': %s\",\n> > +\t\t\t\t\t\targv[i], strerror(errno));\n> > +\t\t\t}\n> > +\t\t\tlen = 1024;\n> > +\t\t\tmessage = xmalloc(len);\n> \n> You cannot anticipate how many bytes the user will type (or\n> pipe-in), but when you opened the file you could fstat() to see\n> how many bytes you would need to hold the contents of that\n> file.  Even in stdin case fstat(fd) could tell you the size, but\n> I am not sure how to tell if the st_size is reliable.  But for\n> the purposes of \"git tag\", 1k buffer that grows on demand is\n> probably cheaper than a fstat() syscall.\n\nFor a one-shot program as git-tag, I agree, the current patch is \nsufficient.\n\nCiao,\nDscho\n"}]}