Subject: [PATCH] Support author and commiter based revision list limiting

From: Jonas Fonseca <[email-masked]>
Date: Fri, 1 Sep 2006 00:57:14 +0200

Adds the two options: --author=string and --committer=string, which can
be used to limit the set of interesting commits to the ones matching the
given idents.

Signed-off-by: Jonas Fonseca <[email-masked]>
---

[ On top of the git-rev-list(1) update posted earlier ... ]

Linus Torvalds <[email-masked]> wrote Mon, Aug 28, 2006:
> Th[e] trivial approach doesn't allow "gitk" to show the results sanely, 
> though (to do that, you'd need to make the commit matching be part of the 
> parent simplification instead - that would be extra bonus points for the 
> intrpid git hacker-wannabe)

Hereby serving one patch doing the trivial thing very stupid, since I
didn't have the imagination to go and hunt for the bonus points just
yet.

 Documentation/git-rev-list.txt |    6 +++++
 builtin-rev-list.c             |    3 ++
 revision.c                     |   53 ++++++++++++++++++++++++++++++++++++++++
 revision.h                     |    2 ++
 4 files changed, 63 insertions(+), 1 deletions(-)

diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt
index d6c86db..4d5f5ab 100644
--- a/Documentation/git-rev-list.txt
+++ b/Documentation/git-rev-list.txt
@@ -12,6 +12,8 @@ SYNOPSIS
 'git-rev-list' [ \--max-count=number ]
 	     [ \--max-age=timestamp ]
 	     [ \--min-age=timestamp ]
+	     [ \--author=string ]
+	     [ \--committer=string ]
 	     [ \--sparse ]
 	     [ \--no-merges ]
 	     [ \--remove-empty ]
@@ -153,6 +155,10 @@ limiting may be applied.
 
 	Limit the commits output to specified time range.
 
+--author='string', --committer='string'::
+
+	Limit the commits output to specified author and/or committer.
+
 --remove-empty::
 
 	Stop when a given path disappears from the tree.
diff --git a/builtin-rev-list.c b/builtin-rev-list.c
index 402af8e..09e3d37 100644
--- a/builtin-rev-list.c
+++ b/builtin-rev-list.c
@@ -352,7 +352,8 @@ int cmd_rev_list(int argc, const char **
 	    revs.diff)
 		usage(rev_list_usage);
 
-	save_commit_buffer = revs.verbose_header;
+	if (!revs.author_pattern && !revs.committer_pattern)
+		save_commit_buffer = revs.verbose_header;
 	track_object_refs = 0;
 	if (bisect_list)
 		revs.limited = 1;
diff --git a/revision.c b/revision.c
index b588f74..31618fe 100644
--- a/revision.c
+++ b/revision.c
@@ -671,6 +671,14 @@ int setup_revisions(int argc, const char
 				revs->min_age = approxidate(arg + 8);
 				continue;
 			}
+			if (!strncmp(arg, "--author=", 9)) {
+				revs->author_pattern = arg + 9;
+				continue;
+			}
+			if (!strncmp(arg, "--committer=", 12)) {
+				revs->committer_pattern = arg + 12;
+				continue;
+			}
 			if (!strcmp(arg, "--all")) {
 				handle_all(revs, flags);
 				continue;
@@ -1015,6 +1023,47 @@ static void mark_boundary_to_show(struct
 	}
 }
 
+static int commit_match_ident(struct commit *commit, const char *field, const char *pattern)
+{
+	const char *pos;
+	int field_len;
+	int pattern_len;
+
+	if (!pattern)
+		return 1;
+
+	field_len = strlen(field);
+	pattern_len = strlen(pattern);
+
+	for (pos = commit->buffer; *pos != '\n'; pos++) {
+		const char *line_end = strchr(pos, '\n');
+
+		if (!strncmp(pos, field, field_len) &&
+		    pos[field_len] == ' ') {
+			const char *ident_end = line_end;
+
+			pos += field_len;
+			while (ident_end > pos && ident_end[-1] != '>')
+				ident_end--;
+
+			/* A slow "strncasestr" */
+			while (pos + pattern_len <= ident_end) {
+				if (!strncasecmp(pos, pattern, pattern_len))
+					return 1;
+				pos++;
+			}
+
+			/* Assumes that fields that should be matched only
+			 * appear once in the commit header. */
+			return 0;
+		}
+
+		pos = line_end;
+	}
+
+	return 0;
+}
+
 struct commit *get_revision(struct rev_info *revs)
 {
 	struct commit_list *list = revs->commits;
@@ -1074,6 +1123,10 @@ struct commit *get_revision(struct rev_i
 		if (revs->no_merges &&
 		    commit->parents && commit->parents->next)
 			continue;
+		if (!commit_match_ident(commit, "author", revs->author_pattern))
+			continue;
+		if (!commit_match_ident(commit, "committer", revs->committer_pattern))
+			continue;
 		if (revs->prune_fn && revs->dense) {
 			/* Commit without changes? */
 			if (!(commit->object.flags & TREECHANGE)) {
diff --git a/revision.h b/revision.h
index d289781..7688e3a 100644
--- a/revision.h
+++ b/revision.h
@@ -71,6 +71,8 @@ struct rev_info {
 	int max_count;
 	unsigned long max_age;
 	unsigned long min_age;
+	const char *author_pattern;
+	const char *committer_pattern;
 
 	/* diff info for patches and for paths limiting */
 	struct diff_options diffopt;
-- 
1.4.2.g2ba6b-dirty

-- 
Jonas Fonseca