Merge branch 'bc/reflog-fix' into js/reflog-delete
[gitweb.git] / builtin-reflog.c
index ce093cad78ce8008cd8a60d3ab6be5663a712a9d..8af75bc3e120a390c11c0054b80e6bc852451ac3 100644 (file)
@@ -25,6 +25,7 @@ struct cmd_reflog_expire_cb {
        int verbose;
        unsigned long expire_total;
        unsigned long expire_unreachable;
+       int recno;
 };
 
 struct expire_reflog_cb {
@@ -34,6 +35,16 @@ struct expire_reflog_cb {
        struct cmd_reflog_expire_cb *cmd;
 };
 
+struct collected_reflog {
+       unsigned char sha1[20];
+       char reflog[FLEX_ARRAY];
+};
+struct collect_reflog_cb {
+       struct collected_reflog **e;
+       int alloc;
+       int nr;
+};
+
 #define INCOMPLETE     (1u<<10)
 #define STUDYING       (1u<<11)
 
@@ -220,6 +231,9 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
                        goto prune;
        }
 
+       if (cb->cmd->recno && --(cb->cmd->recno) == 0)
+               goto prune;
+
        if (cb->newlog) {
                char sign = (tz < 0) ? '-' : '+';
                int zone = (tz < 0) ? (-tz) : tz;
@@ -266,10 +280,11 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,
        for_each_reflog_ent(ref, expire_reflog_ent, &cb);
  finish:
        if (cb.newlog) {
-               if (fclose(cb.newlog))
+               if (fclose(cb.newlog)) {
                        status |= error("%s: %s", strerror(errno),
                                        newlog_path);
-               if (rename(newlog_path, log_file)) {
+                       unlink(newlog_path);
+               } else if (rename(newlog_path, log_file)) {
                        status |= error("cannot rename %s to %s",
                                        newlog_path, log_file);
                        unlink(newlog_path);
@@ -281,15 +296,35 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,
        return status;
 }
 
+static int collect_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)
+{
+       struct collected_reflog *e;
+       struct collect_reflog_cb *cb = cb_data;
+       size_t namelen = strlen(ref);
+
+       e = xmalloc(sizeof(*e) + namelen + 1);
+       hashcpy(e->sha1, sha1);
+       memcpy(e->reflog, ref, namelen + 1);
+       ALLOC_GROW(cb->e, cb->nr + 1, cb->alloc);
+       cb->e[cb->nr++] = e;
+       return 0;
+}
+
 static int reflog_expire_config(const char *var, const char *value)
 {
-       if (!strcmp(var, "gc.reflogexpire"))
+       if (!strcmp(var, "gc.reflogexpire")) {
+               if (!value)
+                       config_error_nonbool(var);
                default_reflog_expire = approxidate(value);
-       else if (!strcmp(var, "gc.reflogexpireunreachable"))
+               return 0;
+       }
+       if (!strcmp(var, "gc.reflogexpireunreachable")) {
+               if (!value)
+                       config_error_nonbool(var);
                default_reflog_expire_unreachable = approxidate(value);
-       else
-               return git_default_config(var, value);
-       return 0;
+               return 0;
+       }
+       return git_default_config(var, value);
 }
 
 static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
@@ -349,8 +384,20 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
                        putchar('\n');
        }
 
-       if (do_all)
-               status |= for_each_reflog(expire_reflog, &cb);
+       if (do_all) {
+               struct collect_reflog_cb collected;
+               int i;
+
+               memset(&collected, 0, sizeof(collected));
+               for_each_reflog(collect_reflog, &collected);
+               for (i = 0; i < collected.nr; i++) {
+                       struct collected_reflog *e = collected.e[i];
+                       status |= expire_reflog(e->reflog, e->sha1, 0, &cb);
+                       free(e);
+               }
+               free(collected.e);
+       }
+
        while (i < argc) {
                const char *ref = argv[i++];
                unsigned char sha1[20];
@@ -363,6 +410,58 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
        return status;
 }
 
+static int count_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
+               const char *email, unsigned long timestamp, int tz,
+               const char *message, void *cb_data)
+{
+       struct cmd_reflog_expire_cb *cb = cb_data;
+       if (!cb->expire_total || timestamp < cb->expire_total)
+               cb->recno++;
+       return 0;
+}
+
+static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
+{
+       struct cmd_reflog_expire_cb cb;
+       int i, status = 0;
+
+       if (argc < 2)
+               return error("Nothing to delete?");
+
+       memset(&cb, 0, sizeof(cb));
+
+       for (i = 1; i < argc; i++) {
+               const char *spec = strstr(argv[i], "@{");
+               unsigned char sha1[20];
+               char *ep, *ref;
+               int recno;
+
+               if (!spec) {
+                       status |= error("Not a reflog: %s", argv[i]);
+                       continue;
+               }
+
+               if (!dwim_ref(argv[i], spec - argv[i], sha1, &ref)) {
+                       status |= error("%s points nowhere!", argv[i]);
+                       continue;
+               }
+
+               recno = strtoul(spec + 2, &ep, 10);
+               if (*ep == '}') {
+                       cb.recno = -recno;
+                       for_each_reflog_ent(ref, count_reflog_ent, &cb);
+               } else {
+                       cb.expire_total = approxidate(spec + 2);
+                       for_each_reflog_ent(ref, count_reflog_ent, &cb);
+                       cb.expire_total = 0;
+               }
+
+               status |= expire_reflog(ref, sha1, 0, &cb);
+               free(ref);
+       }
+       return status;
+}
+
 /*
  * main "reflog"
  */
@@ -382,6 +481,9 @@ int cmd_reflog(int argc, const char **argv, const char *prefix)
        if (!strcmp(argv[1], "expire"))
                return cmd_reflog_expire(argc - 1, argv + 1, prefix);
 
+       if (!strcmp(argv[1], "delete"))
+               return cmd_reflog_delete(argc - 1, argv + 1, prefix);
+
        /* Not a recognized reflog command..*/
        usage(reflog_usage);
 }