void mark_tree_uninteresting(struct tree *tree)
{
+ struct tree_desc desc;
+ struct name_entry entry;
struct object *obj = &tree->object;
- struct tree_entry_list *entry;
if (obj->flags & UNINTERESTING)
return;
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);
+
+ desc.buf = tree->buffer;
+ desc.size = tree->size;
+ while (tree_entry(&desc, &entry)) {
+ if (S_ISDIR(entry.mode))
+ mark_tree_uninteresting(lookup_tree(entry.sha1));
else
- mark_blob_uninteresting(entry->item.blob);
- free(entry);
- entry = next;
+ mark_blob_uninteresting(lookup_blob(entry.sha1));
}
+
+ /*
+ * We don't care about the tree any more
+ * after it has been marked uninteresting.
+ */
+ free(tree->buffer);
+ tree->buffer = NULL;
}
void mark_parents_uninteresting(struct commit *commit)
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)
+static struct object *get_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);
+ object->flags |= flags;
+ return object;
+}
+
+static struct commit *handle_commit(struct rev_info *revs, struct object *object, const char *name)
+{
+ unsigned long flags = object->flags;
/*
* Tag object? Look what it points to..
*/
- while (object->type == tag_type) {
+ while (object->type == TYPE_TAG) {
struct tag *tag = (struct tag *) object;
- object->flags |= flags;
- if (revs->tag_objects && !(object->flags & UNINTERESTING))
+ if (revs->tag_objects && !(flags & UNINTERESTING))
add_pending_object(revs, object, tag->tag);
object = parse_object(tag->tagged->sha1);
if (!object)
* Commit object? Just return it, we'll do all the complex
* reachability crud.
*/
- if (object->type == commit_type) {
+ if (object->type == TYPE_COMMIT) {
struct commit *commit = (struct commit *)object;
- object->flags |= flags;
if (parse_commit(commit) < 0)
die("unable to parse commit %s", name);
if (flags & UNINTERESTING) {
+ commit->object.flags |= UNINTERESTING;
mark_parents_uninteresting(commit);
revs->limited = 1;
}
* Tree object? Either mark it uniniteresting, or add it
* to the list of objects to look at later..
*/
- if (object->type == tree_type) {
+ if (object->type == TYPE_TREE) {
struct tree *tree = (struct tree *)object;
if (!revs->tree_objects)
return NULL;
/*
* Blob object? You know the drill by now..
*/
- if (object->type == blob_type) {
+ if (object->type == TYPE_BLOB) {
struct blob *blob = (struct blob *)object;
if (!revs->blob_objects)
return NULL;
tree_difference = REV_TREE_DIFFERENT;
}
-static struct diff_options diff_opt = {
- .recursive = 1,
- .add_remove = file_add_remove,
- .change = file_change,
-};
-
-int rev_compare_tree(struct tree *t1, struct tree *t2)
+int rev_compare_tree(struct rev_info *revs, struct tree *t1, struct tree *t2)
{
if (!t1)
return REV_TREE_NEW;
if (!t2)
return REV_TREE_DIFFERENT;
tree_difference = REV_TREE_SAME;
- if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "", &diff_opt) < 0)
+ if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "",
+ &revs->pruning) < 0)
return REV_TREE_DIFFERENT;
return tree_difference;
}
-int rev_same_tree_as_empty(struct tree *t1)
+int rev_same_tree_as_empty(struct rev_info *revs, struct tree *t1)
{
int retval;
void *tree;
if (!t1)
return 0;
- tree = read_object_with_reference(t1->object.sha1, "tree", &real.size, NULL);
+ tree = read_object_with_reference(t1->object.sha1, tree_type, &real.size, NULL);
if (!tree)
return 0;
real.buf = tree;
empty.size = 0;
tree_difference = 0;
- retval = diff_tree(&empty, &real, "", &diff_opt);
+ retval = diff_tree(&empty, &real, "", &revs->pruning);
free(tree);
return retval >= 0 && !tree_difference;
return;
if (!commit->parents) {
- if (!rev_same_tree_as_empty(commit->tree))
+ if (!rev_same_tree_as_empty(revs, commit->tree))
commit->object.flags |= TREECHANGE;
return;
}
struct commit *p = parent->item;
parse_commit(p);
- switch (rev_compare_tree(p->tree, commit->tree)) {
+ switch (rev_compare_tree(revs, p->tree, commit->tree)) {
case REV_TREE_SAME:
- if (p->object.flags & UNINTERESTING) {
+ if (!revs->simplify_history || (p->object.flags & UNINTERESTING)) {
/* Even if a merge with an uninteresting
* side branch brought the entire change
* we are interested in, we do not want
case REV_TREE_NEW:
if (revs->remove_empty_trees &&
- rev_same_tree_as_empty(p->tree)) {
+ rev_same_tree_as_empty(revs, p->tree)) {
/* We are adding all the specified
* paths from this parent, so the
* history beyond this parent is not
{
struct commit_list *parent = commit->parents;
+ if (commit->object.flags & ADDED)
+ return;
+ commit->object.flags |= ADDED;
+
/*
* If the commit is uninteresting, don't try to
* prune parents - we want the maximal uninteresting
if (revs->prune_fn)
revs->prune_fn(revs, commit);
+ if (revs->no_walk)
+ return;
+
parent = commit->parents;
while (parent) {
struct commit *p = parent->item;
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);
+ struct object *object = get_reference(all_revs, path, sha1, all_flags);
+ add_pending_object(all_revs, object, "");
return 0;
}
for_each_ref(handle_one_ref);
}
+static int add_parents_only(struct rev_info *revs, const char *arg, int flags)
+{
+ unsigned char sha1[20];
+ struct object *it;
+ struct commit *commit;
+ struct commit_list *parents;
+
+ if (*arg == '^') {
+ flags ^= UNINTERESTING;
+ arg++;
+ }
+ if (get_sha1(arg, sha1))
+ return 0;
+ while (1) {
+ it = get_reference(revs, arg, sha1, 0);
+ if (it->type != TYPE_TAG)
+ break;
+ memcpy(sha1, ((struct tag*)it)->tagged->sha1, 20);
+ }
+ if (it->type != TYPE_COMMIT)
+ return 0;
+ commit = (struct commit *)it;
+ for (parents = commit->parents; parents; parents = parents->next) {
+ it = &parents->item->object;
+ it->flags |= flags;
+ add_pending_object(revs, it, arg);
+ }
+ return 1;
+}
+
void init_revisions(struct rev_info *revs)
{
memset(revs, 0, sizeof(*revs));
+
+ revs->abbrev = DEFAULT_ABBREV;
+ revs->ignore_merges = 1;
+ revs->simplify_history = 1;
+ revs->pruning.recursive = 1;
+ revs->pruning.add_remove = file_add_remove;
+ revs->pruning.change = file_change;
revs->lifo = 1;
revs->dense = 1;
revs->prefix = setup_git_directory();
revs->topo_setter = topo_sort_default_setter;
revs->topo_getter = topo_sort_default_getter;
+
+ revs->commit_format = CMIT_FMT_DEFAULT;
+
+ diff_setup(&revs->diffopt);
}
/*
const char **unrecognized = argv + 1;
int left = 1;
- init_revisions(revs);
-
/* First, search for "--" */
seen_dashdash = 0;
for (i = 1; i < argc; i++) {
flags = 0;
for (i = 1; i < argc; i++) {
- struct commit *commit;
+ struct object *object;
const char *arg = argv[i];
unsigned char sha1[20];
char *dotdot;
int local_flags;
if (*arg == '-') {
+ int opts;
if (!strncmp(arg, "--max-count=", 12)) {
revs->max_count = atoi(arg + 12);
continue;
}
- /* accept -<digit>, like traditilnal "head" */
+ /* accept -<digit>, like traditional "head" */
if ((*arg == '-') && isdigit(arg[1])) {
revs->max_count = atoi(arg + 1);
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 (!strncmp(arg, "--since=", 8)) {
revs->max_age = approxidate(arg + 8);
- revs->limited = 1;
continue;
}
if (!strncmp(arg, "--after=", 8)) {
revs->max_age = approxidate(arg + 8);
- revs->limited = 1;
+ continue;
+ }
+ if (!strncmp(arg, "--min-age=", 10)) {
+ revs->min_age = atoi(arg + 10);
continue;
}
if (!strncmp(arg, "--before=", 9)) {
revs->min_age = approxidate(arg + 9);
- revs->limited = 1;
continue;
}
if (!strncmp(arg, "--until=", 8)) {
revs->min_age = approxidate(arg + 8);
- revs->limited = 1;
continue;
}
if (!strcmp(arg, "--all")) {
}
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, "--parents")) {
+ revs->parents = 1;
continue;
}
if (!strcmp(arg, "--dense")) {
}
if (!strcmp(arg, "--unpacked")) {
revs->unpacked = 1;
- revs->limited = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-r")) {
+ revs->diff = 1;
+ revs->diffopt.recursive = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-t")) {
+ revs->diff = 1;
+ revs->diffopt.recursive = 1;
+ revs->diffopt.tree_in_recursive = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-m")) {
+ revs->ignore_merges = 0;
+ continue;
+ }
+ if (!strcmp(arg, "-c")) {
+ revs->diff = 1;
+ revs->dense_combined_merges = 0;
+ revs->combine_merges = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--cc")) {
+ revs->diff = 1;
+ revs->dense_combined_merges = 1;
+ revs->combine_merges = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-v")) {
+ revs->verbose_header = 1;
+ continue;
+ }
+ if (!strncmp(arg, "--pretty", 8)) {
+ revs->verbose_header = 1;
+ revs->commit_format = get_commit_format(arg+8);
+ continue;
+ }
+ if (!strcmp(arg, "--root")) {
+ revs->show_root_diff = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--no-commit-id")) {
+ revs->no_commit_id = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--always")) {
+ revs->always_show_header = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--no-abbrev")) {
+ revs->abbrev = 0;
+ continue;
+ }
+ if (!strcmp(arg, "--abbrev")) {
+ revs->abbrev = DEFAULT_ABBREV;
+ continue;
+ }
+ if (!strncmp(arg, "--abbrev=", 9)) {
+ revs->abbrev = strtoul(arg + 9, NULL, 10);
+ if (revs->abbrev < MINIMUM_ABBREV)
+ revs->abbrev = MINIMUM_ABBREV;
+ else if (revs->abbrev > 40)
+ revs->abbrev = 40;
+ continue;
+ }
+ if (!strcmp(arg, "--abbrev-commit")) {
+ revs->abbrev_commit = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--full-diff")) {
+ revs->diff = 1;
+ revs->full_diff = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--full-history")) {
+ revs->simplify_history = 0;
+ continue;
+ }
+ opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i);
+ if (opts > 0) {
+ revs->diff = 1;
+ i += opts - 1;
continue;
}
*unrecognized++ = arg;
this = "HEAD";
if (!get_sha1(this, from_sha1) &&
!get_sha1(next, sha1)) {
- struct commit *exclude;
- struct commit *include;
+ struct object *exclude;
+ struct object *include;
- exclude = get_commit_reference(revs, this, from_sha1, flags ^ UNINTERESTING);
- include = get_commit_reference(revs, next, sha1, flags);
+ exclude = get_reference(revs, this, from_sha1, flags ^ UNINTERESTING);
+ include = get_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);
+
+ if (!seen_dashdash) {
+ *dotdot = '.';
+ verify_non_filename(revs->prefix, arg);
+ }
+ add_pending_object(revs, exclude, this);
+ add_pending_object(revs, include, next);
continue;
}
*dotdot = '.';
}
+ dotdot = strstr(arg, "^@");
+ if (dotdot && !dotdot[2]) {
+ *dotdot = 0;
+ if (add_parents_only(revs, arg, flags))
+ continue;
+ *dotdot = '^';
+ }
local_flags = 0;
if (*arg == '^') {
local_flags = UNINTERESTING;
arg++;
}
- if (get_sha1(arg, sha1) < 0) {
- struct stat st;
+ if (get_sha1(arg, sha1)) {
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", argv[j], strerror(errno));
- }
+ /* If we didn't have a "--":
+ * (1) all filenames must exist;
+ * (2) all rev-args must not be interpretable
+ * as a valid filename.
+ * but the latter we have checked in the main loop.
+ */
+ for (j = i; j < argc; j++)
+ verify_filename(revs->prefix, argv[j]);
+
revs->prune_data = get_pathspec(revs->prefix, argv + i);
break;
}
- commit = get_commit_reference(revs, arg, sha1, flags ^ local_flags);
- add_one_commit(commit, revs);
+ if (!seen_dashdash)
+ verify_non_filename(revs->prefix, arg);
+ object = get_reference(revs, arg, sha1, flags ^ local_flags);
+ add_pending_object(revs, object, arg);
}
- if (def && !revs->commits) {
+ if (def && !revs->pending_objects) {
unsigned char sha1[20];
- struct commit *commit;
- if (get_sha1(def, sha1) < 0)
+ struct object *object;
+ if (get_sha1(def, sha1))
die("bad default revision '%s'", def);
- commit = get_commit_reference(revs, def, sha1, 0);
- add_one_commit(commit, revs);
+ object = get_reference(revs, def, sha1, 0);
+ add_pending_object(revs, object, def);
}
+ if (revs->topo_order || revs->unpacked)
+ revs->limited = 1;
+
if (revs->prune_data) {
- diff_tree_setup_paths(revs->prune_data);
+ diff_tree_setup_paths(revs->prune_data, &revs->pruning);
revs->prune_fn = try_to_simplify_commit;
- revs->limited = 1;
+ if (!revs->full_diff)
+ diff_tree_setup_paths(revs->prune_data, &revs->diffopt);
+ }
+ if (revs->combine_merges) {
+ revs->ignore_merges = 0;
+ if (revs->dense_combined_merges &&
+ (revs->diffopt.output_format != DIFF_FORMAT_DIFFSTAT))
+ revs->diffopt.output_format = DIFF_FORMAT_PATCH;
}
+ revs->diffopt.abbrev = revs->abbrev;
+ diff_setup_done(&revs->diffopt);
return left;
}
void prepare_revision_walk(struct rev_info *revs)
{
- sort_by_date(&revs->commits);
+ struct object_list *list;
+
+ list = revs->pending_objects;
+ revs->pending_objects = NULL;
+ while (list) {
+ struct commit *commit = handle_commit(revs, list->item, list->name);
+ if (commit) {
+ if (!(commit->object.flags & SEEN)) {
+ commit->object.flags |= SEEN;
+ insert_by_date(commit, &revs->commits);
+ }
+ }
+ list = list->next;
+ }
+
+ if (revs->no_walk)
+ return;
if (revs->limited)
limit_list(revs);
if (revs->topo_order)
revs->topo_getter);
}
-static int rewrite_one(struct commit **pp)
+static int rewrite_one(struct rev_info *revs, struct commit **pp)
{
for (;;) {
struct commit *p = *pp;
+ if (!revs->limited)
+ add_parents_to_list(revs, p, &revs->commits);
if (p->object.flags & (TREECHANGE | UNINTERESTING))
return 0;
if (!p->parents)
}
}
-static void rewrite_parents(struct commit *commit)
+static void rewrite_parents(struct rev_info *revs, struct commit *commit)
{
struct commit_list **pp = &commit->parents;
while (*pp) {
struct commit_list *parent = *pp;
- if (rewrite_one(&parent->item) < 0) {
+ if (rewrite_one(revs, &parent->item) < 0) {
*pp = parent->next;
continue;
}
}
}
+static void mark_boundary_to_show(struct commit *commit)
+{
+ struct commit_list *p = commit->parents;
+ while (p) {
+ commit = p->item;
+ p = p->next;
+ if (commit->object.flags & BOUNDARY)
+ commit->object.flags |= BOUNDARY_SHOW;
+ }
+}
+
struct commit *get_revision(struct rev_info *revs)
{
struct commit_list *list = revs->commits;
}
do {
- struct commit *commit = revs->commits->item;
+ struct commit_list *entry = revs->commits;
+ struct commit *commit = entry->item;
+
+ revs->commits = entry->next;
+ free(entry);
+ /*
+ * If we haven't done the list limiting, we need to look at
+ * the parents here. We also need to do the date-based limiting
+ * that we'd otherwise have done in limit_list().
+ */
+ if (!revs->limited) {
+ if ((revs->unpacked &&
+ has_sha1_pack(commit->object.sha1)) ||
+ (revs->max_age != -1 &&
+ (commit->date < revs->max_age)))
+ continue;
+ add_parents_to_list(revs, commit, &revs->commits);
+ }
if (commit->object.flags & SHOWN)
- goto next;
- if (!(commit->object.flags & BOUNDARY) &&
- (commit->object.flags & UNINTERESTING))
- goto next;
+ continue;
+
+ /* We want to show boundary commits only when their
+ * children are shown. When path-limiter is in effect,
+ * rewrite_parents() drops some commits from getting shown,
+ * and there is no point showing boundary parents that
+ * are not shown. After rewrite_parents() rewrites the
+ * parents of a commit that is shown, we mark the boundary
+ * parents with BOUNDARY_SHOW.
+ */
+ if (commit->object.flags & BOUNDARY_SHOW) {
+ commit->object.flags |= SHOWN;
+ return commit;
+ }
+ if (commit->object.flags & UNINTERESTING)
+ continue;
if (revs->min_age != -1 && (commit->date > revs->min_age))
- goto next;
- if (revs->max_age != -1 && (commit->date < revs->max_age))
- return NULL;
+ continue;
if (revs->no_merges &&
commit->parents && commit->parents->next)
- goto next;
+ continue;
if (revs->prune_fn && revs->dense) {
if (!(commit->object.flags & TREECHANGE))
- goto next;
- rewrite_parents(commit);
- }
- /* More to go? */
- if (revs->max_count) {
- if (commit->object.flags & BOUNDARY) {
- /* this is already uninteresting,
- * so there is no point popping its
- * parents into the list.
- */
- struct commit_list *it = revs->commits;
- revs->commits = it->next;
- free(it);
- }
- else
- pop_most_recent_commit(&revs->commits, SEEN);
+ continue;
+ if (revs->parents)
+ rewrite_parents(revs, commit);
}
+ if (revs->boundary)
+ mark_boundary_to_show(commit);
commit->object.flags |= SHOWN;
return commit;
-next:
- pop_most_recent_commit(&revs->commits, SEEN);
} while (revs->commits);
return NULL;
}