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