diff options
Diffstat (limited to 'builtin-reflog.c')
-rw-r--r-- | builtin-reflog.c | 159 |
1 files changed, 138 insertions, 21 deletions
diff --git a/builtin-reflog.c b/builtin-reflog.c index d3f2f50d2b..1da7da0916 100644 --- a/builtin-reflog.c +++ b/builtin-reflog.c @@ -4,16 +4,34 @@ #include "refs.h" #include "dir.h" #include "tree-walk.h" +#include "diff.h" +#include "revision.h" +#include "reachable.h" + +/* + * reflog expire + */ + +static const char reflog_expire_usage[] = +"git-reflog expire [--verbose] [--dry-run] [--fix-stale] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>..."; static unsigned long default_reflog_expire; static unsigned long default_reflog_expire_unreachable; +struct cmd_reflog_expire_cb { + struct rev_info revs; + int dry_run; + int stalefix; + int verbose; + unsigned long expire_total; + unsigned long expire_unreachable; +}; + struct expire_reflog_cb { FILE *newlog; const char *ref; struct commit *ref_commit; - unsigned long expire_total; - unsigned long expire_unreachable; + struct cmd_reflog_expire_cb *cmd; }; static int tree_is_complete(const unsigned char *sha1) @@ -43,6 +61,83 @@ static int tree_is_complete(const unsigned char *sha1) return 1; } +#define INCOMPLETE (1u<<10) +#define STUDYING (1u<<11) + +static int commit_is_complete(struct commit *commit) +{ + struct object_array study; + struct object_array found; + int is_incomplete = 0; + int i; + + /* early return */ + if (commit->object.flags & SEEN) + return 1; + if (commit->object.flags & INCOMPLETE) + return 0; + /* + * Find all commits that are reachable and are not marked as + * SEEN. Then make sure the trees and blobs contained are + * complete. After that, mark these commits also as SEEN. + * If some of the objects that are needed to complete this + * commit are missing, mark this commit as INCOMPLETE. + */ + memset(&study, 0, sizeof(study)); + memset(&found, 0, sizeof(found)); + add_object_array(&commit->object, NULL, &study); + add_object_array(&commit->object, NULL, &found); + commit->object.flags |= STUDYING; + while (study.nr) { + struct commit *c; + struct commit_list *parent; + + c = (struct commit *)study.objects[--study.nr].item; + if (!c->object.parsed && !parse_object(c->object.sha1)) + c->object.flags |= INCOMPLETE; + + if (c->object.flags & INCOMPLETE) { + is_incomplete = 1; + break; + } + else if (c->object.flags & SEEN) + continue; + for (parent = c->parents; parent; parent = parent->next) { + struct commit *p = parent->item; + if (p->object.flags & STUDYING) + continue; + p->object.flags |= STUDYING; + add_object_array(&p->object, NULL, &study); + add_object_array(&p->object, NULL, &found); + } + } + if (!is_incomplete) { + /* make sure all commits in found have all the + * necessary objects. + */ + for (i = 0; !is_incomplete && i < found.nr; i++) { + struct commit *c = + (struct commit *)found.objects[i].item; + if (!tree_is_complete(c->tree->object.sha1)) + is_incomplete = 1; + } + if (!is_incomplete) { + /* mark all found commits as complete, iow SEEN */ + for (i = 0; i < found.nr; i++) + found.objects[i].item->flags |= SEEN; + } + } + /* clear flags from the objects we traversed */ + for (i = 0; i < found.nr; i++) + found.objects[i].item->flags &= ~STUDYING; + if (is_incomplete) + commit->object.flags |= INCOMPLETE; + /* free object arrays */ + free(study.objects); + free(found.objects); + return !is_incomplete; +} + static int keep_entry(struct commit **it, unsigned char *sha1) { struct commit *commit; @@ -54,9 +149,15 @@ static int keep_entry(struct commit **it, unsigned char *sha1) if (!commit) return 0; - /* Make sure everything in this commit exists. */ - parse_object(commit->object.sha1); - if (!tree_is_complete(commit->tree->object.sha1)) + /* + * Make sure everything in this commit exists. + * + * We have walked all the objects reachable from the refs + * and cache earlier. The commits reachable by this commit + * must meet SEEN commits -- and then we should mark them as + * SEEN as well. + */ + if (!commit_is_complete(commit)) return 0; *it = commit; return 1; @@ -76,13 +177,14 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, timestamp = strtoul(cp, &ep, 10); if (*ep != ' ') goto prune; - if (timestamp < cb->expire_total) + if (timestamp < cb->cmd->expire_total) goto prune; - if (!keep_entry(&old, osha1) || !keep_entry(&new, nsha1)) + if (cb->cmd->stalefix && + (!keep_entry(&old, osha1) || !keep_entry(&new, nsha1))) goto prune; - if ((timestamp < cb->expire_unreachable) && + if ((timestamp < cb->cmd->expire_unreachable) && (!cb->ref_commit || (old && !in_merge_bases(old, cb->ref_commit)) || (new && !in_merge_bases(new, cb->ref_commit)))) @@ -91,19 +193,15 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, if (cb->newlog) fprintf(cb->newlog, "%s %s %s", sha1_to_hex(osha1), sha1_to_hex(nsha1), data); + if (cb->cmd->verbose) + printf("keep %s", data); return 0; prune: - if (!cb->newlog) - fprintf(stderr, "would prune %s", data); + if (!cb->newlog || cb->cmd->verbose) + printf("%sprune %s", cb->newlog ? "" : "would ", data); return 0; } -struct cmd_reflog_expire_cb { - int dry_run; - unsigned long expire_total; - unsigned long expire_unreachable; -}; - static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data) { struct cmd_reflog_expire_cb *cmd = cb_data; @@ -134,8 +232,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, fprintf(stderr, "warning: ref '%s' does not point at a commit\n", ref); cb.ref = ref; - cb.expire_total = cmd->expire_total; - cb.expire_unreachable = cmd->expire_unreachable; + cb.cmd = cmd; for_each_reflog_ent(ref, expire_reflog_ent, &cb); finish: if (cb.newlog) { @@ -164,9 +261,6 @@ static int reflog_expire_config(const char *var, const char *value) return 0; } -static const char reflog_expire_usage[] = -"git-reflog expire [--dry-run] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>..."; - static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) { struct cmd_reflog_expire_cb cb; @@ -186,6 +280,12 @@ 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")) @@ -194,8 +294,12 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) cb.expire_total = approxidate(arg + 9); else if (!strncmp(arg, "--expire-unreachable=", 21)) cb.expire_unreachable = approxidate(arg + 21); + else if (!strcmp(arg, "--stale-fix")) + cb.stalefix = 1; else if (!strcmp(arg, "--all")) do_all = 1; + else if (!strcmp(arg, "--verbose")) + cb.verbose = 1; else if (!strcmp(arg, "--")) { i++; break; @@ -205,6 +309,15 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) else break; } + if (cb.stalefix) { + init_revisions(&cb.revs, prefix); + if (cb.verbose) + printf("Marking reachable objects..."); + mark_reachable_objects(&cb.revs, 0); + if (cb.verbose) + putchar('\n'); + } + if (do_all) status |= for_each_ref(expire_reflog, &cb); while (i < argc) { @@ -219,6 +332,10 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) return status; } +/* + * main "reflog" + */ + static const char reflog_usage[] = "git-reflog (expire | ...)"; |