Merge branch 'maint'
[gitweb.git] / builtin / reflog.c
index 64e45bd8137bef2b6cfe1f8a3da79e2ff6f8fc47..f73215686d15a266c2d20b007fe80ff4beabbb33 100644 (file)
@@ -13,7 +13,7 @@
  */
 
 static const char reflog_expire_usage[] =
-"git reflog (show|expire) [--verbose] [--dry-run] [--stale-fix] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...";
+"git reflog expire [--verbose] [--dry-run] [--stale-fix] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...";
 static const char reflog_delete_usage[] =
 "git reflog delete [--verbose] [--dry-run] [--rewrite] [--updateref] <refs>...";
 
@@ -36,6 +36,8 @@ struct expire_reflog_cb {
        FILE *newlog;
        const char *ref;
        struct commit *ref_commit;
+       struct commit_list *mark_list;
+       unsigned long mark_limit;
        struct cmd_reflog_expire_cb *cmd;
        unsigned char last_kept_sha1[20];
 };
@@ -210,46 +212,23 @@ static int keep_entry(struct commit **it, unsigned char *sha1)
        return 1;
 }
 
-static int unreachable(struct expire_reflog_cb *cb, struct commit *commit, unsigned char *sha1)
+/*
+ * Starting from commits in the cb->mark_list, mark commits that are
+ * reachable from them.  Stop the traversal at commits older than
+ * the expire_limit and queue them back, so that the caller can call
+ * us again to restart the traversal with longer expire_limit.
+ */
+static void mark_reachable(struct expire_reflog_cb *cb)
 {
-       /*
-        * We may or may not have the commit yet - if not, look it
-        * up using the supplied sha1.
-        */
-       if (!commit) {
-               if (is_null_sha1(sha1))
-                       return 0;
-
-               commit = lookup_commit_reference_gently(sha1, 1);
-
-               /* Not a commit -- keep it */
-               if (!commit)
-                       return 0;
-       }
-
-       /* Reachable from the current ref?  Don't prune. */
-       if (commit->object.flags & REACHABLE)
-               return 0;
-       if (in_merge_bases(commit, &cb->ref_commit, 1))
-               return 0;
-
-       /* We can't reach it - prune it. */
-       return 1;
-}
+       struct commit *commit;
+       struct commit_list *pending;
+       unsigned long expire_limit = cb->mark_limit;
+       struct commit_list *leftover = NULL;
 
-static void mark_reachable(struct commit *commit, unsigned long expire_limit)
-{
-       /*
-        * We need to compute whether the commit on either side of a reflog
-        * entry is reachable from the tip of the ref for all entries.
-        * Mark commits that are reachable from the tip down to the
-        * time threshold first; we know a commit marked thusly is
-        * reachable from the tip without running in_merge_bases()
-        * at all.
-        */
-       struct commit_list *pending = NULL;
+       for (pending = cb->mark_list; pending; pending = pending->next)
+               pending->item->object.flags &= ~REACHABLE;
 
-       commit_list_insert(commit, &pending);
+       pending = cb->mark_list;
        while (pending) {
                struct commit_list *entry = pending;
                struct commit_list *parent;
@@ -261,8 +240,11 @@ static void mark_reachable(struct commit *commit, unsigned long expire_limit)
                if (parse_commit(commit))
                        continue;
                commit->object.flags |= REACHABLE;
-               if (commit->date < expire_limit)
+               if (commit->date < expire_limit) {
+                       commit_list_insert(commit, &leftover);
                        continue;
+               }
+               commit->object.flags |= REACHABLE;
                parent = commit->parents;
                while (parent) {
                        commit = parent->item;
@@ -272,6 +254,36 @@ static void mark_reachable(struct commit *commit, unsigned long expire_limit)
                        commit_list_insert(commit, &pending);
                }
        }
+       cb->mark_list = leftover;
+}
+
+static int unreachable(struct expire_reflog_cb *cb, struct commit *commit, unsigned char *sha1)
+{
+       /*
+        * We may or may not have the commit yet - if not, look it
+        * up using the supplied sha1.
+        */
+       if (!commit) {
+               if (is_null_sha1(sha1))
+                       return 0;
+
+               commit = lookup_commit_reference_gently(sha1, 1);
+
+               /* Not a commit -- keep it */
+               if (!commit)
+                       return 0;
+       }
+
+       /* Reachable from the current ref?  Don't prune. */
+       if (commit->object.flags & REACHABLE)
+               return 0;
+
+       if (cb->mark_list && cb->mark_limit) {
+               cb->mark_limit = 0; /* dig down to the root */
+               mark_reachable(cb);
+       }
+
+       return !(commit->object.flags & REACHABLE);
 }
 
 static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
@@ -348,8 +360,12 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,
        cb.ref_commit = lookup_commit_reference_gently(sha1, 1);
        cb.ref = ref;
        cb.cmd = cmd;
-       if (cb.ref_commit)
-               mark_reachable(cb.ref_commit, cmd->expire_total);
+       if (cb.ref_commit) {
+               cb.mark_list = NULL;
+               commit_list_insert(cb.ref_commit, &cb.mark_list);
+               cb.mark_limit = cmd->expire_total;
+               mark_reachable(&cb);
+       }
        for_each_reflog_ent(ref, expire_reflog_ent, &cb);
        if (cb.ref_commit)
                clear_commit_marks(cb.ref_commit, REACHABLE);