mirror of
https://github.com/git/git
synced 2024-08-28 03:59:25 +00:00
Merge branch 'jk/fsck-connectivity-check-fix'
"git fsck --connectivity-check" was not working at all. * jk/fsck-connectivity-check-fix: fsck: lazily load types under --connectivity-only fsck: move typename() printing to its own function t1450: use "mv -f" within loose object directory fsck: check HAS_OBJ more consistently fsck: do not fallback "git fsck <bogus>" to "git fsck" fsck: tighten error-checks of "git fsck <head>" fsck: prepare dummy objects for --connectivity-check fsck: report trees as dangling t1450: clean up sub-objects in duplicate-entry test
This commit is contained in:
commit
4ba6197887
120
builtin/fsck.c
120
builtin/fsck.c
|
@ -56,6 +56,23 @@ static const char *describe_object(struct object *obj)
|
||||||
return buf.buf;
|
return buf.buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *printable_type(struct object *obj)
|
||||||
|
{
|
||||||
|
const char *ret;
|
||||||
|
|
||||||
|
if (obj->type == OBJ_NONE) {
|
||||||
|
enum object_type type = sha1_object_info(obj->oid.hash, NULL);
|
||||||
|
if (type > 0)
|
||||||
|
object_as_type(obj, type, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = typename(obj->type);
|
||||||
|
if (!ret)
|
||||||
|
ret = "unknown";
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static int fsck_config(const char *var, const char *value, void *cb)
|
static int fsck_config(const char *var, const char *value, void *cb)
|
||||||
{
|
{
|
||||||
if (strcmp(var, "fsck.skiplist") == 0) {
|
if (strcmp(var, "fsck.skiplist") == 0) {
|
||||||
|
@ -83,7 +100,7 @@ static void objreport(struct object *obj, const char *msg_type,
|
||||||
const char *err)
|
const char *err)
|
||||||
{
|
{
|
||||||
fprintf(stderr, "%s in %s %s: %s\n",
|
fprintf(stderr, "%s in %s %s: %s\n",
|
||||||
msg_type, typename(obj->type), describe_object(obj), err);
|
msg_type, printable_type(obj), describe_object(obj), err);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int objerror(struct object *obj, const char *err)
|
static int objerror(struct object *obj, const char *err)
|
||||||
|
@ -114,7 +131,7 @@ static int mark_object(struct object *obj, int type, void *data, struct fsck_opt
|
||||||
if (!obj) {
|
if (!obj) {
|
||||||
/* ... these references to parent->fld are safe here */
|
/* ... these references to parent->fld are safe here */
|
||||||
printf("broken link from %7s %s\n",
|
printf("broken link from %7s %s\n",
|
||||||
typename(parent->type), describe_object(parent));
|
printable_type(parent), describe_object(parent));
|
||||||
printf("broken link from %7s %s\n",
|
printf("broken link from %7s %s\n",
|
||||||
(type == OBJ_ANY ? "unknown" : typename(type)), "unknown");
|
(type == OBJ_ANY ? "unknown" : typename(type)), "unknown");
|
||||||
errors_found |= ERROR_REACHABLE;
|
errors_found |= ERROR_REACHABLE;
|
||||||
|
@ -131,9 +148,9 @@ static int mark_object(struct object *obj, int type, void *data, struct fsck_opt
|
||||||
if (!(obj->flags & HAS_OBJ)) {
|
if (!(obj->flags & HAS_OBJ)) {
|
||||||
if (parent && !has_object_file(&obj->oid)) {
|
if (parent && !has_object_file(&obj->oid)) {
|
||||||
printf("broken link from %7s %s\n",
|
printf("broken link from %7s %s\n",
|
||||||
typename(parent->type), describe_object(parent));
|
printable_type(parent), describe_object(parent));
|
||||||
printf(" to %7s %s\n",
|
printf(" to %7s %s\n",
|
||||||
typename(obj->type), describe_object(obj));
|
printable_type(obj), describe_object(obj));
|
||||||
errors_found |= ERROR_REACHABLE;
|
errors_found |= ERROR_REACHABLE;
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -205,9 +222,7 @@ static void check_reachable_object(struct object *obj)
|
||||||
if (!(obj->flags & HAS_OBJ)) {
|
if (!(obj->flags & HAS_OBJ)) {
|
||||||
if (has_sha1_pack(obj->oid.hash))
|
if (has_sha1_pack(obj->oid.hash))
|
||||||
return; /* it is in pack - forget about it */
|
return; /* it is in pack - forget about it */
|
||||||
if (connectivity_only && has_object_file(&obj->oid))
|
printf("missing %s %s\n", printable_type(obj),
|
||||||
return;
|
|
||||||
printf("missing %s %s\n", typename(obj->type),
|
|
||||||
describe_object(obj));
|
describe_object(obj));
|
||||||
errors_found |= ERROR_REACHABLE;
|
errors_found |= ERROR_REACHABLE;
|
||||||
return;
|
return;
|
||||||
|
@ -225,7 +240,7 @@ static void check_unreachable_object(struct object *obj)
|
||||||
* to complain about it being unreachable (since it does
|
* to complain about it being unreachable (since it does
|
||||||
* not exist).
|
* not exist).
|
||||||
*/
|
*/
|
||||||
if (!obj->parsed)
|
if (!(obj->flags & HAS_OBJ))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -233,7 +248,7 @@ static void check_unreachable_object(struct object *obj)
|
||||||
* since this is something that is prunable.
|
* since this is something that is prunable.
|
||||||
*/
|
*/
|
||||||
if (show_unreachable) {
|
if (show_unreachable) {
|
||||||
printf("unreachable %s %s\n", typename(obj->type),
|
printf("unreachable %s %s\n", printable_type(obj),
|
||||||
describe_object(obj));
|
describe_object(obj));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -252,7 +267,7 @@ static void check_unreachable_object(struct object *obj)
|
||||||
*/
|
*/
|
||||||
if (!obj->used) {
|
if (!obj->used) {
|
||||||
if (show_dangling)
|
if (show_dangling)
|
||||||
printf("dangling %s %s\n", typename(obj->type),
|
printf("dangling %s %s\n", printable_type(obj),
|
||||||
describe_object(obj));
|
describe_object(obj));
|
||||||
if (write_lost_and_found) {
|
if (write_lost_and_found) {
|
||||||
char *filename = git_pathdup("lost-found/%s/%s",
|
char *filename = git_pathdup("lost-found/%s/%s",
|
||||||
|
@ -326,7 +341,7 @@ static int fsck_obj(struct object *obj)
|
||||||
|
|
||||||
if (verbose)
|
if (verbose)
|
||||||
fprintf(stderr, "Checking %s %s\n",
|
fprintf(stderr, "Checking %s %s\n",
|
||||||
typename(obj->type), describe_object(obj));
|
printable_type(obj), describe_object(obj));
|
||||||
|
|
||||||
if (fsck_walk(obj, NULL, &fsck_obj_options))
|
if (fsck_walk(obj, NULL, &fsck_obj_options))
|
||||||
objerror(obj, "broken links");
|
objerror(obj, "broken links");
|
||||||
|
@ -352,7 +367,7 @@ static int fsck_obj(struct object *obj)
|
||||||
struct tag *tag = (struct tag *) obj;
|
struct tag *tag = (struct tag *) obj;
|
||||||
|
|
||||||
if (show_tags && tag->tagged) {
|
if (show_tags && tag->tagged) {
|
||||||
printf("tagged %s %s", typename(tag->tagged->type),
|
printf("tagged %s %s", printable_type(tag->tagged),
|
||||||
describe_object(tag->tagged));
|
describe_object(tag->tagged));
|
||||||
printf(" (%s) in %s\n", tag->tag,
|
printf(" (%s) in %s\n", tag->tag,
|
||||||
describe_object(&tag->object));
|
describe_object(&tag->object));
|
||||||
|
@ -388,7 +403,7 @@ static void fsck_handle_reflog_sha1(const char *refname, unsigned char *sha1,
|
||||||
|
|
||||||
if (!is_null_sha1(sha1)) {
|
if (!is_null_sha1(sha1)) {
|
||||||
obj = lookup_object(sha1);
|
obj = lookup_object(sha1);
|
||||||
if (obj) {
|
if (obj && (obj->flags & HAS_OBJ)) {
|
||||||
if (timestamp && name_objects)
|
if (timestamp && name_objects)
|
||||||
add_decoration(fsck_walk_options.object_names,
|
add_decoration(fsck_walk_options.object_names,
|
||||||
obj,
|
obj,
|
||||||
|
@ -604,6 +619,29 @@ static int fsck_cache_tree(struct cache_tree *it)
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void mark_object_for_connectivity(const unsigned char *sha1)
|
||||||
|
{
|
||||||
|
struct object *obj = lookup_unknown_object(sha1);
|
||||||
|
obj->flags |= HAS_OBJ;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mark_loose_for_connectivity(const unsigned char *sha1,
|
||||||
|
const char *path,
|
||||||
|
void *data)
|
||||||
|
{
|
||||||
|
mark_object_for_connectivity(sha1);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mark_packed_for_connectivity(const unsigned char *sha1,
|
||||||
|
struct packed_git *pack,
|
||||||
|
uint32_t pos,
|
||||||
|
void *data)
|
||||||
|
{
|
||||||
|
mark_object_for_connectivity(sha1);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static char const * const fsck_usage[] = {
|
static char const * const fsck_usage[] = {
|
||||||
N_("git fsck [<options>] [<object>...]"),
|
N_("git fsck [<options>] [<object>...]"),
|
||||||
NULL
|
NULL
|
||||||
|
@ -660,38 +698,41 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
|
||||||
git_config(fsck_config, NULL);
|
git_config(fsck_config, NULL);
|
||||||
|
|
||||||
fsck_head_link();
|
fsck_head_link();
|
||||||
if (!connectivity_only) {
|
if (connectivity_only) {
|
||||||
|
for_each_loose_object(mark_loose_for_connectivity, NULL, 0);
|
||||||
|
for_each_packed_object(mark_packed_for_connectivity, NULL, 0);
|
||||||
|
} else {
|
||||||
fsck_object_dir(get_object_directory());
|
fsck_object_dir(get_object_directory());
|
||||||
|
|
||||||
prepare_alt_odb();
|
prepare_alt_odb();
|
||||||
for (alt = alt_odb_list; alt; alt = alt->next)
|
for (alt = alt_odb_list; alt; alt = alt->next)
|
||||||
fsck_object_dir(alt->path);
|
fsck_object_dir(alt->path);
|
||||||
}
|
|
||||||
|
|
||||||
if (check_full) {
|
if (check_full) {
|
||||||
struct packed_git *p;
|
struct packed_git *p;
|
||||||
uint32_t total = 0, count = 0;
|
uint32_t total = 0, count = 0;
|
||||||
struct progress *progress = NULL;
|
struct progress *progress = NULL;
|
||||||
|
|
||||||
prepare_packed_git();
|
prepare_packed_git();
|
||||||
|
|
||||||
if (show_progress) {
|
if (show_progress) {
|
||||||
for (p = packed_git; p; p = p->next) {
|
for (p = packed_git; p; p = p->next) {
|
||||||
if (open_pack_index(p))
|
if (open_pack_index(p))
|
||||||
continue;
|
continue;
|
||||||
total += p->num_objects;
|
total += p->num_objects;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress = start_progress(_("Checking objects"), total);
|
||||||
}
|
}
|
||||||
|
for (p = packed_git; p; p = p->next) {
|
||||||
progress = start_progress(_("Checking objects"), total);
|
/* verify gives error messages itself */
|
||||||
|
if (verify_pack(p, fsck_obj_buffer,
|
||||||
|
progress, count))
|
||||||
|
errors_found |= ERROR_PACK;
|
||||||
|
count += p->num_objects;
|
||||||
|
}
|
||||||
|
stop_progress(&progress);
|
||||||
}
|
}
|
||||||
for (p = packed_git; p; p = p->next) {
|
|
||||||
/* verify gives error messages itself */
|
|
||||||
if (verify_pack(p, fsck_obj_buffer,
|
|
||||||
progress, count))
|
|
||||||
errors_found |= ERROR_PACK;
|
|
||||||
count += p->num_objects;
|
|
||||||
}
|
|
||||||
stop_progress(&progress);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
heads = 0;
|
heads = 0;
|
||||||
|
@ -701,9 +742,11 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
|
||||||
if (!get_sha1(arg, sha1)) {
|
if (!get_sha1(arg, sha1)) {
|
||||||
struct object *obj = lookup_object(sha1);
|
struct object *obj = lookup_object(sha1);
|
||||||
|
|
||||||
/* Error is printed by lookup_object(). */
|
if (!obj || !(obj->flags & HAS_OBJ)) {
|
||||||
if (!obj)
|
error("%s: object missing", sha1_to_hex(sha1));
|
||||||
|
errors_found |= ERROR_OBJECT;
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
obj->used = 1;
|
obj->used = 1;
|
||||||
if (name_objects)
|
if (name_objects)
|
||||||
|
@ -714,6 +757,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
error("invalid parameter: expected sha1, got '%s'", arg);
|
error("invalid parameter: expected sha1, got '%s'", arg);
|
||||||
|
errors_found |= ERROR_OBJECT;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -721,7 +765,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
|
||||||
* default ones from .git/refs. We also consider the index file
|
* default ones from .git/refs. We also consider the index file
|
||||||
* in this case (ie this implies --cache).
|
* in this case (ie this implies --cache).
|
||||||
*/
|
*/
|
||||||
if (!heads) {
|
if (!argc) {
|
||||||
get_default_heads();
|
get_default_heads();
|
||||||
keep_cache_objects = 1;
|
keep_cache_objects = 1;
|
||||||
}
|
}
|
||||||
|
|
4
fsck.c
4
fsck.c
|
@ -458,6 +458,10 @@ int fsck_walk(struct object *obj, void *data, struct fsck_options *options)
|
||||||
{
|
{
|
||||||
if (!obj)
|
if (!obj)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
if (obj->type == OBJ_NONE)
|
||||||
|
parse_object(obj->oid.hash);
|
||||||
|
|
||||||
switch (obj->type) {
|
switch (obj->type) {
|
||||||
case OBJ_BLOB:
|
case OBJ_BLOB:
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -189,14 +189,16 @@ test_expect_success 'commit with NUL in header' '
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success 'tree object with duplicate entries' '
|
test_expect_success 'tree object with duplicate entries' '
|
||||||
test_when_finished "remove_object \$T" &&
|
test_when_finished "for i in \$T; do remove_object \$i; done" &&
|
||||||
T=$(
|
T=$(
|
||||||
GIT_INDEX_FILE=test-index &&
|
GIT_INDEX_FILE=test-index &&
|
||||||
export GIT_INDEX_FILE &&
|
export GIT_INDEX_FILE &&
|
||||||
rm -f test-index &&
|
rm -f test-index &&
|
||||||
>x &&
|
>x &&
|
||||||
git add x &&
|
git add x &&
|
||||||
|
git rev-parse :x &&
|
||||||
T=$(git write-tree) &&
|
T=$(git write-tree) &&
|
||||||
|
echo $T &&
|
||||||
(
|
(
|
||||||
git cat-file tree $T &&
|
git cat-file tree $T &&
|
||||||
git cat-file tree $T
|
git cat-file tree $T
|
||||||
|
@ -521,9 +523,21 @@ test_expect_success 'fsck --connectivity-only' '
|
||||||
touch empty &&
|
touch empty &&
|
||||||
git add empty &&
|
git add empty &&
|
||||||
test_commit empty &&
|
test_commit empty &&
|
||||||
|
|
||||||
|
# Drop the index now; we want to be sure that we
|
||||||
|
# recursively notice the broken objects
|
||||||
|
# because they are reachable from refs, not because
|
||||||
|
# they are in the index.
|
||||||
|
rm -f .git/index &&
|
||||||
|
|
||||||
|
# corrupt the blob, but in a way that we can still identify
|
||||||
|
# its type. That lets us see that --connectivity-only is
|
||||||
|
# not actually looking at the contents, but leaves it
|
||||||
|
# free to examine the type if it chooses.
|
||||||
empty=.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 &&
|
empty=.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 &&
|
||||||
rm -f $empty &&
|
blob=$(echo unrelated | git hash-object -w --stdin) &&
|
||||||
echo invalid >$empty &&
|
mv -f $(sha1_file $blob) $empty &&
|
||||||
|
|
||||||
test_must_fail git fsck --strict &&
|
test_must_fail git fsck --strict &&
|
||||||
git fsck --strict --connectivity-only &&
|
git fsck --strict --connectivity-only &&
|
||||||
tree=$(git rev-parse HEAD:) &&
|
tree=$(git rev-parse HEAD:) &&
|
||||||
|
@ -535,6 +549,19 @@ test_expect_success 'fsck --connectivity-only' '
|
||||||
)
|
)
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'fsck --connectivity-only with explicit head' '
|
||||||
|
rm -rf connectivity-only &&
|
||||||
|
git init connectivity-only &&
|
||||||
|
(
|
||||||
|
cd connectivity-only &&
|
||||||
|
test_commit foo &&
|
||||||
|
rm -f .git/index &&
|
||||||
|
tree=$(git rev-parse HEAD^{tree}) &&
|
||||||
|
remove_object $(git rev-parse HEAD:foo.t) &&
|
||||||
|
test_must_fail git fsck --connectivity-only $tree
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success 'fsck --name-objects' '
|
test_expect_success 'fsck --name-objects' '
|
||||||
rm -rf name-objects &&
|
rm -rf name-objects &&
|
||||||
git init name-objects &&
|
git init name-objects &&
|
||||||
|
@ -619,4 +646,47 @@ test_expect_success 'fsck detects trailing loose garbage (blob)' '
|
||||||
test_i18ngrep "garbage.*$blob" out
|
test_i18ngrep "garbage.*$blob" out
|
||||||
'
|
'
|
||||||
|
|
||||||
|
# for each of type, we have one version which is referenced by another object
|
||||||
|
# (and so while unreachable, not dangling), and another variant which really is
|
||||||
|
# dangling.
|
||||||
|
test_expect_success 'fsck notices dangling objects' '
|
||||||
|
git init dangling &&
|
||||||
|
(
|
||||||
|
cd dangling &&
|
||||||
|
blob=$(echo not-dangling | git hash-object -w --stdin) &&
|
||||||
|
dblob=$(echo dangling | git hash-object -w --stdin) &&
|
||||||
|
tree=$(printf "100644 blob %s\t%s\n" $blob one | git mktree) &&
|
||||||
|
dtree=$(printf "100644 blob %s\t%s\n" $blob two | git mktree) &&
|
||||||
|
commit=$(git commit-tree $tree) &&
|
||||||
|
dcommit=$(git commit-tree -p $commit $tree) &&
|
||||||
|
|
||||||
|
cat >expect <<-EOF &&
|
||||||
|
dangling blob $dblob
|
||||||
|
dangling commit $dcommit
|
||||||
|
dangling tree $dtree
|
||||||
|
EOF
|
||||||
|
|
||||||
|
git fsck >actual &&
|
||||||
|
# the output order is non-deterministic, as it comes from a hash
|
||||||
|
sort <actual >actual.sorted &&
|
||||||
|
test_cmp expect actual.sorted
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'fsck $name notices bogus $name' '
|
||||||
|
test_must_fail git fsck bogus &&
|
||||||
|
test_must_fail git fsck $_z40
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'bogus head does not fallback to all heads' '
|
||||||
|
# set up a case that will cause a reachability complaint
|
||||||
|
echo to-be-deleted >foo &&
|
||||||
|
git add foo &&
|
||||||
|
blob=$(git rev-parse :foo) &&
|
||||||
|
test_when_finished "git rm --cached foo" &&
|
||||||
|
remove_object $blob &&
|
||||||
|
test_must_fail git fsck $_z40 >out 2>&1 &&
|
||||||
|
! grep $blob out
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
|
Loading…
Reference in a new issue