* [PATCH 2/2] Add support for author-oriented git-rev-list switches [rev 11]
@ 2005-06-10 9:23 Jon Seymour
2005-06-11 5:28 ` Heads up on work in progress - was: [PATCH 2/2] Add support ... " Jon Seymour
0 siblings, 1 reply; 3+ messages in thread
From: Jon Seymour @ 2005-06-10 9:23 UTC (permalink / raw)
To: git; +Cc: jon.seymour
This patch is a complete replacement for the previous --wrt
and --wrt-author patches; it adds support for --wrt-author,
--prune-at-author and --author switches to git-rev-list.
These switches perform various sorting and filtering operations that
help any git user reconstruct a linear view of merge history from
the point of view of any given change author.
When --wrt-author is specified, --merge-order is implied but
the linearisation of non-linear epochs is adjusted so that branches
contributed to by the author sort after branches contributed to by
others. This has the effect of presenting a linear history of changes as seen
by the author's own workspace.
For example, given this commit history:
a4 ---
| \ \
| b4 |
|/ | |
a3 | |
| | |
a2 | |
| | c3
| | |
| | c2
| b3 |
| | /|
| b2 |
| | c1
| | /
| b1 <-- commit authored by author@domain
a1 |
| |
a0 |
| /
root
--merge-order alone would sort it as follows:
= a4
| c3
| c2
| c1
^ b4
| b3
| b2
| b1
^ a3
| a2
| a1
| a0
= root
however, with --wrt-author --author=author@domain, git-rev-list sorts as follows:
= a4
| c3
| c2
| c1
^ b4
| a3
| a2
| a1
| a0
^ b3
| b2
| b1
= root
This has the effect of showing the first 3 of author@domain's changes
occuring before any of the changes in other branches. This is the
order in which author@domain perceived change occurring - changes
a0->a3 weren't visible in author@domain's workspace until the merge at b4.
If --prune-at-author is specified, then git-rev-list behaves as if a
^argument had been added for each commit authored by the author.
The --prune-at-author option allows an author to see a brief summary of
changes since they last made a contribution to the commit graph. For example:
git-rev-list --prune-at-author --pretty HEAD
To configure which author git-rev-list uses when processing the --wrt-author
and --prune-at-author switches, use the --author=author@domain option. If this
option is not specified, git-rev-list uses GIT_AUTHOR_EMAIL or a value
derived from the local user, host and domain names
(per git-commit-tree's algorithm)
To support this change, some additional functions were added to commit.c to
allow the extraction of author from the commit header.
Also, --show-breaks, like --wrt-author now implies --merge-order rather than
requires it.
This change also updates Documentation/git-rev-list.txt to include
documentation for --wrt-author and other git-rev-list options not currently
documented.
A test case has been included in t/t6001-rev-list-merge-order.sh to
verify this change works as expected.
Use of --wrt-author implies a small overhead (linear with respect to nodes
printed) compared with --merge-order.
Signed-off-by: Jon Seymour <jon.seymour@gmail.com>
---
[PATCH 1/2] Changes to non-epoch.c code required for author-oriented git-rev-list changes
must be applied before this patch will apply.
[rev 1] * original patch
[rev 2-4] * various cleanups
[rev 5] * --merge-order bug fixes already merged with Linus.
[rev 6] * remerged with Linus' head (98a96b00b88ee35866cd0b1e94697db76bd5ddf9)
* changed name of --exclude-author switch to --prune-at-author
* reformatted to be more consistent with preferred coding style, namely:
* aggressive elimination of intra-block blank lines
except immediately after declarations
* declarations at top of block
* one statement blocks without braces
* everything except first line of function declaration
wrapped at <= 80 columns
* [ undone in rev 8 ] modified test cases to use /dev/urandom to get
some entropy for commit text so that closely spaced commits aren't identical
* added struct epoch_methods to gather 3 function pointers now used
by merge order logic
* added a comment to commit.c to indicate that epoch.c is assuming
that parse_commit stacks parents read from the commit header
rather than queues them
[rev 7] * further whitespace and style edits
[rev 8] * d'oh - don't need /dev/urandom at all - re-written and simplified
test cases
[rev 9] * oops, missed some whitespace in my commit.c edits - begone!
* slight change to semantics of --prune-at-author - includes the
author changes, but prunes the parents of the author. This helps put
the cut points into some kind of context when the author has contributions
in multiple branches.
[rev 10] * added a function to commit.c to make dependency on commit header order
explicit. now if parse_commit changes, any caller who uses copy_parents_in_header_order
won't need to change provided copy_parents_in_header_order is updated appropriately.
* simplified sort_first_epoch by putting termination tests near top
* changed sort_list_in_local_last_order to sort_list_in_local_first_order
and made the code more concise
* added some more tests to check behaviour on degenerate graphs on 1,2 or 3 nodes.
[rev 11] * substantial refactoring and simplification
* factored several traverses into separate well-named functions
* removed unnecessary parse_commits
* simplified loop invariants
* factored out changes to remainder of git into a separate patch [PATCH 1/2]...
This revision [rev 11] reposted to the list because the refactoring work since rev10 has been substantial.
Hopefully this version will be easier for others to understand.
Diverged from 8c2cf43d6ec4107bc9ce810a248a6b0af544bb4d by Jon Seymour <jon.seymour@gmail.com>
diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt
--- a/Documentation/git-rev-list.txt
+++ b/Documentation/git-rev-list.txt
@@ -9,65 +9,114 @@ git-rev-list - Lists commit objects in r
SYNOPSIS
--------
-'git-rev-list' [ *--max-count*=number ] [ *--max-age*=timestamp ] [ *--min-age*=timestamp ] [ *--merge-order* [ *--show-breaks* ] ] <commit>
+'git-rev-list' [ *--max-count*=number ] [ *--max-age*=timestamp ]
+[ *--min-age*=timestamp ] [ *--pretty* ] [ *--parents* ] [ *--header* ]
+[ *--merge-order* ] [ *--show-breaks* ] [ *--wrt-author* ]
+[ *--prune-at-author* ] [ *--author=author@domain* ]
+<head-to-include> <head-to-include> ...
+^<base-to-prune>
+^<base-to-prune> ...
DESCRIPTION
-----------
-Lists commit objects in reverse chronological order starting at the
-given commit, taking ancestry relationship into account. This is
-useful to produce human-readable log output.
-
-If *--merge-order* is specified, the commit history is decomposed into a
-unique sequence of minimal, non-linear epochs and maximal, linear epochs.
-Non-linear epochs are then linearised by sorting them into merge order, which
+
+Lists commit objects in reverse chronological order starting at the commits
+specified by head-to-include values, taking ancestry relationship into account.
+git-rev-list will not print any commits that are reachable by any of the
+base-to-prune values.
+
+If *--max-count* is specified, the number of commits output is
+limited to the number specified.
+
+If *--max-age* is specified, commits older than the date specified
+(in seconds since Jan 1, 1970) are excluded from the output.
+
+If *--min-age* is specified, commits younger than the date specified
+(in seconds since Jan 1, 1970) are excluded from the output.
+
+If *--pretty* is specified, git-rev-list prints a short summary of each
+commit on stdout. This can be usefully parsed by git-shortlog to produce a
+short log of changes.
+
+If *--parents* is specified, git-rev-list prints a space separated list
+of parents on the same line as the commit identifier.
+
+If *--header* is specified, git-rev-list prints to stdout, on lines after
+the identifier of each commit, the contents of the commit's header followed by
+a trailing NUL.
+
+If *--merge-order* is specified, the commit history is decomposed into a
+unique sequence of minimal, non-linear epochs and maximal, linear epochs.
+Non-linear epochs are then linearised by sorting them into merge order, which
is described below.
-Maximal, linear epochs correspond to periods of sequential development.
-Minimal, non-linear epochs correspond to periods of divergent development
-followed by a converging merge. The theory of epochs is described in more
-detail at
+Maximal, linear epochs correspond to periods of sequential development.
+Minimal, non-linear epochs correspond to periods of divergent development
+followed by a converging merge. The theory of epochs is described in more
+detail at
link:http://blackcubes.dyndns.org/epoch/[http://blackcubes.dyndns.org/epoch/].
-The merge order for a non-linear epoch is defined as a linearisation for which
-the following invariants are true:
-
- 1. if a commit P is reachable from commit N, commit P sorts after commit N
- in the linearised list.
- 2. if Pi and Pj are any two parents of a merge M (with i < j), then any
- commit N, such that N is reachable from Pj but not reachable from Pi,
- sorts before all commits reachable from Pi.
-
-Invariant 1 states that later commits appear before earlier commits they are
-derived from.
+The merge order for a non-linear epoch is defined as a linearisation for
+which the following invariants are true:
-Invariant 2 states that commits unique to "later" parents in a merge, appear
-before all commits from "earlier" parents of a merge.
+ 1. if a commit P is reachable from commit N, commit P sorts after
+ commit N in the linearised list.
+ 2. if Pi and Pj are any two parents of a merge M (with i < j), then
+ any commit N, such that N is reachable from Pj but not reachable from
+ Pi, sorts before all commits reachable from Pi.
+
+Invariant 1 states that later commits appear before earlier commits
+they are derived from.
+
+Invariant 2 states that commits unique to "later" parents in a merge,
+appear before all commits from "earlier" parents of a merge.
+
+If *--show-breaks* is specified, each item of the list is output with a
+2-character prefix consisting of one of: (|), (^), (=) followed by a space.
+
+Commits marked with (=) represent the boundaries of minimal, non-linear
+epochs and correspond either to the start of a period of divergent development
+or to the end of such a period.
-If *--show-breaks* is specified, each item of the list is output with a
-2-character prefix consisting of one of: (|), (^), (=) followed by a space.
-
-Commits marked with (=) represent the boundaries of minimal, non-linear epochs
-and correspond either to the start of a period of divergent development or to
-the end of such a period.
-
-Commits marked with (|) are direct parents of commits immediately preceding
+Commits marked with (|) are direct parents of commits immediately preceding
the marked commit in the list.
-Commits marked with (^) are not parents of the immediately preceding commit.
-These "breaks" represent necessary discontinuities implied by trying to
+Commits marked with (^) are not parents of the immediately preceding commit.
+These "breaks" represent necessary discontinuities implied by trying to
represent an arbtirary DAG in a linear form.
-*--show-breaks* is only valid if *--merge-order* is also specified.
+If *--wrt-author* is specified, then git-rev-list sorts commits so that
+contemporaneously developed commits appear to occur after (that is, sort before)
+any commits authored by the author specified by *--author*. This reflects the
+order in which that author sees contemporaneously developed commits appear
+in her own workspace.
+
+If *--prune-at-author* is specified, then the parent of any commit
+authored by the author is marked as uninteresting as if it had been
+specified with ^ on the command line. This option restricts git-rev-list's
+output to just the changes including and following the last change
+contributed by any author in a given branch.
+
+The *--author* option is used to specify the author used by the
+*--wrt-author* and *--prune-at-author* switches. If an *--author* option is
+not specified, the author used is derived from GIT_AUTHOR_EMAIL or, ultimately,
+from the local user, host and domain names as per the logic used by
+git-commit-tree.
+
+Use of *--show-breaks*, *--prune-at-author* or *--wrt-author*
+implies *--merge-order*.
Author
------
Written by Linus Torvalds <torvalds@osdl.org>
-Original *--merge-order* logic by Jon Seymour <jon.seymour@gmail.com>
+Original *--merge-order*, *--wrt-author* and *--prune-at-author* logic
+by Jon Seymour <jon.seymour@gmail.com>
Documentation
--------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+Documentation by David Greaves, Junio C Hamano, Jon Seymour and
+the git-list <git@vger.kernel.org>.
GIT
---
diff --git a/epoch.c b/epoch.c
--- a/epoch.c
+++ b/epoch.c
@@ -8,7 +8,6 @@
*
*/
#include <stdlib.h>
-
/* Provides arbitrary precision integers required to accurately represent
* fractional mass: */
#include <openssl/bn.h>
@@ -22,17 +21,35 @@ struct fraction {
BIGNUM denominator;
};
-#define HAS_EXACTLY_ONE_PARENT(n) ((n)->parents && !(n)->parents->next)
-
static BN_CTX *context = NULL;
static struct fraction *one = NULL;
static struct fraction *zero = NULL;
+#define HAS_EXACTLY_ONE_PARENT(n) ((n)->parents && !(n)->parents->next)
+#define IS_UNINTERESTING(c) ((c)->object.flags & UNINTERESTING)
+#define IS_VISITED(c) ((c)->object.flags & VISITED)
+#define IS_BASE(c) ((c)->object.flags & BASE)
+#define IS_LOCAL(c) ((c)->object.flags & LOCAL)
+#define IS_DISCONTINUITY(c) ((c)->object.flags & DISCONTINUITY)
+
+/* leave the assertions defined for now, maybe null def them later */
+#define ASSERT(x,m,c) if (!(x)) { assertion_failed(__LINE__, __FUNCTION__, m, c); } else {}
+
+static void assertion_failed(int line, char * function, char * message, struct commit * item)
+{
+ die( "%s:%d:%s: assertion_failed: %s: commit %s, flags %x",
+ __FILE__,
+ line,
+ function,
+ message,
+ item ? sha1_to_hex(item->object.sha1) : "[]",
+ item ? item->object.flags : 0xFFFFFFFF);
+}
+
static BN_CTX *get_BN_CTX()
{
- if (!context) {
+ if (!context)
context = BN_CTX_new();
- }
return context;
}
@@ -58,10 +75,10 @@ static struct fraction *divide(struct fr
BN_init(&bn_divisor);
BN_set_word(&bn_divisor, divisor);
-
BN_copy(&result->numerator, &fraction->numerator);
- BN_mul(&result->denominator, &fraction->denominator, &bn_divisor, get_BN_CTX());
-
+ BN_mul( &result->denominator,
+ &fraction->denominator,
+ &bn_divisor, get_BN_CTX());
BN_clear(&bn_divisor);
return result;
}
@@ -86,9 +103,8 @@ static struct fraction *get_one()
static struct fraction *get_zero()
{
- if (!zero) {
+ if (!zero)
zero = new_zero();
- }
return zero;
}
@@ -99,28 +115,44 @@ static struct fraction *copy(struct frac
return to;
}
-static struct fraction *add(struct fraction *result, struct fraction *left, struct fraction *right)
+static struct fraction *inc(struct fraction *left, struct fraction *right)
{
BIGNUM a, b, gcd;
BN_init(&a);
BN_init(&b);
BN_init(&gcd);
-
- BN_mul(&a, &left->numerator, &right->denominator, get_BN_CTX());
- BN_mul(&b, &left->denominator, &right->numerator, get_BN_CTX());
- BN_mul(&result->denominator, &left->denominator, &right->denominator, get_BN_CTX());
- BN_add(&result->numerator, &a, &b);
-
- BN_gcd(&gcd, &result->denominator, &result->numerator, get_BN_CTX());
- BN_div(&result->denominator, NULL, &result->denominator, &gcd, get_BN_CTX());
- BN_div(&result->numerator, NULL, &result->numerator, &gcd, get_BN_CTX());
-
+ BN_mul( &a,
+ &left->numerator,
+ &right->denominator,
+ get_BN_CTX());
+ BN_mul( &b,
+ &left->denominator,
+ &right->numerator,
+ get_BN_CTX());
+ BN_mul( &left->denominator,
+ &left->denominator,
+ &right->denominator,
+ get_BN_CTX());
+ BN_add( &left->numerator,
+ &a,
+ &b);
+ BN_gcd( &gcd,
+ &left->denominator,
+ &left->numerator,
+ get_BN_CTX());
+ BN_div( &left->denominator,
+ NULL, &left->denominator,
+ &gcd,
+ get_BN_CTX());
+ BN_div( &left->numerator,
+ NULL, &left->numerator,
+ &gcd,
+ get_BN_CTX());
BN_clear(&a);
BN_clear(&b);
BN_clear(&gcd);
-
- return result;
+ return left;
}
static int compare(struct fraction *left, struct fraction *right)
@@ -130,15 +162,11 @@ static int compare(struct fraction *left
BN_init(&a);
BN_init(&b);
-
BN_mul(&a, &left->numerator, &right->denominator, get_BN_CTX());
BN_mul(&b, &left->denominator, &right->numerator, get_BN_CTX());
-
result = BN_cmp(&a, &b);
-
BN_clear(&a);
BN_clear(&b);
-
return result;
}
@@ -150,21 +178,15 @@ struct mass_counter {
static struct mass_counter *new_mass_counter(struct commit *commit, struct fraction *pending)
{
struct mass_counter *mass_counter = xmalloc(sizeof(*mass_counter));
- memset(mass_counter, 0, sizeof(*mass_counter));
+ ASSERT(!commit->object.util, "not already initialized", commit);
+
+ memset(mass_counter, 0, sizeof(*mass_counter));
init_fraction(&mass_counter->seen);
init_fraction(&mass_counter->pending);
-
copy(&mass_counter->pending, pending);
copy(&mass_counter->seen, get_zero());
-
- if (commit->object.util) {
- die("multiple attempts to initialize mass counter for %s",
- sha1_to_hex(commit->object.sha1));
- }
-
commit->object.util = mass_counter;
-
return mass_counter;
}
@@ -175,6 +197,22 @@ static void free_mass_counter(struct mas
free(counter);
}
+static void mark_commit(struct epoch_methods * methods, struct commit * commit)
+{
+ if (methods->marker)
+ (*(methods->marker))(commit);
+}
+
+static int emit_commit(struct epoch_methods * methods, struct commit * commit)
+{
+ return (*(methods->emitter))(commit);
+}
+
+void init_epoch_methods(struct epoch_methods * methods)
+{
+ memset(methods, 0, sizeof(*methods));
+}
+
/*
* Finds the base commit of a list of commits.
*
@@ -218,78 +256,67 @@ static int find_base_for_list(struct com
struct commit_list *cleaner = NULL;
struct commit_list *pending = NULL;
struct fraction injected;
- init_fraction(&injected);
- *boundary = NULL;
+ *boundary = NULL;
+ init_fraction(&injected);
for (; list; list = list->next) {
struct commit *item = list->item;
- if (item->object.util) {
- die("%s:%d:%s: logic error: this should not have happened - commit %s",
- __FILE__, __LINE__, __FUNCTION__,
- sha1_to_hex(item->object.sha1));
- }
-
+ ASSERT(!item->object.util, "no duplicates in list", item);
new_mass_counter(list->item, get_one());
- add(&injected, &injected, get_one());
-
+ inc(&injected, get_one());
commit_list_insert(list->item, &cleaner);
commit_list_insert(list->item, &pending);
}
-
while (!*boundary && pending && !ret) {
struct commit *latest = pop_commit(&pending);
- struct mass_counter *latest_node = (struct mass_counter *) latest->object.util;
+ struct mass_counter *latest_node;
int num_parents;
-
- if ((ret = parse_commit(latest)))
+
+ ret = parse_commit(latest);
+ if (ret)
continue;
- add(&latest_node->seen, &latest_node->seen, &latest_node->pending);
-
+ latest_node = (struct mass_counter *) latest->object.util;
+ inc(&latest_node->seen, &latest_node->pending);
num_parents = count_parents(latest);
if (num_parents) {
- struct fraction distribution;
- struct commit_list *parents;
+ struct fraction div;
+ struct commit_list *parents = latest->parents;
- divide(init_fraction(&distribution), &latest_node->pending, num_parents);
-
- for (parents = latest->parents; parents; parents = parents->next) {
- struct commit *parent = parents->item;
- struct mass_counter *parent_node = (struct mass_counter *) parent->object.util;
-
- if (!parent_node) {
- parent_node = new_mass_counter(parent, &distribution);
- insert_by_date(&pending, parent);
- commit_list_insert(parent, &cleaner);
- } else {
- if (!compare(&parent_node->pending, get_zero()))
- insert_by_date(&pending, parent);
- add(&parent_node->pending, &parent_node->pending, &distribution);
- }
+ divide( init_fraction(&div),
+ &latest_node->pending,
+ num_parents);
+ while (parents) {
+ struct mass_counter * pmc;
+ struct commit *pc = parents->item;
+
+ pmc = (struct mass_counter *) pc->object.util;
+ if (!pmc) {
+ pmc = new_mass_counter(pc, get_zero());
+ commit_list_insert(pc, &cleaner);
+ }
+ if (!compare(&pmc->pending, get_zero()))
+ insert_by_date(&pending, pc);
+ inc(&pmc->pending, &div);
+ parents = parents->next;
}
-
- clear_fraction(&distribution);
+ clear_fraction(&div);
}
-
if (!compare(&latest_node->seen, &injected))
*boundary = latest;
copy(&latest_node->pending, get_zero());
}
-
while (cleaner) {
struct commit *next = pop_commit(&cleaner);
free_mass_counter((struct mass_counter *) next->object.util);
next->object.util = NULL;
}
-
if (pending)
free_commit_list(pending);
-
clear_fraction(&injected);
return ret;
}
-
/*
* Finds the base of an minimal, non-linear epoch, headed at head, by
* applying the find_base_for_list to a list consisting of the parents
@@ -305,7 +332,6 @@ static int find_base(struct commit *head
}
ret = find_base_for_list(pending, boundary);
free_commit_list(pending);
-
return ret;
}
@@ -313,19 +339,15 @@ static int find_base(struct commit *head
* This procedure traverses to the boundary of the first epoch in the epoch
* sequence of the epoch headed at head_of_epoch. This is either the end of
* the maximal linear epoch or the base of a minimal non-linear epoch.
- *
- * The queue of pending nodes is sorted in reverse date order and each node
- * is currently in the queue at most once.
*/
static int find_next_epoch_boundary(struct commit *head_of_epoch, struct commit **boundary)
{
- int ret;
struct commit *item = head_of_epoch;
+ int ret;
ret = parse_commit(item);
if (ret)
return ret;
-
if (HAS_EXACTLY_ONE_PARENT(item)) {
/*
* We are at the start of a maximimal linear epoch.
@@ -336,7 +358,6 @@ static int find_next_epoch_boundary(stru
ret = parse_commit(item);
}
*boundary = item;
-
} else {
/*
* Otherwise, we are at the start of a minimal, non-linear
@@ -344,7 +365,6 @@ static int find_next_epoch_boundary(stru
*/
ret = find_base(item, boundary);
}
-
return ret;
}
@@ -354,38 +374,21 @@ static int find_next_epoch_boundary(stru
static int is_parent_of(struct commit *parent, struct commit *child)
{
struct commit_list *parents;
- for (parents = child->parents; parents; parents = parents->next) {
- if (!memcmp(parent->object.sha1, parents->item->object.sha1,
- sizeof(parents->item->object.sha1)))
+
+ for (parents = child->parents; parents; parents = parents->next)
+ if (parent==parents->item)
return 1;
- }
return 0;
}
/*
- * Pushes an item onto the merge order stack. If the top of the stack is
- * marked as being a possible "break", we check to see whether it actually
- * is a break.
- */
-static void push_onto_merge_order_stack(struct commit_list **stack, struct commit *item)
-{
- struct commit_list *top = *stack;
- if (top && (top->item->object.flags & DISCONTINUITY)) {
- if (is_parent_of(top->item, item)) {
- top->item->object.flags &= ~DISCONTINUITY;
- }
- }
- commit_list_insert(item, stack);
-}
-
-/*
* Marks all interesting, visited commits reachable from this commit
* as uninteresting. We stop recursing when we reach the epoch boundary,
- * an unvisited node or a node that has already been marking uninteresting.
+ * an unvisited node or a node that has already been marked uninteresting.
*
* This doesn't actually mark all ancestors between the start node and the
* epoch boundary uninteresting, but does ensure that they will eventually
- * be marked uninteresting when the main sort_first_epoch() traversal
+ * be marked uninteresting when the main sort_unvisited() traversal
* eventually reaches them.
*/
static void mark_ancestors_uninteresting(struct commit *commit)
@@ -397,111 +400,73 @@ static void mark_ancestors_uninteresting
struct commit_list *next;
commit->object.flags |= UNINTERESTING;
-
/*
* We only need to recurse if
* we are not on the boundary and
* we have not already been marked uninteresting and
* we have already been visited.
*
- * The main sort_first_epoch traverse will mark unreachable
+ * The main sort_unvisited traverse will mark unreachable
* all uninteresting, unvisited parents as they are visited
* so there is no need to duplicate that traversal here.
*
* Similarly, if we are already marked uninteresting
* then either all ancestors have already been marked
- * uninteresting or will be once the sort_first_epoch
+ * uninteresting or will be once the sort_unvisited
* traverse reaches them.
*/
-
if (uninteresting || boundary || !visited)
return;
-
for (next = commit->parents; next; next = next->next)
mark_ancestors_uninteresting(next->item);
}
/*
- * Sorts the nodes of the first epoch of the epoch sequence of the epoch headed at head
- * into merge order.
+ * Sort the epoch in (adjusted) merge order.
*/
-static void sort_first_epoch(struct commit *head, struct commit_list **stack)
+static void sort_unvisited(
+ struct commit *head,
+ struct commit_list **stack,
+ struct epoch_methods * methods)
{
- struct commit_list *parents;
- struct commit_list *reversed_parents = NULL;
+ struct commit_list *parents = NULL;
+ struct commit * top = *stack?(*stack)->item:NULL;
+ if (IS_VISITED(head))
+ return;
head->object.flags |= VISITED;
-
- /*
- * parse_commit() builds the parent list in reverse order with respect
- * to the order of the git-commit-tree arguments. So we need to reverse
- * this list to output the oldest (or most "local") commits last.
- */
- for (parents = head->parents; parents; parents = parents->next)
- commit_list_insert(parents->item, &reversed_parents);
-
- /*
- * TODO: By sorting the parents in a different order, we can alter the
- * merge order to show contemporaneous changes in parallel branches
- * occurring after "local" changes. This is useful for a developer
- * when a developer wants to see all changes that were incorporated
- * into the same merge as her own changes occur after her own
- * changes.
- */
-
- while (reversed_parents) {
- struct commit *parent = pop_commit(&reversed_parents);
-
- if (head->object.flags & UNINTERESTING) {
- /*
- * Propagates the uninteresting bit to all parents.
- * if we have already visited this parent, then
- * the uninteresting bit will be propagated to each
- * reachable commit that is still not marked
- * uninteresting and won't otherwise be reached.
- */
- mark_ancestors_uninteresting(parent);
+ mark_commit(methods, head);
+ if (IS_BASE(head)) {
+ ASSERT(!top, "stack empty on visit to base", head);
+ } else {
+ if (!head->object.util) {
+ parents = copy_parents_in_header_order(head);
+ } else {
+ parents = (struct commit_list *)head->object.util;
+ head->object.util = NULL;
}
+ while (parents) {
+ struct commit *parent = pop_commit(&parents);
- if (!(parent->object.flags & VISITED)) {
- if (parent->object.flags & BOUNDARY) {
- if (*stack) {
- die("something else is on the stack - %s",
- sha1_to_hex((*stack)->item->object.sha1));
- }
- push_onto_merge_order_stack(stack, parent);
- parent->object.flags |= VISITED;
-
- } else {
- sort_first_epoch(parent, stack);
- if (reversed_parents) {
- /*
- * This indicates a possible
- * discontinuity it may not be be
- * actual discontinuity if the head
- * of parent N happens to be the tail
- * of parent N+1.
- *
- * The next push onto the stack will
- * resolve the question.
- */
- (*stack)->item->object.flags |= DISCONTINUITY;
- }
+ if (IS_UNINTERESTING(head)) {
+ mark_ancestors_uninteresting(parent);
}
- }
- }
-
- push_onto_merge_order_stack(stack, head);
+ sort_unvisited(parent, stack, methods);
+ }
+ }
+ top=(*stack)?(*stack)->item:NULL;
+ if (top && !is_parent_of(top, head)) {
+ top->object.flags |= DISCONTINUITY;
+ }
+ commit_list_insert(head, stack);
}
/*
* Emit the contents of the stack.
- *
* The stack is freed and replaced by NULL.
- *
* Sets the return value to STOP if no further output should be generated.
*/
-static int emit_stack(struct commit_list **stack, emitter_func emitter)
+static int emit_stack(struct commit_list **stack, struct epoch_methods * methods)
{
unsigned int seen = 0;
int action = CONTINUE;
@@ -510,140 +475,268 @@ static int emit_stack(struct commit_list
struct commit *next = pop_commit(stack);
seen |= next->object.flags;
if (*stack)
- action = (*emitter) (next);
+ action = emit_commit(methods, next);
}
-
if (*stack) {
free_commit_list(*stack);
*stack = NULL;
+ }
+ if (seen & UNINTERESTING) {
+ /**
+ * We stop at the base of the stack, rather than
+ * when we encounter the first UNINTERESTING flag.
+ *
+ * The reason is that there may still be interesting stuff
+ * on the stack but once we reach the base there can be no
+ * more interesting stuff by definition of what the base
+ * of an epoch is - everything reachable from the base is
+ * also reachable from the UNINTERESTING node and hence
+ * is uninteresting.
+ */
+ action = STOP;
}
+ return action;
+}
+
+/*
+ * Copy the parents of the commit and store the head of the list
+ * in object.util. Return the address of object.util itself.
+ */
+static struct commit_list ** copy_and_store_parents(struct commit * commit)
+{
+ struct commit_list * copied=copy_parents_in_header_order(commit);
+
+ commit->object.util = copied;
+ return (struct commit_list **)&commit->object.util;
+}
+
+/**
+ * Move an item pointed to by *src onto the tail of a list pointed to by dst
+ * and return the updated tail pointer. *src is updated to refer to the
+ * next element on the source list or NULL if there is no such element.
+ */
+static struct commit_list ** move(struct commit_list ** dst, struct commit_list ** src)
+{
+ ASSERT(*src, "*src is never NULL", NULL);
+ ASSERT(!*dst || !(*dst)->next, "*dst is NULL or (*dst)->next is NULL", NULL);
+ *dst = *src;
+ *src = (*src)->next;
+ (*dst)->next = NULL;
+ return &(*dst)->next;
+}
- return (action == STOP || (seen & UNINTERESTING)) ? STOP : CONTINUE;
+/*
+ * Sort a list of commits in local first order. A commit is "local"
+ * if any of its ancestors (except the base) causes (*local_test)() to
+ * return a non-zero value.
+ *
+ * The sorted list is returned in *sorted. A side effect of this function
+ * is to set each object.util pointer in each ancestor up until the base
+ * to a list of parents is sorted in local first order.
+ *
+ * Does nothing if the list is empty.
+ *
+ * The return value contains LOCAL if any of the list is local or had a
+ * local ancestor.
+ */
+static unsigned int sort_local_branches_first(struct commit_list ** list, struct epoch_methods * methods)
+{
+ struct commit_list *local = NULL;
+ struct commit_list **local_tail = &local;
+ struct commit_list **non_local_ptr;
+
+ ASSERT(methods->local_test, "local_test method is defined", NULL);
+ if (!*list) {
+ return 0;
+ }
+ for (non_local_ptr = list; *non_local_ptr; ) {
+ struct commit *item = (*non_local_ptr)->item;
+
+ /*
+ * We don't descend past the base or visit
+ * a commit we have already visited.
+ */
+ if (!IS_BASE(item) && !item->object.util) {
+ struct commit_list ** copied;
+ if ((*(methods->local_test)) (item))
+ item->object.flags |= LOCAL;
+ copied=copy_and_store_parents(item);
+ item->object.flags
+ |= sort_local_branches_first(copied, methods);
+ }
+ /**
+ * Move local items onto their own list.
+ */
+ if (IS_LOCAL(item))
+ local_tail = move(local_tail, non_local_ptr);
+ else
+ non_local_ptr = &(*non_local_ptr)->next;
+ }
+ /*
+ * Splice the non-local list onto the end of the local
+ * list, set head of the list to the head of the local list
+ * return if the LOCAL flag set if we have any local branches
+ */
+ *local_tail = *list;
+ *list = local;
+ return ((*list)->item->object.flags & LOCAL);
}
/*
+ * Sorting a maximal linear epoch involves traversing until we
+ * reach the base and emitting as we go. We don't emit the base
+ * now.
+ */
+static int sort_maximal_linear_epoch(struct commit *next, struct epoch_methods * methods)
+{
+ while (!IS_BASE(next)) {
+ mark_commit(methods, next);
+ if (!IS_UNINTERESTING(next) && (emit_commit(methods, next) != STOP))
+ next = next->parents->item;
+ else
+ return STOP;
+ }
+ return CONTINUE;
+}
+
+/*
+ * Sorting a minimal non-linear epoch involves recursively apply
+ * sort_unvisited after doing a local_branches_first sort to the
+ * parents of each commit in the epoch, if required, then emitting
+ * the stack.
+ */
+static int sort_minimal_non_linear_epoch(struct commit *head, struct epoch_methods * methods)
+{
+ struct commit_list *stack = NULL;
+
+ if (methods->local_test)
+ sort_local_branches_first(copy_and_store_parents(head), methods);
+ sort_unvisited(head, &stack, methods);
+ return emit_stack(&stack, methods);
+}
+/*
* Sorts an arbitrary epoch into merge order by sorting each epoch
* of its epoch sequence into order.
*
* Note: this algorithm currently leaves traces of its execution in the
* object flags of nodes it discovers. This should probably be fixed.
*/
-static int sort_in_merge_order(struct commit *head_of_epoch, emitter_func emitter)
+static int sort_in_merge_order(struct commit *head, struct epoch_methods * methods)
{
- struct commit *next = head_of_epoch;
+ struct commit *next = head;
int ret = 0;
- int action = CONTINUE;
- ret = parse_commit(head_of_epoch);
-
- while (next && next->parents && !ret && (action != STOP)) {
+ ret = parse_commit(next);
+ if (ret)
+ return ret;
+ next->object.flags |= BOUNDARY;
+ while (next && next->parents) {
struct commit *base = NULL;
+ int next_action = CONTINUE;
+ /* scan to the base of the current epoch */
ret = find_next_epoch_boundary(next, &base);
+
+ /* abort if we detected a parsing error */
if (ret)
- return ret;
- next->object.flags |= BOUNDARY;
+ return ret; /* parsing failure */
+
+ /* mark the new base so we know when to stop sorting */
if (base)
- base->object.flags |= BOUNDARY;
-
- if (HAS_EXACTLY_ONE_PARENT(next)) {
- while (HAS_EXACTLY_ONE_PARENT(next)
- && (action != STOP)
- && !ret) {
- if (next->object.flags & UNINTERESTING) {
- action = STOP;
- } else {
- action = (*emitter) (next);
- }
- if (action != STOP) {
- next = next->parents->item;
- ret = parse_commit(next);
- }
- }
-
- } else {
- struct commit_list *stack = NULL;
- sort_first_epoch(next, &stack);
- action = emit_stack(&stack, emitter);
+ base->object.flags |= (BOUNDARY|BASE);
+
+ /* reset flags set by last iteration */
+ next->object.flags &= ~(BASE|VISITED);
+
+ /* sort with the optimal algorithm */
+ if (HAS_EXACTLY_ONE_PARENT(next))
+ next_action = sort_maximal_linear_epoch(next, methods);
+ else
+ next_action = sort_minimal_non_linear_epoch(next, methods);
+
+ /* stop or iterate */
+ if (next_action == STOP)
+ return 0;
+ else
next = base;
- }
}
-
- if (next && (action != STOP) && !ret) {
- (*emitter) (next);
- }
-
- return ret;
+ if (next)
+ emit_commit(methods, next);
+ return 0;
}
/*
* Sorts the nodes reachable from a starting list in merge order, we
* first find the base for the starting list and then sort all nodes
- * in this subgraph using the sort_first_epoch algorithm. Once we have
+ * in this subgraph using the sort_unvisited algorithm. Once we have
* reached the base we can continue sorting using sort_in_merge_order.
*/
-int sort_list_in_merge_order(struct commit_list *list, emitter_func emitter)
+int sort_list_in_merge_order(struct commit_list *list, struct epoch_methods * methods)
{
struct commit_list *stack = NULL;
+ struct commit_list *filtered = NULL;
struct commit *base;
int ret = 0;
- int action = CONTINUE;
- struct commit_list *reversed = NULL;
+ if (!methods)
+ die("methods argument must not be null");
+ if (!methods->emitter)
+ die("an emitter method must be supplied");
+ /**
+ * Remove duplicates and uninteresting items.
+ * Duplicates are not allowed by find_base_for_list and
+ * uninteresting items may prevent us identifying the
+ * right type of traversal to do.
+ */
for (; list; list = list->next) {
struct commit *next = list->item;
- if (!(next->object.flags & UNINTERESTING)) {
- if (next->object.flags & DUPCHECK) {
- fprintf(stderr, "%s: duplicate commit %s ignored\n",
- __FUNCTION__, sha1_to_hex(next->object.sha1));
- } else {
- next->object.flags |= DUPCHECK;
- commit_list_insert(list->item, &reversed);
- }
+ if (!(next->object.flags & (UNINTERESTING|DUPCHECK))) {
+ next->object.flags |= DUPCHECK;
+ commit_list_insert(list->item, &filtered);
}
- }
-
- if (!reversed->next) {
+ }
+ if (!filtered)
+ die("no uninteresting heads were specified\n");
+ if (filtered->next) {
/*
- * If there is only one element in the list, we can sort it
- * using sort_in_merge_order.
- */
- base = reversed->item;
- } else {
- /*
- * Otherwise, we search for the base of the list.
- */
- ret = find_base_for_list(reversed, &base);
+ * With multiple items to start the search with,
+ * we first sort the list into local order (if required)
+ *
+ * This behaves as if a commit was performed which
+ * referenced the filtered list as parents. This
+ * would create a minimal, non-linear epoch.
+ */
+ ret = find_base_for_list(filtered, &base);
if (ret)
- return ret;
- if (base)
- base->object.flags |= BOUNDARY;
+ return ret; /* parsing failure */
- while (reversed) {
- sort_first_epoch(pop_commit(&reversed), &stack);
- if (reversed) {
- /*
- * If we have more commits to push, then the
- * first push for the next parent may (or may
- * not) represent a discontinuity with respect
- * to the parent currently on the top of
- * the stack.
- *
- * Mark it for checking here, and check it
- * with the next push. See sort_first_epoch()
- * for more details.
- */
- stack->item->object.flags |= DISCONTINUITY;
- }
- }
-
- action = emit_stack(&stack, emitter);
- }
+ /* mark the termination condition*/
+ if (base)
+ base->object.flags |= (BOUNDARY|BASE);
- if (base && (action != STOP)) {
- ret = sort_in_merge_order(base, emitter);
- }
+ /* sort local branches first, so they print last */
+ if (methods->local_test)
+ sort_local_branches_first(&filtered, methods);
+
+ /* sort the unvisited part of the epoch in merge order */
+ while (filtered)
+ sort_unvisited(pop_commit(&filtered), &stack, methods);
+
+ /* output the stack */
+ if (emit_stack(&stack, methods) == STOP)
+ return STOP;
+ } else {
+ /*
+ * With only one item on the list, we just use
+ * sort in merge order which handles maximal
+ * linear epochs as well as minimal, non-linear epochs.
+ */
+ base = filtered->item;
+ }
+ /* sort the rest with the sort_in_merge_order algorithm. */
+ if (base)
+ ret = sort_in_merge_order(base, methods);
return ret;
}
diff --git a/epoch.h b/epoch.h
--- a/epoch.h
+++ b/epoch.h
@@ -1,20 +1,56 @@
#ifndef EPOCH_H
#define EPOCH_H
-
-// return codes for emitter_func
-#define STOP 0
-#define CONTINUE 1
-#define DO 2
-typedef int (*emitter_func) (struct commit *);
-
-int sort_list_in_merge_order(struct commit_list *list, emitter_func emitter);
-
+/**
+ * Flags used by merge order logic and also by rev-list.c
+ */
#define UNINTERESTING (1u<<2)
#define BOUNDARY (1u<<3)
#define VISITED (1u<<4)
#define DISCONTINUITY (1u<<5)
#define DUPCHECK (1u<<6)
+#define LOCAL (1u<<7)
+#define BASE (1u<<8)
+/**
+ * Return codes for emitter method. Also used by rev-list.c
+ */
+#define STOP 0
+#define CONTINUE 1
+#define DO 2
-#endif /* EPOCH_H */
+struct epoch_methods {
+ /*
+ * Returns 0 if traversal should stop, non-zero if it should continue.
+ */
+ int (*emitter)(struct commit *);
+ /*
+ * Returns non-zero if the commit is regarded "local", 0 otherwise.
+ */
+ int (*local_test)(struct commit *);
+ /*
+ * Implementers may use this method to mark commits uninteresting
+ * according to some locally determined criteria. The tree
+ * will be pruned at any commit so marked.
+ */
+ void (*marker)(struct commit *);
+};
+
+/**
+ * Initializes an epoch_methods structure which
+ * may be customized by the caller by overriding any of the method pointers.
+ */
+extern void init_epoch_methods(struct epoch_methods *);
+
+/**
+ * Sorts the list of commits in merge order, using the methods specified
+ * to customize the tactics of the search.
+ *
+ * The prune points should be marked with the UNINTERESTING flags.
+ *
+ * Note: this algorithm is dirty in the sense that it leaves traces
+ * of its execution in the object.flags word of some or all of the commits
+ * visited.
+ */
+extern int sort_list_in_merge_order(struct commit_list *list, struct epoch_methods * methods);
+#endif /* EPOCH_H */
diff --git a/rev-list.c b/rev-list.c
--- a/rev-list.c
+++ b/rev-list.c
@@ -1,18 +1,24 @@
#include "cache.h"
#include "commit.h"
#include "epoch.h"
+#include "user.h"
#define SEEN (1u << 0)
#define INTERESTING (1u << 1)
static const char rev_list_usage[] =
"usage: git-rev-list [OPTION] commit-id <commit-id>\n"
- " --max-count=nr\n"
- " --max-age=epoch\n"
- " --min-age=epoch\n"
- " --header\n"
- " --pretty\n"
- " --merge-order [ --show-breaks ]";
+ " --max-count=nr\n"
+ " --max-age=epoch\n"
+ " --min-age=epoch\n"
+ " --header\n"
+ " --parents\n"
+ " --pretty\n"
+ " --merge-order\n"
+ " --wrt-author\n"
+ " --prune-at-author\n"
+ " --author=author@domain\n"
+ " --show-breaks ]";
static int verbose_header = 0;
static int show_parents = 0;
@@ -24,6 +30,9 @@ static int max_count = -1;
static enum cmit_fmt commit_format = CMIT_FMT_RAW;
static int merge_order = 0;
static int show_breaks = 0;
+static char * local_author = NULL;
+static int prune_at_author = 0;
+static int wrt_author = 0;
static void show_commit(struct commit *commit)
{
@@ -147,6 +156,26 @@ static enum cmit_fmt get_commit_format(c
usage(rev_list_usage);
}
+static int is_local_author(struct commit * commit)
+{
+ static char author[16384];
+ if (copy_author(commit, author, sizeof(author)) > 0) {
+ if (strstr(author, local_author)) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static void mark_authors_own_uninteresting(struct commit * commit)
+{
+ if (is_local_author(commit)) {
+ struct commit_list * parents = commit->parents;
+ for (;parents;parents=parents->next) {
+ parents->item->object.flags |= UNINTERESTING;
+ }
+ }
+}
int main(int argc, char **argv)
{
@@ -186,14 +215,26 @@ int main(int argc, char **argv)
show_parents = 1;
continue;
}
- if (!strncmp(arg, "--merge-order", 13)) {
+ if (!strcmp(arg, "--merge-order")) {
merge_order = 1;
continue;
}
- if (!strncmp(arg, "--show-breaks", 13)) {
+ if (!strcmp(arg, "--show-breaks")) {
show_breaks = 1;
continue;
}
+ if (!strncmp(arg, "--author=", 9)) {
+ local_author=strdup(&arg[9]);
+ continue;
+ }
+ if (!strcmp(arg, "--wrt-author")) {
+ wrt_author = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--prune-at-author")) {
+ prune_at_author = 1;
+ continue;
+ }
flags = 0;
if (*arg == '^') {
@@ -201,7 +242,7 @@ int main(int argc, char **argv)
arg++;
limited = 1;
}
- if (get_sha1(arg, sha1) || (show_breaks && !merge_order))
+ if (get_sha1(arg, sha1))
usage(rev_list_usage);
commit = lookup_commit_reference(sha1);
if (!commit || parse_commit(commit) < 0)
@@ -213,14 +254,38 @@ int main(int argc, char **argv)
if (!list)
usage(rev_list_usage);
+ if (!merge_order)
+ merge_order = wrt_author || prune_at_author || show_breaks;
+
if (!merge_order) {
if (limited)
list = limit_list(list);
show_commit_list(list);
- } else {
- if (sort_list_in_merge_order(list, &process_commit)) {
- die("merge order sort failed\n");
- }
+ } else {
+ struct epoch_methods methods;
+
+ init_epoch_methods(&methods);
+ if ((prune_at_author|wrt_author) && !local_author) {
+ local_author = gitenv("GIT_AUTHOR_EMAIL") ? : NULL;
+ if (!local_author)
+ get_real_identity(&local_author, NULL);
+ else
+ local_author = strdup(local_author);
+ }
+ if (local_author) {
+ /* add delimiters to improve accuracy of match */
+ char * tmp=xmalloc(strlen(local_author)+3);
+ sprintf(tmp, "<%s>", local_author);
+ free(local_author);
+ local_author = tmp;
+ }
+ methods.emitter = &process_commit;
+ if (wrt_author)
+ methods.local_test = &is_local_author;
+ if (prune_at_author)
+ methods.marker = &mark_authors_own_uninteresting;
+ if (sort_list_in_merge_order(list, &methods))
+ die("merge order sort failed\n");
}
return 0;
diff --git a/t/t6001-rev-list-merge-order.sh b/t/t6001-rev-list-merge-order.sh
--- a/t/t6001-rev-list-merge-order.sh
+++ b/t/t6001-rev-list-merge-order.sh
@@ -6,10 +6,53 @@
test_description='Test rev-list --merge-order
'
. ./test-lib.sh
+[ -d .git/refs/tags ] || mkdir -p .git/refs/tags
-function do_commit
+sed_script="";
+
+function tag
+{
+ local _tag=$1
+ [ -f .git/refs/tags/$_tag ] || error "tag: \"$_tag\" does not exist"
+ cat .git/refs/tags/$_tag
+}
+
+function commit
+{
+ local _tag=$1
+ local _tree=$2
+ shift 2
+ echo $_tag | git-commit-tree $(tag $_tree) "$@"
+}
+
+function save_tag
{
- git-commit-tree "$@" </dev/null
+ local _tag=$1
+ [ -n "$_tag" ] || error "usage: do_commit tag commit-args ..."
+ shift 1
+ "$@" >.git/refs/tags/$_tag
+ sed_script="s/$(tag $_tag)/$_tag/g${sed_script+;}$sed_script"
+}
+
+function entag
+{
+ sed "$sed_script"
+}
+
+function as_author
+{
+ local _author=$1
+ shift 1
+ local _save=$GIT_AUTHOR_EMAIL
+
+ export GIT_AUTHOR_EMAIL="$_author"
+ "$@"
+ export GIT_AUTHOR_EMAIL="$_save"
+}
+
+function hide_error
+{
+ "$@" 2>/dev/null
}
function check_adjacency
@@ -28,43 +71,97 @@ function check_adjacency
done
}
-function sed_script
+function check_output
{
- for c in root a0 a1 a2 a3 a4 b1 b2 b3 b4 c1 c2 c3 l0 l1 l2 l3 l4 l5
- do
- echo -n "s/${!c}/$c/;"
- done
+ local _name=$1
+ shift 1
+ if "$@" | entag > $_name.actual
+ then
+ diff $_name.expected $_name.actual
+ else
+ return 1;
+ fi
+
+}
+
+function name_from_description
+{
+ tr "'" '.' | tr '~`!@#$%^&*()_+={}[]|\;:"<>,/?-' '.' | tr -s '.' | tr ' ' '-' | tr '[A-Z]' '[a-z]' | sed "s/^\.*//"
+}
+
+#
+# stdin contains expected result
+#
+function test_output_expect_success
+{
+ local _description=$1
+ shift 1
+ local _name=$(echo $_description | name_from_description)
+ cat > $_name.expected
+ test_expect_success "$_description" "check_output $_name $*"
}
date >path0
git-update-cache --add path0
-tree=$(git-write-tree)
-root=$(do_commit $tree 2>/dev/null)
-export GIT_COMMITTER_NAME=foobar # to guarantee that the commit is different
-l0=$(do_commit $tree -p $root)
-l1=$(do_commit $tree -p $l0)
-l2=$(do_commit $tree -p $l1)
-a0=$(do_commit $tree -p $l2)
-a1=$(do_commit $tree -p $a0)
-export GIT_COMMITTER_NAME=foobar2 # to guarantee that the commit is different
-b1=$(do_commit $tree -p $a0)
-c1=$(do_commit $tree -p $b1)
-export GIT_COMMITTER_NAME=foobar3 # to guarantee that the commit is different
-b2=$(do_commit $tree -p $b1)
-b3=$(do_commit $tree -p $b2)
-c2=$(do_commit $tree -p $c1 -p $b2)
-c3=$(do_commit $tree -p $c2)
-a2=$(do_commit $tree -p $a1)
-a3=$(do_commit $tree -p $a2)
-b4=$(do_commit $tree -p $b3 -p $a3)
-a4=$(do_commit $tree -p $a3 -p $b4 -p $c3)
-l3=$(do_commit $tree -p $a4)
-l4=$(do_commit $tree -p $l3)
-l5=$(do_commit $tree -p $l4)
-echo $l5 > .git/HEAD
+save_tag tree git-write-tree
+hide_error save_tag root commit root tree
+save_tag l0 commit l0 tree -p root
+save_tag l1 commit l1 tree -p l0
+save_tag l2 commit l2 tree -p l1
+save_tag a0 commit a0 tree -p l2
+save_tag a1 commit a1 tree -p a0
+save_tag b1 commit b1 tree -p a0
+save_tag c1 commit c1 tree -p b1
+as_author foobar@example.com save_tag b2 commit b2 tree -p b1
+save_tag b3 commit b2 tree -p b2
+save_tag c2 commit c2 tree -p c1 -p b2
+save_tag c3 commit c3 tree -p c2
+save_tag a2 commit a2 tree -p a1
+save_tag a3 commit a3 tree -p a2
+save_tag b4 commit b4 tree -p b3 -p a3
+save_tag a4 commit a4 tree -p a3 -p b4 -p c3
+save_tag l3 commit l3 tree -p a4
+save_tag l4 commit l4 tree -p l3
+save_tag l5 commit l5 tree -p l4
+hide_error save_tag e1 as_author e@example.com commit e1 tree
+save_tag e2 as_author e@example.com commit e2 tree -p e1
+save_tag f1 as_author f@example.com commit f1 tree -p e1
+save_tag e3 as_author e@example.com commit e3 tree -p e2
+save_tag f2 as_author f@example.com commit f2 tree -p f1
+save_tag e4 as_author e@example.com commit e4 tree -p e3 -p f2
+save_tag e5 as_author e@example.com commit e5 tree -p e4
+save_tag f3 as_author f@example.com commit f3 tree -p f2
+save_tag f4 as_author f@example.com commit f4 tree -p f3
+save_tag e6 as_author e@example.com commit e6 tree -p e5 -p f4
+save_tag f5 as_author f@example.com commit f5 tree -p f4
+save_tag f6 as_author f@example.com commit f6 tree -p f5 -p e6
+save_tag e7 as_author e@example.com commit e7 tree -p e6
+save_tag e8 as_author e@example.com commit e8 tree -p e7
+save_tag e9 as_author e@example.com commit e9 tree -p e8
+save_tag f7 as_author f@example.com commit f7 tree -p f6
+save_tag f8 as_author f@example.com commit f8 tree -p f7
+save_tag f9 as_author f@example.com commit f9 tree -p f8
+save_tag e10 as_author e@example.com commit e1 tree -p e9 -p f8
+tag l5 > .git/HEAD
+
+#
+# cd to t/trash and use
+#
+# git-rev-list ... 2>&1 | sed "$(cat sed.script)"
+#
+# if you ever want to manually debug the operation of git-rev-list
+#
+echo $sed_script > sed.script
+
+test_expect_success 'Testing that the rev-list has correct number of entries' 'git-rev-list HEAD | wc -l | tr -s " "' <<EOF
+19
+EOF
+
+normal_adjacency_count=$(git-rev-list HEAD | check_adjacency | grep -c "\^" | tr -d ' ')
+merge_order_adjacency_count=$(git-rev-list --merge-order HEAD | check_adjacency | grep -c "\^" | tr -d ' ')
+test_expect_success 'Testing that --merge-order produces as many or fewer discontinuities' '[ $merge_order_adjacency_count -le $normal_adjacency_count ]'
-git-rev-list --merge-order --show-breaks HEAD | sed "$(sed_script)" > actual-merge-order
-cat > expected-merge-order <<EOF
+test_output_expect_success 'Simple Merge Order Test' 'git-rev-list --merge-order --show-breaks HEAD' <<EOF
= l5
| l4
| l3
@@ -86,15 +183,7 @@ cat > expected-merge-order <<EOF
= root
EOF
-git-rev-list HEAD | check_adjacency | sed "$(sed_script)" > actual-default-order
-normal_adjacency_count=$(git-rev-list HEAD | check_adjacency | grep -c "\^" | tr -d ' ')
-merge_order_adjacency_count=$(git-rev-list --merge-order HEAD | check_adjacency | grep -c "\^" | tr -d ' ')
-
-test_expect_success 'Testing that the rev-list has correct number of entries' '[ $(git-rev-list HEAD | wc -l) -eq 19 ]'
-test_expect_success 'Testing that --merge-order produces the correct result' 'diff expected-merge-order actual-merge-order'
-test_expect_success 'Testing that --merge-order produces as many or fewer discontinuities' '[ $merge_order_adjacency_count -le $normal_adjacency_count ]'
-
-cat > expected-merge-order-1 <<EOF
+test_output_expect_success 'Multiple heads' 'git-rev-list --merge-order a3 b3 c3' <<EOF
c3
c2
c1
@@ -111,10 +200,7 @@ l0
root
EOF
-git-rev-list --merge-order $a3 $b3 $c3 | sed "$(sed_script)" > actual-merge-order-1
-test_expect_success 'Testing multiple heads' 'diff expected-merge-order-1 actual-merge-order-1'
-
-cat > expected-merge-order-2 <<EOF
+test_output_expect_success 'Prune at a1' 'git-rev-list --merge-order a3 b3 c3 ^a1' <<EOF
c3
c2
c1
@@ -125,10 +211,7 @@ a3
a2
EOF
-git-rev-list --merge-order $a3 $b3 $c3 ^$a1 | sed "$(sed_script)" > actual-merge-order-2
-test_expect_success 'Testing stop' 'diff expected-merge-order-2 actual-merge-order-2'
-
-cat > expected-merge-order-3 <<EOF
+test_output_expect_success 'Prune at l1' 'git-rev-list --merge-order a3 b3 c3 ^l1' <<EOF
c3
c2
c1
@@ -142,10 +225,26 @@ a0
l2
EOF
-git-rev-list --merge-order $a3 $b3 $c3 ^$l1 | sed "$(sed_script)" > actual-merge-order-3
-test_expect_success 'Testing stop in linear epoch' 'diff expected-merge-order-3 actual-merge-order-3'
+test_output_expect_success 'Head at l5, Prune at l1' 'git-rev-list --merge-order l5 ^l1' <<EOF
+l5
+l4
+l3
+a4
+c3
+c2
+c1
+b4
+b3
+b2
+b1
+a3
+a2
+a1
+a0
+l2
+EOF
-cat > expected-merge-order-4 <<EOF
+test_output_expect_success 'Duplicated head arguments' 'git-rev-list --merge-order l5 l5 ^l1' <<EOF
l5
l4
l3
@@ -164,12 +263,136 @@ a0
l2
EOF
-git-rev-list --merge-order $l5 ^$l1 | sed "$(sed_script)" > actual-merge-order-4
-test_expect_success 'Testing start in linear epoch, stop after non-linear epoch' 'diff expected-merge-order-4 actual-merge-order-4'
+test_output_expect_success 'Prune near merge' 'git-rev-list --merge-order a4 ^c3' <<EOF
+a4
+b4
+b3
+a3
+a2
+a1
+EOF
+
+test_output_expect_success '--wrt-author foobar@example.com' 'git-rev-list --merge-order --show-breaks --wrt-author --author=foobar@example.com HEAD' <<EOF
+= l5
+| l4
+| l3
+= a4
+| c3
+| c2
+| c1
+^ b4
+| a3
+| a2
+| a1
+^ b3
+| b2
+| b1
+= a0
+| l2
+| l1
+| l0
+= root
+EOF
+
+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
+= l5
+| l4
+| l3
+= a4
+| c3
+| c2
+| c1
+^ b4
+| a3
+| a2
+| a1
+^ b3
+| b2
+EOF
+
+test_output_expect_success "w.r.t e@example.com" 'git-rev-list --show-breaks --wrt-author --author=e@example.com e10 f9' <<EOF
+| f9
+^ e10
+| f8
+| f7
+| f6
+| f5
+^ e9
+| e8
+| e7
+| e6
+| f4
+| f3
+^ e5
+| e4
+| f2
+| f1
+^ e3
+| e2
+= e1
+EOF
-git-rev-list --merge-order $l5 $l5 ^$l1 2>/dev/null | sed "$(sed_script)" > actual-merge-order-5
-test_expect_success 'Testing duplicated start arguments' 'diff expected-merge-order-4 actual-merge-order-5'
-test_expect_success 'Testing exclusion near merge' 'git-rev-list --merge-order $a4 ^$c3 2>/dev/null'
+test_output_expect_success "w.r.t f@example.com" 'git-rev-list --show-breaks --wrt-author --author=f@example.com e10 f9' <<EOF
+| f9
+^ e10
+| f8
+| f7
+| f6
+| f5
+^ e9
+| e8
+| e7
+| e6
+| f4
+| f3
+^ e5
+| e4
+| e3
+| e2
+^ f2
+| f1
+= e1
+EOF
+
+as_author "" test_output_expect_success "w.r.t nobody" 'git-rev-list --show-breaks --wrt-author f9 e10' <<EOF
+| e10
+| e9
+| e8
+| e7
+^ f9
+| f8
+| f7
+| f6
+| e6
+| e5
+| e4
+| e3
+| e2
+^ f5
+| f4
+| f3
+| f2
+| f1
+= e1
+EOF
+
+test_output_expect_success "head has no parent" 'git-rev-list --show-breaks root' <<EOF
+= root
+EOF
+
+test_output_expect_success "two nodes - one head, one base" 'git-rev-list --show-breaks l0' <<EOF
+= l0
+= root
+EOF
+
+test_output_expect_success "three nodes one head, one internal, one base" 'git-rev-list --show-breaks l1' <<EOF
+= l1
+| l0
+= root
+EOF
+test_expect_failure "all heads uninteresting" 'git-rev-list --show-breaks a3 ^a3'
+#
+#
test_done
^ permalink raw reply [flat|nested] 3+ messages in thread
* Heads up on work in progress - was: [PATCH 2/2] Add support ... [rev 11]
2005-06-10 9:23 [PATCH 2/2] Add support for author-oriented git-rev-list switches [rev 11] Jon Seymour
@ 2005-06-11 5:28 ` Jon Seymour
2005-06-11 5:31 ` Jon Seymour
0 siblings, 1 reply; 3+ messages in thread
From: Jon Seymour @ 2005-06-11 5:28 UTC (permalink / raw)
To: git; +Cc: Linus Torvalds, Petr Baudis, Junio C Hamano
Folks,
Thought I'd just give you a heads up that I am presently undertaking a
further substantial re factorisation and generalization of epoch.c.
The changes in this work we be largely limited to epoch.c and
epoch.c-related parts of rev-list.c.
The upcoming changes will not substantially change the existing tools
interfaces, so it would be safe to apply the [rev 11] to the git HEAD
provided there is agreement that the tools interfaces proposed in [rev
11] are acceptable.
However, if you want to perform a detailed review of epoch.c, it might
be better to hold off until my next batch of changes are ready. That
said, if you would like to provide feedback on what I have already
done, please feel welcome to.
In my forthcoming changes I intend to:
1. rename epoch.c to commit-graph.c
2. improve separation of concerns between rev-list.c and commit-graph.c
* commit-graph.c will understand everything about incremental graph
traversals, including merge order search and "localized" perspectives
* rev-list.c will be responsible for output limiting [ that is:
stopping the traversal according to user-defined (e.g. parameterised
criteria) ]
3. the epoch_methods structure in [rev11] will be generalized to a
per-traversal "traversal object" (struct graph_traversal) that will
have additional methods such as:
* visit_edge - called on each edge
* visit_commit - called on each visit to a commit
* clean_commit - called to clean any resources allocated during a
call to visit_edge, visit_commit
I'll try to do the right thing and break the changes into a series of
small patches to rev11.
Please forgive me for my previous practice of submitting large changes
- I am quite new to the development-by-patch style of development, and
haven't quite got into the swing of it yet.
jon.
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: Heads up on work in progress - was: [PATCH 2/2] Add support ... [rev 11]
2005-06-11 5:28 ` Heads up on work in progress - was: [PATCH 2/2] Add support ... " Jon Seymour
@ 2005-06-11 5:31 ` Jon Seymour
0 siblings, 0 replies; 3+ messages in thread
From: Jon Seymour @ 2005-06-11 5:31 UTC (permalink / raw)
To: git
> 2. improve separation of concerns between rev-list.c and commit-graph.c
> * commit-graph.c will understand everything about incremental graph
> traversals, including merge order search and "localized" perspectives
> * rev-list.c will be responsible for output limiting [ that is:
> stopping the traversal according to user-defined (e.g. parameterised
> criteria) ]
That'd be - merge order _sort_.
jon.
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2005-06-11 5:27 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2005-06-10 9:23 [PATCH 2/2] Add support for author-oriented git-rev-list switches [rev 11] Jon Seymour
2005-06-11 5:28 ` Heads up on work in progress - was: [PATCH 2/2] Add support ... " Jon Seymour
2005-06-11 5:31 ` Jon Seymour
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).