Skip to content

Commit 394b84b

Browse files
mjcheethamnewren
andcommitted
fsck: snapshot default refs before object walk
Fsck has a race when operating on live repositories; consider the following simple script that writes new commits as fsck runs: #!/bin/bash git fsck & PID=$! while ps -p $PID >/dev/null; do sleep 3 git commit -q --allow-empty -m "Another commit" done Since fsck reads refs at the beginning, walks those for connectivity, and then reads the refs again at the end to check, this can cause fsck to get confused and think that the new refs refer to missing commits and that new reflog entries are invalid. Running the above script in a clone of git.git results in the following (output ellipsized to remove additional errors of the same type): $ ./fsck-while-writing.sh Checking ref database: 100% (1/1), done. Checking object directories: 100% (256/256), done. warning in tag d6602ec: missingTaggerEntry: invalid format - expected 'tagger' line Checking objects: 100% (835091/835091), done. error: HEAD: invalid reflog entry 2aac9f9286e2164fbf8e4f1d1df53044ace2b310 error: HEAD: invalid reflog entry 2aac9f9286e2164fbf8e4f1d1df53044ace2b310 error: HEAD: invalid reflog entry da0f5b80d61844a6f0ad2ddfd57e4fdfa246ea68 error: HEAD: invalid reflog entry da0f5b80d61844a6f0ad2ddfd57e4fdfa246ea68 [...] error: HEAD: invalid reflog entry 87c8a5c2f6b79d9afa9e941590b9a097b6f7ac09 error: HEAD: invalid reflog entry d80887a48865e6ad165274b152cbbbed29f8a55a error: HEAD: invalid reflog entry d80887a48865e6ad165274b152cbbbed29f8a55a error: HEAD: invalid reflog entry 6724f2dfede88bfa9445a333e06e78536c0c6c0d error: refs/heads/mybranch invalid reflog entry 2aac9f9286e2164fbf8e4f1d1df53044ace2b310 error: refs/heads/mybranch: invalid reflog entry 2aac9f9286e2164fbf8e4f1d1df53044ace2b310 error: refs/heads/mybranch: invalid reflog entry da0f5b80d61844a6f0ad2ddfd57e4fdfa246ea68 error: refs/heads/mybranch: invalid reflog entry da0f5b80d61844a6f0ad2ddfd57e4fdfa246ea68 [...] error: refs/heads/mybranch: invalid reflog entry 87c8a5c2f6b79d9afa9e941590b9a097b6f7ac09 error: refs/heads/mybranch: invalid reflog entry d80887a48865e6ad165274b152cbbbed29f8a55a error: refs/heads/mybranch: invalid reflog entry d80887a48865e6ad165274b152cbbbed29f8a55a error: refs/heads/mybranch: invalid reflog entry 6724f2dfede88bfa9445a333e06e78536c0c6c0d Checking connectivity: 833846, done. missing commit 6724f2dfede88bfa9445a333e06e78536c0c6c0d Verifying commits in commit graph: 100% (242243/242243), done. This problem doesn't occur when refs are specified on the command line for us to check, since we use those specified refs for both walking and checking. Using the same refs for walking and checking seems to just make sense, so modify the existing code to do the same when refs aren't specified. Snapshot the refs at the beginning, and also ignore all reflog entries since the time of our snapshot (while this technically means we could ignore a reflog entry created before the fsck process if the local clock is weird, since reflogs are local-only there are not concerns about differences between clocks on different machines). This combination of changes modifies the output of running the above script to: $ ./fsck-while-writing.sh Checking ref database: 100% (1/1), done. Checking object directories: 100% (256/256), done. warning in tag d6602ec: missingTaggerEntry: invalid format - expected 'tagger' line Checking objects: 100% (835091/835091), done. Checking connectivity: 833846, done. Verifying commits in commit graph: 100% (242243/242243), done. While worries about live updates while running fsck is likely of most interest for forge operators, it will likely also benefit those with automated jobs (such as git maintenance) or even casual users who want to do other work in their clone while fsck is running. Signed-off-by: Matthew John Cheetham <[email protected]> Co-authored-by: Elijah Newren <[email protected]> [en: several changes: * adjusted for upstream refactorings to refs callback call signatures * handle reflogs as well * free recorded snapshot of refs when done * default to snapshotting instead of making it a non-default option * provide reproducible testcase in commit message and rewrite commit message around it ] Signed-off-by: Elijah Newren <[email protected]>
1 parent b31ab93 commit 394b84b

File tree

1 file changed

+70
-4
lines changed

1 file changed

+70
-4
lines changed

