git/builtin-rev-list.c
Linus Torvalds 1f1e895fcc Add "named object array" concept
We've had this notion of a "object_list" for a long time, which eventually
grew a "name" member because some users (notably git-rev-list) wanted to
name each object as it is generated.

That object_list is great for some things, but it isn't all that wonderful
for others, and the "name" member is generally not used by everybody.

This patch splits the users of the object_list array up into two: the
traditional list users, who want the list-like format, and who don't
actually use or want the name. And another class of users that really used
the list as an extensible array, and generally wanted to name the objects.

The patch is fairly straightforward, but it's also biggish. Most of it
really just cleans things up: switching the revision parsing and listing
over to the array makes things like the builtin-diff usage much simpler
(we now see exactly how many members the array has, and we don't get the
objects reversed from the order they were on the command line).

One of the main reasons for doing this at all is that the malloc overhead
of the simple object list was actually pretty high, and the array is just
a lot denser. So this patch brings down memory usage by git-rev-list by
just under 3% (on top of all the other memory use optimizations) on the
mozilla archive.

It does add more lines than it removes, and more importantly, it adds a
whole new infrastructure for maintaining lists of objects, but on the
other hand, the new dynamic array code is pretty obvious. The change to
builtin-diff-tree.c shows a fairly good example of why an array interface
is sometimes more natural, and just much simpler for everybody.

Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
2006-06-19 18:45:48 -07:00

373 lines
8.3 KiB
C

#include "cache.h"
#include "refs.h"
#include "tag.h"
#include "commit.h"
#include "tree.h"
#include "blob.h"
#include "tree-walk.h"
#include "diff.h"
#include "revision.h"
#include "builtin.h"
/* bits #0-15 in revision.h */
#define COUNTED (1u<<16)
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"
" --abbrev-commit\n"
" special purpose:\n"
" --bisect"
;
static struct rev_info revs;
static int bisect_list = 0;
static int show_timestamp = 0;
static int hdr_termination = 0;
static const char *header_prefix;
static void show_commit(struct commit *commit)
{
if (show_timestamp)
printf("%lu ", commit->date);
if (header_prefix)
fputs(header_prefix, stdout);
if (commit->object.flags & BOUNDARY)
putchar('-');
if (revs.abbrev_commit && revs.abbrev)
fputs(find_unique_abbrev(commit->object.sha1, revs.abbrev),
stdout);
else
fputs(sha1_to_hex(commit->object.sha1), stdout);
if (revs.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 (revs.commit_format == CMIT_FMT_ONELINE)
putchar(' ');
else
putchar('\n');
if (revs.verbose_header) {
static char pretty_header[16384];
pretty_print_commit(revs.commit_format, commit, ~0,
pretty_header, sizeof(pretty_header),
revs.abbrev, NULL, NULL);
printf("%s%c", pretty_header, hdr_termination);
}
fflush(stdout);
if (commit->parents) {
free_commit_list(commit->parents);
commit->parents = NULL;
}
if (commit->buffer) {
free(commit->buffer);
commit->buffer = NULL;
}
}
static void process_blob(struct blob *blob,
struct object_array *p,
struct name_path *path,
const char *name)
{
struct object *obj = &blob->object;
if (!revs.blob_objects)
return;
if (obj->flags & (UNINTERESTING | SEEN))
return;
obj->flags |= SEEN;
name = strdup(name);
add_object(obj, p, path, name);
}
static void process_tree(struct tree *tree,
struct object_array *p,
struct name_path *path,
const char *name)
{
struct object *obj = &tree->object;
struct tree_desc desc;
struct name_entry entry;
struct name_path me;
if (!revs.tree_objects)
return;
if (obj->flags & (UNINTERESTING | SEEN))
return;
if (parse_tree(tree) < 0)
die("bad tree object %s", sha1_to_hex(obj->sha1));
obj->flags |= SEEN;
name = strdup(name);
add_object(obj, p, path, name);
me.up = path;
me.elem = name;
me.elem_len = strlen(name);
desc.buf = tree->buffer;
desc.size = tree->size;
while (tree_entry(&desc, &entry)) {
if (S_ISDIR(entry.mode))
process_tree(lookup_tree(entry.sha1), p, &me, entry.path);
else
process_blob(lookup_blob(entry.sha1), p, &me, entry.path);
}
free(tree->buffer);
tree->buffer = NULL;
}
static void show_commit_list(struct rev_info *revs)
{
int i;
struct commit *commit;
struct object_array objects = { 0, 0, NULL };
while ((commit = get_revision(revs)) != NULL) {
process_tree(commit->tree, &objects, NULL, "");
show_commit(commit);
}
for (i = 0; i < revs->pending.nr; i++) {
struct object_array_entry *pending = revs->pending.objects + i;
struct object *obj = pending->item;
const char *name = pending->name;
if (obj->flags & (UNINTERESTING | SEEN))
continue;
if (obj->type == TYPE_TAG) {
obj->flags |= SEEN;
add_object_array(obj, name, &objects);
continue;
}
if (obj->type == TYPE_TREE) {
process_tree((struct tree *)obj, &objects, NULL, name);
continue;
}
if (obj->type == TYPE_BLOB) {
process_blob((struct blob *)obj, &objects, NULL, name);
continue;
}
die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name);
}
for (i = 0; i < objects.nr; i++) {
struct object_array_entry *p = objects.objects + i;
/* An object with name "foo\n0000000..." can be used to
* confuse downstream git-pack-objects very badly.
*/
const char *ep = strchr(p->name, '\n');
if (ep) {
printf("%s %.*s\n", sha1_to_hex(p->item->sha1),
(int) (ep - p->name),
p->name);
}
else
printf("%s %s\n", sha1_to_hex(p->item->sha1), p->name);
}
}
/*
* 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.prune_fn || (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.prune_fn || (p->item->object.flags & TREECHANGE))
nr++;
p = p->next;
}
closest = 0;
best = list;
for (p = list; p; p = p->next) {
int distance;
if (revs.prune_fn && !(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 cmd_rev_list(int argc, const char **argv, char **envp)
{
struct commit_list *list;
int i;
init_revisions(&revs);
revs.abbrev = 0;
revs.commit_format = CMIT_FMT_UNSPECIFIED;
argc = setup_revisions(argc, argv, &revs, NULL);
for (i = 1 ; i < argc; i++) {
const char *arg = argv[i];
if (!strcmp(arg, "--header")) {
revs.verbose_header = 1;
continue;
}
if (!strcmp(arg, "--timestamp")) {
show_timestamp = 1;
continue;
}
if (!strcmp(arg, "--bisect")) {
bisect_list = 1;
continue;
}
usage(rev_list_usage);
}
if (revs.commit_format != CMIT_FMT_UNSPECIFIED) {
/* The command line has a --pretty */
hdr_termination = '\n';
if (revs.commit_format == CMIT_FMT_ONELINE)
header_prefix = "";
else
header_prefix = "commit ";
}
else if (revs.verbose_header)
/* Only --header was specified */
revs.commit_format = CMIT_FMT_RAW;
list = revs.commits;
if ((!list &&
(!(revs.tag_objects||revs.tree_objects||revs.blob_objects) &&
!revs.pending.nr)) ||
revs.diff)
usage(rev_list_usage);
save_commit_buffer = revs.verbose_header;
track_object_refs = 0;
if (bisect_list)
revs.limited = 1;
prepare_revision_walk(&revs);
if (revs.tree_objects)
mark_edges_uninteresting(revs.commits);
if (bisect_list)
revs.commits = find_bisection(revs.commits);
show_commit_list(&revs);
return 0;
}