From 856665f827c31ace3f19e672f8911f7f15f2a0e2 Mon Sep 17 00:00:00 2001 From: Andy Parkins Date: Fri, 28 Sep 2007 15:17:31 +0100 Subject: [PATCH 1/5] parse_date_format(): convert a format name to an enum date_mode Factor out the code to parse --date= parameter to revision walkers into a separate function, parse_date_format(). This function is passed a string and converts it to an enum date_format: - "relative" => DATE_RELATIVE - "iso8601" or "iso" => DATE_ISO8601 - "rfc2822" => DATE_RFC2822 - "short" => DATE_SHORT - "local" => DATE_LOCAL - "default" => DATE_NORMAL In the event that none of these strings is found, the function die()s. Signed-off-by: Andy Parkins Signed-off-by: Junio C Hamano --- cache.h | 1 + date.c | 20 ++++++++++++++++++++ revision.c | 17 +---------------- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/cache.h b/cache.h index 8246500166..5587f7ebc7 100644 --- a/cache.h +++ b/cache.h @@ -432,6 +432,7 @@ const char *show_date(unsigned long time, int timezone, enum date_mode mode); int parse_date(const char *date, char *buf, int bufsize); void datestamp(char *buf, int bufsize); unsigned long approxidate(const char *); +enum date_mode parse_date_format(const char *format); extern const char *git_author_info(int); extern const char *git_committer_info(int); diff --git a/date.c b/date.c index 93bef6efbe..8f70500270 100644 --- a/date.c +++ b/date.c @@ -584,6 +584,26 @@ int parse_date(const char *date, char *result, int maxlen) return date_string(then, offset, result, maxlen); } +enum date_mode parse_date_format(const char *format) +{ + if (!strcmp(format, "relative")) + return DATE_RELATIVE; + else if (!strcmp(format, "iso8601") || + !strcmp(format, "iso")) + return DATE_ISO8601; + else if (!strcmp(format, "rfc2822") || + !strcmp(format, "rfc")) + return DATE_RFC2822; + else if (!strcmp(format, "short")) + return DATE_SHORT; + else if (!strcmp(format, "local")) + return DATE_LOCAL; + else if (!strcmp(format, "default")) + return DATE_NORMAL; + else + die("unknown date format %s", format); +} + void datestamp(char *buf, int bufsize) { time_t now; diff --git a/revision.c b/revision.c index 658471385c..5d294be308 100644 --- a/revision.c +++ b/revision.c @@ -1134,22 +1134,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch continue; } if (!strncmp(arg, "--date=", 7)) { - if (!strcmp(arg + 7, "relative")) - revs->date_mode = DATE_RELATIVE; - else if (!strcmp(arg + 7, "iso8601") || - !strcmp(arg + 7, "iso")) - revs->date_mode = DATE_ISO8601; - else if (!strcmp(arg + 7, "rfc2822") || - !strcmp(arg + 7, "rfc")) - revs->date_mode = DATE_RFC2822; - else if (!strcmp(arg + 7, "short")) - revs->date_mode = DATE_SHORT; - else if (!strcmp(arg + 7, "local")) - revs->date_mode = DATE_LOCAL; - else if (!strcmp(arg + 7, "default")) - revs->date_mode = DATE_NORMAL; - else - die("unknown date format %s", arg); + revs->date_mode = parse_date_format(arg + 7); continue; } if (!strcmp(arg, "--log-size")) { From b64265ca8da17d30561febf62a13990a2dc96d2f Mon Sep 17 00:00:00 2001 From: Andy Parkins Date: Fri, 28 Sep 2007 15:17:39 +0100 Subject: [PATCH 2/5] Make for-each-ref allow atom names like ":" In anticipation of supplying a per-field date format specifier, this patch makes parse_atom() in builtin-for-each-ref.c allow atoms that have a valid atom name (as determined by the valid_atom[] table) followed by a colon, followed by an arbitrary string. The arbitrary string is where the format for the atom will be specified. Note, if different formats are specified for the same atom, multiple entries will be made in the used_atoms table to allow them to be distinguished by the grab_XXXX() functions. Signed-off-by: Andy Parkins Signed-off-by: Junio C Hamano --- builtin-for-each-ref.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c index 0afa1c5c41..74a7337da5 100644 --- a/builtin-for-each-ref.c +++ b/builtin-for-each-ref.c @@ -106,7 +106,16 @@ static int parse_atom(const char *atom, const char *ep) /* Is the atom a valid one? */ for (i = 0; i < ARRAY_SIZE(valid_atom); i++) { int len = strlen(valid_atom[i].name); - if (len == ep - sp && !memcmp(valid_atom[i].name, sp, len)) + /* + * If the atom name has a colon, strip it and everything after + * it off - it specifies the format for this entry, and + * shouldn't be used for checking against the valid_atom + * table. + */ + const char *formatp = strchr(sp, ':'); + if (!formatp) + formatp = ep; + if (len == formatp - sp && !memcmp(valid_atom[i].name, sp, len)) break; } From d392e712a844e6ce029aa901902e08a3da82f89d Mon Sep 17 00:00:00 2001 From: Andy Parkins Date: Fri, 28 Sep 2007 15:17:45 +0100 Subject: [PATCH 3/5] Make for-each-ref's grab_date() support per-atom formatting grab_date() gets an extra parameter - atomname; this extra parameter is checked to see if it has a ":" extra component in it, and if so that "" string is passed to parse_date_format() to produce an enum date_mode value which is then further passed to show_date(). In short it allows the user of git-for-each-ref to do things like this: $ git-for-each-ref --format='%(taggerdate:default)' refs/tags/v1.5.2 Sun May 20 00:30:42 2007 -0700 $ git-for-each-ref --format='%(taggerdate:relative)' refs/tags/v1.5.2 4 months ago $ git-for-each-ref --format='%(taggerdate:short)' refs/tags/v1.5.2 2007-05-20 $ git-for-each-ref --format='%(taggerdate:local)' refs/tags/v1.5.2 Sun May 20 08:30:42 2007 $ git-for-each-ref --format='%(taggerdate:iso8601)' refs/tags/v1.5.2 2007-05-20 00:30:42 -0700 $ git-for-each-ref --format='%(taggerdate:rfc2822)' refs/tags/v1.5.2 Sun, 20 May 2007 00:30:42 -0700 The default, when no ":" is specified is ":default", leaving the existing behaviour unchanged. Signed-off-by: Andy Parkins Signed-off-by: Junio C Hamano --- Documentation/git-for-each-ref.txt | 5 +++++ builtin-for-each-ref.c | 28 +++++++++++++++++++++------- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 6df8e85004..f1f90cca62 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -100,6 +100,11 @@ In any case, a field name that refers to a field inapplicable to the object referred by the ref does not cause an error. It returns an empty string instead. +As a special case for the date-type fields, you may specify a format for +the date by adding one of `:default`, `:relative`, `:short`, `:local`, +`:iso8601` or `:rfc2822` to the end of the fieldname; e.g. +`%(taggerdate:relative)`. + EXAMPLES -------- diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c index 74a7337da5..c904ac3101 100644 --- a/builtin-for-each-ref.c +++ b/builtin-for-each-ref.c @@ -356,12 +356,26 @@ static const char *copy_email(const char *buf) return line; } -static void grab_date(const char *buf, struct atom_value *v) +static void grab_date(const char *buf, struct atom_value *v, const char *atomname) { const char *eoemail = strstr(buf, "> "); char *zone; unsigned long timestamp; long tz; + enum date_mode date_mode = DATE_NORMAL; + const char *formatp; + + /* + * We got here because atomname ends in "date" or "date"; + * it's not possible that is not ":" because + * parse_atom() wouldn't have allowed it, so we can assume that no + * ":" means no format is specified, and use the default. + */ + formatp = strchr(atomname, ':'); + if (formatp != NULL) { + formatp++; + date_mode = parse_date_format(formatp); + } if (!eoemail) goto bad; @@ -371,7 +385,7 @@ static void grab_date(const char *buf, struct atom_value *v) tz = strtol(zone, NULL, 10); if ((tz == LONG_MIN || tz == LONG_MAX) && errno == ERANGE) goto bad; - v->s = xstrdup(show_date(timestamp, tz, 0)); + v->s = xstrdup(show_date(timestamp, tz, date_mode)); v->ul = timestamp; return; bad: @@ -398,7 +412,7 @@ static void grab_person(const char *who, struct atom_value *val, int deref, stru if (name[wholen] != 0 && strcmp(name + wholen, "name") && strcmp(name + wholen, "email") && - strcmp(name + wholen, "date")) + prefixcmp(name + wholen, "date")) continue; if (!wholine) wholine = find_wholine(who, wholen, buf, sz); @@ -410,8 +424,8 @@ static void grab_person(const char *who, struct atom_value *val, int deref, stru v->s = copy_name(wholine); else if (!strcmp(name + wholen, "email")) v->s = copy_email(wholine); - else if (!strcmp(name + wholen, "date")) - grab_date(wholine, v); + else if (!prefixcmp(name + wholen, "date")) + grab_date(wholine, v, name); } /* For a tag or a commit object, if "creator" or "creatordate" is @@ -431,8 +445,8 @@ static void grab_person(const char *who, struct atom_value *val, int deref, stru if (deref) name++; - if (!strcmp(name, "creatordate")) - grab_date(wholine, v); + if (!prefixcmp(name, "creatordate")) + grab_date(wholine, v, name); else if (!strcmp(name, "creator")) v->s = copy_line(wholine); } From 070f6918d148f6bac8f5cc829c53fd28be018697 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 2 Oct 2007 14:31:37 -0700 Subject: [PATCH 4/5] dateformat: parse %(xxdate) %(yydate:format) correctly Andy Parkins noticed that parsing of the above would not correctly notice that xxdate does not have any format specifier. Signed-off-by: Junio C Hamano --- builtin-for-each-ref.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c index c904ac3101..00afe89317 100644 --- a/builtin-for-each-ref.c +++ b/builtin-for-each-ref.c @@ -113,7 +113,7 @@ static int parse_atom(const char *atom, const char *ep) * table. */ const char *formatp = strchr(sp, ':'); - if (!formatp) + if (!formatp || ep < formatp) formatp = ep; if (len == formatp - sp && !memcmp(valid_atom[i].name, sp, len)) break; From 96b2d4fa927c5055adc5b1d08f10a5d7352e2989 Mon Sep 17 00:00:00 2001 From: Andy Parkins Date: Tue, 2 Oct 2007 12:02:57 +0100 Subject: [PATCH 5/5] Add a test script for for-each-ref, including test of date formatting Signed-off-by: Andy Parkins Signed-off-by: Junio C Hamano --- t/t6300-for-each-ref.sh | 151 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 t/t6300-for-each-ref.sh diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh new file mode 100644 index 0000000000..d0809eb651 --- /dev/null +++ b/t/t6300-for-each-ref.sh @@ -0,0 +1,151 @@ +#!/bin/sh +# +# Copyright (c) 2007 Andy Parkins +# + +test_description='for-each-ref test' + +. ./test-lib.sh + +# Mon Jul 3 15:18:43 2006 +0000 +datestamp=1151939923 +setdate_and_increment () { + GIT_COMMITTER_DATE="$datestamp +0200" + datestamp=$(expr "$datestamp" + 1) + GIT_AUTHOR_DATE="$datestamp +0200" + datestamp=$(expr "$datestamp" + 1) + export GIT_COMMITTER_DATE GIT_AUTHOR_DATE +} + +test_expect_success 'Create sample commit with known timestamp' ' + setdate_and_increment && + echo "Using $datestamp" > one && + git add one && + git commit -m "Initial" && + setdate_and_increment && + git tag -a -m "Tagging at $datestamp" testtag +' + +test_expect_success 'Check atom names are valid' ' + bad= + for token in \ + refname objecttype objectsize objectname tree parent \ + numparent object type author authorname authoremail \ + authordate committer committername committeremail \ + committerdate tag tagger taggername taggeremail \ + taggerdate creator creatordate subject body contents + do + git for-each-ref --format="$token=%($token)" refs/heads || { + bad=$token + break + } + done + test -z "$bad" +' + +test_expect_failure 'Check invalid atoms names are errors' ' + git-for-each-ref --format="%(INVALID)" refs/heads +' + +test_expect_success 'Check format specifiers are ignored in naming date atoms' ' + git-for-each-ref --format="%(authordate)" refs/heads && + git-for-each-ref --format="%(authordate:default) %(authordate)" refs/heads && + git-for-each-ref --format="%(authordate) %(authordate:default)" refs/heads && + git-for-each-ref --format="%(authordate:default) %(authordate:default)" refs/heads +' + +test_expect_success 'Check valid format specifiers for date fields' ' + git-for-each-ref --format="%(authordate:default)" refs/heads && + git-for-each-ref --format="%(authordate:relative)" refs/heads && + git-for-each-ref --format="%(authordate:short)" refs/heads && + git-for-each-ref --format="%(authordate:local)" refs/heads && + git-for-each-ref --format="%(authordate:iso8601)" refs/heads && + git-for-each-ref --format="%(authordate:rfc2822)" refs/heads +' + +test_expect_failure 'Check invalid format specifiers are errors' ' + git-for-each-ref --format="%(authordate:INVALID)" refs/heads +' + +cat >expected <<\EOF +'refs/heads/master' 'Mon Jul 3 17:18:43 2006 +0200' 'Mon Jul 3 17:18:44 2006 +0200' +'refs/tags/testtag' 'Mon Jul 3 17:18:45 2006 +0200' +EOF + +test_expect_success 'Check unformatted date fields output' ' + (git for-each-ref --shell --format="%(refname) %(committerdate) %(authordate)" refs/heads && + git for-each-ref --shell --format="%(refname) %(taggerdate)" refs/tags) >actual && + git diff expected actual +' + +test_expect_success 'Check format "default" formatted date fields output' ' + f=default && + (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads && + git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual && + git diff expected actual +' + +# Don't know how to do relative check because I can't know when this script +# is going to be run and can't fake the current time to git, and hence can't +# provide expected output. Instead, I'll just make sure that "relative" +# doesn't exit in error +# +#cat >expected <<\EOF +# +#EOF +# +test_expect_success 'Check format "relative" date fields output' ' + f=relative && + (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads && + git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual +' + +cat >expected <<\EOF +'refs/heads/master' '2006-07-03' '2006-07-03' +'refs/tags/testtag' '2006-07-03' +EOF + +test_expect_success 'Check format "short" date fields output' ' + f=short && + (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads && + git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual && + git diff expected actual +' + +cat >expected <<\EOF +'refs/heads/master' 'Mon Jul 3 15:18:43 2006' 'Mon Jul 3 15:18:44 2006' +'refs/tags/testtag' 'Mon Jul 3 15:18:45 2006' +EOF + +test_expect_success 'Check format "local" date fields output' ' + f=local && + (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads && + git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual && + git diff expected actual +' + +cat >expected <<\EOF +'refs/heads/master' '2006-07-03 17:18:43 +0200' '2006-07-03 17:18:44 +0200' +'refs/tags/testtag' '2006-07-03 17:18:45 +0200' +EOF + +test_expect_success 'Check format "iso8601" date fields output' ' + f=iso8601 && + (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads && + git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual && + git diff expected actual +' + +cat >expected <<\EOF +'refs/heads/master' 'Mon, 3 Jul 2006 17:18:43 +0200' 'Mon, 3 Jul 2006 17:18:44 +0200' +'refs/tags/testtag' 'Mon, 3 Jul 2006 17:18:45 +0200' +EOF + +test_expect_success 'Check format "rfc2822" date fields output' ' + f=rfc2822 && + (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads && + git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual && + git diff expected actual +' + +test_done