summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunio C Hamano <junkio@cox.net>2006-03-04 21:21:17 (GMT)
committerJunio C Hamano <junkio@cox.net>2006-03-04 21:21:17 (GMT)
commit21dbe12c76e43735dc623067c1b09ef43519d9fc (patch)
tree921e8d74970aba64d3f1c40815aa19d24383ebdc
parented19f367220a50e4e2a5c1a00b03c14eafcaba89 (diff)
parent64bc6e3db54ef9e2accfdb8e6c8168f789946fcb (diff)
downloadgit-21dbe12c76e43735dc623067c1b09ef43519d9fc.zip
git-21dbe12c76e43735dc623067c1b09ef43519d9fc.tar.gz
git-21dbe12c76e43735dc623067c1b09ef43519d9fc.tar.bz2
Merge branch 'lt/rev-list'
* lt/rev-list: setup_revisions(): handle -n<n> and -<n> internally. git-log (internal): more options. git-log (internal): add approxidate. Rip out merge-order and make "git log <paths>..." work again. Tie it all together: "git log" Introduce trivial new pager.c helper infrastructure git-rev-list libification: rev-list walking Splitting rev-list into revisions lib, end of beginning. rev-list split: minimum fixup. First cut at libifying revlist generation
-rw-r--r--Documentation/git-rev-list.txt49
-rw-r--r--INSTALL4
-rw-r--r--Makefile13
-rw-r--r--cache.h3
-rw-r--r--epoch.c639
-rw-r--r--epoch.h21
-rwxr-xr-xgit-archimport.perl4
-rw-r--r--git.c79
-rw-r--r--pager.c48
-rw-r--r--rev-list.c737
-rw-r--r--rev-parse.c2
-rw-r--r--revision.c722
-rw-r--r--revision.h57
-rwxr-xr-xt/t6001-rev-list-merge-order.sh462
14 files changed, 954 insertions, 1886 deletions
diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt
index 27f18e2..8255ae1 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 | \--objects-edge] [ \--unpacked ] ]
[ \--pretty | \--header ]
@@ -102,57 +102,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>.
diff --git a/INSTALL b/INSTALL
index 433449f..63af8ec 100644
--- a/INSTALL
+++ b/INSTALL
@@ -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
diff --git a/Makefile b/Makefile
index a5eb0c4..a1ec04e 100644
--- a/Makefile
+++ b/Makefile
@@ -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,8 +191,8 @@ 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 \
- run-command.h strbuf.h tag.h tree.h git-compat-util.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 = \
diff.o diffcore-break.o diffcore-order.o diffcore-pathspec.o \
@@ -206,7 +206,7 @@ LIB_OBJS = \
quote.o read-cache.o refs.o run-command.o \
server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
- fetch-clone.o \
+ fetch-clone.o revision.o pager.o \
$(DIFF_OBJS)
LIBS = $(LIB_FILE)
@@ -329,7 +329,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.
@@ -455,7 +454,7 @@ strip: $(PROGRAMS) git$X
git$X: git.c $(LIB_FILE)
$(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \
- $(CFLAGS) $(COMPAT_CFLAGS) -o $@ $(filter %.c,$^) $(LIB_FILE)
+ $(ALL_CFLAGS) -o $@ $(filter %.c,$^) $(LIB_FILE) $(LIBS)
$(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
rm -f $@
diff --git a/cache.h b/cache.h
index 0d3b244..8dc1de1 100644
--- a/cache.h
+++ b/cache.h
@@ -354,4 +354,7 @@ extern int copy_fd(int ifd, int ofd);
extern int receive_unpack_pack(int fd[2], const char *me, int quiet);
extern int receive_keep_pack(int fd[2], const char *me, int quiet);
+/* pager.c */
+extern void setup_pager(void);
+
#endif /* CACHE_H */
diff --git a/epoch.c b/epoch.c
deleted file mode 100644
index 3a76748..0000000
--- a/epoch.c
+++ /dev/null
@@ -1,639 +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 "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 7493d5a..0000000
--- a/epoch.h
+++ /dev/null
@@ -1,21 +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 UNINTERESTING (1u<<10)
-#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 6792624..740bc1f 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/git.c b/git.c
index 993cd0d..a547dbd 100644
--- a/git.c
+++ b/git.c
@@ -12,6 +12,10 @@
#include "git-compat-util.h"
#include "exec_cmd.h"
+#include "cache.h"
+#include "commit.h"
+#include "revision.h"
+
#ifndef PATH_MAX
# define PATH_MAX 4096
#endif
@@ -245,6 +249,80 @@ static int cmd_help(int argc, char **argv, char **envp)
return 0;
}
+#define LOGSIZE (65536)
+
+static int cmd_log(int argc, char **argv, char **envp)
+{
+ struct rev_info rev;
+ struct commit *commit;
+ char *buf = xmalloc(LOGSIZE);
+ static enum cmit_fmt commit_format = CMIT_FMT_DEFAULT;
+ int abbrev = DEFAULT_ABBREV;
+ int show_parents = 0;
+ const char *commit_prefix = "commit ";
+
+ argc = setup_revisions(argc, argv, &rev, "HEAD");
+ while (1 < argc) {
+ char *arg = argv[1];
+ if (!strncmp(arg, "--pretty", 8)) {
+ commit_format = get_commit_format(arg + 8);
+ if (commit_format == CMIT_FMT_ONELINE)
+ commit_prefix = "";
+ }
+ else if (!strcmp(arg, "--parents")) {
+ show_parents = 1;
+ }
+ else if (!strcmp(arg, "--no-abbrev")) {
+ abbrev = 0;
+ }
+ else if (!strncmp(arg, "--abbrev=", 9)) {
+ abbrev = strtoul(arg + 9, NULL, 10);
+ if (abbrev && abbrev < MINIMUM_ABBREV)
+ abbrev = MINIMUM_ABBREV;
+ else if (40 < abbrev)
+ abbrev = 40;
+ }
+ else
+ die("unrecognized argument: %s", arg);
+ argc--; argv++;
+ }
+
+ prepare_revision_walk(&rev);
+ setup_pager();
+ while ((commit = get_revision(&rev)) != NULL) {
+ printf("%s%s", commit_prefix,
+ sha1_to_hex(commit->object.sha1));
+ if (show_parents) {
+ struct commit_list *parents = commit->parents;
+ while (parents) {
+ struct object *o = &(parents->item->object);
+ parents = parents->next;
+ if (o->flags & TMP_MARK)
+ continue;
+ printf(" %s", sha1_to_hex(o->sha1));
+ o->flags |= TMP_MARK;
+ }
+ /* TMP_MARK is a general purpose flag that can
+ * be used locally, but the user should clean
+ * things up after it is done with them.
+ */
+ for (parents = commit->parents;
+ parents;
+ parents = parents->next)
+ parents->item->object.flags &= ~TMP_MARK;
+ }
+ if (commit_format == CMIT_FMT_ONELINE)
+ putchar(' ');
+ else
+ putchar('\n');
+ pretty_print_commit(commit_format, commit, ~0, buf,
+ LOGSIZE, abbrev);
+ printf("%s\n", buf);
+ }
+ free(buf);
+ return 0;
+}
+
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
static void handle_internal_command(int argc, char **argv, char **envp)
@@ -256,6 +334,7 @@ static void handle_internal_command(int argc, char **argv, char **envp)
} commands[] = {
{ "version", cmd_version },
{ "help", cmd_help },
+ { "log", cmd_log },
};
int i;
diff --git a/pager.c b/pager.c
new file mode 100644
index 0000000..1364e15
--- /dev/null
+++ b/pager.c
@@ -0,0 +1,48 @@
+#include "cache.h"
+
+/*
+ * This is split up from the rest of git so that we might do
+ * something different on Windows, for example.
+ */
+
+static void run_pager(void)
+{
+ const char *prog = getenv("PAGER");
+ if (!prog)
+ prog = "less";
+ setenv("LESS", "-S", 0);
+ execlp(prog, prog, NULL);
+}
+
+void setup_pager(void)
+{
+ pid_t pid;
+ int fd[2];
+
+ if (!isatty(1))
+ return;
+ if (pipe(fd) < 0)
+ return;
+ pid = fork();
+ if (pid < 0) {
+ close(fd[0]);
+ close(fd[1]);
+ return;
+ }
+
+ /* return in the child */
+ if (!pid) {
+ dup2(fd[1], 1);
+ close(fd[0]);
+ close(fd[1]);
+ return;
+ }
+
+ /* The original process turns into the PAGER */
+ dup2(fd[0], 0);
+ close(fd[0]);
+ close(fd[1]);
+
+ run_pager();
+ exit(255);
+}
diff --git a/rev-list.c b/rev-list.c
index 67d2a48..8e4d83e 100644
--- a/rev-list.c
+++ b/rev-list.c
@@ -4,15 +4,12 @@
#include "commit.h"
#include "tree.h"
#include "blob.h"
-#include "epoch.h"
#include "diff.h"
+#include "revision.h"
-#define SEEN (1u << 0)
-#define INTERESTING (1u << 1)
-#define COUNTED (1u << 2)
-#define SHOWN (1u << 3)
-#define TREECHANGE (1u << 4)
-#define TMP_MARK (1u << 5) /* for isolated cases; clean after use */
+/* bits #0-4 in revision.h */
+
+#define COUNTED (1u<<5)
static const char rev_list_usage[] =
"git-rev-list [OPTION] <commit-id>... [ -- paths... ]\n"
@@ -25,7 +22,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"
@@ -38,72 +34,18 @@ static const char rev_list_usage[] =
" --bisect"
;
-static int dense = 1;
-static int unpacked = 0;
+struct rev_info revs;
+
static int bisect_list = 0;
-static int tag_objects = 0;
-static int tree_objects = 0;
-static int blob_objects = 0;
-static int edge_hint = 0;
static int verbose_header = 0;
static int abbrev = DEFAULT_ABBREV;
static int show_parents = 0;
static int hdr_termination = 0;
static const char *commit_prefix = "";
-static unsigned long max_age = -1;
-static unsigned long min_age = -1;
-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 int stop_traversal = 0;
-static int topo_order = 0;
-static int lifo = 1;
-static int no_merges = 0;
-static const char **paths = NULL;
-static int remove_empty_trees = 0;
-
-struct name_path {
- struct name_path *up;
- int elem_len;
- const char *elem;
-};
-
-static char *path_name(struct name_path *path, const char *name)
-{
- struct name_path *p;
- char *n, *m;
- int nlen = strlen(name);
- int len = nlen + 1;
-
- for (p = path; p; p = p->up) {
- if (p->elem_len)
- len += p->elem_len + 1;
- }
- n = xmalloc(len);
- m = n + len - (nlen + 1);
- strcpy(m, name);
- for (p = path; p; p = p->up) {
- if (p->elem_len) {
- m -= p->elem_len + 1;
- memcpy(m, p->elem, p->elem_len);
- m[p->elem_len] = '/';
- }
- }
- return n;
-}
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;
@@ -137,86 +79,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 (min_age != -1 && (commit->date > min_age))
- return CONTINUE;
- if (max_age != -1 && (commit->date < max_age)) {
- stop_traversal=1;
- return CONTINUE;
- }
- if (no_merges && (commit->parents && commit->parents->next))
- return CONTINUE;
- if (paths && 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 (max_count != -1 && !max_count--)
- return STOP;
-
- show_commit(commit);
-
- return CONTINUE;
-}
-
-static struct object_list **add_object(struct object *obj,
- struct object_list **p,
- struct name_path *path,
- const char *name)
-{
- struct object_list *entry = xmalloc(sizeof(*entry));
- entry->item = obj;
- entry->next = *p;
- entry->name = path_name(path, name);
- *p = entry;
- return &entry->next;
-}
-
static struct object_list **process_blob(struct blob *blob,
struct object_list **p,
struct name_path *path,
@@ -224,7 +86,7 @@ static struct object_list **process_blob(struct blob *blob,
{
struct object *obj = &blob->object;
- if (!blob_objects)
+ if (!revs.blob_objects)
return p;
if (obj->flags & (UNINTERESTING | SEEN))
return p;
@@ -241,7 +103,7 @@ static struct object_list **process_tree(struct tree *tree,
struct tree_entry_list *entry;
struct name_path me;
- if (!tree_objects)
+ if (!revs.tree_objects)
return p;
if (obj->flags & (UNINTERESTING | SEEN))
return p;
@@ -266,19 +128,16 @@ static struct object_list **process_tree(struct tree *tree,
return p;
}
-static struct object_list *pending_objects = NULL;
-
-static void show_commit_list(struct commit_list *list)
+static void show_commit_list(struct rev_info *revs)
{
+ struct commit *commit;
struct object_list *objects = NULL, **p = &objects, *pending;
- while (list) {
- struct commit *commit = pop_most_recent_commit(&list, SEEN);
+ while ((commit = get_revision(revs)) != NULL) {
p = process_tree(commit->tree, p, NULL, "");
- if (process_commit(commit) == STOP)
- break;
+ show_commit(commit);
}
- for (pending = pending_objects; pending; pending = pending->next) {
+ for (pending = revs->pending_objects; pending; pending = pending->next) {
struct object *obj = pending->item;
const char *name = pending->name;
if (obj->flags & (UNINTERESTING | SEEN))
@@ -314,88 +173,6 @@ static void show_commit_list(struct commit_list *list)
}
}
-static void mark_blob_uninteresting(struct blob *blob)
-{
- if (!blob_objects)
- return;
- if (blob->object.flags & UNINTERESTING)
- return;
- blob->object.flags |= UNINTERESTING;
-}
-
-static void mark_tree_uninteresting(struct tree *tree)
-{
- struct object *obj = &tree->object;
- struct tree_entry_list *entry;
-
- if (!tree_objects)
- return;
- if (obj->flags & UNINTERESTING)
- return;
- obj->flags |= UNINTERESTING;
- if (!has_sha1_file(obj->sha1))
- return;
- if (parse_tree(tree) < 0)
- die("bad tree %s", sha1_to_hex(obj->sha1));
- entry = tree->entries;
- tree->entries = NULL;
- while (entry) {
- struct tree_entry_list *next = entry->next;
- if (entry->directory)
- mark_tree_uninteresting(entry->item.tree);
- else
- mark_blob_uninteresting(entry->item.blob);
- free(entry);
- entry = next;
- }
-}
-
-static void mark_parents_uninteresting(struct commit *commit)
-{
- struct commit_list *parents = commit->parents;
-
- while (parents) {
- struct commit *commit = parents->item;
- commit->object.flags |= UNINTERESTING;
-
- /*
- * Normally we haven't parsed the parent
- * yet, so we won't have a parent of a parent
- * here. However, it may turn out that we've
- * reached this commit some other way (where it
- * wasn't uninteresting), in which case we need
- * to mark its parents recursively too..
- */
- if (commit->parents)
- mark_parents_uninteresting(commit);
-
- /*
- * A missing commit is ok iff its parent is marked
- * uninteresting.
- *
- * We just mark such a thing parsed, so that when
- * it is popped next time around, we won't be trying
- * to parse it and get an error.
- */
- if (!has_sha1_file(commit->object.sha1))
- commit->object.parsed = 1;
- parents = parents->next;
- }
-}
-
-static int everybody_uninteresting(struct commit_list *orig)
-{
- struct commit_list *list = orig;
- while (list) {
- struct commit *commit = list->item;
- list = list->next;
- if (commit->object.flags & UNINTERESTING)
- continue;
- return 0;
- }
- return 1;
-}
-
/*
* This is a truly stupid algorithm, but it's only
* used for bisection, and we just don't care enough.
@@ -413,7 +190,7 @@ static int count_distance(struct commit_list *entry)
if (commit->object.flags & (UNINTERESTING | COUNTED))
break;
- if (!paths || (commit->object.flags & TREECHANGE))
+ if (!revs.paths || (commit->object.flags & TREECHANGE))
nr++;
commit->object.flags |= COUNTED;
p = commit->parents;
@@ -447,7 +224,7 @@ static struct commit_list *find_bisection(struct commit_list *list)
nr = 0;
p = list;
while (p) {
- if (!paths || (p->item->object.flags & TREECHANGE))
+ if (!revs.paths || (p->item->object.flags & TREECHANGE))
nr++;
p = p->next;
}
@@ -457,7 +234,7 @@ static struct commit_list *find_bisection(struct commit_list *list)
for (p = list; p; p = p->next) {
int distance;
- if (paths && !(p->item->object.flags & TREECHANGE))
+ if (revs.paths && !(p->item->object.flags & TREECHANGE))
continue;
distance = count_distance(p);
@@ -483,7 +260,7 @@ static void mark_edge_parents_uninteresting(struct commit *commit)
if (!(parent->object.flags & UNINTERESTING))
continue;
mark_tree_uninteresting(parent->tree);
- if (edge_hint && !(parent->object.flags & SHOWN)) {
+ if (revs.edge_hint && !(parent->object.flags & SHOWN)) {
parent->object.flags |= SHOWN;
printf("-%s\n", sha1_to_hex(parent->object.sha1));
}
@@ -503,356 +280,29 @@ static void mark_edges_uninteresting(struct commit_list *list)
}
}
-#define TREE_SAME 0
-#define TREE_NEW 1
-#define TREE_DIFFERENT 2
-static int tree_difference = TREE_SAME;
-
-static void file_add_remove(struct diff_options *options,
- int addremove, unsigned mode,
- const unsigned char *sha1,
- const char *base, const char *path)
-{
- int diff = TREE_DIFFERENT;
-
- /*
- * Is it an add of a new file? It means that
- * the old tree didn't have it at all, so we
- * will turn "TREE_SAME" -> "TREE_NEW", but
- * leave any "TREE_DIFFERENT" alone (and if
- * it already was "TREE_NEW", we'll keep it
- * "TREE_NEW" of course).
- */
- if (addremove == '+') {
- diff = tree_difference;
- if (diff != TREE_SAME)
- return;
- diff = TREE_NEW;
- }
- tree_difference = diff;
-}
-
-static void file_change(struct diff_options *options,
- unsigned old_mode, unsigned new_mode,
- const unsigned char *old_sha1,
- const unsigned char *new_sha1,
- const char *base, const char *path)
-{
- tree_difference = TREE_DIFFERENT;
-}
-
-static struct diff_options diff_opt = {
- .recursive = 1,
- .add_remove = file_add_remove,
- .change = file_change,
-};
-
-static int compare_tree(struct tree *t1, struct tree *t2)
-{
- if (!t1)
- return TREE_NEW;
- if (!t2)
- return TREE_DIFFERENT;
- tree_difference = TREE_SAME;
- if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "", &diff_opt) < 0)
- return TREE_DIFFERENT;
- return tree_difference;
-}
-
-static int same_tree_as_empty(struct tree *t1)
-{
- int retval;
- void *tree;
- struct tree_desc empty, real;
-
- if (!t1)
- return 0;
-
- tree = read_object_with_reference(t1->object.sha1, "tree", &real.size, NULL);
- if (!tree)
- return 0;
- real.buf = tree;
-
- empty.buf = "";
- empty.size = 0;
-
- tree_difference = 0;
- retval = diff_tree(&empty, &real, "", &diff_opt);
- free(tree);
-
- return retval >= 0 && !tree_difference;
-}
-
-static void try_to_simplify_commit(struct commit *commit)
-{
- struct commit_list **pp, *parent;
-
- if (!commit->tree)
- return;
-
- if (!commit->parents) {
- if (!same_tree_as_empty(commit->tree))
- commit->object.flags |= TREECHANGE;
- return;
- }
-
- pp = &commit->parents;
- while ((parent = *pp) != NULL) {
- struct commit *p = parent->item;
-
- if (p->object.flags & UNINTERESTING) {
- pp = &parent->next;
- continue;
- }
-
- parse_commit(p);
- switch (compare_tree(p->tree, commit->tree)) {
- case TREE_SAME:
- parent->next = NULL;
- commit->parents = parent;
- return;
-
- case TREE_NEW:
- if (remove_empty_trees && same_tree_as_empty(p->tree)) {
- *pp = parent->next;
- continue;
- }
- /* fallthrough */
- case TREE_DIFFERENT:
- pp = &parent->next;
- continue;
- }
- die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1));
- }
- commit->object.flags |= TREECHANGE;
-}
-
-static void add_parents_to_list(struct commit *commit, struct commit_list **list)
-{
- struct commit_list *parent = commit->parents;
-
- /*
- * If the commit is uninteresting, don't try to
- * prune parents - we want the maximal uninteresting
- * set.
- *
- * Normally we haven't parsed the parent
- * yet, so we won't have a parent of a parent
- * here. However, it may turn out that we've
- * reached this commit some other way (where it
- * wasn't uninteresting), in which case we need
- * to mark its parents recursively too..
- */
- if (commit->object.flags & UNINTERESTING) {
- while (parent) {
- struct commit *p = parent->item;
- parent = parent->next;
- parse_commit(p);
- p->object.flags |= UNINTERESTING;
- if (p->parents)
- mark_parents_uninteresting(p);
- if (p->object.flags & SEEN)
- continue;
- p->object.flags |= SEEN;
- insert_by_date(p, list);
- }
- return;
- }
-
- /*
- * Ok, the commit wasn't uninteresting. Try to
- * simplify the commit history and find the parent
- * that has no differences in the path set if one exists.
- */
- if (paths)
- try_to_simplify_commit(commit);
-
- parent = commit->parents;
- while (parent) {
- struct commit *p = parent->item;
-
- parent = parent->next;
-
- parse_commit(p);
- if (p->object.flags & SEEN)
- continue;
- p->object.flags |= SEEN;
- insert_by_date(p, list);
- }
-}
-
-static struct commit_list *limit_list(struct commit_list *list)
-{
- struct commit_list *newlist = NULL;
- struct commit_list **p = &newlist;
- while (list) {
- struct commit_list *entry = list;
- struct commit *commit = list->item;
- struct object *obj = &commit->object;
-
- list = list->next;
- free(entry);
-
- if (max_age != -1 && (commit->date < max_age))
- obj->flags |= UNINTERESTING;
- if (unpacked && has_sha1_pack(obj->sha1))
- obj->flags |= UNINTERESTING;
- add_parents_to_list(commit, &list);
- if (obj->flags & UNINTERESTING) {
- mark_parents_uninteresting(commit);
- if (everybody_uninteresting(list))
- break;
- continue;
- }
- if (min_age != -1 && (commit->date > min_age))
- continue;
- p = &commit_list_insert(commit, p)->next;
- }
- if (tree_objects)
- mark_edges_uninteresting(newlist);
- if (bisect_list)
- newlist = find_bisection(newlist);
- return newlist;
-}
-
-static void add_pending_object(struct object *obj, const char *name)
-{
- add_object(obj, &pending_objects, NULL, name);
-}
-
-static struct commit *get_commit_reference(const char *name, const unsigned char *sha1, unsigned int flags)
-{
- struct object *object;
-
- object = parse_object(sha1);
- if (!object)
- die("bad object %s", name);
-
- /*
- * Tag object? Look what it points to..
- */
- while (object->type == tag_type) {
- struct tag *tag = (struct tag *) object;
- object->flags |= flags;
- if (tag_objects && !(object->flags & UNINTERESTING))
- add_pending_object(object, tag->tag);
- object = parse_object(tag->tagged->sha1);
- if (!object)
- die("bad object %s", sha1_to_hex(tag->tagged->sha1));
- }
-
- /*
- * Commit object? Just return it, we'll do all the complex
- * reachability crud.
- */
- if (object->type == commit_type) {
- struct commit *commit = (struct commit *)object;
- object->flags |= flags;
- if (parse_commit(commit) < 0)
- die("unable to parse commit %s", name);
- if (flags & UNINTERESTING)
- mark_parents_uninteresting(commit);
- return commit;
- }
-
- /*
- * Tree object? Either mark it uniniteresting, or add it
- * to the list of objects to look at later..
- */
- if (object->type == tree_type) {
- struct tree *tree = (struct tree *)object;
- if (!tree_objects)
- return NULL;
- if (flags & UNINTERESTING) {
- mark_tree_uninteresting(tree);
- return NULL;
- }
- add_pending_object(object, "");
- return NULL;
- }
-
- /*
- * Blob object? You know the drill by now..
- */
- if (object->type == blob_type) {
- struct blob *blob = (struct blob *)object;
- if (!blob_objects)
- return NULL;
- if (flags & UNINTERESTING) {
- mark_blob_uninteresting(blob);
- return NULL;
- }
- add_pending_object(object, "");
- return NULL;
- }
- die("%s is unknown object", name);
-}
-
-static void handle_one_commit(struct commit *com, struct commit_list **lst)
-{
- if (!com || com->object.flags & SEEN)
- return;
- com->object.flags |= SEEN;
- commit_list_insert(com, lst);
-}
-
-/* for_each_ref() callback does not allow user data -- Yuck. */
-static struct commit_list **global_lst;
-
-static int include_one_commit(const char *path, const unsigned char *sha1)
-{
- struct commit *com = get_commit_reference(path, sha1, 0);
- handle_one_commit(com, global_lst);
- return 0;
-}
-
-static void handle_all(struct commit_list **lst)
-{
- global_lst = lst;
- for_each_ref(include_one_commit);
- global_lst = NULL;
-}
-
int main(int argc, const char **argv)
{
- const char *prefix = setup_git_directory();
- struct commit_list *list = NULL;
- int i, limited = 0;
+ struct commit_list *list;
+ int i;
+
+ argc = setup_revisions(argc, argv, &revs, NULL);
for (i = 1 ; i < argc; i++) {
- int flags;
const char *arg = argv[i];
- char *dotdot;
- struct commit *commit;
- unsigned char sha1[20];
/* accept -<digit>, like traditilnal "head" */
if ((*arg == '-') && isdigit(arg[1])) {
- max_count = atoi(arg + 1);
+ revs.max_count = atoi(arg + 1);
continue;
}
if (!strcmp(arg, "-n")) {
if (++i >= argc)
die("-n requires an argument");
- max_count = atoi(argv[i]);
+ revs.max_count = atoi(argv[i]);
continue;
}
if (!strncmp(arg,"-n",2)) {
- max_count = atoi(arg + 2);
- continue;
- }
- if (!strncmp(arg, "--max-count=", 12)) {
- max_count = atoi(arg + 12);
- continue;
- }
- if (!strncmp(arg, "--max-age=", 10)) {
- max_age = atoi(arg + 10);
- limited = 1;
- continue;
- }
- if (!strncmp(arg, "--min-age=", 10)) {
- min_age = atoi(arg + 10);
- limited = 1;
+ revs.max_count = atoi(arg + 2);
continue;
}
if (!strcmp(arg, "--header")) {
@@ -881,10 +331,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;
@@ -893,140 +339,27 @@ int main(int argc, const char **argv)
bisect_list = 1;
continue;
}
- if (!strcmp(arg, "--all")) {
- handle_all(&list);
- continue;
- }
- if (!strcmp(arg, "--objects")) {
- tag_objects = 1;
- tree_objects = 1;
- blob_objects = 1;
- continue;
- }
- if (!strcmp(arg, "--objects-edge")) {
- tag_objects = 1;
- tree_objects = 1;
- blob_objects = 1;
- edge_hint = 1;
- continue;
- }
- if (!strcmp(arg, "--unpacked")) {
- unpacked = 1;
- limited = 1;
- continue;
- }
- if (!strcmp(arg, "--merge-order")) {
- merge_order = 1;
- continue;
- }
- if (!strcmp(arg, "--show-breaks")) {
- show_breaks = 1;
- continue;
- }
- if (!strcmp(arg, "--topo-order")) {
- topo_order = 1;
- lifo = 1;
- limited = 1;
- continue;
- }
- if (!strcmp(arg, "--date-order")) {
- topo_order = 1;
- lifo = 0;
- limited = 1;
- continue;
- }
- if (!strcmp(arg, "--dense")) {
- dense = 1;
- continue;
- }
- if (!strcmp(arg, "--sparse")) {
- dense = 0;
- continue;
- }
- if (!strcmp(arg, "--remove-empty")) {
- remove_empty_trees = 1;
- continue;
- }
- if (!strcmp(arg, "--")) {
- i++;
- break;
- }
-
- if (show_breaks && !merge_order)
- usage(rev_list_usage);
+ usage(rev_list_usage);
- flags = 0;
- dotdot = strstr(arg, "..");
- if (dotdot) {
- unsigned char from_sha1[20];
- char *next = dotdot + 2;
- *dotdot = 0;
- if (!*next)
- next = "HEAD";
- if (!get_sha1(arg, from_sha1) && !get_sha1(next, sha1)) {
- struct commit *exclude;
- struct commit *include;
-
- exclude = get_commit_reference(arg, from_sha1, UNINTERESTING);
- include = get_commit_reference(next, sha1, 0);
- if (!exclude || !include)
- die("Invalid revision range %s..%s", arg, next);
- limited = 1;
- handle_one_commit(exclude, &list);
- handle_one_commit(include, &list);
- continue;
- }
- *dotdot = '.';
- }
- if (*arg == '^') {
- flags = UNINTERESTING;
- arg++;
- limited = 1;
- }
- if (get_sha1(arg, sha1) < 0) {
- struct stat st;
- if (lstat(arg, &st) < 0)
- die("'%s': %s", arg, strerror(errno));
- break;
- }
- commit = get_commit_reference(arg, sha1, flags);
- handle_one_commit(commit, &list);
}
+ list = revs.commits;
+
if (!list &&
- (!(tag_objects||tree_objects||blob_objects) && !pending_objects))
+ (!(revs.tag_objects||revs.tree_objects||revs.blob_objects) && !revs.pending_objects))
usage(rev_list_usage);
- paths = get_pathspec(prefix, argv + i);
- if (paths) {
- limited = 1;
- diff_tree_setup_paths(paths);
- }
+ prepare_revision_walk(&revs);
+ if (revs.tree_objects)
+ mark_edges_uninteresting(revs.commits);
+
+ if (bisect_list)
+ revs.commits = find_bisection(revs.commits);
save_commit_buffer = verbose_header;
track_object_refs = 0;
- if (!merge_order) {
- sort_by_date(&list);
- if (list && !limited && max_count == 1 &&
- !tag_objects && !tree_objects && !blob_objects) {
- show_commit(list->item);
- return 0;
- }
- if (limited)
- list = limit_list(list);
- if (topo_order)
- sort_in_topological_order(&list, lifo);
- show_commit_list(list);
- } 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 610eacb..f90e999 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
new file mode 100644
index 0000000..a3df810
--- /dev/null
+++ b/revision.c
@@ -0,0 +1,722 @@
+#include "cache.h"
+#include "tag.h"
+#include "blob.h"
+#include "tree.h"
+#include "commit.h"
+#include "diff.h"
+#include "refs.h"
+#include "revision.h"
+
+static char *path_name(struct name_path *path, const char *name)
+{
+ struct name_path *p;
+ char *n, *m;
+ int nlen = strlen(name);
+ int len = nlen + 1;
+
+ for (p = path; p; p = p->up) {
+ if (p->elem_len)
+ len += p->elem_len + 1;
+ }
+ n = xmalloc(len);
+ m = n + len - (nlen + 1);
+ strcpy(m, name);
+ for (p = path; p; p = p->up) {
+ if (p->elem_len) {
+ m -= p->elem_len + 1;
+ memcpy(m, p->elem, p->elem_len);
+ m[p->elem_len] = '/';
+ }
+ }
+ return n;
+}
+
+struct object_list **add_object(struct object *obj,
+ struct object_list **p,
+ struct name_path *path,
+ const char *name)
+{
+ struct object_list *entry = xmalloc(sizeof(*entry));
+ entry->item = obj;
+ entry->next = *p;
+ entry->name = path_name(path, name);
+ *p = entry;
+ return &entry->next;
+}
+
+static void mark_blob_uninteresting(struct blob *blob)
+{
+ if (blob->object.flags & UNINTERESTING)
+ return;
+ blob->object.flags |= UNINTERESTING;
+}
+
+void mark_tree_uninteresting(struct tree *tree)
+{
+ struct object *obj = &tree->object;
+ struct tree_entry_list *entry;
+
+ if (obj->flags & UNINTERESTING)
+ return;
+ obj->flags |= UNINTERESTING;
+ if (!has_sha1_file(obj->sha1))
+ return;
+ if (parse_tree(tree) < 0)
+ die("bad tree %s", sha1_to_hex(obj->sha1));
+ entry = tree->entries;
+ tree->entries = NULL;
+ while (entry) {
+ struct tree_entry_list *next = entry->next;
+ if (entry->directory)
+ mark_tree_uninteresting(entry->item.tree);
+ else
+ mark_blob_uninteresting(entry->item.blob);
+ free(entry);
+ entry = next;
+ }
+}
+
+void mark_parents_uninteresting(struct commit *commit)
+{
+ struct commit_list *parents = commit->parents;
+
+ while (parents) {
+ struct commit *commit = parents->item;
+ commit->object.flags |= UNINTERESTING;
+
+ /*
+ * Normally we haven't parsed the parent
+ * yet, so we won't have a parent of a parent
+ * here. However, it may turn out that we've
+ * reached this commit some other way (where it
+ * wasn't uninteresting), in which case we need
+ * to mark its parents recursively too..
+ */
+ if (commit->parents)
+ mark_parents_uninteresting(commit);
+
+ /*
+ * A missing commit is ok iff its parent is marked
+ * uninteresting.
+ *
+ * We just mark such a thing parsed, so that when
+ * it is popped next time around, we won't be trying
+ * to parse it and get an error.
+ */
+ if (!has_sha1_file(commit->object.sha1))
+ commit->object.parsed = 1;
+ parents = parents->next;
+ }
+}
+
+static void add_pending_object(struct rev_info *revs, struct object *obj, const char *name)
+{
+ add_object(obj, &revs->pending_objects, NULL, name);
+}
+
+static struct commit *get_commit_reference(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags)
+{
+ struct object *object;
+
+ object = parse_object(sha1);
+ if (!object)
+ die("bad object %s", name);
+
+ /*
+ * Tag object? Look what it points to..
+ */
+ while (object->type == tag_type) {
+ struct tag *tag = (struct tag *) object;
+ object->flags |= flags;
+ if (revs->tag_objects && !(object->flags & UNINTERESTING))
+ add_pending_object(revs, object, tag->tag);
+ object = parse_object(tag->tagged->sha1);
+ if (!object)
+ die("bad object %s", sha1_to_hex(tag->tagged->sha1));
+ }
+
+ /*
+ * Commit object? Just return it, we'll do all the complex
+ * reachability crud.
+ */
+ if (object->type == commit_type) {
+ struct commit *commit = (struct commit *)object;
+ object->flags |= flags;
+ if (parse_commit(commit) < 0)
+ die("unable to parse commit %s", name);
+ if (flags & UNINTERESTING) {
+ mark_parents_uninteresting(commit);
+ revs->limited = 1;
+ }
+ return commit;
+ }
+
+ /*
+ * Tree object? Either mark it uniniteresting, or add it
+ * to the list of objects to look at later..
+ */
+ if (object->type == tree_type) {
+ struct tree *tree = (struct tree *)object;
+ if (!revs->tree_objects)
+ return NULL;
+ if (flags & UNINTERESTING) {
+ mark_tree_uninteresting(tree);
+ return NULL;
+ }
+ add_pending_object(revs, object, "");
+ return NULL;
+ }
+
+ /*
+ * Blob object? You know the drill by now..
+ */
+ if (object->type == blob_type) {
+ struct blob *blob = (struct blob *)object;
+ if (!revs->blob_objects)
+ return NULL;
+ if (flags & UNINTERESTING) {
+ mark_blob_uninteresting(blob);
+ return NULL;
+ }
+ add_pending_object(revs, object, "");
+ return NULL;
+ }
+ die("%s is unknown object", name);
+}
+
+static int everybody_uninteresting(struct commit_list *orig)
+{
+ struct commit_list *list = orig;
+ while (list) {
+ struct commit *commit = list->item;
+ list = list->next;
+ if (commit->object.flags & UNINTERESTING)
+ continue;
+ return 0;
+ }
+ return 1;
+}
+
+#define TREE_SAME 0
+#define TREE_NEW 1
+#define TREE_DIFFERENT 2
+static int tree_difference = TREE_SAME;
+
+static void file_add_remove(struct diff_options *options,
+ int addremove, unsigned mode,
+ const unsigned char *sha1,
+ const char *base, const char *path)
+{
+ int diff = TREE_DIFFERENT;
+
+ /*
+ * Is it an add of a new file? It means that
+ * the old tree didn't have it at all, so we
+ * will turn "TREE_SAME" -> "TREE_NEW", but
+ * leave any "TREE_DIFFERENT" alone (and if
+ * it already was "TREE_NEW", we'll keep it
+ * "TREE_NEW" of course).
+ */
+ if (addremove == '+') {
+ diff = tree_difference;
+ if (diff != TREE_SAME)
+ return;
+ diff = TREE_NEW;
+ }
+ tree_difference = diff;
+}
+
+static void file_change(struct diff_options *options,
+ unsigned old_mode, unsigned new_mode,
+ const unsigned char *old_sha1,
+ const unsigned char *new_sha1,
+ const char *base, const char *path)
+{
+ tree_difference = TREE_DIFFERENT;
+}
+
+static struct diff_options diff_opt = {
+ .recursive = 1,
+ .add_remove = file_add_remove,
+ .change = file_change,
+};
+
+static int compare_tree(struct tree *t1, struct tree *t2)
+{
+ if (!t1)
+ return TREE_NEW;
+ if (!t2)
+ return TREE_DIFFERENT;
+ tree_difference = TREE_SAME;
+ if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "", &diff_opt) < 0)
+ return TREE_DIFFERENT;
+ return tree_difference;
+}
+
+static int same_tree_as_empty(struct tree *t1)
+{
+ int retval;
+ void *tree;
+ struct tree_desc empty, real;
+
+ if (!t1)
+ return 0;
+
+ tree = read_object_with_reference(t1->object.sha1, "tree", &real.size, NULL);
+ if (!tree)
+ return 0;
+ real.buf = tree;
+
+ empty.buf = "";
+ empty.size = 0;
+
+ tree_difference = 0;
+ retval = diff_tree(&empty, &real, "", &diff_opt);
+ free(tree);
+
+ return retval >= 0 && !tree_difference;
+}
+
+static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
+{
+ struct commit_list **pp, *parent;
+
+ if (!commit->tree)
+ return;
+
+ if (!commit->parents) {
+ if (!same_tree_as_empty(commit->tree))
+ commit->object.flags |= TREECHANGE;
+ return;
+ }
+
+ pp = &commit->parents;
+ while ((parent = *pp) != NULL) {
+ struct commit *p = parent->item;
+
+ if (p->object.flags & UNINTERESTING) {
+ pp = &parent->next;
+ continue;
+ }
+
+ parse_commit(p);
+ switch (compare_tree(p->tree, commit->tree)) {
+ case TREE_SAME:
+ parent->next = NULL;
+ commit->parents = parent;
+ return;
+
+ case TREE_NEW:
+ if (revs->remove_empty_trees && same_tree_as_empty(p->tree)) {
+ *pp = parent->next;
+ continue;
+ }
+ /* fallthrough */
+ case TREE_DIFFERENT:
+ pp = &parent->next;
+ continue;
+ }
+ die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1));
+ }
+ commit->object.flags |= TREECHANGE;
+}
+
+static void add_parents_to_list(struct rev_info *revs, struct commit *commit, struct commit_list **list)
+{
+ struct commit_list *parent = commit->parents;
+
+ /*
+ * If the commit is uninteresting, don't try to
+ * prune parents - we want the maximal uninteresting
+ * set.
+ *
+ * Normally we haven't parsed the parent
+ * yet, so we won't have a parent of a parent
+ * here. However, it may turn out that we've
+ * reached this commit some other way (where it
+ * wasn't uninteresting), in which case we need
+ * to mark its parents recursively too..
+ */
+ if (commit->object.flags & UNINTERESTING) {
+ while (parent) {
+ struct commit *p = parent->item;
+ parent = parent->next;
+ parse_commit(p);
+ p->object.flags |= UNINTERESTING;
+ if (p->parents)
+ mark_parents_uninteresting(p);
+ if (p->object.flags & SEEN)
+ continue;
+ p->object.flags |= SEEN;
+ insert_by_date(p, list);
+ }
+ return;
+ }
+
+ /*
+ * Ok, the commit wasn't uninteresting. Try to
+ * simplify the commit history and find the parent
+ * that has no differences in the path set if one exists.
+ */
+ if (revs->paths)
+ try_to_simplify_commit(revs, commit);
+
+ parent = commit->parents;
+ while (parent) {
+ struct commit *p = parent->item;
+
+ parent = parent->next;
+
+ parse_commit(p);
+ if (p->object.flags & SEEN)
+ continue;
+ p->object.flags |= SEEN;
+ insert_by_date(p, list);
+ }
+}
+
+static void limit_list(struct rev_info *revs)
+{
+ struct commit_list *list = revs->commits;
+ 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;
+ struct object *obj = &commit->object;
+
+ list = list->next;
+ free(entry);
+
+ if (revs->max_age != -1 && (commit->date < revs->max_age))
+ obj->flags |= UNINTERESTING;
+ if (revs->unpacked && has_sha1_pack(obj->sha1))
+ obj->flags |= UNINTERESTING;
+ add_parents_to_list(revs, commit, &list);
+ if (obj->flags & UNINTERESTING) {
+ mark_parents_uninteresting(commit);
+ if (everybody_uninteresting(list))
+ break;
+ continue;
+ }
+ if (revs->min_age != -1 && (commit->date > revs->min_age))
+ continue;
+ p = &commit_list_insert(commit, p)->next;
+ }
+ revs->commits = newlist;
+}
+
+static void add_one_commit(struct commit *commit, struct rev_info *revs)
+{
+ if (!commit || (commit->object.flags & SEEN))
+ return;
+ commit->object.flags |= SEEN;
+ commit_list_insert(commit, &revs->commits);
+}
+
+static int all_flags;
+static struct rev_info *all_revs;
+
+static int handle_one_ref(const char *path, const unsigned char *sha1)
+{
+ struct commit *commit = get_commit_reference(all_revs, path, sha1, all_flags);
+ add_one_commit(commit, all_revs);
+ return 0;
+}
+
+static void handle_all(struct rev_info *revs, unsigned flags)
+{
+ all_revs = revs;
+ all_flags = flags;
+ for_each_ref(handle_one_ref);
+}
+
+/*
+ * Parse revision information, filling in the "rev_info" structure,
+ * and removing the used arguments from the argument list.
+ *
+ * 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;
+ int left = 1;
+
+ memset(revs, 0, sizeof(*revs));
+ revs->lifo = 1;
+ revs->dense = 1;
+ revs->prefix = setup_git_directory();
+ revs->max_age = -1;
+ revs->min_age = -1;
+ revs->max_count = -1;
+
+ /* First, search for "--" */
+ seen_dashdash = 0;
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+ if (strcmp(arg, "--"))
+ continue;
+ argv[i] = NULL;
+ argc = i;
+ revs->paths = get_pathspec(revs->prefix, argv + i + 1);
+ seen_dashdash = 1;
+ break;
+ }
+
+ flags = 0;
+ for (i = 1; i < argc; i++) {
+ struct commit *commit;
+ const char *arg = argv[i];
+ unsigned char sha1[20];
+ char *dotdot;
+ int local_flags;
+
+ if (*arg == '-') {
+ if (!strncmp(arg, "--max-count=", 12)) {
+ revs->max_count = atoi(arg + 12);
+ continue;
+ }
+ /* accept -<digit>, like traditilnal "head" */
+ if ((*arg == '-') && isdigit(arg[1])) {
+ revs->max_count = atoi(arg + 1);
+ continue;
+ }
+ if (!strcmp(arg, "-n")) {
+ if (argc <= i + 1)
+ die("-n requires an argument");
+ revs->max_count = atoi(argv[++i]);
+ continue;
+ }
+ if (!strncmp(arg,"-n",2)) {
+ revs->max_count = atoi(arg + 2);
+ continue;
+ }
+ if (!strncmp(arg, "--max-age=", 10)) {
+ revs->max_age = atoi(arg + 10);
+ revs->limited = 1;
+ continue;
+ }
+ if (!strncmp(arg, "--min-age=", 10)) {
+ revs->min_age = atoi(arg + 10);
+ revs->limited = 1;
+ continue;
+ }
+ if (!strncmp(arg, "--since=", 8)) {
+ revs->max_age = approxidate(arg + 8);
+ revs->limited = 1;
+ continue;
+ }
+ if (!strncmp(arg, "--after=", 8)) {
+ revs->max_age = approxidate(arg + 8);
+ revs->limited = 1;
+ continue;
+ }
+ if (!strncmp(arg, "--before=", 9)) {
+ revs->min_age = approxidate(arg + 9);
+ revs->limited = 1;
+ continue;
+ }
+ if (!strncmp(arg, "--until=", 8)) {
+ revs->min_age = approxidate(arg + 8);
+ revs->limited = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--all")) {
+ handle_all(revs, flags);
+ continue;
+ }
+ if (!strcmp(arg, "--not")) {
+ flags ^= UNINTERESTING;
+ continue;
+ }
+ if (!strcmp(arg, "--default")) {
+ if (++i >= argc)
+ die("bad --default argument");
+ def = argv[i];
+ continue;
+ }
+ if (!strcmp(arg, "--topo-order")) {
+ revs->topo_order = 1;
+ revs->limited = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--date-order")) {
+ revs->lifo = 0;
+ revs->topo_order = 1;
+ revs->limited = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--dense")) {
+ revs->dense = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--sparse")) {
+ revs->dense = 0;
+ continue;
+ }
+ if (!strcmp(arg, "--remove-empty")) {
+ 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;
+ revs->blob_objects = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--objects-edge")) {
+ revs->tag_objects = 1;
+ revs->tree_objects = 1;
+ revs->blob_objects = 1;
+ revs->edge_hint = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--unpacked")) {
+ revs->unpacked = 1;
+ revs->limited = 1;
+ continue;
+ }
+ *unrecognized++ = arg;
+ left++;
+ continue;
+ }
+ dotdot = strstr(arg, "..");
+ if (dotdot) {
+ unsigned char from_sha1[20];
+ char *next = dotdot + 2;
+ *dotdot = 0;
+ if (!*next)
+ next = "HEAD";
+ if (!get_sha1(arg, from_sha1) && !get_sha1(next, sha1)) {
+ struct commit *exclude;
+ struct commit *include;
+
+ exclude = get_commit_reference(revs, arg, from_sha1, flags ^ UNINTERESTING);
+ include = get_commit_reference(revs, next, sha1, flags);
+ if (!exclude || !include)
+ die("Invalid revision range %s..%s", arg, next);
+ add_one_commit(exclude, revs);
+ add_one_commit(include, revs);
+ continue;
+ }
+ *dotdot = '.';
+ }
+ local_flags = 0;
+ if (*arg == '^') {
+ local_flags = UNINTERESTING;
+ arg++;
+ }
+ if (get_sha1(arg, sha1) < 0) {
+ struct stat st;
+ int j;
+
+ if (seen_dashdash || local_flags)
+ die("bad revision '%s'", arg);
+
+ /* If we didn't have a "--", all filenames must exist */
+ for (j = i; j < argc; j++) {
+ if (lstat(argv[j], &st) < 0)
+ die("'%s': %s", arg, strerror(errno));
+ }
+ revs->paths = get_pathspec(revs->prefix, argv + i);
+ break;
+ }
+ commit = get_commit_reference(revs, arg, sha1, flags ^ local_flags);
+ add_one_commit(commit, revs);
+ }
+ if (def && !revs->commits) {
+ unsigned char sha1[20];
+ struct commit *commit;
+ if (get_sha1(def, sha1) < 0)
+ die("bad default revision '%s'", def);
+ commit = get_commit_reference(revs, def, sha1, 0);
+ add_one_commit(commit, revs);
+ }
+ if (revs->paths)
+ revs->limited = 1;
+ return left;
+}
+
+void prepare_revision_walk(struct rev_info *revs)
+{
+ sort_by_date(&revs->commits);
+ if (revs->limited)
+ limit_list(revs);
+ if (revs->topo_order)
+ 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)
+{
+ struct commit_list *list = revs->commits;
+ struct commit *commit;
+
+ if (!list)
+ return NULL;
+
+ /* 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
new file mode 100644
index 0000000..31e8f61
--- /dev/null
+++ b/revision.h
@@ -0,0 +1,57 @@
+#ifndef REVISION_H
+#define REVISION_H
+
+#define SEEN (1u<<0)
+#define UNINTERESTING (1u<<1)
+#define TREECHANGE (1u<<2)
+#define SHOWN (1u<<3)
+#define TMP_MARK (1u<<4) /* for isolated cases; clean after use */
+
+struct rev_info {
+ /* Starting list */
+ struct commit_list *commits;
+ struct object_list *pending_objects;
+
+ /* Basic information */
+ const char *prefix;
+ const char **paths;
+
+ /* Traversal flags */
+ unsigned int dense:1,
+ no_merges:1,
+ remove_empty_trees:1,
+ lifo:1,
+ topo_order:1,
+ tag_objects:1,
+ tree_objects:1,
+ blob_objects:1,
+ edge_hint:1,
+ limited:1,
+ unpacked:1;
+
+ /* special limits */
+ int max_count;
+ unsigned long max_age;
+ unsigned long min_age;
+};
+
+/* revision.c */
+extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def);
+extern void prepare_revision_walk(struct rev_info *revs);
+extern struct commit *get_revision(struct rev_info *revs);
+
+extern void mark_parents_uninteresting(struct commit *commit);
+extern void mark_tree_uninteresting(struct tree *tree);
+
+struct name_path {
+ struct name_path *up;
+ int elem_len;
+ const char *elem;
+};
+
+extern struct object_list **add_object(struct object *obj,
+ struct object_list **p,
+ struct name_path *path,
+ const char *name);
+
+#endif
diff --git a/t/t6001-rev-list-merge-order.sh b/t/t6001-rev-list-merge-order.sh
deleted file mode 100755
index 7724e8a..0000000
--- 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