Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/gitaly/service/repository/create_repository_from_bundle_test.go302
1 files changed, 146 insertions, 156 deletions
diff --git a/internal/gitaly/service/repository/create_repository_from_bundle_test.go b/internal/gitaly/service/repository/create_repository_from_bundle_test.go
index 060534544..4f6ce3d66 100644
--- a/internal/gitaly/service/repository/create_repository_from_bundle_test.go
+++ b/internal/gitaly/service/repository/create_repository_from_bundle_test.go
@@ -19,87 +19,158 @@ import (
"gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/storage"
"gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/transaction"
"gitlab.com/gitlab-org/gitaly/v16/internal/grpc/metadata"
- "gitlab.com/gitlab-org/gitaly/v16/internal/helper/text"
"gitlab.com/gitlab-org/gitaly/v16/internal/structerr"
- "gitlab.com/gitlab-org/gitaly/v16/internal/tempdir"
"gitlab.com/gitlab-org/gitaly/v16/internal/testhelper"
"gitlab.com/gitlab-org/gitaly/v16/internal/testhelper/testserver"
"gitlab.com/gitlab-org/gitaly/v16/internal/transaction/txinfo"
"gitlab.com/gitlab-org/gitaly/v16/internal/transaction/voting"
"gitlab.com/gitlab-org/gitaly/v16/proto/go/gitalypb"
"gitlab.com/gitlab-org/gitaly/v16/streamio"
- "google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
-func TestCreateRepositoryFromBundle_successful(t *testing.T) {
+func TestCreateRepositoryFromBundle(t *testing.T) {
t.Parallel()
- ctx := testhelper.Context(t)
-
- cfg, repo, repoPath, client := setupRepositoryService(t, ctx)
-
- locator := config.NewLocator(cfg)
- tmpdir, err := tempdir.New(ctx, repo.GetStorageName(), locator)
- require.NoError(t, err)
- bundlePath := filepath.Join(tmpdir.Path(), "original.bundle")
-
- gittest.Exec(t, cfg, "-C", repoPath, "update-ref", "refs/custom-refs/ref1", "HEAD")
- // A user may use a default branch other than "main" or "master"
- const wantDefaultBranch = "refs/heads/markdown"
- gittest.Exec(t, cfg, "-C", repoPath, "symbolic-ref", "HEAD", wantDefaultBranch)
-
- gittest.Exec(t, cfg, "-C", repoPath, "bundle", "create", bundlePath, "--all")
- defer func() { require.NoError(t, os.RemoveAll(bundlePath)) }()
-
- stream, err := client.CreateRepositoryFromBundle(ctx)
- require.NoError(t, err)
+ ctx := testhelper.Context(t)
+ cfg, repoClient := setupRepositoryServiceWithoutRepo(t)
- importedRepoProto := &gitalypb.Repository{
- StorageName: repo.GetStorageName(),
- RelativePath: "a-repo-from-bundle",
+ type setupData struct {
+ repoProto *gitalypb.Repository
+ bundleData []byte
+ expectedRefs []git.Reference
+ expectedErr error
}
- request := &gitalypb.CreateRepositoryFromBundleRequest{Repository: importedRepoProto}
- writer := streamio.NewWriter(func(p []byte) error {
- request.Data = p
-
- if err := stream.Send(request); err != nil {
- return err
- }
-
- request = &gitalypb.CreateRepositoryFromBundleRequest{}
-
- return nil
- })
-
- file, err := os.Open(bundlePath)
- require.NoError(t, err)
- defer file.Close()
-
- _, err = io.Copy(writer, file)
- require.NoError(t, err)
-
- _, err = stream.CloseAndRecv()
- require.NoError(t, err)
-
- importedRepo := localrepo.NewTestRepo(t, cfg, importedRepoProto)
- importedRepoPath, err := locator.GetRepoPath(gittest.RewrittenRepository(t, ctx, cfg, importedRepoProto), storage.WithRepositoryVerificationSkipped())
- require.NoError(t, err)
- defer func() { require.NoError(t, os.RemoveAll(importedRepoPath)) }()
-
- gittest.Exec(t, cfg, "-C", importedRepoPath, "fsck")
-
- _, err = os.Lstat(filepath.Join(importedRepoPath, "hooks"))
- require.True(t, os.IsNotExist(err), "hooks directory should not have been created")
-
- commit, err := importedRepo.ReadCommit(ctx, "refs/custom-refs/ref1")
- require.NoError(t, err)
- require.NotNil(t, commit)
-
- gotDefaultBranch, err := importedRepo.HeadReference(ctx)
- require.NoError(t, err)
- require.Equal(t, wantDefaultBranch, gotDefaultBranch.String())
+ for _, tc := range []struct {
+ desc string
+ setup func(t *testing.T) setupData
+ }{
+ {
+ desc: "create repository from bundle",
+ setup: func(t *testing.T) setupData {
+ _, bundleRepoPath := gittest.CreateRepository(t, ctx, cfg)
+
+ // Create objects and references that will be used to validate the repository.
+ oldRef := "refs/heads/old"
+ newRef := "refs/heads/new"
+ commitID1 := gittest.WriteCommit(t, cfg, bundleRepoPath, gittest.WithReference(oldRef))
+ commitID2 := gittest.WriteCommit(t, cfg, bundleRepoPath, gittest.WithReference(newRef))
+
+ // Change HEAD to validate the created repository will use the same reference.
+ gittest.Exec(t, cfg, "-C", bundleRepoPath, "symbolic-ref", "HEAD", newRef)
+
+ // Generate a Git bundle that will be used to create a repository from.
+ bundleData := gittest.Exec(t, cfg, "-C", bundleRepoPath, "bundle", "create", "-", "--all")
+
+ return setupData{
+ repoProto: &gitalypb.Repository{
+ StorageName: cfg.Storages[0].Name,
+ RelativePath: gittest.NewRepositoryName(t),
+ },
+ bundleData: bundleData,
+ expectedRefs: []git.Reference{
+ git.NewSymbolicReference("HEAD", git.ReferenceName(newRef)),
+ git.NewReference(git.ReferenceName(oldRef), commitID1),
+ git.NewReference(git.ReferenceName(newRef), commitID2),
+ },
+ }
+ },
+ },
+ {
+ desc: "invalid bundle",
+ setup: func(t *testing.T) setupData {
+ return setupData{
+ // If an invalid Git bundle is transmitted, the RPC returns an error.
+ repoProto: &gitalypb.Repository{
+ StorageName: cfg.Storages[0].Name,
+ RelativePath: gittest.NewRepositoryName(t),
+ },
+ bundleData: []byte("not-a-bundle"),
+ expectedErr: structerr.NewInternal("fatal: invalid gitfile format:"),
+ }
+ },
+ },
+ {
+ desc: "invalid argument",
+ setup: func(t *testing.T) setupData {
+ // If the repository is not specified in the RPC request, an error is returned.
+ return setupData{
+ repoProto: nil,
+ expectedErr: structerr.NewInvalidArgument("%w", storage.ErrRepositoryNotSet),
+ }
+ },
+ },
+ {
+ desc: "repo already exists",
+ setup: func(t *testing.T) setupData {
+ // If the specified repository already exists a new one can not be created.
+ repoProto, _ := gittest.CreateRepository(t, ctx, cfg)
+
+ return setupData{
+ repoProto: repoProto,
+ expectedErr: testhelper.GitalyOrPraefect(
+ structerr.NewAlreadyExists("creating repository: repository exists already"),
+ structerr.NewAlreadyExists("route repository creation: reserve repository id: repository already exists"),
+ ),
+ }
+ },
+ },
+ } {
+ tc := tc
+ t.Run(tc.desc, func(t *testing.T) {
+ t.Parallel()
+
+ setup := tc.setup(t)
+
+ stream, err := repoClient.CreateRepositoryFromBundle(ctx)
+ require.NoError(t, err)
+
+ writer := streamio.NewWriter(func(p []byte) error {
+ if err := stream.Send(&gitalypb.CreateRepositoryFromBundleRequest{Data: p}); err != nil {
+ return err
+ }
+ return nil
+ })
+
+ // Some test cases will not transmit any bundle data. To ensure the first request with
+ // repository information is received, explicitly send the first request.
+ require.NoError(t, stream.Send(&gitalypb.CreateRepositoryFromBundleRequest{Repository: setup.repoProto}))
+
+ // If the test case is transmitting Git bundle data, write the content to the stream.
+ _, err = writer.Write(setup.bundleData)
+ require.NoError(t, err)
+
+ _, err = stream.CloseAndRecv()
+
+ // It is possible for the returned error to contain metadata embedded in its message
+ // that makes it difficult to assert equivalency. For this reason, the status code is
+ // verified, but the error message only asserts it contains a specified substring.
+ if setup.expectedErr != nil {
+ testhelper.RequireGrpcCode(t, err, status.Code(setup.expectedErr))
+ require.ErrorContains(t, err, setup.expectedErr.Error())
+ return
+ }
+ require.NoError(t, err)
+
+ repo := localrepo.NewTestRepo(t, cfg, setup.repoProto)
+ repoPath, err := repo.Path()
+ require.NoError(t, err)
+
+ // Verify connectivity and validity of the repository objects.
+ gittest.Exec(t, cfg, "-C", repoPath, "fsck")
+
+ refs, err := repo.GetReferences(ctx)
+ require.NoError(t, err)
+
+ headRef, err := repo.HeadReference(ctx)
+ require.NoError(t, err)
+ head := git.NewSymbolicReference("HEAD", headRef)
+
+ // Verify repository contains references from the bundle.
+ require.ElementsMatch(t, setup.expectedRefs, append(refs, head))
+ })
+ }
}
func TestCreateRepositoryFromBundle_transactional(t *testing.T) {
@@ -107,20 +178,21 @@ func TestCreateRepositoryFromBundle_transactional(t *testing.T) {
ctx := testhelper.Context(t)
txManager := transaction.NewTrackingManager()
+ cfg, client := setupRepositoryServiceWithoutRepo(t, testserver.WithTransactionManager(txManager))
- cfg, repoProto, repoPath, client := setupRepositoryService(t, ctx, testserver.WithTransactionManager(txManager))
+ repoProto, repoPath := gittest.CreateRepository(t, ctx, cfg)
// Reset the votes casted while creating the test repository.
txManager.Reset()
- masterOID := text.ChompBytes(gittest.Exec(t, cfg, "-C", repoPath, "rev-parse", "refs/heads/master"))
- featureOID := text.ChompBytes(gittest.Exec(t, cfg, "-C", repoPath, "rev-parse", "refs/heads/feature"))
+ masterOID := gittest.WriteCommit(t, cfg, repoPath, gittest.WithBranch("master"))
+ featureOID := gittest.WriteCommit(t, cfg, repoPath, gittest.WithBranch("feature"))
// keep-around refs are not cloned in the initial step, but are added via the second call to
// git-fetch(1). We thus create some of them to exercise their behaviour with regards to
// transactional voting.
for _, keepAroundRef := range []string{"refs/keep-around/1", "refs/keep-around/2"} {
- gittest.Exec(t, cfg, "-C", repoPath, "update-ref", keepAroundRef, masterOID)
+ gittest.Exec(t, cfg, "-C", repoPath, "update-ref", keepAroundRef, masterOID.String())
}
ctx, err := txinfo.InjectTransaction(ctx, 1, "primary", true)
@@ -141,7 +213,6 @@ func TestCreateRepositoryFromBundle_transactional(t *testing.T) {
bundle := gittest.Exec(t, cfg, "-C", repoPath, "bundle", "create", "-",
"refs/heads/master", "refs/heads/feature", "refs/keep-around/1", "refs/keep-around/2")
- require.Greater(t, len(bundle), 100*1024)
_, err = io.Copy(streamio.NewWriter(func(p []byte) error {
require.NoError(t, stream.Send(&gitalypb.CreateRepositoryFromBundleRequest{
@@ -164,10 +235,10 @@ func TestCreateRepositoryFromBundle_transactional(t *testing.T) {
require.NoError(t, err)
refsVote := voting.VoteFromData([]byte(strings.Join([]string{
- fmt.Sprintf("%s %s refs/keep-around/2", git.ObjectHashSHA1.ZeroOID, masterOID),
- fmt.Sprintf("%s %s refs/keep-around/1", git.ObjectHashSHA1.ZeroOID, masterOID),
- fmt.Sprintf("%s %s refs/heads/feature", git.ObjectHashSHA1.ZeroOID, featureOID),
- fmt.Sprintf("%s %s refs/heads/master", git.ObjectHashSHA1.ZeroOID, masterOID),
+ fmt.Sprintf("%s %s refs/keep-around/2", gittest.DefaultObjectHash.ZeroOID, masterOID),
+ fmt.Sprintf("%s %s refs/keep-around/1", gittest.DefaultObjectHash.ZeroOID, masterOID),
+ fmt.Sprintf("%s %s refs/heads/feature", gittest.DefaultObjectHash.ZeroOID, featureOID),
+ fmt.Sprintf("%s %s refs/heads/master", gittest.DefaultObjectHash.ZeroOID, masterOID),
}, "\n") + "\n"))
// Compute the second vote hash to assert that we really hash exactly the files that we
@@ -203,84 +274,3 @@ func TestCreateRepositoryFromBundle_transactional(t *testing.T) {
createVote(filesVote.String(), voting.Committed),
}, txManager.Votes())
}
-
-func TestCreateRepositoryFromBundle_invalidBundle(t *testing.T) {
- t.Parallel()
-
- ctx := testhelper.Context(t)
- cfg, client := setupRepositoryServiceWithoutRepo(t)
-
- stream, err := client.CreateRepositoryFromBundle(ctx)
- require.NoError(t, err)
-
- importedRepo := &gitalypb.Repository{
- StorageName: cfg.Storages[0].Name,
- RelativePath: "a-repo-from-bundle",
- }
- importedRepoPath := filepath.Join(cfg.Storages[0].Path, importedRepo.GetRelativePath())
- defer func() { require.NoError(t, os.RemoveAll(importedRepoPath)) }()
-
- request := &gitalypb.CreateRepositoryFromBundleRequest{Repository: importedRepo}
- writer := streamio.NewWriter(func(p []byte) error {
- request.Data = p
-
- if err := stream.Send(request); err != nil {
- return err
- }
-
- request = &gitalypb.CreateRepositoryFromBundleRequest{}
-
- return nil
- })
-
- _, err = io.Copy(writer, bytes.NewBufferString("not-a-bundle"))
- require.NoError(t, err)
-
- _, err = stream.CloseAndRecv()
- require.Error(t, err)
- require.Contains(t, err.Error(), "invalid gitfile format")
-}
-
-func TestCreateRepositoryFromBundle_invalidArgument(t *testing.T) {
- t.Parallel()
-
- ctx := testhelper.Context(t)
- _, client := setupRepositoryServiceWithoutRepo(t)
-
- stream, err := client.CreateRepositoryFromBundle(ctx)
- require.NoError(t, err)
-
- require.NoError(t, stream.Send(&gitalypb.CreateRepositoryFromBundleRequest{}))
-
- _, err = stream.CloseAndRecv()
- testhelper.RequireGrpcError(t, structerr.NewInvalidArgument("%w", storage.ErrRepositoryNotSet), err)
-}
-
-func TestCreateRepositoryFromBundle_existingRepository(t *testing.T) {
- t.Parallel()
-
- ctx := testhelper.Context(t)
- cfg, client := setupRepositoryServiceWithoutRepo(t)
-
- // The above test creates the second repository on the server. As this test can run with Praefect in front of it,
- // we'll use the next replica path Praefect will assign in order to ensure this repository creation conflicts even
- // with Praefect in front of it.
- repo, _ := gittest.CreateRepository(t, ctx, cfg, gittest.CreateRepositoryConfig{
- RelativePath: storage.DeriveReplicaPath(1),
- Seed: gittest.SeedGitLabTest,
- })
-
- stream, err := client.CreateRepositoryFromBundle(ctx)
- require.NoError(t, err)
-
- require.NoError(t, stream.Send(&gitalypb.CreateRepositoryFromBundleRequest{
- Repository: repo,
- }))
-
- _, err = stream.CloseAndRecv()
- if testhelper.IsPraefectEnabled() {
- testhelper.ProtoEqual(t, status.Error(codes.AlreadyExists, "route repository creation: reserve repository id: repository already exists"), err)
- } else {
- testhelper.ProtoEqual(t, status.Error(codes.AlreadyExists, "creating repository: repository exists already"), err)
- }
-}