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
This commit is contained in:
Junio C Hamano 2006-03-04 13:21:17 -08:00
commit 21dbe12c76
14 changed files with 957 additions and 1889 deletions

View File

@ -16,7 +16,7 @@ SYNOPSIS
[ \--no-merges ] [ \--no-merges ]
[ \--remove-empty ] [ \--remove-empty ]
[ \--all ] [ \--all ]
[ [ \--merge-order [ \--show-breaks ] ] | [ \--topo-order ] ] [ \--topo-order ]
[ \--parents ] [ \--parents ]
[ [\--objects | \--objects-edge] [ \--unpacked ] ] [ [\--objects | \--objects-edge] [ \--unpacked ] ]
[ \--pretty | \--header ] [ \--pretty | \--header ]
@ -102,57 +102,10 @@ OPTIONS
topological order (i.e. descendant commits are shown topological order (i.e. descendant commits are shown
before their parents). 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 Author
------ ------
Written by Linus Torvalds <torvalds@osdl.org> Written by Linus Torvalds <torvalds@osdl.org>
Original *--merge-order* logic by Jon Seymour <jon.seymour@gmail.com>
Documentation Documentation
-------------- --------------
Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.

View File

@ -40,9 +40,7 @@ Issues of note:
If you don't have openssl, you can use one of the SHA1 libraries 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 that come with git (git includes the one from Mozilla, and has
its own PowerPC-optimized one too - see the Makefile), and you its own PowerPC and ARM optimized ones too - see the Makefile).
can avoid the bignum support by excising git-rev-list support
for "--merge-order" (by hand).
- "libcurl" and "curl" executable. git-http-fetch and - "libcurl" and "curl" executable. git-http-fetch and
git-fetch use them. If you do not use http git-fetch use them. If you do not use http

View File

@ -6,8 +6,8 @@ all:
# on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default # on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default
# choice) has very fast version optimized for i586. # choice) has very fast version optimized for i586.
# #
# Define NO_OPENSSL environment variable if you do not have OpenSSL. You will # Define NO_OPENSSL environment variable if you do not have OpenSSL.
# miss out git-rev-list --merge-order. This also implies MOZILLA_SHA1. # This also implies MOZILLA_SHA1.
# #
# Define NO_CURL if you do not have curl installed. git-http-pull and # 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:// # git-http-push are not built, and you cannot use http:// and https://
@ -191,8 +191,8 @@ LIB_FILE=libgit.a
LIB_H = \ LIB_H = \
blob.h cache.h commit.h count-delta.h csum-file.h delta.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 run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h
DIFF_OBJS = \ DIFF_OBJS = \
diff.o diffcore-break.o diffcore-order.o diffcore-pathspec.o \ 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 \ quote.o read-cache.o refs.o run-command.o \
server-info.o setup.o sha1_file.o sha1_name.o strbuf.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 \ 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) $(DIFF_OBJS)
LIBS = $(LIB_FILE) LIBS = $(LIB_FILE)
@ -329,7 +329,6 @@ ifndef NO_CURL
endif endif
ifndef NO_OPENSSL ifndef NO_OPENSSL
LIB_OBJS += epoch.o
OPENSSL_LIBSSL = -lssl OPENSSL_LIBSSL = -lssl
ifdef OPENSSLDIR ifdef OPENSSLDIR
# Again this may be problematic -- gcc does not always want -R. # 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) git$X: git.c $(LIB_FILE)
$(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \ $(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 $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
rm -f $@ rm -f $@

View File

@ -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_unpack_pack(int fd[2], const char *me, int quiet);
extern int receive_keep_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 */ #endif /* CACHE_H */

639
epoch.c
View File

@ -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;
}

21
epoch.h
View File

@ -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 */

View File

@ -928,7 +928,7 @@ sub find_parents {
# now walk up to the mergepoint collecting what patches we have # now walk up to the mergepoint collecting what patches we have
my $branchtip = git_rev_parse($ps->{branch}); 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 my %have; # collected merges this branch has
foreach my $merge (@{$ps->{merges}}) { foreach my $merge (@{$ps->{merges}}) {
$have{$merge} = 1; $have{$merge} = 1;
@ -951,7 +951,7 @@ sub find_parents {
# see what the remote branch has - these are the merges we # see what the remote branch has - these are the merges we
# will want to have in a consecutive series from the mergebase # will want to have in a consecutive series from the mergebase
my $otherbranchtip = git_rev_parse($branch); 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; my @need;
foreach my $needps (@needraw) { # get the psets foreach my $needps (@needraw) { # get the psets
$needps = commitid2pset($needps); $needps = commitid2pset($needps);

79
git.c
View File

@ -12,6 +12,10 @@
#include "git-compat-util.h" #include "git-compat-util.h"
#include "exec_cmd.h" #include "exec_cmd.h"
#include "cache.h"
#include "commit.h"
#include "revision.h"
#ifndef PATH_MAX #ifndef PATH_MAX
# define PATH_MAX 4096 # define PATH_MAX 4096
#endif #endif
@ -245,6 +249,80 @@ static int cmd_help(int argc, char **argv, char **envp)
return 0; 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])) #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
static void handle_internal_command(int argc, char **argv, char **envp) 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[] = { } commands[] = {
{ "version", cmd_version }, { "version", cmd_version },
{ "help", cmd_help }, { "help", cmd_help },
{ "log", cmd_log },
}; };
int i; int i;

48
pager.c Normal file
View File

@ -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);
}

