Re: [PATCH 2/2] Make git-tag a builtin.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Carlos Rica <jasampler@xxxxxxxxx> 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@xxxxxxxxxx>,

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@xxxxxxxxx>
> + * 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.

-
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]

  Powered by Linux