|
|
|
#include "cache.h"
|
|
|
|
#include "refs.h"
|
|
|
|
#include "tag.h"
|
|
|
|
#include "commit.h"
|
|
|
|
#include "tree.h"
|
|
|
|
#include "blob.h"
|
|
|
|
#include "epoch.h"
|
|
|
|
|
|
|
|
#define SEEN (1u << 0)
|
|
|
|
#define INTERESTING (1u << 1)
|
|
|
|
#define COUNTED (1u << 2)
|
|
|
|
#define SHOWN (1u << 3)
|
|
|
|
|
|
|
|
static const char rev_list_usage[] =
|
|
|
|
"git-rev-list [OPTION] commit-id <commit-id>\n"
|
|
|
|
" --max-count=nr\n"
|
|
|
|
" --max-age=epoch\n"
|
|
|
|
" --min-age=epoch\n"
|
|
|
|
" --parents\n"
|
|
|
|
" --bisect\n"
|
|
|
|
" --objects\n"
|
|
|
|
" --unpacked\n"
|
|
|
|
" --header\n"
|
|
|
|
" --pretty\n"
|
|
|
|
" --no-merges\n"
|
|
|
|
" --merge-order [ --show-breaks ]\n"
|
|
|
|
" --topo-order";
|
|
|
|
|
|
|
|
static int unpacked = 0;
|
|
|
|
static int bisect_list = 0;
|
|
|
|
static int tag_objects = 0;
|
|
|
|
static int tree_objects = 0;
|
|
|
|
static int blob_objects = 0;
|
|
|
|
static int verbose_header = 0;
|
|
|
|
static int show_parents = 0;
|
|
|
|
static int hdr_termination = 0;
|
|
|
|
static const char *commit_prefix = "";
|
|
|
|
static unsigned long max_age = -1;
|
|
|
|
static unsigned long min_age = -1;
|
|
|
|
static int max_count = -1;
|
|
|
|
static enum cmit_fmt commit_format = CMIT_FMT_RAW;
|
|
|
|
static int merge_order = 0;
|
|
|
|
static int show_breaks = 0;
|
|
|
|
static int stop_traversal = 0;
|
|
|
|
static int topo_order = 0;
|
|
|
|
static int no_merges = 0;
|
|
|
|
|
|
|
|
static void show_commit(struct commit *commit)
|
|
|
|
{
|
|
|
|
commit->object.flags |= SHOWN;
|
|
|
|
if (show_breaks) {
|
|
|
|
commit_prefix = "| ";
|
|
|
|
if (commit->object.flags & DISCONTINUITY) {
|
|
|
|
commit_prefix = "^ ";
|
|
|
|
} else if (commit->object.flags & BOUNDARY) {
|
|
|
|
commit_prefix = "= ";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
printf("%s%s", commit_prefix, sha1_to_hex(commit->object.sha1));
|
|
|
|
if (show_parents) {
|
|
|
|
struct commit_list *parents = commit->parents;
|
|
|
|
while (parents) {
|
|
|
|
printf(" %s", sha1_to_hex(parents->item->object.sha1));
|
|
|
|
parents = parents->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (commit_format == CMIT_FMT_ONELINE)
|
|
|
|
putchar(' ');
|
|
|
|
else
|
|
|
|
putchar('\n');
|
|
|
|
|
|
|
|
if (verbose_header) {
|
|
|
|
static char pretty_header[16384];
|
|
|
|
pretty_print_commit(commit_format, commit->buffer, ~0, pretty_header, sizeof(pretty_header));
|
|
|
|
printf("%s%c", pretty_header, hdr_termination);
|
|
|
|
}
|
|
|
|
fflush(stdout);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int filter_commit(struct commit * commit)
|
|
|
|
{
|
|
|
|
if (stop_traversal && (commit->object.flags & BOUNDARY))
|
|
|
|
return STOP;
|
|
|
|
if (commit->object.flags & (UNINTERESTING|SHOWN))
|
|
|
|
return CONTINUE;
|
|
|
|
if (min_age != -1 && (commit->date > min_age))
|
|
|
|
return CONTINUE;
|
|
|
|
if (max_age != -1 && (commit->date < max_age)) {
|
|
|
|
stop_traversal=1;
|
|
|
|
return CONTINUE;
|
|
|
|
}
|
|
|
|
if (max_count != -1 && !max_count--)
|
|
|
|
return STOP;
|
|
|
|
if (no_merges && (commit->parents && commit->parents->next))
|
|
|
|
return CONTINUE;
|
|
|
|
return DO;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int process_commit(struct commit * commit)
|
|
|
|
{
|
|
|
|
int action=filter_commit(commit);
|
|
|
|
|
|
|
|
if (action == STOP) {
|
|
|
|
return STOP;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (action == CONTINUE) {
|
|
|
|
return CONTINUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
show_commit(commit);
|
|
|
|
|
|
|
|
return CONTINUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct object_list **add_object(struct object *obj, struct object_list **p, const char *name)
|
|
|
|
{
|
|
|
|
struct object_list *entry = xmalloc(sizeof(*entry));
|
|
|
|
entry->item = obj;
|
|
|
|
entry->next = *p;
|
|
|
|
entry->name = name;
|
|
|
|
*p = entry;
|
|
|
|
return &entry->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct object_list **process_blob(struct blob *blob, struct object_list **p, const char *name)
|
|
|
|
{
|
|
|
|
struct object *obj = &blob->object;
|
|
|
|
|
|
|
|
if (!blob_objects)
|
|
|
|
return p;
|
|
|
|
if (obj->flags & (UNINTERESTING | SEEN))
|
|
|
|
return p;
|
|
|
|
obj->flags |= SEEN;
|
|
|
|
return add_object(obj, p, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct object_list **process_tree(struct tree *tree, struct object_list **p, const char *name)
|
|
|
|
{
|
|
|
|
struct object *obj = &tree->object;
|
|
|
|
struct tree_entry_list *entry;
|
|
|
|
|
|
|
|
if (!tree_objects)
|
|
|
|
return p;
|
|
|
|
if (obj->flags & (UNINTERESTING | SEEN))
|
|
|
|
return p;
|
|
|
|
if (parse_tree(tree) < 0)
|
|
|
|
die("bad tree object %s", sha1_to_hex(obj->sha1));
|
|
|
|
obj->flags |= SEEN;
|
|
|
|
p = add_object(obj, p, name);
|
|
|
|
entry = tree->entries;
|
|
|
|
tree->entries = NULL;
|
|
|
|
while (entry) {
|
|
|
|
struct tree_entry_list *next = entry->next;
|
|
|
|
if (entry->directory)
|
|
|
|
p = process_tree(entry->item.tree, p, entry->name);
|
|
|
|
else
|
|
|
|
p = process_blob(entry->item.blob, p, entry->name);
|
|
|
|
free(entry);
|
|
|
|
entry = next;
|
|
|
|
}
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct object_list *pending_objects = NULL;
|
|
|
|
|
|
|
|
static void show_commit_list(struct commit_list *list)
|
|
|
|
{
|
|
|
|
struct object_list *objects = NULL, **p = &objects, *pending;
|
|
|
|
while (list) {
|
|
|
|
struct commit *commit = pop_most_recent_commit(&list, SEEN);
|
|
|
|
|
|
|
|
p = process_tree(commit->tree, p, "");
|
|
|
|
if (process_commit(commit) == STOP)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
for (pending = pending_objects; pending; pending = pending->next) {
|
|
|
|
struct object *obj = pending->item;
|
|
|
|
const char *name = pending->name;
|
|
|
|
if (obj->flags & (UNINTERESTING | SEEN))
|
|
|
|
continue;
|
|
|
|
if (obj->type == tag_type) {
|
|
|
|
obj->flags |= SEEN;
|
|
|
|
p = add_object(obj, p, name);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (obj->type == tree_type) {
|
|
|
|
p = process_tree((struct tree *)obj, p, name);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (obj->type == blob_type) {
|
|
|
|
p = process_blob((struct blob *)obj, p, name);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name);
|
|
|
|
}
|
|
|
|
while (objects) {
|
|
|
|
/* An object with name "foo\n0000000000000000000000000000000000000000"
|
|
|
|
* can be used confuse downstream git-pack-objects very badly.
|
|
|
|
*/
|
|
|
|
const char *ep = strchr(objects->name, '\n');
|
|
|
|
if (ep) {
|
|
|
|
printf("%s %.*s\n", sha1_to_hex(objects->item->sha1),
|
|
|
|
(int) (ep - objects->name),
|
|
|
|
objects->name);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
printf("%s %s\n", sha1_to_hex(objects->item->sha1), objects->name);
|
|
|
|
objects = objects->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void mark_blob_uninteresting(struct blob *blob)
|
|
|
|
{
|
|
|
|
if (!blob_objects)
|
|
|
|
return;
|
|
|
|
if (blob->object.flags & UNINTERESTING)
|
|
|
|
return;
|
|
|
|
blob->object.flags |= UNINTERESTING;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void mark_tree_uninteresting(struct tree *tree)
|
|
|
|
{
|
|
|
|
struct object *obj = &tree->object;
|
|
|
|
struct tree_entry_list *entry;
|
|
|
|
|
|
|
|
if (!tree_objects)
|
|
|
|
return;
|
|
|
|
if (obj->flags & UNINTERESTING)
|
|
|
|
return;
|
|
|
|
obj->flags |= UNINTERESTING;
|
|
|
|
if (!has_sha1_file(obj->sha1))
|
|
|
|
return;
|
|
|
|
if (parse_tree(tree) < 0)
|
|
|
|
die("bad tree %s", sha1_to_hex(obj->sha1));
|
|
|
|
entry = tree->entries;
|
|
|
|
tree->entries = NULL;
|
|
|
|
while (entry) {
|
|
|
|
struct tree_entry_list *next = entry->next;
|
|
|
|
if (entry->directory)
|
|
|
|
mark_tree_uninteresting(entry->item.tree);
|
|
|
|
else
|
|
|
|
mark_blob_uninteresting(entry->item.blob);
|
|
|
|
free(entry);
|
|
|
|
entry = next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void mark_parents_uninteresting(struct commit *commit)
|
|
|
|
{
|
|
|
|
struct commit_list *parents = commit->parents;
|
|
|
|
|
|
|
|
while (parents) {
|
|
|
|
struct commit *commit = parents->item;
|
|
|
|
commit->object.flags |= UNINTERESTING;
|
|
|
|
|
[PATCH] Fix interesting git-rev-list corner case
This corner-case was triggered by a kernel commit that was not in date
order, due to a misconfigured time zone that made the commit appear three
hours older than it was.
That caused git-rev-list to traverse the commit tree in a non-obvious
order, and made it parse several of the _parents_ of the misplaced commit
before it actually parsed the commit itself. That's fine, but it meant
that the grandparents of the commit didn't get marked uninteresting,
because they had been reached through an "interesting" branch.
The reason was that "mark_parents_uninteresting()" (which is supposed to
mark all existing parents as being uninteresting - duh) didn't actually
traverse more than one level down the parent chain.
NORMALLY this is fine, since with the date-based traversal order,
grandparents won't ever even have been looked at before their parents (so
traversing the chain down isn't needed, because the next time around when
we pick out the parent we'll mark _its_ parents uninteresting), but since
we'd gotten out of order, we'd already seen the parent and thus never got
around to mark the grandparents.
Anyway, the fix is simple. Just traverse parent chains recursively.
Normally the chain won't even exist (since the parent hasn't been parsed
yet), so this is not actually going to trigger except in this strange
corner-case.
Add a comment to the simple one-liner, since this was a bit subtle, and I
had to really think things through to understand how it could happen.
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
20 years ago
|
|
|
/*
|
|
|
|
* Normally we haven't parsed the parent
|
|
|
|
* yet, so we won't have a parent of a parent
|
|
|
|
* here. However, it may turn out that we've
|
|
|
|
* reached this commit some other way (where it
|
|
|
|
* wasn't uninteresting), in which case we need
|
|
|
|
* to mark its parents recursively too..
|
|
|
|
*/
|
|
|
|
if (commit->parents)
|
|
|
|
mark_parents_uninteresting(commit);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* A missing commit is ok iff its parent is marked
|
|
|
|
* uninteresting.
|
|
|
|
*
|
|
|
|
* We just mark such a thing parsed, so that when
|
|
|
|
* it is popped next time around, we won't be trying
|
|
|
|
* to parse it and get an error.
|
|
|
|
*/
|
|
|
|
if (!has_sha1_file(commit->object.sha1))
|
|
|
|
commit->object.parsed = 1;
|
|
|
|
parents = parents->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int everybody_uninteresting(struct commit_list *orig)
|
|
|
|
{
|
|
|
|
struct commit_list *list = orig;
|
|
|
|
while (list) {
|
|
|
|
struct commit *commit = list->item;
|
|
|
|
list = list->next;
|
|
|
|
if (commit->object.flags & UNINTERESTING)
|
|
|
|
continue;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This is a truly stupid algorithm, but it's only
|
|
|
|
* used for bisection, and we just don't care enough.
|
|
|
|
*
|
|
|
|
* We care just barely enough to avoid recursing for
|
|
|
|
* non-merge entries.
|
|
|
|
*/
|
|
|
|
static int count_distance(struct commit_list *entry)
|
|
|
|
{
|
|
|
|
int nr = 0;
|
|
|
|
|
|
|
|
while (entry) {
|
|
|
|
struct commit *commit = entry->item;
|
|
|
|
struct commit_list *p;
|
|
|
|
|
|
|
|
if (commit->object.flags & (UNINTERESTING | COUNTED))
|
|
|
|
break;
|
|
|
|
nr++;
|
|
|
|
commit->object.flags |= COUNTED;
|
|
|
|
p = commit->parents;
|
|
|
|
entry = p;
|
|
|
|
if (p) {
|
|
|
|
p = p->next;
|
|
|
|
while (p) {
|
|
|
|
nr += count_distance(p);
|
|
|
|
p = p->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nr;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void clear_distance(struct commit_list *list)
|
|
|
|
{
|
|
|
|
while (list) {
|
|
|
|
struct commit *commit = list->item;
|
|
|
|
commit->object.flags &= ~COUNTED;
|
|
|
|
list = list->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct commit_list *find_bisection(struct commit_list *list)
|
|
|
|
{
|
|
|
|
int nr, closest;
|
|
|
|
struct commit_list *p, *best;
|
|
|
|
|
|
|
|
nr = 0;
|
|
|
|
p = list;
|
|
|
|
while (p) {
|
|
|
|
nr++;
|
|
|
|
p = p->next;
|
|
|
|
}
|
|
|
|
closest = 0;
|
|
|
|
best = list;
|
|
|
|
|
|
|
|
p = list;
|
|
|
|
while (p) {
|
|
|
|
int distance = count_distance(p);
|
|
|
|
clear_distance(list);
|
|
|
|
if (nr - distance < distance)
|
|
|
|
distance = nr - distance;
|
|
|
|
if (distance > closest) {
|
|
|
|
best = p;
|
|
|
|
closest = distance;
|
|
|
|
}
|
|
|
|
p = p->next;
|
|
|
|
}
|
|
|
|
if (best)
|
|
|
|
best->next = NULL;
|
|
|
|
return best;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void mark_edges_uninteresting(struct commit_list *list)
|
|
|
|
{
|
|
|
|
for ( ; list; list = list->next) {
|
|
|
|
struct commit_list *parents = list->item->parents;
|
|
|
|
|
|
|
|
for ( ; parents; parents = parents->next) {
|
|
|
|
struct commit *commit = parents->item;
|
|
|
|
if (commit->object.flags & UNINTERESTING)
|
|
|
|
mark_tree_uninteresting(commit->tree);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct commit_list *limit_list(struct commit_list *list)
|
|
|
|
{
|
|
|
|
struct commit_list *newlist = NULL;
|
|
|
|
struct commit_list **p = &newlist;
|
|
|
|
while (list) {
|
|
|
|
struct commit *commit = pop_most_recent_commit(&list, SEEN);
|
|
|
|
struct object *obj = &commit->object;
|
|
|
|
|
|
|
|
if (max_age != -1 && (commit->date < max_age))
|
|
|
|
obj->flags |= UNINTERESTING;
|
|
|
|
if (unpacked && has_sha1_pack(obj->sha1))
|
|
|
|
obj->flags |= UNINTERESTING;
|
git-rev-list: allow arbitrary head selections, use git-rev-tree syntax
This makes git-rev-list use the same command line syntax to mark the
commits as git-rev-tree does, and instead of just allowing a start and
end commit, it allows an arbitrary list of "interesting" and "uninteresting"
commits.
For example, imagine that you had three branches (a, b and c) that you
are interested in, but you don't want to see stuff that already exists
in another persons three releases (x, y and z). You can do
git-rev-list a b c ^x ^y ^z
(order doesn't matter, btw - feel free to put the uninteresting ones
first or otherwise swithc them around), and it will show all the
commits that are reachable from a/b/c but not reachable from x/y/z.
The old syntax "git-rev-list start end" would not be written as
"git-rev-list start ^end", or "git-rev-list ^end start".
There's no limit to the number of heads you can specify (unlike
git-rev-tree, which can handle a maximum of 16 heads).
20 years ago
|
|
|
if (obj->flags & UNINTERESTING) {
|
|
|
|
mark_parents_uninteresting(commit);
|
|
|
|
if (everybody_uninteresting(list))
|
|
|
|
break;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (min_age != -1 && (commit->date > min_age))
|
|
|
|
continue;
|
|
|
|
p = &commit_list_insert(commit, p)->next;
|
|
|
|
}
|
|
|
|
if (tree_objects)
|
|
|
|
mark_edges_uninteresting(newlist);
|
|
|
|
if (bisect_list)
|
|
|
|
newlist = find_bisection(newlist);
|
|
|
|
return newlist;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void add_pending_object(struct object *obj, const char *name)
|
|
|
|
{
|
|
|
|
add_object(obj, &pending_objects, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct commit *get_commit_reference(const char *name, unsigned int flags)
|
|
|
|
{
|
|
|
|
unsigned char sha1[20];
|
|
|
|
struct object *object;
|
|
|
|
|
|
|
|
if (get_sha1(name, sha1))
|
|
|
|
usage(rev_list_usage);
|
|
|
|
object = parse_object(sha1);
|
|
|
|
if (!object)
|
|
|
|
die("bad object %s", name);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Tag object? Look what it points to..
|
|
|
|
*/
|
|
|
|
while (object->type == tag_type) {
|
|
|
|
struct tag *tag = (struct tag *) object;
|
|
|
|
object->flags |= flags;
|
|
|
|
if (tag_objects && !(object->flags & UNINTERESTING))
|
|
|
|
add_pending_object(object, tag->tag);
|
|
|
|
object = parse_object(tag->tagged->sha1);
|
|
|
|
if (!object)
|
|
|
|
die("bad object %s", sha1_to_hex(tag->tagged->sha1));
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Commit object? Just return it, we'll do all the complex
|
|
|
|
* reachability crud.
|
|
|
|
*/
|
|
|
|
if (object->type == commit_type) {
|
|
|
|
struct commit *commit = (struct commit *)object;
|
|
|
|
object->flags |= flags;
|
|
|
|
if (parse_commit(commit) < 0)
|
|
|
|
die("unable to parse commit %s", name);
|
|
|
|
if (flags & UNINTERESTING)
|
|
|
|
mark_parents_uninteresting(commit);
|
|
|
|
return commit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Tree object? Either mark it uniniteresting, or add it
|
|
|
|
* to the list of objects to look at later..
|
|
|
|
*/
|
|
|
|
if (object->type == tree_type) {
|
|
|
|
struct tree *tree = (struct tree *)object;
|
|
|
|
if (!tree_objects)
|
|
|
|
return NULL;
|
|
|
|
if (flags & UNINTERESTING) {
|
|
|
|
mark_tree_uninteresting(tree);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
add_pending_object(object, "");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Blob object? You know the drill by now..
|
|
|
|
*/
|
|
|
|
if (object->type == blob_type) {
|
|
|
|
struct blob *blob = (struct blob *)object;
|
|
|
|
if (!blob_objects)
|
|
|
|
return NULL;
|
|
|
|
if (flags & UNINTERESTING) {
|
|
|
|
mark_blob_uninteresting(blob);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
add_pending_object(object, "");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
die("%s is unknown object", name);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void handle_one_commit(struct commit *com, struct commit_list **lst)
|
|
|
|
{
|
|
|
|
if (!com || com->object.flags & SEEN)
|
|
|
|
return;
|
|
|
|
com->object.flags |= SEEN;
|
|
|
|
commit_list_insert(com, lst);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* for_each_ref() callback does not allow user data -- Yuck. */
|
|
|
|
static struct commit_list **global_lst;
|
|
|
|
|
|
|
|
static int include_one_commit(const char *path, const unsigned char *sha1)
|
|
|
|
{
|
|
|
|
struct commit *com = get_commit_reference(path, 0);
|
|
|
|
handle_one_commit(com, global_lst);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void handle_all(struct commit_list **lst)
|
|
|
|
{
|
|
|
|
global_lst = lst;
|
|
|
|
for_each_ref(include_one_commit);
|
|
|
|
global_lst = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, char **argv)
|
|
|
|
{
|
|
|
|
struct commit_list *list = NULL;
|
git-rev-list: allow arbitrary head selections, use git-rev-tree syntax
This makes git-rev-list use the same command line syntax to mark the
commits as git-rev-tree does, and instead of just allowing a start and
end commit, it allows an arbitrary list of "interesting" and "uninteresting"
commits.
For example, imagine that you had three branches (a, b and c) that you
are interested in, but you don't want to see stuff that already exists
in another persons three releases (x, y and z). You can do
git-rev-list a b c ^x ^y ^z
(order doesn't matter, btw - feel free to put the uninteresting ones
first or otherwise swithc them around), and it will show all the
commits that are reachable from a/b/c but not reachable from x/y/z.
The old syntax "git-rev-list start end" would not be written as
"git-rev-list start ^end", or "git-rev-list ^end start".
There's no limit to the number of heads you can specify (unlike
git-rev-tree, which can handle a maximum of 16 heads).
20 years ago
|
|
|
int i, limited = 0;
|
|
|
|
|
|
|
|
setup_git_directory();
|
|
|
|
for (i = 1 ; i < argc; i++) {
|
git-rev-list: allow arbitrary head selections, use git-rev-tree syntax
This makes git-rev-list use the same command line syntax to mark the
commits as git-rev-tree does, and instead of just allowing a start and
end commit, it allows an arbitrary list of "interesting" and "uninteresting"
commits.
For example, imagine that you had three branches (a, b and c) that you
are interested in, but you don't want to see stuff that already exists
in another persons three releases (x, y and z). You can do
git-rev-list a b c ^x ^y ^z
(order doesn't matter, btw - feel free to put the uninteresting ones
first or otherwise swithc them around), and it will show all the
commits that are reachable from a/b/c but not reachable from x/y/z.
The old syntax "git-rev-list start end" would not be written as
"git-rev-list start ^end", or "git-rev-list ^end start".
There's no limit to the number of heads you can specify (unlike
git-rev-tree, which can handle a maximum of 16 heads).
20 years ago
|
|
|
int flags;
|
|
|
|
char *arg = argv[i];
|
|
|
|
char *dotdot;
|
git-rev-list: allow arbitrary head selections, use git-rev-tree syntax
This makes git-rev-list use the same command line syntax to mark the
commits as git-rev-tree does, and instead of just allowing a start and
end commit, it allows an arbitrary list of "interesting" and "uninteresting"
commits.
For example, imagine that you had three branches (a, b and c) that you
are interested in, but you don't want to see stuff that already exists
in another persons three releases (x, y and z). You can do
git-rev-list a b c ^x ^y ^z
(order doesn't matter, btw - feel free to put the uninteresting ones
first or otherwise swithc them around), and it will show all the
commits that are reachable from a/b/c but not reachable from x/y/z.
The old syntax "git-rev-list start end" would not be written as
"git-rev-list start ^end", or "git-rev-list ^end start".
There's no limit to the number of heads you can specify (unlike
git-rev-tree, which can handle a maximum of 16 heads).
20 years ago
|
|
|
struct commit *commit;
|
|
|
|
|
|
|
|
if (!strncmp(arg, "--max-count=", 12)) {
|
|
|
|
max_count = atoi(arg + 12);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!strncmp(arg, "--max-age=", 10)) {
|
|
|
|
max_age = atoi(arg + 10);
|
|
|
|
limited = 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!strncmp(arg, "--min-age=", 10)) {
|
|
|
|
min_age = atoi(arg + 10);
|
|
|
|
limited = 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!strcmp(arg, "--header")) {
|
|
|
|
verbose_header = 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!strncmp(arg, "--pretty", 8)) {
|
|
|
|
commit_format = get_commit_format(arg+8);
|
|
|
|
verbose_header = 1;
|
|
|
|
hdr_termination = '\n';
|
|
|
|
if (commit_format == CMIT_FMT_ONELINE)
|
|
|
|
commit_prefix = "";
|
|
|
|
else
|
|
|
|
commit_prefix = "commit ";
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!strncmp(arg, "--no-merges", 11)) {
|
|
|
|
no_merges = 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!strcmp(arg, "--parents")) {
|
|
|
|
show_parents = 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!strcmp(arg, "--bisect")) {
|
|
|
|
bisect_list = 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!strcmp(arg, "--all")) {
|
|
|
|
handle_all(&list);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!strcmp(arg, "--objects")) {
|
|
|
|
tag_objects = 1;
|
|
|
|
tree_objects = 1;
|
|
|
|
blob_objects = 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!strcmp(arg, "--unpacked")) {
|
|
|
|
unpacked = 1;
|
|
|
|
limited = 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!strcmp(arg, "--merge-order")) {
|
|
|
|
merge_order = 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!strcmp(arg, "--show-breaks")) {
|
|
|
|
show_breaks = 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!strcmp(arg, "--topo-order")) {
|
|
|
|
topo_order = 1;
|
|
|
|
limited = 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (show_breaks && !merge_order)
|
|
|
|
usage(rev_list_usage);
|
|
|
|
|
git-rev-list: allow arbitrary head selections, use git-rev-tree syntax
This makes git-rev-list use the same command line syntax to mark the
commits as git-rev-tree does, and instead of just allowing a start and
end commit, it allows an arbitrary list of "interesting" and "uninteresting"
commits.
For example, imagine that you had three branches (a, b and c) that you
are interested in, but you don't want to see stuff that already exists
in another persons three releases (x, y and z). You can do
git-rev-list a b c ^x ^y ^z
(order doesn't matter, btw - feel free to put the uninteresting ones
first or otherwise swithc them around), and it will show all the
commits that are reachable from a/b/c but not reachable from x/y/z.
The old syntax "git-rev-list start end" would not be written as
"git-rev-list start ^end", or "git-rev-list ^end start".
There's no limit to the number of heads you can specify (unlike
git-rev-tree, which can handle a maximum of 16 heads).
20 years ago
|
|
|
flags = 0;
|
|
|
|
dotdot = strstr(arg, "..");
|
|
|
|
if (dotdot) {
|
|
|
|
char *next = dotdot + 2;
|
|
|
|
struct commit *exclude = NULL;
|
|
|
|
struct commit *include = NULL;
|
|
|
|
*dotdot = 0;
|
|
|
|
if (!*next)
|
|
|
|
next = "HEAD";
|
|
|
|
exclude = get_commit_reference(arg, UNINTERESTING);
|
|
|
|
include = get_commit_reference(next, 0);
|
|
|
|
if (exclude && include) {
|
|
|
|
limited = 1;
|
|
|
|
handle_one_commit(exclude, &list);
|
|
|
|
handle_one_commit(include, &list);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
*dotdot = '.';
|
|
|
|
}
|
git-rev-list: allow arbitrary head selections, use git-rev-tree syntax
This makes git-rev-list use the same command line syntax to mark the
commits as git-rev-tree does, and instead of just allowing a start and
end commit, it allows an arbitrary list of "interesting" and "uninteresting"
commits.
For example, imagine that you had three branches (a, b and c) that you
are interested in, but you don't want to see stuff that already exists
in another persons three releases (x, y and z). You can do
git-rev-list a b c ^x ^y ^z
(order doesn't matter, btw - feel free to put the uninteresting ones
first or otherwise swithc them around), and it will show all the
commits that are reachable from a/b/c but not reachable from x/y/z.
The old syntax "git-rev-list start end" would not be written as
"git-rev-list start ^end", or "git-rev-list ^end start".
There's no limit to the number of heads you can specify (unlike
git-rev-tree, which can handle a maximum of 16 heads).
20 years ago
|
|
|
if (*arg == '^') {
|
|
|
|
flags = UNINTERESTING;
|
|
|
|
arg++;
|
|
|
|
limited = 1;
|
|
|
|
}
|
|
|
|
commit = get_commit_reference(arg, flags);
|
|
|
|
handle_one_commit(commit, &list);
|
|
|
|
}
|
|
|
|
|
[PATCH] Avoid wasting memory in git-rev-list
As pointed out on the list, git-rev-list can use a lot of memory.
One low-hanging fruit is to free the commit buffer for commits that we
parse. By default, parse_commit() will save away the buffer, since a lot
of cases do want it, and re-reading it continually would be unnecessary.
However, in many cases the buffer isn't actually necessary and saving it
just wastes memory.
We could just free the buffer ourselves, but especially in git-rev-list,
we actually end up using the helper functions that automatically add
parent commits to the commit lists, so we don't actually control the
commit parsing directly.
Instead, just make this behaviour of "parse_commit()" a global flag.
Maybe this is a bit tasteless, but it's very simple, and it makes a
noticable difference in memory usage.
Before the change:
[torvalds@g5 linux]$ /usr/bin/time git-rev-list v2.6.12..HEAD > /dev/null
0.26user 0.02system 0:00.28elapsed 99%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+3714minor)pagefaults 0swaps
after the change:
[torvalds@g5 linux]$ /usr/bin/time git-rev-list v2.6.12..HEAD > /dev/null
0.26user 0.00system 0:00.27elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+2433minor)pagefaults 0swaps
note how the minor faults have decreased from 3714 pages to 2433 pages.
That's all due to the fewer anonymous pages allocated to hold the comment
buffers and their metadata.
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
20 years ago
|
|
|
save_commit_buffer = verbose_header;
|
|
|
|
track_object_refs = 0;
|
[PATCH] Avoid wasting memory in git-rev-list
As pointed out on the list, git-rev-list can use a lot of memory.
One low-hanging fruit is to free the commit buffer for commits that we
parse. By default, parse_commit() will save away the buffer, since a lot
of cases do want it, and re-reading it continually would be unnecessary.
However, in many cases the buffer isn't actually necessary and saving it
just wastes memory.
We could just free the buffer ourselves, but especially in git-rev-list,
we actually end up using the helper functions that automatically add
parent commits to the commit lists, so we don't actually control the
commit parsing directly.
Instead, just make this behaviour of "parse_commit()" a global flag.
Maybe this is a bit tasteless, but it's very simple, and it makes a
noticable difference in memory usage.
Before the change:
[torvalds@g5 linux]$ /usr/bin/time git-rev-list v2.6.12..HEAD > /dev/null
0.26user 0.02system 0:00.28elapsed 99%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+3714minor)pagefaults 0swaps
after the change:
[torvalds@g5 linux]$ /usr/bin/time git-rev-list v2.6.12..HEAD > /dev/null
0.26user 0.00system 0:00.27elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+2433minor)pagefaults 0swaps
note how the minor faults have decreased from 3714 pages to 2433 pages.
That's all due to the fewer anonymous pages allocated to hold the comment
buffers and their metadata.
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
20 years ago
|
|
|
|
|
|
|
if (!merge_order) {
|
|
|
|
sort_by_date(&list);
|
Optimize common case of git-rev-list
I took a look at webgit, and it looks like at least for the "projects"
page, the most common operation ends up being basically
git-rev-list --header --parents --max-count=1 HEAD
Now, the thing is, the way "git-rev-list" works, it always keeps on
popping the parents and parsing them in order to build the list of
parents, and it turns out that even though we just want a single commit,
git-rev-list will invariably look up _three_ generations of commits.
It will parse:
- the commit we want (it obviously needs this)
- it's parent(s) as part of the "pop_most_recent_commit()" logic
- it will then pop one of the parents before it notices that it doesn't
need any more
- and as part of popping the parent, it will parse the grandparent (again
due to "pop_most_recent_commit()".
Now, I've strace'd it, and it really is pretty efficient on the whole, but
if things aren't nicely cached, and with long-latency IO, doing those two
extra objects (at a minimum - if the parent is a merge it will be more) is
just wasted time, and potentially a lot of it.
So here's a quick special-case for the trivial case of "just one commit,
and no date-limits or other special rules".
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
19 years ago
|
|
|
if (list && !limited && max_count == 1 &&
|
|
|
|
!tag_objects && !tree_objects && !blob_objects) {
|
|
|
|
show_commit(list->item);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (limited)
|
|
|
|
list = limit_list(list);
|
|
|
|
if (topo_order)
|
|
|
|
sort_in_topological_order(&list);
|
|
|
|
show_commit_list(list);
|
|
|
|
} else {
|
|
|
|
#ifndef NO_OPENSSL
|
|
|
|
if (sort_list_in_merge_order(list, &process_commit)) {
|
|
|
|
die("merge order sort failed\n");
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
die("merge order sort unsupported, OpenSSL not linked");
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|