Merge branch 'jc/fsck-reflog'
authorJunio C Hamano <junkio@cox.net>
Wed, 27 Dec 2006 07:47:40 +0000 (23:47 -0800)
committerJunio C Hamano <junkio@cox.net>
Wed, 27 Dec 2006 07:47:40 +0000 (23:47 -0800)
* jc/fsck-reflog:
Add git-reflog to .gitignore
reflog expire: do not punt on tags that point at non commits.
reflog expire: prune commits that are not incomplete
Don't crash during repack of a reflog with pruned commits.
git reflog expire
Move in_merge_bases() to commit.c
reflog: fix warning message.
Teach git-repack to preserve objects referred to by reflog entries.
Protect commits recorded in reflog from pruning.
add for_each_reflog_ent() iterator

15 files changed:
.gitignore
Makefile
builtin-branch.c
builtin-pack-objects.c
builtin-prune.c
builtin-reflog.c [new file with mode: 0644]
builtin.h
commit.c
commit.h
fsck-objects.c
git-repack.sh
git.c
refs.c
refs.h
revision.c
index 98e513de5309f61d9c35591c92d364370affb325..60e5002bd55bfc5565cfc5f77084bfc900ae8f4e 100644 (file)
@@ -89,6 +89,7 @@ git-quiltimport
 git-read-tree
 git-rebase
 git-receive-pack
+git-reflog
 git-relink
 git-repack
 git-repo-config
index 475047f100ef1a2a9cf73557eba3b300e74cac9d..52d4a3a86a214a99bd3cf32053539e18f36778a7 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -287,6 +287,7 @@ BUILTIN_OBJS = \
        builtin-prune-packed.o \
        builtin-push.o \
        builtin-read-tree.o \
+       builtin-reflog.o \
        builtin-repo-config.o \
        builtin-rerere.o \
        builtin-rev-list.o \
index 903d5cf05600f9a7e297dc9450ad5980388072e9..745ee04d6e4a8f8b3ea96d35ec65d7a03d1441de 100644 (file)
@@ -74,25 +74,6 @@ const char *branch_get_color(enum color_branch ix)
        return "";
 }
 
