This implements an experimental "git log-fpc" command that shows short-log style output sorted by topics. A "topic" is identified by going through the first-parent chains; this ignores the fast-forward case, but for a top-level integrator it often is good enough. For example, if the commit ancestry graph looks like this: x---x---x---X---o---*---o---o---o HEAD \ / o---o---o---o---o and the command line asks for git log-fpc --no-merges X.. It first finds all the commits 'o'. Then it emits the four commits on the upper line (assume the merge '*' has the commit that is a child of X as its first parent in the picture). When it does so, it the list of authors for these four commits on one line, followed by the title of these commits. After that, it does the same for the five commits on the lower line. --- I initially wanted to do this inside Johannes's enhanced shortlog, but ended up doing this as a pretty much independent thing, because the shortlog implementation stringifies the information from the commits too early to be easily enhanced for this purpose. If this turns out to be a better way to present shortlog, however, this should become an option to git-shortlog. A sample output from: git log-fpc --no-merges v1.4.4.1..f64d7fd2 looks like this (f64d7fd2 was the tip of master when the last "What's in" message was sent out). It shows that many "fixes" and git-svn enhancements were directly done on "master" (that is the first group), while many gitweb enhancements, changing the output from "prune -n", "git branch" enhancements, etc. were first cooked in separate topic branches and then later merged into 'master'. To this output, I can manually add a topic title to the beginning of each group and it would make a better overview than what I currently send out in "What's in" message which is generated with shortlog. ---------------------------------------------------------------- Eric Wong (6), Junio C Hamano (5), Lars Hjemli, Jakub Narebski, Iñaki Arenaza, Petr Baudis, Andy Parkins, and René Scharfe git-fetch: exit with non-zero status when fast-forward check fails git-svn: exit with status 1 for test failures git-svn: correctly access repos when only given partial read permissions git-branch -D: make it work even when on a yet-to-be-born branch Add -v and --abbrev options to git-branch git-clone: stop dumb protocol from copying refs outside heads/ and tags/. gitweb: (style) use chomp without parentheses consistently. gitweb: Replace SPC with also in tag comment git-svn: handle authentication without relying on cached tokens on disk git-cvsimport: add support for CVS pserver method HTTP/1.x proxying Make git-clone --use-separate-remote the default refs outside refs/{heads,tags} match less strongly. Increase length of function name buffer git-svn: preserve uncommitted changes after dcommit git-svn: correctly handle revision 0 in SVN repositories git-svn: error out from dcommit on a parent-less commit archive-zip: don't use sizeof(struct ...) Junio C Hamano and Andy Parkins Typefix builtin-prune.c::prune_object() Improve git-prune -n output Peter Baumann config option log.showroot to show the diff of root commits Andy Parkins Add support to git-branch to show local and remote branches Jakub Narebski (7) gitweb: Finish restoring "blob" links in git_difftree_body gitweb: Refactor feed generation, make output prettier, add Atom feed gitweb: Add an option to href() to return full URL gitweb: New improved formatting of chunk header in diff gitweb: Default to $hash_base or HEAD for $hash in "commit" and "commitdiff" gitweb: Buffer diff header to deal with split patches + git_patchset_body refactoring gitweb: Protect against possible warning in git_commitdiff ---------------------------------------------------------------- builtin-log.c | 177 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ builtin.h | 1 + git.c | 1 + 3 files changed, 179 insertions(+), 0 deletions(-) diff --git a/builtin-log.c b/builtin-log.c index 7acf5d3..1c2838c 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -99,6 +99,183 @@ int cmd_log(int argc, const char **argv, const char *prefix) return cmd_log_walk(&rev); } +/* bits #0..7 in revision.h, #8..11 in commit.c */ +#define FPC_RESULT (1u<<12) +#define FPC_SHOWN (1u<<13) + +struct author_record { + char *name; + int count; +}; +struct author_count { + int nr, alloc; + struct author_record **au; +}; + +static int cmp_count(const void *a_, const void *b_) +{ + struct author_record **a = (struct author_record **) a_; + struct author_record **b = (struct author_record **) b_; + return (*b)->count - (*a)->count; +} + +static void add_author(struct commit *c, struct author_count *ac) +{ + const char *buf = c->buffer; + char *au = strstr(buf, "\nauthor "); + char *eon; + struct author_record *ar; + int i; + + if (!au) + return; /* oops */ + au += 7; + while (*au && isspace(*au)) + au++; + if (!*au) + return; /* oops */ + eon = strchr(au, '<'); + if (!eon) + return; /* oops */ + while (au < --eon && isspace(*eon)) + ; /* back back back... */ + eon++; + for (i = 0; i < ac->nr; i++) + if (!strncmp(ac->au[i]->name, au, eon-au) && + strlen(ac->au[i]->name) == eon - au) { + /* found it */ + ac->au[i]->count++; + return; + } + if (ac->alloc <= ac->nr) { + ac->alloc = alloc_nr(ac->alloc); + ac->au = xrealloc(ac->au, sizeof(struct author_record *) * + ac->alloc); + } + ar = xcalloc(1, sizeof(struct author_record)); + ar->name = xmalloc(eon - au + 1); + memcpy(ar->name, au, eon - au); + ar->name[eon - au] = 0; + ar->count = 1; + ac->au[ac->nr++] = ar; +} + +static void show_fpc(struct object_array *list) +{ + int i; + struct author_count ac; + + if (!list->nr) + return; + memset(&ac, 0, sizeof(ac)); + for (i = 0; i < list->nr; i++) + add_author((struct commit *) list->objects[i].item, &ac); + qsort(ac.au, ac.nr, sizeof(struct author_record *), cmp_count); + + for (i = 0; i < ac.nr; i++) { + if (i) { + if (i < ac.nr - 1) + fputs(", ", stdout); + else if (ac.nr != 2) + fputs(", and ", stdout); + else + fputs(" and ", stdout); + } + if (ac.au[i]->count < 2) + printf("%s", ac.au[i]->name); + else + printf("%s (%d)", ac.au[i]->name, ac.au[i]->count); + free(ac.au[i]->name); + free(ac.au[i]); + } + free(ac.au); + putchar('\n'); + + for (i = 0; i < list->nr; i++) { + struct commit *c = (struct commit *) list->objects[i].item; + char *buf = c->buffer; + char *it = "<unnamed>"; + int len = strlen(it); + buf = strstr(buf, "\n\n"); + if (buf) { + char *lineend; + while (*buf && isspace(*buf)) + buf++; + if (!*buf) + goto emit; + lineend = strchr(buf, '\n'); + if (!lineend) + goto emit; + while (buf < lineend && isspace(*lineend)) + lineend--; + len = lineend - buf + 1; + it = buf; + } + emit: + printf(" %.*s\n", len, it); + } + putchar('\n'); +} + +int cmd_log_fpc(int argc, const char **argv, const char *prefix) +{ + struct rev_info rev; + struct commit *c; + struct object_array result = { 0, 0, NULL }; + int i; + + git_config(git_log_config); + init_revisions(&rev, prefix); + rev.always_show_header = 1; + cmd_log_init(argc, argv, prefix, &rev); + + prepare_revision_walk(&rev); + while ((c = get_revision(&rev)) != NULL) + add_object_array(&(c->object), NULL, &result); + + /* clear flags and mark them "relevant" */ + for (i = 0; i < result.nr; i++) + result.objects[i].item->flags |= FPC_RESULT; + + for (;;) { + struct object_array current; + + for (i = 0; i < result.nr; i++) { + if (!(result.objects[i].item->flags & FPC_SHOWN)) + break; + } + if (i >= result.nr) + break; + + memset(¤t, 0, sizeof(current)); + c = (struct commit *) result.objects[i].item; + while (c) { + int flags = c->object.flags; + + if ((flags & (FPC_RESULT|FPC_SHOWN)) == FPC_RESULT) { + add_object_array(&(c->object), NULL, ¤t); + c->object.flags |= FPC_SHOWN; + } + if (!c->object.parsed) + parse_object(c->object.sha1); + if (!c->parents) + break; + c = c->parents->item; + } + + /* Finally, show the series. */ + show_fpc(¤t); + } + + /* free them */ + for (i = 0; i < result.nr; i++) { + c = (struct commit *) result.objects[i].item; + free(c->buffer); + free_commit_list(c->parents); + } + return 0; +} + static int istitlechar(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || diff --git a/builtin.h b/builtin.h index 43fed32..a94540d 100644 --- a/builtin.h +++ b/builtin.h @@ -38,6 +38,7 @@ extern int cmd_grep(int argc, const char **argv, const char *prefix); extern int cmd_help(int argc, const char **argv, const char *prefix); extern int cmd_init_db(int argc, const char **argv, const char *prefix); extern int cmd_log(int argc, const char **argv, const char *prefix); +extern int cmd_log_fpc(int argc, const char **argv, const char *prefix); extern int cmd_ls_files(int argc, const char **argv, const char *prefix); extern int cmd_ls_tree(int argc, const char **argv, const char *prefix); extern int cmd_mailinfo(int argc, const char **argv, const char *prefix); diff --git a/git.c b/git.c index 1aa07a5..65d98bd 100644 --- a/git.c +++ b/git.c @@ -243,6 +243,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "help", cmd_help }, { "init-db", cmd_init_db }, { "log", cmd_log, RUN_SETUP | USE_PAGER }, + { "log-fpc", cmd_log_fpc, RUN_SETUP | USE_PAGER }, { "ls-files", cmd_ls_files, RUN_SETUP }, { "ls-tree", cmd_ls_tree, RUN_SETUP }, { "mailinfo", cmd_mailinfo }, -- 1.4.4.1.ge3fb - To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html