зеркало из https://github.com/microsoft/git.git
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:
Коммит
21dbe12c76
|
@ -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>.
|
||||
|
|
4
INSTALL
4
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
|
||||
|
|
13
Makefile
13
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 $@
|
||||
|
|
3
cache.h
3
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 */
|
||||
|
|
639
epoch.c
639
epoch.c
|
@ -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
21
epoch.h
|
@ -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 */
|
|
@ -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);
|
||||
|
|
79
git.c
79
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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
743
rev-list.c
743
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);
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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
|
Загрузка…
Ссылка в новой задаче