|
|
|
#include "cache.h"
|
|
|
|
#include "refs.h"
|
|
|
|
#include "tag.h"
|
|
|
|
#include "commit.h"
|
|
|
|
#include "tree.h"
|
|
|
|
#include "blob.h"
|
|
|
|
#include "diff.h"
|
|
|
|
#include "revision.h"
|
|
|
|
|
|
|
|
/* bits #0-4 in revision.h */
|
|
|
|
|
|
|
|
#define COUNTED (1u<<5)
|
|
|
|
|
|
|
|
static const char rev_list_usage[] =
|
|
|
|
"git-rev-list [OPTION] <commit-id>... [ -- paths... ]\n"
|
|
|
|
" limiting output:\n"
|
|
|
|
" --max-count=nr\n"
|
|
|
|
" --max-age=epoch\n"
|
|
|
|
" --min-age=epoch\n"
|
|
|
|
" --sparse\n"
|
|
|
|
" --no-merges\n"
|
|
|
|
" --remove-empty\n"
|
|
|
|
" --all\n"
|
|
|
|
" ordering output:\n"
|
|
|
|
" --topo-order\n"
|
|
|
|
" --date-order\n"
|
|
|
|
" formatting output:\n"
|
|
|
|
" --parents\n"
|
|
|
|
" --objects | --objects-edge\n"
|
|
|
|
" --unpacked\n"
|
|
|
|
" --header | --pretty\n"
|
|
|
|
" --abbrev=nr | --no-abbrev\n"
|
|
|
|
" special purpose:\n"
|
|
|
|
" --bisect"
|
|
|
|
;
|
|
|
|
|
|
|
|
struct rev_info revs;
|
|
|
|
|
|
|
|
static int bisect_list = 0;
|
|
|
|
static int verbose_header = 0;
|
|
|
|
static int abbrev = DEFAULT_ABBREV;
|
|
|
|
static int show_parents = 0;
|
|
|
|
static int hdr_termination = 0;
|
|
|
|
static const char *commit_prefix = "";
|
|
|
|
static enum cmit_fmt commit_format = CMIT_FMT_RAW;
|
|
|
|
|
|
|
|
static void show_commit(struct commit *commit)
|
|
|
|
{
|
|
|
|
printf("%s%s", commit_prefix, sha1_to_hex(commit->object.sha1));
|
|
|
|
if (show_parents) {
|
|
|
|
struct commit_list *parents = commit->parents;
|
|
|
|
while (parents) {
|
|
|
|
struct object *o = &(parents->item->object);
|
|
|
|
parents = parents->next;
|
|
|
|
if (o->flags & TMP_MARK)
|
|
|
|
continue;
|
|
|
|
printf(" %s", sha1_to_hex(o->sha1));
|
|
|
|
o->flags |= TMP_MARK;
|
|
|
|
}
|
|
|
|
/* TMP_MARK is a general purpose flag that can
|
|
|
|
* be used locally, but the user should clean
|
|
|
|
* things up after it is done with them.
|
|
|
|
*/
|
|
|
|
for (parents = commit->parents;
|
|
|
|
parents;
|
|
|
|
parents = parents->next)
|
|
|
|
parents->item->object.flags &= ~TMP_MARK;
|
|
|
|
}
|
|
|
|
if (commit_format == CMIT_FMT_ONELINE)
|
|
|
|
putchar(' ');
|
|
|
|
else
|
|
|
|
putchar('\n');
|
|
|
|
|
|
|
|
if (verbose_header) {
|
|
|
|
static char pretty_header[16384];
|
|
|
|
pretty_print_commit(commit_format, commit, ~0, pretty_header, sizeof(pretty_header), abbrev);
|
|
|
|
printf("%s%c", pretty_header, hdr_termination);
|
|
|
|
}
|
|
|
|
fflush(stdout);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct object_list **process_blob(struct blob *blob,
|
|
|
|
struct object_list **p,
|
|
|
|
struct name_path *path,
|
|
|
|
const char *name)
|
|
|
|
{
|
|
|
|
struct object *obj = &blob->object;
|
|
|
|
|
|
|
|
if (!revs.blob_objects)
|
|
|
|
return p;
|
|
|
|
if (obj->flags & (UNINTERESTING | SEEN))
|
|
|
|
return p;
|
|
|
|
obj->flags |= SEEN;
|
|
|
|
return add_object(obj, p, path, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct object_list **process_tree(struct tree *tree,
|
|
|
|
struct object_list **p,
|
|
|
|
struct name_path *path,
|
|
|
|
const char *name)
|
|
|
|
{
|
|
|
|
struct object *obj = &tree->object;
|
|
|
|
struct tree_entry_list *entry;
|
|
|
|
struct name_path me;
|
|
|
|
|
|
|
|
if (!revs.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, path, name);
|
|
|
|
me.up = path;
|
|
|
|
me.elem = name;
|
|
|
|
me.elem_len = strlen(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, &me, entry->name);
|
|
|
|
else
|
|
|
|
p = process_blob(entry->item.blob, p, &me, entry->name);
|
|
|
|
free(entry);
|
|
|
|
entry = next;
|
|
|
|
}
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void show_commit_list(struct rev_info *revs)
|
|
|
|
{
|
|
|
|
struct commit *commit;
|
|
|
|
struct object_list *objects = NULL, **p = &objects, *pending;
|
|
|
|
|
|
|
|
while ((commit = get_revision(revs)) != NULL) {
|
|
|
|
p = process_tree(commit->tree, p, NULL, "");
|
|
|
|
show_commit(commit);
|
|
|
|
}
|
|
|
|
for (pending = revs->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, NULL, name);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (obj->type == tree_type) {
|
|
|
|
p = process_tree((struct tree *)obj, p, NULL, name);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (obj->type == blob_type) {
|
|
|
|
p = process_blob((struct blob *)obj, p, NULL, name);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name);
|
|
|
|
}
|
|
|
|
while (objects) {
|
|
|
|
/* An object with name "foo\n0000000..." can be used to
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
if (!revs.paths || (commit->object.flags & TREECHANGE))
|
|
|
|
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) {
|
|
|
|
if (!revs.paths || (p->item->object.flags & TREECHANGE))
|
|
|
|
nr++;
|
|
|
|
p = p->next;
|
|
|
|
}
|
|
|
|
closest = 0;
|
|
|
|
best = list;
|
|
|
|
|
|
|
|
for (p = list; p; p = p->next) {
|
|
|
|
int distance;
|
|
|
|
|
|
|
|
if (revs.paths && !(p->item->object.flags & TREECHANGE))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
distance = count_distance(p);
|
|
|
|
clear_distance(list);
|
|
|
|
if (nr - distance < distance)
|
|
|
|
distance = nr - distance;
|
|
|
|
if (distance > closest) {
|
|
|
|
best = p;
|
|
|
|
closest = distance;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (best)
|
|
|
|
best->next = NULL;
|
|
|
|
return best;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void mark_edge_parents_uninteresting(struct commit *commit)
|
|
|
|
{
|
|
|
|
struct commit_list *parents;
|
|
|
|
|
|
|
|
for (parents = commit->parents; parents; parents = parents->next) {
|
|
|
|
struct commit *parent = parents->item;
|
|
|
|
if (!(parent->object.flags & UNINTERESTING))
|
|
|
|
continue;
|
|
|
|
mark_tree_uninteresting(parent->tree);
|
|
|
|
if (revs.edge_hint && !(parent->object.flags & SHOWN)) {
|
|
|
|
parent->object.flags |= SHOWN;
|
|
|
|
printf("-%s\n", sha1_to_hex(parent->object.sha1));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void mark_edges_uninteresting(struct commit_list *list)
|
|
|
|
{
|
|
|
|
for ( ; list; list = list->next) {
|
|
|
|
struct commit *commit = list->item;
|
|
|
|
|
|
|
|
if (commit->object.flags & UNINTERESTING) {
|
|
|
|
mark_tree_uninteresting(commit->tree);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
mark_edge_parents_uninteresting(commit);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, const char **argv)
|
|
|
|
{
|
|
|
|
struct commit_list *list;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
argc = setup_revisions(argc, argv, &revs, NULL);
|
|
|
|
|
|
|
|
for (i = 1 ; i < argc; i++) {
|
|
|
|
const char *arg = argv[i];
|
|
|
|
|
|
|
|
/* accept -<digit>, like traditilnal "head" */
|
|
|
|
if ((*arg == '-') && isdigit(arg[1])) {
|
|
|
|
revs.max_count = atoi(arg + 1);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!strcmp(arg, "-n")) {
|
|
|
|
if (++i >= argc)
|
|
|
|
die("-n requires an argument");
|
|
|
|
revs.max_count = atoi(argv[i]);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!strncmp(arg,"-n",2)) {
|
|
|
|
revs.max_count = atoi(arg + 2);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!strcmp(arg, "--header")) {
|
|
|
|
verbose_header = 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!strcmp(arg, "--no-abbrev")) {
|
|
|
|
abbrev = 0;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!strncmp(arg, "--abbrev=", 9)) {
|
|
|
|
abbrev = strtoul(arg + 9, NULL, 10);
|
|
|
|
if (abbrev && abbrev < MINIMUM_ABBREV)
|
|
|
|
abbrev = MINIMUM_ABBREV;
|
|
|
|
else if (40 < abbrev)
|
|
|
|
abbrev = 40;
|
|
|
|
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 (!strcmp(arg, "--parents")) {
|
|
|
|
show_parents = 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!strcmp(arg, "--bisect")) {
|
|
|
|
bisect_list = 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
usage(rev_list_usage);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
list = revs.commits;
|
|
|
|
|
|
|
|
if (!list &&
|
|
|
|
(!(revs.tag_objects||revs.tree_objects||revs.blob_objects) && !revs.pending_objects))
|
|
|
|
usage(rev_list_usage);
|
|
|
|
|
|
|
|
prepare_revision_walk(&revs);
|
|
|
|
if (revs.tree_objects)
|
|
|
|
mark_edges_uninteresting(revs.commits);
|
|
|
|
|
|
|
|
if (bisect_list)
|
|
|
|
revs.commits = find_bisection(revs.commits);
|
|
|
|
|
[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>
19 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>
19 years ago
|
|
|
|
|
|
|
show_commit_list(&revs);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|