-static int in_merge_bases(const unsigned char *sha1,
-                         struct commit *rev1,
-                         struct commit *rev2)
-{
-       struct commit_list *bases, *b;
-       int ret = 0;
-
-       bases = get_merge_bases(rev1, rev2, 1);
-       for (b = bases; b; b = b->next) {
-               if (!hashcmp(sha1, b->item->object.sha1)) {
-                       ret = 1;
-                       break;
-               }
-       }
-
-       free_commit_list(bases);
-       return ret;
-}
-
 static int delete_branches(int argc, const char **argv, int force, int kinds)
 {
        struct commit *rev, *head_rev = head_rev;
@@ -153,7 +134,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
                 */
 
                if (!force &&
-                   !in_merge_bases(sha1, rev, head_rev)) {
+                   !in_merge_bases(rev, head_rev)) {
                        error("The branch '%s' is not a strict subset of "
                                "your current HEAD.\n"
                                "If you are sure you want to delete it, "
index 807be8c3f8a051d9171cf2ee3c9f973f77a2ff08..9e15beb3ba1e66987bd1fec51ed864414ffbf07c 100644 (file)
@@ -17,7 +17,7 @@ static const char pack_usage[] = "\
 git-pack-objects [{ -q | --progress | --all-progress }] \n\
        [--local] [--incremental] [--window=N] [--depth=N] \n\
        [--no-reuse-delta] [--delta-base-offset] [--non-empty] \n\
-       [--revs [--unpacked | --all]*] [--stdout | base-name] \n\
+       [--revs [--unpacked | --all]*] [--reflog] [--stdout | base-name] \n\
        [<ref-list | <object-list]";
 
 struct object_entry {
@@ -1575,6 +1575,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                }
                if (!strcmp("--unpacked", arg) ||
                    !strncmp("--unpacked=", arg, 11) ||
+                   !strcmp("--reflog", arg) ||
                    !strcmp("--all", arg)) {
                        use_internal_rev_list = 1;
                        if (ARRAY_SIZE(rp_av) - 1 <= rp_ac)
index 8591d28b8e91c94636e9bf8b7e8ff5abcc0705e9..00a53b36479a25a51734fa359ebf2f649795437b 100644 (file)
@@ -181,12 +181,28 @@ static void walk_commit_list(struct rev_info *revs)
        }
 }
 
+static int add_one_reflog_ent(unsigned char *osha1, unsigned char *nsha1, char *datail, void *cb_data)
+{
+       struct object *object;
+
+       object = parse_object(osha1);
+       if (object)
+               add_pending_object(&revs, object, "");
+       object = parse_object(nsha1);
+       if (object)
+               add_pending_object(&revs, object, "");
+       return 0;
+}
+
 static int add_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
 {
        struct object *object = parse_object(sha1);
        if (!object)
                die("bad object ref: %s:%s", path, sha1_to_hex(sha1));
        add_pending_object(&revs, object, "");
+
+       for_each_reflog_ent(path, add_one_reflog_ent, NULL);
+
        return 0;
 }
 
diff --git a/builtin-reflog.c b/builtin-reflog.c
new file mode 100644 (file)
index 0000000..de31967
--- /dev/null
@@ -0,0 +1,212 @@
+#include "cache.h"
+#include "builtin.h"
+#include "commit.h"
+#include "refs.h"
+#include "dir.h"
+#include "tree-walk.h"
+
+struct expire_reflog_cb {
+       FILE *newlog;
+       const char *ref;
+       struct commit *ref_commit;
+       unsigned long expire_total;
+       unsigned long expire_unreachable;
+};
+
+static int tree_is_complete(const unsigned char *sha1)
+{
+       struct tree_desc desc;
+       void *buf;
+       char type[20];
+
+       buf = read_sha1_file(sha1, type, &desc.size);
+       if (!buf)
+               return 0;
+       desc.buf = buf;
+       while (desc.size) {
+               const unsigned char *elem;
+               const char *name;
+               unsigned mode;
+
+               elem = tree_entry_extract(&desc, &name, &mode);
+               if (!has_sha1_file(elem) ||
+                   (S_ISDIR(mode) && !tree_is_complete(elem))) {
+                       free(buf);
+                       return 0;
+               }
+               update_tree_entry(&desc);
+       }
+       free(buf);
+       return 1;
+}
+
+static int keep_entry(struct commit **it, unsigned char *sha1)
+{
+       struct commit *commit;
+
+       *it = NULL;
+       if (is_null_sha1(sha1))
+               return 1;
+       commit = lookup_commit_reference_gently(sha1, 1);
+       if (!commit)
+               return 0;
+
+       /* Make sure everything in this commit exists. */
+       parse_object(commit->object.sha1);
+       if (!tree_is_complete(commit->tree->object.sha1))
+               return 0;
+       *it = commit;
+       return 1;
+}
+
+static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
+                            char *data, void *cb_data)
+{
+       struct expire_reflog_cb *cb = cb_data;
+       unsigned long timestamp;
+       char *cp, *ep;
+       struct commit *old, *new;
+
+       cp = strchr(data, '>');
+       if (!cp || *++cp != ' ')
+               goto prune;
+       timestamp = strtoul(cp, &ep, 10);
+       if (*ep != ' ')
+               goto prune;
+       if (timestamp < cb->expire_total)
+               goto prune;
+
+       if (!keep_entry(&old, osha1) || !keep_entry(&new, nsha1))
+               goto prune;
+
+       if ((timestamp < cb->expire_unreachable) &&
+           (!cb->ref_commit ||
+            (old && !in_merge_bases(old, cb->ref_commit)) ||
+            (new && !in_merge_bases(new, cb->ref_commit))))
+               goto prune;
+
+       if (cb->newlog)
+               fprintf(cb->newlog, "%s %s %s",
+                       sha1_to_hex(osha1), sha1_to_hex(nsha1), data);
+       return 0;
+ prune:
+       if (!cb->newlog)
+               fprintf(stderr, "would prune %s", data);
+       return 0;
+}
+
+struct cmd_reflog_expire_cb {
+       int dry_run;
+       unsigned long expire_total;
+       unsigned long expire_unreachable;
+};
+
+static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)
+{
+       struct cmd_reflog_expire_cb *cmd = cb_data;
+       struct expire_reflog_cb cb;
+       struct ref_lock *lock;
+       char *newlog_path = NULL;
+       int status = 0;
+
+       if (strncmp(ref, "refs/", 5))
+               return error("not a ref '%s'", ref);
+
+       memset(&cb, 0, sizeof(cb));
+       /* we take the lock for the ref itself to prevent it from
+        * getting updated.
+        */
+       lock = lock_ref_sha1(ref + 5, sha1);
+       if (!lock)
+               return error("cannot lock ref '%s'", ref);
+       if (!file_exists(lock->log_file))
+               goto finish;
+       if (!cmd->dry_run) {
+               newlog_path = xstrdup(git_path("logs/%s.lock", ref));
+               cb.newlog = fopen(newlog_path, "w");
+       }
+
+       cb.ref_commit = lookup_commit_reference_gently(sha1, 1);
+       if (!cb.ref_commit)
+               fprintf(stderr,
+                       "warning: ref '%s' does not point at a commit\n", ref);
+       cb.ref = ref;
+       cb.expire_total = cmd->expire_total;
+       cb.expire_unreachable = cmd->expire_unreachable;
+       for_each_reflog_ent(ref, expire_reflog_ent, &cb);
+ finish:
+       if (cb.newlog) {
+               if (fclose(cb.newlog))
+                       status |= error("%s: %s", strerror(errno),
+                                       newlog_path);
+               if (rename(newlog_path, lock->log_file)) {
+                       status |= error("cannot rename %s to %s",
+                                       newlog_path, lock->log_file);
+                       unlink(newlog_path);
+               }
+       }
+       free(newlog_path);
+       unlock_ref(lock);
+       return status;
+}
+
+static const char reflog_expire_usage[] =
+"git-reflog expire [--dry-run] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...";
+
+static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
+{
+       struct cmd_reflog_expire_cb cb;
+       unsigned long now = time(NULL);
+       int i, status, do_all;
+
+       save_commit_buffer = 0;
+       do_all = status = 0;
+       memset(&cb, 0, sizeof(cb));
+       cb.expire_total = now - 90 * 24 * 3600;
+       cb.expire_unreachable = now - 30 * 24 * 3600;
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
+                       cb.dry_run = 1;
+               else if (!strncmp(arg, "--expire=", 9))
+                       cb.expire_total = approxidate(arg + 9);
+               else if (!strncmp(arg, "--expire-unreachable=", 21))
+                       cb.expire_unreachable = approxidate(arg + 21);
+               else if (!strcmp(arg, "--all"))
+                       do_all = 1;
+               else if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+               else if (arg[0] == '-')
+                       usage(reflog_expire_usage);
+               else
+                       break;
+       }
+       if (do_all)
+               status |= for_each_ref(expire_reflog, &cb);
+       while (i < argc) {
+               const char *ref = argv[i++];
+               unsigned char sha1[20];
+               if (!resolve_ref(ref, sha1, 1, NULL)) {
+                       status |= error("%s points nowhere!", ref);
+                       continue;
+               }
+               status |= expire_reflog(ref, sha1, 0, &cb);
+       }
+       return status;
+}
+
+static const char reflog_usage[] =
+"git-reflog (expire | ...)";
+
+int cmd_reflog(int argc, const char **argv, const char *prefix)
+{
+       if (argc < 2)
+               usage(reflog_usage);
+       else if (!strcmp(argv[1], "expire"))
+               return cmd_reflog_expire(argc - 1, argv + 1, prefix);
+       else
+               usage(reflog_usage);
+}
index 8ffd8b2653a4879f6e5ff59d6ae8a82cae34a2ae..df72d09447d0edd17d07eb97a9b3b36fa4b57531 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -51,6 +51,7 @@ extern int cmd_prune(int argc, const char **argv, const char *prefix);
 extern int cmd_prune_packed(int argc, const char **argv, const char *prefix);
 extern int cmd_push(int argc, const char **argv, const char *prefix);
 extern int cmd_read_tree(int argc, const char **argv, const char *prefix);
