@@ -19,6 +19,16 @@ import (
1919// git CLI, or skips the test when the installed git is too old to support it.
2020// It returns the repo dir and the initial commit hash.
2121func initReftableRepo (t * testing.T , name , content string ) (string , string ) {
22+ t .Helper ()
23+ return initReftableRepoWithFormat (t , "" , name , content )
24+ }
25+
26+ // initReftableRepoWithFormat is initReftableRepo with an explicit git object
27+ // format. An empty objectFormat uses git's default (sha1); "sha256" exercises
28+ // the sha256 hash, which additionally makes git write extensions.objectformat
29+ // into the repo config. The test is skipped when the installed git cannot
30+ // initialize the requested reftable + object-format combination.
31+ func initReftableRepoWithFormat (t * testing.T , objectFormat , name , content string ) (string , string ) {
2232 t .Helper ()
2333 repoDir := t .TempDir ()
2434
@@ -28,7 +38,12 @@ func initReftableRepo(t *testing.T, name, content string) (string, string) {
2838 "GIT_TERMINAL_PROMPT=0" ,
2939 )
3040
31- initCmd := exec .Command ("git" , "init" , "-b" , "main" , "--ref-format=reftable" , repoDir ) //nolint:noctx // test capability probe
41+ initArgs := []string {"init" , "-b" , "main" , "--ref-format=reftable" }
42+ if objectFormat != "" {
43+ initArgs = append (initArgs , "--object-format=" + objectFormat )
44+ }
45+ initArgs = append (initArgs , repoDir )
46+ initCmd := exec .Command ("git" , initArgs ... ) //nolint:noctx // test capability probe
3247 initCmd .Env = env
3348 if out , err := initCmd .CombinedOutput (); err != nil {
3449 t .Skipf ("git does not support reftable repositories: %v\n %s" , err , out )
@@ -47,6 +62,11 @@ func initReftableRepo(t *testing.T, name, content string) (string, string) {
4762 if got := git ("rev-parse" , "--show-ref-format" ); got != "reftable" {
4863 t .Skipf ("git initialized ref format %q, not reftable" , got )
4964 }
65+ if objectFormat != "" {
66+ if got := git ("rev-parse" , "--show-object-format" ); got != objectFormat {
67+ t .Skipf ("git initialized object format %q, not %q" , got , objectFormat )
68+ }
69+ }
5070 git ("config" , "user.name" , "Test User" )
5171 git ("config" , "user.email" , "test@example.com" )
5272 git ("config" , "commit.gpgsign" , "false" )
@@ -57,6 +77,22 @@ func initReftableRepo(t *testing.T, name, content string) (string, string) {
5777 return repoDir , git ("rev-parse" , "HEAD" )
5878}
5979
80+ // setRepoConfig sets a local git config key in an existing repo, using an
81+ // isolated global/system config so the developer's real git config is never
82+ // read or written (matching the reftable test helpers).
83+ func setRepoConfig (t * testing.T , repoDir , key , value string ) {
84+ t .Helper ()
85+ cmd := exec .Command ("git" , "config" , key , value ) //nolint:noctx // test helper
86+ cmd .Dir = repoDir
87+ cmd .Env = append (os .Environ (),
88+ "GIT_CONFIG_GLOBAL=" + filepath .Join (t .TempDir (), "gitconfig" ),
89+ "GIT_CONFIG_SYSTEM=/dev/null" ,
90+ "GIT_TERMINAL_PROMPT=0" ,
91+ )
92+ out , err := cmd .CombinedOutput ()
93+ require .NoErrorf (t , err , "git config %s %s: %s" , key , value , out )
94+ }
95+
6096// reftableCommit adds a file and commits it in an existing reftable repo,
6197// returning the new HEAD hash. The repo's user identity is already configured
6298// by initReftableRepo, so only an isolated global/system config is supplied.
@@ -607,6 +643,66 @@ func TestOpenPath_ReftableRepository(t *testing.T) {
607643 require .NoError (t , repo .Storer .RemoveReference (newRef .Name ()))
608644}
609645
646+ // TestOpenPath_Sha256ReftableRepository confirms that a reftable repository
647+ // using the sha256 object format can be opened and that refs round-trip through
648+ // the git-CLI-backed storer.
649+ //
650+ // Such a repository declares TWO extensions in its config:
651+ // extensions.refstorage=reftable AND extensions.objectformat=sha256. go-git's
652+ // verifyExtensions asks the storer's SupportsExtension whether each declared
653+ // extension is supported. The embedded filesystem Storage approves
654+ // objectformat=sha256, but reftableStorer defines its own SupportsExtension
655+ // (to advertise refstorage), which shadows the embedded method by Go's
656+ // promotion rules. As written it approves only refstorage, so objectformat is
657+ // reported unsupported and go-git rejects the open with ErrUnknownExtension.
658+ // The reftable backend thus silently breaks sha256 repositories. This test
659+ // pins the correct behaviour and is the regression guard for that gap in #547.
660+ func TestOpenPath_Sha256ReftableRepository (t * testing.T ) {
661+ t .Parallel ()
662+ repoDir , headHash := initReftableRepoWithFormat (t , "sha256" , "file.txt" , "hello\n " )
663+
664+ repo , err := OpenPath (repoDir )
665+ require .NoError (t , err , "sha256 reftable repository should open; objectformat extension must stay supported" )
666+ defer repo .Close ()
667+
668+ head , err := repo .Head ()
669+ require .NoError (t , err )
670+ require .Equal (t , "refs/heads/main" , head .Name ().String ())
671+ require .Equal (t , headHash , head .Hash ().String ())
672+
673+ // A ref write/read round-trips through the git-CLI-backed storer, proving
674+ // the sha256 repo is not merely openable but usable.
675+ newRef := plumbing .NewHashReference (plumbing .ReferenceName ("refs/entire/sha256" ), head .Hash ())
676+ require .NoError (t , repo .Storer .SetReference (newRef ))
677+ got , err := repo .Storer .Reference (newRef .Name ())
678+ require .NoError (t , err )
679+ require .Equal (t , head .Hash (), got .Hash ())
680+ }
681+
682+ // TestOpenPath_WorktreeConfigReftableRepository confirms that a reftable
683+ // repository that also enables the worktreeConfig extension can be opened.
684+ //
685+ // This is the same extension-shadowing gap as
686+ // TestOpenPath_Sha256ReftableRepository: the embedded filesystem Storage
687+ // approves worktreeconfig=true/false, but reftableStorer's own
688+ // SupportsExtension shadows that method and approves only refstorage. A
689+ // reftable repo with extensions.worktreeConfig=true therefore fails to open
690+ // with ErrUnknownExtension. Regression guard for that gap in #547.
691+ func TestOpenPath_WorktreeConfigReftableRepository (t * testing.T ) {
692+ t .Parallel ()
693+ repoDir , headHash := initReftableRepo (t , "file.txt" , "hello\n " )
694+ setRepoConfig (t , repoDir , "extensions.worktreeConfig" , "true" )
695+
696+ repo , err := OpenPath (repoDir )
697+ require .NoError (t , err , "reftable repository with worktreeConfig should open; worktreeconfig extension must stay supported" )
698+ defer repo .Close ()
699+
700+ head , err := repo .Head ()
701+ require .NoError (t , err )
702+ require .Equal (t , "refs/heads/main" , head .Name ().String ())
703+ require .Equal (t , headHash , head .Hash ().String ())
704+ }
705+
610706// TestRepoUsesReftable_Detection checks that reftable detection distinguishes
611707// reftable repositories from classic files-backend repositories.
612708func TestRepoUsesReftable_Detection (t * testing.T ) {
0 commit comments