builtin/fsck.c

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ static int show_progress = -1;
5151
static int show_dangling = 1;
5252
static int name_objects;
5353
static int check_references = 1;
54+
static timestamp_t now;
5455
#define ERROR_OBJECT 01
5556
#define ERROR_REACHABLE 02
5657
#define ERROR_PACK 04
@@ -509,6 +510,9 @@ static int fsck_handle_reflog_ent(const char *refname,
509510
timestamp_t timestamp, int tz UNUSED,
510511
const char *message UNUSED, void *cb_data UNUSED)
511512
{
513+
if (now && timestamp > now)
514+
return 0;
515+
512516
if (verbose)
513517
fprintf_ln(stderr, _("Checking reflog %s->%s"),
514518
oid_to_hex(ooid), oid_to_hex(noid));
@@ -567,14 +571,53 @@ static int fsck_head_link(const char *head_ref_name,
567571
const char **head_points_at,
568572
struct object_id *head_oid);
569573

570-
static void get_default_heads(void)
574+
struct ref_snapshot {
575+
size_t nr;
576+
size_t name_alloc;
577+
size_t oid_alloc;
578+
char **refname;
579+
struct object_id *oid;
580+
};
581+
582+
static int snapshot_refs(const struct reference *ref, void *cb_data)
583+
{
584+
struct ref_snapshot *refs = cb_data;
585+
586+
ALLOC_GROW(refs->refname, refs->nr + 1, refs->name_alloc);
587+
ALLOC_GROW(refs->oid, refs->nr + 1, refs->oid_alloc);
588+
589+
refs->refname[refs->nr] = xstrdup(ref->name);
590+
oidcpy(&refs->oid[refs->nr], ref->oid);
591+
refs->nr++;
592+
593+
return 0;
594+
}
595+
596+
static void free_snapshot_refs(struct ref_snapshot *snapshot)
597+
{
598+
for (size_t i = 0; i < snapshot->nr; i++)
599+
free(snapshot->refname[i]);
600+
free(snapshot->refname);
601+
free(snapshot->oid);
602+
}
603+
604+
static void get_default_heads(struct ref_snapshot *the_refs)
571605
{
572606
struct worktree **worktrees, **p;
573607
const char *head_points_at;
574608
struct object_id head_oid;
575609

576-
refs_for_each_rawref(get_main_ref_store(the_repository),
577-
fsck_handle_ref, NULL);
610+
if (the_refs)
611+
for (size_t i = 0; i < the_refs->nr; i++) {
612+
struct reference ref = {
613+
.name = the_refs->refname[i],
614+
.oid = &the_refs->oid[i],
615+
};
616+
fsck_handle_ref(&ref, NULL);
617+
}
618+
else
619+
refs_for_each_rawref(get_main_ref_store(the_repository),
620+
fsck_handle_ref, NULL);
578621

579622
worktrees = get_worktrees();
580623
for (p = worktrees; *p; p++) {
@@ -964,6 +1007,14 @@ int cmd_fsck(int argc,
9641007
{
9651008
int i;
9661009
struct odb_source *source;
1010+
struct ref_snapshot default_refs_snapshot = {
1011+
.nr = 0,
1012+
.name_alloc = 0,
1013+
.oid_alloc = 0,
1014+
.refname = NULL,
1015+
.oid = NULL
1016+
};
1017+
bool use_snapshot;
9671018

9681019
/* fsck knows how to handle missing promisor objects */
9691020
fetch_if_missing = 0;
@@ -999,6 +1050,19 @@ int cmd_fsck(int argc,
9991050
if (check_references)
10001051
fsck_refs(the_repository);
10011052

1053+
/*
1054+
* Take a snapshot of the refs before walking objects to avoid looking
1055+
* at a set of refs that may be changed by the user while we are walking
1056+
* objects. We can still walk over new objects that are added during the
1057+
* execution of fsck but won't miss any objects that were reachable.
1058+
*/
1059+
use_snapshot = !argc;
1060+
if (use_snapshot) {
1061+
now = time(NULL);
1062+
refs_for_each_rawref(get_main_ref_store(the_repository),
1063+
snapshot_refs, &default_refs_snapshot);
1064+
}
1065+
10021066
if (connectivity_only) {
10031067
for_each_loose_object(the_repository->objects,
10041068
mark_loose_for_connectivity, NULL, 0);
@@ -1071,7 +1135,7 @@ int cmd_fsck(int argc,
10711135
* in this case (ie this implies --cache).
10721136
*/
10731137
if (!argc) {
1074-
get_default_heads();
1138+
get_default_heads(use_snapshot ? &default_refs_snapshot : NULL);
10751139
keep_cache_objects = 1;
10761140
}
10771141

@@ -1148,5 +1212,7 @@ int cmd_fsck(int argc,
11481212
}
11491213
}
11501214

1215+
if (use_snapshot)
1216+
free_snapshot_refs(&default_refs_snapshot);
11511217
return errors_found;
11521218
}

0 commit comments

Comments
 (0)