diff options
author | Sami Hiltunen <shiltunen@gitlab.com> | 2024-01-12 19:09:37 +0300 |
---|---|---|
committer | Sami Hiltunen <shiltunen@gitlab.com> | 2024-01-13 14:42:39 +0300 |
commit | 9be461c8617c48d3a86069250aba5dca0bc6971f (patch) | |
tree | 50d643d7ed92d8d4e359c7360cf596fce3b29548 | |
parent | 511a64dd9dfbfb9745c4475fe1143b697e5a938b (diff) |
Add QuarantineOnly() for getting the repository with its quarantine only
Repository quarantining works by configuring a temporary object directory
via the `GIT_OBJECTS_DIRECTORY` environment variable, and configuring the
actual object directory as an alternate via `GIT_ALTERNATE_OBJECT_DIRECTORIES`.
This ensures that new objects are written into the quarantine but the old
objects are still readable through the alternate.
There are some use cases where we don't want to have the alternate configured.
For example, if we want to list only the new objects in the quarantine, we can
invoke Git without the alternate configured. This way Git only sees the new
objects.
We're soon going to walk the new objects a transaction introduced to pack
them in TransactionManager. To do so, this commit implements a helper method
to get a quarantined localrepo.Repo instance without the alternates configured.
-rw-r--r-- | internal/git/localrepo/repo.go | 32 | ||||
-rw-r--r-- | internal/git/localrepo/repo_test.go | 53 |
2 files changed, 85 insertions, 0 deletions
diff --git a/internal/git/localrepo/repo.go b/internal/git/localrepo/repo.go index 8bbf40237..7e8f976bc 100644 --- a/internal/git/localrepo/repo.go +++ b/internal/git/localrepo/repo.go @@ -2,6 +2,7 @@ package localrepo import ( "context" + "errors" "fmt" "io" "os" @@ -22,6 +23,7 @@ import ( "gitlab.com/gitlab-org/gitaly/v16/internal/testhelper" "gitlab.com/gitlab-org/gitaly/v16/internal/testhelper/testcfg" "gitlab.com/gitlab-org/gitaly/v16/proto/go/gitalypb" + "google.golang.org/protobuf/proto" ) // Repo represents a local Git repository. @@ -75,6 +77,36 @@ func (repo *Repo) Quarantine(quarantineDirectory string) (*Repo, error) { ), nil } +// QuarantineOnly returns the repository with only the quarantine directory configured as an object +// directory by dropping the alternate object directories. Returns an error if the repository doesn't +// have a quarantine directory configured. +// +// Only the alternates configured in the *gitalypb.Repository object are dropped, not the alternates +// that could be in `objects/info/alternates`. Dropping the configured alternates does however also +// implicitly remove the `objects/info/alternates` in the alternate object directory since the file +// would exist there. The quarantine directory itself would not typically contain an +// `objects/info/alternates` file. +func (repo *Repo) QuarantineOnly() (*Repo, error) { + pbRepo, ok := repo.Repository.(*gitalypb.Repository) + if !ok { + return nil, fmt.Errorf("unexpected repository type %t", repo.Repository) + } + + cloneRepo := proto.Clone(pbRepo).(*gitalypb.Repository) + cloneRepo.GitAlternateObjectDirectories = nil + if cloneRepo.GitObjectDirectory == "" { + return nil, errors.New("repository wasn't quarantined") + } + + return New( + repo.logger, + repo.locator, + repo.gitCmdFactory, + repo.catfileCache, + cloneRepo, + ), nil +} + // NewTestRepo constructs a Repo. It is intended as a helper function for tests which assembles // dependencies ad-hoc from the given config. func NewTestRepo(tb testing.TB, cfg config.Cfg, repo storage.Repository, factoryOpts ...git.ExecCommandFactoryOption) *Repo { diff --git a/internal/git/localrepo/repo_test.go b/internal/git/localrepo/repo_test.go index 41862391d..f5208ef4f 100644 --- a/internal/git/localrepo/repo_test.go +++ b/internal/git/localrepo/repo_test.go @@ -3,6 +3,7 @@ package localrepo import ( "bytes" "context" + "errors" "fmt" "path/filepath" "testing" @@ -171,6 +172,58 @@ func TestRepo_Quarantine_nonExistentRepository(t *testing.T) { } } +func TestRepo_QuarantineOnly(t *testing.T) { + t.Parallel() + + cfg := testcfg.Build(t) + catfileCache := catfile.NewCache(cfg) + defer catfileCache.Stop() + + ctx := testhelper.Context(t) + repoProto, repoPath := gittest.CreateRepository(t, ctx, cfg, gittest.CreateRepositoryConfig{ + SkipCreationViaService: true, + }) + + unquarantinedRepo := New( + testhelper.NewLogger(t), + config.NewLocator(cfg), + gittest.NewCommandFactory(t, cfg), + catfileCache, + repoProto, + ) + + t.Run("fails with unquarantined repository", func(t *testing.T) { + t.Parallel() + + _, err := unquarantinedRepo.QuarantineOnly() + require.Equal(t, err, errors.New("repository wasn't quarantined")) + }) + + t.Run("returns the repository with only the quarantine directory", func(t *testing.T) { + t.Parallel() + + quarantinedRepo, err := unquarantinedRepo.Quarantine(filepath.Join(repoPath, "quarantine-directory")) + require.NoError(t, err) + + expectedRepo := &gitalypb.Repository{ + StorageName: repoProto.StorageName, + RelativePath: repoProto.RelativePath, + GlRepository: repoProto.GlRepository, + GlProjectPath: repoProto.GlProjectPath, + GitObjectDirectory: "quarantine-directory", + GitAlternateObjectDirectories: []string{"objects"}, + } + + testhelper.ProtoEqual(t, expectedRepo, quarantinedRepo.Repository) + + onlyQuarantineRepo, err := quarantinedRepo.QuarantineOnly() + require.NoError(t, err) + + expectedRepo.GitAlternateObjectDirectories = nil + testhelper.ProtoEqual(t, expectedRepo, onlyQuarantineRepo.Repository) + }) +} + func TestRepo_StorageTempDir(t *testing.T) { ctx := testhelper.Context(t) cfg := testcfg.Build(t) |