{"thread":{"id":"888","subject":"[PATCH] Add support for author-oriented git-rev-list switches [rev 10]","startedAt":"2005-06-10T03:35:04Z","lastAt":"2005-06-10T03:35:04Z","messageCount":1,"participants":["Jon Seymour"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"4814","messageId":"20050610033504.9452.qmail@blackcubes.dyndns.org","threadId":"888","inReplyTo":null,"subject":"[PATCH] Add support for author-oriented git-rev-list switches [rev 10]","fromName":"Jon Seymour","fromEmail":"jon.seymour@gmail.com","sentAt":"2005-06-10T03:35:04Z","receivedAt":"2005-06-10T03:35:04Z","isPatch":true,"sender":{"key":"jon.seymour@gmail.com","avatar":"https://avatars.githubusercontent.com/u/207131?v=4"},"body":"\nThis patch is a complete replacement for the previous --wrt \nand --wrt-author patches; it adds support for --wrt-author, \n--prune-at-author and --author switches to git-rev-list.\n\nThese switches perform various sorting and filtering operations that\nhelp any git user reconstruct a linear view of merge history from \nthe point of view of any given change author.\n\nWhen --wrt-author is specified, --merge-order is implied but \nthe linearisation of non-linear epochs is adjusted so that branches\ncontributed to by the author sort after branches contributed to by\nothers. This has the effect of presenting a linear history of changes as seen\nby the author's own workspace.\n\nFor example, given this commit history:\n\na4 ---\n| \\   \\\n|  b4 |\n|/ |  |\na3 |  |\n|  |  |\na2 |  |\n|  |  c3\n|  |  |\n|  |  c2\n|  b3 |\n|  | /|\n|  b2 |\n|  |  c1\n|  | /\n|  b1    <-- commit authored by author@domain\na1 |\n|  |\na0 |\n| /\nroot\n\n--merge-order alone would sort it as follows:\n\n= a4\n| c3\n| c2\n| c1\n^ b4\n| b3\n| b2\n| b1\n^ a3\n| a2\n| a1\n| a0\n= root\n\nhowever, with --wrt-author --author=author@domain, git-rev-list sorts as follows:\n\n= a4\n| c3\n| c2\n| c1\n^ b4\n| a3\n| a2\n| a1\n| a0\n^ b3\n| b2\n| b1\n= root\n\nThis has the effect of showing the first 3 of author@domain's changes \noccuring before any of the changes in other branches. This is the \norder in which author@domain perceived change occurring - changes \na0->a3 weren't visible in author@domain's workspace until the merge at b4.\n\nIf --prune-at-author is specified, then git-rev-list behaves as if a \n^argument had been added for each commit authored by the author.\n\nThe --prune-at-author option allows an author to see a brief summary of \nchanges since they last made a contribution to the commit graph. For example:\n\n      git-rev-list --prune-at-author --pretty HEAD \n\nTo configure which author git-rev-list uses when processing the --wrt-author \nand --prune-at-author switches, use the --author=author@domain option. If this \noption is not specified, git-rev-list uses GIT_AUTHOR_EMAIL or a value\nderived from the local user, host and domain names \n(per git-commit-tree's algorithm)\n\nTo support this change, some additional functions were added to commit.c to \nallow the extraction of author from the commit header.\n\nAlso, --show-breaks, like --wrt-author now implies --merge-order rather than \nrequires it.\n\nThis change also updates Documentation/git-rev-list.txt to include \ndocumentation for --wrt-author and other git-rev-list options not currently \ndocumented.\n\nA test case has been included in t/t6001-rev-list-merge-order.sh to \nverify this change works as expected.\n\nUse of --wrt-author implies a small overhead (linear with respect to nodes \nprinted) compared with --merge-order.\n\nThis change extracts real email id formatting functions from commit-tree.c \ninto user.c and user.h.\n\nSigned-off-by: Jon Seymour <jon.seymour@gmail.com>\n\n---\n\nNOTE TO REVIEWERS: please pay special attention to get_real_identity() \nwhich was extracted from commit-tree.c and placed into user.c. It was \nessentially a cut and paste, but since this has the potential to affect the \noperation of git-commit-tree, it would be worth being very careful about \nchecking this change.\n\n[rev 1]   * original patch\n[rev 2-4] * various cleanups\n[rev 5]   * --merge-order bug fixes already merged with Linus.\n[rev 6]   * remerged with Linus' head (98a96b00b88ee35866cd0b1e94697db76bd5ddf9)\n          * changed name of --exclude-author switch to --prune-at-author\n\t  * reformatted to be more consistent with preferred coding style, namely:\n\t        * aggressive elimination of intra-block blank lines\n                  except immediately after declarations\n                * declarations at top of block\n                * one statement blocks without braces\n                * everything except first line of function declaration \n                  wrapped at <= 80 columns\n          * [ undone in rev 8 ] modified test cases to use /dev/urandom to get \n\t    some entropy for commit text so that closely spaced commits aren't identical\n          * added struct epoch_methods to gather 3 function pointers now used\n            by merge order logic\n          * added a comment to commit.c to indicate that epoch.c is assuming\n            that parse_commit stacks parents read from the commit header\n            rather than queues them\n[rev 7]   * further whitespace and style edits\n[rev 8]   * d'oh - don't need /dev/urandom at all - re-written and simplified\n            test cases\n[rev 9]   * oops, missed some whitespace in my commit.c edits - begone!\n          * slight change to semantics of --prune-at-author - includes the\n            author changes, but prunes the parents of the author. This helps put\n            the cut points into some kind of context when the author has contributions\n            in multiple branches.\n[rev 10]  * added a function to commit.c to make dependency on commit header order\n            explicit. now if parse_commit changes, any caller who uses copy_parents_in_header_order\n            won't need to change provided copy_parents_in_header_order is updated appropriately.\n          * simplified sort_first_epoch by putting termination tests near top\n          * changed sort_list_in_local_last_order to sort_list_in_local_first_order\n            and made the code more concise\n          * added some more tests to check behaviour on degenerate graphs on 1,2 or 3 nodes.\n\nThis revision [rev 10] reposted to the list because the changes since [rev 6], particularly in [rev 9, 10]\nhave been non-trivial. \n\nDiverged from 98a96b00b88ee35866cd0b1e94697db76bd5ddf9 by Linus Torvalds <torvalds@ppc970.osdl.org>\n\ndiff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt\n--- a/Documentation/git-rev-list.txt\n+++ b/Documentation/git-rev-list.txt\n@@ -9,65 +9,114 @@ git-rev-list - Lists commit objects in r\n \n SYNOPSIS\n --------\n-'git-rev-list' [ *--max-count*=number ] [ *--max-age*=timestamp ] [ *--min-age*=timestamp ] [ *--merge-order* [ *--show-breaks* ] ] <commit>\n+'git-rev-list' [ *--max-count*=number ] [ *--max-age*=timestamp ] \n+[ *--min-age*=timestamp ] [ *--pretty* ] [ *--parents* ] [ *--header* ] \n+[ *--merge-order* ] [ *--show-breaks* ] [ *--wrt-author* ] \n+[ *--prune-at-author* ] [ *--author=author@domain* ]  \n+<head-to-include> <head-to-include> ... \n+^<base-to-prune> \n+^<base-to-prune> ...\n \n DESCRIPTION\n -----------\n-Lists commit objects in reverse chronological order starting at the\n-given commit, taking ancestry relationship into account.  This is\n-useful to produce human-readable log output.\n-\n-If *--merge-order* is specified, the commit history is decomposed into a\n-unique sequence of minimal, non-linear epochs and maximal, linear epochs.\n-Non-linear epochs are then linearised by sorting them into merge order, which\n+\n+Lists commit objects in reverse chronological order starting at the commits \n+specified by head-to-include values, taking ancestry relationship into account.  \n+git-rev-list will not print any commits that are reachable by any of the \n+base-to-prune values. \n+\n+If *--max-count* is specified, the number of commits output is \n+limited to the number specified.\n+\n+If *--max-age* is specified, commits older than the date specified \n+(in seconds since Jan 1, 1970) are excluded from the output.\n+\n+If *--min-age* is specified, commits younger than the date specified \n+(in seconds since Jan 1, 1970) are excluded from the output.\n+\n+If *--pretty* is specified, git-rev-list prints a short summary of each \n+commit on stdout. This can be usefully parsed by git-shortlog to produce a \n+short log of changes.\n+\n+If *--parents* is specified, git-rev-list prints a space separated list \n+of parents on the same line as the commit identifier.\n+\n+If *--header* is specified, git-rev-list prints to stdout, on lines after \n+the identifier of each commit, the contents of the commit's header followed by \n+a trailing NUL.\n+\n+If *--merge-order* is specified, the commit history is decomposed into a \n+unique sequence of minimal, non-linear epochs and maximal, linear epochs. \n+Non-linear epochs are then linearised by sorting them into merge order, which \n is described below.\n \n-Maximal, linear epochs correspond to periods of sequential development.\n-Minimal, non-linear epochs correspond to periods of divergent development\n-followed by a converging merge. The theory of epochs is described in more\n-detail at\n+Maximal, linear epochs correspond to periods of sequential development. \n+Minimal, non-linear epochs correspond to periods of divergent development \n+followed by a converging merge. The theory of epochs is described in more \n+detail at \n link:http://blackcubes.dyndns.org/epoch/[http://blackcubes.dyndns.org/epoch/].\n \n-The merge order for a non-linear epoch is defined as a linearisation for which\n-the following invariants are true:\n-\n-    1. if a commit P is reachable from commit N, commit P sorts after commit N\n-       in the linearised list.\n-    2. if Pi and Pj are any two parents of a merge M (with i < j), then any\n-       commit N, such that N is reachable from Pj but not reachable from Pi,\n-       sorts before all commits reachable from Pi.\n-\n-Invariant 1 states that later commits appear before earlier commits they are\n-derived from.\n+The merge order for a non-linear epoch is defined as a linearisation for \n+which the following invariants are true:\n \n-Invariant 2 states that commits unique to \"later\" parents in a merge, appear\n-before all commits from \"earlier\" parents of a merge.\n+    1. if a commit P is reachable from commit N, commit P sorts after \n+       commit N in the linearised list.\n+    2. if Pi and Pj are any two parents of a merge M (with i < j), then \n+       any commit N, such that N is reachable from Pj but not reachable from \n+       Pi, sorts before all commits reachable from Pi.\n+   \n+Invariant 1 states that later commits appear before earlier commits \n+they are derived from.\n+\n+Invariant 2 states that commits unique to \"later\" parents in a merge, \n+appear before all commits from \"earlier\" parents of a merge.\n+\n+If *--show-breaks* is specified, each item of the list is output with a \n+2-character prefix consisting of one of:  (|), (^), (=) followed by a space. \n+ \n+Commits marked with (=) represent the boundaries of minimal, non-linear \n+epochs and correspond either to the start of a period of divergent development \n+or to the end of such a period. \n \n-If *--show-breaks* is specified, each item of the list is output with a\n-2-character prefix consisting of one of: (|), (^), (=) followed by a space.\n-\n-Commits marked with (=) represent the boundaries of minimal, non-linear epochs\n-and correspond either to the start of a period of divergent development or to\n-the end of such a period.\n-\n-Commits marked with (|) are direct parents of commits immediately preceding\n+Commits marked with (|) are direct parents of commits immediately preceding \n the marked commit in the list.\n \n-Commits marked with (^) are not parents of the immediately preceding commit.\n-These \"breaks\" represent necessary discontinuities implied by trying to\n+Commits marked with (^) are not parents of the immediately preceding commit. \n+These \"breaks\" represent necessary discontinuities implied by trying to \n represent an arbtirary DAG in a linear form.\n \n-*--show-breaks* is only valid if *--merge-order* is also specified.\n+If *--wrt-author* is specified, then git-rev-list sorts commits so that \n+contemporaneously developed commits appear to occur after (that is, sort before) \n+any commits authored by the author specified by *--author*. This reflects the \n+order in which that author sees contemporaneously developed commits appear \n+in her own workspace.\n+\n+If *--prune-at-author* is specified, then the parent of any commit \n+authored by the author is marked as uninteresting as if it had been \n+specified with ^ on the command line. This option restricts git-rev-list's \n+output to just the changes including and following the last change\n+contributed by any author in a given branch.\n+\n+The *--author* option is used to specify the author used by the \n+*--wrt-author* and *--prune-at-author* switches. If an *--author* option is \n+not specified, the author used is derived from GIT_AUTHOR_EMAIL or, ultimately, \n+from the local user, host and domain names as per the logic used by \n+git-commit-tree.\n+\n+Use of *--show-breaks*, *--prune-at-author* or *--wrt-author* \n+implies *--merge-order*.\n \n Author\n ------\n Written by Linus Torvalds <torvalds@osdl.org>\n \n-Original *--merge-order* logic by Jon Seymour <jon.seymour@gmail.com>\n+Original *--merge-order*, *--wrt-author* and *--prune-at-author* logic \n+by Jon Seymour <jon.seymour@gmail.com>\n \n Documentation\n --------------\n-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.\n+Documentation by David Greaves, Junio C Hamano, Jon Seymour and \n+the git-list <git@vger.kernel.org>.\n \n GIT\n ---\ndiff --git a/Makefile b/Makefile\n--- a/Makefile\n+++ b/Makefile\n@@ -42,9 +42,9 @@ install: $(PROG) $(SCRIPTS)\n \n LIB_OBJS=read-cache.o sha1_file.o usage.o object.o commit.o tree.o blob.o \\\n \t tag.o delta.o date.o index.o diff-delta.o patch-delta.o entry.o \\\n-\t epoch.o refs.o\n+\t epoch.o refs.o user.o\n LIB_FILE=libgit.a\n-LIB_H=cache.h object.h blob.h tree.h commit.h tag.h delta.h epoch.h\n+LIB_H=cache.h object.h blob.h tree.h commit.h tag.h delta.h epoch.h user.h\n \n LIB_H += strbuf.h\n LIB_OBJS += strbuf.o\n@@ -140,6 +140,7 @@ diffcore-pickaxe.o : $(LIB_H) diffcore.h\n diffcore-break.o : $(LIB_H) diffcore.h\n diffcore-order.o : $(LIB_H) diffcore.h\n epoch.o: $(LIB_H)\n+user.o: $(LIB_H)\n \n test: all\n \t$(MAKE) -C t/ all\ndiff --git a/commit-tree.c b/commit-tree.c\n--- a/commit-tree.c\n+++ b/commit-tree.c\n@@ -4,8 +4,8 @@\n  * Copyright (C) Linus Torvalds, 2005\n  */\n #include \"cache.h\"\n+#include \"user.h\"\n \n-#include <pwd.h>\n #include <time.h>\n #include <ctype.h>\n \n@@ -100,17 +100,16 @@ static char *commit_tree_usage = \"git-co\n \n int main(int argc, char **argv)\n {\n-\tint i, len;\n+\tint i;\n \tint parents = 0;\n \tunsigned char tree_sha1[20];\n \tunsigned char parent_sha1[MAXPARENT][20];\n \tunsigned char commit_sha1[20];\n \tchar *gecos, *realgecos, *commitgecos;\n-\tchar *email, *commitemail, realemail[1000];\n+\tchar *email, *commitemail, *realemail;\n \tchar date[50], realdate[50];\n \tchar *audate, *cmdate;\n \tchar comment[1000];\n-\tstruct passwd *pw;\n \tchar *buffer;\n \tunsigned int size;\n \n@@ -128,19 +127,7 @@ int main(int argc, char **argv)\n \t}\n \tif (!parents)\n \t\tfprintf(stderr, \"Committing initial tree %s\\n\", argv[1]);\n-\tpw = getpwuid(getuid());\n-\tif (!pw)\n-\t\tdie(\"You don't exist. Go away!\");\n-\trealgecos = pw->pw_gecos;\n-\tlen = strlen(pw->pw_name);\n-\tmemcpy(realemail, pw->pw_name, len);\n-\trealemail[len] = '@';\n-\tgethostname(realemail+len+1, sizeof(realemail)-len-1);\n-\tif (!strchr(realemail+len+1, '.')) {\n-\t\tstrcat(realemail, \".\");\n-\t\tgetdomainname(realemail+strlen(realemail), sizeof(realemail)-strlen(realemail)-1);\n-\t}\n-\n+\tget_real_identity(&realemail, &realgecos);\n \tdatestamp(realdate, sizeof(realdate));\n \tstrcpy(date, realdate);\n \ndiff --git a/commit.c b/commit.c\n--- a/commit.c\n+++ b/commit.c\n@@ -76,6 +76,12 @@ int parse_commit_buffer(struct commit *i\n \t       !get_sha1_hex(bufptr + 7, parent)) {\n \t\tstruct commit *new_parent = lookup_commit(parent);\n \t\tif (new_parent) {\n+\t\t\t/** \n+                         * note to maintainers - \n+                         * please read and modify copy_parents_in_header_order\n+                         * if this behaviour ever changes from stacking\n+                         * to queueing behaviour.\n+\t\t\t */\n \t\t\tcommit_list_insert(new_parent, &item->parents);\n \t\t\tadd_ref(&item->object, &new_parent->object);\n \t\t}\n@@ -302,3 +308,69 @@ int count_parents(struct commit * commit\n         return count;\n }\n \n+int copy_commit_header(struct commit * commit, char * header, int index, char * buffer, int len)\n+{\n+\tchar * p = commit->buffer;\n+\n+\twhile (*p != '\\n') {\t\t\n+\t\tchar * q = header;\n+\t\tint matched;\n+\n+\t\tfor (matched = 1; *p != ' ' && *p != '\\n'; p++, q++) {\n+\t\t\tmatched = matched && (*q==*p);\n+\t\t}\n+\t\tif (matched && index) {\n+\t\t\t/*\n+\t\t\t * if we matched but we haven't seen the \n+\t\t\t * index'th element yet, just decrement \n+\t\t\t * the index then pretend we didn't match\n+\t\t\t */\n+\t\t\tindex--;\n+\t\t\tmatched = 0;\n+\t\t}\n+\t\tif (!matched) {\t\t\t\n+\t\t\t/* skip to start of next header line */\t\t\t\n+\t\t\tfor (;*p!='\\n';p++)\n+\t\t\t\t;\n+\t\t\tp++;\n+\t\t} else {\t\t\t\n+\t\t\tint count = 0; /* number of characters in value */\n+\t\t\tif (*p == ' ') {\t\t\t\t\n+\t\t\t\tp++;\n+\t\t\t\tcount = 0;\n+\t\t\t\twhile(*p != '\\n') {\n+\t\t\t\t\tif (len > 0)\n+\t\t\t\t\t\t*buffer++=*p;\t\t\t\t\n+\t\t\t\t\tp++;\n+\t\t\t\t\tcount++;\n+\t\t\t\t\tlen--;\n+\t\t\t\t}\t\t\t\t\t\n+\t\t\t} \n+\t\t\tif (len > 0)\n+\t\t\t\t*buffer = 0;\n+\t\t\treturn (len > 0) ? count+1 : -(count+1);\n+\t\t}\t\t\t\n+\t}\n+\treturn 0;\n+}\n+\n+int copy_author(struct commit * commit, char * buffer, int len)\n+{\n+\treturn copy_commit_header(commit, \"author\", 0, buffer, len);\n+}\n+\n+/*\n+ * Always produces a list of parents in the order they actually\n+ * appear in the commit header. The correct implementation \n+ * of this function depends on decisions made in parse_commit().\n+ */\n+struct commit_list * copy_parents_in_header_order(struct commit * commit)\n+{\n+\tstruct commit_list * parents = commit->parents;\n+        struct commit_list * result = NULL;\n+\n+\tfor (parents = commit->parents;parents;parents = parents->next) {\n+\t\tcommit_list_insert(parents->item, &result);\n+\t}\n+\treturn result;\n+}\ndiff --git a/commit.h b/commit.h\n--- a/commit.h\n+++ b/commit.h\n@@ -53,4 +53,35 @@ struct commit *pop_most_recent_commit(st\n struct commit *pop_commit(struct commit_list **stack);\n \n int count_parents(struct commit * commit);\n+\n+/*\n+ * Copies the value of the (index+1)'th commit header matching the name \n+ * specified into the buffer supplied and append a trailing NUL.\n+ *\n+ * Returns n<0 if the buffer was too short, where -n is the required length.\n+ * \n+ * Returns 0 if the header doesn't exist.\n+ * \n+ * Returns n>0 where n is the length of the copied zero-terminated value, \n+ * including the terminating zero.\n+ */\n+int copy_commit_header(struct commit * commit, char * header, int index, char * buffer, int len);\n+\n+/*\n+ * Copies the commit's author value into the buffer supplied. \n+ * Return values as per copy_commit_header.\n+ */\n+int copy_author(struct commit * commit, char * buffer, int len);\n+\n+/*\n+ * Make a copy of the parents in header order. This is the\n+ * reverse of the parse order. Callers who need guarantees\n+ * about parents being in header order should use this \n+ * call whose semantics will never change even if the\n+ * implementation of parse_commit does.\n+ *\n+ * Callers are expected to destroy the copied list when\n+ * they are finished with it.\n+ */\n+struct commit_list * copy_parents_in_header_order(struct commit * commit);\n #endif /* COMMIT_H */\ndiff --git a/epoch.c b/epoch.c\n--- a/epoch.c\n+++ b/epoch.c\n@@ -8,7 +8,6 @@\n  *\n  */\n #include <stdlib.h>\n-\n /* Provides arbitrary precision integers required to accurately represent\n  * fractional mass: */\n #include <openssl/bn.h>\n@@ -22,17 +21,33 @@ struct fraction {\n \tBIGNUM denominator;\n };\n \n-#define HAS_EXACTLY_ONE_PARENT(n) ((n)->parents && !(n)->parents->next)\n-\n static BN_CTX *context = NULL;\n static struct fraction *one = NULL;\n static struct fraction *zero = NULL;\n \n+#define HAS_EXACTLY_ONE_PARENT(n) ((n)->parents && !(n)->parents->next)\n+#define IS_UNINTERESTING(c) ((c)->object.flags & UNINTERESTING)\n+#define IS_VISITED(c) ((c)->object.flags & VISITED)\n+#define IS_BASE(c) ((c)->object.flags & BASE)\n+#define IS_LOCAL(c) ((c)->object.flags & LOCAL)\n+#define IS_DISCONTINUITY(c) ((c)->object.flags & DISCONTINUITY)\n+#define ASSERT(x,m,c) if (!(x)) { assertion_failed(__LINE__, __FUNCTION__, m, c); }\n+\n+static void assertion_failed(int line, char * function, char * message, struct commit * item)\n+{\n+\tdie(\t\"%s:%d:%s: assertion_failed: %s: commit %s, flags %x\",\n+\t\t__FILE__, \n+\t\tline, \n+\t\tfunction,\n+\t\tmessage,\n+\t\titem ? sha1_to_hex(item->object.sha1) : \"[]\",\n+\t\titem ? item->object.flags : 0xFFFFFFFF);\n+}\n+\t\t\t\n static BN_CTX *get_BN_CTX()\n {\n-\tif (!context) {\n+\tif (!context)\n \t\tcontext = BN_CTX_new();\n-\t}\n \treturn context;\n }\n \n@@ -58,10 +73,10 @@ static struct fraction *divide(struct fr\n \n \tBN_init(&bn_divisor);\n \tBN_set_word(&bn_divisor, divisor);\n-\n \tBN_copy(&result->numerator, &fraction->numerator);\n-\tBN_mul(&result->denominator, &fraction->denominator, &bn_divisor, get_BN_CTX());\n-\n+\tBN_mul(\t&result->denominator, \n+\t\t&fraction->denominator, \n+\t\t&bn_divisor, get_BN_CTX());\n \tBN_clear(&bn_divisor);\n \treturn result;\n }\n@@ -86,9 +101,8 @@ static struct fraction *get_one()\n \n static struct fraction *get_zero()\n {\n-\tif (!zero) {\n+\tif (!zero)\n \t\tzero = new_zero();\n-\t}\n \treturn zero;\n }\n \n@@ -99,28 +113,44 @@ static struct fraction *copy(struct frac\n \treturn to;\n }\n \n-static struct fraction *add(struct fraction *result, struct fraction *left, struct fraction *right)\n+static struct fraction *inc(struct fraction *left, struct fraction *right)\n {\n \tBIGNUM a, b, gcd;\n \n \tBN_init(&a);\n \tBN_init(&b);\n \tBN_init(&gcd);\n-\n-\tBN_mul(&a, &left->numerator, &right->denominator, get_BN_CTX());\n-\tBN_mul(&b, &left->denominator, &right->numerator, get_BN_CTX());\n-\tBN_mul(&result->denominator, &left->denominator, &right->denominator, get_BN_CTX());\n-\tBN_add(&result->numerator, &a, &b);\n-\n-\tBN_gcd(&gcd, &result->denominator, &result->numerator, get_BN_CTX());\n-\tBN_div(&result->denominator, NULL, &result->denominator, &gcd, get_BN_CTX());\n-\tBN_div(&result->numerator, NULL, &result->numerator, &gcd, get_BN_CTX());\n-\n+\tBN_mul(\t&a, \n+\t\t&left->numerator, \n+\t\t&right->denominator, \n+\t\tget_BN_CTX());\n+\tBN_mul(\t&b, \n+\t\t&left->denominator, \n+\t\t&right->numerator, \n+\t\tget_BN_CTX());\n+\tBN_mul( &left->denominator, \n+\t\t&left->denominator, \n+\t\t&right->denominator, \n+\t\tget_BN_CTX());\n+\tBN_add(\t&left->numerator, \n+\t\t&a, \n+\t\t&b);\n+\tBN_gcd(\t&gcd, \n+\t\t&left->denominator, \n+\t\t&left->numerator, \n+\t\tget_BN_CTX());\n+\tBN_div(\t&left->denominator, \n+\t\tNULL, &left->denominator, \n+\t\t&gcd, \n+\t\tget_BN_CTX());\n+\tBN_div(\t&left->numerator, \n+\t\tNULL, &left->numerator, \n+\t\t&gcd, \n+\t\tget_BN_CTX());\n \tBN_clear(&a);\n \tBN_clear(&b);\n \tBN_clear(&gcd);\n-\n-\treturn result;\n+\treturn left;\n }\n \n static int compare(struct fraction *left, struct fraction *right)\n@@ -130,15 +160,11 @@ static int compare(struct fraction *left\n \n \tBN_init(&a);\n \tBN_init(&b);\n-\n \tBN_mul(&a, &left->numerator, &right->denominator, get_BN_CTX());\n \tBN_mul(&b, &left->denominator, &right->numerator, get_BN_CTX());\n-\n \tresult = BN_cmp(&a, &b);\n-\n \tBN_clear(&a);\n \tBN_clear(&b);\n-\n \treturn result;\n }\n \n@@ -150,21 +176,15 @@ struct mass_counter {\n static struct mass_counter *new_mass_counter(struct commit *commit, struct fraction *pending)\n {\n \tstruct mass_counter *mass_counter = xmalloc(sizeof(*mass_counter));\n-\tmemset(mass_counter, 0, sizeof(*mass_counter));\n \n+\tASSERT(!commit->object.util, \"not already initialized\", commit);\n+\n+\tmemset(mass_counter, 0, sizeof(*mass_counter));\n \tinit_fraction(&mass_counter->seen);\n \tinit_fraction(&mass_counter->pending);\n-\n \tcopy(&mass_counter->pending, pending);\n \tcopy(&mass_counter->seen, get_zero());\n-\n-\tif (commit->object.util) {\n-\t\tdie(\"multiple attempts to initialize mass counter for %s\",\n-\t\t    sha1_to_hex(commit->object.sha1));\n-\t}\n-\n \tcommit->object.util = mass_counter;\n-\n \treturn mass_counter;\n }\n \n@@ -218,73 +238,63 @@ static int find_base_for_list(struct com\n \tstruct commit_list *cleaner = NULL;\n \tstruct commit_list *pending = NULL;\n \tstruct fraction injected;\n-\tinit_fraction(&injected);\n-\t*boundary = NULL;\n \n+\t*boundary = NULL;\n+\tinit_fraction(&injected);\n \tfor (; list; list = list->next) {\n \t\tstruct commit *item = list->item;\n \n-\t\tif (item->object.util) {\n-\t\t\tdie(\"%s:%d:%s: logic error: this should not have happened - commit %s\",\n-\t\t\t    __FILE__, __LINE__, __FUNCTION__,\n-\t\t\t    sha1_to_hex(item->object.sha1));\n-\t\t}\n-\n+\t\tASSERT(!item->object.util, \"no duplicates in list\", item);\n \t\tnew_mass_counter(list->item, get_one());\n-\t\tadd(&injected, &injected, get_one());\n-\n+\t\tinc(&injected, get_one());\n \t\tcommit_list_insert(list->item, &cleaner);\n \t\tcommit_list_insert(list->item, &pending);\n \t}\n-\n \twhile (!*boundary && pending && !ret) {\n \t\tstruct commit *latest = pop_commit(&pending);\n-\t\tstruct mass_counter *latest_node = (struct mass_counter *) latest->object.util;\n+\t\tstruct mass_counter *latest_node;\t\t\n \t\tint num_parents;\n-\n-\t\tif ((ret = parse_commit(latest)))\n+\t\t\n+\t\tret = parse_commit(latest);\n+\t\tif (ret)\n \t\t\tcontinue;\n-\t\tadd(&latest_node->seen, &latest_node->seen, &latest_node->pending);\n-\n+\t\tlatest_node = (struct mass_counter *) latest->object.util;\n+\t\tinc(&latest_node->seen, &latest_node->pending);\n \t\tnum_parents = count_parents(latest);\n \t\tif (num_parents) {\n-\t\t\tstruct fraction distribution;\n-\t\t\tstruct commit_list *parents;\n-\n-\t\t\tdivide(init_fraction(&distribution), &latest_node->pending, num_parents);\n+\t\t\tstruct fraction div;\n+\t\t\tstruct commit_list *parents = latest->parents;\n \n-\t\t\tfor (parents = latest->parents; parents; parents = parents->next) {\n-\t\t\t\tstruct commit *parent = parents->item;\n-\t\t\t\tstruct mass_counter *parent_node = (struct mass_counter *) parent->object.util;\n-\n-\t\t\t\tif (!parent_node) {\n-\t\t\t\t\tparent_node = new_mass_counter(parent, &distribution);\n-\t\t\t\t\tinsert_by_date(&pending, parent);\n-\t\t\t\t\tcommit_list_insert(parent, &cleaner);\n-\t\t\t\t} else {\n-\t\t\t\t\tif (!compare(&parent_node->pending, get_zero()))\n-\t\t\t\t\t\tinsert_by_date(&pending, parent);\n-\t\t\t\t\tadd(&parent_node->pending, &parent_node->pending, &distribution);\n-\t\t\t\t}\n+\t\t\tdivide(\tinit_fraction(&div), \n+\t\t\t\t&latest_node->pending, \n+\t\t\t\tnum_parents);\n+\t\t\twhile (parents) {\n+\t\t\t\tstruct mass_counter * pmc;\n+\t\t\t\tstruct commit *pc = parents->item;\n+\t\t\t\t\n+\t\t\t\tpmc = (struct mass_counter *) pc->object.util;\n+\t\t\t\tif (!pmc) {\n+\t\t\t\t\tpmc = new_mass_counter(pc, get_zero());\n+\t\t\t\t\tcommit_list_insert(pc, &cleaner);\n+\t\t\t\t}\t\t\t\t\n+\t\t\t\tif (!compare(&pmc->pending, get_zero()))\n+\t\t\t\t\tinsert_by_date(&pending, pc);\t\t\t\t\t\n+\t\t\t\tinc(&pmc->pending, &div);\n+\t\t\t\tparents = parents->next;\n \t\t\t}\n-\n-\t\t\tclear_fraction(&distribution);\n+\t\t\tclear_fraction(&div);\n \t\t}\n-\n \t\tif (!compare(&latest_node->seen, &injected))\n \t\t\t*boundary = latest;\n \t\tcopy(&latest_node->pending, get_zero());\n \t}\n-\n \twhile (cleaner) {\n \t\tstruct commit *next = pop_commit(&cleaner);\n \t\tfree_mass_counter((struct mass_counter *) next->object.util);\n \t\tnext->object.util = NULL;\n \t}\n-\n \tif (pending)\n \t\tfree_commit_list(pending);\n-\n \tclear_fraction(&injected);\n \treturn ret;\n }\n@@ -305,7 +315,6 @@ static int find_base(struct commit *head\n \t}\n \tret = find_base_for_list(pending, boundary);\n \tfree_commit_list(pending);\n-\n \treturn ret;\n }\n \n@@ -313,19 +322,15 @@ static int find_base(struct commit *head\n  * This procedure traverses to the boundary of the first epoch in the epoch\n  * sequence of the epoch headed at head_of_epoch. This is either the end of\n  * the maximal linear epoch or the base of a minimal non-linear epoch.\n- *\n- * The queue of pending nodes is sorted in reverse date order and each node\n- * is currently in the queue at most once.\n  */\n static int find_next_epoch_boundary(struct commit *head_of_epoch, struct commit **boundary)\n {\n-\tint ret;\n \tstruct commit *item = head_of_epoch;\n+\tint ret;\n \n \tret = parse_commit(item);\n \tif (ret)\n \t\treturn ret;\n-\n \tif (HAS_EXACTLY_ONE_PARENT(item)) {\n \t\t/*\n \t\t * We are at the start of a maximimal linear epoch.\n@@ -336,7 +341,6 @@ static int find_next_epoch_boundary(stru\n \t\t\tret = parse_commit(item);\n \t\t}\n \t\t*boundary = item;\n-\n \t} else {\n \t\t/*\n \t\t * Otherwise, we are at the start of a minimal, non-linear\n@@ -344,7 +348,6 @@ static int find_next_epoch_boundary(stru\n \t\t */\n \t\tret = find_base(item, boundary);\n \t}\n-\n \treturn ret;\n }\n \n@@ -354,34 +357,19 @@ static int find_next_epoch_boundary(stru\n static int is_parent_of(struct commit *parent, struct commit *child)\n {\n \tstruct commit_list *parents;\n-\tfor (parents = child->parents; parents; parents = parents->next) {\n-\t\tif (!memcmp(parent->object.sha1, parents->item->object.sha1,\n-\t\t            sizeof(parents->item->object.sha1)))\n+\n+\tfor (parents = child->parents; parents; parents = parents->next)\n+\t\tif (!memcmp(parent->object.sha1, \n+\t\t\t    parents->item->object.sha1,\n+\t\t\t    sizeof(parents->item->object.sha1)))\n \t\t\treturn 1;\n-\t}\n \treturn 0;\n }\n \n /*\n- * Pushes an item onto the merge order stack. If the top of the stack is\n- * marked as being a possible \"break\", we check to see whether it actually\n- * is a break.\n- */\n-static void push_onto_merge_order_stack(struct commit_list **stack, struct commit *item)\n-{\n-\tstruct commit_list *top = *stack;\n-\tif (top && (top->item->object.flags & DISCONTINUITY)) {\n-\t\tif (is_parent_of(top->item, item)) {\n-\t\t\ttop->item->object.flags &= ~DISCONTINUITY;\n-\t\t}\n-\t}\n-\tcommit_list_insert(item, stack);\n-}\n-\n-/*\n  * Marks all interesting, visited commits reachable from this commit\n  * as uninteresting. We stop recursing when we reach the epoch boundary,\n- * an unvisited node or a node that has already been marking uninteresting.\n+ * an unvisited node or a node that has already been marked uninteresting.\n  *\n  * This doesn't actually mark all ancestors between the start node and the\n  * epoch boundary uninteresting, but does ensure that they will eventually\n@@ -397,7 +385,6 @@ static void mark_ancestors_uninteresting\n \tstruct commit_list *next;\n \n \tcommit->object.flags |= UNINTERESTING;\n-\n \t/*\n \t * We only need to recurse if\n \t *      we are not on the boundary and\n@@ -413,95 +400,89 @@ static void mark_ancestors_uninteresting\n \t * uninteresting or will be once the sort_first_epoch\n \t * traverse reaches them.\n \t */\n-\n \tif (uninteresting || boundary || !visited)\n \t\treturn;\n-\n \tfor (next = commit->parents; next; next = next->next)\n \t\tmark_ancestors_uninteresting(next->item);\n }\n \n /*\n- * Sorts the nodes of the first epoch of the epoch sequence of the epoch headed at head\n- * into merge order.\n+ * Sorts the nodes of the first epoch of the epoch sequence of the epoch \n+ * headed at head into merge order.\n  */\n-static void sort_first_epoch(struct commit *head, struct commit_list **stack)\n+static void sort_first_epoch(struct commit *head, struct commit_list **stack, struct epoch_methods * methods)\n {\n-\tstruct commit_list *parents;\n-\tstruct commit_list *reversed_parents = NULL;\n+\tstruct commit_list *reversed = NULL;\n+        struct commit * top = *stack?(*stack)->item:NULL;\n \n+\tif (IS_VISITED(head)) \n+\t\treturn;\n \thead->object.flags |= VISITED;\n \n \t/*\n-\t * parse_commit() builds the parent list in reverse order with respect\n-\t * to the order of the git-commit-tree arguments. So we need to reverse\n-\t * this list to output the oldest (or most \"local\") commits last.\n-\t */\n-\tfor (parents = head->parents; parents; parents = parents->next)\n-\t\tcommit_list_insert(parents->item, &reversed_parents);\n-\n-\t/*\n-\t * TODO: By sorting the parents in a different order, we can alter the\n-\t * merge order to show contemporaneous changes in parallel branches\n-\t * occurring after \"local\" changes. This is useful for a developer\n-\t * when a developer wants to see all changes that were incorporated\n-\t * into the same merge as her own changes occur after her own\n-\t * changes.\n-\t */\n-\n-\twhile (reversed_parents) {\n-\t\tstruct commit *parent = pop_commit(&reversed_parents);\n+         * give the sort tactics a chance to mark this commit uninteresting\n+         */\n+\tif (methods->marker)\n+\t\t(*(methods->marker)) (head);\n \n-\t\tif (head->object.flags & UNINTERESTING) {\n+        if (IS_BASE(head)) {\n+\t\tASSERT(!top, \"stack empty on visit to base\", head);\n+\t} else {\n+\t\t/*\n+\t\t * Reverse the parents so that \"local\" parents are pushed first\n+\t\t * and hence print last. \n+\t\t */\n+\t\tif (!head->object.util) {\n \t\t\t/*\n-\t\t\t * Propagates the uninteresting bit to all parents.\n-\t\t\t * if we have already visited this parent, then\n-\t\t\t * the uninteresting bit will be propagated to each\n-\t\t\t * reachable commit that is still not marked\n-\t\t\t * uninteresting and won't otherwise be reached.\n+\t\t\t * make a copy of the parents, but don't destroy them\n \t\t\t */\n-\t\t\tmark_ancestors_uninteresting(parent);\n+                        reversed = copy_parents_in_header_order(head);\n+\t\t} else {\n+\t\t\t/*\n+                         * sort_list_in_local_first_order already does this\n+\t\t\t */\n+\t\t\treversed = (struct commit_list *)head->object.util;\n+                        head->object.util = NULL;\n \t\t}\n+\t\twhile (reversed) {\n+\t\t\tstruct commit *parent = pop_commit(&reversed);\n \n-\t\tif (!(parent->object.flags & VISITED)) {\n-\t\t\tif (parent->object.flags & BOUNDARY) {\n-\t\t\t\tif (*stack) {\n-\t\t\t\t\tdie(\"something else is on the stack - %s\",\n-\t\t\t\t\t    sha1_to_hex((*stack)->item->object.sha1));\n-\t\t\t\t}\n-\t\t\t\tpush_onto_merge_order_stack(stack, parent);\n-\t\t\t\tparent->object.flags |= VISITED;\n-\n-\t\t\t} else {\n-\t\t\t\tsort_first_epoch(parent, stack);\n-\t\t\t\tif (reversed_parents) {\n-\t\t\t\t\t/*\n-\t\t\t\t\t * This indicates a possible\n-\t\t\t\t\t * discontinuity it may not be be\n-\t\t\t\t\t * actual discontinuity if the head\n-\t\t\t\t\t * of parent N happens to be the tail\n-\t\t\t\t\t * of parent N+1.\n-\t\t\t\t\t *\n-\t\t\t\t\t * The next push onto the stack will\n-\t\t\t\t\t * resolve the question.\n-\t\t\t\t\t */\n-\t\t\t\t\t(*stack)->item->object.flags |= DISCONTINUITY;\n-\t\t\t\t}\n+\t\t\tif (IS_UNINTERESTING(head)) {\n+\t\t\t\t/*\n+\t\t\t\t * Propagates the uninteresting bit to all parents.\n+\t\t\t\t * If we have already visited this parent, then\n+\t\t\t\t * the uninteresting bit will be propagated to each\n+\t\t\t\t * reachable commit that is still not marked\n+\t\t\t\t * uninteresting and won't otherwise be reached by\n+\t\t\t\t * this traversal.\n+\t\t\t\t */\n+\t\t\t\tmark_ancestors_uninteresting(parent);\n+\t\t\t}\n+\t\t\tsort_first_epoch(parent, stack, methods);\n+\t\t\ttop=(*stack)?(*stack)->item:NULL;\n+\t\t\tif (reversed) {\n+\t\t\t\t/*\n+\t\t\t\t * This indicates a possible discontinuity it \n+                                 * may not be be actual discontinuity if the head\n+\t\t\t\t * of parent N happens to be the tail of \n+                                 * parent N+1. The next push onto the stack will\n+\t\t\t\t * resolve the question.\n+\t\t\t\t */\n+\t\t\t\ttop->object.flags |= DISCONTINUITY;\n \t\t\t}\n \t\t}\n-\t}\n-\n-\tpush_onto_merge_order_stack(stack, head);\n+        }\n+\tif (top && IS_DISCONTINUITY(top) && is_parent_of(top, head))\n+\t\ttop->object.flags &= ~DISCONTINUITY;\n+\tcommit_list_insert(head, stack);\n }\n \n /*\n  * Emit the contents of the stack.\n- *\n  * The stack is freed and replaced by NULL.\n- *\n  * Sets the return value to STOP if no further output should be generated.\n  */\n-static int emit_stack(struct commit_list **stack, emitter_func emitter)\n+static int emit_stack(struct commit_list **stack, struct epoch_methods * methods)\n {\n \tunsigned int seen = 0;\n \tint action = CONTINUE;\n@@ -510,15 +491,98 @@ static int emit_stack(struct commit_list\n \t\tstruct commit *next = pop_commit(stack);\n \t\tseen |= next->object.flags;\n \t\tif (*stack)\n-\t\t\taction = (*emitter) (next);\n+\t\t\taction = (*(methods->emitter)) (next);\n \t}\n-\n \tif (*stack) {\n \t\tfree_commit_list(*stack);\n \t\t*stack = NULL;\n+\t}\t\n+\tif (seen & UNINTERESTING) {\n+\t\t/**\n+\t\t * We stop at the base of the stack, rather than\n+\t\t * when we encounter the first UNINTERESTING flag.\n+\t\t * \n+\t\t * The reason is that there may still be interesting\n+\t\t * stuff on the stack but once we reach the base\n+\t\t * there can be no more interesting stuff by definition\n+\t\t * of what the base of an epoch is - everything reachable\n+\t\t * from the base is also reachable from the UNINTERESTING\n+\t\t * node and hence is uninteresting.\n+\t\t */\t\t \n+\t\taction = STOP;\n \t}\n+\treturn action;\n+}\n \n-\treturn (action == STOP || (seen & UNINTERESTING)) ? STOP : CONTINUE;\n+/*\n+ * Copy the parents of the commit and store the head of the list\n+ * in object.util. Return the address of object.util itself.\n+ */\n+static struct commit_list ** copy_and_store_parents(struct commit * commit)\n+{\n+\tstruct commit_list * copied=copy_parents_in_header_order(commit);\n+\n+        commit->object.util = copied;\n+\treturn (struct commit_list **)&commit->object.util;\n+}\n+\n+/*\n+ * Sort a list of commits in local first order. A commit is \"local\" \n+ * if any of its ancestors (except the base) causes (*local_test)() to \n+ * return a non-zero value.\n+ *\n+ * The sorted list is returned in *sorted. A side effect of this function \n+ * is to set each object.util pointer in each ancestor up until the base \n+ * to a list of parents is sorted in local first order.\n+ *\n+ * Does nothing if the list is empty.\n+ * \n+ * The return value contains LOCAL if any of the list is local or had a \n+ * local ancestor.\n+ */\n+static unsigned int sort_list_in_local_first_order(struct commit_list ** list, struct epoch_methods * methods)\n+{\n+\tstruct commit_list *local = NULL;\n+\tstruct commit_list **local_tail = &local;\n+\tstruct commit_list **non_local_ptr = list;\n+\n+\tASSERT(methods->local_test, \"only called if there is a local test\", NULL);\n+\tif (!*list) {\n+\t\treturn 0;\n+\t}\n+\tfor (non_local_ptr = list; *non_local_ptr; ) {\n+\t\tstruct commit *item = (*non_local_ptr)->item;\n+\t\tstruct commit_list ** copied;\n+\n+\t\t/*\n+\t\t * We don't descend past the base or visit\n+\t\t * a commit we have already visited.\n+                 */\n+\t\tif (!IS_BASE(item) && !item->object.util) {\n+\t\t\tif ((*(methods->local_test)) (item))\n+\t\t\t\titem->object.flags |= LOCAL;\n+\t\t\tcopied=copy_and_store_parents(item);\n+\t\t\titem->object.flags |= sort_list_in_local_first_order(copied, methods);\n+\t\t}\n+\t\t/**\n+\t\t * Move any local item onto the local list.\n+                 */\n+\t\tif (IS_LOCAL(item)) {\n+\t\t\t*local_tail = *non_local_ptr;\n+\t\t\t*non_local_ptr = (*non_local_ptr)->next;\n+\t\t\t(*local_tail)->next = NULL;\n+\t\t\tlocal_tail = &(*local_tail)->next;\n+\t\t} else {\n+\t\t\tnon_local_ptr = &(*non_local_ptr)->next;\n+\t\t}\n+\t}\n+\t/*\n+         * Splice the non-local list onto the end and\n+         * return with the LOCAL flag from the first item.\n+         */\n+        *local_tail = *list;\n+        *list = local;\n+\treturn ((*list)->item->object.flags & LOCAL);\n }\n \n /*\n@@ -528,51 +592,53 @@ static int emit_stack(struct commit_list\n  * Note: this algorithm currently leaves traces of its execution in the\n  * object flags of nodes it discovers. This should probably be fixed.\n  */\n-static int sort_in_merge_order(struct commit *head_of_epoch, emitter_func emitter)\n+static int sort_in_merge_order(struct commit *head_of_epoch, struct epoch_methods * methods)\n {\n \tstruct commit *next = head_of_epoch;\n \tint ret = 0;\n \tint action = CONTINUE;\n \n-\tret = parse_commit(head_of_epoch);\n-\n+\tret = parse_commit(next);\n+        next->object.flags |= BOUNDARY;\n \twhile (next && next->parents && !ret && (action != STOP)) {\n \t\tstruct commit *base = NULL;\n \n \t\tret = find_next_epoch_boundary(next, &base);\n \t\tif (ret)\n \t\t\treturn ret;\n-\t\tnext->object.flags |= BOUNDARY;\n+\t\tnext->object.flags &= ~(BASE|VISITED);\n \t\tif (base)\n-\t\t\tbase->object.flags |= BOUNDARY;\n-\n+\t\t\tbase->object.flags |= (BOUNDARY|BASE);\n \t\tif (HAS_EXACTLY_ONE_PARENT(next)) {\n \t\t\twhile (HAS_EXACTLY_ONE_PARENT(next)\n \t\t\t       && (action != STOP)\n \t\t\t       && !ret) {\n-\t\t\t\tif (next->object.flags & UNINTERESTING) {\n+\t\t\t\tif (methods->marker)\n+\t\t\t\t\t(*(methods->marker)) (next);\n+\n+\t\t\t\tif (IS_UNINTERESTING(next))\n \t\t\t\t\taction = STOP;\n-\t\t\t\t} else {\n-\t\t\t\t\taction = (*emitter) (next);\n-\t\t\t\t}\n+\t\t\t\telse\n+\t\t\t\t\taction = (*(methods->emitter)) (next);\n+\n \t\t\t\tif (action != STOP) {\n \t\t\t\t\tnext = next->parents->item;\n \t\t\t\t\tret = parse_commit(next);\n \t\t\t\t}\n \t\t\t}\n-\n \t\t} else {\n \t\t\tstruct commit_list *stack = NULL;\n-\t\t\tsort_first_epoch(next, &stack);\n-\t\t\taction = emit_stack(&stack, emitter);\n+\t\t\tif (methods->local_test) \n+\t\t\t\tsort_list_in_local_first_order(\n+\t\t\t\t\tcopy_and_store_parents(next),\n+\t\t\t\t\tmethods);\n+\t\t\tsort_first_epoch(next, &stack, methods);\n+\t\t\taction = emit_stack(&stack, methods);\n \t\t\tnext = base;\n \t\t}\n \t}\n-\n-\tif (next && (action != STOP) && !ret) {\n-\t\t(*emitter) (next);\n-\t}\n-\n+\tif (next && (action != STOP) && !ret)\n+\t\t(*(methods->emitter))(next);\n \treturn ret;\n }\n \n@@ -582,68 +648,65 @@ static int sort_in_merge_order(struct co\n  * in this subgraph using the sort_first_epoch algorithm. Once we have\n  * reached the base we can continue sorting using sort_in_merge_order.\n  */\n-int sort_list_in_merge_order(struct commit_list *list, emitter_func emitter)\n+int sort_list_in_merge_order(struct commit_list *list, struct epoch_methods * methods)\n {\n \tstruct commit_list *stack = NULL;\n+\tstruct commit_list *filtered = NULL;\n \tstruct commit *base;\n \tint ret = 0;\n \tint action = CONTINUE;\n-\tstruct commit_list *reversed = NULL;\n \n+\tif (!methods) \n+\t\tdie(\"methods argument must not be null\");\n+\tif (!methods->emitter)\n+\t\tdie(\"an emitter method must be supplied\");\n+\t/**\n+         * Filter the list by removing all uninteresting\n+         * and duplicate items. Removing duplicates\n+         * prevents an assertion failure in find_base_for_list\n+\t */\n \tfor (; list; list = list->next) {\n \t\tstruct commit *next = list->item;\n \n-\t\tif (!(next->object.flags & UNINTERESTING)) {\n-\t\t\tif (next->object.flags & DUPCHECK) {\n-\t\t\t\tfprintf(stderr, \"%s: duplicate commit %s ignored\\n\",\n-\t\t\t\t\t__FUNCTION__, sha1_to_hex(next->object.sha1));\n-\t\t\t} else {\n-\t\t\t\tnext->object.flags |= DUPCHECK;\n-\t\t\t\tcommit_list_insert(list->item, &reversed);\n-\t\t\t}\n+\t\tif (!(next->object.flags & (UNINTERESTING|DUPCHECK))) {\n+\t\t\tnext->object.flags |= DUPCHECK;\n+\t\t\tcommit_list_insert(list->item, &filtered);\n \t\t}\n \t}\n-\n-\tif (!reversed->next) {\n-\t\t/*\n-\t\t * If there is only one element in the list, we can sort it\n-\t\t * using sort_in_merge_order.\n-\t\t */\n-\t\tbase = reversed->item;\n-\t} else {\n+\t/**\n+\t * Find the common base of the listed commits and\n+         * sort up until that base.\n+\t */\n+\tif (!filtered->next)\t\t\n+\t\tbase = filtered->item; /* sort with sort_in_merge_order */\n+\telse {\n \t\t/*\n \t\t * Otherwise, we search for the base of the list.\n-\t\t */\n-\t\tret = find_base_for_list(reversed, &base);\n+\t\t */\t\t \n+\t\tret = find_base_for_list(filtered, &base);\n \t\tif (ret)\n \t\t\treturn ret;\n \t\tif (base)\n-\t\t\tbase->object.flags |= BOUNDARY;\n-\n-\t\twhile (reversed) {\n-\t\t\tsort_first_epoch(pop_commit(&reversed), &stack);\n-\t\t\tif (reversed) {\n-\t\t\t\t/*\n-\t\t\t\t * If we have more commits to push, then the\n-\t\t\t\t * first push for the next parent may (or may\n-\t\t\t\t * not) represent a discontinuity with respect\n-\t\t\t\t * to the parent currently on the top of\n-\t\t\t\t * the stack.\n-\t\t\t\t *\n-\t\t\t\t * Mark it for checking here, and check it\n-\t\t\t\t * with the next push. See sort_first_epoch()\n-\t\t\t\t * for more details.\n-\t\t\t\t */\n+\t\t\tbase->object.flags |= (BOUNDARY|BASE);\n+\t\tif (methods->local_test)\n+\t\t\tsort_list_in_local_first_order(&filtered, methods);\n+\t\twhile (filtered) {\n+\t\t\tsort_first_epoch(pop_commit(&filtered), &stack, methods);\n+\t\t\t/* per discontintuity logic in sort_first_epoch */\n+\t\t\tif (filtered)\n \t\t\t\tstack->item->object.flags |= DISCONTINUITY;\n-\t\t\t}\n \t\t}\n-\n-\t\taction = emit_stack(&stack, emitter);\n-\t}\n-\n-\tif (base && (action != STOP)) {\n-\t\tret = sort_in_merge_order(base, emitter);\n+\t\taction = emit_stack(&stack, methods);\n \t}\n-\n+        /**\n+         * Sort the rest with the sort_in_merge_order algorithm.\n+         */\n+\tif (base && (action != STOP))\n+\t\tret = sort_in_merge_order(base, methods);\n \treturn ret;\n }\n+\n+void init_epoch_methods(struct epoch_methods * methods)\n+{\n+\tmemset(methods, 0, sizeof(*methods));\n+}\ndiff --git a/epoch.h b/epoch.h\n--- a/epoch.h\n+++ b/epoch.h\n@@ -1,20 +1,56 @@\n #ifndef EPOCH_H\n #define EPOCH_H\n \n-\n-// return codes for emitter_func\n-#define STOP     0\n-#define CONTINUE 1\n-#define DO       2\n-typedef int (*emitter_func) (struct commit *); \n-\n-int sort_list_in_merge_order(struct commit_list *list, emitter_func emitter);\n-\n+/**\n+ * Flags used by merge order logic and also by rev-list.c\n+ */\n #define UNINTERESTING  (1u<<2)\n #define BOUNDARY       (1u<<3)\n #define VISITED        (1u<<4)\n #define DISCONTINUITY  (1u<<5)\n #define DUPCHECK       (1u<<6)\n+#define LOCAL          (1u<<7)\n+#define BASE           (1u<<8)\n \n+/**\n+ * Return codes for emitter method. Also used by rev-list.c\n+ */\n+#define STOP     0\n+#define CONTINUE 1\n+#define DO       2\n \n-#endif\t\t\t\t/* EPOCH_H */\n+struct epoch_methods {\n+\t/*\n+\t * Returns 0 if traversal should stop, non-zero if it should continue.\n+\t */\n+\tint (*emitter)(struct commit *);\n+\t/* \n+\t * Returns non-zero if the commit is regarded \"local\", 0 otherwise.\n+\t */\n+\tint (*local_test)(struct commit *);\n+\t/* \n+\t * Implementers may use this method to mark commits uninteresting\n+\t * according to some locally determined criteria. The tree\n+\t * will be pruned at any commit so marked.\n+\t */\n+\tvoid (*marker)(struct commit *);\n+};\n+\n+/**\n+ * Initializes an epoch_methods structure which\n+ * may be customized by the caller by overriding any of the method pointers.\n+ */\n+extern void init_epoch_methods(struct epoch_methods *);\n+\n+/**\n+ * Sorts the list of commits in merge order, using the methods specified\n+ * to customize the tactics of the search.\n+ * \n+ * The prune points should be marked with the UNINTERESTING flags. \n+ * \n+ * Note: this algorithm is dirty in the sense that it leaves traces\n+ * of its execution in the object.flags word of some or all of the commits\n+ * visited.\n+ */\n+extern int sort_list_in_merge_order(struct commit_list *list, struct epoch_methods * methods);\n+#endif\t/* EPOCH_H */\ndiff --git a/rev-list.c b/rev-list.c\n--- a/rev-list.c\n+++ b/rev-list.c\n@@ -1,18 +1,24 @@\n #include \"cache.h\"\n #include \"commit.h\"\n #include \"epoch.h\"\n+#include \"user.h\"\n \n #define SEEN\t\t(1u << 0)\n #define INTERESTING\t(1u << 1)\n \n static const char rev_list_usage[] =\n \t\"usage: git-rev-list [OPTION] commit-id <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      \"  --header\\n\"\n-\t\t      \"  --pretty\\n\"\n-\t\t      \"  --merge-order [ --show-breaks ]\";\n+\t\t\t\"  --max-count=nr\\n\"\n+\t\t\t\"  --max-age=epoch\\n\"\n+\t\t\t\"  --min-age=epoch\\n\"\n+\t\t\t\"  --header\\n\"\n+\t\t\t\"  --parents\\n\"\n+\t\t\t\"  --pretty\\n\"\n+\t\t\t\"  --merge-order\\n\"\n+\t\t\t\"  --wrt-author\\n\"\n+\t\t\t\"  --prune-at-author\\n\"\n+\t\t\t\"  --author=author@domain\\n\"\n+\t\t\t\"  --show-breaks ]\";\n \n static int verbose_header = 0;\n static int show_parents = 0;\n@@ -24,6 +30,9 @@ static int max_count = -1;\n static enum cmit_fmt commit_format = CMIT_FMT_RAW;\n static int merge_order = 0;\n static int show_breaks = 0;\n+static char * local_author = NULL;\n+static int prune_at_author = 0;\n+static int wrt_author = 0;\n \n static void show_commit(struct commit *commit)\n {\n@@ -147,6 +156,26 @@ static enum cmit_fmt get_commit_format(c\n \tusage(rev_list_usage);\t\n }\t\t\t\n \n+static int is_local_author(struct commit * commit)\n+{\n+\tstatic char author[16384];\n+\tif (copy_author(commit, author, sizeof(author)) > 0) {\n+\t\tif (strstr(author, local_author)) {\n+\t\t\treturn 1;\n+\t\t}\n+\t}\n+\treturn 0;\n+}\n+\n+static void mark_authors_own_uninteresting(struct commit * commit)\n+{\n+        if (is_local_author(commit)) {\n+\t\tstruct commit_list * parents = commit->parents;\n+\t\tfor (;parents;parents=parents->next) {\n+\t\t\tparents->item->object.flags |= UNINTERESTING;\n+\t\t}\n+        }\n+}\n \n int main(int argc, char **argv)\n {\n@@ -186,14 +215,26 @@ int main(int argc, char **argv)\n \t\t\tshow_parents = 1;\n \t\t\tcontinue;\n \t\t}\n-\t\tif (!strncmp(arg, \"--merge-order\", 13)) {\n+\t\tif (!strcmp(arg, \"--merge-order\")) {\n \t\t        merge_order = 1;\n \t\t\tcontinue;\n \t\t}\n-\t\tif (!strncmp(arg, \"--show-breaks\", 13)) {\n+\t\tif (!strcmp(arg, \"--show-breaks\")) {\n \t\t\tshow_breaks = 1;\n \t\t\tcontinue;\n \t\t}\n+\t\tif (!strncmp(arg, \"--author=\", 9)) {\n+                        local_author=strdup(&arg[9]);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(arg, \"--wrt-author\")) {\n+\t\t        wrt_author = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(arg, \"--prune-at-author\")) {\n+\t\t        prune_at_author = 1;\n+\t\t\tcontinue;\n+\t\t}\n \n \t\tflags = 0;\n \t\tif (*arg == '^') {\n@@ -201,7 +242,7 @@ int main(int argc, char **argv)\n \t\t\targ++;\n \t\t\tlimited = 1;\n \t\t}\n-\t\tif (get_sha1(arg, sha1) || (show_breaks && !merge_order))\n+\t\tif (get_sha1(arg, sha1))\n \t\t\tusage(rev_list_usage);\n \t\tcommit = lookup_commit_reference(sha1);\n \t\tif (!commit || parse_commit(commit) < 0)\n@@ -213,14 +254,38 @@ int main(int argc, char **argv)\n \tif (!list)\n \t\tusage(rev_list_usage);\n \n+        if (!merge_order) \n+        \tmerge_order = wrt_author || prune_at_author || show_breaks;        \t\n+\n \tif (!merge_order) {\t\t\n \t        if (limited)\n \t\t\tlist = limit_list(list);\n \t\tshow_commit_list(list);\n-\t} else {\n-\t\tif (sort_list_in_merge_order(list, &process_commit)) {\n-\t\t\t  die(\"merge order sort failed\\n\");\n-\t\t}\n+\t} else {\t\t\n+\t\tstruct epoch_methods methods;\n+\t\n+\t\tinit_epoch_methods(&methods);\n+\t\tif ((prune_at_author|wrt_author) && !local_author) {\n+\t\t\tlocal_author = gitenv(\"GIT_AUTHOR_EMAIL\") ? : NULL;\t\t\t    \n+\t\t\tif (!local_author)\n+\t\t\t\t get_real_identity(&local_author, NULL);\n+\t\t\telse\n+\t\t\t\t local_author = strdup(local_author);\n+\t\t}\t\t\n+\t\tif (local_author) {\n+\t\t\t/* add delimiters to improve accuracy of match */\n+\t\t\tchar * tmp=xmalloc(strlen(local_author)+3);\n+\t\t\tsprintf(tmp, \"<%s>\", local_author);\n+\t\t\tfree(local_author);\n+\t\t\tlocal_author = tmp;\n+\t\t}\t\t \n+\t\tmethods.emitter = &process_commit;\t\t  \n+\t\tif (wrt_author)\n+\t\t\tmethods.local_test = &is_local_author;\n+\t\tif (prune_at_author)\n+\t\t\tmethods.marker = &mark_authors_own_uninteresting;\n+\t\tif (sort_list_in_merge_order(list, &methods))\n+\t\t\tdie(\"merge order sort failed\\n\");\n \t}\n \n \treturn 0;\ndiff --git a/t/t6001-rev-list-merge-order.sh b/t/t6001-rev-list-merge-order.sh\nold mode 100644\nnew mode 100755\n--- a/t/t6001-rev-list-merge-order.sh\n+++ b/t/t6001-rev-list-merge-order.sh\n@@ -6,10 +6,53 @@\n test_description='Test rev-list --merge-order\n '\n . ./test-lib.sh\n+[ -d .git/refs/tags ] || mkdir -p .git/refs/tags\n \n-function do_commit\n+sed_script=\"\";\n+\n+function tag\n+{\n+\tlocal _tag=$1\n+\t[ -f .git/refs/tags/$_tag ] || error \"tag: \\\"$_tag\\\" does not exist\"\n+\tcat .git/refs/tags/$_tag\n+}\n+\n+function commit\n+{\n+\tlocal _tag=$1\n+        local _tree=$2\n+\tshift 2\n+    \techo $_tag | git-commit-tree $(tag $_tree) \"$@\"\n+}\n+\n+function save_tag\n {\n-    git-commit-tree \"$@\" </dev/null\n+\tlocal _tag=$1\t\n+\t[ -n \"$_tag\" ] || error \"usage: do_commit tag commit-args ...\"\n+\tshift 1\n+    \t\"$@\" >.git/refs/tags/$_tag\n+    \tsed_script=\"s/$(tag $_tag)/$_tag/g${sed_script+;}$sed_script\"\n+}\n+\n+function entag\n+{\n+\tsed \"$sed_script\"\n+}\n+\n+function as_author\n+{\n+\tlocal _author=$1\n+\tshift 1\n+        local _save=$GIT_AUTHOR_EMAIL\n+\n+\texport GIT_AUTHOR_EMAIL=\"$_author\"\n+\t\"$@\"\n+        export GIT_AUTHOR_EMAIL=\"$_save\"\n+}\n+\n+function hide_error\n+{\n+\t\"$@\" 2>/dev/null\n }\n \n function check_adjacency\n@@ -28,43 +71,97 @@ function check_adjacency\n     done\n }\n \n-function sed_script\n+function check_output\n {\n-   for c in root a0 a1 a2 a3 a4 b1 b2 b3 b4 c1 c2 c3 l0 l1 l2 l3 l4 l5\n-   do\n-       echo -n \"s/${!c}/$c/;\"\n-   done\n+\tlocal _name=$1\n+\tshift 1\n+\tif \"$@\" | entag > $_name.actual\n+\tthen\n+\t\tdiff $_name.expected $_name.actual\n+\telse\n+\t\treturn 1;\n+\tfi\n+\t\n+}\n+\n+function name_from_description\n+{\n+        tr \"'\" '.' | tr '~`!@#$%^&*()_+={}[]|\\;:\"<>,/?-' '.' | tr -s '.' | tr ' ' '-' | tr '[A-Z]' '[a-z]'\n+}\n+\n+#\n+# stdin contains expected result\n+#\n+function test_output_expect_success\n+{\t\n+\tlocal _description=$1\n+\tshift 1\n+        local _name=$(echo $_description | name_from_description)\n+\tcat > $_name.expected\n+\ttest_expect_success \"$_description\" \"check_output $_name $*\" \n }\n \n date >path0\n git-update-cache --add path0\n-tree=$(git-write-tree)\n-root=$(do_commit $tree 2>/dev/null)\n-export GIT_COMMITTER_NAME=foobar  # to guarantee that the commit is different\n-l0=$(do_commit $tree -p $root)\n-l1=$(do_commit $tree -p $l0)\n-l2=$(do_commit $tree -p $l1)\n-a0=$(do_commit $tree -p $l2)\n-a1=$(do_commit $tree -p $a0)\n-export GIT_COMMITTER_NAME=foobar2 # to guarantee that the commit is different\n-b1=$(do_commit $tree -p $a0)\n-c1=$(do_commit $tree -p $b1)\n-export GIT_COMMITTER_NAME=foobar3 # to guarantee that the commit is different\n-b2=$(do_commit $tree -p $b1)\n-b3=$(do_commit $tree -p $b2)\n-c2=$(do_commit $tree -p $c1 -p $b2)\n-c3=$(do_commit $tree -p $c2)\n-a2=$(do_commit $tree -p $a1)\n-a3=$(do_commit $tree -p $a2)\n-b4=$(do_commit $tree -p $b3 -p $a3)\n-a4=$(do_commit $tree -p $a3 -p $b4 -p $c3)\n-l3=$(do_commit $tree -p $a4)\n-l4=$(do_commit $tree -p $l3)\n-l5=$(do_commit $tree -p $l4)\n-echo $l5 > .git/HEAD\n+save_tag tree git-write-tree\n+hide_error save_tag root commit root tree\n+save_tag l0 commit l0 tree -p root\n+save_tag l1 commit l1 tree -p l0\n+save_tag l2 commit l2 tree -p l1\n+save_tag a0 commit a0 tree -p l2\n+save_tag a1 commit a1 tree -p a0\n+save_tag b1 commit b1 tree -p a0\n+save_tag c1 commit c1 tree -p b1\n+as_author foobar@example.com save_tag b2 commit b2 tree -p b1\n+save_tag b3 commit b2 tree -p b2\n+save_tag c2 commit c2 tree -p c1 -p b2\n+save_tag c3 commit c3 tree -p c2\n+save_tag a2 commit a2 tree -p a1\n+save_tag a3 commit a3 tree -p a2\n+save_tag b4 commit b4 tree -p b3 -p a3\n+save_tag a4 commit a4 tree -p a3 -p b4 -p c3\n+save_tag l3 commit l3 tree -p a4\n+save_tag l4 commit l4 tree -p l3\n+save_tag l5 commit l5 tree -p l4\n+hide_error save_tag e1 as_author e@example.com commit e1 tree\n+save_tag e2 as_author e@example.com commit e2 tree -p e1\n+save_tag f1 as_author f@example.com commit f1 tree -p e1\n+save_tag e3 as_author e@example.com commit e3 tree -p e2\n+save_tag f2 as_author f@example.com commit f2 tree -p f1\n+save_tag e4 as_author e@example.com commit e4 tree -p e3 -p f2\n+save_tag e5 as_author e@example.com commit e5 tree -p e4\n+save_tag f3 as_author f@example.com commit f3 tree -p f2\n+save_tag f4 as_author f@example.com commit f4 tree -p f3\n+save_tag e6 as_author e@example.com commit e6 tree -p e5 -p f4\n+save_tag f5 as_author f@example.com commit f5 tree -p f4\n+save_tag f6 as_author f@example.com commit f6 tree -p f5 -p e6\n+save_tag e7 as_author e@example.com commit e7 tree -p e6\n+save_tag e8 as_author e@example.com commit e8 tree -p e7\n+save_tag e9 as_author e@example.com commit e9 tree -p e8\n+save_tag f7 as_author f@example.com commit f7 tree -p f6\n+save_tag f8 as_author f@example.com commit f8 tree -p f7\n+save_tag f9 as_author f@example.com commit f9 tree -p f8\n+save_tag e10 as_author e@example.com commit e1 tree -p e9 -p f8\n+tag l5 > .git/HEAD\n+\n+#\n+# cd to t/trash and use \n+#\n+#    git-rev-list ... 2>&1 | sed \"$(cat sed.script)\" \n+#\n+# if you ever want to manually debug the operation of git-rev-list\n+#\n+echo $sed_script > sed.script\n+\n+test_expect_success 'Testing that the rev-list has correct number of entries' 'git-rev-list HEAD | wc -l | tr -s \" \"' <<EOF\n+19\n+EOF\n+\n+normal_adjacency_count=$(git-rev-list HEAD | check_adjacency | grep -c \"\\^\" | tr -d ' ')\n+merge_order_adjacency_count=$(git-rev-list --merge-order HEAD | check_adjacency | grep -c \"\\^\" | tr -d ' ')\n+test_expect_success 'Testing that --merge-order produces as many or fewer discontinuities' '[ $merge_order_adjacency_count -le $normal_adjacency_count ]'\n \n-git-rev-list --merge-order --show-breaks HEAD | sed \"$(sed_script)\" > actual-merge-order\n-cat > expected-merge-order <<EOF\n+test_output_expect_success 'Simple Merge Order Test' 'git-rev-list --merge-order --show-breaks HEAD' <<EOF\n = l5\n | l4\n | l3\n@@ -86,15 +183,7 @@ cat > expected-merge-order <<EOF\n = root\n EOF\n \n-git-rev-list HEAD | check_adjacency | sed \"$(sed_script)\" > actual-default-order\n-normal_adjacency_count=$(git-rev-list HEAD | check_adjacency | grep -c \"\\^\" | tr -d ' ')\n-merge_order_adjacency_count=$(git-rev-list --merge-order HEAD | check_adjacency | grep -c \"\\^\" | tr -d ' ')\n-\n-test_expect_success 'Testing that the rev-list has correct number of entries' '[ $(git-rev-list HEAD | wc -l) -eq 19 ]'\n-test_expect_success 'Testing that --merge-order produces the correct result' 'diff expected-merge-order actual-merge-order'\n-test_expect_success 'Testing that --merge-order produces as many or fewer discontinuities' '[ $merge_order_adjacency_count -le $normal_adjacency_count ]'\n-\n-cat > expected-merge-order-1 <<EOF\n+test_output_expect_success 'Multiple heads' 'git-rev-list --merge-order a3 b3 c3' <<EOF\n c3\n c2\n c1\n@@ -111,10 +200,7 @@ l0\n root\n EOF\n \n-git-rev-list --merge-order $a3 $b3 $c3 | sed \"$(sed_script)\" > actual-merge-order-1\n-test_expect_success 'Testing multiple heads' 'diff expected-merge-order-1 actual-merge-order-1'\n-\n-cat > expected-merge-order-2 <<EOF\n+test_output_expect_success 'Prune at a1' 'git-rev-list --merge-order a3 b3 c3 ^a1' <<EOF\n c3\n c2\n c1\n@@ -125,10 +211,7 @@ a3\n a2\n EOF\n \n-git-rev-list --merge-order $a3 $b3 $c3 ^$a1 | sed \"$(sed_script)\" > actual-merge-order-2\n-test_expect_success 'Testing stop' 'diff expected-merge-order-2 actual-merge-order-2'\n-\n-cat > expected-merge-order-3 <<EOF\n+test_output_expect_success 'Prune at l1' 'git-rev-list --merge-order a3 b3 c3 ^l1' <<EOF\n c3\n c2\n c1\n@@ -142,10 +225,26 @@ a0\n l2\n EOF\n \n-git-rev-list --merge-order $a3 $b3 $c3 ^$l1 | sed \"$(sed_script)\" > actual-merge-order-3\n-test_expect_success 'Testing stop in linear epoch' 'diff expected-merge-order-3 actual-merge-order-3'\n+test_output_expect_success 'Head at l5, Prune at l1' 'git-rev-list --merge-order l5 ^l1' <<EOF\n+l5\n+l4\n+l3\n+a4\n+c3\n+c2\n+c1\n+b4\n+b3\n+b2\n+b1\n+a3\n+a2\n+a1\n+a0\n+l2\n+EOF\n \n-cat > expected-merge-order-4 <<EOF\n+test_output_expect_success 'Duplicated head arguments' 'git-rev-list --merge-order l5 l5 ^l1' <<EOF\n l5\n l4\n l3\n@@ -164,12 +263,136 @@ a0\n l2\n EOF\n \n-git-rev-list --merge-order $l5 ^$l1 | sed \"$(sed_script)\" > actual-merge-order-4\n-test_expect_success 'Testing start in linear epoch, stop after non-linear epoch' 'diff expected-merge-order-4 actual-merge-order-4'\n+test_output_expect_success 'Prune near merge' 'git-rev-list --merge-order a4 ^c3' <<EOF\n+a4\n+b4\n+b3\n+a3\n+a2\n+a1\n+EOF\n+\n+test_output_expect_success '--wrt-author foobar@example.com' 'git-rev-list --merge-order --show-breaks --wrt-author --author=foobar@example.com HEAD' <<EOF\n+= l5\n+| l4\n+| l3\n+= a4\n+| c3\n+| c2\n+| c1\n+^ b4\n+| a3\n+| a2\n+| a1\n+^ b3\n+| b2\n+| b1\n+= a0\n+| l2\n+| l1\n+| l0\n+= root\n+EOF\n+\n+test_output_expect_success \"Prune at foobar@example.com\" 'git-rev-list --show-breaks --wrt-author --author=foobar@example.com --prune-at-author HEAD' <<EOF\n+= l5\n+| l4\n+| l3\n+= a4\n+| c3\n+| c2\n+| c1\n+^ b4\n+| a3\n+| a2\n+| a1\n+^ b3\n+| b2\n+EOF\n+\n+test_output_expect_success \"w.r.t e@example.com\" 'git-rev-list --show-breaks --wrt-author --author=e@example.com e10 f9' <<EOF\n+| f9\n+^ e10\n+| f8\n+| f7\n+| f6\n+| f5\n+^ e9\n+| e8\n+| e7\n+| e6\n+| f4\n+| f3\n+^ e5\n+| e4\n+| f2\n+| f1\n+^ e3\n+| e2\n+= e1\n+EOF\n \n-git-rev-list --merge-order $l5 $l5 ^$l1 2>/dev/null | sed \"$(sed_script)\" > actual-merge-order-5\n-test_expect_success 'Testing duplicated start arguments' 'diff expected-merge-order-4 actual-merge-order-5'\n \n-test_expect_success 'Testing exclusion near merge' 'git-rev-list --merge-order $a4 ^$c3 2>/dev/null'\n+test_output_expect_success \"w.r.t f@example.com\" 'git-rev-list --show-breaks --wrt-author --author=f@example.com e10 f9' <<EOF\n+| f9\n+^ e10\n+| f8\n+| f7\n+| f6\n+| f5\n+^ e9\n+| e8\n+| e7\n+| e6\n+| f4\n+| f3\n+^ e5\n+| e4\n+| e3\n+| e2\n+^ f2\n+| f1\n+= e1\n+EOF\n+\n+as_author \"\" test_output_expect_success \"w.r.t nobody\" 'git-rev-list --show-breaks --wrt-author f9 e10' <<EOF\n+| e10\n+| e9\n+| e8\n+| e7\n+^ f9\n+| f8\n+| f7\n+| f6\n+| e6\n+| e5\n+| e4\n+| e3\n+| e2\n+^ f5\n+| f4\n+| f3\n+| f2\n+| f1\n+= e1\n+EOF\n+\n+test_output_expect_success \"head has no parent\" 'git-rev-list --show-breaks root' <<EOF\n+= root\n+EOF\n+\n+test_output_expect_success \"two nodes - one head, one base\" 'git-rev-list --show-breaks l0' <<EOF\n+= l0\n+= root\n+EOF\n+\n+test_output_expect_success \"three nodes one head, one internal, one base\" 'git-rev-list --show-breaks l1' <<EOF\n+= l1\n+| l0\n+= root\n+EOF\n \n+#\n+#\n+#\n test_done\ndiff --git a/user.c b/user.c\nnew file mode 100644\n--- /dev/null\n+++ b/user.c\n@@ -0,0 +1,29 @@\n+#include \"cache.h\"\n+#include \"user.h\"\n+#include <string.h>\n+#include <pwd.h>\n+\n+void get_real_identity(char **email, char **gecos)\n+{\n+\tstatic char buffer[1000];\n+\tstruct passwd *pw;\n+\tint len;\n+\n+\tpw = getpwuid(getuid());\n+\tif (!pw)\n+\t\tdie(\"You don't exist. Go away!\");\n+\n+\tlen = strlen(pw->pw_name);\n+\tmemcpy(buffer, pw->pw_name, len);\n+\tbuffer[len] = '@';\n+\tgethostname(buffer + len + 1, sizeof(buffer) - len - 1);\n+\tif (!strchr(buffer + len + 1, '.')) {\n+\t\tstrcat(buffer, \".\");\n+\t\tgetdomainname(buffer + strlen(buffer), \n+\t\t\t\tsizeof(buffer) - strlen(buffer) - 1);\n+\t}\n+\tif (gecos)\n+\t\t*gecos = strdup(pw->pw_gecos);\n+\tif (email)\n+\t\t*email = strdup(buffer);\n+}\ndiff --git a/user.h b/user.h\nnew file mode 100644\n--- /dev/null\n+++ b/user.h\n@@ -0,0 +1,8 @@\n+#ifndef USER_H\n+#define USER_H\n+/*\n+ * Allocates two new strings to contain the real email and\n+ * name of the current user.\n+ */\n+extern void get_real_identity(char **email, char **gecos);\n+#endif\n"}]}