Skip to content

Commit

Permalink
Merge branch 'jk/shortlog'
Browse files Browse the repository at this point in the history
"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
  • Loading branch information
Junio C Hamano committed Jan 29, 2016
2 parents b62624b + d6b16ce commit a1c5405
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 103 deletions.
186 changes: 99 additions & 87 deletions builtin/shortlog.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -56,98 +71,95 @@ 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));

/* Skip any leading whitespace, including any blank lines. */
while (*oneline && isspace(*oneline))
oneline++;
eol = strchr(oneline, '\n');
if (!eol)
eol = oneline + strlen(oneline);
if (starts_with(oneline, "[PATCH")) {
char *eob = strchr(oneline, ']');
if (eob && (!eol || eob < eol))
oneline = eob + 1;
}
while (*oneline && isspace(*oneline) && *oneline != '\n')
oneline++;
format_subject(&subject, oneline, " ");
buffer = strbuf_detach(&subject, NULL);

if (dot3) {
int dot3len = strlen(dot3);
if (dot3len > 5) {
while ((p = strstr(buffer, dot3)) != NULL) {
int taillen = strlen(p) - dot3len;
memcpy(p, "/.../", 5);
memmove(p + 5, p + dot3len, taillen + 1);

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))
oneline++;
eol = strchr(oneline, '\n');
if (!eol)
eol = oneline + strlen(oneline);
if (starts_with(oneline, "[PATCH")) {
char *eob = strchr(oneline, ']');
if (eob && (!eol || eob < eol))
oneline = eob + 1;
}
while (*oneline && isspace(*oneline) && *oneline != '\n')
oneline++;
format_subject(&subject, oneline, " ");
buffer = strbuf_detach(&subject, NULL);

if (dot3) {
int dot3len = strlen(dot3);
if (dot3len > 5) {
while ((p = strstr(buffer, dot3)) != NULL) {
int taillen = strlen(p) - dot3len;
memcpy(p, "/.../", 5);
memmove(p + 5, p + dot3len, taillen + 1);
}
}
}
}

string_list_append(item->util, buffer);
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);
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();

format_commit_message(commit, "%an <%ae>", &author, &ctx);
if (!log->summary) {
if (log->user_format)
pretty_print_commit(&ctx, commit, &oneline);
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;
format_commit_message(commit, "%s", &oneline, &ctx);
}
if (log->user_format) {
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++;
}
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)
Expand Down Expand Up @@ -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;

Expand All @@ -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);
}

onelines->strdup_strings = 1;
string_list_clear(onelines, 0);
free(onelines);
log->list.items[i].util = NULL;
}

Expand Down
22 changes: 6 additions & 16 deletions t/t4201-shortlog.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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 &&
Expand Down

0 comments on commit a1c5405

Please sign in to comment.