diff options
-rw-r--r-- | Makefile | 1 | ||||
-rw-r--r-- | builtin-prune.c | 187 | ||||
-rw-r--r-- | builtin-reflog.c | 224 | ||||
-rw-r--r-- | reachable.c | 199 | ||||
-rw-r--r-- | reachable.h | 6 | ||||
-rwxr-xr-x | t/t1410-reflog.sh | 176 |
6 files changed, 571 insertions, 222 deletions
@@ -251,6 +251,7 @@ LIB_OBJS = \ interpolate.o \ lockfile.o \ object.o pack-check.o patch-delta.o path.o pkt-line.o sideband.o \ + reachable.o \ quote.o read-cache.o refs.o run-command.o dir.o object-refs.o \ server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \ tag.o tree.o usage.o config.o environment.o ctype.o copy.o \ diff --git a/builtin-prune.c b/builtin-prune.c index b469c43bc5..6f0ba0d04d 100644 --- a/builtin-prune.c +++ b/builtin-prune.c @@ -1,18 +1,12 @@ #include "cache.h" -#include "refs.h" -#include "tag.h" #include "commit.h" -#include "tree.h" -#include "blob.h" -#include "tree-walk.h" #include "diff.h" #include "revision.h" #include "builtin.h" -#include "cache-tree.h" +#include "reachable.h" static const char prune_usage[] = "git-prune [-n]"; static int show_only; -static struct rev_info revs; static int prune_object(char *path, const char *filename, const unsigned char *sha1) { @@ -85,164 +79,10 @@ static void prune_object_dir(const char *path) } } -static void process_blob(struct blob *blob, - struct object_array *p, - struct name_path *path, - const char *name) -{ - struct object *obj = &blob->object; - - if (obj->flags & SEEN) - return; - obj->flags |= SEEN; - /* Nothing to do, really .. The blob lookup was the important part */ -} - -static void process_tree(struct tree *tree, - struct object_array *p, - struct name_path *path, - const char *name) -{ - struct object *obj = &tree->object; - struct tree_desc desc; - struct name_entry entry; - struct name_path me; - - if (obj->flags & SEEN) - return; - obj->flags |= SEEN; - if (parse_tree(tree) < 0) - die("bad tree object %s", sha1_to_hex(obj->sha1)); - name = xstrdup(name); - add_object(obj, p, path, name); - me.up = path; - me.elem = name; - me.elem_len = strlen(name); - - desc.buf = tree->buffer; - desc.size = tree->size; - - while (tree_entry(&desc, &entry)) { - if (S_ISDIR(entry.mode)) - process_tree(lookup_tree(entry.sha1), p, &me, entry.path); - else - process_blob(lookup_blob(entry.sha1), p, &me, entry.path); - } - free(tree->buffer); - tree->buffer = NULL; -} - -static void process_tag(struct tag *tag, struct object_array *p, const char *name) -{ - struct object *obj = &tag->object; - struct name_path me; - - if (obj->flags & SEEN) - return; - obj->flags |= SEEN; - - me.up = NULL; - me.elem = "tag:/"; - me.elem_len = 5; - - if (parse_tag(tag) < 0) - die("bad tag object %s", sha1_to_hex(obj->sha1)); - add_object(tag->tagged, p, NULL, name); -} - -static void walk_commit_list(struct rev_info *revs) -{ - int i; - struct commit *commit; - struct object_array objects = { 0, 0, NULL }; - - /* Walk all commits, process their trees */ - while ((commit = get_revision(revs)) != NULL) - process_tree(commit->tree, &objects, NULL, ""); - - /* Then walk all the pending objects, recursively processing them too */ - for (i = 0; i < revs->pending.nr; i++) { - struct object_array_entry *pending = revs->pending.objects + i; - struct object *obj = pending->item; - const char *name = pending->name; - if (obj->type == OBJ_TAG) { - process_tag((struct tag *) obj, &objects, name); - continue; - } - if (obj->type == OBJ_TREE) { - process_tree((struct tree *)obj, &objects, NULL, name); - continue; - } - if (obj->type == OBJ_BLOB) { - process_blob((struct blob *)obj, &objects, NULL, name); - continue; - } - die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name); - } -} - -static int add_one_reflog_ent(unsigned char *osha1, unsigned char *nsha1, char *datail, void *cb_data) -{ - struct object *object; - - object = parse_object(osha1); - if (object) - add_pending_object(&revs, object, ""); - object = parse_object(nsha1); - if (object) - add_pending_object(&revs, object, ""); - return 0; -} - -static int add_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) -{ - struct object *object = parse_object(sha1); - if (!object) - die("bad object ref: %s:%s", path, sha1_to_hex(sha1)); - add_pending_object(&revs, object, ""); - - for_each_reflog_ent(path, add_one_reflog_ent, NULL); - - return 0; -} - -static void add_one_tree(const unsigned char *sha1) -{ - struct tree *tree = lookup_tree(sha1); - add_pending_object(&revs, &tree->object, ""); -} - -static void add_cache_tree(struct cache_tree *it) -{ - int i; - - if (it->entry_count >= 0) - add_one_tree(it->sha1); - for (i = 0; i < it->subtree_nr; i++) - add_cache_tree(it->down[i]->cache_tree); -} - -static void add_cache_refs(void) -{ - int i; - - read_cache(); - for (i = 0; i < active_nr; i++) { - lookup_blob(active_cache[i]->sha1); - /* - * We could add the blobs to the pending list, but quite - * frankly, we don't care. Once we've looked them up, and - * added them as objects, we've really done everything - * there is to do for a blob - */ - } - if (active_cache_tree) - add_cache_tree(active_cache_tree); -} - int cmd_prune(int argc, const char **argv, const char *prefix) { int i; + struct rev_info revs; for (i = 1; i < argc; i++) { const char *arg = argv[i]; @@ -254,29 +94,8 @@ int cmd_prune(int argc, const char **argv, const char *prefix) } save_commit_buffer = 0; - - /* - * Set up revision parsing, and mark us as being interested - * in all object types, not just commits. - */ init_revisions(&revs, prefix); - revs.tag_objects = 1; - revs.blob_objects = 1; - revs.tree_objects = 1; - - /* Add all external refs */ - for_each_ref(add_one_ref, NULL); - - /* Add all refs from the index file */ - add_cache_refs(); - - /* - * Set up the revision walk - this will move all commits - * from the pending list to the commit walking list. - */ - prepare_revision_walk(&revs); - - walk_commit_list(&revs); + mark_reachable_objects(&revs, 1); prune_object_dir(get_object_directory()); diff --git a/builtin-reflog.c b/builtin-reflog.c index d3f2f50d2b..a967117661 100644 --- a/builtin-reflog.c +++ b/builtin-reflog.c @@ -4,43 +4,169 @@ #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; }; +#define INCOMPLETE (1u<<10) +#define STUDYING (1u<<11) + static int tree_is_complete(const unsigned char *sha1) { struct tree_desc desc; - void *buf; - char type[20]; + struct name_entry entry; + int complete; + struct tree *tree; - buf = read_sha1_file(sha1, type, &desc.size); - if (!buf) + tree = lookup_tree(sha1); + if (!tree) + return 0; + if (tree->object.flags & SEEN) + return 1; + if (tree->object.flags & INCOMPLETE) return 0; - desc.buf = buf; - while (desc.size) { - const unsigned char *elem; - const char *name; - unsigned mode; - - elem = tree_entry_extract(&desc, &name, &mode); - if (!has_sha1_file(elem) || - (S_ISDIR(mode) && !tree_is_complete(elem))) { - free(buf); + + desc.buf = tree->buffer; + desc.size = tree->size; + if (!desc.buf) { + char type[20]; + void *data = read_sha1_file(sha1, type, &desc.size); + if (!data) { + tree->object.flags |= INCOMPLETE; return 0; } - update_tree_entry(&desc); + desc.buf = data; + tree->buffer = data; } - free(buf); - return 1; + complete = 1; + while (tree_entry(&desc, &entry)) { + if (!has_sha1_file(entry.sha1) || + (S_ISDIR(entry.mode) && !tree_is_complete(entry.sha1))) { + tree->object.flags |= INCOMPLETE; + complete = 0; + } + } + free(tree->buffer); + tree->buffer = NULL; + + if (complete) + tree->object.flags |= SEEN; + return complete; +} + +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" array have all the + * necessary objects. + */ + for (i = 0; i < found.nr; i++) { + struct commit *c = + (struct commit *)found.objects[i].item; + if (!tree_is_complete(c->tree->object.sha1)) { + is_incomplete = 1; + c->object.flags |= INCOMPLETE; + } + } + 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; + else { + /* + * If we come here, we have (1) traversed the ancestry chain + * from the "commit" until we reach SEEN commits (which are + * known to be complete), and (2) made sure that the commits + * encountered during the above traversal refer to trees that + * are complete. Which means that we know *all* the commits + * we have seen during this process are complete. + */ + for (i = 0; i < found.nr; i++) + found.objects[i].item->flags |= SEEN; + } + /* free object arrays */ + free(study.objects); + free(found.objects); + return !is_incomplete; } static int keep_entry(struct commit **it, unsigned char *sha1) @@ -54,9 +180,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 +208,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 +224,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 +263,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 +292,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 +311,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 +325,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 +340,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 +363,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 | ...)"; diff --git a/reachable.c b/reachable.c new file mode 100644 index 0000000000..4dfee1dbe8 --- /dev/null +++ b/reachable.c @@ -0,0 +1,199 @@ +#include "cache.h" +#include "refs.h" +#include "tag.h" +#include "commit.h" +#include "blob.h" +#include "diff.h" +#include "revision.h" +#include "reachable.h" +#include "cache-tree.h" + +static void process_blob(struct blob *blob, + struct object_array *p, + struct name_path *path, + const char *name) +{ + struct object *obj = &blob->object; + + if (obj->flags & SEEN) + return; + obj->flags |= SEEN; + /* Nothing to do, really .. The blob lookup was the important part */ +} + +static void process_tree(struct tree *tree, + struct object_array *p, + struct name_path *path, + const char *name) +{ + struct object *obj = &tree->object; + struct tree_desc desc; + struct name_entry entry; + struct name_path me; + + if (obj->flags & SEEN) + return; + obj->flags |= SEEN; + if (parse_tree(tree) < 0) + die("bad tree object %s", sha1_to_hex(obj->sha1)); + name = xstrdup(name); + add_object(obj, p, path, name); + me.up = path; + me.elem = name; + me.elem_len = strlen(name); + + desc.buf = tree->buffer; + desc.size = tree->size; + + while (tree_entry(&desc, &entry)) { + if (S_ISDIR(entry.mode)) + process_tree(lookup_tree(entry.sha1), p, &me, entry.path); + else + process_blob(lookup_blob(entry.sha1), p, &me, entry.path); + } + free(tree->buffer); + tree->buffer = NULL; +} + +static void process_tag(struct tag *tag, struct object_array *p, const char *name) +{ + struct object *obj = &tag->object; + struct name_path me; + + if (obj->flags & SEEN) + return; + obj->flags |= SEEN; + + me.up = NULL; + me.elem = "tag:/"; + me.elem_len = 5; + + if (parse_tag(tag) < 0) + die("bad tag object %s", sha1_to_hex(obj->sha1)); + add_object(tag->tagged, p, NULL, name); +} + +static void walk_commit_list(struct rev_info *revs) +{ + int i; + struct commit *commit; + struct object_array objects = { 0, 0, NULL }; + + /* Walk all commits, process their trees */ + while ((commit = get_revision(revs)) != NULL) + process_tree(commit->tree, &objects, NULL, ""); + + /* Then walk all the pending objects, recursively processing them too */ + for (i = 0; i < revs->pending.nr; i++) { + struct object_array_entry *pending = revs->pending.objects + i; + struct object *obj = pending->item; + const char *name = pending->name; + if (obj->type == OBJ_TAG) { + process_tag((struct tag *) obj, &objects, name); + continue; + } + if (obj->type == OBJ_TREE) { + process_tree((struct tree *)obj, &objects, NULL, name); + continue; + } + if (obj->type == OBJ_BLOB) { + process_blob((struct blob *)obj, &objects, NULL, name); + continue; + } + die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name); + } +} + +static int add_one_reflog_ent(unsigned char *osha1, unsigned char *nsha1, char *datail, void *cb_data) +{ + struct object *object; + struct rev_info *revs = (struct rev_info *)cb_data; + + object = parse_object(osha1); + if (object) + add_pending_object(revs, object, ""); + object = parse_object(nsha1); + if (object) + add_pending_object(revs, object, ""); + return 0; +} + +static int add_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) +{ + struct object *object = parse_object(sha1); + struct rev_info *revs = (struct rev_info *)cb_data; + + if (!object) + die("bad object ref: %s:%s", path, sha1_to_hex(sha1)); + add_pending_object(revs, object, ""); + + return 0; +} + +static int add_one_reflog(const char *path, const unsigned char *sha1, int flag, void *cb_data) +{ + for_each_reflog_ent(path, add_one_reflog_ent, cb_data); + return 0; +} + +static void add_one_tree(const unsigned char *sha1, struct rev_info *revs) +{ + struct tree *tree = lookup_tree(sha1); + add_pending_object(revs, &tree->object, ""); +} + +static void add_cache_tree(struct cache_tree *it, struct rev_info *revs) +{ + int i; + + if (it->entry_count >= 0) + add_one_tree(it->sha1, revs); + for (i = 0; i < it->subtree_nr; i++) + add_cache_tree(it->down[i]->cache_tree, revs); +} + +static void add_cache_refs(struct rev_info *revs) +{ + int i; + + read_cache(); + for (i = 0; i < active_nr; i++) { + lookup_blob(active_cache[i]->sha1); + /* + * We could add the blobs to the pending list, but quite + * frankly, we don't care. Once we've looked them up, and + * added them as objects, we've really done everything + * there is to do for a blob + */ + } + if (active_cache_tree) + add_cache_tree(active_cache_tree, revs); +} + +void mark_reachable_objects(struct rev_info *revs, int mark_reflog) +{ + /* + * Set up revision parsing, and mark us as being interested + * in all object types, not just commits. + */ + revs->tag_objects = 1; + revs->blob_objects = 1; + revs->tree_objects = 1; + + /* Add all refs from the index file */ + add_cache_refs(revs); + + /* Add all external refs */ + for_each_ref(add_one_ref, revs); + + /* Add all reflog info from refs */ + if (mark_reflog) + for_each_ref(add_one_reflog, revs); + + /* + * Set up the revision walk - this will move all commits + * from the pending list to the commit walking list. + */ + prepare_revision_walk(revs); + walk_commit_list(revs); +} diff --git a/reachable.h b/reachable.h new file mode 100644 index 0000000000..40751810b6 --- /dev/null +++ b/reachable.h @@ -0,0 +1,6 @@ +#ifndef REACHEABLE_H +#define REACHEABLE_H + +extern void mark_reachable_objects(struct rev_info *revs, int mark_reflog); + +#endif diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh new file mode 100755 index 0000000000..738d1513d4 --- /dev/null +++ b/t/t1410-reflog.sh @@ -0,0 +1,176 @@ +#!/bin/sh +# +# Copyright (c) 2007 Junio C Hamano +# + +test_description='Test prune and reflog expiration' +. ./test-lib.sh + +check_have () { + gaah= && + for N in "$@" + do + eval "o=\$$N" && git cat-file -t $o || { + echo Gaah $N + gaah=$N + break + } + done && + test -z "$gaah" +} + +check_fsck () { + output=$(git fsck-objects --full) + case "$1" in + '') + test -z "$output" ;; + *) + echo "$output" | grep "$1" ;; + esac +} + +corrupt () { + aa=${1%??????????????????????????????????????} zz=${1#??} + mv .git/objects/$aa/$zz .git/$aa$zz +} + +recover () { + aa=${1%??????????????????????????????????????} zz=${1#??} + mkdir -p .git/objects/$aa + mv .git/$aa$zz .git/objects/$aa/$zz +} + +check_dont_have () { + gaah= && + for N in "$@" + do + eval "o=\$$N" + git cat-file -t $o && { + echo Gaah $N + gaah=$N + break + } + done + test -z "$gaah" +} + +test_expect_success setup ' + mkdir -p A/B && + echo rat >C && + echo ox >A/D && + echo tiger >A/B/E && + git add . && + + test_tick && git commit -m rabbit && + H=`git rev-parse --verify HEAD` && + A=`git rev-parse --verify HEAD:A` && + B=`git rev-parse --verify HEAD:A/B` && + C=`git rev-parse --verify HEAD:C` && + D=`git rev-parse --verify HEAD:A/D` && + E=`git rev-parse --verify HEAD:A/B/E` && + check_fsck && + + chmod +x C && + git add C && + test_tick && git commit -m dragon && + L=`git rev-parse --verify HEAD` && + check_fsck && + + rm -f C A/B/E && + echo snake >F && + echo horse >A/G && + git add F A/G && + test_tick && git commit -a -m sheep && + F=`git rev-parse --verify HEAD:F` && + G=`git rev-parse --verify HEAD:A/G` && + I=`git rev-parse --verify HEAD:A` && + J=`git rev-parse --verify HEAD` && + check_fsck && + + rm -f A/G && + test_tick && git commit -a -m monkey && + K=`git rev-parse --verify HEAD` && + check_fsck && + + check_have A B C D E F G H I J K L && + + git prune && + + check_have A B C D E F G H I J K L && + + check_fsck && + + loglen=$(wc -l <.git/logs/refs/heads/master) && + test $loglen = 4 +' + +test_expect_success rewind ' + test_tick && git reset --hard HEAD~2 && + test -f C && + test -f A/B/E && + ! test -f F && + ! test -f A/G && + + check_have A B C D E F G H I J K L && + + git prune && + + check_have A B C D E F G H I J K L && + + loglen=$(wc -l <.git/logs/refs/heads/master) && + test $loglen = 5 +' + +test_expect_success 'corrupt and check' ' + + corrupt $F && + check_fsck "missing blob $F" + +' + +test_expect_success 'reflog expire --dry-run should not touch reflog' ' + + git reflog expire --dry-run \ + --expire=$(($test_tick - 10000)) \ + --expire-unreachable=$(($test_tick - 10000)) \ + --stale-fix \ + --all && + + loglen=$(wc -l <.git/logs/refs/heads/master) && + test $loglen = 5 && + + check_fsck "missing blob $F" +' + +test_expect_success 'reflog expire' ' + + git reflog expire --verbose \ + --expire=$(($test_tick - 10000)) \ + --expire-unreachable=$(($test_tick - 10000)) \ + --stale-fix \ + --all && + + loglen=$(wc -l <.git/logs/refs/heads/master) && + test $loglen = 2 && + + check_fsck "dangling commit $K" +' + +test_expect_success 'prune and fsck' ' + + git prune && + check_fsck && + + check_have A B C D E H L && + check_dont_have F G I J K + +' + +test_expect_success 'recover and check' ' + + recover $F && + check_fsck "dangling blob $F" + +' + +test_done |