+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;
+}
+
+static int tree_difference = REV_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 = REV_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 "REV_TREE_SAME" ->
+ * "REV_TREE_NEW", but leave any "REV_TREE_DIFFERENT" alone
+ * (and if it already was "REV_TREE_NEW", we'll keep it
+ * "REV_TREE_NEW" of course).
+ */
+ if (addremove == '+') {
+ diff = tree_difference;
+ if (diff != REV_TREE_SAME)
+ return;
+ diff = REV_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 = 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)
+{
+ 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)
+ return REV_TREE_DIFFERENT;
+ return tree_difference;
+}
+
+int rev_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;
+ int tree_changed = 0;
+
+ if (!commit->tree)
+ return;
+
+ if (!commit->parents) {
+ if (!rev_same_tree_as_empty(commit->tree))
+ commit->object.flags |= TREECHANGE;
+ return;
+ }
+
+ pp = &commit->parents;
+ while ((parent = *pp) != NULL) {
+ struct commit *p = parent->item;
+
+ parse_commit(p);
+ switch (rev_compare_tree(p->tree, commit->tree)) {
+ case REV_TREE_SAME:
+ if (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
+ * to lose the other branches of this
+ * merge, so we just keep going.
+ */
+ pp = &parent->next;
+ continue;
+ }
+ parent->next = NULL;
+ commit->parents = parent;
+ return;
+
+ case REV_TREE_NEW:
+ if (revs->remove_empty_trees &&
+ rev_same_tree_as_empty(p->tree)) {
+ /* We are adding all the specified
+ * paths from this parent, so the
+ * history beyond this parent is not
+ * interesting. Remove its parents
+ * (they are grandparents for us).
+ * IOW, we pretend this parent is a
+ * "root" commit.
+ */
+ parse_commit(p);
+ p->parents = NULL;
+ }
+ /* fallthrough */
+ case REV_TREE_DIFFERENT:
+ tree_changed = 1;
+ pp = &parent->next;
+ continue;
+ }
+ die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1));
+ }
+ if (tree_changed)
+ 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->prune_fn)
+ revs->prune_fn(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;
+ }
+ if (revs->boundary) {
+ /* mark the ones that are on the result list first */
+ for (list = newlist; list; list = list->next) {
+ struct commit *commit = list->item;
+ commit->object.flags |= TMP_MARK;
+ }
+ for (list = newlist; list; list = list->next) {
+ struct commit *commit = list->item;
+ struct object *obj = &commit->object;
+ struct commit_list *parent;
+ if (obj->flags & UNINTERESTING)
+ continue;
+ for (parent = commit->parents;
+ parent;
+ parent = parent->next) {
+ struct commit *pcommit = parent->item;
+ if (!(pcommit->object.flags & UNINTERESTING))
+ continue;
+ pcommit->object.flags |= BOUNDARY;
+ if (pcommit->object.flags & TMP_MARK)
+ continue;
+ pcommit->object.flags |= TMP_MARK;
+ p = &commit_list_insert(pcommit, p)->next;
+ }
+ }
+ for (list = newlist; list; list = list->next) {
+ struct commit *commit = list->item;
+ commit->object.flags &= ~TMP_MARK;
+ }
+ }
+ revs->commits = newlist;
+}
+