Merge branch 'ap/dateformat'
authorJunio C Hamano <gitster@pobox.com>
Wed, 3 Oct 2007 10:03:22 +0000 (03:03 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 3 Oct 2007 10:03:22 +0000 (03:03 -0700)
* ap/dateformat:
Add a test script for for-each-ref, including test of date formatting
dateformat: parse %(xxdate) %(yydate:format) correctly
Make for-each-ref's grab_date() support per-atom formatting
Make for-each-ref allow atom names like "<name>:<something>"
parse_date_format(): convert a format name to an enum date_mode

Documentation/git-for-each-ref.txt
builtin-for-each-ref.c
cache.h
date.c
revision.c
t/t6300-for-each-ref.sh [new file with mode: 0644]
index 6df8e8500450ad65a2de86e3daa0ab2f7692a2d2..f1f90cca62f61327a13b5ab41862596ca24a9b8f 100644 (file)
@@ -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
 --------
index 29f70aabc3f1df027f59dfdbac82e025653a8d87..5dbf3e59f297b1849c1289b7cc7980bddf1852f7 100644 (file)
@@ -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 || ep < formatp)
+                       formatp = ep;
+               if (len == formatp - sp && !memcmp(valid_atom[i].name, sp, len))
                        break;
        }
 
@@ -349,12 +358,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<something>";
+        * it's not possible that <something> is not ":<format>" 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;
@@ -364,7 +387,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:
@@ -391,7 +414,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);
@@ -403,8 +426,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
@@ -424,8 +447,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);
        }
diff --git a/cache.h b/cache.h
index 824650016677353cfa8c8a140eb3d904f56d60ee..5587f7ebc74a597b54de6014896fcd5481fe4ec9 100644 (file)
--- 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 93bef6efbe38cb8983fdda14b75ce772f90e1b6a..8f7050027053a4e2390097e341327b117404c26a 100644 (file)
--- 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;
index 658471385cfaa2480ee78b19fe38fc51d9b51ab2..5d294be308eed42c244c4424618b43d11048d4e6 100644 (file)
@@ -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")) {
diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh
new file mode 100644 (file)
index 0000000..d0809eb
--- /dev/null
@@ -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