git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 2/2] Make git-tag a builtin.
@ 2007-07-11 18:54 Carlos Rica
  2007-07-12  5:46 ` Junio C Hamano
  0 siblings, 1 reply; 3+ messages in thread
From: Carlos Rica @ 2007-07-11 18:54 UTC (permalink / raw)
  To: git, Junio C Hamano, Johannes Schindelin, Kristian Høgsberg

This replaces the script "git-tag.sh" with "builtin-tag.c".
It is based in a previous work on it from Kristian Høgsberg.

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" did,
"git tag" with no arguments prints all tags, more like "git branch" does, and
the template for the edited message adds an empty line, like in "git commit".

The program is now calling to the script "git verify-tag" for verify,
something that should be changed porting it to C and calling its functions
directly from builtin-tag.c.

Signed-off-by: Carlos Rica <jasampler@gmail.com>
---

  As said, "git verify-tag" should be replaced with "git tag" for
  verify tags, since both do the same job.  The builtin "git mktag"
  is another candidate to be replaced with "git tag", however
  it is now an essential part of the "plumbing" family doing
  a neat thing not easily replaceable.

  The function launch_editor is copied and modified from the initial work
  from Kristian on builtin-commit.c, so we expect share that code on
  both builtins when it is finnished.

 Makefile      |    3 +-
 builtin-tag.c |  430 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 builtin.h     |    1 +
 git-tag.sh    |  205 ---------------------------
 git.c         |    1 +
 5 files changed, 434 insertions(+), 206 deletions(-)
 create mode 100644 builtin-tag.c
 delete mode 100755 git-tag.sh

diff --git a/Makefile b/Makefile
index d7541b4..f0da8f7 100644
--- a/Makefile
+++ b/Makefile
@@ -207,7 +207,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-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 \
@@ -376,6 +376,7 @@ BUILTIN_OBJS = \
 	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 \
diff --git a/builtin-tag.c b/builtin-tag.c
new file mode 100644
index 0000000..1824379
--- /dev/null
+++ b/builtin-tag.c
@@ -0,0 +1,430 @@
+/*
+ * 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>] <tagname> [<head>]";
+
+static char signingkey[1000];
+static int lines;
+
+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("could not launch editor %s.", editor);
+
+	fd = open(path, O_RDONLY);
+	if (fd == -1)
+		die("could not read %s.", path);
+	if (read_pipe(fd, buffer, len))
+		die("could not read message file '%s': %s",
+		    path, strerror(errno));
+	close(fd);
+}
+
+#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
+
+static int show_reference(const char *refname, const unsigned char *sha1,
+			  int flag, void *cb_data)
+{
+	const char *pattern = cb_data;
+
+	if (!fnmatch(pattern, refname, 0)) {
+		int i;
+		unsigned long size;
+		enum object_type type;
+		char *buf, *sp, *eol;
+		size_t len;
+
+		if (!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 < 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)
+{
+	char *newpattern;
+
+	if (pattern == NULL)
+		pattern = "";
+
+	/* prepend/append * to the shell pattern: */
+	newpattern = xmalloc(strlen(pattern) + 3);
+	sprintf(newpattern, "*%s*", pattern);
+
+	for_each_tag_ref(show_reference, (void *) newpattern);
+
+	free(newpattern);
+
+	return 0;
+}
+
+
+static int delete_tags(const char **argv)
+{
+	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)
+			die("tag name '%s' too long.", *p);
+		if (!resolve_ref(ref, sha1, 1, NULL)) {
+			fprintf(stderr, "tag '%s' not found.\n", *p);
+			had_error = 1;
+			continue;
+		}
+
+		if (delete_ref(ref, sha1))
+			had_error = 1;
+		else
+			printf("Deleted tag '%s'\n", *p);
+	}
+
+	return had_error;
+}
+
+static int run_verify_tag_command(unsigned char *sha1)
+{
+	int ret;
+	const char *argv_verify_tag[] = {"git-verify-tag",
+					"-v", "SHA1_HEX", NULL};
+	argv_verify_tag[2] = sha1_to_hex(sha1);
+
+	ret = run_command_v_opt(argv_verify_tag, 0);
+
+	if (ret <= -10000)
+		die("unable to run %s\n", argv_verify_tag[0]);
+	return -ret;
+}
+
+static int verify_tags(const char **argv)
+{
+	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)
+			die("tag name '%s' too long.", *p);
+
+		if (!resolve_ref(ref, sha1, 1, NULL)) {
+			fprintf(stderr, "tag '%s' not found.\n", *p);
+			had_error = 1;
+			continue;
+		}
+		if (run_verify_tag_command(sha1))
+			had_error = 1;
+	}
+
+	return had_error;
+}
+
+static int do_sign(char *buffer, size_t size, size_t max)
+{
+	struct child_process gpg;
+	const char *args[4];
+	char *bracket;
+	int len;
+
+	if (signingkey[0] == '\0') {
+		strlcpy(signingkey, git_committer_info(1), sizeof signingkey);
+		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))
+		die("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);
+
+	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");
+		strlcpy(signingkey, value, sizeof signingkey);
+		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.", path);
+		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 (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;
+	char *message = NULL;
+	char ref[PATH_MAX];
+	const char *object_ref, *tag;
+	int i, fd;
+	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;
+			annotate = 1;
+			i++;
+			if  (i == argc)
+				die("option -F needs an argument.");
+
+			fd = open(argv[i], O_RDONLY);
+			if (fd < 0)
+				die("cannot open %s", argv[1]);
+
+			len = 1024;
+			message = xmalloc(len);
+			if (read_pipe(fd, &message, &len))
+				die("cannot read %s", argv[1]);
+			message = xrealloc(message, len + 1);
+			message[len] = '\0';
+			continue;
+		}
+		if (!strcmp(arg, "-u")) {
+			annotate = 1;
+			sign = 1;
+			i++;
+			if  (i == argc)
+				die("option -u needs an argument.");
+			strlcpy(signingkey, argv[i], sizeof signingkey);
+			continue;
+		}
+		if (!strcmp(arg, "-l")) {
+			return list_tags(argv[i + 1]);
+		}
+		if (!strcmp(arg, "-d")) {
+			return delete_tags(argv + i + 1);
+		}
+		if (!strcmp(arg, "-v")) {
+			return verify_tags(argv + i + 1);
+		}
+		usage(builtin_tag_usage);
+	}
+
+	if (i == argc)
+		return list_tags(NULL);
+	tag = argv[i++];
+
+	object_ref = i < argc ? argv[i] : "HEAD";
+
+	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 '%s' too long.", 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;
+}
diff --git a/builtin.h b/builtin.h
index 4cc228d..ac7417f 100644
--- 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_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);
diff --git a/git-tag.sh b/git-tag.sh
deleted file mode 100755
index 1c25d88..0000000
--- a/git-tag.sh
+++ /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
-        ${VISUAL:-${EDITOR:-vi}} "$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 a647f9c..eb9e5ca 100644
--- 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 },
+		{ "tag", cmd_tag, RUN_SETUP },
 		{ "tar-tree", cmd_tar_tree },
 		{ "unpack-objects", cmd_unpack_objects, RUN_SETUP },
 		{ "update-index", cmd_update_index, RUN_SETUP },
-- 
1.5.0

^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [PATCH 2/2] Make git-tag a builtin.
  2007-07-11 18:54 [PATCH 2/2] Make git-tag a builtin Carlos Rica
@ 2007-07-12  5:46 ` Junio C Hamano
  2007-07-12 21:01   ` Carlos Rica
  0 siblings, 1 reply; 3+ messages in thread
From: Junio C Hamano @ 2007-07-12  5:46 UTC (permalink / raw)
  To: Carlos Rica; +Cc: git, Johannes Schindelin, Kristian Høgsberg

Carlos Rica <jasampler@gmail.com> writes:

> Mime-Version: 1.0
> Content-Type: text/plain; charset=windows-1252

Hmmmmmm.....

> This replaces the script "git-tag.sh" with "builtin-tag.c".
> It is based in a previous work on it from Kristian Høgsberg.
>
> 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" did,
> "git tag" with no arguments prints all tags, more like "git branch" does, and
> the template for the edited message adds an empty line, like in "git commit".

These probably are good changes (except that "no argument" case
might be a bit annoying, but I guess it's Ok).  Is there any
need for documentation updates with these changes?

> diff --git a/builtin-tag.c b/builtin-tag.c
> new file mode 100644
> index 0000000..1824379
> --- /dev/null
> +++ b/builtin-tag.c
> @@ -0,0 +1,430 @@
> +/*
> + * Builtin "git tag"
> + *
> + * Copyright (c) 2007 Kristian H??gsberg <krh@redhat.com>,

I do not know how the above would come out, but it surely was
not Høgsberg in the copy I received...

We probably should make sure our sources are in UTF-8; I suspect
builtin-branch.c is not.

> + *                    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>] <tagname> [<head>]";
> +
> +static char signingkey[1000];
> +static int lines;
> +
> +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);
> +	}

Ah, this is taken from git-commit.sh ;-) Does your "git tag"
support the -F option (builtin_tag_usage[] does not seem to
mention it)?  I wonder what happens when this function migrates
to editor.c and a new program, other than git-tag and
git-commit, that is without -F nor -m options wants to call
this.

> +	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("could not launch editor %s.", editor);

This message is not quite true, isn't it?  The editor might have
been launched but exited with non-zero status.

> +	fd = open(path, O_RDONLY);
> +	if (fd == -1)
> +		die("could not read %s.", path);

And this is "could not open", with probably strerror(errno) to
be helpful.

> +	if (read_pipe(fd, buffer, len))
> +		die("could not read message file '%s': %s",
> +		    path, strerror(errno));
> +	close(fd);
> +}
> +
> +#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
> +
> +static int show_reference(const char *refname, const unsigned char *sha1,
> +			  int flag, void *cb_data)
> +{
> ...
> +}

Ok.

> +static int list_tags(const char *pattern)
> +{
> ...
> +}

Ok.

> +static int delete_tags(const char **argv)
> +{
> +	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)
> +			die("tag name '%s' too long.", *p);

"sizeof foo" may be proper ANSI C, but somehow many people seem
to find "sizeof(foo)" much easier to read, including me. 

I think the overflow check is wrong.  snprintf() does not write
more than sizeof(ref) including the training NUL, but when its
output is truncated, it returns the number of bytes that would
have been written to the buffer, excluding the NUL termination.
What this means is that if it returns sizeof(ref), it also is a
case of overflow.  Further, if C library is not C99, e.g. older
glibc (say 2.0.6), it could return negative status indicating an
error condition upon truncated output.

I suspect you inherited these problems from builtin-branch.c,
but we'd better fix them there as well.

> +		if (!resolve_ref(ref, sha1, 1, NULL)) {
> +			fprintf(stderr, "tag '%s' not found.\n", *p);
> +			had_error = 1;
> +			continue;
> +		}

We might want to call error("tag '%s' not found.") instead for
later libification...

> ...
> +}
> +
> +static int run_verify_tag_command(unsigned char *sha1)
> +{
> +	int ret;
> +	const char *argv_verify_tag[] = {"git-verify-tag",
> +					"-v", "SHA1_HEX", NULL};
> +	argv_verify_tag[2] = sha1_to_hex(sha1);
> +
> +	ret = run_command_v_opt(argv_verify_tag, 0);
> +
> +	if (ret <= -10000)
> +		die("unable to run %s\n", argv_verify_tag[0]);
> +	return -ret;
> +}

I wonder why you need to differentiate between ERR_RUN_COMMAND_*
and non-zero exit status...  Also do you need to "return -ret",
instead of not negating?  In C programs error returns are often
negative and finish_command() follows that convention.

> +static int verify_tags(const char **argv)
> +{
> ...
> +}

Ok.

> +static int do_sign(char *buffer, size_t size, size_t max)
> +{
> +	struct child_process gpg;
> +	const char *args[4];
> +	char *bracket;
> +	int len;
> +
> +	if (signingkey[0] == '\0') {
> +		strlcpy(signingkey, git_committer_info(1), sizeof signingkey);
> +		bracket = strchr(signingkey, '>');
> +		if (bracket)
> +			bracket[1] = '\0';
> +	}

Hmph.  Silently truncate with strlcpy instead of erroring out on
insanely long committer info?

> +	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))
> +		die("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);

Bi-di pipes makes me nervous, but this case should be Ok. It
could try writing "---BEGIN PGP SIGNATURE---" before reading
from you and get stuck because you are not reading from it but
that is only in theory.

I wonder why our read_in_full and write_in_full do not return
ssize_t although that's how they count things internally.  At
least you could make do_sign() to return ssize_t, and change the
die() to "return error()" to have the caller deal with it, which
would be easier for people other than "git tag -s" to call this
if they later want to.

What happens when gpg gave more than you expect?  We get
truncated signature and I do not see any code to notice the
breakage...

> +
> +	finish_command(&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)
> +{
> ...
> +}

Ok, except for the same strlcpy comment above.

> +#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.");

This comparison '>=' is correct (i.e. not '>'), as opposed to
the one I commented way above.

> +	if (!message) {
> ...
> +	}
> +	else {
> +		buffer = message;
> +		size = strlen(message);
> +	}
> +
> +	size = stripspace(buffer, size, 1);
> +
> +	if (!message && !size)
> +		die("no tag message?");

Why check 'message' here?

> ...
> +	free(buffer);
> +}

The rest is Ok.

> +int cmd_tag(int argc, const char **argv, const char *prefix)
> +{
> ...
> +		if (!strcmp(arg, "-F")) {
> +			unsigned long len;
> +			annotate = 1;
> +			i++;
> +			if  (i == argc)
> +				die("option -F needs an argument.");
> +
> +			fd = open(argv[i], O_RDONLY);
> +			if (fd < 0)
> +				die("cannot open %s", argv[1]);

The shell script version relies on the magic "cat -" to read
from standard input upon "git tag -F -".  It is understandable
that both of you and Dscho missed it, though.

> +			len = 1024;
> +			message = xmalloc(len);
> +			if (read_pipe(fd, &message, &len))
> +				die("cannot read %s", argv[1]);
> +			message = xrealloc(message, len + 1);
> +			message[len] = '\0';
> +			continue;
> +		}

We might be better off if read_pipe() is renamed to read_fd()
and made internally always NUL-terminate the buffer (but not
count that NUL as part of length).  I dunno.

> +		if (!strcmp(arg, "-u")) {
> +			annotate = 1;
> +			sign = 1;
> +			i++;
> +			if  (i == argc)
> +				die("option -u needs an argument.");
> +			strlcpy(signingkey, argv[i], sizeof signingkey);
> +			continue;

Extra space "if  (expr".  Return from strlcpy() not checked?

> +	if (snprintf(ref, sizeof ref, "refs/tags/%s", tag) > sizeof ref)
> +		die("tag '%s' too long.", tag);

This use of snprintf() and checking the overflow as error is
much nicer, don't you think, instead of silently ignoring
truncation?

> ...
> +}

The remainder of cmd_tag() looks Ok.

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH 2/2] Make git-tag a builtin.
  2007-07-12  5:46 ` Junio C Hamano
