Browse Source
Now blame will depend on the new revision walker infrastructure, we need to make it depend on earlier parts of Linus' rev-list topic branch, hence this merge. Signed-off-by: Junio C Hamano <junkio@cox.net>maint

80 changed files with 5881 additions and 2342 deletions
@ -0,0 +1,89 @@
@@ -0,0 +1,89 @@
|
||||
git-cvsserver(1) |
||||
================ |
||||
|
||||
NAME |
||||
---- |
||||
git-cvsserver - A CVS server emulator for git |
||||
|
||||
|
||||
SYNOPSIS |
||||
-------- |
||||
[verse] |
||||
export CVS_SERVER=git-cvsserver |
||||
'cvs' -d :ext:user@server/path/repo.git co <HEAD_name> |
||||
|
||||
|
||||
DESCRIPTION |
||||
----------- |
||||
|
||||
This application is a CVS emulation layer for git. |
||||
|
||||
It is highly functional. However, not all methods are implemented, |
||||
and for those methods that are implemented, |
||||
not all switches are implemented. |
||||
|
||||
Testing has been done using both the CLI CVS client, and the Eclipse CVS |
||||
plugin. Most functionality works fine with both of these clients. |
||||
|
||||
LIMITATIONS |
||||
----------- |
||||
Currently gitcvs only works over ssh connections. |
||||
|
||||
|
||||
INSTALLATION |
||||
------------ |
||||
1. Put server.pl somewhere useful on the same machine that is hosting your git repos |
||||
|
||||
2. For each repo that you want accessible from CVS you need to edit config in |
||||
the repo and add the following section. |
||||
|
||||
[gitcvs] |
||||
enabled=1 |
||||
logfile=/path/to/logfile |
||||
|
||||
n.b. you need to ensure each user that is going to invoke server.pl has |
||||
write access to the log file. |
||||
|
||||
5. On each client machine you need to set the following variables. |
||||
CVSROOT should be set as per normal, but the directory should point at the |
||||
appropriate git repo. |
||||
CVS_SERVER should be set to the server.pl script that has been put on the |
||||
remote machine. |
||||
|
||||
6. Clients should now be able to check out modules (where modules are the names |
||||
of branches in git). |
||||
$ cvs co -d mylocaldir master |
||||
|
||||
Operations supported |
||||
-------------------- |
||||
|
||||
All the operations required for normal use are supported, including |
||||
checkout, diff, status, update, log, add, remove, commit. |
||||
Legacy monitoring operations are not supported (edit, watch and related). |
||||
Exports and tagging (tags and branches) are not supported at this stage. |
||||
|
||||
The server will set the -k mode to binary when relevant. In proper GIT |
||||
tradition, the contents of the files are always respected. |
||||
No keyword expansion or newline munging is supported. |
||||
|
||||
Dependencies |
||||
------------ |
||||
|
||||
git-cvsserver depends on DBD::SQLite. |
||||
|
||||
Copyright and Authors |
||||
--------------------- |
||||
|
||||
This program is copyright The Open University UK - 2006. |
||||
|
||||
Authors: Martyn Smith <martyn@catalyst.net.nz> |
||||
Martin Langhoff <martin@catalyst.net.nz> |
||||
with ideas and patches from participants of the git-list <git@vger.kernel.org>. |
||||
|
||||
Documentation |
||||
-------------- |
||||
Documentation by Martyn Smith <martyn@catalyst.net.nz> and Martin Langhoff <martin@catalyst.net.nz>Matthias Urlichs <smurf@smurf.noris.de>. |
||||
|
||||
GIT |
||||
--- |
||||
Part of the gitlink:git[7] suite |
@ -0,0 +1,89 @@
@@ -0,0 +1,89 @@
|
||||
git-rm(1) |
||||
========= |
||||
|
||||
NAME |
||||
---- |
||||
git-rm - Remove files from the working tree and from the index. |
||||
|
||||
SYNOPSIS |
||||
-------- |
||||
'git-rm' [-f] [-n] [-v] [--] <file>... |
||||
|
||||
DESCRIPTION |
||||
----------- |
||||
A convenience wrapper for git-update-index --remove. For those coming |
||||
from cvs, git-rm provides an operation similar to "cvs rm" or "cvs |
||||
remove". |
||||
|
||||
|
||||
OPTIONS |
||||
------- |
||||
<file>...:: |
||||
Files to remove from the index and optionally, from the |
||||
working tree as well. |
||||
|
||||
-f:: |
||||
Remove files from the working tree as well as from the index. |
||||
|
||||
-n:: |
||||
Don't actually remove the file(s), just show if they exist in |
||||
the index. |
||||
|
||||
-v:: |
||||
Be verbose. |
||||
|
||||
--:: |
||||
This option can be used to separate command-line options from |
||||
the list of files, (useful when filenames might be mistaken |
||||
for command-line options). |
||||
|
||||
|
||||
DISCUSSION |
||||
---------- |
||||
|
||||
The list of <file> given to the command is fed to `git-ls-files` |
||||
command to list files that are registered in the index and |
||||
are not ignored/excluded by `$GIT_DIR/info/exclude` file or |
||||
`.gitignore` file in each directory. This means two things: |
||||
|
||||
. You can put the name of a directory on the command line, and the |
||||
command will remove all files in it and its subdirectories (the |
||||
directories themselves are never removed from the working tree); |
||||
|
||||
. Giving the name of a file that is not in the index does not |
||||
remove that file. |
||||
|
||||
|
||||
EXAMPLES |
||||
-------- |
||||
git-rm Documentation/\\*.txt:: |
||||
|
||||
Removes all `\*.txt` files from the index that are under the |
||||
`Documentation` directory and any of its subdirectories. The |
||||
files are not removed from the working tree. |
||||
+ |
||||
Note that the asterisk `\*` is quoted from the shell in this |
||||
example; this lets the command include the files from |
||||
subdirectories of `Documentation/` directory. |
||||
|
||||
git-rm -f git-*.sh:: |
||||
|
||||
Remove all git-*.sh scripts that are in the index. The files |
||||
are removed from the index, and (because of the -f option), |
||||
from the working tree as well. Because this example lets the |
||||
shell expand the asterisk (i.e. you are listing the files |
||||
explicitly), it does not remove `subdir/git-foo.sh`. |
||||
|
||||
|
||||
Author |
||||
------ |
||||
Written by Linus Torvalds <torvalds@osdl.org> |
||||
|
||||
Documentation |
||||
-------------- |
||||
Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. |
||||
|
||||
GIT |
||||
--- |
||||
Part of the gitlink:git[7] suite |
||||
|
@ -1,639 +0,0 @@
@@ -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; |
||||
} |
@ -1,21 +0,0 @@
@@ -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 */ |
@ -0,0 +1,488 @@
@@ -0,0 +1,488 @@
|
||||
#!/usr/bin/perl |
||||
# Copyright 2006, Ryan Anderson <ryan@michonline.com> |
||||
# |
||||
# GPL v2 (See COPYING) |
||||
# |
||||
# This file is licensed under the GPL v2, or a later version |
||||
# at the discretion of Linus Torvalds. |
||||
|
||||
use warnings; |
||||
use strict; |
||||
use Getopt::Long; |
||||
use POSIX qw(strftime gmtime); |
||||
|
||||
sub usage() { |
||||
print STDERR 'Usage: ${\basename $0} [-s] [-S revs-file] file [ revision ] |
||||
-l, --long |
||||
Show long rev (Defaults off) |
||||
-r, --rename |
||||
Follow renames (Defaults on). |
||||
-S, --rev-file revs-file |
||||
use revs from revs-file instead of calling git-rev-list |
||||
-h, --help |
||||
This message. |
||||
'; |
||||
|
||||
exit(1); |
||||
} |
||||
|
||||
our ($help, $longrev, $rename, $starting_rev, $rev_file) = (0, 0, 1); |
||||
|
||||
my $rc = GetOptions( "long|l" => \$longrev, |
||||
"help|h" => \$help, |
||||
"rename|r" => \$rename, |
||||
"rev-file|S" => \$rev_file); |
||||
if (!$rc or $help) { |
||||
usage(); |
||||
} |
||||
|
||||
my $filename = shift @ARGV; |
||||
if (@ARGV) { |
||||
$starting_rev = shift @ARGV; |
||||
} |
||||
|
||||
my @stack = ( |
||||
{ |
||||
'rev' => defined $starting_rev ? $starting_rev : "HEAD", |
||||
'filename' => $filename, |
||||
}, |
||||
); |
||||
|
||||
our @filelines = (); |
||||
|
||||
if (defined $starting_rev) { |
||||
@filelines = git_cat_file($starting_rev, $filename); |
||||
} else { |
||||
open(F,"<",$filename) |
||||
or die "Failed to open filename: $!"; |
||||
|
||||
while(<F>) { |
||||
chomp; |
||||
push @filelines, $_; |
||||
} |
||||
close(F); |
||||
|
||||
} |
||||
|
||||
our %revs; |
||||
our @revqueue; |
||||
our $head; |
||||
|
||||
my $revsprocessed = 0; |
||||
while (my $bound = pop @stack) { |
||||
my @revisions = git_rev_list($bound->{'rev'}, $bound->{'filename'}); |
||||
foreach my $revinst (@revisions) { |
||||
my ($rev, @parents) = @$revinst; |
||||
$head ||= $rev; |
||||
|
||||
if (!defined($rev)) { |
||||
$rev = ""; |
||||
} |
||||
$revs{$rev}{'filename'} = $bound->{'filename'}; |
||||
if (scalar @parents > 0) { |
||||
$revs{$rev}{'parents'} = \@parents; |
||||
next; |
||||
} |
||||
|
||||
if (!$rename) { |
||||
next; |
||||
} |
||||
|
||||
my $newbound = find_parent_renames($rev, $bound->{'filename'}); |
||||
if ( exists $newbound->{'filename'} && $newbound->{'filename'} ne $bound->{'filename'}) { |
||||
push @stack, $newbound; |
||||
$revs{$rev}{'parents'} = [$newbound->{'rev'}]; |
||||
} |
||||
} |
||||
} |
||||
push @revqueue, $head; |
||||
init_claim( defined $starting_rev ? $starting_rev : 'dirty'); |
||||
unless (defined $starting_rev) { |
||||
my $diff = open_pipe("git","diff","-R", "HEAD", "--",$filename) |
||||
or die "Failed to call git diff to check for dirty state: $!"; |
||||
|
||||
_git_diff_parse($diff, $head, "dirty", ( |
||||
'author' => gitvar_name("GIT_AUTHOR_IDENT"), |
||||
'author_date' => sprintf("%s +0000",time()), |
||||
) |
||||
); |
||||
close($diff); |
||||
} |
||||
handle_rev(); |
||||
|
||||
|
||||
my $i = 0; |
||||
foreach my $l (@filelines) { |
||||
my ($output, $rev, $committer, $date); |
||||
if (ref $l eq 'ARRAY') { |
||||
($output, $rev, $committer, $date) = @$l; |
||||
if (!$longrev && length($rev) > 8) { |
||||
$rev = substr($rev,0,8); |
||||
} |
||||
} else { |
||||
$output = $l; |
||||
($rev, $committer, $date) = ('unknown', 'unknown', 'unknown'); |
||||
} |
||||
|
||||
printf("%s\t(%10s\t%10s\t%d)%s\n", $rev, $committer, |
||||
format_date($date), $i++, $output); |
||||
} |
||||
|
||||
sub init_claim { |
||||
my ($rev) = @_; |
||||
for (my $i = 0; $i < @filelines; $i++) { |
||||
$filelines[$i] = [ $filelines[$i], '', '', '', 1]; |
||||
# line, |
||||
# rev, |
||||
# author, |
||||
# date, |
||||
# 1 <-- belongs to the original file. |
||||
} |
||||
$revs{$rev}{'lines'} = \@filelines; |
||||
} |
||||
|
||||
|
||||
sub handle_rev { |
||||
my $i = 0; |
||||
my %seen; |
||||
while (my $rev = shift @revqueue) { |
||||
next if $seen{$rev}++; |
||||
|
||||
my %revinfo = git_commit_info($rev); |
||||
|
||||
foreach my $p (@{$revs{$rev}{'parents'}}) { |
||||
|
||||
git_diff_parse($p, $rev, %revinfo); |
||||
push @revqueue, $p; |
||||
} |
||||
|
||||
|
||||
if (scalar @{$revs{$rev}{parents}} == 0) { |
||||
# We must be at the initial rev here, so claim everything that is left. |
||||
for (my $i = 0; $i < @{$revs{$rev}{lines}}; $i++) { |
||||
if (ref ${$revs{$rev}{lines}}[$i] eq '' || ${$revs{$rev}{lines}}[$i][1] eq '') { |
||||
claim_line($i, $rev, $revs{$rev}{lines}, %revinfo); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
sub git_rev_list { |
||||
my ($rev, $file) = @_; |
||||
|
||||
my $revlist; |
||||
if ($rev_file) { |
||||
open($revlist, '<' . $rev_file); |
||||
} else { |
||||
$revlist = open_pipe("git-rev-list","--parents","--remove-empty",$rev,"--",$file) |
||||
or die "Failed to exec git-rev-list: $!"; |
||||
} |
||||
|
||||
my @revs; |
||||
while(my $line = <$revlist>) { |
||||
chomp $line; |
||||
my ($rev, @parents) = split /\s+/, $line; |
||||
push @revs, [ $rev, @parents ]; |
||||
} |
||||
close($revlist); |
||||
|
||||
printf("0 revs found for rev %s (%s)\n", $rev, $file) if (@revs == 0); |
||||
return @revs; |
||||
} |
||||
|
||||
sub find_parent_renames { |
||||
my ($rev, $file) = @_; |
||||
|
||||
my $patch = open_pipe("git-diff-tree", "-M50", "-r","--name-status", "-z","$rev") |
||||
or die "Failed to exec git-diff: $!"; |
||||
|
||||
local $/ = "\0"; |
||||
my %bound; |
||||
my $junk = <$patch>; |
||||
while (my $change = <$patch>) { |
||||
chomp $change; |
||||
my $filename = <$patch>; |
||||
chomp $filename; |
||||
|
||||
if ($change =~ m/^[AMD]$/ ) { |
||||
next; |
||||
} elsif ($change =~ m/^R/ ) { |
||||
my $oldfilename = $filename; |
||||
$filename = <$patch>; |
||||
chomp $filename; |
||||
if ( $file eq $filename ) { |
||||
my $parent = git_find_parent($rev, $oldfilename); |
||||
@bound{'rev','filename'} = ($parent, $oldfilename); |
||||
last; |
||||
} |
||||
} |
||||
} |
||||
close($patch); |
||||
|
||||
return \%bound; |
||||
} |
||||
|
||||
|
||||
sub git_find_parent { |
||||
my ($rev, $filename) = @_; |
||||
|
||||
my $revparent = open_pipe("git-rev-list","--remove-empty", "--parents","--max-count=1","$rev","--",$filename) |
||||
or die "Failed to open git-rev-list to find a single parent: $!"; |
||||
|
||||
my $parentline = <$revparent>; |
||||
chomp $parentline; |
||||
my ($revfound,$parent) = split m/\s+/, $parentline; |
||||
|
||||
close($revparent); |
||||
|
||||
return $parent; |
||||
} |
||||
|
||||
|
||||
# Get a diff between the current revision and a parent. |
||||
# Record the commit information that results. |
||||
sub git_diff_parse { |
||||
my ($parent, $rev, %revinfo) = @_; |
||||
|
||||
my $diff = open_pipe("git-diff-tree","-M","-p",$rev,$parent,"--", |
||||
$revs{$rev}{'filename'}, $revs{$parent}{'filename'}) |
||||
or die "Failed to call git-diff for annotation: $!"; |
||||
|
||||
_git_diff_parse($diff, $parent, $rev, %revinfo); |
||||
|
||||
close($diff); |
||||
} |
||||
|
||||
sub _git_diff_parse { |
||||
my ($diff, $parent, $rev, %revinfo) = @_; |
||||
|
||||
my ($ri, $pi) = (0,0); |
||||
my $slines = $revs{$rev}{'lines'}; |
||||
my @plines; |
||||
|
||||
my $gotheader = 0; |
||||
my ($remstart); |
||||
my ($hunk_start, $hunk_index); |
||||
while(<$diff>) { |
||||
chomp; |
||||
if (m/^@@ -(\d+),(\d+) \+(\d+),(\d+)/) { |
||||
$remstart = $1; |
||||
# Adjust for 0-based arrays |
||||
$remstart--; |
||||
# Reinit hunk tracking. |
||||
$hunk_start = $remstart; |
||||
$hunk_index = 0; |
||||
$gotheader = 1; |
||||
|
||||
for (my $i = $ri; $i < $remstart; $i++) { |
||||
$plines[$pi++] = $slines->[$i]; |
||||
$ri++; |
||||
} |
||||
next; |
||||
} elsif (!$gotheader) { |
||||
next; |
||||
} |
||||
|
||||
if (m/^\+(.*)$/) { |
||||
my $line = $1; |
||||
$plines[$pi++] = [ $line, '', '', '', 0 ]; |
||||
next; |
||||
|
||||
} elsif (m/^-(.*)$/) { |
||||
my $line = $1; |
||||
if (get_line($slines, $ri) eq $line) { |
||||
# Found a match, claim |
||||
claim_line($ri, $rev, $slines, %revinfo); |
||||
} else { |
||||
die sprintf("Sync error: %d/%d\n|%s\n|%s\n%s => %s\n", |
||||
$ri, $hunk_start + $hunk_index, |
||||
$line, |
||||
get_line($slines, $ri), |
||||
$rev, $parent); |
||||
} |
||||
$ri++; |
||||
|
||||
} else { |
||||
if (substr($_,1) ne get_line($slines,$ri) ) { |
||||
die sprintf("Line %d (%d) does not match:\n|%s\n|%s\n%s => %s\n", |
||||
$hunk_start + $hunk_index, $ri, |
||||
substr($_,1), |
||||
get_line($slines,$ri), |
||||
$rev, $parent); |
||||
} |
||||
$plines[$pi++] = $slines->[$ri++]; |
||||
} |
||||
$hunk_index++; |
||||
} |
||||
for (my $i = $ri; $i < @{$slines} ; $i++) { |
||||
push @plines, $slines->[$ri++]; |
||||
} |
||||
|
||||
$revs{$parent}{lines} = \@plines; |
||||
return; |
||||
} |
||||
|
||||
sub get_line { |
||||
my ($lines, $index) = @_; |
||||
|
||||
return ref $lines->[$index] ne '' ? $lines->[$index][0] : $lines->[$index]; |
||||
} |
||||
|
||||
sub git_cat_file { |
||||
my ($rev, $filename) = @_; |
||||
return () unless defined $rev && defined $filename; |
||||
|
||||
my $blob = git_ls_tree($rev, $filename); |
||||
|
||||
my $catfile = open_pipe("git","cat-file", "blob", $blob) |
||||
or die "Failed to git-cat-file blob $blob (rev $rev, file $filename): " . $!; |
||||
|
||||
my @lines; |
||||
while(<$catfile>) { |
||||
chomp; |
||||
push @lines, $_; |
||||
} |
||||
close($catfile); |
||||
|
||||
return @lines; |
||||
} |
||||
|
||||
sub git_ls_tree { |
||||
my ($rev, $filename) = @_; |
||||
|
||||
my $lstree = open_pipe("git","ls-tree",$rev,$filename) |
||||
or die "Failed to call git ls-tree: $!"; |
||||
|
||||
my ($mode, $type, $blob, $tfilename); |
||||
while(<$lstree>) { |
||||
($mode, $type, $blob, $tfilename) = split(/\s+/, $_, 4); |
||||
last if ($tfilename eq $filename); |
||||
} |
||||
close($lstree); |
||||
|
||||
return $blob if $filename eq $filename; |
||||
die "git-ls-tree failed to find blob for $filename"; |
||||
|
||||
} |
||||
|
||||
|
||||
|
||||
sub claim_line { |
||||
my ($floffset, $rev, $lines, %revinfo) = @_; |
||||
my $oline = get_line($lines, $floffset); |
||||
@{$lines->[$floffset]} = ( $oline, $rev, |
||||
$revinfo{'author'}, $revinfo{'author_date'} ); |
||||
#printf("Claiming line %d with rev %s: '%s'\n", |
||||
# $floffset, $rev, $oline) if 1; |
||||
} |
||||
|
||||
sub git_commit_info { |
||||
my ($rev) = @_; |
||||
my $commit = open_pipe("git-cat-file", "commit", $rev) |
||||
or die "Failed to call git-cat-file: $!"; |
||||
|
||||
my %info; |
||||
while(<$commit>) { |
||||
chomp; |
||||
last if (length $_ == 0); |
||||
|
||||
if (m/^author (.*) <(.*)> (.*)$/) { |
||||
$info{'author'} = $1; |
||||
$info{'author_email'} = $2; |
||||
$info{'author_date'} = $3; |
||||
} elsif (m/^committer (.*) <(.*)> (.*)$/) { |
||||
$info{'committer'} = $1; |
||||
$info{'committer_email'} = $2; |
||||
$info{'committer_date'} = $3; |
||||
} |
||||
} |
||||
close($commit); |
||||
|
||||
return %info; |
||||
} |
||||
|
||||
sub format_date { |
||||
my ($timestamp, $timezone) = split(' ', $_[0]); |
||||
|
||||
return strftime("%Y-%m-%d %H:%M:%S " . $timezone, gmtime($timestamp)); |
||||
} |
||||
|
||||
# Copied from git-send-email.perl - We need a Git.pm module.. |
||||
sub gitvar { |
||||
my ($var) = @_; |
||||
my $fh; |
||||
my $pid = open($fh, '-|'); |
||||
die "$!" unless defined $pid; |
||||
if (!$pid) { |
||||
exec('git-var', $var) or die "$!"; |
||||
} |
||||
my ($val) = <$fh>; |
||||
close $fh or die "$!"; |
||||
chomp($val); |
||||
return $val; |
||||
} |
||||
|
||||
sub gitvar_name { |
||||
my ($name) = @_; |
||||
my $val = gitvar($name); |
||||
my @field = split(/\s+/, $val); |
||||
return join(' ', @field[0...(@field-4)]); |
||||
} |
||||
|
||||
sub open_pipe { |
||||
if ($^O eq '##INSERT_ACTIVESTATE_STRING_HERE##') { |
||||
return open_pipe_activestate(@_); |
||||
} else { |
||||
return open_pipe_normal(@_); |
||||
} |
||||
} |
||||
|
||||
sub open_pipe_activestate { |
||||
tie *fh, "Git::ActiveStatePipe", @_; |
||||
return *fh; |
||||
} |
||||
|
||||
sub open_pipe_normal { |
||||
my (@execlist) = @_; |
||||
|
||||
my $pid = open my $kid, "-|"; |
||||
defined $pid or die "Cannot fork: $!"; |
||||
|
||||
unless ($pid) { |
||||
exec @execlist; |
||||
die "Cannot exec @execlist: $!"; |
||||
} |
||||
|
||||
return $kid; |
||||
} |
||||
|
||||
package Git::ActiveStatePipe; |
||||
use strict; |
||||
|
||||
sub TIEHANDLE { |
||||
my ($class, @params) = @_; |
||||
my $cmdline = join " ", @params; |
||||
my @data = qx{$cmdline}; |
||||
bless { i => 0, data => \@data }, $class; |
||||
} |
||||
|
||||
sub READLINE { |
||||
my $self = shift; |
||||
if ($self->{i} >= scalar @{$self->{data}}) { |
||||
return undef; |
||||
} |
||||
return $self->{'data'}->[ $self->{i}++ ]; |
||||
} |
||||
|
||||
sub CLOSE { |
||||
my $self = shift; |
||||
delete $self->{data}; |
||||
delete $self->{i}; |
||||
} |
||||
|
||||
sub EOF { |
||||
my $self = shift; |
||||
return ($self->{i} >= scalar @{$self->{data}}); |
||||
} |
@ -0,0 +1,70 @@
@@ -0,0 +1,70 @@
|
||||
#!/bin/sh |
||||
|
||||
USAGE='[-f] [-n] [-v] [--] <file>...' |
||||
SUBDIRECTORY_OK='Yes' |
||||
. git-sh-setup |
||||
|
||||
remove_files= |
||||
show_only= |
||||
verbose= |
||||
while : ; do |
||||
case "$1" in |
||||
-f) |
||||
remove_files=true |
||||
;; |
||||
-n) |
||||
show_only=true |
||||
;; |
||||
-v) |
||||
verbose=--verbose |
||||
;; |
||||
--) |
||||
shift; break |
||||
;; |
||||
-*) |
||||
usage |
||||
;; |
||||
*) |
||||
break |
||||
;; |
||||
esac |
||||
shift |
||||
done |
||||
|
||||
# This is typo-proofing. If some paths match and some do not, we want |
||||
# to do nothing. |
||||
case "$#" in |
||||
0) ;; |
||||
*) |
||||
git-ls-files --error-unmatch -- "$@" >/dev/null || { |
||||
echo >&2 "Maybe you misspelled it?" |
||||
exit 1 |
||||
} |
||||
;; |
||||
esac |
||||
|
||||
if test -f "$GIT_DIR/info/exclude" |
||||
then |
||||
git-ls-files -z \ |
||||
--exclude-from="$GIT_DIR/info/exclude" \ |
||||
--exclude-per-directory=.gitignore -- "$@" |
||||
else |
||||
git-ls-files -z \ |
||||
--exclude-per-directory=.gitignore -- "$@" |
||||
fi | |
||||
case "$show_only,$remove_files" in |
||||
true,*) |
||||
xargs -0 echo |
||||
;; |
||||
*,true) |
||||
xargs -0 sh -c " |
||||
while [ \$# -gt 0 ]; do |
||||
file=\$1; shift |
||||
rm -- \"\$file\" && git-update-index --remove $verbose \"\$file\" |
||||
done |
||||
" inline |
||||
;; |
||||
*) |
||||
git-update-index --force-remove $verbose -z --stdin |
||||
;; |
||||
esac |
@ -0,0 +1,48 @@
@@ -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); |
||||
} |
@ -0,0 +1,687 @@
@@ -0,0 +1,687 @@
|
||||
#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; |
||||
} |
||||
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 (!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,56 @@
@@ -0,0 +1,56 @@
|
||||
#ifndef REVISION_H |
||||
#define REVISION_H |
||||
|
||||
#define SEEN (1u<<0) |
||||
#define UNINTERESTING (1u<<1) |
||||
#define TREECHANGE (1u<<2) |
||||
#define SHOWN (1u<<3) |
||||
|
||||
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 |
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
#!/bin/sh |
||||
# |
||||
# Copyright (c) 2006 Carl D. Worth |
||||
# |
||||
|
||||
test_description='git-ls-files test for --error-unmatch option |
||||
|
||||
This test runs git-ls-files --error-unmatch to ensure it correctly |
||||
returns an error when a non-existent path is provided on the command |
||||
line. |
||||
' |
||||
. ./test-lib.sh |
||||
|
||||
touch foo bar |
||||
git-update-index --add foo bar |
||||
git-commit -m "add foo bar" |
||||
|
||||
test_expect_failure \ |
||||
'git-ls-files --error-unmatch should fail with unmatched path.' \ |
||||
'git-ls-files --error-unmatch foo bar-does-not-match' |
||||
|
||||
test_expect_success \ |
||||
'git-ls-files --error-unmatch should succeed eith matched paths.' \ |
||||
'git-ls-files --error-unmatch foo bar' |
||||
|
||||
test_done |
||||
1 |
@ -0,0 +1,60 @@
@@ -0,0 +1,60 @@
|
||||
#!/bin/sh |
||||
# |
||||
# Copyright (c) 2006 Carl D. Worth |
||||
# |
||||
|
||||
test_description='Test of the various options to git-rm.' |
||||
|
||||
. ./test-lib.sh |
||||
|
||||
# Setup some files to be removed, some with funny characters |
||||
touch -- foo bar baz 'space embedded' 'tab embedded' 'newline |
||||
embedded' -q |
||||
git-add -- foo bar baz 'space embedded' 'tab embedded' 'newline |
||||
embedded' -q |
||||
git-commit -m "add files" |
||||
|
||||
test_expect_success \ |
||||
'Pre-check that foo exists and is in index before git-rm foo' \ |
||||
'[ -f foo ] && git-ls-files --error-unmatch foo' |
||||
|
||||
test_expect_success \ |
||||
'Test that git-rm foo succeeds' \ |
||||
'git-rm foo' |
||||
|
||||
test_expect_success \ |
||||
'Post-check that foo exists but is not in index after git-rm foo' \ |
||||
'[ -f foo ] && ! git-ls-files --error-unmatch foo' |
||||
|
||||
test_expect_success \ |
||||
'Pre-check that bar exists and is in index before "git-rm -f bar"' \ |
||||
'[ -f bar ] && git-ls-files --error-unmatch bar' |
||||
|
||||
test_expect_success \ |
||||
'Test that "git-rm -f bar" succeeds' \ |
||||
'git-rm -f bar' |
||||
|
||||
test_expect_success \ |
||||
'Post-check that bar does not exist and is not in index after "git-rm -f bar"' \ |
||||
'! [ -f bar ] && ! git-ls-files --error-unmatch bar' |
||||
|
||||
test_expect_success \ |
||||
'Test that "git-rm -- -q" succeeds (remove a file that looks like an option)' \ |
||||
'git-rm -- -q' |
||||
|
||||
test_expect_success \ |
||||
"Test that \"git-rm -f\" succeeds with embedded space, tab, or newline characters." \ |
||||
"git-rm -f 'space embedded' 'tab embedded' 'newline |
||||
embedded'" |
||||
|
||||
chmod u-w . |
||||
test_expect_failure \ |
||||
'Test that "git-rm -f" fails if its rm fails' \ |
||||
'git-rm -f baz' |
||||
chmod u+w . |
||||
|
||||
test_expect_success \ |
||||
'When the rm in "git-rm -f" fails, it should not remove the file from the index' \ |
||||
'git-ls-files --error-unmatch baz' |
||||
|
||||
test_done |
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
#!/bin/sh |
||||
# |
||||
# Copyright (c) 2006 Carl D. Worth |
||||
# |
||||
|
||||
test_description='Test of git-add, including the -- option.' |
||||
|
||||
. ./test-lib.sh |
||||
|
||||
test_expect_success \ |
||||
'Test of git-add' \ |
||||
'touch foo && git-add foo' |
||||
|
||||
test_expect_success \ |
||||
'Post-check that foo is in the index' \ |
||||
'git-ls-files foo | grep foo' |
||||
|
||||
test_expect_success \ |
||||
'Test that "git-add -- -q" works' \ |
||||
'touch -- -q && git-add -- -q' |
||||
|
||||
test_done |
@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
|
||||
#!/bin/sh |
||||
# |
||||
# Copyright (C) 2006 Carl D. Worth <cworth@cworth.org> |
||||
# |
||||
|
||||
test_description='test git-clone to cleanup after failure |
||||
|
||||
This test covers the fact that if git-clone fails, it should remove |
||||
the directory it created, to avoid the user having to manually |
||||
remove the directory before attempting a clone again.' |
||||
|
||||
. ./test-lib.sh |
||||
|
||||
test_expect_failure \ |
||||
'clone of non-existent source should fail' \ |
||||
'git-clone foo bar' |
||||
|
||||
test_expect_failure \ |
||||
'failed clone should not leave a directory' \ |
||||
'cd bar' |
||||
|
||||
# Need a repo to clone |
||||
test_create_repo foo |
||||
|
||||
# clone doesn't like it if there is no HEAD. Is that a bug? |
||||
(cd foo && touch file && git add file && git commit -m 'add file' >/dev/null 2>&1) |
||||
|
||||
test_expect_success \ |
||||
'clone should work now that source exists' \ |
||||
'git-clone foo bar' |
||||
|
||||
test_expect_success \ |
||||
'successfull clone must leave the directory' \ |
||||
'cd bar' |
||||
|
||||
test_done |
@ -1,462 +0,0 @@
@@ -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 |
Loading…
Reference in new issue