From: Kristian Høgsberg <krh@xxxxxxxxxx> A more or less straight-forward port of git-tag.sh to C. Signed-off-by: Kristian Høgsberg <krh@xxxxxxxxxx> Cc: Johannes Schindelin <Johannes.Schindelin@xxxxxx> --- Ok, here's an updated version that passes the test suite. Johannes, I leave it to you and jasam to merge the bits you find useful, but as far as I see it, this conversion is complete, and there's enough other shell scripts to port. My port doesn't pass jasam's test suite, it looks like he is expecting the -l glob to be a regexp, but the git-tag.sh I started from used shell globs. Anyways, it'd be nice if you or jasam could keep the list a little more in the loop with the SoC changes, it is where most of the development happens, after all. What's next on your list? cheers, Kristian Makefile | 3 +- builtin-tag.c | 371 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ builtin.h | 1 + git-tag.sh | 183 ---------------------------- git.c | 1 + 5 files changed, 375 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..26035f5 --- /dev/null +++ b/builtin-tag.c @@ -0,0 +1,371 @@ +/* + * Builtin "git tag" + * + * Copyright (c) 2007 Kristian Høgsberg <krh@xxxxxxxxxx> + * 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"; + +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); +} + +static void create_tag(const unsigned char *object, const char *tag, + const char *message, int sign, unsigned char *result) +{ + enum object_type type; + char buffer[4096]; + int header, body, total; + + 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 (message == NULL) + body = launch_editor(git_path("TAGMSG"), tag_template, + buffer + header, sizeof buffer - header); + else + body = snprintf(buffer + header, sizeof buffer - header, + "%s\n", message); + + if (body == 0) + die("no tag message?"); + + if (header + body > sizeof buffer) + die("tag message too big."); + + if (sign) + 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"); +} + +int cmd_tag(int argc, const char **argv, const char *prefix) +{ + unsigned char object[20], prev[20], result[20]; + int annotate = 0, sign = 0, force = 0, lines = 0; + const 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 + /* 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; + 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++]; + + 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); + } + + 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 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.0.6 - 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