View File

@ -4,15 +4,12 @@
#include "commit.h" #include "commit.h"
#include "tree.h" #include "tree.h"
#include "blob.h" #include "blob.h"
#include "epoch.h"
#include "diff.h" #include "diff.h"
#include "revision.h"
#define SEEN (1u << 0) /* bits #0-4 in revision.h */
#define INTERESTING (1u << 1)
#define COUNTED (1u << 2) #define COUNTED (1u<<5)
#define SHOWN (1u << 3)
#define TREECHANGE (1u << 4)
#define TMP_MARK (1u << 5) /* for isolated cases; clean after use */
static const char rev_list_usage[] = static const char rev_list_usage[] =
"git-rev-list [OPTION] <commit-id>... [ -- paths... ]\n" "git-rev-list [OPTION] <commit-id>... [ -- paths... ]\n"
@ -25,7 +22,6 @@ static const char rev_list_usage[] =
" --remove-empty\n" " --remove-empty\n"
" --all\n" " --all\n"
" ordering output:\n" " ordering output:\n"
" --merge-order [ --show-breaks ]\n"
" --topo-order\n" " --topo-order\n"
" --date-order\n" " --date-order\n"
" formatting output:\n" " formatting output:\n"
@ -38,72 +34,18 @@ static const char rev_list_usage[] =
" --bisect" " --bisect"
; ;
static int dense = 1; struct rev_info revs;
static int unpacked = 0;
static int bisect_list = 0; 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 verbose_header = 0;
static int abbrev = DEFAULT_ABBREV; static int abbrev = DEFAULT_ABBREV;
static int show_parents = 0; static int show_parents = 0;
static int hdr_termination = 0; static int hdr_termination = 0;
static const char *commit_prefix = ""; 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 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) 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)); printf("%s%s", commit_prefix, sha1_to_hex(commit->object.sha1));
if (show_parents) { if (show_parents) {
struct commit_list *parents = commit->parents; struct commit_list *parents = commit->parents;
@ -137,86 +79,6 @@ static void show_commit(struct commit *commit)
fflush(stdout); 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, static struct object_list **process_blob(struct blob *blob,
struct object_list **p, struct object_list **p,
struct name_path *path, struct name_path *path,
@ -224,7 +86,7 @@ static struct object_list **process_blob(struct blob *blob,
{ {
struct object *obj = &blob->object; struct object *obj = &blob->object;
if (!blob_objects) if (!revs.blob_objects)
return p; return p;
if (obj->flags & (UNINTERESTING | SEEN)) if (obj->flags & (UNINTERESTING | SEEN))
return p; return p;
@ -241,7 +103,7 @@ static struct object_list **process_tree(struct tree *tree,
struct tree_entry_list *entry; struct tree_entry_list *entry;
struct name_path me; struct name_path me;
if (!tree_objects) if (!revs.tree_objects)
return p; return p;
if (obj->flags & (UNINTERESTING | SEEN)) if (obj->flags & (UNINTERESTING | SEEN))
return p; return p;
@ -266,19 +128,16 @@ static struct object_list **process_tree(struct tree *tree,
return p; return p;
} }
static struct object_list *pending_objects = NULL; static void show_commit_list(struct rev_info *revs)
static void show_commit_list(struct commit_list *list)
{ {
struct commit *commit;
struct object_list *objects = NULL, **p = &objects, *pending; 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, ""); p = process_tree(commit->tree, p, NULL, "");
if (process_commit(commit) == STOP) show_commit(commit);
break;
} }
for (pending = pending_objects; pending; pending = pending->next) { for (pending = revs->pending_objects; pending; pending = pending->next) {
struct object *obj = pending->item; struct object *obj = pending->item;
const char *name = pending->name; const char *name = pending->name;
if (obj->flags & (UNINTERESTING | SEEN)) 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 * This is a truly stupid algorithm, but it's only
* used for bisection, and we just don't care enough. * 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)) if (commit->object.flags & (UNINTERESTING | COUNTED))
break; break;
if (!paths || (commit->object.flags & TREECHANGE)) if (!revs.paths || (commit->object.flags & TREECHANGE))
nr++; nr++;
commit->object.flags |= COUNTED; commit->object.flags |= COUNTED;
p = commit->parents; p = commit->parents;
@ -447,7 +224,7 @@ static struct commit_list *find_bisection(struct commit_list *list)
nr = 0; nr = 0;
p = list; p = list;
while (p) { while (p) {
if (!paths || (p->item->object.flags & TREECHANGE)) if (!revs.paths || (p->item->object.flags & TREECHANGE))
nr++; nr++;
p = p->next; p = p->next;
} }
@ -457,7 +234,7 @@ static struct commit_list *find_bisection(struct commit_list *list)
for (p = list; p; p = p->next) { for (p = list; p; p = p->next) {
int distance; int distance;
if (paths && !(p->item->object.flags & TREECHANGE)) if (revs.paths && !(p->item->object.flags & TREECHANGE))
continue; continue;
distance = count_distance(p); distance = count_distance(p);
@ -483,7 +260,7 @@ static void mark_edge_parents_uninteresting(struct commit *commit)
if (!(parent->object.flags & UNINTERESTING)) if (!(parent->object.flags & UNINTERESTING))
continue; continue;
mark_tree_uninteresting(parent->tree); mark_tree_uninteresting(parent->tree);
if (edge_hint && !(parent->object.flags & SHOWN)) { if (revs.edge_hint && !(parent->object.flags & SHOWN)) {
parent->object.flags |= SHOWN; parent->object.flags |= SHOWN;
printf("-%s\n", sha1_to_hex(parent->object.sha1)); 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) int main(int argc, const char **argv)
{ {
const char *prefix = setup_git_directory(); struct commit_list *list;
struct commit_list *list = NULL; int i;
int i, limited = 0;
argc = setup_revisions(argc, argv, &revs, NULL);
for (i = 1 ; i < argc; i++) { for (i = 1 ; i < argc; i++) {
int flags;
const char *arg = argv[i]; const char *arg = argv[i];
char *dotdot;
struct commit *commit;
unsigned char sha1[20];
/* accept -<digit>, like traditilnal "head" */ /* accept -<digit>, like traditilnal "head" */
if ((*arg == '-') && isdigit(arg[1])) { if ((*arg == '-') && isdigit(arg[1])) {
max_count = atoi(arg + 1); revs.max_count = atoi(arg + 1);
continue; continue;
} }
if (!strcmp(arg, "-n")) { if (!strcmp(arg, "-n")) {
if (++i >= argc) if (++i >= argc)
die("-n requires an argument"); die("-n requires an argument");
max_count = atoi(argv[i]); revs.max_count = atoi(argv[i]);
continue; continue;
} }
if (!strncmp(arg,"-n",2)) { if (!strncmp(arg,"-n",2)) {
max_count = atoi(arg + 2); revs.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;
continue; continue;
} }
if (!strcmp(arg, "--header")) { if (!strcmp(arg, "--header")) {
@ -881,10 +331,6 @@ int main(int argc, const char **argv)
commit_prefix = "commit "; commit_prefix = "commit ";
continue; continue;
} }
if (!strncmp(arg, "--no-merges", 11)) {
no_merges = 1;
continue;
}
if (!strcmp(arg, "--parents")) { if (!strcmp(arg, "--parents")) {
show_parents = 1; show_parents = 1;
continue; continue;
@ -893,140 +339,27 @@ int main(int argc, const char **argv)
bisect_list = 1; bisect_list = 1;
continue; 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);
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);
}
if (!list &&
(!(tag_objects||tree_objects||blob_objects) && !pending_objects))
usage(rev_list_usage); usage(rev_list_usage);
paths = get_pathspec(prefix, argv + i);
if (paths) {
limited = 1;
diff_tree_setup_paths(paths);
} }
list = revs.commits;
if (!list &&
(!(revs.tag_objects||revs.tree_objects||revs.blob_objects) && !revs.pending_objects))
usage(rev_list_usage);
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; save_commit_buffer = verbose_header;
track_object_refs = 0; track_object_refs = 0;
if (!merge_order) { show_commit_list(&revs);
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
}
return 0; return 0;
} }

View File

@ -39,14 +39,12 @@ static int is_rev_argument(const char *arg)
"--header", "--header",
"--max-age=", "--max-age=",
"--max-count=", "--max-count=",
"--merge-order",
"--min-age=", "--min-age=",
"--no-merges", "--no-merges",
"--objects", "--objects",
"--objects-edge", "--objects-edge",
"--parents", "--parents",
"--pretty", "--pretty",
"--show-breaks",
"--sparse", "--sparse",
"--topo-order", "--topo-order",
"--date-order", "--date-order",

722
revision.c Normal file
View File

@ -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;
}

57
revision.h Normal file
View File

@ -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

View File

@ -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