builtin-reflog.con commit Merge branch 'master' into jc/fsck-reflog (90cee09)
   1#include "cache.h"
   2#include "builtin.h"
   3#include "commit.h"
   4#include "refs.h"
   5#include "dir.h"
   6#include <time.h>
   7
   8struct expire_reflog_cb {
   9        FILE *newlog;
  10        const char *ref;
  11        struct commit *ref_commit;
  12        unsigned long expire_total;
  13        unsigned long expire_unreachable;
  14};
  15
  16static int keep_entry(struct commit **it, unsigned char *sha1)
  17{
  18        *it = NULL;
  19        if (is_null_sha1(sha1))
  20                return 1;
  21        *it = lookup_commit_reference_gently(sha1, 1);
  22        return (*it != NULL);
  23}
  24
  25static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
  26                             char *data, void *cb_data)
  27{
  28        struct expire_reflog_cb *cb = cb_data;
  29        unsigned long timestamp;
  30        char *cp, *ep;
  31        struct commit *old, *new;
  32
  33        cp = strchr(data, '>');
  34        if (!cp || *++cp != ' ')
  35                goto prune;
  36        timestamp = strtoul(cp, &ep, 10);
  37        if (*ep != ' ')
  38                goto prune;
  39        if (timestamp < cb->expire_total)
  40                goto prune;
  41
  42        if (!keep_entry(&old, osha1) || !keep_entry(&new, nsha1))
  43                goto prune;
  44
  45        if ((timestamp < cb->expire_unreachable) &&
  46            ((old && !in_merge_bases(old, cb->ref_commit)) ||
  47             (new && !in_merge_bases(new, cb->ref_commit))))
  48                goto prune;
  49
  50        if (cb->newlog)
  51                fprintf(cb->newlog, "%s %s %s",
  52                        sha1_to_hex(osha1), sha1_to_hex(nsha1), data);
  53        return 0;
  54 prune:
  55        if (!cb->newlog)
  56                fprintf(stderr, "would prune %s", data);
  57        return 0;
  58}
  59
  60struct cmd_reflog_expire_cb {
  61        int dry_run;
  62        unsigned long expire_total;
  63        unsigned long expire_unreachable;
  64};
  65
  66static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)
  67{
  68        struct cmd_reflog_expire_cb *cmd = cb_data;
  69        struct expire_reflog_cb cb;
  70        struct ref_lock *lock;
  71        char *newlog_path = NULL;
  72        int status = 0;
  73
  74        if (strncmp(ref, "refs/", 5))
  75                return error("not a ref '%s'", ref);
  76
  77        memset(&cb, 0, sizeof(cb));
  78        /* we take the lock for the ref itself to prevent it from
  79         * getting updated.
  80         */
  81        lock = lock_ref_sha1(ref + 5, sha1);
  82        if (!lock)
  83                return error("cannot lock ref '%s'", ref);
  84        if (!file_exists(lock->log_file))
  85                goto finish;
  86        if (!cmd->dry_run) {
  87                newlog_path = xstrdup(git_path("logs/%s.lock", ref));
  88                cb.newlog = fopen(newlog_path, "w");
  89        }
  90
  91        cb.ref_commit = lookup_commit_reference_gently(sha1, 1);
  92        if (!cb.ref_commit) {
  93                status = error("ref '%s' does not point at a commit", ref);
  94                goto finish;
  95        }
  96        cb.ref = ref;
  97        cb.expire_total = cmd->expire_total;
  98        cb.expire_unreachable = cmd->expire_unreachable;
  99        for_each_reflog_ent(ref, expire_reflog_ent, &cb);
 100 finish:
 101        if (cb.newlog) {
 102                if (fclose(cb.newlog))
 103                        status |= error("%s: %s", strerror(errno),
 104                                        newlog_path);
 105                if (rename(newlog_path, lock->log_file)) {
 106                        status |= error("cannot rename %s to %s",
 107                                        newlog_path, lock->log_file);
 108                        unlink(newlog_path);
 109                }
 110        }
 111        free(newlog_path);
 112        unlock_ref(lock);
 113        return status;
 114}
 115
 116static const char reflog_expire_usage[] =
 117"git-reflog expire [--dry-run] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...";
 118
 119static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
 120{
 121        struct cmd_reflog_expire_cb cb;
 122        unsigned long now = time(NULL);
 123        int i, status, do_all;
 124
 125        save_commit_buffer = 0;
 126        do_all = status = 0;
 127        memset(&cb, 0, sizeof(cb));
 128        cb.expire_total = now - 90 * 24 * 3600;
 129        cb.expire_unreachable = now - 30 * 24 * 3600;
 130
 131        for (i = 1; i < argc; i++) {
 132                const char *arg = argv[i];
 133                if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
 134                        cb.dry_run = 1;
 135                else if (!strncmp(arg, "--expire=", 9))
 136                        cb.expire_total = approxidate(arg + 9);
 137                else if (!strncmp(arg, "--expire-unreachable=", 21))
 138                        cb.expire_unreachable = approxidate(arg + 21);
 139                else if (!strcmp(arg, "--all"))
 140                        do_all = 1;
 141                else if (!strcmp(arg, "--")) {
 142                        i++;
 143                        break;
 144                }
 145                else if (arg[0] == '-')
 146                        usage(reflog_expire_usage);
 147                else
 148                        break;
 149        }
 150        if (do_all)
 151                status |= for_each_ref(expire_reflog, &cb);
 152        while (i < argc) {
 153                const char *ref = argv[i++];
 154                unsigned char sha1[20];
 155                if (!resolve_ref(ref, sha1, 1, NULL)) {
 156                        status |= error("%s points nowhere!", ref);
 157                        continue;
 158                }
 159                status |= expire_reflog(ref, sha1, 0, &cb);
 160        }
 161        return status;
 162}
 163
 164static const char reflog_usage[] =
 165"git-reflog (expire | ...)";
 166
 167int cmd_reflog(int argc, const char **argv, const char *prefix)
 168{
 169        if (argc < 2)
 170                usage(reflog_usage);
 171        else if (!strcmp(argv[1], "expire"))
 172                return cmd_reflog_expire(argc - 1, argv + 1, prefix);
 173        else
 174                usage(reflog_usage);
 175}