From: Jeff King <hidden> Date: 2016-06-15 23:07:46
These are split out from my the shortlog-trailer series I sent a few
weeks ago. The "trailer" parts still need some re-working, but there is
no need to hold these fixes hostage.
I also dropped the early part of the series, adding skip_prefix_icase().
After digging into the history, I ended up reworking the first patch
here to do a more thorough parse, so we don't need it anymore.
Thanks to Eric Sunshine for review on the first iteration; this
incorporates his comments.
[1/6]: shortlog: match both "Author:" and "author" on stdin
[2/6]: shortlog: use strbufs to read from stdin
[3/6]: shortlog: replace hand-parsing of author with pretty-printer
[4/6]: shortlog: optimize "--summary" mode
[5/6]: shortlog: optimize out useless "<none>" normalization
[6/6]: shortlog: optimize out useless string list
[1] http://thread.gmane.org/gmane.comp.version-control.git/283091
From: Jeff King <hidden> Date: 2016-06-15 23:07:46
The original git-shortlog could read both the normal "git
log" output as well as "git log --format=raw". However, when it was
converted to C by b8ec592 (Build in shortlog, 2006-10-22),
the trailing colon became mandatory, and we no longer
matched the raw output.
Given the amount of intervening time without any bug
reports, it's probable that nobody cares. But given that
it's easy to fix, and that the end result is hopefully more
obvious and flexible (it could now easily accomodate matching
"Committer"), let's just make it work.
Signed-off-by: Jeff King <redacted>
---
Another option would be to assume that nobody cares about
"--format=raw" and just do:
if (!skip_prefix(author, "Author: ", &v))
continue;
That technically breaks somebody who was feeding shortlog output that
contains "author: ", but since Git itself doesn't generate that, it
seems rather unlikely.
builtin/shortlog.c | 27 ++++++++++++++++++++++++---
t/t4201-shortlog.sh | 6 ++++++
2 files changed, 30 insertions(+), 3 deletions(-)
@@ -120,6 +120,12 @@ test_expect_success !MINGW 'shortlog from non-git directory' 'test_cmpexpectout'+test_expect_success!MINGW'shortlog can read --format=raw output''+gitlog--format=rawHEAD>log&&+GIT_DIR=non-existinggitshortlog-w<log>out&&+test_cmpexpectout+'+ test_expect_success'shortlog should add newline when input line matches wraplen''cat>expect<<\EOF&& AUThor(2):
From: Jeff King <hidden> Date: 2016-06-15 23:07:46
We currently use fixed-size buffers with fgets(), which
could lead to incorrect results in the unlikely event that a
line had something like "Author:" at exactly its 1024th
character.
But it's easy to convert this to a strbuf, and because we
can reuse the same buffer through the loop, we don't even
pay the extra allocation cost.
Signed-off-by: Jeff King <redacted>
---
builtin/shortlog.c | 19 +++++++++++--------
1 file changed, 11 insertions(+), 8 deletions(-)
From: Jeff King <hidden> Date: 2016-06-15 23:07:46
When gathering the author and oneline subject for each
commit, we hand-parse the commit headers to find the
"author" line, and then continue past to the blank line at
the end of the header.
We can replace this tricky hand-parsing by simply asking the
pretty-printer for the relevant items. This also decouples
the author and oneline parsing, opening up some new
optimizations in further commits.
One reason to avoid the pretty-printer is that it might be
less efficient than hand-parsing. However, I measured no
slowdown at all running "git shortlog -ns HEAD" on
linux.git.
As a bonus, we also fix a memory leak in the (uncommon) case
that the author field is blank.
Signed-off-by: Jeff King <redacted>
---
builtin/shortlog.c | 62 +++++++++++++++++++++++-------------------------------
1 file changed, 26 insertions(+), 36 deletions(-)
@@ -133,45 +133,35 @@ static void read_from_stdin(struct shortlog *log)voidshortlog_add_commit(structshortlog*log,structcommit*commit){-constchar*author=NULL,*buffer;-structstrbufbuf=STRBUF_INIT;-structstrbufufbuf=STRBUF_INIT;--pp_commit_easy(CMIT_FMT_RAW,commit,&buf);-buffer=buf.buf;-while(*buffer&&*buffer!='\n'){-constchar*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){+structstrbufauthor=STRBUF_INIT;+structstrbufoneline=STRBUF_INIT;+structpretty_print_contextctx={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);+/* we can detect a total failure only by seeing " <>" in the output */+if(author.len<=3){warning(_("Missing author: %s"),oid_to_hex(&commit->object.oid));-return;-}-if(log->user_format){-structpretty_print_contextctx={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;-}elseif(*buffer){-buffer++;+gotoout;}-insert_one_record(log,author,!*buffer?"<none>":buffer);-strbuf_release(&ufbuf);-strbuf_release(&buf);++if(log->user_format)+pretty_print_commit(&ctx,commit,&oneline);+else+format_commit_message(commit,"%s",&oneline,&ctx);++insert_one_record(log,author.buf,oneline.len?oneline.buf:"<none>");++out:+strbuf_release(&author);+strbuf_release(&oneline);}staticvoidget_from_rev(structrev_info*rev,structshortlog*log)
From: Jeff King <hidden> Date: 2016-06-15 23:07:46
If the user asked us only to show counts for each author,
rather than the individual summary lines, then there is no
point in us generating the summaries only to throw them
away. With this patch, I measured the following speedup for
"git shortlog -ns HEAD" on linux.git (best-of-five):
[before]
real 0m5.644s
user 0m5.472s
sys 0m0.176s
[after]
real 0m5.257s
user 0m5.104s
sys 0m0.156s
That's only ~7%, but it's so easy to do, there's no good
reason not to. We don't have to touch any downstream code,
since we already fill in the magic string "<none>" to handle
commits without a message.
Signed-off-by: Jeff King <redacted>
---
builtin/shortlog.c | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
From: Jeff King <hidden> Date: 2016-06-15 23:07:46
If we are in --summary mode, we will always pass <none> to
insert_one_record, which will then do some normalization
(e.g., cutting out "[PATCH]"). There's no point in doing so
if we aren't going to use the result anyway.
This drops my best-of-five for "git shortlog -ns HEAD" on
linux.git from:
real 0m5.257s
user 0m5.104s
sys 0m0.156s
to:
real 0m5.194s
user 0m5.028s
sys 0m0.168s
That's only 1%, but arguably the result is clearer to read,
as we're able to group our variable declarations inside the
conditional block. It also opens up further optimization
possibilities for future patches.
Signed-off-by: Jeff King <redacted>
---
builtin/shortlog.c | 63 +++++++++++++++++++++++++++++-------------------------
1 file changed, 34 insertions(+), 29 deletions(-)
@@ -59,34 +55,43 @@ static void insert_one_record(struct shortlog *log,if(item->util==NULL)item->util=xcalloc(1,sizeof(structstring_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){-intdot3len=strlen(dot3);-if(dot3len>5){-while((p=strstr(buffer,dot3))!=NULL){-inttaillen=strlen(p)-dot3len;-memcpy(p,"/.../",5);-memmove(p+5,p+dot3len,taillen+1);+if(log->summary)+string_list_append(item->util,xstrdup(""));+else{+constchar*dot3=log->common_repo_prefix;+char*buffer,*p;+structstrbufsubject=STRBUF_INIT;+constchar*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){+intdot3len=strlen(dot3);+if(dot3len>5){+while((p=strstr(buffer,dot3))!=NULL){+inttaillen=strlen(p)-dot3len;+memcpy(p,"/.../",5);+memmove(p+5,p+dot3len,taillen+1);+}}}-}-string_list_append(item->util,buffer);+string_list_append(item->util,buffer);+}}/*
From: Jeff King <hidden> Date: 2016-06-15 23:07:46
If we are in "--summary" mode, then we do not care about the
actual list of subject onelines associated with each author.
We care only about the number. So rather than store a
string-list for each author full of "<none>", let's just
keep a count.
This drops my best-of-five for "git shortlog -ns HEAD" on
linux.git from:
real 0m5.194s
user 0m5.028s
sys 0m0.168s
to:
real 0m5.057s
user 0m4.916s
sys 0m0.144s
That's about 2.5%.
Signed-off-by: Jeff King <redacted>
---
builtin/shortlog.c | 43 +++++++++++++++++++++++++++++++------------
1 file changed, 31 insertions(+), 12 deletions(-)
From: Eric Sunshine <hidden> Date: 2016-06-15 23:07:47
On Friday, January 15, 2016, Jeff King [off-list ref] wrote:
quoted hunk
The original git-shortlog could read both the normal "git
log" output as well as "git log --format=raw". However, when it was
converted to C by b8ec592 (Build in shortlog, 2006-10-22),
the trailing colon became mandatory, and we no longer
matched the raw output.
Given the amount of intervening time without any bug
reports, it's probable that nobody cares. But given that
it's easy to fix, and that the end result is hopefully more
obvious and flexible (it could now easily accomodate matching
"Committer"), let's just make it work.
Signed-off-by: Jeff King <redacted>
---
@@ -89,13 +89,34 @@ static void insert_one_record(struct shortlog *log,+/*+ * If header is "author", match candidate against the regex /[Aa]uthor:? /,+ * and return a pointer to the remainder of the string in out_value.+ */+static int match_ident_header(const char *candidate, const char *header,+ const char **out_value)+{+ const char *v;++ if (tolower(*candidate++) != tolower(*header++))+ return 0;
Presumably, this will never be invoked as match_ident_header("", "",
...) so we don't have to worry about it accessing beyond end-of-string
when it gets past this conditional. Does it deserve an
assert(*candidate) at the top of the function, though, or is that
overkill?
From: Jeff King <hidden> Date: 2016-06-15 23:07:48
On Fri, Jan 15, 2016 at 12:08:23PM -0500, Jeff King wrote:
The original git-shortlog could read both the normal "git
log" output as well as "git log --format=raw". However, when it was
converted to C by b8ec592 (Build in shortlog, 2006-10-22),
the trailing colon became mandatory, and we no longer
matched the raw output.
Given the amount of intervening time without any bug
reports, it's probable that nobody cares. But given that
it's easy to fix, and that the end result is hopefully more
obvious and flexible (it could now easily accomodate matching
"Committer"), let's just make it work.
I rebased the rest of my shortlog-trailer series on this, and sadly,
this final sentence isn't quite true.
The regular "git log" output uses "Commit:" for the committer line, and
the raw output uses "committer". So the match_ident_header function
_can't_ be reused.
So it's not wrong, but it's perhaps more complicated than it needs to
be. We could scrap this patch in favor of just:
if (!skip_prefix(author, "Author: ", &v) &&
!skip_prefix(author, "author ", &v))
continue;
That is technically more strict (it does not take "author: ", which is
accepted by the current code), but matches "git log" and "git log --raw"
output, and misses nothing that git has ever generated. And it extends
naturally to:
if (!skip_prefix(author, "Commit: ", &v) &&
!skip_prefix(author, "committer ", &v))
continue;
-Peff
From: Jeff King <hidden> Date: 2016-06-15 23:07:48
On Fri, Jan 15, 2016 at 06:19:30PM -0500, Eric Sunshine wrote:
quoted
+/*
+ * If header is "author", match candidate against the regex /[Aa]uthor:? /,
+ * and return a pointer to the remainder of the string in out_value.
+ */
+static int match_ident_header(const char *candidate, const char *header,
+ const char **out_value)
+{
+ const char *v;
+
+ if (tolower(*candidate++) != tolower(*header++))
+ return 0;
Presumably, this will never be invoked as match_ident_header("", "",
...) so we don't have to worry about it accessing beyond end-of-string
when it gets past this conditional. Does it deserve an
assert(*candidate) at the top of the function, though, or is that
overkill?
Good point. It shouldn't happen (we will always feed a string literal),
but it never hurts to document assumptions with an assertion.
However, there is some reason to think this isn't the ideal function;
see the message I just posted elsewhere in the thread.
-Peff