summaryrefslogtreecommitdiff
path: root/revision.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@osdl.org>2006-02-28 11:24:00 -0800
committerJunio C Hamano <junkio@cox.net>2006-02-28 14:49:29 -0800
commita4a88b2bab3b6fb0b30f63418701f42388e0fe0a (patch)
tree4e27435857e281ec0476076705b7a988086b270b /revision.c
parentd9a83684c473e04c61f0060c4926d20f8183f7b6 (diff)
downloadgit-a4a88b2bab3b6fb0b30f63418701f42388e0fe0a.tar.gz
git-rev-list libification: rev-list walking
This actually moves the "meat" of the revision walking from rev-list.c to the new library code in revision.h. It introduces the new functions void prepare_revision_walk(struct rev_info *revs); struct commit *get_revision(struct rev_info *revs); to prepare and then walk the revisions that we have. Signed-off-by: Linus Torvalds <torvalds@osdl.org> Signed-off-by: Junio C Hamano <junkio@cox.net>
Diffstat (limited to 'revision.c')
-rw-r--r--revision.c247
1 files changed, 245 insertions, 2 deletions
diff --git a/revision.c b/revision.c
index 67ff4de2d1..f1ac62d7d8 100644
--- a/revision.c
+++ b/revision.c
@@ -3,6 +3,7 @@
#include "blob.h"
#include "tree.h"
#include "commit.h"
+#include "diff.h"
#include "refs.h"
#include "revision.h"
@@ -183,6 +184,229 @@ static struct commit *get_commit_reference(struct rev_info *revs, const char *na
die("%s is unknown object", name);
}
+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;
+}
+
+#define TREE_SAME 0
+#define TREE_NEW 1
+#define TREE_DIFFERENT 2
+static int tree_difference = TREE_SAME;
+
+static void file_add_remove(struct diff_options *options,
+ int addremove, unsigned mode,
+ const unsigned char *sha1,
+ const char *base, const char *path)
+{
+ int diff = TREE_DIFFERENT;
+
+ /*
+ * Is it an add of a new file? It means that
+ * the old tree didn't have it at all, so we
+ * will turn "TREE_SAME" -> "TREE_NEW", but
+ * leave any "TREE_DIFFERENT" alone (and if
+ * it already was "TREE_NEW", we'll keep it
+ * "TREE_NEW" of course).
+ */
+ if (addremove == '+') {
+ diff = tree_difference;
+ if (diff != TREE_SAME)
+ return;
+ diff = TREE_NEW;
+ }
+ tree_difference = diff;
+}
+
+static void file_change(struct diff_options *options,
+ unsigned old_mode, unsigned new_mode,
+ const unsigned char *old_sha1,
+ const unsigned char *new_sha1,
+ const char *base, const char *path)
+{
+ tree_difference = TREE_DIFFERENT;
+}
+
+static struct diff_options diff_opt = {
+ .recursive = 1,
+ .add_remove = file_add_remove,
+ .change = file_change,
+};
+
+static int compare_tree(struct tree *t1, struct tree *t2)
+{
+ if (!t1)
+ return TREE_NEW;
+ if (!t2)
+ return TREE_DIFFERENT;
+ tree_difference = TREE_SAME;
+ if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "", &diff_opt) < 0)
+ return TREE_DIFFERENT;
+ return tree_difference;
+}
+
+static int same_tree_as_empty(struct tree *t1)
+{
+ int retval;
+ void *tree;
+ struct tree_desc empty, real;
+
+ if (!t1)
+ return 0;
+
+ tree = read_object_with_reference(t1->object.sha1, "tree", &real.size, NULL);
+ if (!tree)
+ return 0;
+ real.buf = tree;
+
+ empty.buf = "";
+ empty.size = 0;
+
+ tree_difference = 0;
+ retval = diff_tree(&empty, &real, "", &diff_opt);
+ free(tree);
+
+ return retval >= 0 && !tree_difference;
+}
+
+static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
+{
+ struct commit_list **pp, *parent;
+
+ if (!commit->tree)
+ return;
+
+ if (!commit->parents) {
+ if (!same_tree_as_empty(commit->tree))
+ commit->object.flags |= TREECHANGE;
+ return;
+ }
+
+ pp = &commit->parents;
+ while ((parent = *pp) != NULL) {
+ struct commit *p = parent->item;
+
+ if (p->object.flags & UNINTERESTING) {
+ pp = &parent->next;
+ continue;
+ }
+
+ parse_commit(p);
+ switch (compare_tree(p->tree, commit->tree)) {
+ case TREE_SAME:
+ parent->next = NULL;
+ commit->parents = parent;
+ return;
+
+ case TREE_NEW:
+ if (revs->remove_empty_trees && same_tree_as_empty(p->tree)) {
+ *pp = parent->next;
+ continue;
+ }
+ /* fallthrough */
+ case TREE_DIFFERENT:
+ pp = &parent->next;
+ continue;
+ }
+ die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1));
+ }
+ commit->object.flags |= TREECHANGE;
+}
+
+static void add_parents_to_list(struct rev_info *revs, struct commit *commit, struct commit_list **list)
+{
+ struct commit_list *parent = commit->parents;
+
+ /*
+ * If the commit is uninteresting, don't try to
+ * prune parents - we want the maximal uninteresting
+ * set.
+ *
+ * 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->object.flags & UNINTERESTING) {
+ while (parent) {
+ struct commit *p = parent->item;
+ parent = parent->next;
+ parse_commit(p);
+ p->object.flags |= UNINTERESTING;
+ if (p->parents)
+ mark_parents_uninteresting(p);
+ if (p->object.flags & SEEN)
+ continue;
+ p->object.flags |= SEEN;
+ insert_by_date(p, list);
+ }
+ return;
+ }
+
+ /*
+ * Ok, the commit wasn't uninteresting. Try to
+ * simplify the commit history and find the parent
+ * that has no differences in the path set if one exists.
+ */
+ if (revs->paths)
+ try_to_simplify_commit(revs, commit);
+
+ parent = commit->parents;
+ while (parent) {
+ struct commit *p = parent->item;
+
+ parent = parent->next;
+
+ parse_commit(p);
+ if (p->object.flags & SEEN)
+ continue;
+ p->object.flags |= SEEN;
+ insert_by_date(p, list);
+ }
+}
+
+static void limit_list(struct rev_info *revs)
+{
+ struct commit_list *list = revs->commits;
+ struct commit_list *newlist = NULL;
+ struct commit_list **p = &newlist;
+
+ while (list) {
+ struct commit_list *entry = list;
+ struct commit *commit = list->item;
+ struct object *obj = &commit->object;
+
+ list = list->next;
+ free(entry);
+
+ if (revs->max_age != -1 && (commit->date < revs->max_age))
+ obj->flags |= UNINTERESTING;
+ if (revs->unpacked && has_sha1_pack(obj->sha1))
+ obj->flags |= UNINTERESTING;
+ add_parents_to_list(revs, commit, &list);
+ if (obj->flags & UNINTERESTING) {
+ mark_parents_uninteresting(commit);
+ if (everybody_uninteresting(list))
+ break;
+ continue;
+ }
+ if (revs->min_age != -1 && (commit->date > revs->min_age))
+ continue;
+ p = &commit_list_insert(commit, p)->next;
+ }
+ revs->commits = newlist;
+}
+
static void add_one_commit(struct commit *commit, struct rev_info *revs)
{
if (!commit || (commit->object.flags & SEEN))
@@ -214,10 +438,9 @@ static void handle_all(struct rev_info *revs, unsigned flags)
*
* Returns the number of arguments left ("new argc").
*/
-int setup_revisions(int argc, const char **argv, struct rev_info *revs)
+int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def)
{
int i, flags, seen_dashdash;
- const char *def = NULL;
const char **unrecognized = argv+1;
int left = 1;
@@ -381,3 +604,23 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs)
*unrecognized = NULL;
return left;
}
+
+void prepare_revision_walk(struct rev_info *revs)
+{
+ if (revs->paths)
+ diff_tree_setup_paths(revs->paths);
+ sort_by_date(&revs->commits);
+ if (revs->limited)
+ limit_list(revs);
+ if (revs->topo_order)
+ sort_in_topological_order(&revs->commits, revs->lifo);
+}
+
+struct commit *get_revision(struct rev_info *revs)
+{
+ if (!revs->commits)
+ return NULL;
+ return pop_most_recent_commit(&revs->commits, SEEN);
+}
+
+