Skip to content

Commit 3c23bea

Browse files
author
Junio C Hamano
committed
Merge branch 'js/shortlog'
* js/shortlog: git-shortlog: make common repository prefix configurable with .mailmap git-shortlog: fix common repository prefix abbreviation. builtin git-shortlog is broken shortlog: fix "-n" shortlog: handle email addresses case-insensitively shortlog: read mailmap from ./.mailmap again shortlog: do not crash on parsing "[PATCH" Build in shortlog
2 parents a22f542 + 7595e2e commit 3c23bea

File tree

7 files changed

+384
-235
lines changed

7 files changed

+384
-235
lines changed

Documentation/git-shortlog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ git-shortlog - Summarize 'git log' output
88
SYNOPSIS
99
--------
1010
git-log --pretty=short | 'git-shortlog' [-h] [-n] [-s]
11+
git-shortlog [-n|--number] [-s|--summary] [<committish>...]
1112

1213
DESCRIPTION
1314
-----------

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ SCRIPT_SH = \
171171

172172
SCRIPT_PERL = \
173173
git-archimport.perl git-cvsimport.perl git-relink.perl \
174-
git-shortlog.perl git-rerere.perl \
174+
git-rerere.perl \
175175
git-cvsserver.perl \
176176
git-svnimport.perl git-cvsexportcommit.perl \
177177
git-send-email.perl git-svn.perl
@@ -287,6 +287,7 @@ BUILTIN_OBJS = \
287287
builtin-rev-parse.o \
288288
builtin-rm.o \
289289
builtin-runstatus.o \
290+
builtin-shortlog.o \
290291
builtin-show-branch.o \
291292
builtin-stripspace.o \
292293
builtin-symbolic-ref.o \

builtin-shortlog.c

