Merge branch 'wp/pretty-enhancement'
authorJunio C Hamano <gitster@pobox.com>
Sun, 13 Jun 2010 18:21:00 +0000 (11:21 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 13 Jun 2010 18:21:00 +0000 (11:21 -0700)
* wp/pretty-enhancement:
pretty: initialize new cmt_fmt_map to 0
pretty: add aliases for pretty formats
pretty: add infrastructure for commit format aliases
pretty: make it easier to add new formats

Documentation/config.txt
Documentation/pretty-formats.txt
pretty.c
t/t4205-log-pretty-formats.sh [new file with mode: 0755]
index 85f763c4d0dc6656a45fa876b453d2cb34a72cb3..7e2ae254b0b25b65d8589d7ca568b9902ca3baa7 100644 (file)
@@ -1481,6 +1481,16 @@ pager.<cmd>::
        it takes precedence over this option.  To disable pagination for
        all commands, set `core.pager` or `GIT_PAGER` to `cat`.
 
+pretty.<name>::
+       Alias for a --pretty= format string, as specified in
+       linkgit:git-log[1]. Any aliases defined here can be used just
+       as the built-in pretty formats could. For example,
+       running `git config pretty.changelog "format:{asterisk} %H %s"`
+       would cause the invocation `git log --pretty=changelog`
+       to be equivalent to running `git log "--pretty=format:{asterisk} %H %s"`.
+       Note that an alias with the same name as a built-in format
+       will be silently ignored.
+
 pull.octopus::
        The default merge strategy to use when pulling multiple branches
        at once.
index b055a673d83bcf4c106f3f703ca7f4db4ca1d60f..8c68ce94f990b0c7f3bc909cc3dc0c4e2a37cf3b 100644 (file)
@@ -11,7 +11,12 @@ have limited your view of history: for example, if you are
 only interested in changes related to a certain directory or
 file.
 
-Here are some additional details for each format:
+There are several built-in formats, and you can define
+additional formats by setting a pretty.<name>
+config option to either another format name, or a
+'format:' string, as described below (see
+linkgit:git-config[1]). Here are the details of the
+built-in formats:
 
 * 'oneline'
 
index 74cda1b44e37f414d9d61326bbc5c548d56ba780..8b18efda9cd6eb615900687c0205daa832794c92 100644 (file)
--- a/pretty.c
+++ b/pretty.c
 #include "reflog-walk.h"
 
 static char *user_format;
+static struct cmt_fmt_map {
+       const char *name;
+       enum cmit_fmt format;
+       int is_tformat;
+       int is_alias;
+       const char *user_format;
+} *commit_formats;
+static size_t builtin_formats_len;
+static size_t commit_formats_len;
+static size_t commit_formats_alloc;
+static struct cmt_fmt_map *find_commit_format(const char *sought);
 
 static void save_user_format(struct rev_info *rev, const char *cp, int is_tformat)
 {
@@ -21,22 +32,118 @@ static void save_user_format(struct rev_info *rev, const char *cp, int is_tforma
        rev->commit_format = CMIT_FMT_USERFORMAT;
 }
 
-void get_commit_format(const char *arg, struct rev_info *rev)
+static int git_pretty_formats_config(const char *var, const char *value, void *cb)
 {
+       struct cmt_fmt_map *commit_format = NULL;
+       const char *name;
+       const char *fmt;
        int i;
-       static struct cmt_fmt_map {
-               const char *n;
-               size_t cmp_len;
-               enum cmit_fmt v;
-       } cmt_fmts[] = {
-               { "raw",        1,      CMIT_FMT_RAW },
-               { "medium",     1,      CMIT_FMT_MEDIUM },
-               { "short",      1,      CMIT_FMT_SHORT },
-               { "email",      1,      CMIT_FMT_EMAIL },
-               { "full",       5,      CMIT_FMT_FULL },
-               { "fuller",     5,      CMIT_FMT_FULLER },
-               { "oneline",    1,      CMIT_FMT_ONELINE },
+
+       if (prefixcmp(var, "pretty."))
+               return 0;
+
+       name = var + strlen("pretty.");
+       for (i = 0; i < builtin_formats_len; i++) {
+               if (!strcmp(commit_formats[i].name, name))
+                       return 0;
+       }
+
+       for (i = builtin_formats_len; i < commit_formats_len; i++) {
+               if (!strcmp(commit_formats[i].name, name)) {
+                       commit_format = &commit_formats[i];
+                       break;
+               }
+       }
+
+       if (!commit_format) {
+               ALLOC_GROW(commit_formats, commit_formats_len+1,
+                          commit_formats_alloc);
+               commit_format = &commit_formats[commit_formats_len];
+               memset(commit_format, 0, sizeof(*commit_format));
+               commit_formats_len++;
+       }
+
+       commit_format->name = xstrdup(name);
+       commit_format->format = CMIT_FMT_USERFORMAT;
+       git_config_string(&fmt, var, value);
+       if (!prefixcmp(fmt, "format:") || !prefixcmp(fmt, "tformat:")) {
+               commit_format->is_tformat = fmt[0] == 't';
+               fmt = strchr(fmt, ':') + 1;
+       } else if (strchr(fmt, '%'))
+               commit_format->is_tformat = 1;
+       else
+               commit_format->is_alias = 1;
+       commit_format->user_format = fmt;
+
+       return 0;
+}
+
+static void setup_commit_formats(void)
+{
+       struct cmt_fmt_map builtin_formats[] = {
+               { "raw",        CMIT_FMT_RAW,           0 },
+               { "medium",     CMIT_FMT_MEDIUM,        0 },
+               { "short",      CMIT_FMT_SHORT,         0 },
+               { "email",      CMIT_FMT_EMAIL,         0 },
+               { "fuller",     CMIT_FMT_FULLER,        0 },
+               { "full",       CMIT_FMT_FULL,          0 },
+               { "oneline",    CMIT_FMT_ONELINE,       1 }
        };
+       commit_formats_len = ARRAY_SIZE(builtin_formats);
+       builtin_formats_len = commit_formats_len;
+       ALLOC_GROW(commit_formats, commit_formats_len, commit_formats_alloc);
+       memcpy(commit_formats, builtin_formats,
+              sizeof(*builtin_formats)*ARRAY_SIZE(builtin_formats));
+
+       git_config(git_pretty_formats_config, NULL);
+}
+
+static struct cmt_fmt_map *find_commit_format_recursive(const char *sought,
+                                                       const char *original,
+                                                       int num_redirections)
+{
+       struct cmt_fmt_map *found = NULL;
+       size_t found_match_len = 0;
+       int i;
+
+       if (num_redirections >= commit_formats_len)
+               die("invalid --pretty format: "
+                   "'%s' references an alias which points to itself",
+                   original);
+
+       for (i = 0; i < commit_formats_len; i++) {
+               size_t match_len;
+
+               if (prefixcmp(commit_formats[i].name, sought))
+                       continue;
+
+               match_len = strlen(commit_formats[i].name);
+               if (found == NULL || found_match_len > match_len) {
+                       found = &commit_formats[i];
+                       found_match_len = match_len;
+               }
+       }
+
+       if (found && found->is_alias) {
+               found = find_commit_format_recursive(found->user_format,
+                                                    original,
+                                                    num_redirections+1);
+       }
+
+       return found;
+}
+
+static struct cmt_fmt_map *find_commit_format(const char *sought)
+{
+       if (!commit_formats)
+               setup_commit_formats();
+
+       return find_commit_format_recursive(sought, sought, 0);
+}
+
+void get_commit_format(const char *arg, struct rev_info *rev)
+{
+       struct cmt_fmt_map *commit_format;
 
        rev->use_terminator = 0;
        if (!arg || !*arg) {
@@ -47,21 +154,22 @@ void get_commit_format(const char *arg, struct rev_info *rev)
                save_user_format(rev, strchr(arg, ':') + 1, arg[0] == 't');
                return;
        }
-       for (i = 0; i < ARRAY_SIZE(cmt_fmts); i++) {
-               if (!strncmp(arg, cmt_fmts[i].n, cmt_fmts[i].cmp_len) &&
-                   !strncmp(arg, cmt_fmts[i].n, strlen(arg))) {
-                       if (cmt_fmts[i].v == CMIT_FMT_ONELINE)
-                               rev->use_terminator = 1;
-                       rev->commit_format = cmt_fmts[i].v;
-                       return;
-               }
-       }
+
        if (strchr(arg, '%')) {
                save_user_format(rev, arg, 1);
                return;
        }
 
-       die("invalid --pretty format: %s", arg);
+       commit_format = find_commit_format(arg);
+       if (!commit_format)
+               die("invalid --pretty format: %s", arg);
+
+       rev->commit_format = commit_format->format;
+       rev->use_terminator = commit_format->is_tformat;
+       if (commit_format->format == CMIT_FMT_USERFORMAT) {
+               save_user_format(rev, commit_format->user_format,
+                                commit_format->is_tformat);
+       }
 }
 
 /*
diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh
new file mode 100755 (executable)
index 0000000..cb9f2bd
--- /dev/null
@@ -0,0 +1,74 @@
+#!/bin/sh
+#
+# Copyright (c) 2010, Will Palmer
+#
+
+test_description='Test pretty formats'
+. ./test-lib.sh
+
+test_expect_success 'set up basic repos' '
+       >foo &&
+       >bar &&
+       git add foo &&
+       test_tick &&
+       git commit -m initial &&
+       git add bar &&
+       test_tick &&
+       git commit -m "add bar"
+'
+
+test_expect_success 'alias builtin format' '
+       git log --pretty=oneline >expected &&
+       git config pretty.test-alias oneline &&
+       git log --pretty=test-alias >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'alias masking builtin format' '
+       git log --pretty=oneline >expected &&
+       git config pretty.oneline "%H" &&
+       git log --pretty=oneline >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'alias user-defined format' '
+       git log --pretty="format:%h" >expected &&
+       git config pretty.test-alias "format:%h" &&
+       git log --pretty=test-alias >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'alias user-defined tformat' '
+       git log --pretty="tformat:%h" >expected &&
+       git config pretty.test-alias "tformat:%h" &&
+       git log --pretty=test-alias >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'alias non-existant format' '
+       git config pretty.test-alias format-that-will-never-exist &&
+       test_must_fail git log --pretty=test-alias
+'
+
+test_expect_success 'alias of an alias' '
+       git log --pretty="tformat:%h" >expected &&
+       git config pretty.test-foo "tformat:%h" &&
+       git config pretty.test-bar test-foo &&
+       git log --pretty=test-bar >actual && test_cmp expected actual
+'
+
+test_expect_success 'alias masking an alias' '
+       git log --pretty=format:"Two %H" >expected &&
+       git config pretty.duplicate "format:One %H" &&
+       git config --add pretty.duplicate "format:Two %H" &&
+       git log --pretty=duplicate >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'alias loop' '
+       git config pretty.test-foo test-bar &&
+       git config pretty.test-bar test-foo &&
+       test_must_fail git log --pretty=test-foo
+'
+
+test_done