summaryrefslogtreecommitdiff
path: root/rev-list.c
diff options
context:
space:
mode:
Diffstat (limited to 'rev-list.c')
-rw-r--r--rev-list.c137
1 files changed, 79 insertions, 58 deletions
diff --git a/rev-list.c b/rev-list.c
index 334713a..0b142c1 100644
--- a/rev-list.c
+++ b/rev-list.c
@@ -21,6 +21,7 @@ static const char rev_list_usage[] =
" --min-age=epoch\n"
" --sparse\n"
" --no-merges\n"
+" --remove-empty\n"
" --all\n"
" ordering output:\n"
" --merge-order [ --show-breaks ]\n"
@@ -54,6 +55,7 @@ static int stop_traversal = 0;
static int topo_order = 0;
static int no_merges = 0;
static const char **paths = NULL;
+static int remove_empty_trees = 0;
static void show_commit(struct commit *commit)
{
@@ -424,14 +426,33 @@ static void mark_edges_uninteresting(struct commit_list *list)
}
}
-static int is_different = 0;
+#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)
{
- is_different = 1;
+ 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,
@@ -440,7 +461,7 @@ static void file_change(struct diff_options *options,
const unsigned char *new_sha1,
const char *base, const char *path)
{
- is_different = 1;
+ tree_difference = TREE_DIFFERENT;
}
static struct diff_options diff_opt = {
@@ -449,12 +470,16 @@ static struct diff_options diff_opt = {
.change = file_change,
};
-static int same_tree(struct tree *t1, struct tree *t2)
+static int compare_tree(struct tree *t1, struct tree *t2)
{
- is_different = 0;
+ 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 0;
- return !is_different;
+ return TREE_DIFFERENT;
+ return tree_difference;
}
static int same_tree_as_empty(struct tree *t1)
@@ -474,28 +499,55 @@ static int same_tree_as_empty(struct tree *t1)
empty.buf = "";
empty.size = 0;
- is_different = 0;
+ tree_difference = 0;
retval = diff_tree(&empty, &real, "", &diff_opt);
free(tree);
- return retval >= 0 && !is_different;
+ return retval >= 0 && !tree_difference;
}
-static struct commit *try_to_simplify_merge(struct commit *commit, struct commit_list *parent)
+static void try_to_simplify_commit(struct commit *commit)
{
+ struct commit_list **pp, *parent;
+
if (!commit->tree)
- return NULL;
+ return;
- while (parent) {
+ 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;
- parent = parent->next;
+
+ if (p->object.flags & UNINTERESTING) {
+ pp = &parent->next;
+ continue;
+ }
+
parse_commit(p);
- if (!p->tree)
+ switch (compare_tree(p->tree, commit->tree)) {
+ case TREE_SAME:
+ parent->next = NULL;
+ commit->parents = parent;
+ return;
+
+ case TREE_NEW:
+ if (remove_empty_trees && same_tree_as_empty(p->tree)) {
+ *pp = parent->next;
+ continue;
+ }
+ /* fallthrough */
+ case TREE_DIFFERENT:
+ pp = &parent->next;
continue;
- if (same_tree(commit->tree, p->tree))
- return p;
+ }
+ die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1));
}
- return NULL;
+ commit->object.flags |= TREECHANGE;
}
static void add_parents_to_list(struct commit *commit, struct commit_list **list)
@@ -531,20 +583,14 @@ static void add_parents_to_list(struct commit *commit, struct commit_list **list
}
/*
- * Ok, the commit wasn't uninteresting. If it
- * is a merge, try to find the parent that has
- * no differences in the path set if one exists.
+ * 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 (paths && parent && parent->next) {
- struct commit *preferred;
-
- preferred = try_to_simplify_merge(commit, parent);
- if (preferred) {
- parent->item = preferred;
- parent->next = NULL;
- }
- }
+ if (paths)
+ try_to_simplify_commit(commit);
+ parent = commit->parents;
while (parent) {
struct commit *p = parent->item;
@@ -558,33 +604,6 @@ static void add_parents_to_list(struct commit *commit, struct commit_list **list
}
}
-static void compress_list(struct commit_list *list)
-{
- while (list) {
- struct commit *commit = list->item;
- struct commit_list *parent = commit->parents;
- list = list->next;
-
- if (!parent) {
- if (!same_tree_as_empty(commit->tree))
- commit->object.flags |= TREECHANGE;
- continue;
- }
-
- /*
- * Exactly one parent? Check if it leaves the tree
- * unchanged
- */
- if (!parent->next) {
- struct tree *t1 = commit->tree;
- struct tree *t2 = parent->item->tree;
- if (!t1 || !t2 || same_tree(t1, t2))
- continue;
- }
- commit->object.flags |= TREECHANGE;
- }
-}
-
static struct commit_list *limit_list(struct commit_list *list)
{
struct commit_list *newlist = NULL;
@@ -614,8 +633,6 @@ static struct commit_list *limit_list(struct commit_list *list)
}
if (tree_objects)
mark_edges_uninteresting(newlist);
- if (paths && dense)
- compress_list(newlist);
if (bisect_list)
newlist = find_bisection(newlist);
return newlist;
@@ -808,6 +825,10 @@ int main(int argc, const char **argv)
dense = 0;
continue;
}
+ if (!strcmp(arg, "--remove-empty")) {
+ remove_empty_trees = 1;
+ continue;
+ }
if (!strcmp(arg, "--")) {
i++;
break;