[PATCH] Port git-tag.sh to C.

Subsystems: kernel build + files below scripts/ (unless maintained elsewhere), the rest

DORMANTno replies

6 messages, 4 authors, 2016-06-15 · open the first message on its own page

[PATCH] Port git-tag.sh to C.

From: Kristian Høgsberg <hidden>
Date: 2016-06-15 22:43:14

A more or less straight-forward port of git-tag.sh to C.

Signed-off-by: Kristian Høgsberg <redacted>
---

There's two behavioral changes here: git tag with no arguments behaves like
git branch with no arguments; it lists all tags.  The other change is
an empty line in the tag message template to make it look more like the
commit message template.

cheers,
Kristian
 

 Makefile      |    3 +-
 builtin-tag.c |  364 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 builtin.h     |    1 +
 git-tag.sh    |  183 -----------------------------
 git.c         |    1 +
 5 files changed, 368 insertions(+), 184 deletions(-)
 create mode 100644 builtin-tag.c
 delete mode 100755 git-tag.sh
diff --git a/Makefile b/Makefile
index 0f75955..bb1bed1 100644
--- a/Makefile
+++ b/Makefile
@@ -205,7 +205,7 @@ SCRIPT_SH = \
 	git-pull.sh git-rebase.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 \
@@ -372,6 +372,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..06eafc0
--- /dev/null
+++ b/builtin-tag.c
@@ -0,0 +1,364 @@
+/*
+ * Builtin "git tag"
+ *
+ * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>
+ * Based on git-tag.sh and mktag.c by Linus Torvalds.
+ */
+
+#include "cache.h"
+#include "refs.h"
+#include "commit.h"
+#include "builtin.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 launch_editor(const char *path, const char *template,
+			  char *buffer, size_t size)
+{
+	struct child_process child;
+	const char *editor;
+	const char *args[3];
+	char *eol;
+	int len, fd, blank_lines, i, j;
+
+	fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0644);
+	if (fd < 0)
+		die("could not create file %s.", path);
+
+	len = strlen(template);
+	write_or_die(fd, template, len);
+	close(fd);
+
+	editor = getenv("VISUAL");
+	if (!editor)
+		editor = getenv("EDITOR");
+	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, 0644);
+	if (fd == -1)
+		die("could not read %s.", path);
+	len = read_in_full(fd, buffer, size);
+	if (len < 0)
+		die("failed to read '%s', %m", path);
+	close(fd);
+
+	blank_lines = 1;
+	for (i = 0, j = 0; i < len; i++) {
+		if (blank_lines > 0 && buffer[i] == '#') {
+			eol = strchr(buffer + i, '\n');
+			if (!eol)
+				break;
+
+			i = eol - buffer;
+			continue;
+		}
+
+		if (buffer[i] == '\n') {
+			blank_lines++;
+			if (blank_lines > 1)
+				continue;
+		} else {
+			if (blank_lines > 2)
+				buffer[j++] = '\n';
+			blank_lines = 0;
+		}
+
+		buffer[j++] = buffer[i];
+	}
+
+	if (buffer[j - 1] != '\n')
+		buffer[j++] = '\n';
+
+	unlink(path);
+
+	return j;
+}
+
+static int show_reference(const char *refname, const unsigned char *sha1,
+			  int flag, void *cb_data)
+{
+	const char *pattern = cb_data;
+
+	if (pattern == NULL || !fnmatch(pattern, refname, 0))
+		printf("%s\n", refname);
+
+	return 0;
+}
+
+static int list_tags(const char *pattern)
+{
+	for_each_tag_ref(show_reference, (void *) pattern);
+
+	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))
+			printf("Deleted tag '%s'\n", *p);
+	}
+			
+	return had_error;
+}
+
+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;
+		}
+
+		printf("FIXME: verify tag '%s'\n", *p);
+	}
+
+	return had_error;
+}
+
+static int do_sign(char *buffer, size_t size, size_t max)
+{
+	struct child_process gpg;
+	const char *args[5];
+	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] = "-bsa";
+	args[2] = "-u";
+	args[3] = signingkey;
+	args[4] = 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";
+
+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);
+}
+
+int cmd_tag(int argc, const char **argv, const char *prefix)
+{
+	char buffer[4096];
+	unsigned char object[20], prev[20], result[20];
+	int annotate = 0, is_signed = 0, force = 0, lines = 0;
+	const char *message = NULL;
+	char ref[PATH_MAX];
+	const char *object_ref, *tag;
+	int i, body, header, total, fd;
+	enum object_type type;
+	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;
+			is_signed = 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
+				/* FIXME, fallback to 1 on invalid integer */
+				lines = atoi(argv[i + 1]);
+			continue;
+		}
+		if (!strcmp(arg, "-m")) {
+			annotate = 1;
+			i++;
+			if  (i == argc)
+				die("option -m needs an argument.");
+			message = argv[i];
+			continue;
+		}
+		if (!strcmp(arg, "-F")) {
+			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]);
+
+			message = xmalloc(4096);
+			if (read_in_full(fd, (char *) message, 4096) < 0)
+				die("cannot read %s", argv[1]);
+			continue;
+		}
+		if (!strcmp(arg, "-u")) {
+			annotate = 1;
+			is_signed = 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++];
+
+	if (i < argc)
+		object_ref = argv[i];
+	else
+		object_ref = "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)) {
+		if (!force)
+			die("tag '%s' already exists", tag);
+	} else {
+		hashclr(prev);
+	}
+
+	type = sha1_object_info(object, NULL);
+	if (type <= 0)
+	    die("bad object type.");
+
+	header = snprintf(buffer, sizeof buffer,
+			  "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 (annotate && message == NULL)
+		body = launch_editor(git_path("TAGMSG"), tag_template,
+				     buffer + header, sizeof buffer - header);
+	else if (annotate)
+		body = snprintf(buffer + header, sizeof buffer - header,
+				"%s\n", message);
+	else
+		body = 0;
+
+	if (annotate && body == 0)
+		die("no tag message?");
+
+	if (header + body > sizeof buffer)
+		die("tag message too big.");
+
+	if (is_signed)
+		total = do_sign(buffer, header + body, sizeof buffer);
+	else
+		total = header + body;
+
+	if (write_sha1_file(buffer, total, tag_type, result) < 0)
+		die("unable to write tag file");
+
+	lock = lock_any_ref_for_update(ref, prev, 0);
+	if (!lock)
+		die("%s: cannot lock the ref", ref);
+	if (write_ref_sha1(lock, result, NULL) < 0)
+		die("%s: cannot update the ref", ref);
+
+	return 0;
+}
diff --git a/builtin.h b/builtin.h
index 39290d1..91166e1 100644
--- a/builtin.h
+++ b/builtin.h
@@ -72,6 +72,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 37cee97..0000000
--- a/git-tag.sh
+++ /dev/null
@@ -1,183 +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
-	;;
-    -s)
-	annotate=1
-	signed=1
-	;;
-    -f)
-	force=1
-	;;
-    -n)
-        case $2 in
-	-*)	LINES=1 	# no argument
-		;;
-	*)	shift
-		LINES=$(expr "$1" : '\([0-9]*\)')
-		[ -z "$LINES" ] && LINES=1 # 1 line is default when -n is used
-		;;
-	esac
-	;;
-    -l)
-	list=1
-	shift
-	PATTERN="$1"	# select tags by shell pattern, not re
-	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' \
-					   -e '/^-----BEGIN PGP SIGNATURE-----$/Q' )
-			printf "%-15s %s\n" "$TAG" "$ANNOTATION" |
-			  sed -e '2,$s/^/    /' \
-			      -e "${LINES}q"
-			;;
-		*)      echo "$TAG"
-			;;
-		esac
-	    done
-	;;
-    -m)
-    	annotate=1
-	shift
-	message="$1"
-	if test "$#" = "0"; then
-	    die "error: option -m needs an argument"
-	else
-	    message_given=1
-	fi
-	;;
-    -F)
-	annotate=1
-	shift
-	if test "$#" = "0"; then
-	    die "error: option -F needs an argument"
-	else
-	    message="$(cat "$1")"
-	    message_given=1
-	fi
-	;;
-    -u)
-	annotate=1
-	signed=1
-	shift
-	username="$1"
-	;;
-    -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
-    shift
-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 29b55a1..c9c20fb 100644
--- a/git.c
+++ b/git.c
@@ -285,6 +285,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
 		{ "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.2.1.134.g447c2

Re: [PATCH] Port git-tag.sh to C.

From: Johannes Schindelin <hidden>
Date: 2016-06-15 22:43:14

Hi,

On Fri, 8 Jun 2007, Kristian Høgsberg wrote:
A more or less straight-forward port of git-tag.sh to C.
It is somewhat unfortunate that you did not say that you were working on 
this stuff; we have a Google Summer of Code project going on, which tries 
to port many scripts to builtins.

As it happens, I am working with jasam on exactly the same script.

Pity,
Dscho

Re: [PATCH] Port git-tag.sh to C.

From: Kristian Høgsberg <hidden>
Date: 2016-06-15 22:43:14

On Fri, 2007-06-08 at 22:51 +0100, Johannes Schindelin wrote:
Hi,

On Fri, 8 Jun 2007, Kristian Høgsberg wrote:
quoted
A more or less straight-forward port of git-tag.sh to C.
It is somewhat unfortunate that you did not say that you were working on 
this stuff; we have a Google Summer of Code project going on, which tries 
to port many scripts to builtins.
Ugh, yeah... wasted effort...  I didn't see anything about the SoC
project on the list and I didn't pre-announce my work because I only
spent little more than half a day...
As it happens, I am working with jasam on exactly the same script.
Hm... not sure how to resolve this.  FWIW, I'm going to send an updated
version of the patch that should also pass the test suite.  The previous
version always only creates annotated tags.

Kristian

Re: [PATCH] Port git-tag.sh to C.

From: Johannes Schindelin <hidden>
Date: 2016-06-15 22:43:14

Hi,

On Fri, 8 Jun 2007, Kristian H�gsberg wrote:
FWIW, I'm going to send an updated version of the patch that should also 
pass the test suite.  The previous version always only creates annotated 
tags.
As it happens, jasam also worked on a relatively extensive test script.

You might want to test it:

	http://repo.or.cz/w/git/builtin-gsoc.git

Branch is "builtin-tag", the test script is t/t7400-tag.sh.

Ciao,
Dscho

Re: [PATCH] Port git-tag.sh to C.

From: Carlos Rica <hidden>
Date: 2016-06-15 22:43:14

2007/6/9, Kristian Høgsberg [off-list ref]:
Ugh, yeah... wasted effort...  I didn't see anything about the SoC
project on the list and I didn't pre-announce my work because I only
spent little more than half a day...
Sorry, it should have been announced before this could happen.
quoted
As it happens, I am working with jasam on exactly the same script.
We were doing a conservative work to get exactly the same options
and behaviour that git-tag.sh does. The work is now almost finished,
so you could help us comparing the two versions to see how could
end with a better git-tag. It would be fantastic if we could point what
parts of my code can be enhanced with those of yours.

My test suite can also be run against the git-tag.sh code now, you
can check check your code with it. Feel free to say everything you want
about it!

Carlos

Re: [PATCH] Port git-tag.sh to C.

From: Matthijs Melchior <hidden>
Date: 2016-06-15 22:43:14

Hi,

Johannes Schindelin wrote:
Hi,

On Fri, 8 Jun 2007, Kristian Høgsberg wrote:
quoted
A more or less straight-forward port of git-tag.sh to C.
It is somewhat unfortunate that you did not say that you were working on 
this stuff; we have a Google Summer of Code project going on, which tries 
to port many scripts to builtins.

As it happens, I am working with jasam on exactly the same script.

Pity,
Dscho
Please consider the idea of showing (part of) the tag annotation with
git-tag as well.

See message archived at:
   http://permalink.gmane.org/gmane.comp.version-control.git/48962

Thanks,
	Matthijs Melchior.
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help