From 62e09ce998dd7f6b844deb650101c743a5c4ce50 Mon Sep 17 00:00:00 2001 From: Carlos Rica Date: Fri, 20 Jul 2007 01:42:28 +0200 Subject: Make git tag a builtin. This replaces the script "git-tag.sh" with "builtin-tag.c". The existing test suite for "git tag" guarantees the compatibility with the features provided by the script version. 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" does, "git tag" with no arguments prints all tags, more like "git branch" does, and "git tag -n" also prints all tags with annotations (without needing -l). Tests and documentation were also updated to reflect these changes. The program is currently calling the script "git verify-tag" for verify. This can be changed porting it to C and calling its functions directly from builtin-tag.c. Signed-off-by: Carlos Rica Signed-off-by: Junio C Hamano diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index aee2c1b..119117f 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -12,7 +12,7 @@ SYNOPSIS 'git-tag' [-a | -s | -u ] [-f] [-m | -F ] [] 'git-tag' -d ... 'git-tag' [-n []] -l [] -'git-tag' -v +'git-tag' -v ... DESCRIPTION ----------- @@ -23,7 +23,7 @@ Unless `-f` is given, the tag must not yet exist in If one of `-a`, `-s`, or `-u ` is passed, the command creates a 'tag' object, and requires the tag message. Unless -`-m ` is given, an editor is started for the user to type +`-m ` or `-F ` is given, an editor is started for the user to type in the tag message. Otherwise just the SHA1 object name of the commit object is @@ -59,15 +59,17 @@ OPTIONS Delete existing tags with the given names. -v:: - Verify the gpg signature of given the tag + Verify the gpg signature of the given tag names. -n :: specifies how many lines from the annotation, if any, are printed when using -l. The default is not to print any annotation lines. + If no number is given to `-n`, only the first line is printed. -l :: List tags with names that match the given pattern (or all if no pattern is given). + Typing "git tag" without arguments, also lists all tags. -m :: Use the given tag message (instead of prompting) diff --git a/Makefile b/Makefile index 73b487f..8db6646 100644 --- a/Makefile +++ b/Makefile @@ -206,7 +206,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 \ @@ -361,6 +361,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..507f510 --- /dev/null +++ b/builtin-tag.c @@ -0,0 +1,450 @@ +/* + * Builtin "git tag" + * + * Copyright (c) 2007 Kristian Høgsberg , + * Carlos Rica + * 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 []] -l [] | [-a | -s | -u ] [-f | -d | -v] [-m | -F ] []"; + +static char signingkey[1000]; + +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("There was a problem with the editor %s.", editor); + + fd = open(path, O_RDONLY); + if (fd < 0) + die("could not open '%s': %s", path, strerror(errno)); + if (read_fd(fd, buffer, len)) { + free(*buffer); + die("could not read message file '%s': %s", + path, strerror(errno)); + } + close(fd); +} + +struct tag_filter { + const char *pattern; + int lines; +}; + +#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----" + +static int show_reference(const char *refname, const unsigned char *sha1, + int flag, void *cb_data) +{ + struct tag_filter *filter = cb_data; + + if (!fnmatch(filter->pattern, refname, 0)) { + int i; + unsigned long size; + enum object_type type; + char *buf, *sp, *eol; + size_t len; + + if (!filter->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 < filter->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, int lines) +{ + struct tag_filter filter; + char *newpattern; + + if (pattern == NULL) + pattern = ""; + + /* prepend/append * to the shell pattern: */ + newpattern = xmalloc(strlen(pattern) + 3); + sprintf(newpattern, "*%s*", pattern); + + filter.pattern = newpattern; + filter.lines = lines; + + for_each_tag_ref(show_reference, (void *) &filter); + + free(newpattern); + + return 0; +} + +typedef int (*func_tag)(const char *name, const char *ref, + const unsigned char *sha1); + +static int do_tag_names(const char **argv, func_tag fn) +{ + 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)) { + error("tag name too long: %.*s...", 50, *p); + had_error = 1; + continue; + } + if (!resolve_ref(ref, sha1, 1, NULL)) { + error("tag '%s' not found.", *p); + had_error = 1; + continue; + } + if (fn(*p, ref, sha1)) + had_error = 1; + } + return had_error; +} + +static int delete_tag(const char *name, const char *ref, + const unsigned char *sha1) +{ + if (delete_ref(ref, sha1)) + return 1; + printf("Deleted tag '%s'\n", name); + return 0; +} + +static int verify_tag(const char *name, const char *ref, + const unsigned char *sha1) +{ + const char *argv_verify_tag[] = {"git-verify-tag", + "-v", "SHA1_HEX", NULL}; + argv_verify_tag[2] = sha1_to_hex(sha1); + + if (run_command_v_opt(argv_verify_tag, 0)) + return error("could not verify the tag '%s'", name); + return 0; +} + +static ssize_t do_sign(char *buffer, size_t size, size_t max) +{ + struct child_process gpg; + const char *args[4]; + char *bracket; + int len; + + if (!*signingkey) { + if (strlcpy(signingkey, git_committer_info(1), + sizeof(signingkey)) >= sizeof(signingkey)) + return error("committer info too long."); + 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)) + return error("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); + + if (len == max - size) + return error("could not read the entire signature from 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"); + if (strlcpy(signingkey, value, sizeof(signingkey)) + >= sizeof(signingkey)) + die("user.signingkey value too long"); + 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': %s", + path, strerror(errno)); + 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 (size < 0) + die("unable to sign the tag"); + } + + 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, lines = 0; + char *message = NULL; + char ref[PATH_MAX]; + const char *object_ref, *tag; + int i; + 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; + int fd; + + annotate = 1; + i++; + if (i == argc) + die("option -F needs an argument."); + + if (!strcmp(argv[i], "-")) + fd = 0; + else { + fd = open(argv[i], O_RDONLY); + if (fd < 0) + die("could not open '%s': %s", + argv[i], strerror(errno)); + } + len = 1024; + message = xmalloc(len); + if (read_fd(fd, &message, &len)) { + free(message); + die("cannot read %s", argv[i]); + } + continue; + } + if (!strcmp(arg, "-u")) { + annotate = 1; + sign = 1; + i++; + if (i == argc) + die("option -u needs an argument."); + if (strlcpy(signingkey, argv[i], sizeof(signingkey)) + >= sizeof(signingkey)) + die("argument to option -u too long"); + continue; + } + if (!strcmp(arg, "-l")) { + return list_tags(argv[i + 1], lines); + } + if (!strcmp(arg, "-d")) { + return do_tag_names(argv + i + 1, delete_tag); + } + if (!strcmp(arg, "-v")) { + return do_tag_names(argv + i + 1, verify_tag); + } + usage(builtin_tag_usage); + } + + if (i == argc) { + if (annotate) + usage(builtin_tag_usage); + return list_tags(NULL, lines); + } + tag = argv[i++]; + + object_ref = i < argc ? argv[i] : "HEAD"; + if (i + 1 < argc) + die("too many params"); + + 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 name too long: %.*s...", 50, 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/contrib/examples/git-tag.sh b/contrib/examples/git-tag.sh new file mode 100755 index 0000000..5ee3f50 --- /dev/null +++ b/contrib/examples/git-tag.sh @@ -0,0 +1,205 @@ +#!/bin/sh +# Copyright (c) 2005 Linus Torvalds + +USAGE='[-n []] -l [] | [-a | -s | -u ] [-f | -d | -v] [-m ] []' +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 + git_editor "$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-tag.sh b/git-tag.sh deleted file mode 100755 index 5ee3f50..0000000 --- a/git-tag.sh +++ /dev/null @@ -1,205 +0,0 @@ -#!/bin/sh -# Copyright (c) 2005 Linus Torvalds - -USAGE='[-n []] -l [] | [-a | -s | -u ] [-f | -d | -v] [-m ] []' -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 - git_editor "$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 }, diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 17de2a9..a0be164 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -5,7 +5,7 @@ test_description='git-tag -Basic tests for operations with tags.' +Tests for operations with tags.' . ./test-lib.sh @@ -16,11 +16,15 @@ tag_exists () { } # todo: git tag -l now returns always zero, when fixed, change this test -test_expect_success 'listing all tags in an empty tree should succeed' \ - 'git tag -l' +test_expect_success 'listing all tags in an empty tree should succeed' ' + git tag -l && + git tag +' -test_expect_success 'listing all tags in an empty tree should output nothing' \ - 'test `git-tag -l | wc -l` -eq 0' +test_expect_success 'listing all tags in an empty tree should output nothing' ' + test `git-tag -l | wc -l` -eq 0 && + test `git-tag | wc -l` -eq 0 +' test_expect_failure 'looking for a tag in an empty tree should fail' \ 'tag_exists mytag' @@ -49,11 +53,15 @@ test_expect_success 'creating a tag using default HEAD should succeed' ' git tag mytag ' -test_expect_success 'listing all tags if one exists should succeed' \ - 'git-tag -l' +test_expect_success 'listing all tags if one exists should succeed' ' + git-tag -l && + git-tag +' -test_expect_success 'listing all tags if one exists should output that tag' \ - 'test `git-tag -l` = mytag' +test_expect_success 'listing all tags if one exists should output that tag' ' + test `git-tag -l` = mytag && + test `git-tag` = mytag +' # pattern matching: @@ -165,6 +173,8 @@ test_expect_success 'listing all tags should print them ordered' ' git tag v1.0 && git tag t210 && git tag -l > actual && + git diff expect actual && + git tag > actual && git diff expect actual ' @@ -264,6 +274,10 @@ test_expect_failure \ 'trying to verify a non-annotated and non-signed tag should fail' \ 'git-tag -v non-annotated-tag' +test_expect_failure \ + 'trying to verify many non-annotated or unknown tags, should fail' \ + 'git-tag -v unknown-tag1 non-annotated-tag unknown-tag2' + # creating annotated tags: get_tag_msg () { @@ -306,6 +320,18 @@ test_expect_success \ git diff expect actual ' +cat >inputmsg <expect +cat inputmsg >>expect +test_expect_success 'creating an annotated tag with -F - should succeed' ' + git-tag -F - stdin-annotated-tag actual && + git diff expect actual +' + # blank and empty messages: get_tag_header empty-annotated-tag $commit commit $time >expect @@ -551,6 +577,12 @@ test_expect_success \ ! git-tag -v file-annotated-tag ' +test_expect_success \ + 'trying to verify two annotated non-signed tags should fail' ' + tag_exists annotated-tag file-annotated-tag && + ! git-tag -v annotated-tag file-annotated-tag +' + # creating and verifying signed tags: gpg --version >/dev/null @@ -589,9 +621,47 @@ test_expect_success 'creating a signed tag with -m message should succeed' ' git diff expect actual ' +cat >sigmsgfile <expect +cat sigmsgfile >>expect +echo '-----BEGIN PGP SIGNATURE-----' >>expect +test_expect_success \ + 'creating a signed tag with -F messagefile should succeed' ' + git-tag -s -F sigmsgfile file-signed-tag && + get_tag_msg file-signed-tag >actual && + git diff expect actual +' + +cat >siginputmsg <expect +cat siginputmsg >>expect +echo '-----BEGIN PGP SIGNATURE-----' >>expect +test_expect_success 'creating a signed tag with -F - should succeed' ' + git-tag -s -F - stdin-signed-tag actual && + git diff expect actual +' + test_expect_success 'verifying a signed tag should succeed' \ 'git-tag -v signed-tag' +test_expect_success 'verifying two signed tags in one command should succeed' \ + 'git-tag -v signed-tag file-signed-tag' + +test_expect_success \ + 'verifying many signed and non-signed tags should fail' ' + ! git-tag -v signed-tag annotated-tag && + ! git-tag -v file-annotated-tag file-signed-tag && + ! git-tag -v annotated-tag file-signed-tag file-annotated-tag && + ! git-tag -v signed-tag annotated-tag file-signed-tag +' + test_expect_success 'verifying a forged tag should fail' ' forged=$(git cat-file tag signed-tag | sed -e "s/signed-tag/forged-tag/" | -- cgit v0.10.2-6-g49f6 From 4d87b9c5db2590f7616fedfc0a538fc78f528e14 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 20 Jul 2007 13:06:09 +0100 Subject: launch_editor(): Heed GIT_EDITOR and core.editor settings In the commit 'Add GIT_EDITOR environment and core.editor configuration variables', this was done for the shell scripts. Port it over to builtin-tag's version of launch_editor(), which is just about to be refactored into editor.c. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/builtin-tag.c b/builtin-tag.c index 507f510..81d37ce 100644 --- a/builtin-tag.c +++ b/builtin-tag.c @@ -24,7 +24,11 @@ static void launch_editor(const char *path, char **buffer, unsigned long *len) const char *args[3]; int fd; - editor = getenv("VISUAL"); + editor = getenv("GIT_EDITOR"); + if (!editor && editor_program) + editor = editor_program; + if (!editor) + editor = getenv("VISUAL"); if (!editor) editor = getenv("EDITOR"); @@ -249,9 +253,9 @@ 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; + char header_buf[1024], *buffer = NULL; int header_len, max_size; - unsigned long size; + unsigned long size = 0; type = sha1_object_info(object, NULL); if (type <= 0) diff --git a/cache.h b/cache.h index 53801b8..67b1af1 100644 --- a/cache.h +++ b/cache.h @@ -560,6 +560,8 @@ extern char *pager_program; extern int pager_in_use; extern int pager_use_color; +extern char *editor_program; + /* base85 */ int decode_85(char *dst, const char *line, int linelen); void encode_85(char *buf, const unsigned char *data, int bytes); diff --git a/config.c b/config.c index f89a611..103b4fc 100644 --- a/config.c +++ b/config.c @@ -426,6 +426,11 @@ int git_default_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.editor")) { + editor_program = xstrdup(value); + return 0; + } + /* Add other config variables here and to Documentation/config.txt. */ return 0; } diff --git a/environment.c b/environment.c index f83fb9e..35d3f4b 100644 --- a/environment.c +++ b/environment.c @@ -33,6 +33,7 @@ size_t delta_base_cache_limit = 16 * 1024 * 1024; char *pager_program; int pager_in_use; int pager_use_color = 1; +char *editor_program; int auto_crlf = 0; /* 1: both ways, -1: only when adding git objects */ static const char *git_dir; -- cgit v0.10.2-6-g49f6 From e317cfafd247b279055e9ee64a6a982043bd06e7 Mon Sep 17 00:00:00 2001 From: Carlos Rica Date: Sat, 21 Jul 2007 14:13:12 +0200 Subject: builtin-tag.c: Fix two memory leaks and minor notation changes. A repeated call to read_sha1_file was not freing memory when the buffer was allocated but returned size was zero. Also, now the program does not allow many -F or -m options, which was a bug too because it was not freing the memory allocated for any previous -F or -m options. Tests are provided for ensuring that only one option -F or -m is given. Also, another test is shipped here, to check that "git tag" fails when a non-existing file is passed to the -F option, something that git-tag.sh allowed creating the tag with an empty message. Signed-off-by: Carlos Rica Acked-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/builtin-tag.c b/builtin-tag.c index 81d37ce..d6d38ad 100644 --- a/builtin-tag.c +++ b/builtin-tag.c @@ -89,14 +89,19 @@ static int show_reference(const char *refname, const unsigned char *sha1, printf("%-15s ", refname); sp = buf = read_sha1_file(sha1, &type, &size); - if (!buf || !size) + if (!buf) return 0; + if (!size) { + free(buf); + 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 < filter->lines && sp < buf + size && + for (i = 0, sp += 2; + i < filter->lines && sp < buf + size && prefixcmp(sp, PGP_SIGNATURE "\n"); i++) { if (i) @@ -137,10 +142,10 @@ static int list_tags(const char *pattern, int lines) return 0; } -typedef int (*func_tag)(const char *name, const char *ref, +typedef int (*each_tag_name_fn)(const char *name, const char *ref, const unsigned char *sha1); -static int do_tag_names(const char **argv, func_tag fn) +static int for_each_tag_name(const char **argv, each_tag_name_fn fn) { const char **p; char ref[PATH_MAX]; @@ -195,7 +200,7 @@ static ssize_t do_sign(char *buffer, size_t size, size_t max) if (!*signingkey) { if (strlcpy(signingkey, git_committer_info(1), - sizeof(signingkey)) >= sizeof(signingkey)) + sizeof(signingkey)) > sizeof(signingkey) - 1) return error("committer info too long."); bracket = strchr(signingkey, '>'); if (bracket) @@ -258,7 +263,7 @@ static void create_tag(const unsigned char *object, const char *tag, unsigned long size = 0; type = sha1_object_info(object, NULL); - if (type <= 0) + if (type <= OBJ_NONE) die("bad object type."); header_len = snprintf(header_buf, sizeof(header_buf), @@ -271,7 +276,7 @@ static void create_tag(const unsigned char *object, const char *tag, tag, git_committer_info(1)); - if (header_len >= sizeof(header_buf)) + if (header_len > sizeof(header_buf) - 1) die("tag header too big."); if (!message) { @@ -366,6 +371,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) i++; if (i == argc) die("option -m needs an argument."); + if (message) + die("only one -F or -m option is allowed."); message = xstrdup(argv[i]); continue; } @@ -377,6 +384,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) i++; if (i == argc) die("option -F needs an argument."); + if (message) + die("only one -F or -m option is allowed."); if (!strcmp(argv[i], "-")) fd = 0; @@ -405,15 +414,12 @@ int cmd_tag(int argc, const char **argv, const char *prefix) die("argument to option -u too long"); continue; } - if (!strcmp(arg, "-l")) { + if (!strcmp(arg, "-l")) return list_tags(argv[i + 1], lines); - } - if (!strcmp(arg, "-d")) { - return do_tag_names(argv + i + 1, delete_tag); - } - if (!strcmp(arg, "-v")) { - return do_tag_names(argv + i + 1, verify_tag); - } + if (!strcmp(arg, "-d")) + return for_each_tag_name(argv + i + 1, delete_tag); + if (!strcmp(arg, "-v")) + return for_each_tag_name(argv + i + 1, verify_tag); usage(builtin_tag_usage); } @@ -431,7 +437,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) 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)) + if (snprintf(ref, sizeof(ref), "refs/tags/%s", tag) > sizeof(ref) - 1) die("tag name too long: %.*s...", 50, tag); if (check_ref_format(ref)) die("'%s' is not a valid tag name.", tag); diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index a0be164..c4fa446 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -332,6 +332,33 @@ test_expect_success 'creating an annotated tag with -F - should succeed' ' git diff expect actual ' +test_expect_success \ + 'trying to create a tag with a non-existing -F file should fail' ' + ! test -f nonexistingfile && + ! tag_exists notag && + ! git-tag -F nonexistingfile notag && + ! tag_exists notag +' + +test_expect_success \ + 'trying to create tags giving many -m or -F options should fail' ' + echo "message file 1" >msgfile1 && + echo "message file 2" >msgfile2 && + ! tag_exists msgtag && + ! git-tag -m "message 1" -m "message 2" msgtag && + ! tag_exists msgtag && + ! git-tag -F msgfile1 -F msgfile2 msgtag && + ! tag_exists msgtag && + ! git-tag -m "message 1" -F msgfile1 msgtag && + ! tag_exists msgtag && + ! git-tag -F msgfile1 -m "message 1" msgtag && + ! tag_exists msgtag && + ! git-tag -F msgfile1 -m "message 1" -F msgfile2 msgtag && + ! tag_exists msgtag && + ! git-tag -m "message 1" -F msgfile1 -m "message 2" msgtag && + ! tag_exists msgtag +' + # blank and empty messages: get_tag_header empty-annotated-tag $commit commit $time >expect @@ -648,6 +675,14 @@ test_expect_success 'creating a signed tag with -F - should succeed' ' git diff expect actual ' +test_expect_success \ + 'trying to create a signed tag with non-existing -F file should fail' ' + ! test -f nonexistingfile && + ! tag_exists nosigtag && + ! git-tag -s -F nonexistingfile nosigtag && + ! tag_exists nosigtag +' + test_expect_success 'verifying a signed tag should succeed' \ 'git-tag -v signed-tag' -- cgit v0.10.2-6-g49f6 From 2ae68fcb785a617793813abcea19893e13e436b0 Mon Sep 17 00:00:00 2001 From: Carlos Rica Date: Fri, 27 Jul 2007 06:07:34 +0200 Subject: Make verify-tag a builtin. This replaces "git-verify-tag.sh" with "builtin-verify-tag.c". Testing relies on the "git tag -v" tests calling this command. A temporary file is needed when calling to gpg, because git is already creating detached signatures (gpg option -b) to sign tags (instead of leaving gpg to add the signature to the file by itself), and those signatures need to be supplied in a separate file to be verified by gpg. The program uses git_mkstemp to create that temporary file needed by gpg, instead of the previously used "$GIT_DIR/.tmp-vtag", in order to allow the command to be used in read-only repositories, and also prevent other instances of git to read or remove the same file. Signal SIGPIPE is ignored because the program sometimes was terminated because that signal when writing the input for gpg. The command now can receive many tag names to be verified. Documentation is also updated here to reflect this new behaviour. Signed-off-by: Carlos Rica Signed-off-by: Junio C Hamano diff --git a/Documentation/git-verify-tag.txt b/Documentation/git-verify-tag.txt index 48d17fd..ac7fb19 100644 --- a/Documentation/git-verify-tag.txt +++ b/Documentation/git-verify-tag.txt @@ -3,11 +3,11 @@ git-verify-tag(1) NAME ---- -git-verify-tag - Check the GPG signature of tag +git-verify-tag - Check the GPG signature of tags SYNOPSIS -------- -'git-verify-tag' +'git-verify-tag' ... DESCRIPTION ----------- diff --git a/Makefile b/Makefile index 8db6646..98670bb 100644 --- a/Makefile +++ b/Makefile @@ -206,7 +206,6 @@ 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-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 \ @@ -368,6 +367,7 @@ BUILTIN_OBJS = \ builtin-update-ref.o \ builtin-upload-archive.o \ builtin-verify-pack.o \ + builtin-verify-tag.o \ builtin-write-tree.o \ builtin-show-ref.o \ builtin-pack-refs.o diff --git a/builtin-verify-tag.c b/builtin-verify-tag.c new file mode 100644 index 0000000..dfcfcd0 --- /dev/null +++ b/builtin-verify-tag.c @@ -0,0 +1,110 @@ +/* + * Builtin "git verify-tag" + * + * Copyright (c) 2007 Carlos Rica + * + * Based on git-verify-tag.sh + */ +#include "cache.h" +#include "builtin.h" +#include "tag.h" +#include "run-command.h" +#include + +static const char builtin_verify_tag_usage[] = + "git-verify-tag [-v|--verbose] ..."; + +#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----" + +static int run_gpg_verify(const char *buf, unsigned long size, int verbose) +{ + struct child_process gpg; + const char *args_gpg[] = {"gpg", "--verify", "FILE", "-", NULL}; + char path[PATH_MAX], *eol; + size_t len; + int fd, ret; + + fd = git_mkstemp(path, PATH_MAX, ".git_vtag_tmpXXXXXX"); + if (fd < 0) + return error("could not create temporary file '%s': %s", + path, strerror(errno)); + if (write_in_full(fd, buf, size) < 0) + return error("failed writing temporary file '%s': %s", + path, strerror(errno)); + close(fd); + + /* find the length without signature */ + len = 0; + while (len < size && prefixcmp(buf + len, PGP_SIGNATURE "\n")) { + eol = memchr(buf + len, '\n', size - len); + len += eol ? eol - (buf + len) + 1 : size - len; + } + if (verbose) + write_in_full(1, buf, len); + + memset(&gpg, 0, sizeof(gpg)); + gpg.argv = args_gpg; + gpg.in = -1; + gpg.out = 1; + args_gpg[2] = path; + if (start_command(&gpg)) + return error("could not run gpg."); + + write_in_full(gpg.in, buf, len); + close(gpg.in); + gpg.close_in = 0; + ret = finish_command(&gpg); + + unlink(path); + + return ret; +} + +static int verify_tag(const char *name, int verbose) +{ + enum object_type type; + unsigned char sha1[20]; + char *buf; + unsigned long size; + int ret; + + if (get_sha1(name, sha1)) + return error("tag '%s' not found.", name); + + type = sha1_object_info(sha1, NULL); + if (type != OBJ_TAG) + return error("%s: cannot verify a non-tag object of type %s.", + name, typename(type)); + + buf = read_sha1_file(sha1, &type, &size); + if (!buf) + return error("%s: unable to read file.", name); + + ret = run_gpg_verify(buf, size, verbose); + + free(buf); + return ret; +} + +int cmd_verify_tag(int argc, const char **argv, const char *prefix) +{ + int i = 1, verbose = 0, had_error = 0; + + git_config(git_default_config); + + if (argc == 1) + usage(builtin_verify_tag_usage); + + if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose")) { + verbose = 1; + i++; + } + + /* sometimes the program was terminated because this signal + * was received in the process of writing the gpg input: */ + signal(SIGPIPE, SIG_IGN); + while (i < argc) + if (verify_tag(argv[i++], verbose)) + had_error = 1; + return had_error; +} diff --git a/builtin.h b/builtin.h index ac7417f..bb72000 100644 --- a/builtin.h +++ b/builtin.h @@ -77,6 +77,7 @@ extern int cmd_update_index(int argc, const char **argv, const char *prefix); extern int cmd_update_ref(int argc, const char **argv, const char *prefix); extern int cmd_upload_archive(int argc, const char **argv, const char *prefix); extern int cmd_upload_tar(int argc, const char **argv, const char *prefix); +extern int cmd_verify_tag(int argc, const char **argv, const char *prefix); extern int cmd_version(int argc, const char **argv, const char *prefix); extern int cmd_whatchanged(int argc, const char **argv, const char *prefix); extern int cmd_write_tree(int argc, const char **argv, const char *prefix); diff --git a/contrib/examples/git-verify-tag.sh b/contrib/examples/git-verify-tag.sh new file mode 100755 index 0000000..37b0023 --- /dev/null +++ b/contrib/examples/git-verify-tag.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +USAGE='' +SUBDIRECTORY_OK='Yes' +. git-sh-setup + +verbose= +while case $# in 0) break;; esac +do + case "$1" in + -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) + verbose=t ;; + *) + break ;; + esac + shift +done + +if [ "$#" != "1" ] +then + usage +fi + +type="$(git cat-file -t "$1" 2>/dev/null)" || + die "$1: no such object." + +test "$type" = tag || + die "$1: cannot verify a non-tag object of type $type." + +case "$verbose" in +t) + git cat-file -p "$1" | + sed -n -e '/^-----BEGIN PGP SIGNATURE-----/q' -e p + ;; +esac + +trap 'rm -f "$GIT_DIR/.tmp-vtag"' 0 + +git cat-file tag "$1" >"$GIT_DIR/.tmp-vtag" || exit 1 +sed -n -e ' + /^-----BEGIN PGP SIGNATURE-----$/q + p +' <"$GIT_DIR/.tmp-vtag" | +gpg --verify "$GIT_DIR/.tmp-vtag" - || exit 1 +rm -f "$GIT_DIR/.tmp-vtag" diff --git a/git-verify-tag.sh b/git-verify-tag.sh deleted file mode 100755 index 37b0023..0000000 --- a/git-verify-tag.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/sh - -USAGE='' -SUBDIRECTORY_OK='Yes' -. git-sh-setup - -verbose= -while case $# in 0) break;; esac -do - case "$1" in - -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) - verbose=t ;; - *) - break ;; - esac - shift -done - -if [ "$#" != "1" ] -then - usage -fi - -type="$(git cat-file -t "$1" 2>/dev/null)" || - die "$1: no such object." - -test "$type" = tag || - die "$1: cannot verify a non-tag object of type $type." - -case "$verbose" in -t) - git cat-file -p "$1" | - sed -n -e '/^-----BEGIN PGP SIGNATURE-----/q' -e p - ;; -esac - -trap 'rm -f "$GIT_DIR/.tmp-vtag"' 0 - -git cat-file tag "$1" >"$GIT_DIR/.tmp-vtag" || exit 1 -sed -n -e ' - /^-----BEGIN PGP SIGNATURE-----$/q - p -' <"$GIT_DIR/.tmp-vtag" | -gpg --verify "$GIT_DIR/.tmp-vtag" - || exit 1 -rm -f "$GIT_DIR/.tmp-vtag" diff --git a/git.c b/git.c index eb9e5ca..230e506 100644 --- a/git.c +++ b/git.c @@ -369,6 +369,7 @@ static void handle_internal_command(int argc, const char **argv) { "update-index", cmd_update_index, RUN_SETUP }, { "update-ref", cmd_update_ref, RUN_SETUP }, { "upload-archive", cmd_upload_archive }, + { "verify-tag", cmd_verify_tag, RUN_SETUP }, { "version", cmd_version }, { "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER }, { "write-tree", cmd_write_tree, RUN_SETUP }, -- cgit v0.10.2-6-g49f6 From f653aee5a37b909e772d612eb7e226f09fd2f3d3 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 23 Jul 2007 12:58:27 +0100 Subject: Teach "git stripspace" the --strip-comments option With --strip-comments (or short -s), git stripspace now removes lines beginning with a '#', too. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/Documentation/git-stripspace.txt b/Documentation/git-stripspace.txt index 1306d7b..5212358 100644 --- a/Documentation/git-stripspace.txt +++ b/Documentation/git-stripspace.txt @@ -8,7 +8,7 @@ git-stripspace - Filter out empty lines SYNOPSIS -------- -'git-stripspace' < +'git-stripspace' [-s | --strip-comments] < DESCRIPTION ----------- @@ -16,6 +16,9 @@ Remove multiple empty lines, and empty lines at beginning and end. OPTIONS ------- +-s\|--strip-comments:: + In addition to empty lines, also strip lines starting with '#'. + :: Byte stream to act on. diff --git a/builtin-stripspace.c b/builtin-stripspace.c index 5571687..916355c 100644 --- a/builtin-stripspace.c +++ b/builtin-stripspace.c @@ -76,6 +76,11 @@ int cmd_stripspace(int argc, const char **argv, const char *prefix) { char *buffer; unsigned long size; + int strip_comments = 0; + + if (argc > 1 && (!strcmp(argv[1], "-s") || + !strcmp(argv[1], "--strip-comments"))) + strip_comments = 1; size = 1024; buffer = xmalloc(size); @@ -84,7 +89,7 @@ int cmd_stripspace(int argc, const char **argv, const char *prefix) die("could not read the input"); } - size = stripspace(buffer, size, 0); + size = stripspace(buffer, size, strip_comments); write_or_die(1, buffer, size); if (size) putc('\n', stdout); diff --git a/t/t0030-stripspace.sh b/t/t0030-stripspace.sh index b1c9003..cad95f3 100755 --- a/t/t0030-stripspace.sh +++ b/t/t0030-stripspace.sh @@ -392,4 +392,9 @@ test_expect_success \ git diff expect actual ' +test_expect_success 'strip comments, too' ' + test ! -z "$(echo "# comment" | git stripspace)" && + test -z "$(echo "# comment" | git stripspace -s)" +' + test_done -- cgit v0.10.2-6-g49f6