@ 2007-07-12 21:01   ` Carlos Rica
  0 siblings, 0 replies; 3+ messages in thread
From: Carlos Rica @ 2007-07-12 21:01 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Kristian Høgsberg

Thank you for your review, Junio, I'm studing all of your suggestions.

2007/7/12, Junio C Hamano <gitster@pobox.com>:
> Carlos Rica <jasampler@gmail.com> writes:
>
> > Mime-Version: 1.0
> > Content-Type: text/plain; charset=windows-1252
>
> Hmmmmmm.....

> > diff --git a/builtin-tag.c b/builtin-tag.c
> > new file mode 100644
> > index 0000000..1824379
> > --- /dev/null
> > +++ b/builtin-tag.c
> > @@ -0,0 +1,430 @@
> > +/*
> > + * Builtin "git tag"
> > + *
> > + * Copyright (c) 2007 Kristian H??gsberg <krh@redhat.com>,
>
> I do not know how the above would come out, but it surely was
> not Høgsberg in the copy I received...

I think it is my fault. My original builtin-tag.c file has the
name encoded in UTF-8, and the patch too, except because
I also added his name in the commit's message in Latin-1 encoding,
and then, programs cannot recode or recognize both. Sorry.

>
> > + *                    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>] <tagname> [<head>]";
> > +
> > +static char signingkey[1000];
> > +static int lines;
> > +
> > +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);
> > +     }
>
> Ah, this is taken from git-commit.sh ;-) Does your "git tag"
> support the -F option (builtin_tag_usage[] does not seem to
> mention it)?  I wonder what happens when this function migrates
> to editor.c and a new program, other than git-tag and
> git-commit, that is without -F nor -m options wants to call
> this.

I will include that -F option in the builtin_tag_usage and I will also find
if documentation already has it.

As I said above, launch_editor is copied and modified from the builtin-commit.c
that Kristian is currently writing. Indeed, I removed every mention on
it related
specifically to commits, so it will be difficult to share the code between both
versions in the current state. Perhaps a raw function not printing out errors
(and returning error codes instead) could be the only option to reuse it. I need
to ask Kristian for this.

> > +static int run_verify_tag_command(unsigned char *sha1)
> > +{
> > +     int ret;
> > +     const char *argv_verify_tag[] = {"git-verify-tag",
> > +                                     "-v", "SHA1_HEX", NULL};
> > +     argv_verify_tag[2] = sha1_to_hex(sha1);
> > +
> > +     ret = run_command_v_opt(argv_verify_tag, 0);
> > +
> > +     if (ret <= -10000)
> > +             die("unable to run %s\n", argv_verify_tag[0]);
> > +     return -ret;
> > +}
>
> I wonder why you need to differentiate between ERR_RUN_COMMAND_*
> and non-zero exit status...  Also do you need to "return -ret",
> instead of not negating?  In C programs error returns are often
> negative and finish_command() follows that convention.

The point here was to mimic the behaviour of git-tag.sh,
returning the exit status returned by the called script: "git-verify-tag.sh".
However, I didn't realize that git-verify tag.sh was exiting only with 1 or 0,
so "return ret;" will also work since the call to the function is:
   if (run_verify_tag_command(sha1))
      had_error = 1;

When die is called (because of an ERR_RUN_COMMAND_* error condition),
it will return 128 to the system, and won't do more processing to other tags.
Do yo mean that it would be better to remove this "die" call here?

>
> > +     if (!message) {
> > ...
> > +     }
> > +     else {
> > +             buffer = message;
> > +             size = strlen(message);
> > +     }
> > +
> > +     size = stripspace(buffer, size, 1);
> > +
> > +     if (!message && !size)
> > +             die("no tag message?");
>
> Why check 'message' here?

Because the behaviour of git-tag.sh here is not allow empty
messages when the editor is executed, but allow them when
-m or -F options are given (message then will be not NULL).

> > +int cmd_tag(int argc, const char **argv, const char *prefix)
> > +{
> > ...
> > +             if (!strcmp(arg, "-F")) {
> > +                     unsigned long len;
> > +                     annotate = 1;
> > +                     i++;
> > +                     if  (i == argc)
> > +                             die("option -F needs an argument.");
> > +
> > +                     fd = open(argv[i], O_RDONLY);
> > +                     if (fd < 0)
> > +                             die("cannot open %s", argv[1]);
>
> The shell script version relies on the magic "cat -" to read
> from standard input upon "git tag -F -".  It is understandable
> that both of you and Dscho missed it, though.

It should be easy to implement, I will do it.

> > +                     len = 1024;
> > +                     message = xmalloc(len);
> > +                     if (read_pipe(fd, &message, &len))
> > +                             die("cannot read %s", argv[1]);
> > +                     message = xrealloc(message, len + 1);
> > +                     message[len] = '\0';
> > +                     continue;
> > +             }
>
> We might be better off if read_pipe() is renamed to read_fd()
> and made internally always NUL-terminate the buffer (but not
> count that NUL as part of length).  I dunno.

I saw an implementation for read_pipe named read_fd from Kristian:
http://article.gmane.org/gmane.comp.version-control.git/51366
but it is only focused on allowing NULL and 0 in pointer and size
parameters. It should be harmless NUL-terminating the buffer
since the routine is already allocating more memory than needed
for efficiency purposes.

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2007-07-12 21:02 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2007-07-11 18:54 [PATCH 2/2] Make git-tag a builtin Carlos Rica
2007-07-12  5:46 ` Junio C Hamano
2007-07-12 21:01   ` Carlos Rica

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).