Merge branch 'tb/shortlog-group'
"git shortlog" learned to group by the "format" string. * tb/shortlog-group: shortlog: implement `--group=committer` in terms of `--group=<format>` shortlog: implement `--group=author` in terms of `--group=<format>` shortlog: extract `shortlog_finish_setup()` shortlog: support arbitrary commit format `--group`s shortlog: extract `--group` fragment for translation shortlog: make trailer insertion a noop when appropriate shortlog: accept `--date`-related options
This commit is contained in:
@ -47,6 +47,11 @@ OPTIONS
|
|||||||
|
|
||||||
Each pretty-printed commit will be rewrapped before it is shown.
|
Each pretty-printed commit will be rewrapped before it is shown.
|
||||||
|
|
||||||
|
--date=<format>::
|
||||||
|
Show dates formatted according to the given date string. (See
|
||||||
|
the `--date` option in the "Commit Formatting" section of
|
||||||
|
linkgit:git-log[1]). Useful with `--group=format:<format>`.
|
||||||
|
|
||||||
--group=<type>::
|
--group=<type>::
|
||||||
Group commits based on `<type>`. If no `--group` option is
|
Group commits based on `<type>`. If no `--group` option is
|
||||||
specified, the default is `author`. `<type>` is one of:
|
specified, the default is `author`. `<type>` is one of:
|
||||||
@ -59,6 +64,9 @@ OPTIONS
|
|||||||
example, if your project uses `Reviewed-by` trailers, you might want
|
example, if your project uses `Reviewed-by` trailers, you might want
|
||||||
to see who has been reviewing with
|
to see who has been reviewing with
|
||||||
`git shortlog -ns --group=trailer:reviewed-by`.
|
`git shortlog -ns --group=trailer:reviewed-by`.
|
||||||
|
- `format:<format>`, any string accepted by the `--format` option of
|
||||||
|
'git log'. (See the "PRETTY FORMATS" section of
|
||||||
|
linkgit:git-log[1].)
|
||||||
+
|
+
|
||||||
Note that commits that do not include the trailer will not be counted.
|
Note that commits that do not include the trailer will not be counted.
|
||||||
Likewise, commits with multiple trailers (e.g., multiple signoffs) may
|
Likewise, commits with multiple trailers (e.g., multiple signoffs) may
|
||||||
|
@ -1334,6 +1334,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
|
|||||||
log.in2 = 4;
|
log.in2 = 4;
|
||||||
log.file = rev->diffopt.file;
|
log.file = rev->diffopt.file;
|
||||||
log.groups = SHORTLOG_GROUP_AUTHOR;
|
log.groups = SHORTLOG_GROUP_AUTHOR;
|
||||||
|
shortlog_finish_setup(&log);
|
||||||
for (i = 0; i < nr; i++)
|
for (i = 0; i < nr; i++)
|
||||||
shortlog_add_commit(&log, list[i]);
|
shortlog_add_commit(&log, list[i]);
|
||||||
|
|
||||||
|
@ -132,7 +132,9 @@ static void read_from_stdin(struct shortlog *log)
|
|||||||
match = committer_match;
|
match = committer_match;
|
||||||
break;
|
break;
|
||||||
case SHORTLOG_GROUP_TRAILER:
|
case SHORTLOG_GROUP_TRAILER:
|
||||||
die(_("using --group=trailer with stdin is not supported"));
|
die(_("using %s with stdin is not supported"), "--group=trailer");
|
||||||
|
case SHORTLOG_GROUP_FORMAT:
|
||||||
|
die(_("using %s with stdin is not supported"), "--group=format");
|
||||||
default:
|
default:
|
||||||
BUG("unhandled shortlog group");
|
BUG("unhandled shortlog group");
|
||||||
}
|
}
|
||||||
@ -170,6 +172,9 @@ static void insert_records_from_trailers(struct shortlog *log,
|
|||||||
const char *commit_buffer, *body;
|
const char *commit_buffer, *body;
|
||||||
struct strbuf ident = STRBUF_INIT;
|
struct strbuf ident = STRBUF_INIT;
|
||||||
|
|
||||||
|
if (!log->trailers.nr)
|
||||||
|
return;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Using format_commit_message("%B") would be simpler here, but
|
* Using format_commit_message("%B") would be simpler here, but
|
||||||
* this saves us copying the message.
|
* this saves us copying the message.
|
||||||
@ -200,9 +205,34 @@ static void insert_records_from_trailers(struct shortlog *log,
|
|||||||
unuse_commit_buffer(commit, commit_buffer);
|
unuse_commit_buffer(commit, commit_buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int shortlog_needs_dedup(const struct shortlog *log)
|
||||||
|
{
|
||||||
|
return HAS_MULTI_BITS(log->groups) || log->format.nr > 1 || log->trailers.nr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void insert_records_from_format(struct shortlog *log,
|
||||||
|
struct strset *dups,
|
||||||
|
struct commit *commit,
|
||||||
|
struct pretty_print_context *ctx,
|
||||||
|
const char *oneline)
|
||||||
|
{
|
||||||
|
struct strbuf buf = STRBUF_INIT;
|
||||||
|
struct string_list_item *item;
|
||||||
|
|
||||||
|
for_each_string_list_item(item, &log->format) {
|
||||||
|
strbuf_reset(&buf);
|
||||||
|
|
||||||
|
format_commit_message(commit, item->string, &buf, ctx);
|
||||||
|
|
||||||
|
if (!shortlog_needs_dedup(log) || strset_add(dups, buf.buf))
|
||||||
|
insert_one_record(log, buf.buf, oneline);
|
||||||
|
}
|
||||||
|
|
||||||
|
strbuf_release(&buf);
|
||||||
|
}
|
||||||
|
|
||||||
void shortlog_add_commit(struct shortlog *log, struct commit *commit)
|
void shortlog_add_commit(struct shortlog *log, struct commit *commit)
|
||||||
{
|
{
|
||||||
struct strbuf ident = STRBUF_INIT;
|
|
||||||
struct strbuf oneline = STRBUF_INIT;
|
struct strbuf oneline = STRBUF_INIT;
|
||||||
struct strset dups = STRSET_INIT;
|
struct strset dups = STRSET_INIT;
|
||||||
struct pretty_print_context ctx = {0};
|
struct pretty_print_context ctx = {0};
|
||||||
@ -211,7 +241,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)
|
|||||||
ctx.fmt = CMIT_FMT_USERFORMAT;
|
ctx.fmt = CMIT_FMT_USERFORMAT;
|
||||||
ctx.abbrev = log->abbrev;
|
ctx.abbrev = log->abbrev;
|
||||||
ctx.print_email_subject = 1;
|
ctx.print_email_subject = 1;
|
||||||
ctx.date_mode.type = DATE_NORMAL;
|
ctx.date_mode = log->date_mode;
|
||||||
ctx.output_encoding = get_log_output_encoding();
|
ctx.output_encoding = get_log_output_encoding();
|
||||||
|
|
||||||
if (!log->summary) {
|
if (!log->summary) {
|
||||||
@ -222,30 +252,10 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)
|
|||||||
}
|
}
|
||||||
oneline_str = oneline.len ? oneline.buf : "<none>";
|
oneline_str = oneline.len ? oneline.buf : "<none>";
|
||||||
|
|
||||||
if (log->groups & SHORTLOG_GROUP_AUTHOR) {
|
insert_records_from_trailers(log, &dups, commit, &ctx, oneline_str);
|
||||||
strbuf_reset(&ident);
|
insert_records_from_format(log, &dups, commit, &ctx, oneline_str);
|
||||||
format_commit_message(commit,
|
|
||||||
log->email ? "%aN <%aE>" : "%aN",
|
|
||||||
&ident, &ctx);
|
|
||||||
if (!HAS_MULTI_BITS(log->groups) ||
|
|
||||||
strset_add(&dups, ident.buf))
|
|
||||||
insert_one_record(log, ident.buf, oneline_str);
|
|
||||||
}
|
|
||||||
if (log->groups & SHORTLOG_GROUP_COMMITTER) {
|
|
||||||
strbuf_reset(&ident);
|
|
||||||
format_commit_message(commit,
|
|
||||||
log->email ? "%cN <%cE>" : "%cN",
|
|
||||||
&ident, &ctx);
|
|
||||||
if (!HAS_MULTI_BITS(log->groups) ||
|
|
||||||
strset_add(&dups, ident.buf))
|
|
||||||
insert_one_record(log, ident.buf, oneline_str);
|
|
||||||
}
|
|
||||||
if (log->groups & SHORTLOG_GROUP_TRAILER) {
|
|
||||||
insert_records_from_trailers(log, &dups, commit, &ctx, oneline_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
strset_clear(&dups);
|
strset_clear(&dups);
|
||||||
strbuf_release(&ident);
|
|
||||||
strbuf_release(&oneline);
|
strbuf_release(&oneline);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -314,6 +324,7 @@ static int parse_group_option(const struct option *opt, const char *arg, int uns
|
|||||||
if (unset) {
|
if (unset) {
|
||||||
log->groups = 0;
|
log->groups = 0;
|
||||||
string_list_clear(&log->trailers, 0);
|
string_list_clear(&log->trailers, 0);
|
||||||
|
string_list_clear(&log->format, 0);
|
||||||
} else if (!strcasecmp(arg, "author"))
|
} else if (!strcasecmp(arg, "author"))
|
||||||
log->groups |= SHORTLOG_GROUP_AUTHOR;
|
log->groups |= SHORTLOG_GROUP_AUTHOR;
|
||||||
else if (!strcasecmp(arg, "committer"))
|
else if (!strcasecmp(arg, "committer"))
|
||||||
@ -321,8 +332,15 @@ static int parse_group_option(const struct option *opt, const char *arg, int uns
|
|||||||
else if (skip_prefix(arg, "trailer:", &field)) {
|
else if (skip_prefix(arg, "trailer:", &field)) {
|
||||||
log->groups |= SHORTLOG_GROUP_TRAILER;
|
log->groups |= SHORTLOG_GROUP_TRAILER;
|
||||||
string_list_append(&log->trailers, field);
|
string_list_append(&log->trailers, field);
|
||||||
} else
|
} else if (skip_prefix(arg, "format:", &field)) {
|
||||||
|
log->groups |= SHORTLOG_GROUP_FORMAT;
|
||||||
|
string_list_append(&log->format, field);
|
||||||
|
} else if (strchr(arg, '%')) {
|
||||||
|
log->groups |= SHORTLOG_GROUP_FORMAT;
|
||||||
|
string_list_append(&log->format, arg);
|
||||||
|
} else {
|
||||||
return error(_("unknown group type: %s"), arg);
|
return error(_("unknown group type: %s"), arg);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -340,6 +358,19 @@ void shortlog_init(struct shortlog *log)
|
|||||||
log->in2 = DEFAULT_INDENT2;
|
log->in2 = DEFAULT_INDENT2;
|
||||||
log->trailers.strdup_strings = 1;
|
log->trailers.strdup_strings = 1;
|
||||||
log->trailers.cmp = strcasecmp;
|
log->trailers.cmp = strcasecmp;
|
||||||
|
log->format.strdup_strings = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void shortlog_finish_setup(struct shortlog *log)
|
||||||
|
{
|
||||||
|
if (log->groups & SHORTLOG_GROUP_AUTHOR)
|
||||||
|
string_list_append(&log->format,
|
||||||
|
log->email ? "%aN <%aE>" : "%aN");
|
||||||
|
if (log->groups & SHORTLOG_GROUP_COMMITTER)
|
||||||
|
string_list_append(&log->format,
|
||||||
|
log->email ? "%cN <%cE>" : "%cN");
|
||||||
|
|
||||||
|
string_list_sort(&log->trailers);
|
||||||
}
|
}
|
||||||
|
|
||||||
int cmd_shortlog(int argc, const char **argv, const char *prefix)
|
int cmd_shortlog(int argc, const char **argv, const char *prefix)
|
||||||
@ -407,10 +438,11 @@ parse_done:
|
|||||||
log.user_format = rev.commit_format == CMIT_FMT_USERFORMAT;
|
log.user_format = rev.commit_format == CMIT_FMT_USERFORMAT;
|
||||||
log.abbrev = rev.abbrev;
|
log.abbrev = rev.abbrev;
|
||||||
log.file = rev.diffopt.file;
|
log.file = rev.diffopt.file;
|
||||||
|
log.date_mode = rev.date_mode;
|
||||||
|
|
||||||
if (!log.groups)
|
if (!log.groups)
|
||||||
log.groups = SHORTLOG_GROUP_AUTHOR;
|
log.groups = SHORTLOG_GROUP_AUTHOR;
|
||||||
string_list_sort(&log.trailers);
|
shortlog_finish_setup(&log);
|
||||||
|
|
||||||
/* assume HEAD if from a tty */
|
/* assume HEAD if from a tty */
|
||||||
if (!nongit && !rev.pending.nr && isatty(0))
|
if (!nongit && !rev.pending.nr && isatty(0))
|
||||||
@ -479,4 +511,5 @@ void shortlog_output(struct shortlog *log)
|
|||||||
log->list.strdup_strings = 1;
|
log->list.strdup_strings = 1;
|
||||||
string_list_clear(&log->list, 1);
|
string_list_clear(&log->list, 1);
|
||||||
clear_mailmap(&log->mailmap);
|
clear_mailmap(&log->mailmap);
|
||||||
|
string_list_clear(&log->format, 0);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
#define SHORTLOG_H
|
#define SHORTLOG_H
|
||||||
|
|
||||||
#include "string-list.h"
|
#include "string-list.h"
|
||||||
|
#include "date.h"
|
||||||
|
|
||||||
struct commit;
|
struct commit;
|
||||||
|
|
||||||
@ -15,13 +16,16 @@ struct shortlog {
|
|||||||
int in2;
|
int in2;
|
||||||
int user_format;
|
int user_format;
|
||||||
int abbrev;
|
int abbrev;
|
||||||
|
struct date_mode date_mode;
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
SHORTLOG_GROUP_AUTHOR = (1 << 0),
|
SHORTLOG_GROUP_AUTHOR = (1 << 0),
|
||||||
SHORTLOG_GROUP_COMMITTER = (1 << 1),
|
SHORTLOG_GROUP_COMMITTER = (1 << 1),
|
||||||
SHORTLOG_GROUP_TRAILER = (1 << 2),
|
SHORTLOG_GROUP_TRAILER = (1 << 2),
|
||||||
|
SHORTLOG_GROUP_FORMAT = (1 << 3),
|
||||||
} groups;
|
} groups;
|
||||||
struct string_list trailers;
|
struct string_list trailers;
|
||||||
|
struct string_list format;
|
||||||
|
|
||||||
int email;
|
int email;
|
||||||
struct string_list mailmap;
|
struct string_list mailmap;
|
||||||
@ -29,6 +33,7 @@ struct shortlog {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void shortlog_init(struct shortlog *log);
|
void shortlog_init(struct shortlog *log);
|
||||||
|
void shortlog_finish_setup(struct shortlog *log);
|
||||||
|
|
||||||
void shortlog_add_commit(struct shortlog *log, struct commit *commit);
|
void shortlog_add_commit(struct shortlog *log, struct commit *commit);
|
||||||
|
|
||||||
|
@ -83,6 +83,13 @@ test_expect_success 'pretty format' '
|
|||||||
test_cmp expect log.predictable
|
test_cmp expect log.predictable
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'pretty format (with --date)' '
|
||||||
|
sed "s/SUBJECT/2005-04-07 OBJECT_NAME/" expect.template >expect &&
|
||||||
|
git shortlog --format="%ad %H" --date=short HEAD >log &&
|
||||||
|
fuzz log >log.predictable &&
|
||||||
|
test_cmp expect log.predictable
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success '--abbrev' '
|
test_expect_success '--abbrev' '
|
||||||
sed s/SUBJECT/OBJID/ expect.template >expect &&
|
sed s/SUBJECT/OBJID/ expect.template >expect &&
|
||||||
git shortlog --format="%h" --abbrev=35 HEAD >log &&
|
git shortlog --format="%h" --abbrev=35 HEAD >log &&
|
||||||
@ -237,6 +244,26 @@ test_expect_success 'shortlog --group=trailer:signed-off-by' '
|
|||||||
test_cmp expect actual
|
test_cmp expect actual
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'shortlog --group=format' '
|
||||||
|
git shortlog -s --date="format:%Y" --group="format:%cN (%cd)" \
|
||||||
|
HEAD >actual &&
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
4 C O Mitter (2005)
|
||||||
|
1 Sin Nombre (2005)
|
||||||
|
EOF
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'shortlog --group=<format> DWIM' '
|
||||||
|
git shortlog -s --date="format:%Y" --group="%cN (%cd)" HEAD >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'shortlog bogus --group' '
|
||||||
|
test_must_fail git shortlog --group=bogus HEAD 2>err &&
|
||||||
|
grep "unknown group type" err
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success 'trailer idents are split' '
|
test_expect_success 'trailer idents are split' '
|
||||||
cat >expect <<-\EOF &&
|
cat >expect <<-\EOF &&
|
||||||
2 C O Mitter
|
2 C O Mitter
|
||||||
@ -319,6 +346,18 @@ test_expect_success 'shortlog can match multiple groups' '
|
|||||||
test_cmp expect actual
|
test_cmp expect actual
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'shortlog can match multiple format groups' '
|
||||||
|
GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME" \
|
||||||
|
git commit --allow-empty -m "identical names" &&
|
||||||
|
test_tick &&
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
2 A U Thor
|
||||||
|
1 C O Mitter
|
||||||
|
EOF
|
||||||
|
git shortlog -ns --group="%cn" --group="%an" -2 HEAD >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success 'set up option selection tests' '
|
test_expect_success 'set up option selection tests' '
|
||||||
git commit --allow-empty -F - <<-\EOF
|
git commit --allow-empty -F - <<-\EOF
|
||||||
subject
|
subject
|
||||||
|
Reference in New Issue
Block a user