git notes merge: Handle real, non-conflicting notes merges
[gitweb.git] / revision.c
index b209d493e169ae58130a998f7dc1239f5a385c44..7e82efd9324e84582732485c53c6c25a24c29997 100644 (file)
@@ -646,6 +646,93 @@ static int still_interesting(struct commit_list *src, unsigned long date, int sl
        return slop-1;
 }
 
+/*
+ * "rev-list --ancestry-path A..B" computes commits that are ancestors
+ * of B but not ancestors of A but further limits the result to those
+ * that are descendants of A.  This takes the list of bottom commits and
+ * the result of "A..B" without --ancestry-path, and limits the latter
+ * further to the ones that can reach one of the commits in "bottom".
+ */
+static void limit_to_ancestry(struct commit_list *bottom, struct commit_list *list)
+{
+       struct commit_list *p;
+       struct commit_list *rlist = NULL;
+       int made_progress;
+
+       /*
+        * Reverse the list so that it will be likely that we would
+        * process parents before children.
+        */
+       for (p = list; p; p = p->next)
+               commit_list_insert(p->item, &rlist);
+
+       for (p = bottom; p; p = p->next)
+               p->item->object.flags |= TMP_MARK;
+
+       /*
+        * Mark the ones that can reach bottom commits in "list",
+        * in a bottom-up fashion.
+        */
+       do {
+               made_progress = 0;
+               for (p = rlist; p; p = p->next) {
+                       struct commit *c = p->item;
+                       struct commit_list *parents;
+                       if (c->object.flags & (TMP_MARK | UNINTERESTING))
+                               continue;
+                       for (parents = c->parents;
+                            parents;
+                            parents = parents->next) {
+                               if (!(parents->item->object.flags & TMP_MARK))
+                                       continue;
+                               c->object.flags |= TMP_MARK;
+                               made_progress = 1;
+                               break;
+                       }
+               }
+       } while (made_progress);
+
+       /*
+        * NEEDSWORK: decide if we want to remove parents that are
+        * not marked with TMP_MARK from commit->parents for commits
+        * in the resulting list.  We may not want to do that, though.
+        */
+
+       /*
+        * The ones that are not marked with TMP_MARK are uninteresting
+        */
+       for (p = list; p; p = p->next) {
+               struct commit *c = p->item;
+               if (c->object.flags & TMP_MARK)
+                       continue;
+               c->object.flags |= UNINTERESTING;
+       }
+
+       /* We are done with the TMP_MARK */
+       for (p = list; p; p = p->next)
+               p->item->object.flags &= ~TMP_MARK;
+       for (p = bottom; p; p = p->next)
+               p->item->object.flags &= ~TMP_MARK;
+       free_commit_list(rlist);
+}
+
+/*
+ * Before walking the history, keep the set of "negative" refs the
+ * caller has asked to exclude.
+ *
+ * This is used to compute "rev-list --ancestry-path A..B", as we need
+ * to filter the result of "A..B" further to the ones that can actually
+ * reach A.
+ */
+static struct commit_list *collect_bottom_commits(struct commit_list *list)
+{
+       struct commit_list *elem, *bottom = NULL;
+       for (elem = list; elem; elem = elem->next)
+               if (elem->item->object.flags & UNINTERESTING)
+                       commit_list_insert(elem->item, &bottom);
+       return bottom;
+}
+
 static int limit_list(struct rev_info *revs)
 {
        int slop = SLOP;
@@ -653,6 +740,13 @@ static int limit_list(struct rev_info *revs)
        struct commit_list *list = revs->commits;
        struct commit_list *newlist = NULL;
        struct commit_list **p = &newlist;
+       struct commit_list *bottom = NULL;
+
+       if (revs->ancestry_path) {
+               bottom = collect_bottom_commits(list);
+               if (!bottom)
+                       die("--ancestry-path given but there are no bottom commits");
+       }
 
        while (list) {
                struct commit_list *entry = list;
@@ -694,6 +788,11 @@ static int limit_list(struct rev_info *revs)
        if (revs->cherry_pick)
                cherry_pick_list(newlist, revs);
 
+       if (bottom) {
+               limit_to_ancestry(bottom, newlist);
+               free_commit_list(bottom);
+       }
+
        revs->commits = newlist;
        return 0;
 }
@@ -1063,18 +1162,22 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
 
        if (!prefixcmp(arg, "--max-count=")) {
                revs->max_count = atoi(arg + 12);
+               revs->no_walk = 0;
        } else if (!prefixcmp(arg, "--skip=")) {
                revs->skip_count = atoi(arg + 7);
        } else if ((*arg == '-') && isdigit(arg[1])) {
        /* accept -<digit>, like traditional "head" */
                revs->max_count = atoi(arg + 1);
+               revs->no_walk = 0;
        } else if (!strcmp(arg, "-n")) {
                if (argc <= 1)
                        return error("-n requires an argument");
                revs->max_count = atoi(argv[1]);
+               revs->no_walk = 0;
                return 2;
        } else if (!prefixcmp(arg, "-n")) {
                revs->max_count = atoi(arg + 2);
+               revs->no_walk = 0;
        } else if (!prefixcmp(arg, "--max-age=")) {
                revs->max_age = atoi(arg + 10);
        } else if (!prefixcmp(arg, "--since=")) {
@@ -1089,6 +1192,10 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->min_age = approxidate(arg + 8);
        } else if (!strcmp(arg, "--first-parent")) {
                revs->first_parent_only = 1;
+       } else if (!strcmp(arg, "--ancestry-path")) {
+               revs->ancestry_path = 1;
+               revs->simplify_history = 0;
+               revs->limited = 1;
        } else if (!strcmp(arg, "-g") || !strcmp(arg, "--walk-reflogs")) {
                init_reflog_walk(&revs->reflog_info);
        } else if (!strcmp(arg, "--default")) {
@@ -1146,6 +1253,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->boundary = 1;
        } else if (!strcmp(arg, "--left-right")) {
                revs->left_right = 1;
+       } else if (!strcmp(arg, "--count")) {
+               revs->count = 1;
        } else if (!strcmp(arg, "--cherry-pick")) {
                revs->cherry_pick = 1;
                revs->limited = 1;
@@ -1205,8 +1314,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                else
                        strbuf_addstr(&buf, "refs/notes/");
                strbuf_addstr(&buf, arg+13);
-               string_list_append(strbuf_detach(&buf, NULL),
-                                  revs->notes_opt.extra_notes_refs);
+               string_list_append(revs->notes_opt.extra_notes_refs,
+                                  strbuf_detach(&buf, NULL));
        } else if (!strcmp(arg, "--no-notes")) {
                revs->show_notes = 0;
                revs->show_notes_given = 1;