Lines changed: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
#include "builtin.h"
2+
#include "cache.h"
3+
#include "commit.h"
4+
#include "diff.h"
5+
#include "path-list.h"
6+
#include "revision.h"
7+
#include <string.h>
8+
9+
static const char shortlog_usage[] =
10+
"git-shortlog [-n] [-s] [<commit-id>... ]";
11+
12+
static char *common_repo_prefix;
13+
14+
static int compare_by_number(const void *a1, const void *a2)
15+
{
16+
const struct path_list_item *i1 = a1, *i2 = a2;
17+
const struct path_list *l1 = i1->util, *l2 = i2->util;
18+
19+
if (l1->nr < l2->nr)
20+
return 1;
21+
else if (l1->nr == l2->nr)
22+
return 0;
23+
else
24+
return -1;
25+
}
26+
27+
static struct path_list mailmap = {NULL, 0, 0, 0};
28+
29+
static int read_mailmap(const char *filename)
30+
{
31+
char buffer[1024];
32+
FILE *f = fopen(filename, "r");
33+
34+
if (f == NULL)
35+
return 1;
36+
while (fgets(buffer, sizeof(buffer), f) != NULL) {
37+
char *end_of_name, *left_bracket, *right_bracket;
38+
char *name, *email;
39+
int i;
40+
if (buffer[0] == '#') {
41+
static const char abbrev[] = "# repo-abbrev:";
42+
int abblen = sizeof(abbrev) - 1;
43+
int len = strlen(buffer);
44+
45+
if (len && buffer[len - 1] == '\n')
46+
buffer[--len] = 0;
47+
if (!strncmp(buffer, abbrev, abblen)) {
48+
char *cp;
49+
50+
if (common_repo_prefix)
51+
free(common_repo_prefix);
52+
common_repo_prefix = xmalloc(len);
53+
54+
for (cp = buffer + abblen; isspace(*cp); cp++)
55+
; /* nothing */
56+
strcpy(common_repo_prefix, cp);
57+
}
58+
continue;
59+
}
60+
if ((left_bracket = strchr(buffer, '<')) == NULL)
61+
continue;
62+
if ((right_bracket = strchr(left_bracket + 1, '>')) == NULL)
63+
continue;
64+
if (right_bracket == left_bracket + 1)
65+
continue;
66+
for (end_of_name = left_bracket; end_of_name != buffer
67+
&& isspace(end_of_name[-1]); end_of_name--)
68+
/* keep on looking */
69+
if (end_of_name == buffer)
70+
continue;
71+
name = xmalloc(end_of_name - buffer + 1);
72+
strlcpy(name, buffer, end_of_name - buffer + 1);
73+
email = xmalloc(right_bracket - left_bracket);
74+
for (i = 0; i < right_bracket - left_bracket - 1; i++)
75+
email[i] = tolower(left_bracket[i + 1]);
76+
email[right_bracket - left_bracket - 1] = '\0';
77+
path_list_insert(email, &mailmap)->util = name;
78+
}
79+
fclose(f);
80+
return 0;
81+
}
82+
83+
static int map_email(char *email, char *name, int maxlen)
84+
{
85+
char *p;
86+
struct path_list_item *item;
87+
88+
/* autocomplete common developers */
89+
p = strchr(email, '>');
90+
if (!p)
91+
return 0;
92+
93+
*p = '\0';
94+
/* downcase the email address */
95+
for (p = email; *p; p++)
96+
*p = tolower(*p);
97+
item = path_list_lookup(email, &mailmap);
98+
if (item != NULL) {
99+
const char *realname = (const char *)item->util;
100+
strncpy(name, realname, maxlen);
101+
return 1;
102+
}
103+
return 0;
104+
}
105+
106+
static void insert_author_oneline(struct path_list *list,
107+
const char *author, int authorlen,
108+
const char *oneline, int onelinelen)
109+
{
110+
const char *dot3 = common_repo_prefix;
111+
char *buffer, *p;
112+
struct path_list_item *item;
113+
struct path_list *onelines;
114+
115+
while (authorlen > 0 && isspace(author[authorlen - 1]))
116+
authorlen--;
117+
118+
buffer = xmalloc(authorlen + 1);
119+
memcpy(buffer, author, authorlen);
120+
buffer[authorlen] = '\0';
121+
122+
item = path_list_insert(buffer, list);
123+
if (item->util == NULL)
124+
item->util = xcalloc(1, sizeof(struct path_list));
125+
else
126+
free(buffer);
127+
128+
if (!strncmp(oneline, "[PATCH", 6)) {
129+
char *eob = strchr(oneline, ']');
130+
131+
if (eob) {
132+
while (isspace(eob[1]) && eob[1] != '\n')
133+
eob++;
134+
if (eob - oneline < onelinelen) {
135+
onelinelen -= eob - oneline;
136+
oneline = eob;
137+
}
138+
}
139+
}
140+
141+
while (onelinelen > 0 && isspace(oneline[0])) {
142+
oneline++;
143+
onelinelen--;
144+
}
145+
146+
while (onelinelen > 0 && isspace(oneline[onelinelen - 1]))
147+
onelinelen--;
148+
149+
buffer = xmalloc(onelinelen + 1);
150+
memcpy(buffer, oneline, onelinelen);
151+
buffer[onelinelen] = '\0';
152+
153+
if (dot3) {
154+
int dot3len = strlen(dot3);
155+
if (dot3len > 5) {
156+
while ((p = strstr(buffer, dot3)) != NULL) {
157+
int taillen = strlen(p) - dot3len;
158+
memcpy(p, "/.../", 5);
159+
memmove(p + 5, p + dot3len, taillen + 1);
160+
}
161+
}
162+
}
163+
164+
onelines = item->util;
165+
if (onelines->nr >= onelines->alloc) {
166+
onelines->alloc = alloc_nr(onelines->nr);
167+
onelines->items = xrealloc(onelines->items,
168+
onelines->alloc
169+
* sizeof(struct path_list_item));
170+
}
171+
172+
onelines->items[onelines->nr].util = NULL;
173+
onelines->items[onelines->nr++].path = buffer;
174+
}
175+
176+
static void read_from_stdin(struct path_list *list)
177+
{
178+
char buffer[1024];
179+
180+
while (fgets(buffer, sizeof(buffer), stdin) != NULL) {
181+
char *bob;
182+
if ((buffer[0] == 'A' || buffer[0] == 'a') &&
183+
!strncmp(buffer + 1, "uthor: ", 7) &&
184+
(bob = strchr(buffer + 7, '<')) != NULL) {
185+
char buffer2[1024], offset = 0;
186+
187+
if (map_email(bob + 1, buffer, sizeof(buffer)))
188+
bob = buffer + strlen(buffer);
189+
else {
190+
offset = 8;
191+
while (isspace(bob[-1]))
192+
bob--;
193+
}
194+
195+
while (fgets(buffer2, sizeof(buffer2), stdin) &&
196+
buffer2[0] != '\n')
197+
; /* chomp input */
198+
if (fgets(buffer2, sizeof(buffer2), stdin))
199+
insert_author_oneline(list,
200+
buffer + offset,
201+
bob - buffer - offset,
202+
buffer2, strlen(buffer2));
203+
}
204+
}
205+
}
206+
207+
static void get_from_rev(struct rev_info *rev, struct path_list *list)
208+
{
209+
char scratch[1024];
210+
struct commit *commit;
211+
212+
prepare_revision_walk(rev);
213+
while ((commit = get_revision(rev)) != NULL) {
214+
char *author = NULL, *oneline, *buffer;
215+
int authorlen = authorlen, onelinelen;
216+
217+
/* get author and oneline */
218+
for (buffer = commit->buffer; buffer && *buffer != '\0' &&
219+
*buffer != '\n'; ) {
220+
char *eol = strchr(buffer, '\n');
221+
222+
if (eol == NULL)
223+
eol = buffer + strlen(buffer);
224+
else
225+
eol++;
226+
227+
if (!strncmp(buffer, "author ", 7)) {
228+
char *bracket = strchr(buffer, '<');
229+
230+
if (bracket == NULL || bracket > eol)
231+
die("Invalid commit buffer: %s",
232+
sha1_to_hex(commit->object.sha1));
233+
234+
if (map_email(bracket + 1, scratch,
235+
sizeof(scratch))) {
236+
author = scratch;
237+
authorlen = strlen(scratch);
238+
} else {
239+
while (bracket[-1] == ' ')
240+
bracket--;
241+
242+
author = buffer + 7;
243+
authorlen = bracket - buffer - 7;
244+
}
245+
}
246+
buffer = eol;
247+
}
248+
249+
if (author == NULL)
250+
die ("Missing author: %s",
251+
sha1_to_hex(commit->object.sha1));
252+
253+
if (buffer == NULL || *buffer == '\0') {
254+
oneline = "<none>";
255+
onelinelen = sizeof(oneline) + 1;
256+
} else {
257+
char *eol;
258+
259+
oneline = buffer + 1;
260+
eol = strchr(oneline, '\n');
261+
if (eol == NULL)
262+
onelinelen = strlen(oneline);
263+
else
264+
onelinelen = eol - oneline;
265+
}
266+
267+
insert_author_oneline(list,
268+
author, authorlen, oneline, onelinelen);
269+
}
270+
271+
}
272+
273+
int cmd_shortlog(int argc, const char **argv, const char *prefix)
274+
{
275+
struct rev_info rev;
276+
struct path_list list = { NULL, 0, 0, 1 };
277+
int i, j, sort_by_number = 0, summary = 0;
278+
279+
/* since -n is a shadowed rev argument, parse our args first */
280+
while (argc > 1) {
281+
if (!strcmp(argv[1], "-n") || !strcmp(argv[1], "--numbered"))
282+
sort_by_number = 1;
283+
else if (!strcmp(argv[1], "-s") ||
284+
!strcmp(argv[1], "--summary"))
285+
summary = 1;
286+
else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
287+
usage(shortlog_usage);
288+
else
289+
break;
290+
argv++;
291+
argc--;
292+
}
293+
init_revisions(&rev, prefix);
294+
argc = setup_revisions(argc, argv, &rev, NULL);
295+
if (argc > 1)
296+
die ("unrecognized argument: %s", argv[1]);
297+
298+
if (!access(".mailmap", R_OK))
299+
read_mailmap(".mailmap");
300+
301+
if (rev.pending.nr == 1)
302+
die ("Need a range!");
303+
else if (rev.pending.nr == 0)
304+
read_from_stdin(&list);
305+
else
306+
get_from_rev(&rev, &list);
307+
308+
if (sort_by_number)
309+
qsort(list.items, list.nr, sizeof(struct path_list_item),
310+
compare_by_number);
311+
312+
for (i = 0; i < list.nr; i++) {
313+
struct path_list *onelines = list.items[i].util;
314+
315+
if (summary) {
316+
printf("%s: %d\n", list.items[i].path, onelines->nr);
317+
} else {
318+
printf("%s (%d):\n", list.items[i].path, onelines->nr);
319+
for (j = onelines->nr - 1; j >= 0; j--)
320+
printf(" %s\n", onelines->items[j].path);
321+
printf("\n");
322+
}
323+
324+
onelines->strdup_paths = 1;
325+
path_list_clear(onelines, 1);
326+
free(onelines);
327+
list.items[i].util = NULL;
328+
}
329+
330+
list.strdup_paths = 1;
331+
path_list_clear(&list, 1);
332+
mailmap.strdup_paths = 1;
333+
path_list_clear(&mailmap, 1);
334+
335+
return 0;
336+
}
337+

builtin.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ extern int cmd_rev_list(int argc, const char **argv, const char *prefix);
5555
extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);
5656
extern int cmd_rm(int argc, const char **argv, const char *prefix);
5757
extern int cmd_runstatus(int argc, const char **argv, const char *prefix);
58+
extern int cmd_shortlog(int argc, const char **argv, const char *prefix);
5859
extern int cmd_show(int argc, const char **argv, const char *prefix);
5960
extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
6061
extern int cmd_stripspace(int argc, const char **argv, const char *prefix);

0 commit comments

Comments
 (0)