previously the only ways to alias a --pretty format within git were either to set the format as your default format (via the format.pretty configuration variable), or by using a regular git alias. This left the definition of more complicated formats to the realm of "builtin or nothing", with user-defined formats usually being reserved for quick one-offs. Here we allow user-defined formats to enjoy more or less the same benefits of builtins. By defining format.pretty.myalias, "myalias" can be used in place of whatever would normally come after --pretty=. This can be a format:, tformat:, raw (ie, defaulting to tformat), or the name of another builtin or user-defined pretty format. Signed-off-by: Will Palmer <wmpalmer@xxxxxxxxx> --- Documentation/config.txt | 8 ++ pretty.c | 169 +++++++++++++++++++++++++++++++++++------ t/t4205-log-pretty-formats.sh | 53 ++++++++++++- 3 files changed, 202 insertions(+), 28 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 92f851e..6573d18 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -894,6 +894,14 @@ format.pretty:: See linkgit:git-log[1], linkgit:git-show[1], linkgit:git-whatchanged[1]. +format.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 builtin pretty formats could. For example, defining + "format.pretty.hash = format:%H" would cause the invocation + "git log --pretty=hash" to be equivalent to running + "git log --pretty=format:%H". + format.thread:: The default threading style for 'git format-patch'. Can be a boolean value, or `shallow` or `deep`. `shallow` threading diff --git a/pretty.c b/pretty.c index f884f48..d49fec7 100644 --- a/pretty.c +++ b/pretty.c @@ -11,6 +11,16 @@ #include "reflog-walk.h" static char *user_format; +static struct cmt_fmt_map { + const char *name; + enum cmit_fmt format; + const char *user_format; + int is_tformat; + int is_alias; +} *commit_formats = NULL; +static size_t commit_formats_len = 0; +static size_t commit_formats_alloc = 0; +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 +31,134 @@ 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) +{ + if (!prefixcmp(var, "format.pretty.")) { + struct cmt_fmt_map user_format = {0}; + const char *fmt; + + user_format.name = xstrdup(&var[14]); + user_format.format = CMIT_FMT_USERFORMAT; + git_config_string(&fmt, var, value); + if (!prefixcmp(fmt, "format:") || !prefixcmp(fmt, "tformat:")) { + user_format.is_tformat = fmt[0] == 't'; + fmt = strchr(fmt, ':') + 1; + } else if (strchr(fmt, '%')) + user_format.is_tformat = 1; + else + user_format.is_alias = 1; + user_format.user_format = fmt; + + ALLOC_GROW(commit_formats, commit_formats_len+1, + commit_formats_alloc); + memcpy(&commit_formats[ commit_formats_len ], &user_format, + sizeof(user_format)); + commit_formats_len++; + } + return 0; +} + +static void setup_commit_formats(void) { 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 }, + const char **attempted_aliases = NULL; + size_t attempted_aliases_alloc = 0; + size_t attempted_aliases_len; + struct cmt_fmt_map builtin_formats[] = { + { "raw", CMIT_FMT_RAW, NULL, 0 }, + { "medium", CMIT_FMT_MEDIUM, NULL, 0 }, + { "short", CMIT_FMT_SHORT, NULL, 0 }, + { "email", CMIT_FMT_EMAIL, NULL, 0 }, + { "full", CMIT_FMT_FULL, NULL, 0 }, + { "fuller", CMIT_FMT_FULLER, NULL, 0 }, + { "oneline", CMIT_FMT_ONELINE, NULL, 1 } }; + commit_formats_len = ARRAY_SIZE(builtin_formats); + 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); + + for (i = ARRAY_SIZE(builtin_formats); i < commit_formats_len; i++) { + attempted_aliases_len = 0; + struct cmt_fmt_map *aliased_format = &commit_formats[i]; + const char *fmt = commit_formats[i].user_format; + int j; + + if (!commit_formats[i].is_alias) + continue; + + while ((aliased_format = find_commit_format(fmt))) { + if (!aliased_format->is_alias) + break; + + fmt = aliased_format->user_format; + for (j=0; j<attempted_aliases_len; j++) { + if (!strcmp(fmt, attempted_aliases[j])) { + aliased_format = NULL; + break; + } + } + if (!aliased_format) + break; + + ALLOC_GROW(attempted_aliases, attempted_aliases_len+1, + attempted_aliases_alloc); + attempted_aliases[attempted_aliases_len] = fmt; + attempted_aliases_len++; + } + if (aliased_format) { + commit_formats[i].format = aliased_format->format; + commit_formats[i].user_format = aliased_format->user_format; + commit_formats[i].is_tformat = aliased_format->is_tformat; + commit_formats[i].is_alias = 0; + } else + commit_formats[i].format = CMIT_FMT_UNSPECIFIED; + } +} + +static struct cmt_fmt_map *find_commit_format(const char *sought) +{ + struct cmt_fmt_map *found = NULL; + size_t found_match_len = 0; + + if (!commit_formats) + setup_commit_formats(); + + int i; + for (i = 0; i < commit_formats_len; i++) { + const char *candidate = commit_formats[i].name; + const char *s = sought; + size_t match_len = 0; + + if (commit_formats[i].format == CMIT_FMT_UNSPECIFIED) + continue; + + for (; s++, candidate++;) { + if (!*candidate && *s) { + match_len = 0; + break; + } + if (!*candidate || !*s) { + match_len = s - sought; + break; + } + if (*s != *candidate) { + match_len = 0; + break; + } + } + if (match_len > found_match_len) { + found = &commit_formats[i]; + } + } + return found; +} + +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 +169,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 index 28d2948..ee3e934 100755 --- a/t/t4205-log-pretty-formats.sh +++ b/t/t4205-log-pretty-formats.sh @@ -6,7 +6,15 @@ test_description='Test pretty formats' . ./test-lib.sh -test_expect_success "set up basic repos" ">foo && git add foo && git commit -m initial" +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'" for flag in false true always; do for color in red green blue reset; do @@ -16,29 +24,64 @@ for color in red green blue reset; do "$make_expected && git config color.ui $flag && git log -1 --pretty=format:'%C$color' > actual && - cmp expected actual" + test_cmp expected actual" test_expect_success "%C($color) with color.ui $flag" \ "$make_expected && git config color.ui $flag && git log -1 --pretty=format:'%C($color)' > actual && - cmp expected actual" + test_cmp expected actual" [ ! "$flag" = "always" ] && make_expected=">expected" test_expect_success "%C?$color with color.ui $flag" \ "$make_expected && git config color.ui $flag && git log -1 --pretty=format:'%C?$color' > actual && - cmp expected actual" + test_cmp expected actual" test_expect_success "%C?($color) with color.ui $flag" \ "$make_expected && git config color.ui $flag && git log -1 --pretty=format:'%C?($color)' > actual && - cmp expected actual" + test_cmp expected actual" done done +test_expect_success "reset color flags" "git config --unset color.ui" + +test_expect_success "alias builtin format" \ + "git log --pretty=oneline >expected && + git config format.pretty.test-alias oneline && + git log --pretty=test-alias >actual && + test_cmp expected actual" + +test_expect_success "alias user-defined format" \ + "git log --pretty='format:%h' >expected && + git config format.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 format.pretty.test-alias 'tformat:%h' && + git log --pretty=test-alias >actual && + test_cmp expected actual" + +test_expect_code 128 "alias non-existant format" \ + "git config format.pretty.test-alias format-that-will-never-exist && + git log --pretty=test-alias" + +test_expect_success "alias of an alias" \ + "git log --pretty='tformat:%h' >expected && + git config format.pretty.test-foo 'tformat:%h' && + git config format.pretty.test-bar test-foo && + git log --pretty=test-bar >actual && + test_cmp expected actual" + +test_expect_code 128 "alias loop" \ + "git config format.pretty.test-foo test-bar && + git config format.pretty.test-bar test-foo && + git log --pretty=test-foo" test_done -- 1.7.1.rc1.13.gbb0a0a.dirty -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html