mirror of
https://github.com/git/git
synced 2024-10-28 19:25:47 +00:00
1f1e895fcc
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>
372 lines
8.3 KiB
C
372 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;
|
|
}
|