Merge branch 'jk/shortlog'

"git shortlog" used to accumulate various pieces of information
regardless of what was asked to be shown in the final output.  It
has been optimized by noticing what need not to be collected
(e.g. there is no need to collect the log messages when showing
only the number of changes).

* jk/shortlog:
  shortlog: don't warn on empty author
  shortlog: optimize out useless string list
  shortlog: optimize out useless "<none>" normalization
  shortlog: optimize "--summary" mode
  shortlog: replace hand-parsing of author with pretty-printer
  shortlog: use strbufs to read from stdin
  shortlog: match both "Author:" and "author" on stdin
This commit is contained in:
Junio C Hamano 2016-01-28 16:10:14 -08:00
commit a1c5405a52
2 changed files with 101 additions and 99 deletions

View File

@ -14,7 +14,26 @@ static char const * const shortlog_usage[] = {
NULL
};
static int compare_by_number(const void *a1, const void *a2)
/*
* The util field of our string_list_items will contain one of two things:
*
* - if --summary is not in use, it will point to a string list of the
* oneline subjects assigned to this author
*
* - if --summary is in use, we don't need that list; we only need to know
* its size. So we abuse the pointer slot to store our integer counter.
*
* This macro accesses the latter.
*/
#define UTIL_TO_INT(x) ((intptr_t)(x)->util)
static int compare_by_counter(const void *a1, const void *a2)
{
const struct string_list_item *i1 = a1, *i2 = a2;
return UTIL_TO_INT(i2) - UTIL_TO_INT(i1);
}
static int compare_by_list(const void *a1, const void *a2)
{
const struct string_list_item *i1 = a1, *i2 = a2;
const struct string_list *l1 = i1->util, *l2 = i2->util;
@ -31,13 +50,9 @@ static void insert_one_record(struct shortlog *log,
const char *author,
const char *oneline)
{
const char *dot3 = log->common_repo_prefix;
char *buffer, *p;
struct string_list_item *item;
const char *mailbuf, *namebuf;
size_t namelen, maillen;
const char *eol;
struct strbuf subject = STRBUF_INIT;
struct strbuf namemailbuf = STRBUF_INIT;
struct ident_split ident;
@ -56,8 +71,14 @@ static void insert_one_record(struct shortlog *log,
strbuf_addf(&namemailbuf, " <%.*s>", (int)maillen, mailbuf);
item = string_list_insert(&log->list, namemailbuf.buf);
if (item->util == NULL)
item->util = xcalloc(1, sizeof(struct string_list));
if (log->summary)
item->util = (void *)(UTIL_TO_INT(item) + 1);
else {
const char *dot3 = log->common_repo_prefix;
char *buffer, *p;
struct strbuf subject = STRBUF_INIT;
const char *eol;
/* Skip any leading whitespace, including any blank lines. */
while (*oneline && isspace(*oneline))
@ -86,68 +107,59 @@ static void insert_one_record(struct shortlog *log,
}
}
if (item->util == NULL)
item->util = xcalloc(1, sizeof(struct string_list));
string_list_append(item->util, buffer);
}
}
static void read_from_stdin(struct shortlog *log)
{
char author[1024], oneline[1024];
struct strbuf author = STRBUF_INIT;
struct strbuf oneline = STRBUF_INIT;
while (fgets(author, sizeof(author), stdin) != NULL) {
if (!(author[0] == 'A' || author[0] == 'a') ||
!starts_with(author + 1, "uthor: "))
while (strbuf_getline_lf(&author, stdin) != EOF) {
const char *v;
if (!skip_prefix(author.buf, "Author: ", &v) &&
!skip_prefix(author.buf, "author ", &v))
continue;
while (fgets(oneline, sizeof(oneline), stdin) &&
oneline[0] != '\n')
while (strbuf_getline_lf(&oneline, stdin) != EOF &&
oneline.len)
; /* discard headers */
while (fgets(oneline, sizeof(oneline), stdin) &&
oneline[0] == '\n')
while (strbuf_getline_lf(&oneline, stdin) != EOF &&
!oneline.len)
; /* discard blanks */
insert_one_record(log, author + 8, oneline);
insert_one_record(log, v, oneline.buf);
}
strbuf_release(&author);
strbuf_release(&oneline);
}
void shortlog_add_commit(struct shortlog *log, struct commit *commit)
{
const char *author = NULL, *buffer;
struct strbuf buf = STRBUF_INIT;
struct strbuf ufbuf = STRBUF_INIT;
pp_commit_easy(CMIT_FMT_RAW, commit, &buf);
buffer = buf.buf;
while (*buffer && *buffer != '\n') {
const char *eol = strchr(buffer, '\n');
if (eol == NULL)
eol = buffer + strlen(buffer);
else
eol++;
if (starts_with(buffer, "author "))
author = buffer + 7;
buffer = eol;
}
if (!author) {
warning(_("Missing author: %s"),
oid_to_hex(&commit->object.oid));
return;
}
if (log->user_format) {
struct strbuf author = STRBUF_INIT;
struct strbuf oneline = STRBUF_INIT;
struct pretty_print_context ctx = {0};
ctx.fmt = CMIT_FMT_USERFORMAT;
ctx.abbrev = log->abbrev;
ctx.subject = "";
ctx.after_subject = "";
ctx.date_mode.type = DATE_NORMAL;
ctx.output_encoding = get_log_output_encoding();
pretty_print_commit(&ctx, commit, &ufbuf);
buffer = ufbuf.buf;
} else if (*buffer) {
buffer++;
format_commit_message(commit, "%an <%ae>", &author, &ctx);
if (!log->summary) {
if (log->user_format)
pretty_print_commit(&ctx, commit, &oneline);
else
format_commit_message(commit, "%s", &oneline, &ctx);
}
insert_one_record(log, author, !*buffer ? "<none>" : buffer);
strbuf_release(&ufbuf);
strbuf_release(&buf);
insert_one_record(log, author.buf, oneline.len ? oneline.buf : "<none>");
strbuf_release(&author);
strbuf_release(&oneline);
}
static void get_from_rev(struct rev_info *rev, struct shortlog *log)
@ -294,14 +306,14 @@ void shortlog_output(struct shortlog *log)
if (log->sort_by_number)
qsort(log->list.items, log->list.nr, sizeof(struct string_list_item),
compare_by_number);
log->summary ? compare_by_counter : compare_by_list);
for (i = 0; i < log->list.nr; i++) {
struct string_list *onelines = log->list.items[i].util;
const struct string_list_item *item = &log->list.items[i];
if (log->summary) {
printf("%6d\t%s\n", onelines->nr, log->list.items[i].string);
printf("%6d\t%s\n", (int)UTIL_TO_INT(item), item->string);
} else {
printf("%s (%d):\n", log->list.items[i].string, onelines->nr);
struct string_list *onelines = item->util;
printf("%s (%d):\n", item->string, onelines->nr);
for (j = onelines->nr - 1; j >= 0; j--) {
const char *msg = onelines->items[j].string;
@ -314,11 +326,11 @@ void shortlog_output(struct shortlog *log)
printf(" %s\n", msg);
}
putchar('\n');
}
onelines->strdup_strings = 1;
string_list_clear(onelines, 0);
free(onelines);
}
log->list.items[i].util = NULL;
}

View File

@ -120,6 +120,12 @@ test_expect_success !MINGW 'shortlog from non-git directory' '
test_cmp expect out
'
test_expect_success !MINGW 'shortlog can read --format=raw output' '
git log --format=raw HEAD >log &&
GIT_DIR=non-existing git shortlog -w <log >out &&
test_cmp expect out
'
test_expect_success 'shortlog should add newline when input line matches wraplen' '
cat >expect <<\EOF &&
A U Thor (2):
@ -172,22 +178,6 @@ test_expect_success !MINGW 'shortlog encoding' '
git shortlog HEAD~2.. > out &&
test_cmp expect out'
test_expect_success 'shortlog ignores commits with missing authors' '
git commit --allow-empty -m normal &&
git commit --allow-empty -m soon-to-be-broken &&
git cat-file commit HEAD >commit.tmp &&
sed "/^author/d" commit.tmp >broken.tmp &&
commit=$(git hash-object -w -t commit --stdin <broken.tmp) &&
git update-ref HEAD $commit &&
cat >expect <<-\EOF &&
A U Thor (1):
normal
EOF
git shortlog HEAD~2.. >actual &&
test_cmp expect actual
'
test_expect_success 'shortlog with revision pseudo options' '
git shortlog --all &&
git shortlog --branches &&