+extern int cmd_reflog(int argc, const char **argv, const char *prefix);
 extern int cmd_repo_config(int argc, const char **argv, const char *prefix);
 extern int cmd_rerere(int argc, const char **argv, const char *prefix);
 extern int cmd_rev_list(int argc, const char **argv, const char *prefix);
index 289ef65eb1162ff8f386bf31fa6ee27008bb3096..3167ce62acd5eb1076a4c089b54f2b37f821130b 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -1009,3 +1009,20 @@ struct commit_list *get_merge_bases(struct commit *one,
        free(rslt);
        return result;
 }
+
+int in_merge_bases(struct commit *rev1, struct commit *rev2)
+{
+       struct commit_list *bases, *b;
+       int ret = 0;
+
+       bases = get_merge_bases(rev1, rev2, 1);
+       for (b = bases; b; b = b->next) {
+               if (!hashcmp(rev1->object.sha1, b->item->object.sha1)) {
+                       ret = 1;
+                       break;
+               }
+       }
+
+       free_commit_list(bases);
+       return ret;
+}
index fc13de9780f98c3bd9f330ef6177fd47a4da3d80..10eea9f26ff5db9df82245756c9c8bff0f6be3f6 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -107,4 +107,5 @@ int read_graft_file(const char *graft_file);
 
 extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2, int cleanup);
 
