diff options
author | Sami Hiltunen <shiltunen@gitlab.com> | 2022-08-01 11:54:11 +0300 |
---|---|---|
committer | Sami Hiltunen <shiltunen@gitlab.com> | 2022-08-01 11:54:11 +0300 |
commit | c61904befa4e882cc3990db5f9021c4a559cd8f1 (patch) | |
tree | 0804b95f8491c8f9964050d29f6201d52b8919b8 | |
parent | 32e72fea8fc391e76812cd1cde295b12c34d5174 (diff) | |
parent | 018958fb1cb4a8550d15bbd096b55fdee9f2a3b4 (diff) |
Merge branch 'pks-objectpool-fetch-df-conflict' into 'master'
objectpool: Fix conflicting references when fetching into pools
Closes #4373
See merge request gitlab-org/gitaly!4745
-rw-r--r-- | internal/git/gittest/commit.go | 20 | ||||
-rw-r--r-- | internal/git/gittest/commit_test.go | 14 | ||||
-rw-r--r-- | internal/git/gittest/delta_islands.go | 31 | ||||
-rw-r--r-- | internal/git/housekeeping/objects_test.go | 2 | ||||
-rw-r--r-- | internal/git/objectpool/fetch.go | 141 | ||||
-rw-r--r-- | internal/git/objectpool/fetch_test.go | 52 | ||||
-rw-r--r-- | internal/gitaly/service/objectpool/fetch_into_object_pool_test.go | 228 | ||||
-rw-r--r-- | internal/gitaly/service/repository/gc_test.go | 2 | ||||
-rw-r--r-- | internal/gitaly/service/repository/repack_test.go | 2 | ||||
-rw-r--r-- | internal/metadata/featureflag/ff_fetch_into_object_pool_prune_refs.go | 11 | ||||
-rw-r--r-- | internal/praefect/protoregistry/find_oid_test.go | 1 | ||||
-rw-r--r-- | proto/go/gitalypb/objectpool.pb.go | 144 | ||||
-rw-r--r-- | proto/go/gitalypb/objectpool_grpc.pb.go | 10 | ||||
-rw-r--r-- | proto/objectpool.proto | 20 | ||||
-rw-r--r-- | ruby/proto/gitaly/objectpool_pb.rb | 1 | ||||
-rw-r--r-- | ruby/proto/gitaly/objectpool_services_pb.rb | 5 |
16 files changed, 534 insertions, 150 deletions
diff --git a/internal/git/gittest/commit.go b/internal/git/gittest/commit.go index 526e73545..3ffc1035a 100644 --- a/internal/git/gittest/commit.go +++ b/internal/git/gittest/commit.go @@ -32,7 +32,7 @@ var ( ) type writeCommitConfig struct { - branch string + reference string parents []git.ObjectID authorDate time.Time authorName string @@ -47,14 +47,20 @@ type writeCommitConfig struct { // WriteCommitOption is an option which can be passed to WriteCommit. type WriteCommitOption func(*writeCommitConfig) -// WithBranch is an option for WriteCommit which will cause it to update the update the given branch -// name to the new commit. -func WithBranch(branch string) WriteCommitOption { +// WithReference is an option for WriteCommit which will cause it to update the given reference to +// point to the new commit. This function requires the fully-qualified reference name. +func WithReference(reference string) WriteCommitOption { return func(cfg *writeCommitConfig) { - cfg.branch = branch + cfg.reference = reference } } +// WithBranch is an option for WriteCommit which will cause it to update the given branch name to +// the new commit. +func WithBranch(branch string) WriteCommitOption { + return WithReference("refs/heads/" + branch) +} + // WithMessage is an option for WriteCommit which will set the commit message. func WithMessage(message string) WriteCommitOption { return func(cfg *writeCommitConfig) { @@ -218,10 +224,10 @@ func WriteCommit(t testing.TB, cfg config.Cfg, repoPath string, opts ...WriteCom oid, err := DefaultObjectHash.FromHex(text.ChompBytes(stdout)) require.NoError(t, err) - if writeCommitConfig.branch != "" { + if writeCommitConfig.reference != "" { ExecOpts(t, cfg, ExecConfig{ Env: env, - }, "-C", repoPath, "update-ref", "refs/heads/"+writeCommitConfig.branch, oid.String()) + }, "-C", repoPath, "update-ref", writeCommitConfig.reference, oid.String()) } return oid diff --git a/internal/git/gittest/commit_test.go b/internal/git/gittest/commit_test.go index d1f279554..b4fa49a17 100644 --- a/internal/git/gittest/commit_test.go +++ b/internal/git/gittest/commit_test.go @@ -125,6 +125,20 @@ func TestWriteCommit(t *testing.T) { expectedRevUpdate: "refs/heads/foo", }, { + desc: "with reference", + opts: []WriteCommitOption{ + WithReference("refs/custom/namespace"), + }, + expectedCommit: strings.Join([]string{ + "tree " + DefaultObjectHash.EmptyTreeOID.String(), + "author " + DefaultCommitterSignature, + "committer " + DefaultCommitterSignature, + "", + "message", + }, "\n"), + expectedRevUpdate: "refs/custom/namespace", + }, + { desc: "with tree entry", opts: []WriteCommitOption{ WithTreeEntries(treeEntryA), diff --git a/internal/git/gittest/delta_islands.go b/internal/git/gittest/delta_islands.go index c582d0813..485afe95e 100644 --- a/internal/git/gittest/delta_islands.go +++ b/internal/git/gittest/delta_islands.go @@ -11,8 +11,19 @@ import ( ) // TestDeltaIslands checks whether functions that repack objects in a repository correctly set up -// delta islands. Based on https://github.com/git/git/blob/master/t/t5320-delta-islands.sh. -func TestDeltaIslands(t *testing.T, cfg config.Cfg, repoPath string, isPoolRepo bool, repack func() error) { +// delta islands. Based on https://github.com/git/git/blob/master/t/t5320-delta-islands.sh. Note +// that this function accepts two different repository paths: one repo to modify that shall grow the +// new references and objects, and one repository that we ultimately end up repacking. In the +// general case these should refer to the same repository, but for object pools these may be the +// pool member and the pool, respectively. +func TestDeltaIslands( + t *testing.T, + cfg config.Cfg, + repoPathToModify string, + repoPathToRepack string, + isPoolRepo bool, + repack func() error, +) { t.Helper() // Create blobs that we expect Git to use delta compression on. @@ -32,25 +43,25 @@ func TestDeltaIslands(t *testing.T, cfg config.Cfg, repoPath string, isPoolRepo } // Make the first two blobs reachable via references that are part of the delta island. - blob1ID := commitBlob(t, cfg, repoPath, refsPrefix+"/heads/branch1", blob1) - blob2ID := commitBlob(t, cfg, repoPath, refsPrefix+"/tags/tag2", blob2) + blob1ID := commitBlob(t, cfg, repoPathToModify, refsPrefix+"/heads/branch1", blob1) + blob2ID := commitBlob(t, cfg, repoPathToModify, refsPrefix+"/tags/tag2", blob2) // The bad blob will only be reachable via a reference that is not covered by a delta // island. Because of that it should be excluded from delta chains in the main island. - badBlobID := commitBlob(t, cfg, repoPath, refsPrefix+"/bad/ref3", badBlob) + badBlobID := commitBlob(t, cfg, repoPathToModify, refsPrefix+"/bad/ref3", badBlob) // Repack all objects into a single pack so that we can verify that delta chains are built // by Git as expected. Most notably, we don't use the delta islands here yet and thus the // delta base for both blob1 and blob2 should be the bad blob. - Exec(t, cfg, "-C", repoPath, "repack", "-ad") - require.Equal(t, badBlobID, deltaBase(t, cfg, repoPath, blob1ID), "expect blob 1 delta base to be bad blob after test setup") - require.Equal(t, badBlobID, deltaBase(t, cfg, repoPath, blob2ID), "expect blob 2 delta base to be bad blob after test setup") + Exec(t, cfg, "-C", repoPathToModify, "repack", "-ad") + require.Equal(t, badBlobID, deltaBase(t, cfg, repoPathToModify, blob1ID), "expect blob 1 delta base to be bad blob after test setup") + require.Equal(t, badBlobID, deltaBase(t, cfg, repoPathToModify, blob2ID), "expect blob 2 delta base to be bad blob after test setup") // Now we run the repacking function provided to us by the caller. We expect it to use delta // chains, and thus neither of the two blobs should use the bad blob as delta base. require.NoError(t, repack(), "repack after delta island setup") - require.Equal(t, blob2ID, deltaBase(t, cfg, repoPath, blob1ID), "blob 1 delta base should be blob 2 after repack") - require.Equal(t, DefaultObjectHash.ZeroOID.String(), deltaBase(t, cfg, repoPath, blob2ID), "blob 2 should not be delta compressed after repack") + require.Equal(t, blob2ID, deltaBase(t, cfg, repoPathToRepack, blob1ID), "blob 1 delta base should be blob 2 after repack") + require.Equal(t, DefaultObjectHash.ZeroOID.String(), deltaBase(t, cfg, repoPathToRepack, blob2ID), "blob 2 should not be delta compressed after repack") } func commitBlob(t *testing.T, cfg config.Cfg, repoPath, ref string, content string) string { diff --git a/internal/git/housekeeping/objects_test.go b/internal/git/housekeeping/objects_test.go index 6156c65bf..156a58ff2 100644 --- a/internal/git/housekeeping/objects_test.go +++ b/internal/git/housekeeping/objects_test.go @@ -60,7 +60,7 @@ func TestRepackObjects(t *testing.T) { }) repo := localrepo.NewTestRepo(t, cfg, repoProto) - gittest.TestDeltaIslands(t, cfg, repoPath, IsPoolRepository(repoProto), func() error { + gittest.TestDeltaIslands(t, cfg, repoPath, repoPath, IsPoolRepository(repoProto), func() error { return RepackObjects(ctx, repo, RepackObjectsConfig{ FullRepack: true, }) diff --git a/internal/git/objectpool/fetch.go b/internal/git/objectpool/fetch.go index 2c491d9a0..0ee7d6d0a 100644 --- a/internal/git/objectpool/fetch.go +++ b/internal/git/objectpool/fetch.go @@ -16,8 +16,13 @@ import ( "gitlab.com/gitlab-org/gitaly/v15/internal/git" "gitlab.com/gitlab-org/gitaly/v15/internal/git/localrepo" "gitlab.com/gitlab-org/gitaly/v15/internal/git/updateref" + "gitlab.com/gitlab-org/gitaly/v15/internal/gitaly/transaction" + "gitlab.com/gitlab-org/gitaly/v15/internal/metadata/featureflag" + "gitlab.com/gitlab-org/gitaly/v15/internal/transaction/voting" ) +var objectPoolRefspec = fmt.Sprintf("+refs/*:%s/*", git.ObjectPoolRefNamespace) + // FetchFromOrigin initializes the pool and fetches the objects from its origin repository func (o *ObjectPool) FetchFromOrigin(ctx context.Context, origin *localrepo.Repo) error { if err := o.Init(ctx); err != nil { @@ -37,7 +42,28 @@ func (o *ObjectPool) FetchFromOrigin(ctx context.Context, origin *localrepo.Repo return fmt.Errorf("computing stats before fetch: %w", err) } - refSpec := fmt.Sprintf("+refs/*:%s/*", git.ObjectPoolRefNamespace) + // Ideally we wouldn't want to prune old references at all so that we can keep alive all + // objects without having to create loads of dangling references. But unfortunately keeping + // around old refs can lead to D/F conflicts between old references that have since + // been deleted in the pool and new references that have been added in the pool member we're + // fetching from. E.g. if we have the old reference `refs/heads/branch` and the pool member + // has replaced that since with a new reference `refs/heads/branch/conflict` then + // the fetch would now always fail because of that conflict. + // + // Due to the lack of an alternative to resolve that conflict we are thus forced to enable + // pruning. This isn't too bad given that we know to keep alive the old objects via dangling + // refs anyway, but I'd sleep easier if we didn't have to do this. + // + // Note that we need to perform the pruning separately from the fetch: if the fetch is using + // `--atomic` and `--prune` together then it still wouldn't be able to recover from the D/F + // conflict. So we first to a preliminary prune that only prunes refs without fetching + // objects yet to avoid that scenario. + if featureflag.FetchIntoObjectPoolPruneRefs.IsEnabled(ctx) { + if err := o.pruneReferences(ctx, origin); err != nil { + return fmt.Errorf("pruning references: %w", err) + } + } + var stderr bytes.Buffer if err := o.Repo.ExecAndWait(ctx, git.SubCmd{ @@ -54,7 +80,7 @@ func (o *ObjectPool) FetchFromOrigin(ctx context.Context, origin *localrepo.Repo // references. git.Flag{Name: "--no-write-fetch-head"}, }, - Args: []string{originPath, refSpec}, + Args: []string{originPath, objectPoolRefspec}, }, git.WithRefTxHook(o.Repo), git.WithStderr(&stderr), @@ -78,6 +104,117 @@ func (o *ObjectPool) FetchFromOrigin(ctx context.Context, origin *localrepo.Repo return nil } +// pruneReferences prunes any references that have been deleted in the origin repository. +func (o *ObjectPool) pruneReferences(ctx context.Context, origin *localrepo.Repo) error { + originPath, err := origin.Path() + if err != nil { + return fmt.Errorf("computing origin repo's path: %w", err) + } + + // Ideally, we'd just use `git remote prune` directly. But unfortunately, this command does + // not support atomic updates, but will instead use a separate reference transaction for + // updating the packed-refs file and for updating each of the loose references. This can be + // really expensive in case we are about to prune a lot of references given that every time, + // the reference-transaction hook needs to vote on the deletion and reach quorum. + // + // Instead we ask for a dry-run, parse the output and queue up every reference into a + // git-update-ref(1) process. While ugly, it works around the performance issues. + prune, err := o.Repo.Exec(ctx, + git.SubSubCmd{ + Name: "remote", + Action: "prune", + Args: []string{"origin"}, + Flags: []git.Option{ + git.Flag{Name: "--dry-run"}, + }, + }, + git.WithConfig(git.ConfigPair{Key: "remote.origin.url", Value: originPath}), + git.WithConfig(git.ConfigPair{Key: "remote.origin.fetch", Value: objectPoolRefspec}), + // This is a dry-run, only, so we don't have to enable hooks. + git.WithDisabledHooks(), + ) + if err != nil { + return fmt.Errorf("spawning prune: %w", err) + } + + updater, err := updateref.New(ctx, o.Repo) + if err != nil { + return fmt.Errorf("spawning updater: %w", err) + } + + // We need to manually compute a vote because all deletions we queue up here are + // force-deletions. We are forced to filter out force-deletions because these may also + // happen when evicting references from the packed-refs file. + voteHash := voting.NewVoteHash() + + scanner := bufio.NewScanner(prune) + for scanner.Scan() { + line := scanner.Bytes() + + // We need to skip the first two lines that represent the header of git-remote(1)'s + // output. While we should ideally just use a state machine here, it doesn't feel + // worth it given that the output is comparatively simple and given that the pruned + // branches are distinguished by a special prefix. + switch { + case bytes.Equal(line, []byte("Pruning origin")): + continue + case bytes.HasPrefix(line, []byte("URL: ")): + continue + case bytes.HasPrefix(line, []byte(" * [would prune] ")): + // The references announced by git-remote(1) only have the remote's name as + // prefix, which is "origin". We thus have to reassemble the complete name + // of every reference here. + deletedRef := "refs/remotes/" + string(bytes.TrimPrefix(line, []byte(" * [would prune] "))) + + if _, err := io.Copy(voteHash, strings.NewReader(fmt.Sprintf("%[1]s %[1]s %s\n", git.ObjectHashSHA1.ZeroOID, deletedRef))); err != nil { + return fmt.Errorf("hashing reference deletion: %w", err) + } + + if err := updater.Delete(git.ReferenceName(deletedRef)); err != nil { + return fmt.Errorf("queueing ref for deletion: %w", err) + } + default: + return fmt.Errorf("unexpected line: %q", line) + } + } + + if err := scanner.Err(); err != nil { + return fmt.Errorf("scanning deleted refs: %w", err) + } + + if err := prune.Wait(); err != nil { + return fmt.Errorf("waiting for prune: %w", err) + } + + vote, err := voteHash.Vote() + if err != nil { + return fmt.Errorf("computing vote: %w", err) + } + + // Prepare references so that they're locked and cannot be written by any concurrent + // processes. This also verifies that we can indeed delete the references. + if err := updater.Prepare(); err != nil { + return fmt.Errorf("preparing deletion of references: %w", err) + } + + // Vote on the references we're about to delete. + if err := transaction.VoteOnContext(ctx, o.txManager, vote, voting.Prepared); err != nil { + return fmt.Errorf("preparational vote on pruned references: %w", err) + } + + // Commit the pruned references to disk so that the change gets applied. + if err := updater.Commit(); err != nil { + return fmt.Errorf("deleting references: %w", err) + } + + // And then confirm that we actually deleted the references. + if err := transaction.VoteOnContext(ctx, o.txManager, vote, voting.Committed); err != nil { + return fmt.Errorf("preparational vote on pruned references: %w", err) + } + + return nil +} + const danglingObjectNamespace = "refs/dangling/" // rescueDanglingObjects creates refs for all dangling objects if finds diff --git a/internal/git/objectpool/fetch_test.go b/internal/git/objectpool/fetch_test.go index 514927555..2df9a769d 100644 --- a/internal/git/objectpool/fetch_test.go +++ b/internal/git/objectpool/fetch_test.go @@ -3,6 +3,7 @@ package objectpool import ( + "context" "fmt" "os" "path/filepath" @@ -16,13 +17,18 @@ import ( "gitlab.com/gitlab-org/gitaly/v15/internal/git/localrepo" "gitlab.com/gitlab-org/gitaly/v15/internal/gitaly/config" "gitlab.com/gitlab-org/gitaly/v15/internal/helper/text" + "gitlab.com/gitlab-org/gitaly/v15/internal/metadata/featureflag" "gitlab.com/gitlab-org/gitaly/v15/internal/testhelper" ) func TestFetchFromOrigin_dangling(t *testing.T) { t.Parallel() + testhelper.NewFeatureSets(featureflag.FetchIntoObjectPoolPruneRefs).Run(t, testFetchFromOriginDangling) +} + +func testFetchFromOriginDangling(t *testing.T, ctx context.Context) { + t.Parallel() - ctx := testhelper.Context(t) cfg, pool, repoProto := setupObjectPool(t, ctx) repo := localrepo.NewTestRepo(t, cfg, repoProto) @@ -92,8 +98,12 @@ func TestFetchFromOrigin_dangling(t *testing.T) { func TestFetchFromOrigin_fsck(t *testing.T) { t.Parallel() + testhelper.NewFeatureSets(featureflag.FetchIntoObjectPoolPruneRefs).Run(t, testFetchFromOriginFsck) +} + +func testFetchFromOriginFsck(t *testing.T, ctx context.Context) { + t.Parallel() - ctx := testhelper.Context(t) cfg, pool, repoProto := setupObjectPool(t, ctx) repo := localrepo.NewTestRepo(t, cfg, repoProto) repoPath := filepath.Join(cfg.Storages[0].Path, repo.GetRelativePath()) @@ -117,8 +127,12 @@ func TestFetchFromOrigin_fsck(t *testing.T) { func TestFetchFromOrigin_deltaIslands(t *testing.T) { t.Parallel() + testhelper.NewFeatureSets(featureflag.FetchIntoObjectPoolPruneRefs).Run(t, testFetchFromOriginDeltaIslands) +} + +func testFetchFromOriginDeltaIslands(t *testing.T, ctx context.Context) { + t.Parallel() - ctx := testhelper.Context(t) cfg, pool, repoProto := setupObjectPool(t, ctx) repo := localrepo.NewTestRepo(t, cfg, repoProto) @@ -128,24 +142,22 @@ func TestFetchFromOrigin_deltaIslands(t *testing.T) { require.NoError(t, pool.FetchFromOrigin(ctx, repo), "seed pool") require.NoError(t, pool.Link(ctx, repo)) - gittest.TestDeltaIslands(t, cfg, pool.FullPath(), true, func() error { - // The first fetch has already fetched all objects into the pool repository, so - // there is nothing new to fetch anymore. Consequentially, FetchFromOrigin doesn't - // alter the object database and thus OptimizeRepository would notice that nothing - // needs to be optimized. - // - // We thus write a new commit into the pool member's repository so that we end up - // with two packfiles after the fetch. - gittest.WriteCommit(t, cfg, repoPath, gittest.WithBranch("changed-ref")) - + // The setup of delta islands is done in the normal repository, and thus we pass `false` + // for `isPoolRepo`. Verification whether we correctly handle repacking though happens in + // the pool repository. + gittest.TestDeltaIslands(t, cfg, repoPath, pool.FullPath(), false, func() error { return pool.FetchFromOrigin(ctx, repo) }) } func TestFetchFromOrigin_bitmapHashCache(t *testing.T) { t.Parallel() + testhelper.NewFeatureSets(featureflag.FetchIntoObjectPoolPruneRefs).Run(t, testFetchFromOriginBitmapHashCache) +} + +func testFetchFromOriginBitmapHashCache(t *testing.T, ctx context.Context) { + t.Parallel() - ctx := testhelper.Context(t) cfg, pool, repoProto := setupObjectPool(t, ctx) repo := localrepo.NewTestRepo(t, cfg, repoProto) @@ -170,8 +182,12 @@ func TestFetchFromOrigin_bitmapHashCache(t *testing.T) { func TestFetchFromOrigin_refUpdates(t *testing.T) { t.Parallel() + testhelper.NewFeatureSets(featureflag.FetchIntoObjectPoolPruneRefs).Run(t, testFetchFromOriginRefUpdates) +} + +func testFetchFromOriginRefUpdates(t *testing.T, ctx context.Context) { + t.Parallel() - ctx := testhelper.Context(t) cfg, pool, repoProto := setupObjectPool(t, ctx) repo := localrepo.NewTestRepo(t, cfg, repoProto) repoPath := filepath.Join(cfg.Storages[0].Path, repo.GetRelativePath()) @@ -223,8 +239,12 @@ func TestFetchFromOrigin_refUpdates(t *testing.T) { func TestFetchFromOrigin_refs(t *testing.T) { t.Parallel() + testhelper.NewFeatureSets(featureflag.FetchIntoObjectPoolPruneRefs).Run(t, testFetchFromOriginRefs) +} + +func testFetchFromOriginRefs(t *testing.T, ctx context.Context) { + t.Parallel() - ctx := testhelper.Context(t) cfg, pool, _ := setupObjectPool(t, ctx) poolPath := pool.FullPath() diff --git a/internal/gitaly/service/objectpool/fetch_into_object_pool_test.go b/internal/gitaly/service/objectpool/fetch_into_object_pool_test.go index c9c6aaa10..03262537b 100644 --- a/internal/gitaly/service/objectpool/fetch_into_object_pool_test.go +++ b/internal/gitaly/service/objectpool/fetch_into_object_pool_test.go @@ -3,8 +3,11 @@ package objectpool import ( + "context" + "fmt" "os" "path/filepath" + "sync" "testing" "time" @@ -19,21 +22,30 @@ import ( "gitlab.com/gitlab-org/gitaly/v15/internal/git/housekeeping" "gitlab.com/gitlab-org/gitaly/v15/internal/gitaly/config" "gitlab.com/gitlab-org/gitaly/v15/internal/gitaly/transaction" + "gitlab.com/gitlab-org/gitaly/v15/internal/helper" + "gitlab.com/gitlab-org/gitaly/v15/internal/metadata" + "gitlab.com/gitlab-org/gitaly/v15/internal/metadata/featureflag" "gitlab.com/gitlab-org/gitaly/v15/internal/testhelper" "gitlab.com/gitlab-org/gitaly/v15/internal/testhelper/testcfg" "gitlab.com/gitlab-org/gitaly/v15/internal/testhelper/testserver" + "gitlab.com/gitlab-org/gitaly/v15/internal/transaction/txinfo" + "gitlab.com/gitlab-org/gitaly/v15/internal/transaction/voting" "gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb" "gitlab.com/gitlab-org/labkit/log" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials/insecure" - "google.golang.org/grpc/status" + "google.golang.org/grpc/peer" ) func TestFetchIntoObjectPool_Success(t *testing.T) { t.Parallel() + testhelper.NewFeatureSets(featureflag.FetchIntoObjectPoolPruneRefs).Run(t, testFetchIntoObjectPoolSuccess) +} + +func testFetchIntoObjectPoolSuccess(t *testing.T, ctx context.Context) { + t.Parallel() - ctx := testhelper.Context(t) cfg, repo, repoPath, locator, client := setup(ctx, t) repoCommit := gittest.WriteCommit(t, cfg, repoPath, gittest.WithBranch(t.Name())) @@ -48,7 +60,6 @@ func TestFetchIntoObjectPool_Success(t *testing.T) { req := &gitalypb.FetchIntoObjectPoolRequest{ ObjectPool: pool.ToProto(), Origin: repo, - Repack: true, } _, err = client.FetchIntoObjectPool(ctx, req) @@ -85,16 +96,37 @@ func TestFetchIntoObjectPool_Success(t *testing.T) { require.Error(t, err, "Expected refs/heads/broken to be deleted") } -func TestFetchIntoObjectPool_hooks(t *testing.T) { - cfg := testcfg.Build(t) - gitCmdFactory := gittest.NewCommandFactory(t, cfg, git.WithHooksPath(testhelper.TempDir(t))) +func TestFetchIntoObjectPool_transactional(t *testing.T) { + t.Parallel() + testhelper.NewFeatureSets(featureflag.FetchIntoObjectPoolPruneRefs).Run(t, testFetchIntoObjectPoolTransactional) +} - cfg.SocketPath = runObjectPoolServer(t, cfg, config.NewLocator(cfg), testhelper.NewDiscardingLogger(t), testserver.WithGitCommandFactory(gitCmdFactory)) +func testFetchIntoObjectPoolTransactional(t *testing.T, ctx context.Context) { + t.Parallel() - ctx := testhelper.Context(t) - repo, _ := gittest.CreateRepository(ctx, t, cfg, gittest.CreateRepositoryConfig{ - Seed: gittest.SeedGitLabTest, - }) + var votes []voting.Vote + var votesMutex sync.Mutex + txManager := transaction.MockManager{ + VoteFn: func(_ context.Context, _ txinfo.Transaction, vote voting.Vote, _ voting.Phase) error { + votesMutex.Lock() + defer votesMutex.Unlock() + votes = append(votes, vote) + return nil + }, + } + + cfg := testcfg.Build(t) + cfg.SocketPath = runObjectPoolServer( + t, cfg, config.NewLocator(cfg), + testhelper.NewDiscardingLogger(t), + testserver.WithTransactionManager(&txManager), + // We need to disable Praefect given that we replace transactions with our own logic + // here. + testserver.WithDisablePraefect(), + ) + testcfg.BuildGitalyHooks(t, cfg) + + repo, repoPath := gittest.CreateRepository(ctx, t, cfg) conn, err := grpc.Dial(cfg.SocketPath, grpc.WithTransportCredentials(insecure.NewCredentials())) require.NoError(t, err) @@ -109,24 +141,109 @@ func TestFetchIntoObjectPool_hooks(t *testing.T) { }) require.NoError(t, err) - // Set up a custom reference-transaction hook which simply exits failure. This asserts that - // the RPC doesn't invoke any reference-transaction. - testhelper.WriteExecutable(t, filepath.Join(gitCmdFactory.HooksPath(ctx), "reference-transaction"), []byte("#!/bin/sh\nexit 1\n")) + // CreateObjectPool has a bug because it will leave the configuration of the origin remote + // in the gitconfig. This will get cleaned up at a later point by our housekeeping logic, so + // it doesn't hurt much in the first place to have it. But the cleanup logic would trigger + // another transactional vote which we want to avoid, so we simply unset the configuration + // here. + gittest.Exec(t, cfg, "-C", pool.FullPath(), "config", "--unset", "remote.origin.url") - req := &gitalypb.FetchIntoObjectPoolRequest{ - ObjectPool: pool.ToProto(), - Origin: repo, - Repack: true, - } + // Inject transaction information so that FetchInotObjectPool knows to perform + // transactional voting. + ctx, err = txinfo.InjectTransaction(peer.NewContext(ctx, &peer.Peer{}), 1, "node", true) + require.NoError(t, err) + ctx = metadata.IncomingToOutgoing(ctx) - _, err = client.FetchIntoObjectPool(ctx, req) - testhelper.RequireGrpcError(t, status.Error(codes.Internal, "fetch into object pool: exit status 128, stderr: \"fatal: ref updates aborted by hook\\n\""), err) + t.Run("without changed data", func(t *testing.T) { + votes = nil + + _, err = client.FetchIntoObjectPool(ctx, &gitalypb.FetchIntoObjectPoolRequest{ + ObjectPool: pool.ToProto(), + Origin: repo, + }) + require.NoError(t, err) + + if featureflag.FetchIntoObjectPoolPruneRefs.IsEnabled(ctx) { + require.Equal(t, []voting.Vote{ + // We expect to see two votes that demonstrate we're voting on no deleted + // references. + voting.VoteFromData(nil), voting.VoteFromData(nil), + // It is a bug though that we don't have a vote on the unchanged references + // in git-fetch(1). + }, votes) + } else { + require.Nil(t, votes) + } + }) + + t.Run("with a new reference", func(t *testing.T) { + votes = nil + + // Create a new reference that we'd in fact fetch into the object pool so that we + // know that something will be voted on. + repoCommit := gittest.WriteCommit(t, cfg, repoPath, gittest.WithParents(), gittest.WithBranch("new-branch")) + + _, err = client.FetchIntoObjectPool(ctx, &gitalypb.FetchIntoObjectPoolRequest{ + ObjectPool: pool.ToProto(), + Origin: repo, + }) + require.NoError(t, err) + + vote := voting.VoteFromData([]byte(fmt.Sprintf( + "%s %s refs/remotes/origin/heads/new-branch\n", git.ObjectHashSHA1.ZeroOID, repoCommit, + ))) + if featureflag.FetchIntoObjectPoolPruneRefs.IsEnabled(ctx) { + require.Equal(t, []voting.Vote{ + // The first two votes stem from the fact that we're voting on no + // deleted references. + voting.VoteFromData(nil), voting.VoteFromData(nil), + // And the other two votes are from the new branch we pull in. + vote, vote, + }, votes) + } else { + require.Equal(t, []voting.Vote{vote, vote}, votes) + } + }) + + t.Run("with a stale reference in pool", func(t *testing.T) { + votes = nil + + reference := "refs/remotes/origin/heads/to-be-pruned" + + // Create a commit in the pool repository itself. Right now, we don't touch this + // commit at all, but this will change in one of the next commits. + gittest.WriteCommit(t, cfg, pool.FullPath(), gittest.WithParents(), gittest.WithReference(reference)) + + _, err = client.FetchIntoObjectPool(ctx, &gitalypb.FetchIntoObjectPoolRequest{ + ObjectPool: pool.ToProto(), + Origin: repo, + }) + require.NoError(t, err) + + if featureflag.FetchIntoObjectPoolPruneRefs.IsEnabled(ctx) { + // We expect a single vote on the reference we have deleted. + vote := voting.VoteFromData([]byte(fmt.Sprintf( + "%[1]s %[1]s %s\n", git.ObjectHashSHA1.ZeroOID, reference, + ))) + require.Equal(t, []voting.Vote{vote, vote}, votes) + } else { + require.Nil(t, votes) + } + + exists, err := pool.Repo.HasRevision(ctx, git.Revision(reference)) + require.NoError(t, err) + require.Equal(t, exists, featureflag.FetchIntoObjectPoolPruneRefs.IsDisabled(ctx)) + }) } func TestFetchIntoObjectPool_CollectLogStatistics(t *testing.T) { t.Parallel() + testhelper.NewFeatureSets(featureflag.FetchIntoObjectPoolPruneRefs).Run(t, testFetchIntoObjectPoolCollectLogStatistics) +} + +func testFetchIntoObjectPoolCollectLogStatistics(t *testing.T, ctx context.Context) { + t.Parallel() - ctx := testhelper.Context(t) cfg := testcfg.Build(t) testcfg.BuildGitalyHooks(t, cfg) @@ -155,7 +272,6 @@ func TestFetchIntoObjectPool_CollectLogStatistics(t *testing.T) { req := &gitalypb.FetchIntoObjectPoolRequest{ ObjectPool: pool.ToProto(), Origin: repo, - Repack: true, } _, err = client.FetchIntoObjectPool(ctx, req) @@ -252,3 +368,69 @@ func TestFetchIntoObjectPool_Failure(t *testing.T) { }) } } + +func TestFetchIntoObjectPool_dfConflict(t *testing.T) { + t.Parallel() + testhelper.NewFeatureSets(featureflag.FetchIntoObjectPoolPruneRefs).Run(t, testFetchIntoObjectPoolDfConflict) +} + +func testFetchIntoObjectPoolDfConflict(t *testing.T, ctx context.Context) { + t.Parallel() + + cfg, repo, repoPath, _, client := setup(ctx, t) + + pool := initObjectPool(t, cfg, cfg.Storages[0]) + _, err := client.CreateObjectPool(ctx, &gitalypb.CreateObjectPoolRequest{ + ObjectPool: pool.ToProto(), + Origin: repo, + }) + require.NoError(t, err) + + gittest.WriteCommit(t, cfg, repoPath, gittest.WithBranch("branch")) + + // Perform an initial fetch into the object pool with the given object that exists in the + // pool member's repository. + _, err = client.FetchIntoObjectPool(ctx, &gitalypb.FetchIntoObjectPoolRequest{ + ObjectPool: pool.ToProto(), + Origin: repo, + }) + require.NoError(t, err) + + // Now we delete the reference in the pool member and create a new reference that has the + // same prefix, but is stored in a subdirectory. This will create a D/F conflict. + gittest.Exec(t, cfg, "-C", repoPath, "update-ref", "-d", "refs/heads/branch") + gittest.WriteCommit(t, cfg, repoPath, gittest.WithBranch("branch/conflict")) + + gitVersion, err := gittest.NewCommandFactory(t, cfg).GitVersion(ctx) + require.NoError(t, err) + + // Due to a bug in old Git versions we may get an unexpected exit status. + expectedExitStatus := 254 + if !gitVersion.FlushesUpdaterefStatus() { + expectedExitStatus = 1 + } + + // Verify that we can still fetch into the object pool regardless of the D/F conflict. While + // it is not possible to store both references at the same time due to the conflict, we + // should know to delete the old conflicting reference and replace it with the new one. + _, err = client.FetchIntoObjectPool(ctx, &gitalypb.FetchIntoObjectPoolRequest{ + ObjectPool: pool.ToProto(), + Origin: repo, + }) + if featureflag.FetchIntoObjectPoolPruneRefs.IsEnabled(ctx) { + require.NoError(t, err) + + poolPath, err := config.NewLocator(cfg).GetRepoPath(gittest.RewrittenRepository(ctx, t, cfg, pool.ToProto().GetRepository())) + require.NoError(t, err) + + // Verify that the conflicting reference exists now. + gittest.Exec(t, cfg, "-C", poolPath, "rev-parse", "refs/remotes/origin/heads/branch/conflict") + } else { + // But right now it fails due to a bug. + testhelper.RequireGrpcError(t, helper.ErrInternalf( + "fetch into object pool: exit status %d, stderr: %q", + expectedExitStatus, + "error: cannot lock ref 'refs/remotes/origin/heads/branch/conflict': 'refs/remotes/origin/heads/branch' exists; cannot create 'refs/remotes/origin/heads/branch/conflict'\n", + ), err) + } +} diff --git a/internal/gitaly/service/repository/gc_test.go b/internal/gitaly/service/repository/gc_test.go index feaeab138..3dcdb3a02 100644 --- a/internal/gitaly/service/repository/gc_test.go +++ b/internal/gitaly/service/repository/gc_test.go @@ -536,7 +536,7 @@ func TestGarbageCollectDeltaIslands(t *testing.T) { ctx := testhelper.Context(t) cfg, repo, repoPath, client := setupRepositoryService(ctx, t) - gittest.TestDeltaIslands(t, cfg, repoPath, false, func() error { + gittest.TestDeltaIslands(t, cfg, repoPath, repoPath, false, func() error { //nolint:staticcheck _, err := client.GarbageCollect(ctx, &gitalypb.GarbageCollectRequest{Repository: repo}) return err diff --git a/internal/gitaly/service/repository/repack_test.go b/internal/gitaly/service/repository/repack_test.go index e89f25cb9..eb3d8b912 100644 --- a/internal/gitaly/service/repository/repack_test.go +++ b/internal/gitaly/service/repository/repack_test.go @@ -300,7 +300,7 @@ func TestRepackFullDeltaIslands(t *testing.T) { ctx := testhelper.Context(t) cfg, repo, repoPath, client := setupRepositoryService(ctx, t) - gittest.TestDeltaIslands(t, cfg, repoPath, false, func() error { + gittest.TestDeltaIslands(t, cfg, repoPath, repoPath, false, func() error { //nolint:staticcheck _, err := client.RepackFull(ctx, &gitalypb.RepackFullRequest{Repository: repo}) return err diff --git a/internal/metadata/featureflag/ff_fetch_into_object_pool_prune_refs.go b/internal/metadata/featureflag/ff_fetch_into_object_pool_prune_refs.go new file mode 100644 index 000000000..21d8eb3f0 --- /dev/null +++ b/internal/metadata/featureflag/ff_fetch_into_object_pool_prune_refs.go @@ -0,0 +1,11 @@ +package featureflag + +// FetchIntoObjectPoolPruneRefs enables pruning of references in object pools. This is required in +// order to fix cases where updating pools doesn't work anymore due to preexisting references +// conflicting with new references in the pool member. +var FetchIntoObjectPoolPruneRefs = NewFeatureFlag( + "fetch_into_object_pool_prune_refs", + "v15.3.0", + "https://gitlab.com/gitlab-org/gitaly/-/issues/4394", + false, +) diff --git a/internal/praefect/protoregistry/find_oid_test.go b/internal/praefect/protoregistry/find_oid_test.go index a6dbcf745..d4b01f25a 100644 --- a/internal/praefect/protoregistry/find_oid_test.go +++ b/internal/praefect/protoregistry/find_oid_test.go @@ -78,7 +78,6 @@ func TestProtoRegistryTargetRepo(t *testing.T) { pbMsg: &gitalypb.FetchIntoObjectPoolRequest{ Origin: testRepos[0], ObjectPool: &gitalypb.ObjectPool{Repository: testRepos[1]}, - Repack: false, }, expectRepo: testRepos[1], expectAdditionalRepo: testRepos[0], diff --git a/proto/go/gitalypb/objectpool.pb.go b/proto/go/gitalypb/objectpool.pb.go index 3099253e4..6f052357b 100644 --- a/proto/go/gitalypb/objectpool.pb.go +++ b/proto/go/gitalypb/objectpool.pb.go @@ -480,18 +480,16 @@ func (*DisconnectGitAlternatesResponse) Descriptor() ([]byte, []int) { return file_objectpool_proto_rawDescGZIP(), []int{9} } -// This comment is left unintentionally blank. +// FetchIntoObjectPoolRequest is a request for the FetchIntoObjectPool RPC. type FetchIntoObjectPoolRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // This comment is left unintentionally blank. + // Origin is the repository to fetch changes from. Origin *Repository `protobuf:"bytes,1,opt,name=origin,proto3" json:"origin,omitempty"` - // This comment is left unintentionally blank. + // ObjectPool is the repository to fetch changes into. ObjectPool *ObjectPool `protobuf:"bytes,2,opt,name=object_pool,json=objectPool,proto3" json:"object_pool,omitempty"` - // This comment is left unintentionally blank. - Repack bool `protobuf:"varint,3,opt,name=repack,proto3" json:"repack,omitempty"` } func (x *FetchIntoObjectPoolRequest) Reset() { @@ -540,14 +538,7 @@ func (x *FetchIntoObjectPoolRequest) GetObjectPool() *ObjectPool { return nil } -func (x *FetchIntoObjectPoolRequest) GetRepack() bool { - if x != nil { - return x.Repack - } - return false -} - -// This comment is left unintentionally blank. +// FetchIntoObjectPoolResponse is a response for the FetchIntoObjectPool RPC. type FetchIntoObjectPoolResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -735,7 +726,7 @@ var file_objectpool_proto_rawDesc = []byte{ 0x79, 0x42, 0x04, 0x98, 0xc6, 0x2c, 0x01, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x22, 0x21, 0x0a, 0x1f, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x47, 0x69, 0x74, 0x41, 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xa1, 0x01, 0x0a, 0x1a, 0x46, 0x65, 0x74, 0x63, 0x68, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x97, 0x01, 0x0a, 0x1a, 0x46, 0x65, 0x74, 0x63, 0x68, 0x49, 0x6e, 0x74, 0x6f, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x30, 0x0a, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x52, @@ -744,71 +735,70 @@ var file_objectpool_proto_rawDesc = []byte{ 0x74, 0x5f, 0x70, 0x6f, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x42, 0x04, 0x98, 0xc6, 0x2c, 0x01, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, - 0x6f, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x70, 0x61, 0x63, 0x6b, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x06, 0x72, 0x65, 0x70, 0x61, 0x63, 0x6b, 0x22, 0x1d, 0x0a, 0x1b, 0x46, 0x65, - 0x74, 0x63, 0x68, 0x49, 0x6e, 0x74, 0x6f, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, 0x6f, - 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x50, 0x0a, 0x14, 0x47, 0x65, 0x74, - 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x38, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x52, - 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x42, 0x04, 0x98, 0xc6, 0x2c, 0x01, 0x52, - 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x22, 0x4c, 0x0a, 0x15, 0x47, - 0x65, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x70, - 0x6f, 0x6f, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67, 0x69, 0x74, 0x61, - 0x6c, 0x79, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x0a, 0x6f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x32, 0xee, 0x05, 0x0a, 0x11, 0x4f, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, - 0x5d, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, - 0x6f, 0x6f, 0x6c, 0x12, 0x1f, 0x2e, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x06, 0xfa, 0x97, 0x28, 0x02, 0x08, 0x01, 0x12, 0x5d, - 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, - 0x6f, 0x6c, 0x12, 0x1f, 0x2e, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x06, 0xfa, 0x97, 0x28, 0x02, 0x08, 0x01, 0x12, 0x7b, 0x0a, - 0x1a, 0x4c, 0x69, 0x6e, 0x6b, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x54, - 0x6f, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x29, 0x2e, 0x67, 0x69, - 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x4c, 0x69, 0x6e, 0x6b, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, - 0x6f, 0x72, 0x79, 0x54, 0x6f, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e, - 0x4c, 0x69, 0x6e, 0x6b, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x54, 0x6f, - 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x06, 0xfa, 0x97, 0x28, 0x02, 0x08, 0x01, 0x12, 0x6c, 0x0a, 0x15, 0x52, 0x65, - 0x64, 0x75, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, - 0x6f, 0x72, 0x79, 0x12, 0x24, 0x2e, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x52, 0x65, 0x64, - 0x75, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, - 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x67, 0x69, 0x74, 0x61, - 0x6c, 0x79, 0x2e, 0x52, 0x65, 0x64, 0x75, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, - 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x06, 0xfa, 0x97, 0x28, 0x02, 0x08, 0x01, 0x12, 0x72, 0x0a, 0x17, 0x44, 0x69, 0x73, 0x63, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x47, 0x69, 0x74, 0x41, 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x74, 0x65, 0x73, 0x12, 0x26, 0x2e, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x44, 0x69, 0x73, - 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x47, 0x69, 0x74, 0x41, 0x6c, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x67, 0x69, - 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x47, - 0x69, 0x74, 0x41, 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x06, 0xfa, 0x97, 0x28, 0x02, 0x08, 0x01, 0x12, 0x66, 0x0a, 0x13, - 0x46, 0x65, 0x74, 0x63, 0x68, 0x49, 0x6e, 0x74, 0x6f, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, - 0x6f, 0x6f, 0x6c, 0x12, 0x22, 0x2e, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x46, 0x65, 0x74, - 0x63, 0x68, 0x49, 0x6e, 0x74, 0x6f, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, 0x6f, 0x6c, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, - 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x49, 0x6e, 0x74, 0x6f, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x6f, 0x6c, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x52, 0x06, 0x72, 0x65, 0x70, 0x61, 0x63, 0x6b, + 0x22, 0x1d, 0x0a, 0x1b, 0x46, 0x65, 0x74, 0x63, 0x68, 0x49, 0x6e, 0x74, 0x6f, 0x4f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x50, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, 0x6f, 0x6c, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, + 0x69, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67, 0x69, + 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x42, + 0x04, 0x98, 0xc6, 0x2c, 0x01, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, + 0x79, 0x22, 0x4c, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, + 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x0b, 0x6f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x70, 0x6f, 0x6f, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x12, 0x2e, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, + 0x6f, 0x6f, 0x6c, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x32, + 0xee, 0x05, 0x0a, 0x11, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5d, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x1f, 0x2e, 0x67, 0x69, 0x74, 0x61, + 0x6c, 0x79, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, + 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x67, 0x69, 0x74, + 0x61, 0x6c, 0x79, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x06, 0xfa, 0x97, - 0x28, 0x02, 0x08, 0x01, 0x12, 0x54, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x1c, 0x2e, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x47, - 0x65, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x47, 0x65, 0x74, + 0x28, 0x02, 0x08, 0x01, 0x12, 0x5d, 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x1f, 0x2e, 0x67, 0x69, 0x74, 0x61, 0x6c, + 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, + 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x67, 0x69, 0x74, 0x61, + 0x6c, 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, + 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x06, 0xfa, 0x97, 0x28, + 0x02, 0x08, 0x01, 0x12, 0x7b, 0x0a, 0x1a, 0x4c, 0x69, 0x6e, 0x6b, 0x52, 0x65, 0x70, 0x6f, 0x73, + 0x69, 0x74, 0x6f, 0x72, 0x79, 0x54, 0x6f, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, 0x6f, + 0x6c, 0x12, 0x29, 0x2e, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x4c, 0x69, 0x6e, 0x6b, 0x52, + 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x54, 0x6f, 0x4f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x67, + 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x4c, 0x69, 0x6e, 0x6b, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, + 0x74, 0x6f, 0x72, 0x79, 0x54, 0x6f, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, 0x6f, 0x6c, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x06, 0xfa, 0x97, 0x28, 0x02, 0x08, 0x01, + 0x12, 0x6c, 0x0a, 0x15, 0x52, 0x65, 0x64, 0x75, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x24, 0x2e, 0x67, 0x69, 0x74, 0x61, + 0x6c, 0x79, 0x2e, 0x52, 0x65, 0x64, 0x75, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, + 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x25, 0x2e, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x52, 0x65, 0x64, 0x75, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x06, 0xfa, 0x97, 0x28, 0x02, 0x08, 0x01, 0x12, 0x72, + 0x0a, 0x17, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x47, 0x69, 0x74, 0x41, + 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x65, 0x73, 0x12, 0x26, 0x2e, 0x67, 0x69, 0x74, 0x61, + 0x6c, 0x79, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x47, 0x69, 0x74, + 0x41, 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x27, 0x2e, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x47, 0x69, 0x74, 0x41, 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, + 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x06, 0xfa, 0x97, 0x28, 0x02, + 0x08, 0x01, 0x12, 0x66, 0x0a, 0x13, 0x46, 0x65, 0x74, 0x63, 0x68, 0x49, 0x6e, 0x74, 0x6f, 0x4f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x22, 0x2e, 0x67, 0x69, 0x74, 0x61, + 0x6c, 0x79, 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x49, 0x6e, 0x74, 0x6f, 0x4f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, + 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x49, 0x6e, 0x74, 0x6f, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x06, 0xfa, 0x97, 0x28, 0x02, 0x08, 0x02, 0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, - 0x74, 0x6c, 0x61, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x2d, - 0x6f, 0x72, 0x67, 0x2f, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2f, 0x76, 0x31, 0x35, 0x2f, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x70, 0x62, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x65, 0x22, 0x06, 0xfa, 0x97, 0x28, 0x02, 0x08, 0x01, 0x12, 0x54, 0x0a, 0x0d, 0x47, 0x65, + 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x1c, 0x2e, 0x67, 0x69, + 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, + 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x67, 0x69, 0x74, 0x61, + 0x6c, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x6f, 0x6f, 0x6c, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x06, 0xfa, 0x97, 0x28, 0x02, 0x08, 0x02, + 0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, + 0x69, 0x74, 0x6c, 0x61, 0x62, 0x2d, 0x6f, 0x72, 0x67, 0x2f, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, + 0x2f, 0x76, 0x31, 0x35, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x67, 0x69, + 0x74, 0x61, 0x6c, 0x79, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/proto/go/gitalypb/objectpool_grpc.pb.go b/proto/go/gitalypb/objectpool_grpc.pb.go index e4b1038c6..4acbe3aa1 100644 --- a/proto/go/gitalypb/objectpool_grpc.pb.go +++ b/proto/go/gitalypb/objectpool_grpc.pb.go @@ -32,7 +32,10 @@ type ObjectPoolServiceClient interface { ReduplicateRepository(ctx context.Context, in *ReduplicateRepositoryRequest, opts ...grpc.CallOption) (*ReduplicateRepositoryResponse, error) // This comment is left unintentionally blank. DisconnectGitAlternates(ctx context.Context, in *DisconnectGitAlternatesRequest, opts ...grpc.CallOption) (*DisconnectGitAlternatesResponse, error) - // This comment is left unintentionally blank. + // FetchIntoObjectPool fetches all references from a pool member into an object pool so that + // objects shared between this repository and other pool members can be deduplicated. This RPC + // will perform housekeeping tasks after the object pool has been updated to ensure that the pool + // is in an optimal state. FetchIntoObjectPool(ctx context.Context, in *FetchIntoObjectPoolRequest, opts ...grpc.CallOption) (*FetchIntoObjectPoolResponse, error) // This comment is left unintentionally blank. GetObjectPool(ctx context.Context, in *GetObjectPoolRequest, opts ...grpc.CallOption) (*GetObjectPoolResponse, error) @@ -123,7 +126,10 @@ type ObjectPoolServiceServer interface { ReduplicateRepository(context.Context, *ReduplicateRepositoryRequest) (*ReduplicateRepositoryResponse, error) // This comment is left unintentionally blank. DisconnectGitAlternates(context.Context, *DisconnectGitAlternatesRequest) (*DisconnectGitAlternatesResponse, error) - // This comment is left unintentionally blank. + // FetchIntoObjectPool fetches all references from a pool member into an object pool so that + // objects shared between this repository and other pool members can be deduplicated. This RPC + // will perform housekeeping tasks after the object pool has been updated to ensure that the pool + // is in an optimal state. FetchIntoObjectPool(context.Context, *FetchIntoObjectPoolRequest) (*FetchIntoObjectPoolResponse, error) // This comment is left unintentionally blank. GetObjectPool(context.Context, *GetObjectPoolRequest) (*GetObjectPoolResponse, error) diff --git a/proto/objectpool.proto b/proto/objectpool.proto index 4d9a36d23..9634f62c7 100644 --- a/proto/objectpool.proto +++ b/proto/objectpool.proto @@ -48,7 +48,10 @@ service ObjectPoolService { }; } - // This comment is left unintentionally blank. + // FetchIntoObjectPool fetches all references from a pool member into an object pool so that + // objects shared between this repository and other pool members can be deduplicated. This RPC + // will perform housekeeping tasks after the object pool has been updated to ensure that the pool + // is in an optimal state. rpc FetchIntoObjectPool(FetchIntoObjectPoolRequest) returns (FetchIntoObjectPoolResponse) { option (op_type) = { op: MUTATOR @@ -120,17 +123,20 @@ message DisconnectGitAlternatesRequest { message DisconnectGitAlternatesResponse { } -// This comment is left unintentionally blank. +// FetchIntoObjectPoolRequest is a request for the FetchIntoObjectPool RPC. message FetchIntoObjectPoolRequest { - // This comment is left unintentionally blank. + // Origin is the repository to fetch changes from. Repository origin = 1 [(additional_repository)=true]; - // This comment is left unintentionally blank. + // ObjectPool is the repository to fetch changes into. ObjectPool object_pool = 2 [(target_repository)=true]; - // This comment is left unintentionally blank. - bool repack = 3; + + // Repack had the intent to control whether FetchIntoObjectPool would perform housekeeping tasks + // in the pool repository or not. This flag wasn't ever honored though and is thus doing nothing. + reserved 3; + reserved "repack"; } -// This comment is left unintentionally blank. +// FetchIntoObjectPoolResponse is a response for the FetchIntoObjectPool RPC. message FetchIntoObjectPoolResponse { } diff --git a/ruby/proto/gitaly/objectpool_pb.rb b/ruby/proto/gitaly/objectpool_pb.rb index ebbe7fa8f..f022a19d6 100644 --- a/ruby/proto/gitaly/objectpool_pb.rb +++ b/ruby/proto/gitaly/objectpool_pb.rb @@ -37,7 +37,6 @@ Google::Protobuf::DescriptorPool.generated_pool.build do add_message "gitaly.FetchIntoObjectPoolRequest" do optional :origin, :message, 1, "gitaly.Repository" optional :object_pool, :message, 2, "gitaly.ObjectPool" - optional :repack, :bool, 3 end add_message "gitaly.FetchIntoObjectPoolResponse" do end diff --git a/ruby/proto/gitaly/objectpool_services_pb.rb b/ruby/proto/gitaly/objectpool_services_pb.rb index d0529fa3d..fe12b6ab2 100644 --- a/ruby/proto/gitaly/objectpool_services_pb.rb +++ b/ruby/proto/gitaly/objectpool_services_pb.rb @@ -28,7 +28,10 @@ module Gitaly rpc :ReduplicateRepository, ::Gitaly::ReduplicateRepositoryRequest, ::Gitaly::ReduplicateRepositoryResponse # This comment is left unintentionally blank. rpc :DisconnectGitAlternates, ::Gitaly::DisconnectGitAlternatesRequest, ::Gitaly::DisconnectGitAlternatesResponse - # This comment is left unintentionally blank. + # FetchIntoObjectPool fetches all references from a pool member into an object pool so that + # objects shared between this repository and other pool members can be deduplicated. This RPC + # will perform housekeeping tasks after the object pool has been updated to ensure that the pool + # is in an optimal state. rpc :FetchIntoObjectPool, ::Gitaly::FetchIntoObjectPoolRequest, ::Gitaly::FetchIntoObjectPoolResponse # This comment is left unintentionally blank. rpc :GetObjectPool, ::Gitaly::GetObjectPoolRequest, ::Gitaly::GetObjectPoolResponse |