builtin-reflog.con commit reflog expire: prune commits that are not incomplete (8d8b9f6)
   1#include "cache.h"
   2#include "builtin.h"
   3#include "commit.h"
   4#include "refs.h"
   5#include "dir.h"
   6#include "tree-walk.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 tree_is_complete(const unsigned char *sha1)
  17{
  18        struct tree_desc desc;
  19        void *buf;
  20        char type[20];
  21
  22        buf = read_sha1_file(sha1, type, &desc.size);
  23        if (!buf)
  24                return 0;
  25        desc.buf = buf;
  26        while (desc.size) {
  27                const unsigned char *elem;
  28                const char *name;
  29                unsigned mode;
  30
  31                elem = tree_entry_extract(&desc, &name, &mode);
  32                if (!has_sha1_file(elem) ||
  33                    (S_ISDIR(mode) && !tree_is_complete(elem))) {
  34                        free(buf);
  35                        return 0;
  36                }
  37                update_tree_entry(&desc);
  38        }
  39        free(buf);
  40        return 1;
  41}
  42
  43static int keep_entry(struct commit **it, unsigned char *sha1)
  44{
  45        struct commit *commit;
  46
  47        *it = NULL;
  48        if (is_null_sha1(sha1))
  49                return 1;
  50        commit = lookup_commit_reference_gently(sha1, 1);
  51        if (!commit)
  52                return 0;
  53
  54        /* Make sure everything in this commit exists. */
  55        parse_object(commit->object.sha1);
  56        if (!tree_is_complete(commit->tree->object.sha1))
  57                return 0;
  58        *it = commit;
  59        return 1;
  60}
  61
  62static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
  63                             char *data, void *cb_data)
  64{
  65        struct expire_reflog_cb *cb = cb_data;
  66        unsigned long timestamp;
  67        char *cp, *ep;
  68        struct commit *old, *new;
  69
  70        cp = strchr(data, '>');
  71        if (!cp || *++cp != ' ')
  72                goto prune;
  73        timestamp = strtoul(cp, &ep, 10);
  74        if (*ep != ' ')
  75                goto prune;
  76        if (timestamp < cb->expire_total)
  77                goto prune;
  78
  79        if (!keep_entry(&old, osha1) || !keep_entry(&new, nsha1))
  80                goto prune;
  81
  82        if ((timestamp < cb->expire_unreachable) &&
  83            ((old && !in_merge_bases(old, cb->ref_commit)) ||
  84             (new && !in_merge_bases(new, cb->ref_commit))))
  85                goto prune;
  86
  87        if (cb->newlog)
  88                fprintf(cb->newlog, "%s %s %s",
  89                        sha1_to_hex(osha1), sha1_to_hex(nsha1), data);
  90        return 0;
  91 prune:
  92        if (!cb->newlog)
  93                fprintf(stderr, "would prune %s", data);
  94        return 0;
  95}
  96
  97struct cmd_reflog_expire_cb {
  98        int dry_run;
  99        unsigned long expire_total;
 100        unsigned long expire_unreachable;
 101};
 102
 103static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)
 104{
 105        struct cmd_reflog_expire_cb *cmd = cb_data;
 106        struct expire_reflog_cb cb;
 107        struct ref_lock *lock;
 108        char *newlog_path = NULL;
 109        int status = 0;
 110
 111        if (strncmp(ref, "refs/", 5))
 112                return error("not a ref '%s'", ref);
 113
 114        memset(&cb, 0, sizeof(cb));
 115        /* we take the lock for the ref itself to prevent it from
 116         * getting updated.
 117         */
 118        lock = lock_ref_sha1(ref + 5, sha1);
 119        if (!lock)
 120                return error("cannot lock ref '%s'", ref);
 121        if (!file_exists(lock->log_file))
 122                goto finish;
 123        if (!cmd->dry_run) {
 124                newlog_path = xstrdup(git_path("logs/%s.lock", ref));
 125                cb.newlog = fopen(newlog_path, "w");
 126        }
 127
 128        cb.ref_commit = lookup_commit_reference_gently(sha1, 1);
 129        if (!cb.ref_commit) {
 130                status = error("ref '%s' does not point at a commit", ref);
 131                goto finish;
 132        }
 133        cb.ref = ref;
 134        cb.expire_total = cmd->expire_total;
 135        cb.expire_unreachable = cmd->expire_unreachable;
 136        for_each_reflog_ent(ref, expire_reflog_ent, &cb);
 137 finish:
 138        if (cb.newlog) {
 139                if (fclose(cb.newlog))
 140                        status |= error("%s: %s", strerror(errno),
 141                                        newlog_path);
 142                if (rename(newlog_path, lock->log_file)) {
 143                        status |= error("cannot rename %s to %s",
 144                                        newlog_path, lock->log_file);
 145                        unlink(newlog_path);
 146                }
 147        }
 148        free(newlog_path);
 149        unlock_ref(lock);
 150        return status;
 151}
 152
 153static const char reflog_expire_usage[] =
 154"git-reflog expire [--dry-run] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...";
 155
 156static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
 157{
 158        struct cmd_reflog_expire_cb cb;
 159        unsigned long now = time(NULL);
 160        int i, status, do_all;
 161
 162        save_commit_buffer = 0;
 163        do_all = status = 0;
 164        memset(&cb, 0, sizeof(cb));
 165        cb.expire_total = now - 90 * 24 * 3600;
 166        cb.expire_unreachable = now - 30 * 24 * 3600;
 167
 168        for (i = 1; i < argc; i++) {
 169                const char *arg = argv[i];
 170                if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
 171                        cb.dry_run = 1;
 172                else if (!strncmp(arg, "--expire=", 9))
 173                        cb.expire_total = approxidate(arg + 9);
 174                else if (!strncmp(arg, "--expire-unreachable=", 21))
 175                        cb.expire_unreachable = approxidate(arg + 21);
 176                else if (!strcmp(arg, "--all"))
 177                        do_all = 1;
 178                else if (!strcmp(arg, "--")) {
 179                        i++;
 180                        break;
 181                }
 182                else if (arg[0] == '-')
 183                        usage(reflog_expire_usage);
 184                else
 185                        break;
 186        }
 187        if (do_all)
 188                status |= for_each_ref(expire_reflog, &cb);
 189        while (i < argc) {
 190                const char *ref = argv[i++];
 191                unsigned char sha1[20];
 192                if (!resolve_ref(ref, sha1, 1, NULL)) {
 193                        status |= error("%s points nowhere!", ref);
 194                        continue;
 195                }
 196                status |= expire_reflog(ref, sha1, 0, &cb);
 197        }
 198        return status;
 199}
 200
 201static const char reflog_usage[] =
 202"git-reflog (expire | ...)";
 203
 204int cmd_reflog(int argc, const char **argv, const char *prefix)
 205{
 206        if (argc < 2)
 207                usage(reflog_usage);
 208        else if (!strcmp(argv[1], "expire"))
 209                return cmd_reflog_expire(argc - 1, argv + 1, prefix);
 210        else
 211                usage(reflog_usage);
 212}