From ca51699961664890fdaabd276af539e6b3514053 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 6 Feb 2012 03:13:12 -0500 Subject: tag: fix output of "tag -n" when errors occur When "git tag" is instructed to print lines from annotated tags via "-n", it first prints the tag name, then attempts to parse and print the lines of the tag object, and then finally adds a trailing newline. If an error occurs, we return early from the function and never print the newline, screwing up the output for the next tag. Let's factor the line-printing into its own function so we can manage the early returns better, and make sure that we always terminate the line. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/tag.c | 66 ++++++++++++++++++++++++++++++----------------------------- 1 file changed, 34 insertions(+), 32 deletions(-) (limited to 'builtin/tag.c') diff --git a/builtin/tag.c b/builtin/tag.c index 667515e527..391160cf83 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -84,18 +84,45 @@ static int contains(struct commit *candidate, const struct commit_list *want) return contains_recurse(candidate, want); } +static void show_tag_lines(const unsigned char *sha1, int lines) +{ + int i; + unsigned long size; + enum object_type type; + char *buf, *sp, *eol; + size_t len; + + buf = read_sha1_file(sha1, &type, &size); + if (!buf || !size) + return; + + /* skip header */ + sp = strstr(buf, "\n\n"); + if (!sp) { + free(buf); + return; + } + /* only take up to "lines" lines, and strip the signature */ + size = parse_signature(buf, size); + for (i = 0, sp += 2; i < lines && sp < buf + size; 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; + } + free(buf); +} + static int show_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { struct tag_filter *filter = cb_data; if (match_pattern(filter->patterns, refname)) { - int i; - unsigned long size; - enum object_type type; - char *buf, *sp, *eol; - size_t len; - if (filter->with_commit) { struct commit *commit; @@ -111,33 +138,8 @@ static int show_reference(const char *refname, const unsigned char *sha1, return 0; } printf("%-15s ", refname); - - buf = read_sha1_file(sha1, &type, &size); - if (!buf || !size) - return 0; - - /* skip header */ - sp = strstr(buf, "\n\n"); - if (!sp) { - free(buf); - return 0; - } - /* only take up to "lines" lines, and strip the signature */ - size = parse_signature(buf, size); - for (i = 0, sp += 2; - i < filter->lines && sp < buf + size; - 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; - } + show_tag_lines(sha1, filter->lines); putchar('\n'); - free(buf); } return 0; -- cgit v1.2.3 From fb630e048c6efabe4d46b1b125fe7348062d38e6 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 6 Feb 2012 03:13:42 -0500 Subject: tag: die when listing missing or corrupt objects We don't usually bother looking at tagged objects at all when listing. However, if "-n" is specified, we open the objects to read the annotations of the tags. If we fail to read an object, or if the object has zero length, we simply silently return. The first case is an indication of a broken or corrupt repo, and we should notify the user of the error. The second case is OK to silently ignore; however, the existing code leaked the buffer returned by read_sha1_file. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/tag.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'builtin/tag.c') diff --git a/builtin/tag.c b/builtin/tag.c index 391160cf83..1e27f5c3d6 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -93,8 +93,12 @@ static void show_tag_lines(const unsigned char *sha1, int lines) size_t len; buf = read_sha1_file(sha1, &type, &size); - if (!buf || !size) + if (!buf) + die_errno("unable to read object %s", sha1_to_hex(sha1)); + if (!size) { + free(buf); return; + } /* skip header */ sp = strstr(buf, "\n\n"); -- cgit v1.2.3 From ae7706b9ac98e3c412d799c910de604347ffce5d Mon Sep 17 00:00:00 2001 From: Tom Grennan Date: Wed, 8 Feb 2012 15:03:43 -0800 Subject: tag: add --points-at list option This filters the list for tags of the given object. Example, john$ git tag v1.0-john v1.0 john$ git tag -l --points-at v1.0 v1.0-john v1.0 Signed-off-by: Tom Grennan Reviewed-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/git-tag.txt | 6 +++++- builtin/tag.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++- t/t7004-tag.sh | 39 ++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 2 deletions(-) (limited to 'builtin/tag.c') diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 53ff5f6cf7..8d32b9a814 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -12,7 +12,8 @@ SYNOPSIS 'git tag' [-a | -s | -u ] [-f] [-m | -F ] [ | ] 'git tag' -d ... -'git tag' [-n[]] -l [--contains ] [...] +'git tag' [-n[]] -l [--contains ] [--points-at ] + [...] 'git tag' -v ... DESCRIPTION @@ -86,6 +87,9 @@ OPTIONS --contains :: Only list tags which contain the specified commit. +--points-at :: + Only list tags of the given object. + -m :: --message=:: Use the given tag message (instead of prompting). diff --git a/builtin/tag.c b/builtin/tag.c index 31f02e80f6..27c35571ab 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -15,11 +15,13 @@ #include "diff.h" #include "revision.h" #include "gpg-interface.h" +#include "sha1-array.h" static const char * const git_tag_usage[] = { "git tag [-a|-s|-u ] [-f] [-m |-F ] []", "git tag -d ...", - "git tag -l [-n[]] [...]", + "git tag -l [-n[]] [--contains ] [--points-at ] " + "\n\t\t[...]", "git tag -v ...", NULL }; @@ -30,6 +32,8 @@ struct tag_filter { struct commit_list *with_commit; }; +static struct sha1_array points_at; + static int match_pattern(const char **patterns, const char *ref) { /* no pattern means match everything */ @@ -41,6 +45,24 @@ static int match_pattern(const char **patterns, const char *ref) return 0; } +static const unsigned char *match_points_at(const char *refname, + const unsigned char *sha1) +{ + const unsigned char *tagged_sha1 = NULL; + struct object *obj; + + if (sha1_array_lookup(&points_at, sha1) >= 0) + return sha1; + obj = parse_object(sha1); + if (!obj) + die(_("malformed object at '%s'"), refname); + if (obj->type == OBJ_TAG) + tagged_sha1 = ((struct tag *)obj)->tagged->sha1; + if (tagged_sha1 && sha1_array_lookup(&points_at, tagged_sha1) >= 0) + return tagged_sha1; + return NULL; +} + static int in_commit_list(const struct commit_list *want, struct commit *c) { for (; want; want = want->next) @@ -105,6 +127,9 @@ static int show_reference(const char *refname, const unsigned char *sha1, return 0; } + if (points_at.nr && !match_points_at(refname, sha1)) + return 0; + if (!filter->lines) { printf("%s\n", refname); return 0; @@ -375,6 +400,23 @@ static int strbuf_check_tag_ref(struct strbuf *sb, const char *name) return check_refname_format(sb->buf, 0); } +int parse_opt_points_at(const struct option *opt __attribute__ ((unused)), + const char *arg, int unset) +{ + unsigned char sha1[20]; + + if (unset) { + sha1_array_clear(&points_at); + return 0; + } + if (!arg) + return error(_("switch 'points-at' requires an object")); + if (get_sha1(arg, sha1)) + return error(_("malformed object name '%s'"), arg); + sha1_array_append(&points_at, sha1); + return 0; +} + int cmd_tag(int argc, const char **argv, const char *prefix) { struct strbuf buf = STRBUF_INIT; @@ -417,6 +459,10 @@ int cmd_tag(int argc, const char **argv, const char *prefix) PARSE_OPT_LASTARG_DEFAULT, parse_opt_with_commit, (intptr_t)"HEAD", }, + { + OPTION_CALLBACK, 0, "points-at", NULL, "object", + "print only tags of the object", 0, parse_opt_points_at + }, OPT_END() }; @@ -448,6 +494,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) die(_("-n option is only allowed with -l.")); if (with_commit) die(_("--contains option is only allowed with -l.")); + if (points_at.nr) + die(_("--points-at option is only allowed with -l.")); if (delete) return for_each_tag_name(argv, delete_tag); if (verify) diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index e93ac73829..f61e3987ee 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -1269,4 +1269,43 @@ test_expect_success 'mixing incompatibles modes and options is forbidden' ' test_must_fail git tag -v -s ' +# check points-at + +test_expect_success '--points-at cannot be used in non-list mode' ' + test_must_fail git tag --points-at=v4.0 foo +' + +test_expect_success '--points-at finds lightweight tags' ' + echo v4.0 >expect && + git tag --points-at v4.0 >actual && + test_cmp expect actual +' + +test_expect_success '--points-at finds annotated tags of commits' ' + git tag -m "v4.0, annotated" annotated-v4.0 v4.0 && + echo annotated-v4.0 >expect && + git tag -l --points-at v4.0 "annotated*" >actual && + test_cmp expect actual +' + +test_expect_success '--points-at finds annotated tags of tags' ' + git tag -m "describing the v4.0 tag object" \ + annotated-again-v4.0 annotated-v4.0 && + cat >expect <<-\EOF && + annotated-again-v4.0 + annotated-v4.0 + EOF + git tag --points-at=annotated-v4.0 >actual && + test_cmp expect actual +' + +test_expect_success 'multiple --points-at are OR-ed together' ' + cat >expect <<-\EOF && + v2.0 + v3.0 + EOF + git tag --points-at=v2.0 --points-at=v3.0 >actual && + test_cmp expect actual +' + test_done -- cgit v1.2.3 From 31fd8d72f2eb82c2b6d8d450ee4e13a3925291c3 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 6 Feb 2012 10:13:27 -0800 Subject: tag: do not show non-tag contents with "-n" "git tag -n" did not check the type of the object it is reading the top n lines from. At least, avoid showing the beginning of trees and blobs when dealing with lightweight tags that point at them. As the payload of a tag and a commit look similar in that they both start with a header block, which is skipped for the purpose of "-n" output, followed by human readable text, allow the message of commit objects to be shown just like the contents of tag objects. This avoids regression for people who have been using "tag -n" to show the log messages of commits that are pointed at by lightweight tags. Test script is from Jeff King. Signed-off-by: Junio C Hamano --- builtin/tag.c | 22 ++++++++++++---------- t/t7004-tag.sh | 13 +++++++++++++ 2 files changed, 25 insertions(+), 10 deletions(-) (limited to 'builtin/tag.c') diff --git a/builtin/tag.c b/builtin/tag.c index 1e27f5c3d6..6ca53e3310 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -95,19 +95,20 @@ static void show_tag_lines(const unsigned char *sha1, int lines) buf = read_sha1_file(sha1, &type, &size); if (!buf) die_errno("unable to read object %s", sha1_to_hex(sha1)); - if (!size) { - free(buf); - return; - } + if (type != OBJ_COMMIT && type != OBJ_TAG) + goto free_return; + if (!size) + die("an empty %s object %s?", + typename(type), sha1_to_hex(sha1)); /* skip header */ sp = strstr(buf, "\n\n"); - if (!sp) { - free(buf); - return; - } - /* only take up to "lines" lines, and strip the signature */ - size = parse_signature(buf, size); + if (!sp) + goto free_return; + + /* only take up to "lines" lines, and strip the signature from a tag */ + if (type == OBJ_TAG) + size = parse_signature(buf, size); for (i = 0, sp += 2; i < lines && sp < buf + size; i++) { if (i) printf("\n "); @@ -118,6 +119,7 @@ static void show_tag_lines(const unsigned char *sha1, int lines) break; sp = eol + 1; } +free_return: free(buf); } diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 097ce2bc83..7687e62cc5 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -585,6 +585,19 @@ test_expect_success \ test_cmp expect actual ' +test_expect_success 'annotations for blobs are empty' ' + blob=$(git hash-object -w --stdin <<-\EOF + Blob paragraph 1. + + Blob paragraph 2. + EOF + ) && + git tag tag-blob $blob && + echo "tag-blob " >expect && + git tag -n1 -l tag-blob >actual && + test_cmp expect actual +' + # subsequent tests require gpg; check if it is available gpg --version >/dev/null 2>/dev/null if [ $? -eq 127 ]; then -- cgit v1.2.3 From 0975a5020ef050a9fd5b518cbd601e5ee43ddbcf Mon Sep 17 00:00:00 2001 From: Ramsay Jones Date: Mon, 13 Feb 2012 18:24:41 +0000 Subject: builtin/tag.c: Fix a sparse warning In particular, sparse complains as follows: SP builtin/tag.c builtin/tag.c:411:5: warning: symbol 'parse_opt_points_at' was \ not declared. Should it be static? In order to suppress the warning, since the parse_opt_points_at() function does not need to be an external symbol, we simply add the static modifier to the function definition. Signed-off-by: Ramsay Jones Signed-off-by: Junio C Hamano --- builtin/tag.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'builtin/tag.c') diff --git a/builtin/tag.c b/builtin/tag.c index 27c35571ab..e377706e6d 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -400,7 +400,7 @@ static int strbuf_check_tag_ref(struct strbuf *sb, const char *name) return check_refname_format(sb->buf, 0); } -int parse_opt_points_at(const struct option *opt __attribute__ ((unused)), +static int parse_opt_points_at(const struct option *opt __attribute__((unused)), const char *arg, int unset) { unsigned char sha1[20]; -- cgit v1.2.3