git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH] (experimental) per-topic shortlog.

From
Junio C Hamano <junkio@cox.net>
Date
Nov 27, 2006, 00:44 UTC
Message-ID
<7v8xhxsopp.fsf@assigned-by-dhcp.cox.net>

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 &nbsp; 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(&current, 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, &current);
+				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(&current);
+	}
+
+	/* 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
Next: Linus Torvalds
Message 1 of 18 in “(experimental) per-topic shortlog.”
  1. (experimental) per-topic shortlog.Junio C Hamano, Nov 27, 2006
  2. Linus TorvaldsNov 27, 2006
  3. Junio C HamanoNov 27, 2006
  4. Linus TorvaldsNov 27, 2006
  5. Junio C HamanoNov 27, 2006
  6. Linus TorvaldsNov 27, 2006
  7. Junio C HamanoNov 27, 2006
  8. Linus TorvaldsNov 27, 2006
  9. Johannes SchindelinNov 27, 2006
  10. Junio C HamanoNov 28, 2006
  11. Jeff KingNov 28, 2006
  12. Johannes SchindelinNov 28, 2006
  13. Jeff KingNov 28, 2006
  14. Junio C HamanoNov 29, 2006
  15. Jeff KingDec 1, 2006
  16. Junio C HamanoDec 1, 2006
  17. Junio C HamanoDec 1, 2006
  18. Jeff KingDec 1, 2006

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.