Skip to content

Commit c112d8d

Browse files
committed
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
2 parents 71aa6e3 + 7b11234 commit c112d8d

File tree

5 files changed

+113
-27
lines changed

5 files changed

+113
-27
lines changed

Documentation/git-shortlog.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ OPTIONS
4747

4848
Each pretty-printed commit will be rewrapped before it is shown.
4949

50+
--date=<format>::
51+
Show dates formatted according to the given date string. (See
52+
the `--date` option in the "Commit Formatting" section of
53+
linkgit:git-log[1]). Useful with `--group=format:<format>`.
54+
5055
--group=<type>::
5156
Group commits based on `<type>`. If no `--group` option is
5257
specified, the default is `author`. `<type>` is one of:
@@ -59,6 +64,9 @@ OPTIONS
5964
example, if your project uses `Reviewed-by` trailers, you might want
6065
to see who has been reviewing with
6166
`git shortlog -ns --group=trailer:reviewed-by`.
67+
- `format:<format>`, any string accepted by the `--format` option of
68+
'git log'. (See the "PRETTY FORMATS" section of
69+
linkgit:git-log[1].)
6270
+
6371
Note that commits that do not include the trailer will not be counted.
6472
Likewise, commits with multiple trailers (e.g., multiple signoffs) may

builtin/log.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1334,6 +1334,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
13341334
log.in2 = 4;
13351335
log.file = rev->diffopt.file;
13361336
log.groups = SHORTLOG_GROUP_AUTHOR;
1337+
shortlog_finish_setup(&log);
13371338
for (i = 0; i < nr; i++)
13381339
shortlog_add_commit(&log, list[i]);
13391340

builtin/shortlog.c

