diff options
author | Linus Torvalds <torvalds@osdl.org> | 2006-02-28 15:07:20 -0800 |
---|---|---|
committer | Junio C Hamano <junkio@cox.net> | 2006-03-01 01:45:50 -0800 |
commit | 765ac8ec469f110e88376e4fac05d0ed475bcb28 (patch) | |
tree | 151dd9135fb335aeffc4b0e4406c8c4cf8b27e2c | |
parent | 70b006b9712b57741ec1320b15aef2f8b1d6a905 (diff) | |
download | git-765ac8ec469f110e88376e4fac05d0ed475bcb28.tar.gz |
Rip out merge-order and make "git log <paths>..." work again.
Well, assuming breaking --merge-order is fine, here's a patch (on top of
the other ones) that makes
git log <filename>
actually work, as far as I can tell.
I didn't add the logic for --before/--after flags, but that should be
pretty trivial, and is independent of this anyway.
Signed-off-by: Junio C Hamano <junkio@cox.net>
-rw-r--r-- | Documentation/git-rev-list.txt | 49 | ||||
-rw-r--r-- | INSTALL | 4 | ||||
-rw-r--r-- | Makefile | 7 | ||||
-rw-r--r-- | epoch.c | 640 | ||||
-rw-r--r-- | epoch.h | 20 | ||||
-rwxr-xr-x | git-archimport.perl | 4 | ||||
-rw-r--r-- | rev-list.c | 114 | ||||
-rw-r--r-- | rev-parse.c | 2 | ||||
-rw-r--r-- | revision.c | 77 | ||||
-rw-r--r-- | revision.h | 2 | ||||
-rwxr-xr-x | t/t6001-rev-list-merge-order.sh | 462 |
11 files changed, 82 insertions, 1299 deletions
diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index 1c6146c764..5b306d6593 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -16,7 +16,7 @@ SYNOPSIS [ \--no-merges ] [ \--remove-empty ] [ \--all ] - [ [ \--merge-order [ \--show-breaks ] ] | [ \--topo-order ] ] + [ \--topo-order ] [ \--parents ] [ \--objects [ \--unpacked ] ] [ \--pretty | \--header ] @@ -94,57 +94,10 @@ OPTIONS topological order (i.e. descendant commits are shown before their parents). ---merge-order:: - When 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 -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. -+ -Invariant 2 states that commits unique to "later" parents in a merge, appear -before all commits from "earlier" parents of a merge. - ---show-breaks:: - 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 -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 -represent an arbitrary DAG in a linear form. -+ -`--show-breaks` is only valid if `--merge-order` is also specified. - - Author ------ Written by Linus Torvalds <torvalds@osdl.org> -Original *--merge-order* logic by Jon Seymour <jon.seymour@gmail.com> - Documentation -------------- Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. @@ -40,9 +40,7 @@ Issues of note: If you don't have openssl, you can use one of the SHA1 libraries that come with git (git includes the one from Mozilla, and has - its own PowerPC-optimized one too - see the Makefile), and you - can avoid the bignum support by excising git-rev-list support - for "--merge-order" (by hand). + its own PowerPC and ARM optimized ones too - see the Makefile). - "libcurl" and "curl" executable. git-http-fetch and git-fetch use them. If you do not use http @@ -6,8 +6,8 @@ all: # on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default # choice) has very fast version optimized for i586. # -# Define NO_OPENSSL environment variable if you do not have OpenSSL. You will -# miss out git-rev-list --merge-order. This also implies MOZILLA_SHA1. +# Define NO_OPENSSL environment variable if you do not have OpenSSL. +# This also implies MOZILLA_SHA1. # # Define NO_CURL if you do not have curl installed. git-http-pull and # git-http-push are not built, and you cannot use http:// and https:// @@ -191,7 +191,7 @@ LIB_FILE=libgit.a LIB_H = \ blob.h cache.h commit.h count-delta.h csum-file.h delta.h \ - diff.h epoch.h object.h pack.h pkt-line.h quote.h refs.h \ + diff.h object.h pack.h pkt-line.h quote.h refs.h \ run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h DIFF_OBJS = \ @@ -324,7 +324,6 @@ ifndef NO_CURL endif ifndef NO_OPENSSL - LIB_OBJS += epoch.o OPENSSL_LIBSSL = -lssl ifdef OPENSSLDIR # Again this may be problematic -- gcc does not always want -R. diff --git a/epoch.c b/epoch.c deleted file mode 100644 index 0f374921da..0000000000 --- a/epoch.c +++ /dev/null @@ -1,640 +0,0 @@ -/* - * Copyright (c) 2005, Jon Seymour - * - * For more information about epoch theory on which this module is based, - * refer to http://blackcubes.dyndns.org/epoch/. That web page defines - * terms such as "epoch" and "minimal, non-linear epoch" and provides rationales - * for some of the algorithms used here. - * - */ -#include <stdlib.h> - -/* Provides arbitrary precision integers required to accurately represent - * fractional mass: */ -#include <openssl/bn.h> - -#include "cache.h" -#include "commit.h" -#include "revision.h" -#include "epoch.h" - -struct fraction { - BIGNUM numerator; - 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; - -static BN_CTX *get_BN_CTX(void) -{ - if (!context) { - context = BN_CTX_new(); - } - return context; -} - -static struct fraction *new_zero(void) -{ - struct fraction *result = xmalloc(sizeof(*result)); - BN_init(&result->numerator); - BN_init(&result->denominator); - BN_zero(&result->numerator); - BN_one(&result->denominator); - return result; -} - -static void clear_fraction(struct fraction *fraction) -{ - BN_clear(&fraction->numerator); - BN_clear(&fraction->denominator); -} - -static struct fraction *divide(struct fraction *result, struct fraction *fraction, int divisor) -{ - BIGNUM bn_divisor; - - 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_clear(&bn_divisor); - return result; -} - -static struct fraction *init_fraction(struct fraction *fraction) -{ - BN_init(&fraction->numerator); - BN_init(&fraction->denominator); - BN_zero(&fraction->numerator); - BN_one(&fraction->denominator); - return fraction; -} - -static struct fraction *get_one(void) -{ - if (!one) { - one = new_zero(); - BN_one(&one->numerator); - } - return one; -} - -static struct fraction *get_zero(void) -{ - if (!zero) { - zero = new_zero(); - } - return zero; -} - -static struct fraction *copy(struct fraction *to, struct fraction *from) -{ - BN_copy(&to->numerator, &from->numerator); - BN_copy(&to->denominator, &from->denominator); - return to; -} - -static struct fraction *add(struct fraction *result, 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_clear(&a); - BN_clear(&b); - BN_clear(&gcd); - - return result; -} - -static int compare(struct fraction *left, struct fraction *right) -{ - BIGNUM a, b; - int result; - - 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; -} - -struct mass_counter { - struct fraction seen; - struct fraction pending; -}; - -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)); - - 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; -} - -static void free_mass_counter(struct mass_counter *counter) -{ - clear_fraction(&counter->seen); - clear_fraction(&counter->pending); - free(counter); -} - -/* - * Finds the base commit of a list of commits. - * - * One property of the commit being searched for is that every commit reachable - * from the base commit is reachable from the commits in the starting list only - * via paths that include the base commit. - * - * This algorithm uses a conservation of mass approach to find the base commit. - * - * We start by injecting one unit of mass into the graph at each - * of the commits in the starting list. Injecting mass into a commit - * is achieved by adding to its pending mass counter and, if it is not already - * enqueued, enqueuing the commit in a list of pending commits, in latest - * commit date first order. - * - * The algorithm then proceeds to visit each commit in the pending queue. - * Upon each visit, the pending mass is added to the mass already seen for that - * commit and then divided into N equal portions, where N is the number of - * parents of the commit being visited. The divided portions are then injected - * into each of the parents. - * - * The algorithm continues until we discover a commit which has seen all the - * mass originally injected or until we run out of things to do. - * - * If we find a commit that has seen all the original mass, we have found - * the common base of all the commits in the starting list. - * - * The algorithm does _not_ depend on accurate timestamps for correct operation. - * However, reasonably sane (e.g. non-random) timestamps are required in order - * to prevent an exponential performance characteristic. The occasional - * timestamp inaccuracy will not dramatically affect performance but may - * result in more nodes being processed than strictly necessary. - * - * This procedure sets *boundary to the address of the base commit. It returns - * non-zero if, and only if, there was a problem parsing one of the - * commits discovered during the traversal. - */ -static int find_base_for_list(struct commit_list *list, struct commit **boundary) -{ - int ret = 0; - struct commit_list *cleaner = NULL; - struct commit_list *pending = NULL; - struct fraction injected; - init_fraction(&injected); - *boundary = NULL; - - for (; list; list = list->next) { - struct commit *item = list->item; - - if (!item->object.util) { - new_mass_counter(list->item, get_one()); - add(&injected, &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; - int num_parents; - - if ((ret = parse_commit(latest))) - continue; - add(&latest_node->seen, &latest_node->seen, &latest_node->pending); - - num_parents = count_parents(latest); - if (num_parents) { - struct fraction distribution; - struct commit_list *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(parent, &pending); - commit_list_insert(parent, &cleaner); - } else { - if (!compare(&parent_node->pending, get_zero())) - insert_by_date(parent, &pending); - add(&parent_node->pending, &parent_node->pending, &distribution); - } - } - - clear_fraction(&distribution); - } - - 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 - */ -static int find_base(struct commit *head, struct commit **boundary) -{ - int ret = 0; - struct commit_list *pending = NULL; - struct commit_list *next; - - for (next = head->parents; next; next = next->next) { - commit_list_insert(next->item, &pending); - } - ret = find_base_for_list(pending, boundary); - free_commit_list(pending); - - return ret; -} - -/* - * 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; - - ret = parse_commit(item); - if (ret) - return ret; - - if (HAS_EXACTLY_ONE_PARENT(item)) { - /* - * We are at the start of a maximimal linear epoch. - * Traverse to the end. - */ - while (HAS_EXACTLY_ONE_PARENT(item) && !ret) { - item = item->parents->item; - ret = parse_commit(item); - } - *boundary = item; - - } else { - /* - * Otherwise, we are at the start of a minimal, non-linear - * epoch - find the common base of all parents. - */ - ret = find_base(item, boundary); - } - - return ret; -} - -/* - * Returns non-zero if parent is known to be a parent of child. - */ -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))) - 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. - * - * 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 - * eventually reaches them. - */ -static void mark_ancestors_uninteresting(struct commit *commit) -{ - unsigned int flags = commit->object.flags; - int visited = flags & VISITED; - int boundary = flags & BOUNDARY; - int uninteresting = flags & 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 - * 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 - * 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. - */ -static void sort_first_epoch(struct commit *head, struct commit_list **stack) -{ - struct commit_list *parents; - - head->object.flags |= VISITED; - - /* - * 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. - */ - - for (parents = head->parents; parents; parents = parents->next) { - struct commit *parent = parents->item; - - 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); - } - - 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 (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; - } - } - } - } - - push_onto_merge_order_stack(stack, head); -} - -/* - * 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, int include_last) -{ - unsigned int seen = 0; - int action = CONTINUE; - - while (*stack && (action != STOP)) { - struct commit *next = pop_commit(stack); - seen |= next->object.flags; - if (*stack || include_last) { - if (!*stack) - next->object.flags |= BOUNDARY; - action = emitter(next); - } - } - - if (*stack) { - free_commit_list(*stack); - *stack = NULL; - } - - return (action == STOP || (seen & UNINTERESTING)) ? STOP : CONTINUE; -} - -/* - * 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) -{ - struct commit *next = head_of_epoch; - int ret = 0; - int action = CONTINUE; - - ret = parse_commit(head_of_epoch); - - next->object.flags |= BOUNDARY; - - while (next && next->parents && !ret && (action != STOP)) { - struct commit *base = NULL; - - ret = find_next_epoch_boundary(next, &base); - if (ret) - return ret; - next->object.flags |= BOUNDARY; - 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 == NULL)); - next = base; - } - } - - if (next && (action != STOP) && !ret) { - emitter(next); - } - - return ret; -} - -/* - * 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 - * 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) -{ - struct commit_list *stack = NULL; - struct commit *base; - int ret = 0; - int action = CONTINUE; - struct commit_list *reversed = NULL; - - for (; list; list = list->next) - commit_list_insert(list->item, &reversed); - - if (!reversed) - return ret; - else if (!reversed->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); - if (ret) - return ret; - if (base) - base->object.flags |= BOUNDARY; - - while (reversed) { - struct commit * next = pop_commit(&reversed); - - if (!(next->object.flags & VISITED) && next!=base) { - sort_first_epoch(next, &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, (base==NULL)); - } - - if (base && (action != STOP)) { - ret = sort_in_merge_order(base, emitter); - } - - return ret; -} diff --git a/epoch.h b/epoch.h deleted file mode 100644 index 3756009060..0000000000 --- a/epoch.h +++ /dev/null @@ -1,20 +0,0 @@ -#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); - -/* Low bits are used by rev-list */ -#define BOUNDARY (1u<<11) -#define VISITED (1u<<12) -#define DISCONTINUITY (1u<<13) -#define LAST_EPOCH_FLAG (1u<<14) - - -#endif /* EPOCH_H */ diff --git a/git-archimport.perl b/git-archimport.perl index 6792624d46..740bc1fd52 100755 --- a/git-archimport.perl +++ b/git-archimport.perl @@ -928,7 +928,7 @@ sub find_parents { # now walk up to the mergepoint collecting what patches we have my $branchtip = git_rev_parse($ps->{branch}); - my @ancestors = `git-rev-list --merge-order $branchtip ^$mergebase`; + my @ancestors = `git-rev-list --topo-order $branchtip ^$mergebase`; my %have; # collected merges this branch has foreach my $merge (@{$ps->{merges}}) { $have{$merge} = 1; @@ -951,7 +951,7 @@ sub find_parents { # see what the remote branch has - these are the merges we # will want to have in a consecutive series from the mergebase my $otherbranchtip = git_rev_parse($branch); - my @needraw = `git-rev-list --merge-order $otherbranchtip ^$mergebase`; + my @needraw = `git-rev-list --topo-order $otherbranchtip ^$mergebase`; my @need; foreach my $needps (@needraw) { # get the psets $needps = commitid2pset($needps); diff --git a/rev-list.c b/rev-list.c index 94f22dd673..6af8d869ee 100644 --- a/rev-list.c +++ b/rev-list.c @@ -4,14 +4,12 @@ #include "commit.h" #include "tree.h" #include "blob.h" -#include "epoch.h" #include "diff.h" #include "revision.h" -/* bits #0-2 in revision.h */ +/* bits #0-3 in revision.h */ -#define COUNTED (1u << 3) -#define SHOWN (1u << 4) +#define COUNTED (1u << 4) #define TMP_MARK (1u << 5) /* for isolated cases; clean after use */ static const char rev_list_usage[] = @@ -25,7 +23,6 @@ static const char rev_list_usage[] = " --remove-empty\n" " --all\n" " ordering output:\n" -" --merge-order [ --show-breaks ]\n" " --topo-order\n" " --date-order\n" " formatting output:\n" @@ -47,22 +44,9 @@ static int show_parents = 0; static int hdr_termination = 0; static const char *commit_prefix = ""; static enum cmit_fmt commit_format = CMIT_FMT_RAW; -static int merge_order = 0; -static int show_breaks = 0; -static int stop_traversal = 0; -static int no_merges = 0; static void show_commit(struct commit *commit) { - commit->object.flags |= SHOWN; - if (show_breaks) { - commit_prefix = "| "; - if (commit->object.flags & DISCONTINUITY) { - commit_prefix = "^ "; - } else if (commit->object.flags & BOUNDARY) { - commit_prefix = "= "; - } - } printf("%s%s", commit_prefix, sha1_to_hex(commit->object.sha1)); if (show_parents) { struct commit_list *parents = commit->parents; @@ -96,73 +80,6 @@ static void show_commit(struct commit *commit) fflush(stdout); } -static int rewrite_one(struct commit **pp) -{ - for (;;) { - struct commit *p = *pp; - if (p->object.flags & (TREECHANGE | UNINTERESTING)) - return 0; - if (!p->parents) - return -1; - *pp = p->parents->item; - } -} - -static void rewrite_parents(struct commit *commit) -{ - struct commit_list **pp = &commit->parents; - while (*pp) { - struct commit_list *parent = *pp; - if (rewrite_one(&parent->item) < 0) { - *pp = parent->next; - continue; - } - pp = &parent->next; - } -} - -static int filter_commit(struct commit * commit) -{ - if (stop_traversal && (commit->object.flags & BOUNDARY)) - return STOP; - if (commit->object.flags & (UNINTERESTING|SHOWN)) - return CONTINUE; - if (revs.min_age != -1 && (commit->date > revs.min_age)) - return CONTINUE; - if (revs.max_age != -1 && (commit->date < revs.max_age)) { - stop_traversal=1; - return CONTINUE; - } - if (no_merges && (commit->parents && commit->parents->next)) - return CONTINUE; - if (revs.paths && revs.dense) { - if (!(commit->object.flags & TREECHANGE)) - return CONTINUE; - rewrite_parents(commit); - } - return DO; -} - -static int process_commit(struct commit * commit) -{ - int action=filter_commit(commit); - - if (action == STOP) { - return STOP; - } - - if (action == CONTINUE) { - return CONTINUE; - } - - if (revs.max_count != -1 && !revs.max_count--) - return STOP; - - show_commit(commit); - - return CONTINUE; -} - static struct object_list **process_blob(struct blob *blob, struct object_list **p, struct name_path *path, @@ -219,8 +136,7 @@ static void show_commit_list(struct rev_info *revs) while ((commit = get_revision(revs)) != NULL) { p = process_tree(commit->tree, p, NULL, ""); - if (process_commit(commit) == STOP) - break; + show_commit(commit); } for (pending = revs->pending_objects; pending; pending = pending->next) { struct object *obj = pending->item; @@ -416,10 +332,6 @@ int main(int argc, const char **argv) commit_prefix = "commit "; continue; } - if (!strncmp(arg, "--no-merges", 11)) { - no_merges = 1; - continue; - } if (!strcmp(arg, "--parents")) { show_parents = 1; continue; @@ -428,14 +340,6 @@ int main(int argc, const char **argv) bisect_list = 1; continue; } - if (!strcmp(arg, "--merge-order")) { - merge_order = 1; - continue; - } - if (!strcmp(arg, "--show-breaks")) { - show_breaks = 1; - continue; - } usage(rev_list_usage); } @@ -456,17 +360,7 @@ int main(int argc, const char **argv) save_commit_buffer = verbose_header; track_object_refs = 0; - if (!merge_order) { - show_commit_list(&revs); - } else { -#ifndef NO_OPENSSL - if (sort_list_in_merge_order(list, &process_commit)) { - die("merge order sort failed\n"); - } -#else - die("merge order sort unsupported, OpenSSL not linked"); -#endif - } + show_commit_list(&revs); return 0; } diff --git a/rev-parse.c b/rev-parse.c index 610eacb35a..f90e999e60 100644 --- a/rev-parse.c +++ b/rev-parse.c @@ -39,14 +39,12 @@ static int is_rev_argument(const char *arg) "--header", "--max-age=", "--max-count=", - "--merge-order", "--min-age=", "--no-merges", "--objects", "--objects-edge", "--parents", "--pretty", - "--show-breaks", "--sparse", "--topo-order", "--date-order", diff --git a/revision.c b/revision.c index f1ac62d7d8..c84f14609b 100644 --- a/revision.c +++ b/revision.c @@ -381,6 +381,9 @@ static void limit_list(struct rev_info *revs) struct commit_list *newlist = NULL; struct commit_list **p = &newlist; + if (revs->paths) + diff_tree_setup_paths(revs->paths); + while (list) { struct commit_list *entry = list; struct commit *commit = list->item; @@ -436,12 +439,13 @@ static void handle_all(struct rev_info *revs, unsigned flags) * Parse revision information, filling in the "rev_info" structure, * and removing the used arguments from the argument list. * - * Returns the number of arguments left ("new argc"). + * Returns the number of arguments left that weren't recognized + * (which are also moved to the head of the argument list) */ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def) { int i, flags, seen_dashdash; - const char **unrecognized = argv+1; + const char **unrecognized = argv + 1; int left = 1; memset(revs, 0, sizeof(*revs)); @@ -525,6 +529,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch revs->remove_empty_trees = 1; continue; } + if (!strncmp(arg, "--no-merges", 11)) { + revs->no_merges = 1; + continue; + } if (!strcmp(arg, "--objects")) { revs->tag_objects = 1; revs->tree_objects = 1; @@ -601,14 +609,11 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch } if (revs->paths) revs->limited = 1; - *unrecognized = NULL; return left; } void prepare_revision_walk(struct rev_info *revs) { - if (revs->paths) - diff_tree_setup_paths(revs->paths); sort_by_date(&revs->commits); if (revs->limited) limit_list(revs); @@ -616,11 +621,67 @@ void prepare_revision_walk(struct rev_info *revs) sort_in_topological_order(&revs->commits, revs->lifo); } +static int rewrite_one(struct commit **pp) +{ + for (;;) { + struct commit *p = *pp; + if (p->object.flags & (TREECHANGE | UNINTERESTING)) + return 0; + if (!p->parents) + return -1; + *pp = p->parents->item; + } +} + +static void rewrite_parents(struct commit *commit) +{ + struct commit_list **pp = &commit->parents; + while (*pp) { + struct commit_list *parent = *pp; + if (rewrite_one(&parent->item) < 0) { + *pp = parent->next; + continue; + } + pp = &parent->next; + } +} + struct commit *get_revision(struct rev_info *revs) { - if (!revs->commits) + struct commit_list *list = revs->commits; + struct commit *commit; + + if (!list) return NULL; - return pop_most_recent_commit(&revs->commits, SEEN); -} + /* Check the max_count ... */ + commit = list->item; + switch (revs->max_count) { + case -1: + break; + case 0: + return NULL; + default: + revs->max_count--; + } + do { + commit = pop_most_recent_commit(&revs->commits, SEEN); + if (commit->object.flags & (UNINTERESTING|SHOWN)) + continue; + if (revs->min_age != -1 && (commit->date > revs->min_age)) + continue; + if (revs->max_age != -1 && (commit->date < revs->max_age)) + return NULL; + if (revs->no_merges && commit->parents && commit->parents->next) + continue; + if (revs->paths && revs->dense) { + if (!(commit->object.flags & TREECHANGE)) + continue; + rewrite_parents(commit); + } + commit->object.flags |= SHOWN; + return commit; + } while (revs->commits); + return NULL; +} diff --git a/revision.h b/revision.h index 0bed3c04ff..0043c1694c 100644 --- a/revision.h +++ b/revision.h @@ -4,6 +4,7 @@ #define SEEN (1u<<0) #define UNINTERESTING (1u<<1) #define TREECHANGE (1u<<2) +#define SHOWN (1u<<3) struct rev_info { /* Starting list */ @@ -16,6 +17,7 @@ struct rev_info { /* Traversal flags */ unsigned int dense:1, + no_merges:1, remove_empty_trees:1, lifo:1, topo_order:1, diff --git a/t/t6001-rev-list-merge-order.sh b/t/t6001-rev-list-merge-order.sh deleted file mode 100755 index 7724e8a8c5..0000000000 --- a/t/t6001-rev-list-merge-order.sh +++ /dev/null @@ -1,462 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2005 Jon Seymour -# - -test_description='Tests git-rev-list --merge-order functionality' - -. ./test-lib.sh -. ../t6000lib.sh # t6xxx specific functions - -# test-case specific test function -check_adjacency() -{ - read previous - echo "= $previous" - while read next - do - if ! (git-cat-file commit $previous | grep "^parent $next" >/dev/null) - then - echo "^ $next" - else - echo "| $next" - fi - previous=$next - done -} - -list_duplicates() -{ - "$@" | sort | uniq -d -} - -grep_stderr() -{ - args=$1 - shift 1 - "$@" 2>&1 | grep "$args" -} - -date >path0 -git-update-index --add path0 -save_tag tree git-write-tree -on_committer_date "1971-08-16 00:00:00" hide_error save_tag root unique_commit root tree -on_committer_date "1971-08-16 00:00:01" save_tag l0 unique_commit l0 tree -p root -on_committer_date "1971-08-16 00:00:02" save_tag l1 unique_commit l1 tree -p l0 -on_committer_date "1971-08-16 00:00:03" save_tag l2 unique_commit l2 tree -p l1 -on_committer_date "1971-08-16 00:00:04" save_tag a0 unique_commit a0 tree -p l2 -on_committer_date "1971-08-16 00:00:05" save_tag a1 unique_commit a1 tree -p a0 -on_committer_date "1971-08-16 00:00:06" save_tag b1 unique_commit b1 tree -p a0 -on_committer_date "1971-08-16 00:00:07" save_tag c1 unique_commit c1 tree -p b1 -on_committer_date "1971-08-16 00:00:08" as_author foobar@example.com save_tag b2 unique_commit b2 tree -p b1 -on_committer_date "1971-08-16 00:00:09" save_tag b3 unique_commit b2 tree -p b2 -on_committer_date "1971-08-16 00:00:10" save_tag c2 unique_commit c2 tree -p c1 -p b2 -on_committer_date "1971-08-16 00:00:11" save_tag c3 unique_commit c3 tree -p c2 -on_committer_date "1971-08-16 00:00:12" save_tag a2 unique_commit a2 tree -p a1 -on_committer_date "1971-08-16 00:00:13" save_tag a3 unique_commit a3 tree -p a2 -on_committer_date "1971-08-16 00:00:14" save_tag b4 unique_commit b4 tree -p b3 -p a3 -on_committer_date "1971-08-16 00:00:15" save_tag a4 unique_commit a4 tree -p a3 -p b4 -p c3 -on_committer_date "1971-08-16 00:00:16" save_tag l3 unique_commit l3 tree -p a4 -on_committer_date "1971-08-16 00:00:17" save_tag l4 unique_commit l4 tree -p l3 -on_committer_date "1971-08-16 00:00:18" save_tag l5 unique_commit l5 tree -p l4 -on_committer_date "1971-08-16 00:00:19" save_tag m1 unique_commit m1 tree -p a4 -p c3 -on_committer_date "1971-08-16 00:00:20" save_tag m2 unique_commit m2 tree -p c3 -p a4 -on_committer_date "1971-08-16 00:00:21" hide_error save_tag alt_root unique_commit alt_root tree -on_committer_date "1971-08-16 00:00:22" save_tag r0 unique_commit r0 tree -p alt_root -on_committer_date "1971-08-16 00:00:23" save_tag r1 unique_commit r1 tree -p r0 -on_committer_date "1971-08-16 00:00:24" save_tag l5r1 unique_commit l5r1 tree -p l5 -p r1 -on_committer_date "1971-08-16 00:00:25" save_tag r1l5 unique_commit r1l5 tree -p r1 -p l5 - - -# -# note: as of 20/6, it isn't possible to create duplicate parents, so this -# can't be tested. -# -#on_committer_date "1971-08-16 00:00:20" save_tag m3 unique_commit m3 tree -p c3 -p a4 -p c3 -hide_error save_tag e1 as_author e@example.com unique_commit e1 tree -save_tag e2 as_author e@example.com unique_commit e2 tree -p e1 -save_tag f1 as_author f@example.com unique_commit f1 tree -p e1 -save_tag e3 as_author e@example.com unique_commit e3 tree -p e2 -save_tag f2 as_author f@example.com unique_commit f2 tree -p f1 -save_tag e4 as_author e@example.com unique_commit e4 tree -p e3 -p f2 -save_tag e5 as_author e@example.com unique_commit e5 tree -p e4 -save_tag f3 as_author f@example.com unique_commit f3 tree -p f2 -save_tag f4 as_author f@example.com unique_commit f4 tree -p f3 -save_tag e6 as_author e@example.com unique_commit e6 tree -p e5 -p f4 -save_tag f5 as_author f@example.com unique_commit f5 tree -p f4 -save_tag f6 as_author f@example.com unique_commit f6 tree -p f5 -p e6 -save_tag e7 as_author e@example.com unique_commit e7 tree -p e6 -save_tag e8 as_author e@example.com unique_commit e8 tree -p e7 -save_tag e9 as_author e@example.com unique_commit e9 tree -p e8 -save_tag f7 as_author f@example.com unique_commit f7 tree -p f6 -save_tag f8 as_author f@example.com unique_commit f8 tree -p f7 -save_tag f9 as_author f@example.com unique_commit f9 tree -p f8 -save_tag e10 as_author e@example.com unique_commit e1 tree -p e9 -p f8 - -hide_error save_tag g0 unique_commit g0 tree -save_tag g1 unique_commit g1 tree -p g0 -save_tag h1 unique_commit g2 tree -p g0 -save_tag g2 unique_commit g3 tree -p g1 -p h1 -save_tag h2 unique_commit g4 tree -p g2 -save_tag g3 unique_commit g5 tree -p g2 -save_tag g4 unique_commit g6 tree -p g3 -p h2 - -git-update-ref HEAD $(tag l5) - -test_output_expect_success 'rev-list has correct number of entries' 'git-rev-list HEAD | wc -l | tr -d \" \"' <<EOF -19 -EOF - -if git-rev-list --merge-order HEAD 2>&1 | grep 'OpenSSL not linked' >/dev/null -then - test_expect_success 'skipping merge-order test' : - test_done - exit -fi - -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 '--merge-order produces as many or fewer discontinuities' '[ $merge_order_adjacency_count -le $normal_adjacency_count ]' -test_output_expect_success 'simple merge order' 'git-rev-list --merge-order --show-breaks HEAD' <<EOF -= l5 -| l4 -| l3 -= a4 -| c3 -| c2 -| c1 -^ b4 -| b3 -| b2 -| b1 -^ a3 -| a2 -| a1 -= a0 -| l2 -| l1 -| l0 -= root -EOF - -test_output_expect_success 'two diamonds merge order (g6)' 'git-rev-list --merge-order --show-breaks g4' <<EOF -= g4 -| h2 -^ g3 -= g2 -| h1 -^ g1 -= g0 -EOF - -test_output_expect_success 'multiple heads' 'git-rev-list --merge-order a3 b3 c3' <<EOF -c3 -c2 -c1 -b3 -b2 -b1 -a3 -a2 -a1 -a0 -l2 -l1 -l0 -root -EOF - -test_output_expect_success 'multiple heads, prune at a1' 'git-rev-list --merge-order a3 b3 c3 ^a1' <<EOF -c3 -c2 -c1 -b3 -b2 -b1 -a3 -a2 -EOF - -test_output_expect_success 'multiple heads, prune at l1' 'git-rev-list --merge-order a3 b3 c3 ^l1' <<EOF -c3 -c2 -c1 -b3 -b2 -b1 -a3 -a2 -a1 -a0 -l2 -EOF - -test_output_expect_success 'cross-epoch, 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 - -test_output_expect_success 'duplicated head arguments' 'git-rev-list --merge-order l5 l5 ^l1' <<EOF -l5 -l4 -l3 -a4 -c3 -c2 -c1 -b4 -b3 -b2 -b1 -a3 -a2 -a1 -a0 -l2 -EOF - -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 "head has no parent" 'git-rev-list --merge-order --show-breaks root' <<EOF -= root -EOF - -test_output_expect_success "two nodes - one head, one base" 'git-rev-list --merge-order --show-breaks l0' <<EOF -= l0 -= root -EOF - -test_output_expect_success "three nodes one head, one internal, one base" 'git-rev-list --merge-order --show-breaks l1' <<EOF -= l1 -| l0 -= root -EOF - -test_output_expect_success "linear prune l2 ^root" 'git-rev-list --merge-order --show-breaks l2 ^root' <<EOF -^ l2 -| l1 -| l0 -EOF - -test_output_expect_success "linear prune l2 ^l0" 'git-rev-list --merge-order --show-breaks l2 ^l0' <<EOF -^ l2 -| l1 -EOF - -test_output_expect_success "linear prune l2 ^l1" 'git-rev-list --merge-order --show-breaks l2 ^l1' <<EOF -^ l2 -EOF - -test_output_expect_success "linear prune l5 ^a4" 'git-rev-list --merge-order --show-breaks l5 ^a4' <<EOF -^ l5 -| l4 -| l3 -EOF - -test_output_expect_success "linear prune l5 ^l3" 'git-rev-list --merge-order --show-breaks l5 ^l3' <<EOF -^ l5 -| l4 -EOF - -test_output_expect_success "linear prune l5 ^l4" 'git-rev-list --merge-order --show-breaks l5 ^l4' <<EOF -^ l5 -EOF - -test_output_expect_success "max-count 10 - merge order" 'git-rev-list --merge-order --show-breaks --max-count=10 l5' <<EOF -= l5 -| l4 -| l3 -= a4 -| c3 -| c2 -| c1 -^ b4 -| b3 -| b2 -EOF - -test_output_expect_success "max-count 10 - non merge order" 'git-rev-list --max-count=10 l5' <<EOF -l5 -l4 -l3 -a4 -b4 -a3 -a2 -c3 -c2 -b3 -EOF - -test_output_expect_success '--max-age=c3, no --merge-order' "git-rev-list --max-age=$(commit_date c3) l5" <<EOF -l5 -l4 -l3 -a4 -b4 -a3 -a2 -c3 -EOF - -test_output_expect_success '--max-age=c3, --merge-order' "git-rev-list --merge-order --max-age=$(commit_date c3) l5" <<EOF -l5 -l4 -l3 -a4 -c3 -b4 -a3 -a2 -EOF - -test_output_expect_success 'one specified head reachable from another a4, c3, --merge-order' "list_duplicates git-rev-list --merge-order a4 c3" <<EOF -EOF - -test_output_expect_success 'one specified head reachable from another c3, a4, --merge-order' "list_duplicates git-rev-list --merge-order c3 a4" <<EOF -EOF - -test_output_expect_success 'one specified head reachable from another a4, c3, no --merge-order' "list_duplicates git-rev-list a4 c3" <<EOF -EOF - -test_output_expect_success 'one specified head reachable from another c3, a4, no --merge-order' "list_duplicates git-rev-list c3 a4" <<EOF -EOF - -test_output_expect_success 'graph with c3 and a4 parents of head' "list_duplicates git-rev-list m1" <<EOF -EOF - -test_output_expect_success 'graph with a4 and c3 parents of head' "list_duplicates git-rev-list m2" <<EOF -EOF - -test_expect_success "head ^head --merge-order" 'git-rev-list --merge-order --show-breaks a3 ^a3' <<EOF -EOF - -# -# can't test this now - duplicate parents can't be created -# -#test_output_expect_success 'duplicate parents' 'git-rev-list --parents --merge-order --show-breaks m3' <<EOF -#= m3 c3 a4 c3 -#| a4 c3 b4 a3 -#| b4 a3 b3 -#| b3 b2 -#^ a3 a2 -#| a2 a1 -#| a1 a0 -#^ c3 c2 -#| c2 b2 c1 -#| b2 b1 -#^ c1 b1 -#| b1 a0 -#= a0 l2 -#| l2 l1 -#| l1 l0 -#| l0 root -#= root -#EOF - -test_expect_success "head ^head no --merge-order" 'git-rev-list a3 ^a3' <<EOF -EOF - -test_output_expect_success 'simple merge order (l5r1)' 'git-rev-list --merge-order --show-breaks l5r1' <<EOF -= l5r1 -| r1 -| r0 -| alt_root -^ l5 -| l4 -| l3 -| a4 -| c3 -| c2 -| c1 -^ b4 -| b3 -| b2 -| b1 -^ a3 -| a2 -| a1 -| a0 -| l2 -| l1 -| l0 -= root -EOF - -test_output_expect_success 'simple merge order (r1l5)' 'git-rev-list --merge-order --show-breaks r1l5' <<EOF -= r1l5 -| l5 -| l4 -| l3 -| a4 -| c3 -| c2 -| c1 -^ b4 -| b3 -| b2 -| b1 -^ a3 -| a2 -| a1 -| a0 -| l2 -| l1 -| l0 -| root -^ r1 -| r0 -= alt_root -EOF - -test_output_expect_success "don't print things unreachable from one branch" "git-rev-list a3 ^b3 --merge-order" <<EOF -a3 -a2 -a1 -EOF - -test_output_expect_success "--merge-order a4 l3" "git-rev-list --merge-order a4 l3" <<EOF -l3 -a4 -c3 -c2 -c1 -b4 -b3 -b2 -b1 -a3 -a2 -a1 -a0 -l2 -l1 -l0 -root -EOF - -# -# - -test_done |