77f70a62ab0f8fed0ceb5f12efac5d5965cbe227
   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#include "diff.h"
   8#include "revision.h"
   9#include "reachable.h"
  10
  11/*
  12 * reflog expire
  13 */
  14
  15static const char reflog_expire_usage[] =
  16"git-reflog (show|expire) [--verbose] [--dry-run] [--stale-fix] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...";
  17static const char reflog_delete_usage[] =
  18"git-reflog delete [--verbose] [--dry-run] <refs>...";
  19
  20static unsigned long default_reflog_expire;
  21static unsigned long default_reflog_expire_unreachable;
  22
  23struct cmd_reflog_expire_cb {
  24        struct rev_info revs;
  25        int dry_run;
  26        int stalefix;
  27        int verbose;
  28        unsigned long expire_total;
  29        unsigned long expire_unreachable;
  30        int recno;
  31};
  32
  33struct expire_reflog_cb {
  34        FILE *newlog;
  35        const char *ref;
  36        struct commit *ref_commit;
  37        struct cmd_reflog_expire_cb *cmd;
  38};
  39
  40struct collected_reflog {
  41        unsigned char sha1[20];
  42        char reflog[FLEX_ARRAY];
  43};
  44struct collect_reflog_cb {
  45        struct collected_reflog **e;
  46        int alloc;
  47        int nr;
  48};
  49
  50#define INCOMPLETE      (1u<<10)
  51#define STUDYING        (1u<<11)
  52
  53static int tree_is_complete(const unsigned char *sha1)
  54{
  55        struct tree_desc desc;
  56        struct name_entry entry;
  57        int complete;
  58        struct tree *tree;
  59
  60        tree = lookup_tree(sha1);
  61        if (!tree)
  62                return 0;
  63        if (tree->object.flags & SEEN)
  64                return 1;
  65        if (tree->object.flags & INCOMPLETE)
  66                return 0;
  67
  68        if (!tree->buffer) {
  69                enum object_type type;
  70                unsigned long size;
  71                void *data = read_sha1_file(sha1, &type, &size);
  72                if (!data) {
  73                        tree->object.flags |= INCOMPLETE;
  74                        return 0;
  75                }
  76                tree->buffer = data;
  77                tree->size = size;
  78        }
  79        init_tree_desc(&desc, tree->buffer, tree->size);
  80        complete = 1;
  81        while (tree_entry(&desc, &entry)) {
  82                if (!has_sha1_file(entry.sha1) ||
  83                    (S_ISDIR(entry.mode) && !tree_is_complete(entry.sha1))) {
  84                        tree->object.flags |= INCOMPLETE;
  85                        complete = 0;
  86                }
  87        }
  88        free(tree->buffer);
  89        tree->buffer = NULL;
  90
  91        if (complete)
  92                tree->object.flags |= SEEN;
  93        return complete;
  94}
  95
  96static int commit_is_complete(struct commit *commit)
  97{
  98        struct object_array study;
  99        struct object_array found;
 100        int is_incomplete = 0;
 101        int i;
 102
 103        /* early return */
 104        if (commit->object.flags & SEEN)
 105                return 1;
 106        if (commit->object.flags & INCOMPLETE)
 107                return 0;
 108        /*
 109         * Find all commits that are reachable and are not marked as
 110         * SEEN.  Then make sure the trees and blobs contained are
 111         * complete.  After that, mark these commits also as SEEN.
 112         * If some of the objects that are needed to complete this
 113         * commit are missing, mark this commit as INCOMPLETE.
 114         */
 115        memset(&study, 0, sizeof(study));
 116        memset(&found, 0, sizeof(found));
 117        add_object_array(&commit->object, NULL, &study);
 118        add_object_array(&commit->object, NULL, &found);
 119        commit->object.flags |= STUDYING;
 120        while (study.nr) {
 121                struct commit *c;
 122                struct commit_list *parent;
 123
 124                c = (struct commit *)study.objects[--study.nr].item;
 125                if (!c->object.parsed && !parse_object(c->object.sha1))
 126                        c->object.flags |= INCOMPLETE;
 127
 128                if (c->object.flags & INCOMPLETE) {
 129                        is_incomplete = 1;
 130                        break;
 131                }
 132                else if (c->object.flags & SEEN)
 133                        continue;
 134                for (parent = c->parents; parent; parent = parent->next) {
 135                        struct commit *p = parent->item;
 136                        if (p->object.flags & STUDYING)
 137                                continue;
 138                        p->object.flags |= STUDYING;
 139                        add_object_array(&p->object, NULL, &study);
 140                        add_object_array(&p->object, NULL, &found);
 141                }
 142        }
 143        if (!is_incomplete) {
 144                /*
 145                 * make sure all commits in "found" array have all the
 146                 * necessary objects.
 147                 */
 148                for (i = 0; i < found.nr; i++) {
 149                        struct commit *c =
 150                                (struct commit *)found.objects[i].item;
 151                        if (!tree_is_complete(c->tree->object.sha1)) {
 152                                is_incomplete = 1;
 153                                c->object.flags |= INCOMPLETE;
 154                        }
 155                }
 156                if (!is_incomplete) {
 157                        /* mark all found commits as complete, iow SEEN */
 158                        for (i = 0; i < found.nr; i++)
 159                                found.objects[i].item->flags |= SEEN;
 160                }
 161        }
 162        /* clear flags from the objects we traversed */
 163        for (i = 0; i < found.nr; i++)
 164                found.objects[i].item->flags &= ~STUDYING;
 165        if (is_incomplete)
 166                commit->object.flags |= INCOMPLETE;
 167        else {
 168                /*
 169                 * If we come here, we have (1) traversed the ancestry chain
 170                 * from the "commit" until we reach SEEN commits (which are
 171                 * known to be complete), and (2) made sure that the commits
 172                 * encountered during the above traversal refer to trees that
 173                 * are complete.  Which means that we know *all* the commits
 174                 * we have seen during this process are complete.
 175                 */
 176                for (i = 0; i < found.nr; i++)
 177                        found.objects[i].item->flags |= SEEN;
 178        }
 179        /* free object arrays */
 180        free(study.objects);
 181        free(found.objects);
 182        return !is_incomplete;
 183}
 184
 185static int keep_entry(struct commit **it, unsigned char *sha1)
 186{
 187        struct commit *commit;
 188
 189        if (is_null_sha1(sha1))
 190                return 1;
 191        commit = lookup_commit_reference_gently(sha1, 1);
 192        if (!commit)
 193                return 0;
 194
 195        /*
 196         * Make sure everything in this commit exists.
 197         *
 198         * We have walked all the objects reachable from the refs
 199         * and cache earlier.  The commits reachable by this commit
 200         * must meet SEEN commits -- and then we should mark them as
 201         * SEEN as well.
 202         */
 203        if (!commit_is_complete(commit))
 204                return 0;
 205        *it = commit;
 206        return 1;
 207}
 208
 209static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
 210                const char *email, unsigned long timestamp, int tz,
 211                const char *message, void *cb_data)
 212{
 213        struct expire_reflog_cb *cb = cb_data;
 214        struct commit *old, *new;
 215
 216        if (timestamp < cb->cmd->expire_total)
 217                goto prune;
 218
 219        old = new = NULL;
 220        if (cb->cmd->stalefix &&
 221            (!keep_entry(&old, osha1) || !keep_entry(&new, nsha1)))
 222                goto prune;
 223
 224        if (timestamp < cb->cmd->expire_unreachable) {
 225                if (!cb->ref_commit)
 226                        goto prune;
 227                if (!old && !is_null_sha1(osha1))
 228                        old = lookup_commit_reference_gently(osha1, 1);
 229                if (!new && !is_null_sha1(nsha1))
 230                        new = lookup_commit_reference_gently(nsha1, 1);
 231                if ((old && !in_merge_bases(old, &cb->ref_commit, 1)) ||
 232                    (new && !in_merge_bases(new, &cb->ref_commit, 1)))
 233                        goto prune;
 234        }
 235
 236        if (cb->cmd->recno && --(cb->cmd->recno) == 0)
 237                goto prune;
 238
 239        if (cb->newlog) {
 240                char sign = (tz < 0) ? '-' : '+';
 241                int zone = (tz < 0) ? (-tz) : tz;
 242                fprintf(cb->newlog, "%s %s %s %lu %c%04d\t%s",
 243                        sha1_to_hex(osha1), sha1_to_hex(nsha1),
 244                        email, timestamp, sign, zone,
 245                        message);
 246        }
 247        if (cb->cmd->verbose)
 248                printf("keep %s", message);
 249        return 0;
 250 prune:
 251        if (!cb->newlog || cb->cmd->verbose)
 252                printf("%sprune %s", cb->newlog ? "" : "would ", message);
 253        return 0;
 254}
 255
 256static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)
 257{
 258        struct cmd_reflog_expire_cb *cmd = cb_data;
 259        struct expire_reflog_cb cb;
 260        struct ref_lock *lock;
 261        char *log_file, *newlog_path = NULL;
 262        int status = 0;
 263
 264        memset(&cb, 0, sizeof(cb));
 265        /* we take the lock for the ref itself to prevent it from
 266         * getting updated.
 267         */
 268        lock = lock_any_ref_for_update(ref, sha1, 0);
 269        if (!lock)
 270                return error("cannot lock ref '%s'", ref);
 271        log_file = xstrdup(git_path("logs/%s", ref));
 272        if (!file_exists(log_file))
 273                goto finish;
 274        if (!cmd->dry_run) {
 275                newlog_path = xstrdup(git_path("logs/%s.lock", ref));
 276                cb.newlog = fopen(newlog_path, "w");
 277        }
 278
 279        cb.ref_commit = lookup_commit_reference_gently(sha1, 1);
 280        cb.ref = ref;
 281        cb.cmd = cmd;
 282        for_each_reflog_ent(ref, expire_reflog_ent, &cb);
 283 finish:
 284        if (cb.newlog) {
 285                if (fclose(cb.newlog)) {
 286                        status |= error("%s: %s", strerror(errno),
 287                                        newlog_path);
 288                        unlink(newlog_path);
 289                } else if (rename(newlog_path, log_file)) {
 290                        status |= error("cannot rename %s to %s",
 291                                        newlog_path, log_file);
 292                        unlink(newlog_path);
 293                }
 294        }
 295        free(newlog_path);
 296        free(log_file);
 297        unlock_ref(lock);
 298        return status;
 299}
 300
 301static int collect_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)
 302{
 303        struct collected_reflog *e;
 304        struct collect_reflog_cb *cb = cb_data;
 305        size_t namelen = strlen(ref);
 306
 307        e = xmalloc(sizeof(*e) + namelen + 1);
 308        hashcpy(e->sha1, sha1);
 309        memcpy(e->reflog, ref, namelen + 1);
 310        ALLOC_GROW(cb->e, cb->nr + 1, cb->alloc);
 311        cb->e[cb->nr++] = e;
 312        return 0;
 313}
 314
 315static int reflog_expire_config(const char *var, const char *value)
 316{
 317        if (!strcmp(var, "gc.reflogexpire")) {
 318                if (!value)
 319                        config_error_nonbool(var);
 320                default_reflog_expire = approxidate(value);
 321                return 0;
 322        }
 323        if (!strcmp(var, "gc.reflogexpireunreachable")) {
 324                if (!value)
 325                        config_error_nonbool(var);
 326                default_reflog_expire_unreachable = approxidate(value);
 327                return 0;
 328        }
 329        return git_default_config(var, value);
 330}
 331
 332static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
 333{
 334        struct cmd_reflog_expire_cb cb;
 335        unsigned long now = time(NULL);
 336        int i, status, do_all;
 337
 338        git_config(reflog_expire_config);
 339
 340        save_commit_buffer = 0;
 341        do_all = status = 0;
 342        memset(&cb, 0, sizeof(cb));
 343
 344        if (!default_reflog_expire_unreachable)
 345                default_reflog_expire_unreachable = now - 30 * 24 * 3600;
 346        if (!default_reflog_expire)
 347                default_reflog_expire = now - 90 * 24 * 3600;
 348        cb.expire_total = default_reflog_expire;
 349        cb.expire_unreachable = default_reflog_expire_unreachable;
 350
 351        /*
 352         * We can trust the commits and objects reachable from refs
 353         * even in older repository.  We cannot trust what's reachable
 354         * from reflog if the repository was pruned with older git.
 355         */
 356
 357        for (i = 1; i < argc; i++) {
 358                const char *arg = argv[i];
 359                if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
 360                        cb.dry_run = 1;
 361                else if (!prefixcmp(arg, "--expire="))
 362                        cb.expire_total = approxidate(arg + 9);
 363                else if (!prefixcmp(arg, "--expire-unreachable="))
 364                        cb.expire_unreachable = approxidate(arg + 21);
 365                else if (!strcmp(arg, "--stale-fix"))
 366                        cb.stalefix = 1;
 367                else if (!strcmp(arg, "--all"))
 368                        do_all = 1;
 369                else if (!strcmp(arg, "--verbose"))
 370                        cb.verbose = 1;
 371                else if (!strcmp(arg, "--")) {
 372                        i++;
 373                        break;
 374                }
 375                else if (arg[0] == '-')
 376                        usage(reflog_expire_usage);
 377                else
 378                        break;
 379        }
 380        if (cb.stalefix) {
 381                init_revisions(&cb.revs, prefix);
 382                if (cb.verbose)
 383                        printf("Marking reachable objects...");
 384                mark_reachable_objects(&cb.revs, 0);
 385                if (cb.verbose)
 386                        putchar('\n');
 387        }
 388
 389        if (do_all) {
 390                struct collect_reflog_cb collected;
 391                int i;
 392
 393                memset(&collected, 0, sizeof(collected));
 394                for_each_reflog(collect_reflog, &collected);
 395                for (i = 0; i < collected.nr; i++) {
 396                        struct collected_reflog *e = collected.e[i];
 397                        status |= expire_reflog(e->reflog, e->sha1, 0, &cb);
 398                        free(e);
 399                }
 400                free(collected.e);
 401        }
 402
 403        while (i < argc) {
 404                const char *ref = argv[i++];
 405                unsigned char sha1[20];
 406                if (!resolve_ref(ref, sha1, 1, NULL)) {
 407                        status |= error("%s points nowhere!", ref);
 408                        continue;
 409                }
 410                status |= expire_reflog(ref, sha1, 0, &cb);
 411        }
 412        return status;
 413}
 414
 415static int count_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
 416                const char *email, unsigned long timestamp, int tz,
 417                const char *message, void *cb_data)
 418{
 419        struct cmd_reflog_expire_cb *cb = cb_data;
 420        if (!cb->expire_total || timestamp < cb->expire_total)
 421                cb->recno++;
 422        return 0;
 423}
 424
 425static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
 426{
 427        struct cmd_reflog_expire_cb cb;
 428        int i, status = 0;
 429
 430        memset(&cb, 0, sizeof(cb));
 431
 432        for (i = 1; i < argc; i++) {
 433                const char *arg = argv[i];
 434                if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
 435                        cb.dry_run = 1;
 436                else if (!strcmp(arg, "--verbose"))
 437                        cb.verbose = 1;
 438                else if (!strcmp(arg, "--")) {
 439                        i++;
 440                        break;
 441                }
 442                else if (arg[0] == '-')
 443                        usage(reflog_delete_usage);
 444                else
 445                        break;
 446        }
 447
 448        if (argc - i < 1)
 449                return error("Nothing to delete?");
 450
 451        for ( ; i < argc; i++) {
 452                const char *spec = strstr(argv[i], "@{");
 453                unsigned char sha1[20];
 454                char *ep, *ref;
 455                int recno;
 456
 457                if (!spec) {
 458                        status |= error("Not a reflog: %s", argv[i]);
 459                        continue;
 460                }
 461
 462                if (!dwim_ref(argv[i], spec - argv[i], sha1, &ref)) {
 463                        status |= error("%s points nowhere!", argv[i]);
 464                        continue;
 465                }
 466
 467                recno = strtoul(spec + 2, &ep, 10);
 468                if (*ep == '}') {
 469                        cb.recno = -recno;
 470                        for_each_reflog_ent(ref, count_reflog_ent, &cb);
 471                } else {
 472                        cb.expire_total = approxidate(spec + 2);
 473                        for_each_reflog_ent(ref, count_reflog_ent, &cb);
 474                        cb.expire_total = 0;
 475                }
 476
 477                status |= expire_reflog(ref, sha1, 0, &cb);
 478                free(ref);
 479        }
 480        return status;
 481}
 482
 483/*
 484 * main "reflog"
 485 */
 486
 487static const char reflog_usage[] =
 488"git-reflog (expire | ...)";
 489
 490int cmd_reflog(int argc, const char **argv, const char *prefix)
 491{
 492        /* With no command, we default to showing it. */
 493        if (argc < 2 || *argv[1] == '-')
 494                return cmd_log_reflog(argc, argv, prefix);
 495
 496        if (!strcmp(argv[1], "show"))
 497                return cmd_log_reflog(argc - 1, argv + 1, prefix);
 498
 499        if (!strcmp(argv[1], "expire"))
 500                return cmd_reflog_expire(argc - 1, argv + 1, prefix);
 501
 502        if (!strcmp(argv[1], "delete"))
 503                return cmd_reflog_delete(argc - 1, argv + 1, prefix);
 504
 505        /* Not a recognized reflog command..*/
 506        usage(reflog_usage);
 507}