Lines changed: 60 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,9 @@ static void read_from_stdin(struct shortlog *log)
132132
match = committer_match;
133133
break;
134134
case SHORTLOG_GROUP_TRAILER:
135-
die(_("using --group=trailer with stdin is not supported"));
135+
die(_("using %s with stdin is not supported"), "--group=trailer");
136+
case SHORTLOG_GROUP_FORMAT:
137+
die(_("using %s with stdin is not supported"), "--group=format");
136138
default:
137139
BUG("unhandled shortlog group");
138140
}
@@ -170,6 +172,9 @@ static void insert_records_from_trailers(struct shortlog *log,
170172
const char *commit_buffer, *body;
171173
struct strbuf ident = STRBUF_INIT;
172174

175+
if (!log->trailers.nr)
176+
return;
177+
173178
/*
174179
* Using format_commit_message("%B") would be simpler here, but
175180
* this saves us copying the message.
@@ -200,9 +205,34 @@ static void insert_records_from_trailers(struct shortlog *log,
200205
unuse_commit_buffer(commit, commit_buffer);
201206
}
202207

208+
static int shortlog_needs_dedup(const struct shortlog *log)
209+
{
210+
return HAS_MULTI_BITS(log->groups) || log->format.nr > 1 || log->trailers.nr;
211+
}
212+
213+
static void insert_records_from_format(struct shortlog *log,
214+
struct strset *dups,
215+
struct commit *commit,
216+
struct pretty_print_context *ctx,
217+
const char *oneline)
218+
{
219+
struct strbuf buf = STRBUF_INIT;
220+
struct string_list_item *item;
221+
222+
for_each_string_list_item(item, &log->format) {
223+
strbuf_reset(&buf);
224+
225+
format_commit_message(commit, item->string, &buf, ctx);
226+
227+
if (!shortlog_needs_dedup(log) || strset_add(dups, buf.buf))
228+
insert_one_record(log, buf.buf, oneline);
229+
}
230+
231+
strbuf_release(&buf);
232+
}
233+
203234
void shortlog_add_commit(struct shortlog *log, struct commit *commit)
204235
{
205-
struct strbuf ident = STRBUF_INIT;
206236
struct strbuf oneline = STRBUF_INIT;
207237
struct strset dups = STRSET_INIT;
208238
struct pretty_print_context ctx = {0};
@@ -211,7 +241,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)
211241
ctx.fmt = CMIT_FMT_USERFORMAT;
212242
ctx.abbrev = log->abbrev;
213243
ctx.print_email_subject = 1;
214-
ctx.date_mode.type = DATE_NORMAL;
244+
ctx.date_mode = log->date_mode;
215245
ctx.output_encoding = get_log_output_encoding();
216246

217247
if (!log->summary) {
@@ -222,30 +252,10 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)
222252
}
223253
oneline_str = oneline.len ? oneline.buf : "<none>";
224254

225-
if (log->groups & SHORTLOG_GROUP_AUTHOR) {
226-
strbuf_reset(&ident);
227-
format_commit_message(commit,
228-
log->email ? "%aN <%aE>" : "%aN",
229-
&ident, &ctx);
230-
if (!HAS_MULTI_BITS(log->groups) ||
231-
strset_add(&dups, ident.buf))
232-
insert_one_record(log, ident.buf, oneline_str);
233-
}
234-
if (log->groups & SHORTLOG_GROUP_COMMITTER) {
235-
strbuf_reset(&ident);
236-
format_commit_message(commit,
237-
log->email ? "%cN <%cE>" : "%cN",
238-
&ident, &ctx);
239-
if (!HAS_MULTI_BITS(log->groups) ||
240-
strset_add(&dups, ident.buf))
241-
insert_one_record(log, ident.buf, oneline_str);
242-
}
243-
if (log->groups & SHORTLOG_GROUP_TRAILER) {
244-
insert_records_from_trailers(log, &dups, commit, &ctx, oneline_str);
245-
}
255+
insert_records_from_trailers(log, &dups, commit, &ctx, oneline_str);
256+
insert_records_from_format(log, &dups, commit, &ctx, oneline_str);
246257

247258
strset_clear(&dups);
248-
strbuf_release(&ident);
249259
strbuf_release(&oneline);
250260
}
251261

@@ -314,15 +324,23 @@ static int parse_group_option(const struct option *opt, const char *arg, int uns
314324
if (unset) {
315325
log->groups = 0;
316326
string_list_clear(&log->trailers, 0);
327+
string_list_clear(&log->format, 0);
317328
} else if (!strcasecmp(arg, "author"))
318329
log->groups |= SHORTLOG_GROUP_AUTHOR;
319330
else if (!strcasecmp(arg, "committer"))
320331
log->groups |= SHORTLOG_GROUP_COMMITTER;
321332
else if (skip_prefix(arg, "trailer:", &field)) {
322333
log->groups |= SHORTLOG_GROUP_TRAILER;
323334
string_list_append(&log->trailers, field);
324-
} else
335+
} else if (skip_prefix(arg, "format:", &field)) {
336+
log->groups |= SHORTLOG_GROUP_FORMAT;
337+
string_list_append(&log->format, field);
338+
} else if (strchr(arg, '%')) {
339+
log->groups |= SHORTLOG_GROUP_FORMAT;
340+
string_list_append(&log->format, arg);
341+
} else {
325342
return error(_("unknown group type: %s"), arg);
343+
}
326344

327345
return 0;
328346
}
@@ -340,6 +358,19 @@ void shortlog_init(struct shortlog *log)
340358
log->in2 = DEFAULT_INDENT2;
341359
log->trailers.strdup_strings = 1;
342360
log->trailers.cmp = strcasecmp;
361+
log->format.strdup_strings = 1;
362+
}
363+
364+
void shortlog_finish_setup(struct shortlog *log)
365+
{
366+
if (log->groups & SHORTLOG_GROUP_AUTHOR)
367+
string_list_append(&log->format,
368+
log->email ? "%aN <%aE>" : "%aN");
369+
if (log->groups & SHORTLOG_GROUP_COMMITTER)
370+
string_list_append(&log->format,
371+
log->email ? "%cN <%cE>" : "%cN");
372+
373+
string_list_sort(&log->trailers);
343374
}
344375

345376
int cmd_shortlog(int argc, const char **argv, const char *prefix)
@@ -407,10 +438,11 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
407438
log.user_format = rev.commit_format == CMIT_FMT_USERFORMAT;
408439
log.abbrev = rev.abbrev;
409440
log.file = rev.diffopt.file;
441+
log.date_mode = rev.date_mode;
410442

411443
if (!log.groups)
412444
log.groups = SHORTLOG_GROUP_AUTHOR;
413-
string_list_sort(&log.trailers);
445+
shortlog_finish_setup(&log);
414446

415447
/* assume HEAD if from a tty */
416448
if (!nongit && !rev.pending.nr && isatty(0))
@@ -479,4 +511,5 @@ void shortlog_output(struct shortlog *log)
479511
log->list.strdup_strings = 1;
480512
string_list_clear(&log->list, 1);
481513
clear_mailmap(&log->mailmap);
514+
string_list_clear(&log->format, 0);
482515
}

