diff options
-rw-r--r-- | Documentation/git-rev-list.txt | 49 | ||||
-rw-r--r-- | INSTALL | 4 | ||||
-rw-r--r-- | Makefile | 13 | ||||
-rw-r--r-- | cache.h | 3 | ||||
-rw-r--r-- | epoch.c | 639 | ||||
-rw-r--r-- | epoch.h | 21 | ||||
-rwxr-xr-x | git-archimport.perl | 4 | ||||
-rw-r--r-- | git.c | 79 | ||||
-rw-r--r-- | pager.c | 48 | ||||
-rw-r--r-- | rev-list.c | 737 | ||||
-rw-r--r-- | rev-parse.c | 2 | ||||
-rw-r--r-- | revision.c | 722 | ||||
-rw-r--r-- | revision.h | 57 | ||||
-rwxr-xr-x | t/t6001-rev-list-merge-order.sh | 462 |
14 files changed, 954 insertions, 1886 deletions
diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index 27f18e2c50..8255ae1bce 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -16,7 +16,7 @@ SYNOPSIS [ \--no-merges ] [ \--remove-empty ] [ \--all ] - [ [ \--merge-order [ \--show-breaks ] ] | [ \--topo-order ] ] + [ \--topo-order ] [ \--parents ] [ [\--objects | \--objects-edge] [ \--unpacked ] ] [ \--pretty | \--header ] @@ -102,57 +102,10 @@ OPTIONS topological order (i.e. descendant commits are shown before their parents). ---merge-order:: - When specified the commit history is decomposed into a unique - sequence of minimal, non-linear epochs and maximal, linear epochs. - Non-linear epochs are then linearised by sorting them into merge - order, which is described below. -+ -Maximal, linear epochs correspond to periods of sequential development. -Minimal, non-linear epochs correspond to periods of divergent development -followed by a converging merge. The theory of epochs is described in more -detail at -link:http://blackcubes.dyndns.org/epoch/[http://blackcubes.dyndns.org/epoch/]. -+ -The merge order for a non-linear epoch is defined as a linearisation for which -the following invariants are true: -+ - 1. if a commit P is reachable from commit N, commit P sorts after commit N - in the linearised list. - 2. if Pi and Pj are any two parents of a merge M (with i < j), then any - commit N, such that N is reachable from Pj but not reachable from Pi, - sorts before all commits reachable from Pi. -+ -Invariant 1 states that later commits appear before earlier commits they are -derived from. -+ -Invariant 2 states that commits unique to "later" parents in a merge, appear -before all commits from "earlier" parents of a merge. - ---show-breaks:: - Each item of the list is output with a 2-character prefix consisting - of one of: (|), (^), (=) followed by a space. -+ -Commits marked with (=) represent the boundaries of minimal, non-linear epochs -and correspond either to the start of a period of divergent development or to -the end of such a period. -+ -Commits marked with (|) are direct parents of commits immediately preceding -the marked commit in the list. -+ -Commits marked with (^) are not parents of the immediately preceding commit. -These "breaks" represent necessary discontinuities implied by trying to -represent an arbitrary DAG in a linear form. -+ -`--show-breaks` is only valid if `--merge-order` is also specified. - - Author ------ Written by Linus Torvalds <torvalds@osdl.org> -Original *--merge-order* logic by Jon Seymour <jon.seymour@gmail.com> - Documentation -------------- Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. @@ -40,9 +40,7 @@ Issues of note: If you don't have openssl, you can use one of the SHA1 libraries that come with git (git includes the one from Mozilla, and has - its own PowerPC-optimized one too - see the Makefile), and you - can avoid the bignum support by excising git-rev-list support - for "--merge-order" (by hand). + its own PowerPC and ARM optimized ones too - see the Makefile). - "libcurl" and "curl" executable. git-http-fetch and git-fetch use them. If you do not use http @@ -6,8 +6,8 @@ all: # on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default # choice) has very fast version optimized for i586. # -# Define NO_OPENSSL environment variable if you do not have OpenSSL. You will -# miss out git-rev-list --merge-order. This also implies MOZILLA_SHA1. +# Define NO_OPENSSL environment variable if you do not have OpenSSL. +# This also implies MOZILLA_SHA1. # # Define NO_CURL if you do not have curl installed. git-http-pull and # git-http-push are not built, and you cannot use http:// and https:// @@ -191,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 $@ @@ -354,4 +354,7 @@ extern int copy_fd(int ifd, int ofd); extern int receive_unpack_pack(int fd[2], const char *me, int quiet); extern int receive_keep_pack(int fd[2], const char *me, int quiet); +/* pager.c */ +extern void setup_pager(void); + #endif /* CACHE_H */ diff --git a/epoch.c b/epoch.c deleted file mode 100644 index 3a767486da..0000000000 --- a/epoch.c +++ /dev/null @@ -1,639 +0,0 @@ -/* - * Copyright (c) 2005, Jon Seymour - * - * For more information about epoch theory on which this module is based, - * refer to http://blackcubes.dyndns.org/epoch/. That web page defines - * terms such as "epoch" and "minimal, non-linear epoch" and provides rationales - * for some of the algorithms used here. - * - */ -#include <stdlib.h> - -/* Provides arbitrary precision integers required to accurately represent - * fractional mass: */ -#include <openssl/bn.h> - -#include "cache.h" -#include "commit.h" -#include "epoch.h" - -struct fraction { - BIGNUM numerator; - BIGNUM denominator; -}; - -#define HAS_EXACTLY_ONE_PARENT(n) ((n)->parents && !(n)->parents->next) - -static BN_CTX *context = NULL; -static struct fraction *one = NULL; -static struct fraction *zero = NULL; - -static BN_CTX *get_BN_CTX(void) -{ - if (!context) { - context = BN_CTX_new(); - } - return context; -} - -static struct fraction *new_zero(void) -{ - struct fraction *result = xmalloc(sizeof(*result)); - BN_init(&result->numerator); - BN_init(&result->denominator); - BN_zero(&result->numerator); - BN_one(&result->denominator); - return result; -} - -static void clear_fraction(struct fraction *fraction) -{ - BN_clear(&fraction->numerator); - BN_clear(&fraction->denominator); -} - -static struct fraction *divide(struct fraction *result, struct fraction *fraction, int divisor) -{ - BIGNUM bn_divisor; - - BN_init(&bn_divisor); - BN_set_word(&bn_divisor, divisor); - - BN_copy(&result->numerator, &fraction->numerator); - BN_mul(&result->denominator, &fraction->denominator, &bn_divisor, get_BN_CTX()); - - BN_clear(&bn_divisor); - return result; -} - -static struct fraction *init_fraction(struct fraction *fraction) -{ - BN_init(&fraction->numerator); - BN_init(&fraction->denominator); - BN_zero(&fraction->numerator); - BN_one(&fraction->denominator); - return fraction; -} - -static struct fraction *get_one(void) -{ - if (!one) { - one = new_zero(); - BN_one(&one->numerator); - } - return one; -} - -static struct fraction *get_zero(void) -{ - if (!zero) { - zero = new_zero(); - } - return zero; -} - -static struct fraction *copy(struct fraction *to, struct fraction *from) -{ - BN_copy(&to->numerator, &from->numerator); - BN_copy(&to->denominator, &from->denominator); - return to; -} - -static struct fraction *add(struct fraction *result, struct fraction *left, struct fraction *right) -{ - BIGNUM a, b, gcd; - - BN_init(&a); - BN_init(&b); - BN_init(&gcd); - - BN_mul(&a, &left->numerator, &right->denominator, get_BN_CTX()); - BN_mul(&b, &left->denominator, &right->numerator, get_BN_CTX()); - BN_mul(&result->denominator, &left->denominator, &right->denominator, get_BN_CTX()); - BN_add(&result->numerator, &a, &b); - - BN_gcd(&gcd, &result->denominator, &result->numerator, get_BN_CTX()); - BN_div(&result->denominator, NULL, &result->denominator, &gcd, get_BN_CTX()); - BN_div(&result->numerator, NULL, &result->numerator, &gcd, get_BN_CTX()); - - BN_clear(&a); - BN_clear(&b); - BN_clear(&gcd); - - return result; -} - -static int compare(struct fraction *left, struct fraction *right) -{ - BIGNUM a, b; - int result; - - BN_init(&a); - BN_init(&b); - - BN_mul(&a, &left->numerator, &right->denominator, get_BN_CTX()); - BN_mul(&b, &left->denominator, &right->numerator, get_BN_CTX()); - - result = BN_cmp(&a, &b); - - BN_clear(&a); - BN_clear(&b); - - return result; -} - -struct mass_counter { - struct fraction seen; - struct fraction pending; -}; - -static struct mass_counter *new_mass_counter(struct commit *commit, struct fraction *pending) -{ - struct mass_counter *mass_counter = xmalloc(sizeof(*mass_counter)); - memset(mass_counter, 0, sizeof(*mass_counter)); - - init_fraction(&mass_counter->seen); - init_fraction(&mass_counter->pending); - - copy(&mass_counter->pending, pending); - copy(&mass_counter->seen, get_zero()); - - if (commit->object.util) { - die("multiple attempts to initialize mass counter for %s", - sha1_to_hex(commit->object.sha1)); - } - - commit->object.util = mass_counter; - - return mass_counter; -} - -static void free_mass_counter(struct mass_counter *counter) -{ - clear_fraction(&counter->seen); - clear_fraction(&counter->pending); - free(counter); -} - -/* - * Finds the base commit of a list of commits. - * - * One property of the commit being searched for is that every commit reachable - * from the base commit is reachable from the commits in the starting list only - * via paths that include the base commit. - * - * This algorithm uses a conservation of mass approach to find the base commit. - * - * We start by injecting one unit of mass into the graph at each - * of the commits in the starting list. Injecting mass into a commit - * is achieved by adding to its pending mass counter and, if it is not already - * enqueued, enqueuing the commit in a list of pending commits, in latest - * commit date first order. - * - * The algorithm then proceeds to visit each commit in the pending queue. - * Upon each visit, the pending mass is added to the mass already seen for that - * commit and then divided into N equal portions, where N is the number of - * parents of the commit being visited. The divided portions are then injected - * into each of the parents. - * - * The algorithm continues until we discover a commit which has seen all the - * mass originally injected or until we run out of things to do. - * - * If we find a commit that has seen all the original mass, we have found - * the common base of all the commits in the starting list. - * - * The algorithm does _not_ depend on accurate timestamps for correct operation. - * However, reasonably sane (e.g. non-random) timestamps are required in order - * to prevent an exponential performance characteristic. The occasional - * timestamp inaccuracy will not dramatically affect performance but may - * result in more nodes being processed than strictly necessary. - * - * This procedure sets *boundary to the address of the base commit. It returns - * non-zero if, and only if, there was a problem parsing one of the - * commits discovered during the traversal. - */ -static int find_base_for_list(struct commit_list *list, struct commit **boundary) -{ - int ret = 0; - struct commit_list *cleaner = NULL; - struct commit_list *pending = NULL; - struct fraction injected; - init_fraction(&injected); - *boundary = NULL; - - for (; list; list = list->next) { - struct commit *item = list->item; - - if (!item->object.util) { - new_mass_counter(list->item, get_one()); - add(&injected, &injected, get_one()); - - commit_list_insert(list->item, &cleaner); - commit_list_insert(list->item, &pending); - } - } - - while (!*boundary && pending && !ret) { - struct commit *latest = pop_commit(&pending); - struct mass_counter *latest_node = (struct mass_counter *) latest->object.util; - int num_parents; - - if ((ret = parse_commit(latest))) - continue; - add(&latest_node->seen, &latest_node->seen, &latest_node->pending); - - num_parents = count_parents(latest); - if (num_parents) { - struct fraction distribution; - struct commit_list *parents; - - divide(init_fraction(&distribution), &latest_node->pending, num_parents); - - for (parents = latest->parents; parents; parents = parents->next) { - struct commit *parent = parents->item; - struct mass_counter *parent_node = (struct mass_counter *) parent->object.util; - - if (!parent_node) { - parent_node = new_mass_counter(parent, &distribution); - insert_by_date(parent, &pending); - commit_list_insert(parent, &cleaner); - } else { - if (!compare(&parent_node->pending, get_zero())) - insert_by_date(parent, &pending); - add(&parent_node->pending, &parent_node->pending, &distribution); - } - } - - clear_fraction(&distribution); - } - - if (!compare(&latest_node->seen, &injected)) - *boundary = latest; - copy(&latest_node->pending, get_zero()); - } - - while (cleaner) { - struct commit *next = pop_commit(&cleaner); - free_mass_counter((struct mass_counter *) next->object.util); - next->object.util = NULL; - } - - if (pending) - free_commit_list(pending); - - clear_fraction(&injected); - return ret; -} - - -/* - * Finds the base of an minimal, non-linear epoch, headed at head, by - * applying the find_base_for_list to a list consisting of the parents - */ -static int find_base(struct commit *head, struct commit **boundary) -{ - int ret = 0; - struct commit_list *pending = NULL; - struct commit_list *next; - - for (next = head->parents; next; next = next->next) { - commit_list_insert(next->item, &pending); - } - ret = find_base_for_list(pending, boundary); - free_commit_list(pending); - - return ret; -} - -/* - * This procedure traverses to the boundary of the first epoch in the epoch - * sequence of the epoch headed at head_of_epoch. This is either the end of - * the maximal linear epoch or the base of a minimal non-linear epoch. - * - * The queue of pending nodes is sorted in reverse date order and each node - * is currently in the queue at most once. - */ -static int find_next_epoch_boundary(struct commit *head_of_epoch, struct commit **boundary) -{ - int ret; - struct commit *item = head_of_epoch; - - ret = parse_commit(item); - if (ret) - return ret; - - if (HAS_EXACTLY_ONE_PARENT(item)) { - /* - * We are at the start of a maximimal linear epoch. - * Traverse to the end. - */ - while (HAS_EXACTLY_ONE_PARENT(item) && !ret) { - item = item->parents->item; - ret = parse_commit(item); - } - *boundary = item; - - } else { - /* - * Otherwise, we are at the start of a minimal, non-linear - * epoch - find the common base of all parents. - */ - ret = find_base(item, boundary); - } - - return ret; -} - -/* - * Returns non-zero if parent is known to be a parent of child. - */ -static int is_parent_of(struct commit *parent, struct commit *child) -{ - struct commit_list *parents; - for (parents = child->parents; parents; parents = parents->next) { - if (!memcmp(parent->object.sha1, parents->item->object.sha1, - sizeof(parents->item->object.sha1))) - return 1; - } - return 0; -} - -/* - * Pushes an item onto the merge order stack. If the top of the stack is - * marked as being a possible "break", we check to see whether it actually - * is a break. - */ -static void push_onto_merge_order_stack(struct commit_list **stack, struct commit *item) -{ - struct commit_list *top = *stack; - if (top && (top->item->object.flags & DISCONTINUITY)) { - if (is_parent_of(top->item, item)) { - top->item->object.flags &= ~DISCONTINUITY; - } - } - commit_list_insert(item, stack); -} - -/* - * Marks all interesting, visited commits reachable from this commit - * as uninteresting. We stop recursing when we reach the epoch boundary, - * an unvisited node or a node that has already been marking uninteresting. - * - * This doesn't actually mark all ancestors between the start node and the - * epoch boundary uninteresting, but does ensure that they will eventually - * be marked uninteresting when the main sort_first_epoch() traversal - * eventually reaches them. - */ -static void mark_ancestors_uninteresting(struct commit *commit) -{ - unsigned int flags = commit->object.flags; - int visited = flags & VISITED; - int boundary = flags & BOUNDARY; - int uninteresting = flags & UNINTERESTING; - struct commit_list *next; - - commit->object.flags |= UNINTERESTING; - - /* - * We only need to recurse if - * we are not on the boundary and - * we have not already been marked uninteresting and - * we have already been visited. - * - * The main sort_first_epoch traverse will mark unreachable - * all uninteresting, unvisited parents as they are visited - * so there is no need to duplicate that traversal here. - * - * Similarly, if we are already marked uninteresting - * then either all ancestors have already been marked - * uninteresting or will be once the sort_first_epoch - * traverse reaches them. - */ - - if (uninteresting || boundary || !visited) - return; - - for (next = commit->parents; next; next = next->next) - mark_ancestors_uninteresting(next->item); -} - -/* - * Sorts the nodes of the first epoch of the epoch sequence of the epoch headed at head - * into merge order. - */ -static void sort_first_epoch(struct commit *head, struct commit_list **stack) -{ - struct commit_list *parents; - - head->object.flags |= VISITED; - - /* - * TODO: By sorting the parents in a different order, we can alter the - * merge order to show contemporaneous changes in parallel branches - * occurring after "local" changes. This is useful for a developer - * when a developer wants to see all changes that were incorporated - * into the same merge as her own changes occur after her own - * changes. - */ - - for (parents = head->parents; parents; parents = parents->next) { - struct commit *parent = parents->item; - - if (head->object.flags & UNINTERESTING) { - /* - * Propagates the uninteresting bit to all parents. - * if we have already visited this parent, then - * the uninteresting bit will be propagated to each - * reachable commit that is still not marked - * uninteresting and won't otherwise be reached. - */ - mark_ancestors_uninteresting(parent); - } - - if (!(parent->object.flags & VISITED)) { - if (parent->object.flags & BOUNDARY) { - if (*stack) { - die("something else is on the stack - %s", - sha1_to_hex((*stack)->item->object.sha1)); - } - push_onto_merge_order_stack(stack, parent); - parent->object.flags |= VISITED; - - } else { - sort_first_epoch(parent, stack); - if (parents) { - /* - * This indicates a possible - * discontinuity it may not be be - * actual discontinuity if the head - * of parent N happens to be the tail - * of parent N+1. - * - * The next push onto the stack will - * resolve the question. - */ - (*stack)->item->object.flags |= DISCONTINUITY; - } - } - } - } - - push_onto_merge_order_stack(stack, head); -} - -/* - * Emit the contents of the stack. - * - * The stack is freed and replaced by NULL. - * - * Sets the return value to STOP if no further output should be generated. - */ -static int emit_stack(struct commit_list **stack, emitter_func emitter, int include_last) -{ - unsigned int seen = 0; - int action = CONTINUE; - - while (*stack && (action != STOP)) { - struct commit *next = pop_commit(stack); - seen |= next->object.flags; - if (*stack || include_last) { - if (!*stack) - next->object.flags |= BOUNDARY; - action = emitter(next); - } - } - - if (*stack) { - free_commit_list(*stack); - *stack = NULL; - } - - return (action == STOP || (seen & UNINTERESTING)) ? STOP : CONTINUE; -} - -/* - * Sorts an arbitrary epoch into merge order by sorting each epoch - * of its epoch sequence into order. - * - * Note: this algorithm currently leaves traces of its execution in the - * object flags of nodes it discovers. This should probably be fixed. - */ -static int sort_in_merge_order(struct commit *head_of_epoch, emitter_func emitter) -{ - struct commit *next = head_of_epoch; - int ret = 0; - int action = CONTINUE; - - ret = parse_commit(head_of_epoch); - - next->object.flags |= BOUNDARY; - - while (next && next->parents && !ret && (action != STOP)) { - struct commit *base = NULL; - - ret = find_next_epoch_boundary(next, &base); - if (ret) - return ret; - next->object.flags |= BOUNDARY; - if (base) - base->object.flags |= BOUNDARY; - - if (HAS_EXACTLY_ONE_PARENT(next)) { - while (HAS_EXACTLY_ONE_PARENT(next) - && (action != STOP) - && !ret) { - if (next->object.flags & UNINTERESTING) { - action = STOP; - } else { - action = emitter(next); - } - if (action != STOP) { - next = next->parents->item; - ret = parse_commit(next); - } - } - - } else { - struct commit_list *stack = NULL; - sort_first_epoch(next, &stack); - action = emit_stack(&stack, emitter, (base == NULL)); - next = base; - } - } - - if (next && (action != STOP) && !ret) { - emitter(next); - } - - return ret; -} - -/* - * Sorts the nodes reachable from a starting list in merge order, we - * first find the base for the starting list and then sort all nodes - * in this subgraph using the sort_first_epoch algorithm. Once we have - * reached the base we can continue sorting using sort_in_merge_order. - */ -int sort_list_in_merge_order(struct commit_list *list, emitter_func emitter) -{ - struct commit_list *stack = NULL; - struct commit *base; - int ret = 0; - int action = CONTINUE; - struct commit_list *reversed = NULL; - - for (; list; list = list->next) - commit_list_insert(list->item, &reversed); - - if (!reversed) - return ret; - else if (!reversed->next) { - /* - * If there is only one element in the list, we can sort it - * using sort_in_merge_order. - */ - base = reversed->item; - } else { - /* - * Otherwise, we search for the base of the list. - */ - ret = find_base_for_list(reversed, &base); - if (ret) - return ret; - if (base) - base->object.flags |= BOUNDARY; - - while (reversed) { - struct commit * next = pop_commit(&reversed); - - if (!(next->object.flags & VISITED) && next!=base) { - sort_first_epoch(next, &stack); - if (reversed) { - /* - * If we have more commits - * to push, then the first - * push for the next parent may - * (or may * not) represent a - * discontinuity with respect - * to the parent currently on - * the top of the stack. - * - * Mark it for checking here, - * and check it with the next - * push. See sort_first_epoch() - * for more details. - */ - stack->item->object.flags |= DISCONTINUITY; - } - } - } - - action = emit_stack(&stack, emitter, (base==NULL)); - } - - if (base && (action != STOP)) { - ret = sort_in_merge_order(base, emitter); - } - - return ret; -} diff --git a/epoch.h b/epoch.h deleted file mode 100644 index 7493d5a241..0000000000 --- a/epoch.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef EPOCH_H -#define EPOCH_H - - -// return codes for emitter_func -#define STOP 0 -#define CONTINUE 1 -#define DO 2 -typedef int (*emitter_func) (struct commit *); - -int sort_list_in_merge_order(struct commit_list *list, emitter_func emitter); - -/* Low bits are used by rev-list */ -#define UNINTERESTING (1u<<10) -#define BOUNDARY (1u<<11) -#define VISITED (1u<<12) -#define DISCONTINUITY (1u<<13) -#define LAST_EPOCH_FLAG (1u<<14) - - -#endif /* EPOCH_H */ diff --git a/git-archimport.perl b/git-archimport.perl index 6792624d46..740bc1fd52 100755 --- a/git-archimport.perl +++ b/git-archimport.perl @@ -928,7 +928,7 @@ sub find_parents { # now walk up to the mergepoint collecting what patches we have my $branchtip = git_rev_parse($ps->{branch}); - my @ancestors = `git-rev-list --merge-order $branchtip ^$mergebase`; + my @ancestors = `git-rev-list --topo-order $branchtip ^$mergebase`; my %have; # collected merges this branch has foreach my $merge (@{$ps->{merges}}) { $have{$merge} = 1; @@ -951,7 +951,7 @@ sub find_parents { # see what the remote branch has - these are the merges we # will want to have in a consecutive series from the mergebase my $otherbranchtip = git_rev_parse($branch); - my @needraw = `git-rev-list --merge-order $otherbranchtip ^$mergebase`; + my @needraw = `git-rev-list --topo-order $otherbranchtip ^$mergebase`; my @need; foreach my $needps (@needraw) { # get the psets $needps = commitid2pset($needps); @@ -12,6 +12,10 @@ #include "git-compat-util.h" #include "exec_cmd.h" +#include "cache.h" +#include "commit.h" +#include "revision.h" + #ifndef PATH_MAX # define PATH_MAX 4096 #endif @@ -245,6 +249,80 @@ static int cmd_help(int argc, char **argv, char **envp) return 0; } +#define LOGSIZE (65536) + +static int cmd_log(int argc, char **argv, char **envp) +{ + struct rev_info rev; + struct commit *commit; + char *buf = xmalloc(LOGSIZE); + static enum cmit_fmt commit_format = CMIT_FMT_DEFAULT; + int abbrev = DEFAULT_ABBREV; + int show_parents = 0; + const char *commit_prefix = "commit "; + + argc = setup_revisions(argc, argv, &rev, "HEAD"); + while (1 < argc) { + char *arg = argv[1]; + if (!strncmp(arg, "--pretty", 8)) { + commit_format = get_commit_format(arg + 8); + if (commit_format == CMIT_FMT_ONELINE) + commit_prefix = ""; + } + else if (!strcmp(arg, "--parents")) { + show_parents = 1; + } + else if (!strcmp(arg, "--no-abbrev")) { + abbrev = 0; + } + else if (!strncmp(arg, "--abbrev=", 9)) { + abbrev = strtoul(arg + 9, NULL, 10); + if (abbrev && abbrev < MINIMUM_ABBREV) + abbrev = MINIMUM_ABBREV; + else if (40 < abbrev) + abbrev = 40; + } + else + die("unrecognized argument: %s", arg); + argc--; argv++; + } + + prepare_revision_walk(&rev); + setup_pager(); + while ((commit = get_revision(&rev)) != NULL) { + printf("%s%s", commit_prefix, + sha1_to_hex(commit->object.sha1)); + if (show_parents) { + struct commit_list *parents = commit->parents; + while (parents) { + struct object *o = &(parents->item->object); + parents = parents->next; + if (o->flags & TMP_MARK) + continue; + printf(" %s", sha1_to_hex(o->sha1)); + o->flags |= TMP_MARK; + } + /* TMP_MARK is a general purpose flag that can + * be used locally, but the user should clean + * things up after it is done with them. + */ + for (parents = commit->parents; + parents; + parents = parents->next) + parents->item->object.flags &= ~TMP_MARK; + } + if (commit_format == CMIT_FMT_ONELINE) + putchar(' '); + else + putchar('\n'); + pretty_print_commit(commit_format, commit, ~0, buf, + LOGSIZE, abbrev); + printf("%s\n", buf); + } + free(buf); + return 0; +} + #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) static void handle_internal_command(int argc, char **argv, char **envp) @@ -256,6 +334,7 @@ static void handle_internal_command(int argc, char **argv, char **envp) } commands[] = { { "version", cmd_version }, { "help", cmd_help }, + { "log", cmd_log }, }; int i; diff --git a/pager.c b/pager.c new file mode 100644 index 0000000000..1364e15d23 --- /dev/null +++ b/pager.c @@ -0,0 +1,48 @@ +#include "cache.h" + +/* + * This is split up from the rest of git so that we might do + * something different on Windows, for example. + */ + +static void run_pager(void) +{ + const char *prog = getenv("PAGER"); + if (!prog) + prog = "less"; + setenv("LESS", "-S", 0); + execlp(prog, prog, NULL); +} + +void setup_pager(void) +{ + pid_t pid; + int fd[2]; + + if (!isatty(1)) + return; + if (pipe(fd) < 0) + return; + pid = fork(); + if (pid < 0) { + close(fd[0]); + close(fd[1]); + return; + } + + /* return in the child */ + if (!pid) { + dup2(fd[1], 1); + close(fd[0]); + close(fd[1]); + return; + } + + /* The original process turns into the PAGER */ + dup2(fd[0], 0); + close(fd[0]); + close(fd[1]); + + run_pager(); + exit(255); +} diff --git a/rev-list.c b/rev-list.c index 67d2a483fc..8e4d83efba 100644 --- a/rev-list.c +++ b/rev-list.c @@ -4,15 +4,12 @@ #include "commit.h" #include "tree.h" #include "blob.h" -#include "epoch.h" #include "diff.h" +#include "revision.h" -#define SEEN (1u << 0) -#define INTERESTING (1u << 1) -#define COUNTED (1u << 2) -#define SHOWN (1u << 3) -#define TREECHANGE (1u << 4) -#define TMP_MARK (1u << 5) /* for isolated cases; clean after use */ +/* bits #0-4 in revision.h */ + +#define COUNTED (1u<<5) static const char rev_list_usage[] = "git-rev-list [OPTION] <commit-id>... [ -- paths... ]\n" @@ -25,7 +22,6 @@ static const char rev_list_usage[] = " --remove-empty\n" " --all\n" " ordering output:\n" -" --merge-order [ --show-breaks ]\n" " --topo-order\n" " --date-order\n" " formatting output:\n" @@ -38,72 +34,18 @@ static const char rev_list_usage[] = " --bisect" ; -static int dense = 1; -static int unpacked = 0; +struct rev_info revs; + static int bisect_list = 0; -static int tag_objects = 0; -static int tree_objects = 0; -static int blob_objects = 0; -static int edge_hint = 0; static int verbose_header = 0; static int abbrev = DEFAULT_ABBREV; static int show_parents = 0; static int hdr_termination = 0; static const char *commit_prefix = ""; -static unsigned long max_age = -1; -static unsigned long min_age = -1; -static int max_count |