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