+int in_merge_bases(struct commit *rev1, struct commit *rev2);
 #endif /* COMMIT_H */
index 409aea02b4f8570dbaf0a8425eb29e5d45515367..1cc3b399bcf2b8ef875ded8dfcff5aef0f345811 100644 (file)
@@ -399,6 +399,25 @@ static void fsck_dir(int i, char *path)
 
 static int default_refs;
 
+static int fsck_handle_reflog_ent(unsigned char *osha1, unsigned char *nsha1, char *datail, void *cb_data)
+{
+       struct object *obj;
+
+       if (!is_null_sha1(osha1)) {
+               obj = lookup_object(osha1);
+               if (obj) {
+                       obj->used = 1;
+                       mark_reachable(obj, REACHABLE);
+               }
+       }
+       obj = lookup_object(nsha1);
+       if (obj) {
+               obj->used = 1;
+               mark_reachable(obj, REACHABLE);
+       }
+       return 0;
+}
+
 static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
 {
        struct object *obj;
@@ -416,6 +435,9 @@ static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int f
        default_refs++;
        obj->used = 1;
        mark_reachable(obj, REACHABLE);
+
+       for_each_reflog_ent(refname, fsck_handle_reflog_ent, NULL);
+
        return 0;
 }
 
index 067898f1207736732c2c80eabbef5537f8dfef69..375434b1dcdd3183c85b1278c4415e9136508192 100755 (executable)
@@ -62,7 +62,7 @@ case ",$all_into_one," in
 esac
 
 args="$args $local $quiet $no_reuse_delta$extra"
-name=$(git-pack-objects --non-empty --all $args </dev/null "$PACKTMP") ||
+name=$(git-pack-objects --non-empty --all --reflog $args </dev/null "$PACKTMP") ||
        exit 1
 if [ -z "$name" ]; then
        echo Nothing new to pack.
diff --git a/git.c b/git.c
index e732a098fc164da10ed9f41113c7d32d1b022966..50ebd869ad47cb2803a5a1e581442d7e72842034 100644 (file)
--- a/git.c
+++ b/git.c
@@ -246,6 +246,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
                { "prune-packed", cmd_prune_packed, RUN_SETUP },
                { "push", cmd_push, RUN_SETUP },
                { "read-tree", cmd_read_tree, RUN_SETUP },
+               { "reflog", cmd_reflog, RUN_SETUP },
                { "repo-config", cmd_repo_config },
                { "rerere", cmd_rerere, RUN_SETUP },
                { "rev-list", cmd_rev_list, RUN_SETUP },
diff --git a/refs.c b/refs.c
index a101ff3bf88dafa56ac85fcd977c0ae6be1b495e..8b2a3c13784166e93e15be6f9222d26272a3b78b 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -1013,7 +1013,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
 {
        const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec;
        char *tz_c;
-       int logfd, tz;
+       int logfd, tz, reccnt = 0;
        struct stat st;
        unsigned long date;
        unsigned char logged_sha1[20];
@@ -1031,6 +1031,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
        lastrec = NULL;
        rec = logend = logdata + st.st_size;
        while (logdata < rec) {
+               reccnt++;
                if (logdata < rec && *(rec-1) == '\n')
                        rec--;
                lastgt = NULL;
@@ -1087,7 +1088,37 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
        if (get_sha1_hex(logdata, sha1))
                die("Log %s is corrupt.", logfile);
        munmap((void*)logdata, st.st_size);
-       fprintf(stderr, "warning: Log %s only goes back to %s.\n",
-               logfile, show_rfc2822_date(date, tz));
+       if (at_time)
+               fprintf(stderr, "warning: Log %s only goes back to %s.\n",
+                       logfile, show_rfc2822_date(date, tz));
+       else
+               fprintf(stderr, "warning: Log %s only has %d entries.\n",
+                       logfile, reccnt);
        return 0;
 }
+
+void for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
+{
+       const char *logfile;
+       FILE *logfp;
+       char buf[1024];
+
+       logfile = git_path("logs/%s", ref);
+       logfp = fopen(logfile, "r");
+       if (!logfp)
+               return;
+       while (fgets(buf, sizeof(buf), logfp)) {
+               unsigned char osha1[20], nsha1[20];
+               int len;
+
+               /* old SP new SP name <email> SP time TAB msg LF */
+               len = strlen(buf);
+               if (len < 83 || buf[len-1] != '\n' ||
+                   get_sha1_hex(buf, osha1) || buf[40] != ' ' ||
+                   get_sha1_hex(buf + 41, nsha1) || buf[81] != ' ')
+                       continue; /* corrupt? */
+               fn(osha1, nsha1, buf+82, cb_data);
+       }
+       fclose(logfp);
+}
+
diff --git a/refs.h b/refs.h
index 51aab1e6bf592ed6412289695d6fe33e5140fab2..de43cc768af559538b2db9f3383bbf2e08713ae0 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -44,6 +44,10 @@ extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, cons
 /** Reads log for the value of ref during at_time. **/
 extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1);
 
