diff options
Diffstat (limited to 'builtin-reflog.c')
-rw-r--r-- | builtin-reflog.c | 263 |
1 files changed, 225 insertions, 38 deletions
diff --git a/builtin-reflog.c b/builtin-reflog.c index 280e24e151..749821078d 100644 --- a/builtin-reflog.c +++ b/builtin-reflog.c @@ -13,9 +13,9 @@ */ static const char reflog_expire_usage[] = -"git-reflog (show|expire) [--verbose] [--dry-run] [--stale-fix] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>..."; +"git reflog (show|expire) [--verbose] [--dry-run] [--stale-fix] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>..."; static const char reflog_delete_usage[] = -"git-reflog delete [--verbose] [--dry-run] [--rewrite] [--updateref] <refs>..."; +"git reflog delete [--verbose] [--dry-run] [--rewrite] [--updateref] <refs>..."; static unsigned long default_reflog_expire; static unsigned long default_reflog_expire_unreachable; @@ -52,6 +52,7 @@ struct collect_reflog_cb { #define INCOMPLETE (1u<<10) #define STUDYING (1u<<11) +#define REACHABLE (1u<<12) static int tree_is_complete(const unsigned char *sha1) { @@ -209,6 +210,70 @@ static int keep_entry(struct commit **it, unsigned char *sha1) return 1; } +static int unreachable(struct expire_reflog_cb *cb, struct commit *commit, unsigned char *sha1) +{ + /* + * We may or may not have the commit yet - if not, look it + * up using the supplied sha1. + */ + if (!commit) { + if (is_null_sha1(sha1)) + return 0; + + commit = lookup_commit_reference_gently(sha1, 1); + + /* Not a commit -- keep it */ + if (!commit) + return 0; + } + + /* Reachable from the current ref? Don't prune. */ + if (commit->object.flags & REACHABLE) + return 0; + if (in_merge_bases(commit, &cb->ref_commit, 1)) + return 0; + + /* We can't reach it - prune it. */ + return 1; +} + +static void mark_reachable(struct commit *commit, unsigned long expire_limit) +{ + /* + * We need to compute whether the commit on either side of a reflog + * entry is reachable from the tip of the ref for all entries. + * Mark commits that are reachable from the tip down to the + * time threshold first; we know a commit marked thusly is + * reachable from the tip without running in_merge_bases() + * at all. + */ + struct commit_list *pending = NULL; + + commit_list_insert(commit, &pending); + while (pending) { + struct commit_list *entry = pending; + struct commit_list *parent; + pending = entry->next; + commit = entry->item; + free(entry); + if (commit->object.flags & REACHABLE) + continue; + if (parse_commit(commit)) + continue; + commit->object.flags |= REACHABLE; + if (commit->date < expire_limit) + continue; + parent = commit->parents; + while (parent) { + commit = parent->item; + parent = parent->next; + if (commit->object.flags & REACHABLE) + continue; + commit_list_insert(commit, &pending); + } + } +} + static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, const char *email, unsigned long timestamp, int tz, const char *message, void *cb_data) @@ -230,12 +295,7 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, if (timestamp < cb->cmd->expire_unreachable) { if (!cb->ref_commit) goto prune; - if (!old && !is_null_sha1(osha1)) - old = lookup_commit_reference_gently(osha1, 1); - if (!new && !is_null_sha1(nsha1)) - new = lookup_commit_reference_gently(nsha1, 1); - if ((old && !in_merge_bases(old, &cb->ref_commit, 1)) || - (new && !in_merge_bases(new, &cb->ref_commit, 1))) + if (unreachable(cb, old, osha1) || unreachable(cb, new, nsha1)) goto prune; } @@ -269,24 +329,30 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, int status = 0; memset(&cb, 0, sizeof(cb)); - /* we take the lock for the ref itself to prevent it from + + /* + * we take the lock for the ref itself to prevent it from * getting updated. */ lock = lock_any_ref_for_update(ref, sha1, 0); if (!lock) return error("cannot lock ref '%s'", ref); - log_file = xstrdup(git_path("logs/%s", ref)); + log_file = git_pathdup("logs/%s", ref); if (!file_exists(log_file)) goto finish; if (!cmd->dry_run) { - newlog_path = xstrdup(git_path("logs/%s.lock", ref)); + newlog_path = git_pathdup("logs/%s.lock", ref); cb.newlog = fopen(newlog_path, "w"); } cb.ref_commit = lookup_commit_reference_gently(sha1, 1); cb.ref = ref; cb.cmd = cmd; + if (cb.ref_commit) + mark_reachable(cb.ref_commit, cmd->expire_total); for_each_reflog_ent(ref, expire_reflog_ent, &cb); + if (cb.ref_commit) + clear_commit_marks(cb.ref_commit, REACHABLE); finish: if (cb.newlog) { if (fclose(cb.newlog)) { @@ -296,7 +362,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, } else if (cmd->updateref && (write_in_full(lock->lock_fd, sha1_to_hex(cb.last_kept_sha1), 40) != 40 || - write_in_full(lock->lock_fd, "\n", 1) != 1 || + write_str_in_full(lock->lock_fd, "\n") != 1 || close_ref(lock) < 0)) { status |= error("Couldn't write %s", lock->lk->filename); @@ -307,6 +373,8 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, unlink(newlog_path); } else if (cmd->updateref && commit_ref(lock)) { status |= error("Couldn't set %s", lock->ref_name); + } else { + adjust_shared_perm(log_file); } } free(newlog_path); @@ -329,21 +397,130 @@ static int collect_reflog(const char *ref, const unsigned char *sha1, int unused return 0; } -static int reflog_expire_config(const char *var, const char *value) +static struct reflog_expire_cfg { + struct reflog_expire_cfg *next; + unsigned long expire_total; + unsigned long expire_unreachable; + size_t len; + char pattern[FLEX_ARRAY]; +} *reflog_expire_cfg, **reflog_expire_cfg_tail; + +static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len) { - if (!strcmp(var, "gc.reflogexpire")) { - if (!value) - config_error_nonbool(var); - default_reflog_expire = approxidate(value); + struct reflog_expire_cfg *ent; + + if (!reflog_expire_cfg_tail) + reflog_expire_cfg_tail = &reflog_expire_cfg; + + for (ent = reflog_expire_cfg; ent; ent = ent->next) + if (ent->len == len && + !memcmp(ent->pattern, pattern, len)) + return ent; + + ent = xcalloc(1, (sizeof(*ent) + len)); + memcpy(ent->pattern, pattern, len); + ent->len = len; + *reflog_expire_cfg_tail = ent; + reflog_expire_cfg_tail = &(ent->next); + return ent; +} + +static int parse_expire_cfg_value(const char *var, const char *value, unsigned long *expire) +{ + if (!value) + return config_error_nonbool(var); + if (!strcmp(value, "never") || !strcmp(value, "false")) { + *expire = 0; return 0; } - if (!strcmp(var, "gc.reflogexpireunreachable")) { - if (!value) - config_error_nonbool(var); - default_reflog_expire_unreachable = approxidate(value); + *expire = approxidate(value); + return 0; +} + +/* expiry timer slot */ +#define EXPIRE_TOTAL 01 +#define EXPIRE_UNREACH 02 + +static int reflog_expire_config(const char *var, const char *value, void *cb) +{ + const char *lastdot = strrchr(var, '.'); + unsigned long expire; + int slot; + struct reflog_expire_cfg *ent; + + if (!lastdot || prefixcmp(var, "gc.")) + return git_default_config(var, value, cb); + + if (!strcmp(lastdot, ".reflogexpire")) { + slot = EXPIRE_TOTAL; + if (parse_expire_cfg_value(var, value, &expire)) + return -1; + } else if (!strcmp(lastdot, ".reflogexpireunreachable")) { + slot = EXPIRE_UNREACH; + if (parse_expire_cfg_value(var, value, &expire)) + return -1; + } else + return git_default_config(var, value, cb); + + if (lastdot == var + 2) { + switch (slot) { + case EXPIRE_TOTAL: + default_reflog_expire = expire; + break; + case EXPIRE_UNREACH: + default_reflog_expire_unreachable = expire; + break; + } return 0; } - return git_default_config(var, value); + + ent = find_cfg_ent(var + 3, lastdot - (var+3)); + if (!ent) + return -1; + switch (slot) { + case EXPIRE_TOTAL: + ent->expire_total = expire; + break; + case EXPIRE_UNREACH: + ent->expire_unreachable = expire; + break; + } + return 0; +} + +static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, int slot, const char *ref) +{ + struct reflog_expire_cfg *ent; + + if (slot == (EXPIRE_TOTAL|EXPIRE_UNREACH)) + return; /* both given explicitly -- nothing to tweak */ + + for (ent = reflog_expire_cfg; ent; ent = ent->next) { + if (!fnmatch(ent->pattern, ref, 0)) { + if (!(slot & EXPIRE_TOTAL)) + cb->expire_total = ent->expire_total; + if (!(slot & EXPIRE_UNREACH)) + cb->expire_unreachable = ent->expire_unreachable; + return; + } + } + + /* + * If unconfigured, make stash never expire + */ + if (!strcmp(ref, "refs/stash")) { + if (!(slot & EXPIRE_TOTAL)) + cb->expire_total = 0; + if (!(slot & EXPIRE_UNREACH)) + cb->expire_unreachable = 0; + return; + } + + /* Nothing matched -- use the default value */ + if (!(slot & EXPIRE_TOTAL)) + cb->expire_total = default_reflog_expire; + if (!(slot & EXPIRE_UNREACH)) + cb->expire_unreachable = default_reflog_expire_unreachable; } static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) @@ -351,8 +528,9 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) struct cmd_reflog_expire_cb cb; unsigned long now = time(NULL); int i, status, do_all; + int explicit_expiry = 0; - git_config(reflog_expire_config); + git_config(reflog_expire_config, NULL); save_commit_buffer = 0; do_all = status = 0; @@ -365,20 +543,18 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) cb.expire_total = default_reflog_expire; cb.expire_unreachable = default_reflog_expire_unreachable; - /* - * We can trust the commits and objects reachable from refs - * even in older repository. We cannot trust what's reachable - * from reflog if the repository was pruned with older git. - */ - for (i = 1; i < argc; i++) { const char *arg = argv[i]; if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n")) cb.dry_run = 1; - else if (!prefixcmp(arg, "--expire=")) + else if (!prefixcmp(arg, "--expire=")) { cb.expire_total = approxidate(arg + 9); - else if (!prefixcmp(arg, "--expire-unreachable=")) + explicit_expiry |= EXPIRE_TOTAL; + } + else if (!prefixcmp(arg, "--expire-unreachable=")) { cb.expire_unreachable = approxidate(arg + 21); + explicit_expiry |= EXPIRE_UNREACH; + } else if (!strcmp(arg, "--stale-fix")) cb.stalefix = 1; else if (!strcmp(arg, "--rewrite")) @@ -398,6 +574,12 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) else break; } + + /* + * We can trust the commits and objects reachable from refs + * even in older repository. We cannot trust what's reachable + * from reflog if the repository was pruned with older git. + */ if (cb.stalefix) { init_revisions(&cb.revs, prefix); if (cb.verbose) @@ -415,19 +597,21 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) for_each_reflog(collect_reflog, &collected); for (i = 0; i < collected.nr; i++) { struct collected_reflog *e = collected.e[i]; + set_reflog_expiry_param(&cb, explicit_expiry, e->reflog); status |= expire_reflog(e->reflog, e->sha1, 0, &cb); free(e); } free(collected.e); } - while (i < argc) { - const char *ref = argv[i++]; + for (; i < argc; i++) { + char *ref; unsigned char sha1[20]; - if (!resolve_ref(ref, sha1, 1, NULL)) { - status |= error("%s points nowhere!", ref); + if (!dwim_log(argv[i], strlen(argv[i]), sha1, &ref)) { + status |= error("%s points nowhere!", argv[i]); continue; } + set_reflog_expiry_param(&cb, explicit_expiry, ref); status |= expire_reflog(ref, sha1, 0, &cb); } return status; @@ -484,8 +668,8 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix) continue; } - if (!dwim_ref(argv[i], spec - argv[i], sha1, &ref)) { - status |= error("%s points nowhere!", argv[i]); + if (!dwim_log(argv[i], spec - argv[i], sha1, &ref)) { + status |= error("no reflog for '%s'", argv[i]); continue; } @@ -510,10 +694,13 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix) */ static const char reflog_usage[] = -"git-reflog (expire | ...)"; +"git reflog [ show | expire | delete ]"; int cmd_reflog(int argc, const char **argv, const char *prefix) { + if (argc > 1 && !strcmp(argv[1], "-h")) + usage(reflog_usage); + /* With no command, we default to showing it. */ if (argc < 2 || *argv[1] == '-') return cmd_log_reflog(argc, argv, prefix); |