diff options
Diffstat (limited to 'builtin-shortlog.c')
-rw-r--r-- | builtin-shortlog.c | 464 |
1 files changed, 234 insertions, 230 deletions
diff --git a/builtin-shortlog.c b/builtin-shortlog.c index 16af6199ab..b28091b445 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -2,20 +2,24 @@ #include "cache.h" #include "commit.h" #include "diff.h" -#include "path-list.h" +#include "string-list.h" #include "revision.h" #include "utf8.h" #include "mailmap.h" +#include "shortlog.h" +#include "parse-options.h" -static const char shortlog_usage[] = -"git-shortlog [-n] [-s] [<commit-id>... ]"; - -static char *common_repo_prefix; +static char const * const shortlog_usage[] = { + "git shortlog [-n] [-s] [-e] [-w] [rev-opts] [--] [<commit-id>... ]", + "", + "[rev-opts] are documented in git-rev-list(1)", + NULL +}; static int compare_by_number(const void *a1, const void *a2) { - const struct path_list_item *i1 = a1, *i2 = a2; - const struct path_list *l1 = i1->util, *l2 = i2->util; + const struct string_list_item *i1 = a1, *i2 = a2; + const struct string_list *l1 = i1->util, *l2 = i2->util; if (l1->nr < l2->nr) return 1; @@ -25,54 +29,80 @@ static int compare_by_number(const void *a1, const void *a2) return -1; } -static struct path_list mailmap = {NULL, 0, 0, 0}; +const char *format_subject(struct strbuf *sb, const char *msg, + const char *line_separator); -static void insert_author_oneline(struct path_list *list, - const char *author, int authorlen, - const char *oneline, int onelinelen) +static void insert_one_record(struct shortlog *log, + const char *author, + const char *oneline) { - const char *dot3 = common_repo_prefix; + const char *dot3 = log->common_repo_prefix; char *buffer, *p; - struct path_list_item *item; - struct path_list *onelines; - - while (authorlen > 0 && isspace(author[authorlen - 1])) - authorlen--; + struct string_list_item *item; + char namebuf[1024]; + char emailbuf[1024]; + size_t len; + const char *eol; + const char *boemail, *eoemail; + struct strbuf subject = STRBUF_INIT; + + boemail = strchr(author, '<'); + if (!boemail) + return; + eoemail = strchr(boemail, '>'); + if (!eoemail) + return; + + /* copy author name to namebuf, to support matching on both name and email */ + memcpy(namebuf, author, boemail - author); + len = boemail - author; + while(len > 0 && isspace(namebuf[len-1])) + len--; + namebuf[len] = 0; + + /* copy email name to emailbuf, to allow email replacement as well */ + memcpy(emailbuf, boemail+1, eoemail - boemail); + emailbuf[eoemail - boemail - 1] = 0; + + if (!map_user(&log->mailmap, emailbuf, sizeof(emailbuf), namebuf, sizeof(namebuf))) { + while (author < boemail && isspace(*author)) + author++; + for (len = 0; + len < sizeof(namebuf) - 1 && author + len < boemail; + len++) + namebuf[len] = author[len]; + while (0 < len && isspace(namebuf[len-1])) + len--; + namebuf[len] = '\0'; + } + else + len = strlen(namebuf); - buffer = xmalloc(authorlen + 1); - memcpy(buffer, author, authorlen); - buffer[authorlen] = '\0'; + if (log->email) { + size_t room = sizeof(namebuf) - len - 1; + int maillen = strlen(emailbuf); + snprintf(namebuf + len, room, " <%.*s>", maillen, emailbuf); + } - item = path_list_insert(buffer, list); + item = string_list_insert(namebuf, &log->list); if (item->util == NULL) - item->util = xcalloc(1, sizeof(struct path_list)); - else - free(buffer); + item->util = xcalloc(1, sizeof(struct string_list)); + /* Skip any leading whitespace, including any blank lines. */ + while (*oneline && isspace(*oneline)) + oneline++; + eol = strchr(oneline, '\n'); + if (!eol) + eol = oneline + strlen(oneline); if (!prefixcmp(oneline, "[PATCH")) { char *eob = strchr(oneline, ']'); - - if (eob) { - while (isspace(eob[1]) && eob[1] != '\n') - eob++; - if (eob - oneline < onelinelen) { - onelinelen -= eob - oneline; - oneline = eob; - } - } + if (eob && (!eol || eob < eol)) + oneline = eob + 1; } - - while (onelinelen > 0 && isspace(oneline[0])) { + while (*oneline && isspace(*oneline) && *oneline != '\n') oneline++; - onelinelen--; - } - - while (onelinelen > 0 && isspace(oneline[onelinelen - 1])) - onelinelen--; - - buffer = xmalloc(onelinelen + 1); - memcpy(buffer, oneline, onelinelen); - buffer[onelinelen] = '\0'; + format_subject(&subject, oneline, " "); + buffer = strbuf_detach(&subject, NULL); if (dot3) { int dot3len = strlen(dot3); @@ -85,137 +115,84 @@ static void insert_author_oneline(struct path_list *list, } } - onelines = item->util; - if (onelines->nr >= onelines->alloc) { - onelines->alloc = alloc_nr(onelines->nr); - onelines->items = xrealloc(onelines->items, - onelines->alloc - * sizeof(struct path_list_item)); - } - - onelines->items[onelines->nr].util = NULL; - onelines->items[onelines->nr++].path = buffer; + string_list_append(buffer, item->util); } -static void read_from_stdin(struct path_list *list) +static void read_from_stdin(struct shortlog *log) { - char buffer[1024]; - - while (fgets(buffer, sizeof(buffer), stdin) != NULL) { - char *bob; - if ((buffer[0] == 'A' || buffer[0] == 'a') && - !prefixcmp(buffer + 1, "uthor: ") && - (bob = strchr(buffer + 7, '<')) != NULL) { - char buffer2[1024], offset = 0; - - if (map_email(&mailmap, bob + 1, buffer, sizeof(buffer))) - bob = buffer + strlen(buffer); - else { - offset = 8; - while (buffer + offset < bob && - isspace(bob[-1])) - bob--; - } - - while (fgets(buffer2, sizeof(buffer2), stdin) && - buffer2[0] != '\n') - ; /* chomp input */ - if (fgets(buffer2, sizeof(buffer2), stdin)) { - int l2 = strlen(buffer2); - int i; - for (i = 0; i < l2; i++) - if (!isspace(buffer2[i])) - break; - insert_author_oneline(list, - buffer + offset, - bob - buffer - offset, - buffer2 + i, l2 - i); - } - } + char author[1024], oneline[1024]; + + while (fgets(author, sizeof(author), stdin) != NULL) { + if (!(author[0] == 'A' || author[0] == 'a') || + prefixcmp(author + 1, "uthor: ")) + continue; + while (fgets(oneline, sizeof(oneline), stdin) && + oneline[0] != '\n') + ; /* discard headers */ + while (fgets(oneline, sizeof(oneline), stdin) && + oneline[0] == '\n') + ; /* discard blanks */ + insert_one_record(log, author + 8, oneline); } } -static void get_from_rev(struct rev_info *rev, struct path_list *list) +void shortlog_add_commit(struct shortlog *log, struct commit *commit) { - char scratch[1024]; - struct commit *commit; + const char *author = NULL, *buffer; - prepare_revision_walk(rev); - while ((commit = get_revision(rev)) != NULL) { - const char *author = NULL, *oneline, *buffer; - int authorlen = authorlen, onelinelen; - - /* get author and oneline */ - for (buffer = commit->buffer; buffer && *buffer != '\0' && - *buffer != '\n'; ) { - const char *eol = strchr(buffer, '\n'); - - if (eol == NULL) - eol = buffer + strlen(buffer); - else - eol++; - - if (!prefixcmp(buffer, "author ")) { - char *bracket = strchr(buffer, '<'); - - if (bracket == NULL || bracket > eol) - die("Invalid commit buffer: %s", - sha1_to_hex(commit->object.sha1)); - - if (map_email(&mailmap, bracket + 1, scratch, - sizeof(scratch))) { - author = scratch; - authorlen = strlen(scratch); - } else { - if (bracket[-1] == ' ') - bracket--; - - author = buffer + 7; - authorlen = bracket - buffer - 7; - } - } - buffer = eol; - } - - if (author == NULL) - die ("Missing author: %s", - sha1_to_hex(commit->object.sha1)); + buffer = commit->buffer; + while (*buffer && *buffer != '\n') { + const char *eol = strchr(buffer, '\n'); - if (buffer == NULL || *buffer == '\0') { - oneline = "<none>"; - onelinelen = sizeof(oneline) + 1; - } else { - char *eol; - - oneline = buffer + 1; - eol = strchr(oneline, '\n'); - if (eol == NULL) - onelinelen = strlen(oneline); - else - onelinelen = eol - oneline; - } + if (eol == NULL) + eol = buffer + strlen(buffer); + else + eol++; - insert_author_oneline(list, - author, authorlen, oneline, onelinelen); + if (!prefixcmp(buffer, "author ")) + author = buffer + 7; + buffer = eol; } + if (!author) + die("Missing author: %s", + sha1_to_hex(commit->object.sha1)); + if (log->user_format) { + struct strbuf buf = STRBUF_INIT; + + pretty_print_commit(CMIT_FMT_USERFORMAT, commit, &buf, + DEFAULT_ABBREV, "", "", DATE_NORMAL, 0); + insert_one_record(log, author, buf.buf); + strbuf_release(&buf); + return; + } + if (*buffer) + buffer++; + insert_one_record(log, author, !*buffer ? "<none>" : buffer); +} +static void get_from_rev(struct rev_info *rev, struct shortlog *log) +{ + struct commit *commit; + + if (prepare_revision_walk(rev)) + die("revision walk setup failed"); + while ((commit = get_revision(rev)) != NULL) + shortlog_add_commit(log, commit); } -static int parse_uint(char const **arg, int comma) +static int parse_uint(char const **arg, int comma, int defval) { unsigned long ul; int ret; char *endp; ul = strtoul(*arg, &endp, 10); - if (endp != *arg && *endp && *endp != comma) + if (*endp && *endp != comma) return -1; - ret = (int) ul; - if (ret != ul) + if (ul > INT_MAX) return -1; - *arg = endp; - if (**arg) - (*arg)++; + ret = *arg == endp ? defval : (int)ul; + *arg = *endp ? endp + 1 : endp; return ret; } @@ -224,92 +201,122 @@ static const char wrap_arg_usage[] = "-w[<width>[,<indent1>[,<indent2>]]]"; #define DEFAULT_INDENT1 6 #define DEFAULT_INDENT2 9 -static void parse_wrap_args(const char *arg, int *in1, int *in2, int *wrap) +static int parse_wrap_args(const struct option *opt, const char *arg, int unset) +{ + struct shortlog *log = opt->value; + + log->wrap_lines = !unset; + if (unset) + return 0; + if (!arg) { + log->wrap = DEFAULT_WRAPLEN; + log->in1 = DEFAULT_INDENT1; + log->in2 = DEFAULT_INDENT2; + return 0; + } + + log->wrap = parse_uint(&arg, ',', DEFAULT_WRAPLEN); + log->in1 = parse_uint(&arg, ',', DEFAULT_INDENT1); + log->in2 = parse_uint(&arg, '\0', DEFAULT_INDENT2); + if (log->wrap < 0 || log->in1 < 0 || log->in2 < 0) + return error(wrap_arg_usage); + if (log->wrap && + ((log->in1 && log->wrap <= log->in1) || + (log->in2 && log->wrap <= log->in2))) + return error(wrap_arg_usage); + return 0; +} + +void shortlog_init(struct shortlog *log) { - arg += 2; /* skip -w */ - - *wrap = parse_uint(&arg, ','); - if (*wrap < 0) - die(wrap_arg_usage); - *in1 = parse_uint(&arg, ','); - if (*in1 < 0) - die(wrap_arg_usage); - *in2 = parse_uint(&arg, '\0'); - if (*in2 < 0) - die(wrap_arg_usage); - - if (!*wrap) - *wrap = DEFAULT_WRAPLEN; - if (!*in1) - *in1 = DEFAULT_INDENT1; - if (!*in2) - *in2 = DEFAULT_INDENT2; - if (*wrap && - ((*in1 && *wrap <= *in1) || - (*in2 && *wrap <= *in2))) - die(wrap_arg_usage); + memset(log, 0, sizeof(*log)); + + read_mailmap(&log->mailmap, &log->common_repo_prefix); + + log->list.strdup_strings = 1; + log->wrap = DEFAULT_WRAPLEN; + log->in1 = DEFAULT_INDENT1; + log->in2 = DEFAULT_INDENT2; } int cmd_shortlog(int argc, const char **argv, const char *prefix) { - struct rev_info rev; - struct path_list list = { NULL, 0, 0, 1 }; - int i, j, sort_by_number = 0, summary = 0; - int wrap_lines = 0; - int wrap = DEFAULT_WRAPLEN; - int in1 = DEFAULT_INDENT1; - int in2 = DEFAULT_INDENT2; - - /* since -n is a shadowed rev argument, parse our args first */ - while (argc > 1) { - if (!strcmp(argv[1], "-n") || !strcmp(argv[1], "--numbered")) - sort_by_number = 1; - else if (!strcmp(argv[1], "-s") || - !strcmp(argv[1], "--summary")) - summary = 1; - else if (!prefixcmp(argv[1], "-w")) { - wrap_lines = 1; - parse_wrap_args(argv[1], &in1, &in2, &wrap); + static struct shortlog log; + static struct rev_info rev; + int nongit; + + static const struct option options[] = { + OPT_BOOLEAN('n', "numbered", &log.sort_by_number, + "sort output according to the number of commits per author"), + OPT_BOOLEAN('s', "summary", &log.summary, + "Suppress commit descriptions, only provides commit count"), + OPT_BOOLEAN('e', "email", &log.email, + "Show the email address of each author"), + { OPTION_CALLBACK, 'w', NULL, &log, "w[,i1[,i2]]", + "Linewrap output", PARSE_OPT_OPTARG, &parse_wrap_args }, + OPT_END(), + }; + + struct parse_opt_ctx_t ctx; + + prefix = setup_git_directory_gently(&nongit); + git_config(git_default_config, NULL); + shortlog_init(&log); + init_revisions(&rev, prefix); + parse_options_start(&ctx, argc, argv, PARSE_OPT_KEEP_DASHDASH | + PARSE_OPT_KEEP_ARGV0); + + for (;;) { + switch (parse_options_step(&ctx, options, shortlog_usage)) { + case PARSE_OPT_HELP: + exit(129); + case PARSE_OPT_DONE: + goto parse_done; } - else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) - usage(shortlog_usage); - else - break; - argv++; - argc--; + parse_revision_opt(&rev, &ctx, options, shortlog_usage); + } +parse_done: + argc = parse_options_end(&ctx); + + if (setup_revisions(argc, argv, &rev, NULL) != 1) { + error("unrecognized argument: %s", argv[1]); + usage_with_options(shortlog_usage, options); } - init_revisions(&rev, prefix); - argc = setup_revisions(argc, argv, &rev, NULL); - if (argc > 1) - die ("unrecognized argument: %s", argv[1]); - read_mailmap(&mailmap, ".mailmap", &common_repo_prefix); + log.user_format = rev.commit_format == CMIT_FMT_USERFORMAT; + /* assume HEAD if from a tty */ + if (!nongit && !rev.pending.nr && isatty(0)) + add_head_to_pending(&rev); if (rev.pending.nr == 0) { - if (isatty(0)) - fprintf(stderr, "(reading log to summarize from standard input)\n"); - read_from_stdin(&list); + read_from_stdin(&log); } else - get_from_rev(&rev, &list); + get_from_rev(&rev, &log); - if (sort_by_number) - qsort(list.items, list.nr, sizeof(struct path_list_item), - compare_by_number); + shortlog_output(&log); + return 0; +} - for (i = 0; i < list.nr; i++) { - struct path_list *onelines = list.items[i].util; +void shortlog_output(struct shortlog *log) +{ + int i, j; + if (log->sort_by_number) + qsort(log->list.items, log->list.nr, sizeof(struct string_list_item), + compare_by_number); + for (i = 0; i < log->list.nr; i++) { + struct string_list *onelines = log->list.items[i].util; - if (summary) { - printf("%s: %d\n", list.items[i].path, onelines->nr); + if (log->summary) { + printf("%6d\t%s\n", onelines->nr, log->list.items[i].string); } else { - printf("%s (%d):\n", list.items[i].path, onelines->nr); + printf("%s (%d):\n", log->list.items[i].string, onelines->nr); for (j = onelines->nr - 1; j >= 0; j--) { - const char *msg = onelines->items[j].path; + const char *msg = onelines->items[j].string; - if (wrap_lines) { - int col = print_wrapped_text(msg, in1, in2, wrap); - if (col != wrap) + if (log->wrap_lines) { + int col = print_wrapped_text(msg, log->in1, log->in2, log->wrap); + if (col != log->wrap) putchar('\n'); } else @@ -318,16 +325,13 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) putchar('\n'); } - onelines->strdup_paths = 1; - path_list_clear(onelines, 1); + onelines->strdup_strings = 1; + string_list_clear(onelines, 0); free(onelines); - list.items[i].util = NULL; + log->list.items[i].util = NULL; } - list.strdup_paths = 1; - path_list_clear(&list, 1); - mailmap.strdup_paths = 1; - path_list_clear(&mailmap, 1); - - return 0; + log->list.strdup_strings = 1; + string_list_clear(&log->list, 1); + clear_mailmap(&log->mailmap); } |