9
9
#include "mailmap.h"
10
10
#include "shortlog.h"
11
11
#include "parse-options.h"
12
+ #include "trailer.h"
12
13
13
14
static char const * const shortlog_usage [] = {
14
15
N_ ("git shortlog [<options>] [<revision-range>] [[--] <path>...]" ),
@@ -49,12 +50,12 @@ static int compare_by_list(const void *a1, const void *a2)
49
50
}
50
51
51
52
static void insert_one_record (struct shortlog * log ,
52
- const char * author ,
53
+ const char * ident ,
53
54
const char * oneline )
54
55
{
55
56
struct string_list_item * item ;
56
57
57
- item = string_list_insert (& log -> list , author );
58
+ item = string_list_insert (& log -> list , ident );
58
59
59
60
if (log -> summary )
60
61
item -> util = (void * )(UTIL_TO_INT (item ) + 1 );
@@ -97,8 +98,8 @@ static void insert_one_record(struct shortlog *log,
97
98
}
98
99
}
99
100
100
- static int parse_stdin_author (struct shortlog * log ,
101
- struct strbuf * out , const char * in )
101
+ static int parse_ident (struct shortlog * log ,
102
+ struct strbuf * out , const char * in )
102
103
{
103
104
const char * mailbuf , * namebuf ;
104
105
size_t namelen , maillen ;
@@ -122,18 +123,33 @@ static int parse_stdin_author(struct shortlog *log,
122
123
123
124
static void read_from_stdin (struct shortlog * log )
124
125
{
125
- struct strbuf author = STRBUF_INIT ;
126
- struct strbuf mapped_author = STRBUF_INIT ;
126
+ struct strbuf ident = STRBUF_INIT ;
127
+ struct strbuf mapped_ident = STRBUF_INIT ;
127
128
struct strbuf oneline = STRBUF_INIT ;
128
129
static const char * author_match [2 ] = { "Author: " , "author " };
129
130
static const char * committer_match [2 ] = { "Commit: " , "committer " };
130
131
const char * * match ;
131
132
132
- match = log -> committer ? committer_match : author_match ;
133
- while (strbuf_getline_lf (& author , stdin ) != EOF ) {
133
+ if (HAS_MULTI_BITS (log -> groups ))
134
+ die (_ ("using multiple --group options with stdin is not supported" ));
135
+
136
+ switch (log -> groups ) {
137
+ case SHORTLOG_GROUP_AUTHOR :
138
+ match = author_match ;
139
+ break ;
140
+ case SHORTLOG_GROUP_COMMITTER :
141
+ match = committer_match ;
142
+ break ;
143
+ case SHORTLOG_GROUP_TRAILER :
144
+ die (_ ("using --group=trailer with stdin is not supported" ));
145
+ default :
146
+ BUG ("unhandled shortlog group" );
147
+ }
148
+
149
+ while (strbuf_getline_lf (& ident , stdin ) != EOF ) {
134
150
const char * v ;
135
- if (!skip_prefix (author .buf , match [0 ], & v ) &&
136
- !skip_prefix (author .buf , match [1 ], & v ))
151
+ if (!skip_prefix (ident .buf , match [0 ], & v ) &&
152
+ !skip_prefix (ident .buf , match [1 ], & v ))
137
153
continue ;
138
154
while (strbuf_getline_lf (& oneline , stdin ) != EOF &&
139
155
oneline .len )
@@ -142,45 +158,157 @@ static void read_from_stdin(struct shortlog *log)
142
158
!oneline .len )
143
159
; /* discard blanks */
144
160
145
- strbuf_reset (& mapped_author );
146
- if (parse_stdin_author (log , & mapped_author , v ) < 0 )
161
+ strbuf_reset (& mapped_ident );
162
+ if (parse_ident (log , & mapped_ident , v ) < 0 )
147
163
continue ;
148
164
149
- insert_one_record (log , mapped_author .buf , oneline .buf );
165
+ insert_one_record (log , mapped_ident .buf , oneline .buf );
150
166
}
151
- strbuf_release (& author );
152
- strbuf_release (& mapped_author );
167
+ strbuf_release (& ident );
168
+ strbuf_release (& mapped_ident );
153
169
strbuf_release (& oneline );
154
170
}
155
171
172
+ struct strset_item {
173
+ struct hashmap_entry ent ;
174
+ char value [FLEX_ARRAY ];
175
+ };
176
+
177
+ struct strset {
178
+ struct hashmap map ;
179
+ };
180
+
181
+ #define STRSET_INIT { { NULL } }
182
+
183
+ static int strset_item_hashcmp (const void * hash_data ,
184
+ const struct hashmap_entry * entry ,
185
+ const struct hashmap_entry * entry_or_key ,
186
+ const void * keydata )
187
+ {
188
+ const struct strset_item * a , * b ;
189
+
190
+ a = container_of (entry , const struct strset_item , ent );
191
+ if (keydata )
192
+ return strcmp (a -> value , keydata );
193
+
194
+ b = container_of (entry_or_key , const struct strset_item , ent );
195
+ return strcmp (a -> value , b -> value );
196
+ }
197
+
198
+ /*
199
+ * Adds "str" to the set if it was not already present; returns true if it was
200
+ * already there.
201
+ */
202
+ static int strset_check_and_add (struct strset * ss , const char * str )
203
+ {
204
+ unsigned int hash = strhash (str );
205
+ struct strset_item * item ;
206
+
207
+ if (!ss -> map .table )
208
+ hashmap_init (& ss -> map , strset_item_hashcmp , NULL , 0 );
209
+
210
+ if (hashmap_get_from_hash (& ss -> map , hash , str ))
211
+ return 1 ;
212
+
213
+ FLEX_ALLOC_STR (item , value , str );
214
+ hashmap_entry_init (& item -> ent , hash );
215
+ hashmap_add (& ss -> map , & item -> ent );
216
+ return 0 ;
217
+ }
218
+
219
+ static void strset_clear (struct strset * ss )
220
+ {
221
+ if (!ss -> map .table )
222
+ return ;
223
+ hashmap_free_entries (& ss -> map , struct strset_item , ent );
224
+ }
225
+
226
+ static void insert_records_from_trailers (struct shortlog * log ,
227
+ struct strset * dups ,
228
+ struct commit * commit ,
229
+ struct pretty_print_context * ctx ,
230
+ const char * oneline )
231
+ {
232
+ struct trailer_iterator iter ;
233
+ const char * commit_buffer , * body ;
234
+ struct strbuf ident = STRBUF_INIT ;
235
+
236
+ /*
237
+ * Using format_commit_message("%B") would be simpler here, but
238
+ * this saves us copying the message.
239
+ */
240
+ commit_buffer = logmsg_reencode (commit , NULL , ctx -> output_encoding );
241
+ body = strstr (commit_buffer , "\n\n" );
242
+ if (!body )
243
+ return ;
244
+
245
+ trailer_iterator_init (& iter , body );
246
+ while (trailer_iterator_advance (& iter )) {
247
+ const char * value = iter .val .buf ;
248
+
249
+ if (!string_list_has_string (& log -> trailers , iter .key .buf ))
250
+ continue ;
251
+
252
+ strbuf_reset (& ident );
253
+ if (!parse_ident (log , & ident , value ))
254
+ value = ident .buf ;
255
+
256
+ if (strset_check_and_add (dups , value ))
257
+ continue ;
258
+ insert_one_record (log , value , oneline );
259
+ }
260
+ trailer_iterator_release (& iter );
261
+
262
+ strbuf_release (& ident );
263
+ unuse_commit_buffer (commit , commit_buffer );
264
+ }
265
+
156
266
void shortlog_add_commit (struct shortlog * log , struct commit * commit )
157
267
{
158
- struct strbuf author = STRBUF_INIT ;
268
+ struct strbuf ident = STRBUF_INIT ;
159
269
struct strbuf oneline = STRBUF_INIT ;
270
+ struct strset dups = STRSET_INIT ;
160
271
struct pretty_print_context ctx = {0 };
161
- const char * fmt ;
272
+ const char * oneline_str ;
162
273
163
274
ctx .fmt = CMIT_FMT_USERFORMAT ;
164
275
ctx .abbrev = log -> abbrev ;
165
276
ctx .print_email_subject = 1 ;
166
277
ctx .date_mode .type = DATE_NORMAL ;
167
278
ctx .output_encoding = get_log_output_encoding ();
168
279
169
- fmt = log -> committer ?
170
- (log -> email ? "%cN <%cE>" : "%cN" ) :
171
- (log -> email ? "%aN <%aE>" : "%aN" );
172
-
173
- format_commit_message (commit , fmt , & author , & ctx );
174
280
if (!log -> summary ) {
175
281
if (log -> user_format )
176
282
pretty_print_commit (& ctx , commit , & oneline );
177
283
else
178
284
format_commit_message (commit , "%s" , & oneline , & ctx );
179
285
}
286
+ oneline_str = oneline .len ? oneline .buf : "<none>" ;
287
+
288
+ if (log -> groups & SHORTLOG_GROUP_AUTHOR ) {
289
+ strbuf_reset (& ident );
290
+ format_commit_message (commit ,
291
+ log -> email ? "%aN <%aE>" : "%aN" ,
292
+ & ident , & ctx );
293
+ if (!HAS_MULTI_BITS (log -> groups ) ||
294
+ !strset_check_and_add (& dups , ident .buf ))
295
+ insert_one_record (log , ident .buf , oneline_str );
296
+ }
297
+ if (log -> groups & SHORTLOG_GROUP_COMMITTER ) {
298
+ strbuf_reset (& ident );
299
+ format_commit_message (commit ,
300
+ log -> email ? "%cN <%cE>" : "%cN" ,
301
+ & ident , & ctx );
302
+ if (!HAS_MULTI_BITS (log -> groups ) ||
303
+ !strset_check_and_add (& dups , ident .buf ))
304
+ insert_one_record (log , ident .buf , oneline_str );
305
+ }
306
+ if (log -> groups & SHORTLOG_GROUP_TRAILER ) {
307
+ insert_records_from_trailers (log , & dups , commit , & ctx , oneline_str );
308
+ }
180
309
181
- insert_one_record (log , author .buf , oneline .len ? oneline .buf : "<none>" );
182
-
183
- strbuf_release (& author );
310
+ strset_clear (& dups );
311
+ strbuf_release (& ident );
184
312
strbuf_release (& oneline );
185
313
}
186
314
@@ -241,6 +369,28 @@ static int parse_wrap_args(const struct option *opt, const char *arg, int unset)
241
369
return 0 ;
242
370
}
243
371
372
+ static int parse_group_option (const struct option * opt , const char * arg , int unset )
373
+ {
374
+ struct shortlog * log = opt -> value ;
375
+ const char * field ;
376
+
377
+ if (unset ) {
378
+ log -> groups = 0 ;
379
+ string_list_clear (& log -> trailers , 0 );
380
+ } else if (!strcasecmp (arg , "author" ))
381
+ log -> groups |= SHORTLOG_GROUP_AUTHOR ;
382
+ else if (!strcasecmp (arg , "committer" ))
383
+ log -> groups |= SHORTLOG_GROUP_COMMITTER ;
384
+ else if (skip_prefix (arg , "trailer:" , & field )) {
385
+ log -> groups |= SHORTLOG_GROUP_TRAILER ;
386
+ string_list_append (& log -> trailers , field );
387
+ } else
388
+ return error (_ ("unknown group type: %s" ), arg );
389
+
390
+ return 0 ;
391
+ }
392
+
393
+
244
394
void shortlog_init (struct shortlog * log )
245
395
{
246
396
memset (log , 0 , sizeof (* log ));
@@ -251,6 +401,8 @@ void shortlog_init(struct shortlog *log)
251
401
log -> wrap = DEFAULT_WRAPLEN ;
252
402
log -> in1 = DEFAULT_INDENT1 ;
253
403
log -> in2 = DEFAULT_INDENT2 ;
404
+ log -> trailers .strdup_strings = 1 ;
405
+ log -> trailers .cmp = strcasecmp ;
254
406
}
255
407
256
408
int cmd_shortlog (int argc , const char * * argv , const char * prefix )
@@ -260,8 +412,9 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
260
412
int nongit = !startup_info -> have_repository ;
261
413
262
414
const struct option options [] = {
263
- OPT_BOOL ('c' , "committer" , & log .committer ,
264
- N_ ("Group by committer rather than author" )),
415
+ OPT_BIT ('c' , "committer" , & log .groups ,
416
+ N_ ("Group by committer rather than author" ),
417
+ SHORTLOG_GROUP_COMMITTER ),
265
418
OPT_BOOL ('n' , "numbered" , & log .sort_by_number ,
266
419
N_ ("sort output according to the number of commits per author" )),
267
420
OPT_BOOL ('s' , "summary" , & log .summary ,
@@ -271,6 +424,8 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
271
424
OPT_CALLBACK_F ('w' , NULL , & log , N_ ("<w>[,<i1>[,<i2>]]" ),
272
425
N_ ("Linewrap output" ), PARSE_OPT_OPTARG ,
273
426
& parse_wrap_args ),
427
+ OPT_CALLBACK (0 , "group" , & log , N_ ("field" ),
428
+ N_ ("Group by field" ), parse_group_option ),
274
429
OPT_END (),
275
430
};
276
431
@@ -311,6 +466,10 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
311
466
log .abbrev = rev .abbrev ;
312
467
log .file = rev .diffopt .file ;
313
468
469
+ if (!log .groups )
470
+ log .groups = SHORTLOG_GROUP_AUTHOR ;
471
+ string_list_sort (& log .trailers );
472
+
314
473
/* assume HEAD if from a tty */
315
474
if (!nongit && !rev .pending .nr && isatty (0 ))
316
475
add_head_to_pending (& rev );
0 commit comments