diff --git a/odb/source-files.c b/odb/source-files.c index 455f409db0..9268f5aa9a 100644 --- a/odb/source-files.c +++ b/odb/source-files.c @@ -426,6 +426,7 @@ static enum odb_read_status odb_source_files_read_object_info(struct odb_source { struct odb_source_files *files = odb_source_files_downcast(source); enum odb_read_status status = ODB_READ_NOT_FOUND; + enum odb_read_status ret; /* * Reading an object may fail even though the object exists, for @@ -434,15 +435,15 @@ static enum odb_read_status odb_source_files_read_object_info(struct odb_source * prefer the first such error in case multiple reads have failed. */ for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) { - enum odb_read_status ret; - ret = odb_source_read_object_info(&dir->packed->base, oid, oi, flags, status == ODB_READ_NOT_FOUND ? errmsg : NULL); if (!ret) return 0; if (ret != ODB_READ_NOT_FOUND && status == ODB_READ_NOT_FOUND) status = ret; + } + for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) { ret = odb_source_read_object_info(&dir->loose->base, oid, oi, flags, status == ODB_READ_NOT_FOUND ? errmsg : NULL); if (!ret) diff --git a/t/t5615-alternate-env.sh b/t/t5615-alternate-env.sh index 9d6aa2187f..3259852a0f 100755 --- a/t/t5615-alternate-env.sh +++ b/t/t5615-alternate-env.sh @@ -88,4 +88,47 @@ test_expect_success !MINGW 'broken quoting falls back to interpreting raw' ' EOF ' +test_expect_success 'packs across sources are checked before loose objects' ' + # Regression test for a performance issue in which an object that + # resides in an alternate as a packed object caused a spurious loose + # object lookup (a filesystem stat) on the main object store before the + # alternate packfile was consulted. Reading such an object must resolve + # to the alternate packfile, never to a loose copy in the main store. + # + # Build an alternate whose object "B" is stored as a delta in a + # packfile. git deltifies successive versions of a tracked file, so the + # older, shorter blob "B" becomes a delta against the newer, longer + # blob "O". A loose object has no delta base, so %(deltabase) tells us + # which store answered the read: the alternate pack (O) or a loose copy + # (the zero oid). + git init alt-src && + test_seq 1 200 >alt-src/file && + git -C alt-src add file && + git -C alt-src commit -q -m base && + B=$(git -C alt-src rev-parse HEAD:file) && + git -C alt-src cat-file blob "$B" >b-content && + test_seq 1 210 >alt-src/file && + git -C alt-src add file && + git -C alt-src commit -q -m more && + O=$(git -C alt-src rev-parse HEAD:file) && + git -C alt-src repack -adf --window=10 --depth=50 && + + # Precondition: in the alternate, B is a delta based on O. + echo "$B" >in && + echo "$O" >expect && + git -C alt-src cat-file --batch-check="%(deltabase)" actual && + test_cmp expect actual && + + # Main repo: write B as a loose object, before any alternate is active. + git init main && + git -C main hash-object -w --stdin /dev/null && + test_path_is_file "main/.git/objects/$(test_oid_to_path "$B")" && + + # With the alternate active, B must resolve to the alternate packfile + # (deltabase O), not to the main store loose copy (deltabase zero oid). + GIT_ALTERNATE_OBJECT_DIRECTORIES="$PWD/alt-src/.git/objects" \ + git -C main cat-file --batch-check="%(deltabase)" actual && + test_cmp expect actual +' + test_done