shortlog.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#define SHORTLOG_H
33

44
#include "string-list.h"
5+
#include "date.h"
56

67
struct commit;
78

@@ -15,20 +16,24 @@ struct shortlog {
1516
int in2;
1617
int user_format;
1718
int abbrev;
19+
struct date_mode date_mode;
1820

1921
enum {
2022
SHORTLOG_GROUP_AUTHOR = (1 << 0),
2123
SHORTLOG_GROUP_COMMITTER = (1 << 1),
2224
SHORTLOG_GROUP_TRAILER = (1 << 2),
25+
SHORTLOG_GROUP_FORMAT = (1 << 3),
2326
} groups;
2427
struct string_list trailers;
28+
struct string_list format;
2529

2630
int email;
2731
struct string_list mailmap;
2832
FILE *file;
2933
};
3034

3135
void shortlog_init(struct shortlog *log);
36+
void shortlog_finish_setup(struct shortlog *log);
3237

3338
void shortlog_add_commit(struct shortlog *log, struct commit *commit);
3439

t/t4201-shortlog.sh

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,13 @@ test_expect_success 'pretty format' '
8383
test_cmp expect log.predictable
8484
'
8585

86+
test_expect_success 'pretty format (with --date)' '
87+
sed "s/SUBJECT/2005-04-07 OBJECT_NAME/" expect.template >expect &&
88+
git shortlog --format="%ad %H" --date=short HEAD >log &&
89+
fuzz log >log.predictable &&
90+
test_cmp expect log.predictable
91+
'
92+
8693
test_expect_success '--abbrev' '
8794
sed s/SUBJECT/OBJID/ expect.template >expect &&
8895
git shortlog --format="%h" --abbrev=35 HEAD >log &&
@@ -237,6 +244,26 @@ test_expect_success 'shortlog --group=trailer:signed-off-by' '
237244
test_cmp expect actual
238245
'
239246

247+
test_expect_success 'shortlog --group=format' '
248+
git shortlog -s --date="format:%Y" --group="format:%cN (%cd)" \
249+
HEAD >actual &&
250+
cat >expect <<-\EOF &&
251+
4 C O Mitter (2005)
252+
1 Sin Nombre (2005)
253+
EOF
254+
test_cmp expect actual
255+
'
256+
257+
test_expect_success 'shortlog --group=<format> DWIM' '
258+
git shortlog -s --date="format:%Y" --group="%cN (%cd)" HEAD >actual &&
259+
test_cmp expect actual
260+
'
261+
262+
test_expect_success 'shortlog bogus --group' '
263+
test_must_fail git shortlog --group=bogus HEAD 2>err &&
264+
grep "unknown group type" err
265+
'
266+
240267
test_expect_success 'trailer idents are split' '
241268
cat >expect <<-\EOF &&
242269
2 C O Mitter
@@ -319,6 +346,18 @@ test_expect_success 'shortlog can match multiple groups' '
319346
test_cmp expect actual
320347
'
321348

349+
test_expect_success 'shortlog can match multiple format groups' '
350+
GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME" \
351+
git commit --allow-empty -m "identical names" &&
352+
test_tick &&
353+
cat >expect <<-\EOF &&
354+
2 A U Thor
355+
1 C O Mitter
356+
EOF
357+
git shortlog -ns --group="%cn" --group="%an" -2 HEAD >actual &&
358+
test_cmp expect actual
359+
'
360+
322361
test_expect_success 'set up option selection tests' '
323362
git commit --allow-empty -F - <<-\EOF
324363
subject

0 commit comments

Comments
 (0)