{"thread":{"id":"633","subject":"[PATCH 1/4] Add --author and --committer match to git-rev-list and git-rev-tree.","startedAt":"2005-05-15T21:18:36Z","lastAt":"2005-05-15T21:18:36Z","messageCount":1,"participants":["Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":4},"messages":[{"id":"3389","messageId":"7vy8agqjbn.fsf@assigned-by-dhcp.cox.net","threadId":"633","inReplyTo":null,"subject":"[PATCH 1/4] Add --author and --committer match to git-rev-list and git-rev-tree.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2005-05-15T21:18:36Z","receivedAt":"2005-05-15T21:18:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Zack Brown wondered if handling author match at core GIT level\nwould make cg-log -u go faster (JIT also can use this in jit-log\n--author).  Later Peter Baudis wanted to have --committer match\nsimilar to it.\n\nThis version is improved from the one I posted to GIT list\nearlier in that:\n\n (1) I bit the bullet and added author and committer names to\n     the commit object, so there is no double unpacking anymore.\n     The strings are shared across multiple commits so consider\n     them intern'ed and do not free them.\n\n (2) Determination of if author and committer names are\n     \"interesting\" is done only once per name, not per commit.\n     This version uses simple \"substring\" logic, but it is\n     easily extendable for more interesting match such as\n     regexps.\n\nThis also fixes documentation of git-rev-list which did not\ndescribe its already existing options.\n\nSigned-off-by: Junio C Hamano <junkio@cox.net>\n---\n\nDocumentation/git-rev-list.txt    |   19 +++++\nDocumentation/git-rev-tree.txt    |    8 +-\ncommit.c                          |  135 +++++++++++++++++++++++++++++++++-----\ncommit.h                          |   18 ++++-\nrev-list.c                        |   18 ++++-\nrev-tree.c                        |   27 +++++++\nt/t6010-rev-tree-author-commit.sh |   98 +++++++++++++++++++++++++++\nt/t6110-rev-list-author-commit.sh |   97 +++++++++++++++++++++++++++\n8 files changed, 396 insertions(+), 24 deletions(-)\nt/t6010-rev-tree-author-commit.sh (. --> 100755)\nt/t6110-rev-list-author-commit.sh (. --> 100755)\n\n--- a/Documentation/git-rev-list.txt\n+++ b/Documentation/git-rev-list.txt\n@@ -9,7 +9,7 @@\n \n SYNOPSIS\n --------\n-'git-rev-list' <commit>\n+'git-rev-list' [--author=author] [--committer=committer] [--max-count=count] [--max-age=unixtime] [--min-age=unixtime] <commit>\n \n DESCRIPTION\n -----------\n@@ -18,6 +18,23 @@\n useful to produce human-readable log output.\n \n \n+OPTIONS\n+-------\n+--author::\n+\tLimit the final output to commits written by the author.\n+\n+--committer::\n+\tLimit the final output to commits committed by the author.\n+\n+--max-count::\n+\tStop after showing the specified number of commits.\n+\n+--max-age::\n+\tStop before showing the commits older than specified time.\n+\n+--min-age::\n+\tDo not show the commits newer than specified time.\n+\n Author\n ------\n Written by Linus Torvalds <torvalds@osdl.org>\n--- a/Documentation/git-rev-tree.txt\n+++ b/Documentation/git-rev-tree.txt\n@@ -9,7 +9,7 @@\n \n SYNOPSIS\n --------\n-'git-rev-tree' [--edges] [--cache <cache-file>] [^]<commit> [[^]<commit>]\n+'git-rev-tree' [--author author] [--committer committer] [--edges] [--cache <cache-file>] [^]<commit> [[^]<commit>]\n \n DESCRIPTION\n -----------\n@@ -17,6 +17,12 @@\n \n OPTIONS\n -------\n+--author::\n+\tLimit the final output to commits written by the author.\n+\n+--committer::\n+\tLimit the final output to commits committed by the author.\n+\n --edges::\n \tShow edges (ie places where the marking changes between parent\n \tand child)\n--- a/commit.c\n+++ b/commit.c\n@@ -23,22 +23,111 @@\n \treturn (struct commit *) obj;\n }\n \n-static unsigned long parse_commit_date(const char *buf)\n-{\n-\tunsigned long date;\n+static struct person_name **person_name;\n+static int person_nr;\n+static int person_alloc;\n+\n+static const char *interesting_author = NULL;\n+static const char *interesting_committer = NULL;\n+\n+void commit_author_committer_match_initialize(const char *author,\n+\t\t\t\t\t      const char *committer)\n+{\n+\tinteresting_author = author;\n+\tinteresting_committer = committer;\n+}\n+\n+static int person_name_pos(const char *name)\n+{\n+\tint first, last;\n+\n+\tfirst = 0;\n+\tlast = person_nr;\n+\twhile (last > first) {\n+\t\tint next = (last + first) >> 1;\n+\t\tstruct person_name *pn = person_name[next];\n+\t\tint cmp = strcmp(name, pn->name);\n+\t\tif (!cmp)\n+\t\t\treturn next;\n+\t\tif (cmp < 0) {\n+\t\t\tlast = next;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tfirst = next+1;\n+\t}\n+\treturn -first-1;\n+}\n \n-\tif (memcmp(buf, \"author\", 6))\n-\t\treturn 0;\n-\twhile (*buf++ != '\\n')\n-\t\t/* nada */;\n-\tif (memcmp(buf, \"committer\", 9))\n-\t\treturn 0;\n-\twhile (*buf++ != '>')\n-\t\t/* nada */;\n-\tdate = strtoul(buf, NULL, 10);\n-\tif (date == ULONG_MAX)\n-\t\tdate = 0;\n-\treturn date;\n+#define INTERESTING_AUTHOR    01\n+#define INTERESTING_COMMITTER 02\n+\n+static struct person_name *person_name_lookup(const char *name)\n+{\n+\tint pos = person_name_pos(name);\n+\tstruct person_name *pn; \n+\tif (0 <= pos)\n+\t\treturn person_name[pos];\n+\tpos = -pos-1;\n+\tpn = xmalloc(sizeof(*pn) + strlen(name) + 1);\n+\tstrcpy(pn->name, name);\n+\tpn->mark = 0;\n+\n+\t/*\n+\t * When we decide we want to go fancier, strstr() below\n+\t * can be replaced with something like regexp match to\n+\t * pick up more than one author or committer.  For now\n+\t * let's try simple substring find and see what happens.\n+\t * Note that not specifying anybody means we are interested\n+\t * in everybody.\n+\t */\n+\tif (!interesting_author || strstr(name, interesting_author))\n+\t\tpn->mark |= INTERESTING_AUTHOR;\n+\tif (!interesting_committer || strstr(name, interesting_committer))\n+\t\tpn->mark |= INTERESTING_COMMITTER;\n+\n+\tif (person_nr == person_alloc) {\n+\t\tperson_alloc = alloc_nr(person_alloc);\n+\t\tperson_name = xrealloc(person_name, person_alloc *\n+\t\t\t\t       sizeof(struct person_name *));\n+\t}\n+\tperson_nr++;\n+\tif (pos < person_nr)\n+\t\tmemmove(person_name + pos + 1, person_name + pos,\n+\t\t\t(person_nr - pos - 1) * sizeof(pn));\n+\tperson_name[pos] = pn;\n+\treturn pn;\n+}\n+\n+static void *parse_commit_nametime(char *ptr, const char *ep,\n+\t\t\t\t   const char *field,\n+\t\t\t\t   unsigned long *date,\n+\t\t\t\t   struct person_name **name)\n+{\n+\tunsigned long d;\n+\tchar *cp;\n+\tint fldlen = strlen(field);\n+\tif (ptr == NULL || memcmp(ptr, field, fldlen) || ptr[fldlen] != ' ') {\n+\t\t*date = 0;\n+\t\t*name = NULL;\n+\t\treturn NULL; /* malformed commit */\n+\t}\n+\tfor (cp = ptr + fldlen + 1; cp < ep && *cp != '>'; cp++)\n+\t\t; /* skip */\n+\tif (*cp != '>' || *++cp != ' ') {\n+\t\t*date = 0;\n+\t\t*name = NULL;\n+\t\treturn NULL; /* malformed commit */\n+\t}\n+\t*cp = 0;\n+\t*name = person_name_lookup(ptr + fldlen + 1);\n+\t*cp++ = ' ';\n+\td = strtoul(cp, NULL, 10);\n+\tif (d == ULONG_MAX)\n+\t\td = 0;\n+\t*date = d;\n+\twhile (cp < ep && *cp++ != '\\n')\n+\t\t; /* skip */\n+\treturn cp;\n }\n \n int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size)\n@@ -63,7 +152,13 @@\n \t\t}\n \t\tbufptr += 48;\n \t}\n-\titem->date = parse_commit_date(bufptr);\n+\t\n+\tbufptr = parse_commit_nametime(bufptr, (char *)buffer + size,\n+\t\t\t\t       \"author\", &item->author_date,\n+\t\t\t\t       &item->author);\n+\tparse_commit_nametime(bufptr, (char *)buffer + size,\n+\t\t\t      \"committer\", &item->date,\n+\t\t\t      &item->committer);\n \treturn 0;\n }\n \n@@ -152,3 +247,11 @@\n \t}\n \treturn ret;\n }\n+\n+int commit_author_committer_match(struct commit *item)\n+{\n+\treturn ( ((item->author != NULL) &&\n+\t\t  (item->author->mark & INTERESTING_AUTHOR)) &&\n+\t\t ((item->committer != NULL) &&\n+\t\t  (item->committer->mark & INTERESTING_COMMITTER)) );\n+}\n--- a/commit.h\n+++ b/commit.h\n@@ -11,8 +11,9 @@\n \n struct commit {\n \tstruct object object;\n-\tunsigned long date;\n+\tunsigned long author_date, date;\n \tstruct commit_list *parents;\n+\tstruct person_name *author, *committer; \n \tstruct tree *tree;\n };\n \n@@ -36,4 +37,19 @@\n struct commit *pop_most_recent_commit(struct commit_list **list, \n \t\t\t\t      unsigned int mark);\n \n+struct person_name {\n+\tchar mark;\n+\tchar name[0];\n+};\n+\n+/* This function must be called before fetching any commit object\n+ * if commit-author-committer-match function is to be used to filter\n+ * commits for output.  Passing NULL is permitted, which makes all\n+ * authors (or committers) \"interesting\".\n+ */\n+void commit_author_committer_match_initialize(const char *, const char *);\n+\n+/* Returns true only if author and committer are \"interesting\". */\n+int commit_author_committer_match(struct commit *);\n+\n #endif /* COMMIT_H */\n--- a/rev-list.c\n+++ b/rev-list.c\n@@ -11,6 +11,8 @@\n \tunsigned long max_age = -1;\n \tunsigned long min_age = -1;\n \tint max_count = -1;\n+\tconst char *author = NULL;\n+\tconst char *committer = NULL;\n \n \tfor (i = 1 ; i < argc; i++) {\n \t\tchar *arg = argv[i];\n@@ -21,6 +23,10 @@\n \t\t\tmax_age = atoi(arg + 10);\n \t\t} else if (!strncmp(arg, \"--min-age=\", 10)) {\n \t\t\tmin_age = atoi(arg + 10);\n+\t\t} else if (!strncmp(arg, \"--author=\", 9)) {\n+\t\t\tauthor = arg + 9;\n+\t\t} else if (!strncmp(arg, \"--committer=\", 12)) {\n+\t\t\tcommitter = arg + 12;\n \t\t} else {\n \t\t\tcommit_arg = arg;\n \t\t}\n@@ -28,9 +34,13 @@\n \n \tif (!commit_arg || get_sha1(commit_arg, sha1))\n \t\tusage(\"usage: rev-list [OPTION] commit-id\\n\"\n-\t\t      \"  --max-count=nr\\n\"\n-\t\t      \"  --max-age=epoch\\n\"\n-\t\t      \"  --min-age=epoch\\n\");\n+\t\t      \"  --author=author\\n\"\n+\t\t      \"  --committer=committer\\n\"\n+\t\t      \"  --max-count=number\\n\"\n+\t\t      \"  --max-age=unixtime\\n\"\n+\t\t      \"  --min-age=unixtime\\n\");\n+\n+\tcommit_author_committer_match_initialize(author, committer);\n \n \tcommit = lookup_commit(sha1);\n \tif (!commit || parse_commit(commit) < 0)\n@@ -44,6 +54,8 @@\n \t\t\tcontinue;\n \t\tif (max_age != -1 && (commit->date < max_age))\n \t\t\tbreak;\n+\t\tif (!commit_author_committer_match(commit))\n+\t\t\tcontinue;\n \t\tif (max_count != -1 && !max_count--)\n \t\t\tbreak;\n \t\tprintf(\"%s\\n\", sha1_to_hex(commit->object.sha1));\n--- a/rev-tree.c\n+++ b/rev-tree.c\n@@ -64,7 +64,7 @@\n }\n \n /*\n- * Usage: rev-tree [--edges] [--cache <cache-file>] <commit-id> [<commit-id2>]\n+ * Usage: rev-tree [--edges] [--author <author>] [--cache <cache-file>] <commit-id> [<commit-id2>]\n  *\n  * The cache-file can be quite important for big trees. This is an\n  * expensive operation if you have to walk the whole chain of\n@@ -75,6 +75,9 @@\n \tint i;\n \tint nr = 0;\n \tunsigned char sha1[MAX_COMMITS][20];\n+\tconst char *author = NULL; \n+\tconst char *committer = NULL;\n+\tint initialized_author_commiter = 0;\n \n \t/*\n \t * First - pick up all the revisions we can (both from\n@@ -83,6 +86,16 @@\n \tfor (i = 1; i < argc ; i++) {\n \t\tchar *arg = argv[i];\n \n+\t\tif (!strcmp(arg, \"--author\")) {\n+\t\t\tauthor = argv[++i];\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (!strcmp(arg, \"--committer\")) {\n+\t\t\tcommitter = argv[++i];\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\tif (!strcmp(arg, \"--cache\")) {\n \t\t\tread_cache_file(argv[++i]);\n \t\t\tcontinue;\n@@ -98,7 +111,14 @@\n \t\t\tbasemask |= 1<<nr;\n \t\t}\n \t\tif (nr >= MAX_COMMITS || get_sha1(arg, sha1[nr]))\n-\t\t\tusage(\"rev-tree [--edges] [--cache <cache-file>] <commit-id> [<commit-id>]\");\n+\t\t\tusage(\"rev-tree [--edges] [--author <author>] [--committer <committer>] [--cache <cache-file>] <commit-id> [<commit-id>]\");\n+\n+\t\tif (!initialized_author_commiter) {\n+\t\t\tcommit_author_committer_match_initialize(author,\n+\t\t\t\t\t\t\t\t committer);\n+\t\t\tinitialized_author_commiter = 1;\n+\t\t}\n+\n \t\tprocess_commit(sha1[nr]);\n \t\tnr++;\n \t}\n@@ -125,6 +145,9 @@\n \t\tif (!interesting(commit))\n \t\t\tcontinue;\n \n+\t\tif (!commit_author_committer_match(commit))\n+\t\t\tcontinue;\n+\n \t\tprintf(\"%lu %s:%d\", commit->date, sha1_to_hex(obj->sha1), \n \t\t       obj->flags);\n \t\tp = commit->parents;\n--- a/t/t6010-rev-tree-author-commit.sh\n+++ b/t/t6010-rev-tree-author-commit.sh\n@@ -0,0 +1,98 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2005 Junio C Hamano\n+#\n+\n+test_description='git-rev-tree --author and --committer flags.\n+'\n+\n+. ./test-lib.sh\n+\n+export_them () {\n+export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL\n+}\n+\n+set_author_zero () {\n+    GIT_AUTHOR_NAME=\"A U Thor\" &&\n+    GIT_AUTHOR_EMAIL=\"<author@example.xz>\"\n+}\n+set_author_one () {\n+    GIT_AUTHOR_NAME=\"R O Htua\" &&\n+    GIT_AUTHOR_EMAIL=\"<rohtua@example.xz>\"\n+}\n+set_committer_zero () {\n+    GIT_COMMITTER_NAME=\"C O Mmitter\" &&\n+    GIT_COMMITTER_EMAIL=\"<committer@example.xz>\"\n+}\n+set_committer_one () {\n+    GIT_COMMITTER_NAME=\"R E Ttimmoc\" &&\n+    GIT_COMMITTER_EMAIL=\"<rettimmoc@example.xz>\"\n+}\n+\n+# read rev-tree output, and find the author|committer of commit object\n+pick_actor () {\n+    sed -e 's/^[^ ]* //;s/:.*//' |\n+    xargs -r -n1 git-cat-file commit |\n+    sed -ne 's/^\\('\"$1\"'\\) \\([^>]*>\\).*/\\2/p'\n+}\n+\n+test_expect_success \\\n+    'preparation' '\n+echo frotz >path0 &&\n+git-update-cache --add path0 &&\n+tree0=$(git-write-tree) &&\n+\n+set_author_zero &&\n+set_committer_zero &&\n+export_them &&\n+commit0=$(echo frotz | git-commit-tree $tree0) &&\n+\n+set_author_zero &&\n+set_committer_one &&\n+export_them &&\n+commit1=$(echo frotz | git-commit-tree $tree0 -p $commit0) &&\n+\n+set_author_one &&\n+set_committer_zero &&\n+export_them &&\n+commit2=$(echo frotz | git-commit-tree $tree0 -p $commit1) &&\n+\n+set_author_one &&\n+set_committer_one &&\n+export_them &&\n+commit3=$(echo frotz | git-commit-tree $tree0 -p $commit2)'\n+\n+test_expect_success \\\n+    'without restriction git-rev-tree should report all four commits.' \\\n+    'test $(git-rev-tree $commit3 | wc -l) == 4'\n+\n+test_expect_success \\\n+    'limiting to A U Thor (two commits)' '\n+    case \"$(git-rev-tree --author \"A U Thor\" $commit3 |\n+          pick_actor \"author\" | sort -u)\" in\n+    \"A U Thor <author@example.xz>\") : ;;\n+    *) (exit 1) ;;\n+    esac'\n+\n+test_expect_success \\\n+    'limiting to R E Ttimmoc (two commits)' '\n+    case \"$(git-rev-tree --committer \"R E Ttimmoc\" $commit3 |\n+          pick_actor \"committer\" | sort -u)\" in\n+    \"R E Ttimmoc <rettimmoc@example.xz>\") : ;;\n+    *) (exit 1) ;;\n+    esac'\n+\n+LF='\n+'\n+\n+test_expect_success \\\n+    'limiting to A U Thor and C O Mmitter (one commit)' '\n+    case \"$(git-rev-tree --author \"A U Thor\" --committer \"C O Mmitter\" \\\n+            $commit3 | pick_actor \"committer\\\\|author\" | sort -u)\" in\n+    \"A U Thor <author@example.xz>${LF}C O Mmitter <committer@example.xz>\")\n+        : ;;\n+    *) (exit 1) ;;\n+    esac\n+'\n+\n+test_done\n--- a/t/t6110-rev-list-author-commit.sh\n+++ b/t/t6110-rev-list-author-commit.sh\n@@ -0,0 +1,97 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2005 Junio C Hamano\n+#\n+\n+test_description='git-rev-list --author and --committer flags.\n+'\n+\n+. ./test-lib.sh\n+\n+export_them () {\n+export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL\n+}\n+\n+set_author_zero () {\n+    GIT_AUTHOR_NAME=\"A U Thor\" &&\n+    GIT_AUTHOR_EMAIL=\"<author@example.xz>\"\n+}\n+set_author_one () {\n+    GIT_AUTHOR_NAME=\"R O Htua\" &&\n+    GIT_AUTHOR_EMAIL=\"<rohtua@example.xz>\"\n+}\n+set_committer_zero () {\n+    GIT_COMMITTER_NAME=\"C O Mmitter\" &&\n+    GIT_COMMITTER_EMAIL=\"<committer@example.xz>\"\n+}\n+set_committer_one () {\n+    GIT_COMMITTER_NAME=\"R E Ttimmoc\" &&\n+    GIT_COMMITTER_EMAIL=\"<rettimmoc@example.xz>\"\n+}\n+\n+# read rev-list output, and find the author|committer of commit object\n+pick_actor () {\n+    xargs -r -n1 git-cat-file commit |\n+    sed -ne 's/^\\('\"$1\"'\\) \\([^>]*>\\).*/\\2/p'\n+}\n+\n+test_expect_success \\\n+    'preparation' '\n+echo frotz >path0 &&\n+git-update-cache --add path0 &&\n+tree0=$(git-write-tree) &&\n+\n+set_author_zero &&\n+set_committer_zero &&\n+export_them &&\n+commit0=$(echo frotz | git-commit-tree $tree0) &&\n+\n+set_author_zero &&\n+set_committer_one &&\n+export_them &&\n+commit1=$(echo frotz | git-commit-tree $tree0 -p $commit0) &&\n+\n+set_author_one &&\n+set_committer_zero &&\n+export_them &&\n+commit2=$(echo frotz | git-commit-tree $tree0 -p $commit1) &&\n+\n+set_author_one &&\n+set_committer_one &&\n+export_them &&\n+commit3=$(echo frotz | git-commit-tree $tree0 -p $commit2)'\n+\n+test_expect_success \\\n+    'without restriction git-rev-tree should report all four commits.' \\\n+    'test $(git-rev-list $commit3 | wc -l) == 4'\n+\n+test_expect_success \\\n+    'limiting to A U Thor (two commits)' '\n+    case \"$(git-rev-list --author=\"A U Thor\" $commit3 |\n+          pick_actor \"author\" | sort -u)\" in\n+    \"A U Thor <author@example.xz>\") : ;;\n+    *) (exit 1) ;;\n+    esac'\n+\n+test_expect_success \\\n+    'limiting to R E Ttimmoc (two commits)' '\n+    case \"$(git-rev-list --committer=\"R E Ttimmoc\" $commit3 |\n+          pick_actor \"committer\" | sort -u)\" in\n+    \"R E Ttimmoc <rettimmoc@example.xz>\") : ;;\n+    *) (exit 1) ;;\n+    esac'\n+\n+LF='\n+'\n+\n+test_expect_success \\\n+    'limiting to A U Thor and C O Mmitter (one commit)' '\n+    case \"$(git-rev-list --author=\"A U Thor\" --committer=\"C O Mmitter\" \\\n+            $commit3 | pick_actor \"committer\\\\|author\" | sort -u)\" in\n+    \"A U Thor <author@example.xz>${LF}C O Mmitter <committer@example.xz>\")\n+        : ;;\n+    *) (exit 1) ;;\n+    esac\n+'\n+\n+test_done\n------------------------------------------------\n\n"}]}