+/* iterate over reflog entries */
+typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, char *, void *);
+void for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data);
+
 /** Returns 0 if target has the right format for a ref. **/
 extern int check_ref_format(const char *target);
 
index e7eccd9180f855f7ff0df5c4515a6d98288fd127..af9f87418c6ed342e0a3d751b8f8e59fe5e8aeed 100644 (file)
@@ -464,21 +464,69 @@ static void limit_list(struct rev_info *revs)
        revs->commits = newlist;
 }
 
-static int all_flags;
-static struct rev_info *all_revs;
+struct all_refs_cb {
+       int all_flags;
+       int warned_bad_reflog;
+       struct rev_info *all_revs;
+       const char *name_for_errormsg;
+};
 
 static int handle_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
 {
-       struct object *object = get_reference(all_revs, path, sha1, all_flags);
-       add_pending_object(all_revs, object, "");
+       struct all_refs_cb *cb = cb_data;
+       struct object *object = get_reference(cb->all_revs, path, sha1,
+                                             cb->all_flags);
+       add_pending_object(cb->all_revs, object, "");
        return 0;
 }
 
 static void handle_all(struct rev_info *revs, unsigned flags)
 {
-       all_revs = revs;
-       all_flags = flags;
-       for_each_ref(handle_one_ref, NULL);
+       struct all_refs_cb cb;
+       cb.all_revs = revs;
+       cb.all_flags = flags;
+       for_each_ref(handle_one_ref, &cb);
+}
+
+static void handle_one_reflog_commit(unsigned char *sha1, void *cb_data)
+{
+       struct all_refs_cb *cb = cb_data;
+       if (!is_null_sha1(sha1)) {
+               struct object *o = parse_object(sha1);
+               if (o) {
+                       o->flags |= cb->all_flags;
+                       add_pending_object(cb->all_revs, o, "");
+               }
+               else if (!cb->warned_bad_reflog) {
+                       warn("reflog of '%s' references pruned commits",
+                               cb->name_for_errormsg);
+                       cb->warned_bad_reflog = 1;
+               }
+       }
+}
+
+static int handle_one_reflog_ent(unsigned char *osha1, unsigned char *nsha1, char *detail, void *cb_data)
+{
+       handle_one_reflog_commit(osha1, cb_data);
+       handle_one_reflog_commit(nsha1, cb_data);
+       return 0;
+}
+
+static int handle_one_reflog(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+{
+       struct all_refs_cb *cb = cb_data;
+       cb->warned_bad_reflog = 0;
+       cb->name_for_errormsg = path;
+       for_each_reflog_ent(path, handle_one_reflog_ent, cb_data);
+       return 0;
+}
+
+static void handle_reflog(struct rev_info *revs, unsigned flags)
+{
+       struct all_refs_cb cb;
+       cb.all_revs = revs;
+       cb.all_flags = flags;
+       for_each_ref(handle_one_reflog, &cb);
 }
 
 static int add_parents_only(struct rev_info *revs, const char *arg, int flags)
@@ -810,6 +858,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                handle_all(revs, flags);
                                continue;
                        }
+                       if (!strcmp(arg, "--reflog")) {
+                               handle_reflog(revs, flags);
+                               continue;
+                       }
                        if (!strcmp(arg, "--not")) {
                                flags ^= UNINTERESTING;
                                continue;