diff options
author | Sami Hiltunen <shiltunen@gitlab.com> | 2020-10-14 13:34:08 +0300 |
---|---|---|
committer | Sami Hiltunen <shiltunen@gitlab.com> | 2020-10-14 13:34:08 +0300 |
commit | 34674109637c211a1a9d153de83ac58ce51c7dec (patch) | |
tree | e32f0797f62a865c93d3761a5a842a6fbf6f6a16 | |
parent | 9866ebbbb431fd0e767514a345ce2bd0015c8c4b (diff) | |
parent | cf68b09f14c64e0ca2b7b91366d326deff09b1bb (diff) |
Merge branch 'pks-repository-read-object' into 'master'
git: Rename CatFile to ReadObject
See merge request gitlab-org/gitaly!2651
28 files changed, 1380 insertions, 232 deletions
diff --git a/changelogs/unreleased/pks-reftx-dup-votes.yml b/changelogs/unreleased/pks-reftx-dup-votes.yml new file mode 100644 index 000000000..c4cffd264 --- /dev/null +++ b/changelogs/unreleased/pks-reftx-dup-votes.yml @@ -0,0 +1,5 @@ +--- +title: 'transactions: Only vote when reftx hook is in prepared state' +merge_request: 2571 +author: +type: changed diff --git a/changelogs/unreleased/po-remote-repo-resolve-refish.yml b/changelogs/unreleased/po-remote-repo-resolve-refish.yml new file mode 100644 index 000000000..003ec9ebd --- /dev/null +++ b/changelogs/unreleased/po-remote-repo-resolve-refish.yml @@ -0,0 +1,5 @@ +--- +title: Remote repository abstraction for resolving refish +merge_request: 2629 +author: +type: other diff --git a/cmd/gitaly-hooks/hooks.go b/cmd/gitaly-hooks/hooks.go index f4b849dde..6b9173a14 100644 --- a/cmd/gitaly-hooks/hooks.go +++ b/cmd/gitaly-hooks/hooks.go @@ -104,7 +104,7 @@ func main() { case "update": args := fixedArgs[2:] if len(args) != 3 { - logger.Fatalf("hook %q is missing required arguments", subCmd) + logger.Fatalf("hook %q expects exactly three arguments", subCmd) } ref, oldValue, newValue := args[0], args[1], args[2] @@ -194,6 +194,22 @@ func main() { os.Exit(0) } + if len(fixedArgs) != 3 { + logger.Fatalf("hook %q is missing required arguments", subCmd) + } + + var state gitalypb.ReferenceTransactionHookRequest_State + switch fixedArgs[2] { + case "prepared": + state = gitalypb.ReferenceTransactionHookRequest_PREPARED + case "committed": + state = gitalypb.ReferenceTransactionHookRequest_COMMITTED + case "aborted": + state = gitalypb.ReferenceTransactionHookRequest_ABORTED + default: + logger.Fatalf("hook %q has invalid state %s", subCmd, fixedArgs[2]) + } + referenceTransactionHookStream, err := hookClient.ReferenceTransactionHook(ctx) if err != nil { logger.Fatalf("error when getting referenceTransactionHookStream client for %q: %v", subCmd, err) @@ -211,6 +227,7 @@ func main() { if err := referenceTransactionHookStream.Send(&gitalypb.ReferenceTransactionHookRequest{ Repository: repository, EnvironmentVariables: environment, + State: state, }); err != nil { logger.Fatalf("error when sending request for %q: %v", subCmd, err) } diff --git a/internal/git/remote.go b/internal/git/remote.go new file mode 100644 index 000000000..2aba6ff49 --- /dev/null +++ b/internal/git/remote.go @@ -0,0 +1,58 @@ +package git + +import ( + "context" + "fmt" + + "gitlab.com/gitlab-org/gitaly/client" + "gitlab.com/gitlab-org/gitaly/internal/helper" + "gitlab.com/gitlab-org/gitaly/internal/storage" + "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" +) + +// remoteRepository represents a Git repository on a different Gitaly storage +type remoteRepository struct { + UnimplementedRepo + repo *gitalypb.Repository + server storage.ServerInfo + pool *client.Pool +} + +// NewRepository creates a new remote Repository from its protobuf representation. +func NewRemoteRepository(ctx context.Context, repo *gitalypb.Repository, pool *client.Pool) (Repository, error) { + server, err := helper.ExtractGitalyServer(ctx, repo.GetStorageName()) + if err != nil { + return nil, fmt.Errorf("remote repository: %w", err) + } + + return remoteRepository{ + repo: repo, + server: server, + pool: pool, + }, nil +} + +// ResolveRefish will dial to the remote repository and attempt to resolve the +// refish string via the gRPC interface +func (rr remoteRepository) ResolveRefish(ctx context.Context, ref string) (string, error) { + cc, err := rr.pool.Dial(ctx, rr.server.Address, rr.server.Token) + if err != nil { + return "", err + } + + cli := gitalypb.NewCommitServiceClient(cc) + resp, err := cli.FindCommit(ctx, &gitalypb.FindCommitRequest{ + Repository: rr.repo, + Revision: []byte(ref), + }) + if err != nil { + return "", err + } + + oid := resp.GetCommit().GetId() + if oid == "" { + return "", ErrReferenceNotFound + } + + return oid, nil +} diff --git a/internal/git/repository.go b/internal/git/repository.go index c4a71c752..e8c5d743d 100644 --- a/internal/git/repository.go +++ b/internal/git/repository.go @@ -74,6 +74,52 @@ type Repository interface { ReadObject(ctx context.Context, oid string) ([]byte, error) } +// ErrUnimplemented indicates the repository abstraction does not yet implement +// a specific behavior +var ErrUnimplemented = errors.New("behavior not implemented yet") + +// UnimplementedRepo satisfies the Repository interface to reduce friction in +// writing new Repository implementations +type UnimplementedRepo struct{} + +func (UnimplementedRepo) ResolveRefish(ctx context.Context, ref string) (string, error) { + return "", ErrUnimplemented +} + +func (UnimplementedRepo) ContainsRef(ctx context.Context, ref string) (bool, error) { + return false, ErrUnimplemented +} + +func (UnimplementedRepo) GetReference(ctx context.Context, ref string) (Reference, error) { + return Reference{}, ErrUnimplemented +} + +func (UnimplementedRepo) GetReferences(ctx context.Context, pattern string) ([]Reference, error) { + return nil, ErrUnimplemented +} + +func (UnimplementedRepo) GetBranch(ctx context.Context, branch string) (Reference, error) { + return Reference{}, ErrUnimplemented +} + +func (UnimplementedRepo) GetBranches(ctx context.Context) ([]Reference, error) { + return nil, ErrUnimplemented +} + +func (UnimplementedRepo) UpdateRef(ctx context.Context, reference, newrev, oldrev string) error { + return ErrUnimplemented +} + +func (UnimplementedRepo) WriteBlob(context.Context, string, io.Reader) (string, error) { + return "", ErrUnimplemented +} + +func (UnimplementedRepo) CatFile(context.Context, string) ([]byte, error) { + return nil, ErrUnimplemented +} + +var _ Repository = UnimplementedRepo{} // compile time assertion + // localRepository represents a local Git repository. type localRepository struct { repo repository.GitRepo diff --git a/internal/git/repository_test.go b/internal/git/repository_test.go index 685da5a92..78ee145ff 100644 --- a/internal/git/repository_test.go +++ b/internal/git/repository_test.go @@ -1,7 +1,8 @@ -package git +package git_test import ( "errors" + "fmt" "io" "io/ioutil" "os" @@ -11,7 +12,12 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gitlab.com/gitlab-org/gitaly/client" + "gitlab.com/gitlab-org/gitaly/internal/git" + "gitlab.com/gitlab-org/gitaly/internal/gitaly/config" + "gitlab.com/gitlab-org/gitaly/internal/helper" "gitlab.com/gitlab-org/gitaly/internal/testhelper" + "gitlab.com/gitlab-org/gitaly/internal/testhelper/testserver" ) const ( @@ -19,12 +25,10 @@ const ( NonexistentID = "ba4f184e126b751d1bffad5897f263108befc780" ) -func TestLocalRepository_ResolveRefish(t *testing.T) { +func TestRepository_ResolveRefish(t *testing.T) { ctx, cancel := testhelper.Context() defer cancel() - repo := NewRepository(testhelper.TestRepository()) - testcases := []struct { desc string refish string @@ -60,18 +64,38 @@ func TestLocalRepository_ResolveRefish(t *testing.T) { }, } - for _, tc := range testcases { - t.Run(tc.desc, func(t *testing.T) { - oid, err := repo.ResolveRefish(ctx, tc.refish) + _, serverSocketPath, cleanup := testserver.RunInternalGitalyServer(t, config.Config.Storages, config.Config.Auth.Token) + defer cleanup() - if tc.expected == "" { - require.Error(t, err) - require.Equal(t, err, ErrReferenceNotFound) - return - } + for _, repo := range []git.Repository{ + git.NewRepository(testhelper.TestRepository()), + func() git.Repository { + ctx, err := helper.InjectGitalyServers(ctx, "default", serverSocketPath, config.Config.Auth.Token) + require.NoError(t, err) + r, err := git.NewRemoteRepository( + helper.OutgoingToIncoming(ctx), + testhelper.TestRepository(), + client.NewPool(), + ) require.NoError(t, err) - require.Equal(t, tc.expected, oid) + return r + }(), + } { + t.Run(fmt.Sprintf("%T", repo), func(t *testing.T) { + for _, tc := range testcases { + t.Run(tc.desc, func(t *testing.T) { + oid, err := repo.ResolveRefish(ctx, tc.refish) + + if tc.expected == "" { + require.Equal(t, err, git.ErrReferenceNotFound) + return + } + + require.NoError(t, err) + require.Equal(t, tc.expected, oid) + }) + } }) } } @@ -80,7 +104,7 @@ func TestLocalRepository_ContainsRef(t *testing.T) { ctx, cancel := testhelper.Context() defer cancel() - repo := NewRepository(testhelper.TestRepository()) + repo := git.NewRepository(testhelper.TestRepository()) testcases := []struct { desc string @@ -117,32 +141,32 @@ func TestLocalRepository_GetReference(t *testing.T) { ctx, cancel := testhelper.Context() defer cancel() - repo := NewRepository(testhelper.TestRepository()) + repo := git.NewRepository(testhelper.TestRepository()) testcases := []struct { desc string ref string - expected Reference + expected git.Reference }{ { desc: "fully qualified master branch", ref: "refs/heads/master", - expected: NewReference("refs/heads/master", MasterID), + expected: git.NewReference("refs/heads/master", MasterID), }, { desc: "unqualified master branch fails", ref: "master", - expected: Reference{}, + expected: git.Reference{}, }, { desc: "nonexistent branch", ref: "refs/heads/nonexistent", - expected: Reference{}, + expected: git.Reference{}, }, { desc: "nonexistent branch", ref: "nonexistent", - expected: Reference{}, + expected: git.Reference{}, }, } @@ -150,7 +174,7 @@ func TestLocalRepository_GetReference(t *testing.T) { t.Run(tc.desc, func(t *testing.T) { ref, err := repo.GetReference(ctx, tc.ref) if tc.expected.Name == "" { - require.True(t, errors.Is(err, ErrReferenceNotFound)) + require.True(t, errors.Is(err, git.ErrReferenceNotFound)) } else { require.NoError(t, err) require.Equal(t, tc.expected, ref) @@ -163,32 +187,32 @@ func TestLocalRepository_GetBranch(t *testing.T) { ctx, cancel := testhelper.Context() defer cancel() - repo := NewRepository(testhelper.TestRepository()) + repo := git.NewRepository(testhelper.TestRepository()) testcases := []struct { desc string ref string - expected Reference + expected git.Reference }{ { desc: "fully qualified master branch", ref: "refs/heads/master", - expected: NewReference("refs/heads/master", MasterID), + expected: git.NewReference("refs/heads/master", MasterID), }, { desc: "half-qualified master branch", ref: "heads/master", - expected: NewReference("refs/heads/master", MasterID), + expected: git.NewReference("refs/heads/master", MasterID), }, { desc: "fully qualified master branch", ref: "master", - expected: NewReference("refs/heads/master", MasterID), + expected: git.NewReference("refs/heads/master", MasterID), }, { desc: "nonexistent branch", ref: "nonexistent", - expected: Reference{}, + expected: git.Reference{}, }, } @@ -196,7 +220,7 @@ func TestLocalRepository_GetBranch(t *testing.T) { t.Run(tc.desc, func(t *testing.T) { ref, err := repo.GetBranch(ctx, tc.ref) if tc.expected.Name == "" { - require.True(t, errors.Is(err, ErrReferenceNotFound)) + require.True(t, errors.Is(err, git.ErrReferenceNotFound)) } else { require.NoError(t, err) require.Equal(t, tc.expected, ref) @@ -209,40 +233,40 @@ func TestLocalRepository_GetReferences(t *testing.T) { ctx, cancel := testhelper.Context() defer cancel() - repo := NewRepository(testhelper.TestRepository()) + repo := git.NewRepository(testhelper.TestRepository()) testcases := []struct { desc string pattern string - match func(t *testing.T, refs []Reference) + match func(t *testing.T, refs []git.Reference) }{ { desc: "master branch", pattern: "refs/heads/master", - match: func(t *testing.T, refs []Reference) { - require.Equal(t, []Reference{ - NewReference("refs/heads/master", MasterID), + match: func(t *testing.T, refs []git.Reference) { + require.Equal(t, []git.Reference{ + git.NewReference("refs/heads/master", MasterID), }, refs) }, }, { desc: "all references", pattern: "", - match: func(t *testing.T, refs []Reference) { + match: func(t *testing.T, refs []git.Reference) { require.Len(t, refs, 94) }, }, { desc: "branches", pattern: "refs/heads/", - match: func(t *testing.T, refs []Reference) { + match: func(t *testing.T, refs []git.Reference) { require.Len(t, refs, 91) }, }, { desc: "branches", pattern: "refs/heads/nonexistent", - match: func(t *testing.T, refs []Reference) { + match: func(t *testing.T, refs []git.Reference) { require.Empty(t, refs) }, }, @@ -275,7 +299,7 @@ crlf binary lf text `), os.ModePerm)) - repo := NewRepository(pbRepo) + repo := git.NewRepository(pbRepo) for _, tc := range []struct { desc string @@ -288,7 +312,7 @@ lf text { desc: "error reading", input: ReaderFunc(func([]byte) (int, error) { return 0, assert.AnError }), - error: errorWithStderr(assert.AnError, nil), + error: fmt.Errorf("%w, stderr: %q", assert.AnError, []byte{}), }, { desc: "successful empty blob", @@ -336,7 +360,7 @@ func TestLocalRepository_ReadObject(t *testing.T) { ctx, cancel := testhelper.Context() defer cancel() - repo := NewRepository(testhelper.TestRepository()) + repo := git.NewRepository(testhelper.TestRepository()) for _, tc := range []struct { desc string @@ -346,8 +370,8 @@ func TestLocalRepository_ReadObject(t *testing.T) { }{ { desc: "invalid object", - oid: NullSHA, - error: InvalidObjectError(NullSHA), + oid: git.NullSHA, + error: git.InvalidObjectError(git.NullSHA), }, { desc: "valid object", @@ -368,7 +392,7 @@ func TestLocalRepository_GetBranches(t *testing.T) { ctx, cancel := testhelper.Context() defer cancel() - repo := NewRepository(testhelper.TestRepository()) + repo := git.NewRepository(testhelper.TestRepository()) refs, err := repo.GetBranches(ctx) require.NoError(t, err) @@ -382,7 +406,7 @@ func TestLocalRepository_UpdateRef(t *testing.T) { testRepo, _, cleanup := testhelper.NewTestRepo(t) defer cleanup() - otherRef, err := NewRepository(testRepo).GetReference(ctx, "refs/heads/gitaly-test-ref") + otherRef, err := git.NewRepository(testRepo).GetReference(ctx, "refs/heads/gitaly-test-ref") require.NoError(t, err) testcases := []struct { @@ -390,14 +414,14 @@ func TestLocalRepository_UpdateRef(t *testing.T) { ref string newrev string oldrev string - verify func(t *testing.T, repo Repository, err error) + verify func(t *testing.T, repo git.Repository, err error) }{ { desc: "successfully update master", ref: "refs/heads/master", newrev: otherRef.Target, oldrev: MasterID, - verify: func(t *testing.T, repo Repository, err error) { + verify: func(t *testing.T, repo git.Repository, err error) { require.NoError(t, err) ref, err := repo.GetReference(ctx, "refs/heads/master") require.NoError(t, err) @@ -409,7 +433,7 @@ func TestLocalRepository_UpdateRef(t *testing.T) { ref: "refs/heads/master", newrev: otherRef.Target, oldrev: NonexistentID, - verify: func(t *testing.T, repo Repository, err error) { + verify: func(t *testing.T, repo git.Repository, err error) { require.Error(t, err) ref, err := repo.GetReference(ctx, "refs/heads/master") require.NoError(t, err) @@ -421,7 +445,7 @@ func TestLocalRepository_UpdateRef(t *testing.T) { ref: "refs/heads/master", newrev: NonexistentID, oldrev: MasterID, - verify: func(t *testing.T, repo Repository, err error) { + verify: func(t *testing.T, repo git.Repository, err error) { require.Error(t, err) ref, err := repo.GetReference(ctx, "refs/heads/master") require.NoError(t, err) @@ -433,7 +457,7 @@ func TestLocalRepository_UpdateRef(t *testing.T) { ref: "refs/heads/master", newrev: otherRef.Target, oldrev: "", - verify: func(t *testing.T, repo Repository, err error) { + verify: func(t *testing.T, repo git.Repository, err error) { require.NoError(t, err) ref, err := repo.GetReference(ctx, "refs/heads/master") require.NoError(t, err) @@ -445,7 +469,7 @@ func TestLocalRepository_UpdateRef(t *testing.T) { ref: "master", newrev: otherRef.Target, oldrev: MasterID, - verify: func(t *testing.T, repo Repository, err error) { + verify: func(t *testing.T, repo git.Repository, err error) { require.Error(t, err) ref, err := repo.GetReference(ctx, "refs/heads/master") require.NoError(t, err) @@ -457,7 +481,7 @@ func TestLocalRepository_UpdateRef(t *testing.T) { ref: "refs/heads/master", newrev: strings.Repeat("0", 40), oldrev: MasterID, - verify: func(t *testing.T, repo Repository, err error) { + verify: func(t *testing.T, repo git.Repository, err error) { require.NoError(t, err) _, err = repo.GetReference(ctx, "refs/heads/master") require.Error(t, err) @@ -468,7 +492,7 @@ func TestLocalRepository_UpdateRef(t *testing.T) { ref: "refs/heads/new", newrev: MasterID, oldrev: strings.Repeat("0", 40), - verify: func(t *testing.T, repo Repository, err error) { + verify: func(t *testing.T, repo git.Repository, err error) { require.NoError(t, err) ref, err := repo.GetReference(ctx, "refs/heads/new") require.NoError(t, err) @@ -483,7 +507,7 @@ func TestLocalRepository_UpdateRef(t *testing.T) { testRepo, _, cleanup := testhelper.NewTestRepo(t) defer cleanup() - repo := NewRepository(testRepo) + repo := git.NewRepository(testRepo) err := repo.UpdateRef(ctx, tc.ref, tc.newrev, tc.oldrev) tc.verify(t, repo, err) diff --git a/internal/gitaly/hook/manager.go b/internal/gitaly/hook/manager.go index 09cb82df1..092816468 100644 --- a/internal/gitaly/hook/manager.go +++ b/internal/gitaly/hook/manager.go @@ -10,6 +10,22 @@ import ( "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" ) +// ReferenceTransactionState is the state of the Git reference transaction. It reflects the first +// parameter of the reference-transaction hook. See githooks(1) for more information. +type ReferenceTransactionState int + +const ( + // ReferenceTransactionPrepared indicates all reference updates have been queued to the + // transaction and were locked on disk. + ReferenceTransactionPrepared = ReferenceTransactionState(iota) + // ReferenceTransactionCommitted indicates the reference transaction was committed and all + // references now have their respective new value. + ReferenceTransactionCommitted + // ReferenceTransactionAborted indicates the transaction was aborted, no changes were + // performed and the reference locks have been released. + ReferenceTransactionAborted +) + // Manager is an interface providing the ability to execute Git hooks. type Manager interface { // PreReceiveHook executes the pre-receive Git hook and any installed custom hooks. stdin @@ -26,7 +42,7 @@ type Manager interface { // ReferenceTransactionHook executes the reference-transaction Git hook. stdin must contain // all references to be updated and match the format specified in githooks(5). - ReferenceTransactionHook(ctx context.Context, env []string, stdin io.Reader) error + ReferenceTransactionHook(ctx context.Context, state ReferenceTransactionState, env []string, stdin io.Reader) error } // GitLabHookManager is a hook manager containing Git hook business logic. It diff --git a/internal/gitaly/hook/referencetransaction.go b/internal/gitaly/hook/referencetransaction.go index d16cce80a..0b899d21c 100644 --- a/internal/gitaly/hook/referencetransaction.go +++ b/internal/gitaly/hook/referencetransaction.go @@ -9,7 +9,14 @@ import ( "gitlab.com/gitlab-org/gitaly/internal/helper" ) -func (m *GitLabHookManager) ReferenceTransactionHook(ctx context.Context, env []string, stdin io.Reader) error { +func (m *GitLabHookManager) ReferenceTransactionHook(ctx context.Context, state ReferenceTransactionState, env []string, stdin io.Reader) error { + // We're only voting in prepared state as this is the only stage in + // Git's reference transaction which allows us to abort the + // transaction. + if state != ReferenceTransactionPrepared { + return nil + } + changes, err := ioutil.ReadAll(stdin) if err != nil { return helper.ErrInternalf("reading stdin from request: %w", err) diff --git a/internal/gitaly/service/hook/reference_transaction.go b/internal/gitaly/service/hook/reference_transaction.go index 2118ceb02..92122ba23 100644 --- a/internal/gitaly/service/hook/reference_transaction.go +++ b/internal/gitaly/service/hook/reference_transaction.go @@ -3,6 +3,7 @@ package hook import ( "errors" + "gitlab.com/gitlab-org/gitaly/internal/gitaly/hook" "gitlab.com/gitlab-org/gitaly/internal/helper" "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" "gitlab.com/gitlab-org/gitaly/streamio" @@ -26,6 +27,18 @@ func (s *server) ReferenceTransactionHook(stream gitalypb.HookService_ReferenceT return helper.ErrInvalidArgument(err) } + var state hook.ReferenceTransactionState + switch request.State { + case gitalypb.ReferenceTransactionHookRequest_PREPARED: + state = hook.ReferenceTransactionPrepared + case gitalypb.ReferenceTransactionHookRequest_COMMITTED: + state = hook.ReferenceTransactionCommitted + case gitalypb.ReferenceTransactionHookRequest_ABORTED: + state = hook.ReferenceTransactionAborted + default: + return helper.ErrInvalidArgument(errors.New("invalid hook state")) + } + stdin := streamio.NewReader(func() ([]byte, error) { req, err := stream.Recv() return req.GetStdin(), err @@ -33,6 +46,7 @@ func (s *server) ReferenceTransactionHook(stream gitalypb.HookService_ReferenceT if err := s.manager.ReferenceTransactionHook( stream.Context(), + state, request.GetEnvironmentVariables(), stdin, ); err != nil { diff --git a/internal/gitaly/service/hook/reference_transaction_test.go b/internal/gitaly/service/hook/reference_transaction_test.go index 67ce79d8d..c7052f4fc 100644 --- a/internal/gitaly/service/hook/reference_transaction_test.go +++ b/internal/gitaly/service/hook/reference_transaction_test.go @@ -50,18 +50,39 @@ func TestReferenceTransactionHook(t *testing.T) { testCases := []struct { desc string stdin []byte + state gitalypb.ReferenceTransactionHookRequest_State voteResponse gitalypb.VoteTransactionResponse_TransactionState expectedCode codes.Code expectedReftxHash []byte }{ { - desc: "hook triggers transaction", + desc: "hook triggers transaction with default state", stdin: []byte("foobar"), voteResponse: gitalypb.VoteTransactionResponse_COMMIT, expectedCode: codes.OK, expectedReftxHash: []byte("foobar"), }, { + desc: "hook triggers transaction with explicit prepared state", + stdin: []byte("foobar"), + state: gitalypb.ReferenceTransactionHookRequest_PREPARED, + voteResponse: gitalypb.VoteTransactionResponse_COMMIT, + expectedCode: codes.OK, + expectedReftxHash: []byte("foobar"), + }, + { + desc: "hook does not trigger transaction with aborted state", + stdin: []byte("foobar"), + state: gitalypb.ReferenceTransactionHookRequest_ABORTED, + expectedCode: codes.OK, + }, + { + desc: "hook does not trigger transaction with committed state", + stdin: []byte("foobar"), + state: gitalypb.ReferenceTransactionHookRequest_COMMITTED, + expectedCode: codes.OK, + }, + { desc: "hook fails with failed vote", stdin: []byte("foobar"), voteResponse: gitalypb.VoteTransactionResponse_ABORT, @@ -130,6 +151,7 @@ func TestReferenceTransactionHook(t *testing.T) { require.NoError(t, err) require.NoError(t, stream.Send(&gitalypb.ReferenceTransactionHookRequest{ Repository: testRepo, + State: tc.state, EnvironmentVariables: environment, })) require.NoError(t, stream.Send(&gitalypb.ReferenceTransactionHookRequest{ diff --git a/internal/gitaly/service/operations/commit_files_test.go b/internal/gitaly/service/operations/commit_files_test.go index 391ba9c24..5ef05f171 100644 --- a/internal/gitaly/service/operations/commit_files_test.go +++ b/internal/gitaly/service/operations/commit_files_test.go @@ -1,7 +1,9 @@ package operations import ( + "bytes" "context" + "encoding/base64" "fmt" "strconv" "testing" @@ -13,13 +15,784 @@ import ( "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" ) var ( commitFilesMessage = []byte("Change files") ) -func testSuccessfulUserCommitFilesRequest(t *testing.T, ctxWithFeatureFlags context.Context) { +func testImplementations(t *testing.T, test func(t *testing.T, ctx context.Context)) { + ctx, cancel := testhelper.Context() + defer cancel() + + for _, tc := range []struct { + desc string + context context.Context + }{ + {desc: "ruby", context: ctx}, + } { + t.Run(tc.desc, func(t *testing.T) { test(t, tc.context) }) + } +} + +func TestUserCommitFiles(t *testing.T) { + testImplementations(t, testUserCommitFiles) +} + +func testUserCommitFiles(t *testing.T, ctx context.Context) { + type treeEntry struct { + Mode string + Path string + Content string + } + + requireTreeEntries := func(t testing.TB, repoPath, branch string, expectedEntries []treeEntry) { + t.Helper() + + var actualEntries []treeEntry + + output := bytes.TrimSpace(testhelper.MustRunCommand(t, nil, "git", "-C", repoPath, "ls-tree", "-r", branch)) + + if len(output) > 0 { + for _, line := range bytes.Split(output, []byte("\n")) { + // Format: <mode> SP <type> SP <object> TAB <file> + tabSplit := bytes.Split(line, []byte("\t")) + spaceSplit := bytes.Split(tabSplit[0], []byte(" ")) + path := string(tabSplit[1]) + actualEntries = append(actualEntries, treeEntry{ + Mode: string(spaceSplit[0]), + Path: path, + Content: string(testhelper.MustRunCommand(t, nil, "git", "-C", repoPath, "show", branch+":"+path)), + }) + } + } + + require.Equal(t, expectedEntries, actualEntries) + } + + const ( + DefaultMode = "100644" + ExecutableMode = "100755" + ) + + type step struct { + actions []*gitalypb.UserCommitFilesRequest + error error + indexError string + repoCreated bool + branchCreated bool + treeEntries []treeEntry + } + + for _, tc := range []struct { + desc string + steps []step + }{ + { + desc: "create directory", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + createDirHeaderRequest("directory-1"), + }, + repoCreated: true, + branchCreated: true, + treeEntries: []treeEntry{ + {Mode: DefaultMode, Path: "directory-1/.gitkeep"}, + }, + }, + }, + }, + { + desc: "create directory ignores mode and content", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + actionRequest(&gitalypb.UserCommitFilesAction{ + UserCommitFilesActionPayload: &gitalypb.UserCommitFilesAction_Header{ + Header: &gitalypb.UserCommitFilesActionHeader{ + Action: gitalypb.UserCommitFilesActionHeader_CREATE_DIR, + FilePath: []byte("directory-1"), + ExecuteFilemode: true, + Base64Content: true, + }, + }, + }), + actionContentRequest("content-1"), + }, + repoCreated: true, + branchCreated: true, + treeEntries: []treeEntry{ + {Mode: DefaultMode, Path: "directory-1/.gitkeep"}, + }, + }, + }, + }, + { + desc: "create directory created duplicate", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + createDirHeaderRequest("directory-1"), + createDirHeaderRequest("directory-1"), + }, + indexError: "A directory with this name already exists", + }, + }, + }, + { + desc: "create directory with traversal", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + createDirHeaderRequest("../directory-1"), + }, + indexError: "Path cannot include directory traversal", + }, + }, + }, + { + desc: "create directory existing duplicate", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + createDirHeaderRequest("directory-1"), + }, + repoCreated: true, + branchCreated: true, + treeEntries: []treeEntry{ + {Mode: DefaultMode, Path: "directory-1/.gitkeep"}, + }, + }, + { + actions: []*gitalypb.UserCommitFilesRequest{ + createDirHeaderRequest("directory-1"), + }, + indexError: "A directory with this name already exists", + }, + }, + }, + { + desc: "create directory with files name", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + createFileHeaderRequest("file-1"), + actionContentRequest("content-1"), + }, + repoCreated: true, + branchCreated: true, + treeEntries: []treeEntry{ + {Mode: DefaultMode, Path: "file-1", Content: "content-1"}, + }, + }, + { + actions: []*gitalypb.UserCommitFilesRequest{ + createDirHeaderRequest("file-1"), + }, + indexError: "A file with this name already exists", + }, + }, + }, + { + desc: "create file with directory traversal", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + createFileHeaderRequest("../file-1"), + actionContentRequest("content-1"), + }, + indexError: "Path cannot include directory traversal", + }, + }, + }, + { + desc: "create file without content", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + createFileHeaderRequest("file-1"), + }, + repoCreated: true, + branchCreated: true, + treeEntries: []treeEntry{ + {Mode: DefaultMode, Path: "file-1"}, + }, + }, + }, + }, + { + desc: "create file", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + createFileHeaderRequest("file-1"), + actionContentRequest("content-1"), + actionContentRequest(" content-2"), + }, + repoCreated: true, + branchCreated: true, + treeEntries: []treeEntry{ + {Mode: DefaultMode, Path: "file-1", Content: "content-1 content-2"}, + }, + }, + }, + }, + { + desc: "create file with base64 content", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + createBase64FileHeaderRequest("file-1"), + actionContentRequest(base64.StdEncoding.EncodeToString([]byte("content-1"))), + actionContentRequest(base64.StdEncoding.EncodeToString([]byte(" content-2"))), + }, + repoCreated: true, + branchCreated: true, + treeEntries: []treeEntry{ + {Mode: DefaultMode, Path: "file-1", Content: "content-1 content-2"}, + }, + }, + }, + }, + { + desc: "create duplicate file", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + createFileHeaderRequest("file-1"), + actionContentRequest("content-1"), + createFileHeaderRequest("file-1"), + }, + indexError: "A file with this name already exists", + }, + }, + }, + { + desc: "create file overwrites directory", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + createDirHeaderRequest("file-1"), + createFileHeaderRequest("file-1"), + actionContentRequest("content-1"), + }, + repoCreated: true, + branchCreated: true, + treeEntries: []treeEntry{ + {Mode: DefaultMode, Path: "file-1", Content: "content-1"}, + }, + }, + }, + }, + { + desc: "update created file", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + createFileHeaderRequest("file-1"), + actionContentRequest("content-1"), + updateFileHeaderRequest("file-1"), + actionContentRequest("content-2"), + }, + repoCreated: true, + branchCreated: true, + treeEntries: []treeEntry{ + {Mode: DefaultMode, Path: "file-1", Content: "content-2"}, + }, + }, + }, + }, + { + desc: "update base64 content", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + createFileHeaderRequest("file-1"), + actionContentRequest("content-1"), + updateBase64FileHeaderRequest("file-1"), + actionContentRequest(base64.StdEncoding.EncodeToString([]byte("content-2"))), + }, + repoCreated: true, + branchCreated: true, + treeEntries: []treeEntry{ + {Mode: DefaultMode, Path: "file-1", Content: "content-2"}, + }, + }, + }, + }, + { + desc: "update ignores mode", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + createFileHeaderRequest("file-1"), + actionContentRequest("content-1"), + actionRequest(&gitalypb.UserCommitFilesAction{ + UserCommitFilesActionPayload: &gitalypb.UserCommitFilesAction_Header{ + Header: &gitalypb.UserCommitFilesActionHeader{ + Action: gitalypb.UserCommitFilesActionHeader_UPDATE, + FilePath: []byte("file-1"), + ExecuteFilemode: true, + }, + }, + }), + actionContentRequest("content-2"), + }, + repoCreated: true, + branchCreated: true, + treeEntries: []treeEntry{ + {Mode: DefaultMode, Path: "file-1", Content: "content-2"}, + }, + }, + }, + }, + { + desc: "update existing file", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + createFileHeaderRequest("file-1"), + actionContentRequest("content-1"), + }, + repoCreated: true, + branchCreated: true, + treeEntries: []treeEntry{ + {Mode: DefaultMode, Path: "file-1", Content: "content-1"}, + }, + }, + { + actions: []*gitalypb.UserCommitFilesRequest{ + updateFileHeaderRequest("file-1"), + actionContentRequest("content-2"), + }, + treeEntries: []treeEntry{ + {Mode: DefaultMode, Path: "file-1", Content: "content-2"}, + }, + }, + }, + }, + { + desc: "update non-existing file", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + updateFileHeaderRequest("non-existing"), + actionContentRequest("content"), + }, + indexError: "A file with this name doesn't exist", + }, + }, + }, + { + desc: "move file with traversal in source", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + moveFileHeaderRequest("../original-file", "moved-file", true), + }, + indexError: "Path cannot include directory traversal", + }, + }, + }, + { + desc: "move file with traversal in destination", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + moveFileHeaderRequest("original-file", "../moved-file", true), + }, + indexError: "Path cannot include directory traversal", + }, + }, + }, + { + desc: "move created file", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + createFileHeaderRequest("original-file"), + actionContentRequest("content-1"), + moveFileHeaderRequest("original-file", "moved-file", true), + }, + repoCreated: true, + branchCreated: true, + treeEntries: []treeEntry{ + {Mode: DefaultMode, Path: "moved-file", Content: "content-1"}, + }, + }, + }, + }, + { + desc: "move ignores mode", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + createFileHeaderRequest("original-file"), + actionContentRequest("content-1"), + actionRequest(&gitalypb.UserCommitFilesAction{ + UserCommitFilesActionPayload: &gitalypb.UserCommitFilesAction_Header{ + Header: &gitalypb.UserCommitFilesActionHeader{ + Action: gitalypb.UserCommitFilesActionHeader_MOVE, + FilePath: []byte("moved-file"), + PreviousPath: []byte("original-file"), + ExecuteFilemode: true, + InferContent: true, + }, + }, + }), + }, + repoCreated: true, + branchCreated: true, + treeEntries: []treeEntry{ + {Mode: DefaultMode, Path: "moved-file", Content: "content-1"}, + }, + }, + }, + }, + { + desc: "moving directory fails", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + createDirHeaderRequest("directory"), + moveFileHeaderRequest("directory", "moved-directory", true), + }, + indexError: "A file with this name doesn't exist", + }, + }, + }, + { + desc: "move file inferring content", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + createFileHeaderRequest("original-file"), + actionContentRequest("original-content"), + }, + repoCreated: true, + branchCreated: true, + treeEntries: []treeEntry{ + {Mode: DefaultMode, Path: "original-file", Content: "original-content"}, + }, + }, + { + actions: []*gitalypb.UserCommitFilesRequest{ + moveFileHeaderRequest("original-file", "moved-file", true), + actionContentRequest("ignored-content"), + }, + treeEntries: []treeEntry{ + {Mode: DefaultMode, Path: "moved-file", Content: "original-content"}, + }, + }, + }, + }, + { + desc: "move file with non-existing source", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + moveFileHeaderRequest("non-existing", "destination-file", true), + }, + indexError: "A file with this name doesn't exist", + }, + }, + }, + { + desc: "move file with already existing destination file", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + createFileHeaderRequest("source-file"), + createFileHeaderRequest("already-existing"), + moveFileHeaderRequest("source-file", "already-existing", true), + }, + indexError: "A file with this name already exists", + }, + }, + }, + { + // seems like a bug in the original implementation to allow overwriting a + // directory + desc: "move file with already existing destination directory", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + createFileHeaderRequest("source-file"), + actionContentRequest("source-content"), + createDirHeaderRequest("already-existing"), + moveFileHeaderRequest("source-file", "already-existing", true), + }, + repoCreated: true, + branchCreated: true, + treeEntries: []treeEntry{ + {Mode: DefaultMode, Path: "already-existing", Content: "source-content"}, + }, + }, + }, + }, + { + desc: "move file providing content", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + createFileHeaderRequest("original-file"), + actionContentRequest("original-content"), + }, + repoCreated: true, + branchCreated: true, + treeEntries: []treeEntry{ + {Mode: DefaultMode, Path: "original-file", Content: "original-content"}, + }, + }, + { + actions: []*gitalypb.UserCommitFilesRequest{ + moveFileHeaderRequest("original-file", "moved-file", false), + actionContentRequest("new-content"), + }, + treeEntries: []treeEntry{ + {Mode: DefaultMode, Path: "moved-file", Content: "new-content"}, + }, + }, + }, + }, + { + desc: "mark non-existing file executable", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + chmodFileHeaderRequest("file-1", true), + }, + indexError: "A file with this name doesn't exist", + }, + }, + }, + { + desc: "mark executable file executable", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + createFileHeaderRequest("file-1"), + chmodFileHeaderRequest("file-1", true), + }, + repoCreated: true, + branchCreated: true, + treeEntries: []treeEntry{ + {Mode: ExecutableMode, Path: "file-1"}, + }, + }, + { + actions: []*gitalypb.UserCommitFilesRequest{ + chmodFileHeaderRequest("file-1", true), + }, + treeEntries: []treeEntry{ + {Mode: ExecutableMode, Path: "file-1"}, + }, + }, + }, + }, + { + desc: "mark file executable with directory traversal", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + chmodFileHeaderRequest("../file-1", true), + }, + indexError: "Path cannot include directory traversal", + }, + }, + }, + { + desc: "mark created file executable", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + createFileHeaderRequest("file-1"), + actionContentRequest("content-1"), + chmodFileHeaderRequest("file-1", true), + }, + repoCreated: true, + branchCreated: true, + treeEntries: []treeEntry{ + {Mode: ExecutableMode, Path: "file-1", Content: "content-1"}, + }, + }, + }, + }, + { + desc: "mark existing file executable", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + createFileHeaderRequest("file-1"), + actionContentRequest("content-1"), + }, + repoCreated: true, + branchCreated: true, + treeEntries: []treeEntry{ + {Mode: DefaultMode, Path: "file-1", Content: "content-1"}, + }, + }, + { + actions: []*gitalypb.UserCommitFilesRequest{ + chmodFileHeaderRequest("file-1", true), + }, + treeEntries: []treeEntry{ + {Mode: ExecutableMode, Path: "file-1", Content: "content-1"}, + }, + }, + }, + }, + { + desc: "move non-existing file", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + moveFileHeaderRequest("non-existing", "should-not-be-created", true), + }, + indexError: "A file with this name doesn't exist", + }, + }, + }, + { + desc: "move doesn't overwrite a file", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + createFileHeaderRequest("file-1"), + actionContentRequest("content-1"), + createFileHeaderRequest("file-2"), + actionContentRequest("content-2"), + moveFileHeaderRequest("file-1", "file-2", true), + }, + indexError: "A file with this name already exists", + }, + }, + }, + { + desc: "delete non-existing file", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + deleteFileHeaderRequest("non-existing"), + }, + indexError: "A file with this name doesn't exist", + }, + }, + }, + { + desc: "delete file with directory traversal", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + deleteFileHeaderRequest("../file-1"), + }, + indexError: "Path cannot include directory traversal", + }, + }, + }, + { + desc: "delete created file", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + createFileHeaderRequest("file-1"), + actionContentRequest("content-1"), + deleteFileHeaderRequest("file-1"), + }, + branchCreated: true, + repoCreated: true, + }, + }, + }, + { + desc: "delete existing file", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + createFileHeaderRequest("file-1"), + actionContentRequest("content-1"), + }, + branchCreated: true, + repoCreated: true, + treeEntries: []treeEntry{ + {Mode: DefaultMode, Path: "file-1", Content: "content-1"}, + }, + }, + { + actions: []*gitalypb.UserCommitFilesRequest{ + deleteFileHeaderRequest("file-1"), + }, + }, + }, + }, + { + desc: "invalid action", + steps: []step{ + { + actions: []*gitalypb.UserCommitFilesRequest{ + actionRequest(&gitalypb.UserCommitFilesAction{ + UserCommitFilesActionPayload: &gitalypb.UserCommitFilesAction_Header{ + Header: &gitalypb.UserCommitFilesActionHeader{ + Action: -1, + }, + }, + }), + }, + error: status.Error(codes.Unknown, "NoMethodError: undefined method `downcase' for -1:Integer"), + }, + }, + }, + } { + t.Run(tc.desc, func(t *testing.T) { + repo, repoPath, clean := testhelper.InitBareRepo(t) + defer clean() + + serverSocketPath, stop := runOperationServiceServer(t) + defer stop() + + client, conn := newOperationClient(t, serverSocketPath) + defer conn.Close() + + const branch = "master" + + for i, step := range tc.steps { + stream, err := client.UserCommitFiles(ctx) + require.NoError(t, err) + + headerRequest := headerRequest(repo, testhelper.TestUser, branch, []byte("commit message")) + setAuthorAndEmail(headerRequest, []byte("Author Name"), []byte("author.email@example.com")) + require.NoError(t, stream.Send(headerRequest)) + + for j, action := range step.actions { + require.NoError(t, stream.Send(action), "step %d, action %d", i+1, j+1) + } + + resp, err := stream.CloseAndRecv() + require.Equal(t, step.error, err) + if step.error != nil { + continue + } + + require.Equal(t, step.indexError, resp.IndexError, "step %d", i+1) + if step.indexError != "" { + continue + } + + require.Equal(t, step.branchCreated, resp.BranchUpdate.BranchCreated, "step %d", i+1) + require.Equal(t, step.repoCreated, resp.BranchUpdate.RepoCreated, "step %d", i+1) + requireTreeEntries(t, repoPath, branch, step.treeEntries) + } + }) + } +} + +func TestSuccessfulUserCommitFilesRequest(t *testing.T) { + testImplementations(t, testSuccessfulUserCommitFilesRequest) +} + +func testSuccessfulUserCommitFilesRequest(t *testing.T, ctx context.Context) { testRepo, testRepoPath, cleanup := testhelper.NewTestRepo(t) defer cleanup() @@ -32,7 +805,6 @@ func testSuccessfulUserCommitFilesRequest(t *testing.T, ctxWithFeatureFlags cont newRepo, newRepoPath, newRepoCleanupFn := testhelper.InitBareRepo(t) defer newRepoCleanupFn() - md := testhelper.GitalyServersMetadata(t, serverSocketPath) filePath := "héllo/wörld" authorName := []byte("Jane Doe") authorEmail := []byte("janedoe@gitlab.com") @@ -82,7 +854,6 @@ func testSuccessfulUserCommitFilesRequest(t *testing.T, ctxWithFeatureFlags cont for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { - ctx := metadata.NewOutgoingContext(ctxWithFeatureFlags, md) headerRequest := headerRequest(tc.repo, testhelper.TestUser, tc.branchName, commitFilesMessage) setAuthorAndEmail(headerRequest, authorName, authorEmail) @@ -104,7 +875,7 @@ func testSuccessfulUserCommitFilesRequest(t *testing.T, ctxWithFeatureFlags cont require.Equal(t, tc.repoCreated, resp.GetBranchUpdate().GetRepoCreated()) require.Equal(t, tc.branchCreated, resp.GetBranchUpdate().GetBranchCreated()) - headCommit, err := log.GetCommit(ctxWithFeatureFlags, tc.repo, tc.branchName) + headCommit, err := log.GetCommit(ctx, tc.repo, tc.branchName) require.NoError(t, err) require.Equal(t, authorName, headCommit.Author.Name) require.Equal(t, testhelper.TestUser.Name, headCommit.Committer.Name) @@ -125,23 +896,17 @@ func testSuccessfulUserCommitFilesRequest(t *testing.T, ctxWithFeatureFlags cont } } -func TestSuccessfulUserCommitFilesRequest(t *testing.T) { - ctx, cancel := testhelper.Context() - defer cancel() - - testSuccessfulUserCommitFilesRequest(t, ctx) +func TestSuccessfulUserCommitFilesRequestMove(t *testing.T) { + testImplementations(t, testSuccessfulUserCommitFilesRequestMove) } -func TestSuccessfulUserCommitFilesRequestMove(t *testing.T) { +func testSuccessfulUserCommitFilesRequestMove(t *testing.T, ctx context.Context) { serverSocketPath, stop := runOperationServiceServer(t) defer stop() client, conn := newOperationClient(t, serverSocketPath) defer conn.Close() - ctxOuter, cancel := testhelper.Context() - defer cancel() - branchName := "master" previousFilePath := "README" filePath := "NEWREADME" @@ -162,8 +927,6 @@ func TestSuccessfulUserCommitFilesRequestMove(t *testing.T) { defer cleanupFn() origFileContent := testhelper.MustRunCommand(t, nil, "git", "-C", testRepoPath, "show", branchName+":"+previousFilePath) - md := testhelper.GitalyServersMetadata(t, serverSocketPath) - ctx := metadata.NewOutgoingContext(ctxOuter, md) headerRequest := headerRequest(testRepo, testhelper.TestUser, branchName, commitFilesMessage) setAuthorAndEmail(headerRequest, authorName, authorEmail) actionsRequest1 := moveFileHeaderRequest(previousFilePath, filePath, tc.infer) @@ -196,6 +959,10 @@ func TestSuccessfulUserCommitFilesRequestMove(t *testing.T) { } func TestSuccessfulUserCommitFilesRequestForceCommit(t *testing.T) { + testImplementations(t, testSuccessfulUserCommitFilesRequestForceCommit) +} + +func testSuccessfulUserCommitFilesRequestForceCommit(t *testing.T, ctx context.Context) { serverSocketPath, stop := runOperationServiceServer(t) defer stop() @@ -205,20 +972,15 @@ func TestSuccessfulUserCommitFilesRequestForceCommit(t *testing.T) { testRepo, testRepoPath, cleanupFn := testhelper.NewTestRepo(t) defer cleanupFn() - ctxOuter, cancel := testhelper.Context() - defer cancel() - - md := testhelper.GitalyServersMetadata(t, serverSocketPath) - ctx := metadata.NewOutgoingContext(ctxOuter, md) authorName := []byte("Jane Doe") authorEmail := []byte("janedoe@gitlab.com") targetBranchName := "feature" startBranchName := []byte("master") - startBranchCommit, err := log.GetCommit(ctxOuter, testRepo, string(startBranchName)) + startBranchCommit, err := log.GetCommit(ctx, testRepo, string(startBranchName)) require.NoError(t, err) - targetBranchCommit, err := log.GetCommit(ctxOuter, testRepo, targetBranchName) + targetBranchCommit, err := log.GetCommit(ctx, testRepo, targetBranchName) require.NoError(t, err) mergeBaseOut := testhelper.MustRunCommand(t, nil, "git", "-C", testRepoPath, "merge-base", targetBranchCommit.Id, startBranchCommit.Id) @@ -240,7 +1002,7 @@ func TestSuccessfulUserCommitFilesRequestForceCommit(t *testing.T) { require.NoError(t, err) update := resp.GetBranchUpdate() - newTargetBranchCommit, err := log.GetCommit(ctxOuter, testRepo, targetBranchName) + newTargetBranchCommit, err := log.GetCommit(ctx, testRepo, targetBranchName) require.NoError(t, err) require.Equal(t, newTargetBranchCommit.Id, update.CommitId) @@ -248,6 +1010,10 @@ func TestSuccessfulUserCommitFilesRequestForceCommit(t *testing.T) { } func TestSuccessfulUserCommitFilesRequestStartSha(t *testing.T) { + testImplementations(t, testSuccessfulUserCommitFilesRequestStartSha) +} + +func testSuccessfulUserCommitFilesRequestStartSha(t *testing.T, ctx context.Context) { serverSocketPath, stop := runOperationServiceServer(t) defer stop() @@ -257,14 +1023,9 @@ func TestSuccessfulUserCommitFilesRequestStartSha(t *testing.T) { testRepo, _, cleanupFn := testhelper.NewTestRepo(t) defer cleanupFn() - ctxOuter, cancel := testhelper.Context() - defer cancel() - - md := testhelper.GitalyServersMetadata(t, serverSocketPath) - ctx := metadata.NewOutgoingContext(ctxOuter, md) targetBranchName := "new" - startCommit, err := log.GetCommit(ctxOuter, testRepo, "master") + startCommit, err := log.GetCommit(ctx, testRepo, "master") require.NoError(t, err) headerRequest := headerRequest(testRepo, testhelper.TestUser, targetBranchName, commitFilesMessage) @@ -280,7 +1041,7 @@ func TestSuccessfulUserCommitFilesRequestStartSha(t *testing.T) { require.NoError(t, err) update := resp.GetBranchUpdate() - newTargetBranchCommit, err := log.GetCommit(ctxOuter, testRepo, targetBranchName) + newTargetBranchCommit, err := log.GetCommit(ctx, testRepo, targetBranchName) require.NoError(t, err) require.Equal(t, newTargetBranchCommit.Id, update.CommitId) @@ -288,6 +1049,10 @@ func TestSuccessfulUserCommitFilesRequestStartSha(t *testing.T) { } func TestSuccessfulUserCommitFilesRequestStartShaRemoteRepository(t *testing.T) { + testImplementations(t, testSuccessfulUserCommitFilesRequestStartShaRemoteRepository) +} + +func testSuccessfulUserCommitFilesRequestStartShaRemoteRepository(t *testing.T, ctx context.Context) { serverSocketPath, stop := runOperationServiceServer(t) defer stop() @@ -297,17 +1062,18 @@ func TestSuccessfulUserCommitFilesRequestStartShaRemoteRepository(t *testing.T) testRepo, _, cleanupFn := testhelper.NewTestRepo(t) defer cleanupFn() - ctxOuter, cancel := testhelper.Context() - defer cancel() - newRepo, _, newRepoCleanupFn := testhelper.InitBareRepo(t) defer newRepoCleanupFn() - md := testhelper.GitalyServersMetadata(t, serverSocketPath) - ctx := metadata.NewOutgoingContext(ctxOuter, md) + for key, values := range testhelper.GitalyServersMetadata(t, serverSocketPath) { + for _, value := range values { + ctx = metadata.AppendToOutgoingContext(ctx, key, value) + } + } + targetBranchName := "new" - startCommit, err := log.GetCommit(ctxOuter, testRepo, "master") + startCommit, err := log.GetCommit(ctx, testRepo, "master") require.NoError(t, err) headerRequest := headerRequest(newRepo, testhelper.TestUser, targetBranchName, commitFilesMessage) @@ -324,7 +1090,7 @@ func TestSuccessfulUserCommitFilesRequestStartShaRemoteRepository(t *testing.T) require.NoError(t, err) update := resp.GetBranchUpdate() - newTargetBranchCommit, err := log.GetCommit(ctxOuter, newRepo, targetBranchName) + newTargetBranchCommit, err := log.GetCommit(ctx, newRepo, targetBranchName) require.NoError(t, err) require.Equal(t, newTargetBranchCommit.Id, update.CommitId) @@ -332,6 +1098,10 @@ func TestSuccessfulUserCommitFilesRequestStartShaRemoteRepository(t *testing.T) } func TestSuccessfulUserCommitFilesRequestWithSpecialCharactersInSignature(t *testing.T) { + testImplementations(t, testSuccessfulUserCommitFilesRequestWithSpecialCharactersInSignature) +} + +func testSuccessfulUserCommitFilesRequestWithSpecialCharactersInSignature(t *testing.T, ctx context.Context) { serverSocketPath, stop := runOperationServiceServer(t) defer stop() @@ -341,10 +1111,6 @@ func TestSuccessfulUserCommitFilesRequestWithSpecialCharactersInSignature(t *tes testRepo, _, cleanupFn := testhelper.InitBareRepo(t) defer cleanupFn() - ctxOuter, cancel := testhelper.Context() - defer cancel() - - md := testhelper.GitalyServersMetadata(t, serverSocketPath) targetBranchName := "master" testCases := []struct { @@ -366,7 +1132,6 @@ func TestSuccessfulUserCommitFilesRequestWithSpecialCharactersInSignature(t *tes for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { - ctx := metadata.NewOutgoingContext(ctxOuter, md) headerRequest := headerRequest(testRepo, tc.user, targetBranchName, commitFilesMessage) setAuthorAndEmail(headerRequest, tc.user.Name, tc.user.Email) @@ -377,7 +1142,7 @@ func TestSuccessfulUserCommitFilesRequestWithSpecialCharactersInSignature(t *tes _, err = stream.CloseAndRecv() require.NoError(t, err) - newCommit, err := log.GetCommit(ctxOuter, testRepo, targetBranchName) + newCommit, err := log.GetCommit(ctx, testRepo, targetBranchName) require.NoError(t, err) require.Equal(t, tc.author.Name, newCommit.Author.Name, "author name") @@ -389,6 +1154,10 @@ func TestSuccessfulUserCommitFilesRequestWithSpecialCharactersInSignature(t *tes } func TestFailedUserCommitFilesRequestDueToHooks(t *testing.T) { + testImplementations(t, testFailedUserCommitFilesRequestDueToHooks) +} + +func testFailedUserCommitFilesRequestDueToHooks(t *testing.T, ctx context.Context) { serverSocketPath, stop := runOperationServiceServer(t) defer stop() @@ -398,9 +1167,6 @@ func TestFailedUserCommitFilesRequestDueToHooks(t *testing.T) { testRepo, testRepoPath, cleanupFn := testhelper.NewTestRepo(t) defer cleanupFn() - ctxOuter, cancel := testhelper.Context() - defer cancel() - branchName := "feature" filePath := "my/file.txt" headerRequest := headerRequest(testRepo, testhelper.TestUser, branchName, commitFilesMessage) @@ -413,8 +1179,7 @@ func TestFailedUserCommitFilesRequestDueToHooks(t *testing.T) { remove, err := testhelper.WriteCustomHook(testRepoPath, hookName, hookContent) require.NoError(t, err) defer remove() - md := testhelper.GitalyServersMetadata(t, serverSocketPath) - ctx := metadata.NewOutgoingContext(ctxOuter, md) + stream, err := client.UserCommitFiles(ctx) require.NoError(t, err) require.NoError(t, stream.Send(headerRequest)) @@ -431,6 +1196,10 @@ func TestFailedUserCommitFilesRequestDueToHooks(t *testing.T) { } func TestFailedUserCommitFilesRequestDueToIndexError(t *testing.T) { + testImplementations(t, testFailedUserCommitFilesRequestDueToIndexError) +} + +func testFailedUserCommitFilesRequestDueToIndexError(t *testing.T, ctx context.Context) { serverSocketPath, stop := runOperationServiceServer(t) defer stop() @@ -440,11 +1209,6 @@ func TestFailedUserCommitFilesRequestDueToIndexError(t *testing.T) { testRepo, _, cleanupFn := testhelper.NewTestRepo(t) defer cleanupFn() - ctxOuter, cancel := testhelper.Context() - defer cancel() - - md := testhelper.GitalyServersMetadata(t, serverSocketPath) - ctx := metadata.NewOutgoingContext(ctxOuter, md) testCases := []struct { desc string requests []*gitalypb.UserCommitFilesRequest @@ -503,6 +1267,10 @@ func TestFailedUserCommitFilesRequestDueToIndexError(t *testing.T) { } func TestFailedUserCommitFilesRequest(t *testing.T) { + testImplementations(t, testFailedUserCommitFilesRequest) +} + +func testFailedUserCommitFilesRequest(t *testing.T, ctx context.Context) { serverSocketPath, stop := runOperationServiceServer(t) defer stop() @@ -512,11 +1280,6 @@ func TestFailedUserCommitFilesRequest(t *testing.T) { testRepo, _, cleanupFn := testhelper.NewTestRepo(t) defer cleanupFn() - ctxOuter, cancel := testhelper.Context() - defer cancel() - - md := testhelper.GitalyServersMetadata(t, serverSocketPath) - ctx := metadata.NewOutgoingContext(ctxOuter, md) branchName := "feature" testCases := []struct { @@ -626,6 +1389,17 @@ func getHeader(headerRequest *gitalypb.UserCommitFilesRequest) *gitalypb.UserCom return headerRequest.UserCommitFilesRequestPayload.(*gitalypb.UserCommitFilesRequest_Header).Header } +func createDirHeaderRequest(filePath string) *gitalypb.UserCommitFilesRequest { + return actionRequest(&gitalypb.UserCommitFilesAction{ + UserCommitFilesActionPayload: &gitalypb.UserCommitFilesAction_Header{ + Header: &gitalypb.UserCommitFilesActionHeader{ + Action: gitalypb.UserCommitFilesActionHeader_CREATE_DIR, + FilePath: []byte(filePath), + }, + }, + }) +} + func createFileHeaderRequest(filePath string) *gitalypb.UserCommitFilesRequest { return actionRequest(&gitalypb.UserCommitFilesAction{ UserCommitFilesActionPayload: &gitalypb.UserCommitFilesAction_Header{ @@ -638,6 +1412,41 @@ func createFileHeaderRequest(filePath string) *gitalypb.UserCommitFilesRequest { }) } +func createBase64FileHeaderRequest(filePath string) *gitalypb.UserCommitFilesRequest { + return actionRequest(&gitalypb.UserCommitFilesAction{ + UserCommitFilesActionPayload: &gitalypb.UserCommitFilesAction_Header{ + Header: &gitalypb.UserCommitFilesActionHeader{ + Action: gitalypb.UserCommitFilesActionHeader_CREATE, + Base64Content: true, + FilePath: []byte(filePath), + }, + }, + }) +} + +func updateFileHeaderRequest(filePath string) *gitalypb.UserCommitFilesRequest { + return actionRequest(&gitalypb.UserCommitFilesAction{ + UserCommitFilesActionPayload: &gitalypb.UserCommitFilesAction_Header{ + Header: &gitalypb.UserCommitFilesActionHeader{ + Action: gitalypb.UserCommitFilesActionHeader_UPDATE, + FilePath: []byte(filePath), + }, + }, + }) +} + +func updateBase64FileHeaderRequest(filePath string) *gitalypb.UserCommitFilesRequest { + return actionRequest(&gitalypb.UserCommitFilesAction{ + UserCommitFilesActionPayload: &gitalypb.UserCommitFilesAction_Header{ + Header: &gitalypb.UserCommitFilesActionHeader{ + Action: gitalypb.UserCommitFilesActionHeader_UPDATE, + FilePath: []byte(filePath), + Base64Content: true, + }, + }, + }) +} + func chmodFileHeaderRequest(filePath string, executeFilemode bool) *gitalypb.UserCommitFilesRequest { return actionRequest(&gitalypb.UserCommitFilesAction{ UserCommitFilesActionPayload: &gitalypb.UserCommitFilesAction_Header{ @@ -663,6 +1472,17 @@ func moveFileHeaderRequest(previousPath, filePath string, infer bool) *gitalypb. }) } +func deleteFileHeaderRequest(filePath string) *gitalypb.UserCommitFilesRequest { + return actionRequest(&gitalypb.UserCommitFilesAction{ + UserCommitFilesActionPayload: &gitalypb.UserCommitFilesAction_Header{ + Header: &gitalypb.UserCommitFilesActionHeader{ + Action: gitalypb.UserCommitFilesActionHeader_DELETE, + FilePath: []byte(filePath), + }, + }, + }) +} + func actionContentRequest(content string) *gitalypb.UserCommitFilesRequest { return actionRequest(&gitalypb.UserCommitFilesAction{ UserCommitFilesActionPayload: &gitalypb.UserCommitFilesAction_Content{ diff --git a/internal/gitaly/service/operations/testhelper_test.go b/internal/gitaly/service/operations/testhelper_test.go index 036205964..c453a7fa6 100644 --- a/internal/gitaly/service/operations/testhelper_test.go +++ b/internal/gitaly/service/operations/testhelper_test.go @@ -97,7 +97,7 @@ func runOperationServiceServerWithRubyServer(t *testing.T, ruby *rubyserver.Serv gitalypb.RegisterRepositoryServiceServer(srv.GrpcServer(), repository.NewServer(ruby, locator, internalSocket)) gitalypb.RegisterRefServiceServer(srv.GrpcServer(), ref.NewServer(locator)) gitalypb.RegisterCommitServiceServer(srv.GrpcServer(), commit.NewServer(locator)) - gitalypb.RegisterSSHServiceServer(srv.GrpcServer(), ssh.NewServer()) + gitalypb.RegisterSSHServiceServer(srv.GrpcServer(), ssh.NewServer(locator)) reflection.Register(srv.GrpcServer()) require.NoError(t, srv.Start()) diff --git a/internal/gitaly/service/register.go b/internal/gitaly/service/register.go index 736067c86..c8bab2613 100644 --- a/internal/gitaly/service/register.go +++ b/internal/gitaly/service/register.go @@ -77,6 +77,7 @@ func RegisterAll(grpcServer *grpc.Server, cfg config.Cfg, rubyServer *rubyserver gitalypb.RegisterRefServiceServer(grpcServer, ref.NewServer(locator)) gitalypb.RegisterRepositoryServiceServer(grpcServer, repository.NewServer(rubyServer, locator, config.GitalyInternalSocketPath())) gitalypb.RegisterSSHServiceServer(grpcServer, ssh.NewServer( + locator, ssh.WithPackfileNegotiationMetrics(sshPackfileNegotiationMetrics), )) gitalypb.RegisterSmartHTTPServiceServer(grpcServer, smarthttp.NewServer( diff --git a/internal/gitaly/service/remote/fetch_internal_remote_test.go b/internal/gitaly/service/remote/fetch_internal_remote_test.go index c7e43d05b..534bd694a 100644 --- a/internal/gitaly/service/remote/fetch_internal_remote_test.go +++ b/internal/gitaly/service/remote/fetch_internal_remote_test.go @@ -45,8 +45,9 @@ func TestSuccessfulFetchInternalRemote(t *testing.T) { }, }...) + locator := config.NewLocator(config.Config) gitaly0Server := testhelper.NewServer(t, nil, nil, testhelper.WithStorages([]string{"gitaly-0"})) - gitalypb.RegisterSSHServiceServer(gitaly0Server.GrpcServer(), ssh.NewServer()) + gitalypb.RegisterSSHServiceServer(gitaly0Server.GrpcServer(), ssh.NewServer(locator)) gitalypb.RegisterRefServiceServer(gitaly0Server.GrpcServer(), ref.NewServer(config.NewLocator(config.Config))) reflection.Register(gitaly0Server.GrpcServer()) require.NoError(t, gitaly0Server.Start()) diff --git a/internal/gitaly/service/smarthttp/receive_pack_test.go b/internal/gitaly/service/smarthttp/receive_pack_test.go index eb3dbbfee..0f5793844 100644 --- a/internal/gitaly/service/smarthttp/receive_pack_test.go +++ b/internal/gitaly/service/smarthttp/receive_pack_test.go @@ -648,7 +648,7 @@ func TestPostReceiveWithReferenceTransactionHook(t *testing.T) { if features.IsDisabled(featureflag.ReferenceTransactionHook) || !supported { require.Equal(t, 0, refTransactionServer.called) } else { - require.Equal(t, 4, refTransactionServer.called) + require.Equal(t, 2, refTransactionServer.called) } }) } diff --git a/internal/gitaly/service/ssh/receive_pack.go b/internal/gitaly/service/ssh/receive_pack.go index 949e35dbb..037ed18f6 100644 --- a/internal/gitaly/service/ssh/receive_pack.go +++ b/internal/gitaly/service/ssh/receive_pack.go @@ -31,14 +31,14 @@ func (s *server) SSHReceivePack(stream gitalypb.SSHService_SSHReceivePackServer) return helper.ErrInvalidArgument(err) } - if err := sshReceivePack(stream, req); err != nil { + if err := s.sshReceivePack(stream, req); err != nil { return helper.ErrInternal(err) } return nil } -func sshReceivePack(stream gitalypb.SSHService_SSHReceivePackServer, req *gitalypb.SSHReceivePackRequest) error { +func (s *server) sshReceivePack(stream gitalypb.SSHService_SSHReceivePackServer, req *gitalypb.SSHReceivePackRequest) error { ctx := stream.Context() stdin := streamio.NewReader(func() ([]byte, error) { @@ -58,7 +58,7 @@ func sshReceivePack(stream gitalypb.SSHService_SSHReceivePackServer, req *gitaly } env := append(hookEnv, "GL_PROTOCOL=ssh") - repoPath, err := helper.GetRepoPath(req.Repository) + repoPath, err := s.locator.GetRepoPath(req.Repository) if err != nil { return err } diff --git a/internal/gitaly/service/ssh/server.go b/internal/gitaly/service/ssh/server.go index b86df0a34..3a92c72b5 100644 --- a/internal/gitaly/service/ssh/server.go +++ b/internal/gitaly/service/ssh/server.go @@ -4,6 +4,7 @@ import ( "time" "github.com/prometheus/client_golang/prometheus" + "gitlab.com/gitlab-org/gitaly/internal/storage" "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" ) @@ -13,14 +14,16 @@ var ( ) type server struct { + locator storage.Locator uploadPackRequestTimeout time.Duration uploadArchiveRequestTimeout time.Duration packfileNegotiationMetrics *prometheus.CounterVec } // NewServer creates a new instance of a grpc SSHServer -func NewServer(serverOpts ...ServerOpt) gitalypb.SSHServiceServer { +func NewServer(locator storage.Locator, serverOpts ...ServerOpt) gitalypb.SSHServiceServer { s := &server{ + locator: locator, uploadPackRequestTimeout: defaultUploadPackRequestTimeout, uploadArchiveRequestTimeout: defaultUploadArchiveRequestTimeout, packfileNegotiationMetrics: prometheus.NewCounterVec( diff --git a/internal/gitaly/service/ssh/testhelper_test.go b/internal/gitaly/service/ssh/testhelper_test.go index 6884fa3b9..a2f71bcfd 100644 --- a/internal/gitaly/service/ssh/testhelper_test.go +++ b/internal/gitaly/service/ssh/testhelper_test.go @@ -55,7 +55,7 @@ func testMain(m *testing.M) int { func runSSHServer(t *testing.T, serverOpts ...ServerOpt) (string, func()) { srv := testhelper.NewServer(t, nil, nil) - gitalypb.RegisterSSHServiceServer(srv.GrpcServer(), NewServer(serverOpts...)) + gitalypb.RegisterSSHServiceServer(srv.GrpcServer(), NewServer(config.NewLocator(config.Config), serverOpts...)) reflection.Register(srv.GrpcServer()) require.NoError(t, srv.Start()) diff --git a/internal/gitaly/service/ssh/upload_archive.go b/internal/gitaly/service/ssh/upload_archive.go index ec61468a1..bf7cd47af 100644 --- a/internal/gitaly/service/ssh/upload_archive.go +++ b/internal/gitaly/service/ssh/upload_archive.go @@ -32,7 +32,7 @@ func (s *server) sshUploadArchive(stream gitalypb.SSHService_SSHUploadArchiveSer ctx, cancelCtx := context.WithCancel(stream.Context()) defer cancelCtx() - repoPath, err := helper.GetRepoPath(req.Repository) + repoPath, err := s.locator.GetRepoPath(req.Repository) if err != nil { return err } diff --git a/internal/gitaly/service/ssh/upload_pack.go b/internal/gitaly/service/ssh/upload_pack.go index 91cb456e3..372fb03f0 100644 --- a/internal/gitaly/service/ssh/upload_pack.go +++ b/internal/gitaly/service/ssh/upload_pack.go @@ -69,7 +69,7 @@ func (s *server) sshUploadPack(stream gitalypb.SSHService_SSHUploadPackServer, r env := git.AddGitProtocolEnv(ctx, req, command.GitEnv) - repoPath, err := helper.GetRepoPath(req.Repository) + repoPath, err := s.locator.GetRepoPath(req.Repository) if err != nil { return err } diff --git a/internal/helper/storage.go b/internal/helper/storage.go index 254e51fa6..1ae7985ab 100644 --- a/internal/helper/storage.go +++ b/internal/helper/storage.go @@ -11,11 +11,15 @@ import ( "google.golang.org/grpc/metadata" ) +// ErrEmptyMetadata indicates that the gRPC metadata was not found in the +// context +var ErrEmptyMetadata = errors.New("empty metadata") + // ExtractGitalyServers extracts `storage.GitalyServers` from an incoming context. func ExtractGitalyServers(ctx context.Context) (gitalyServersInfo storage.GitalyServers, err error) { md, ok := metadata.FromIncomingContext(ctx) if !ok { - return nil, fmt.Errorf("empty metadata") + return nil, ErrEmptyMetadata } gitalyServersJSONEncoded := md["gitaly-servers"] @@ -60,6 +64,16 @@ func IncomingToOutgoing(ctx context.Context) context.Context { return metadata.NewOutgoingContext(ctx, md) } +// OutgoingToIncoming creates an incoming context out of an outgoing context with the same storage metadata +func OutgoingToIncoming(ctx context.Context) context.Context { + md, ok := metadata.FromOutgoingContext(ctx) + if !ok { + return ctx + } + + return metadata.NewIncomingContext(ctx, md) +} + // InjectGitalyServers injects gitaly-servers metadata into an outgoing context func InjectGitalyServers(ctx context.Context, name, address, token string) (context.Context, error) { gitalyServers := storage.GitalyServers{ diff --git a/internal/helper/storage_test.go b/internal/helper/storage_test.go index 44e49b218..91d749e9b 100644 --- a/internal/helper/storage_test.go +++ b/internal/helper/storage_test.go @@ -97,3 +97,19 @@ func TestInjectGitalyServers(t *testing.T) { require.Equal(t, []string{"bar"}, md["foo"]) }) } + +func TestOutgoingToIncoming(t *testing.T) { + ctx := context.Background() + ctx, err := InjectGitalyServers(ctx, "a", "b", "c") + require.NoError(t, err) + + _, err = ExtractGitalyServer(ctx, "a") + require.Equal(t, ErrEmptyMetadata, err, + "server should not be found in the incoming context") + + ctx = OutgoingToIncoming(ctx) + + info, err := ExtractGitalyServer(ctx, "a") + require.NoError(t, err) + require.Equal(t, storage.ServerInfo{Address: "b", Token: "c"}, info) +} diff --git a/internal/praefect/helper_test.go b/internal/praefect/helper_test.go index 499df18d8..960185cf4 100644 --- a/internal/praefect/helper_test.go +++ b/internal/praefect/helper_test.go @@ -14,9 +14,6 @@ import ( gconfig "gitlab.com/gitlab-org/gitaly/internal/gitaly/config" internalauth "gitlab.com/gitlab-org/gitaly/internal/gitaly/config/auth" "gitlab.com/gitlab-org/gitaly/internal/gitaly/server/auth" - "gitlab.com/gitlab-org/gitaly/internal/gitaly/service/internalgitaly" - "gitlab.com/gitlab-org/gitaly/internal/gitaly/service/repository" - gitalyserver "gitlab.com/gitlab-org/gitaly/internal/gitaly/service/server" "gitlab.com/gitlab-org/gitaly/internal/log" "gitlab.com/gitlab-org/gitaly/internal/praefect/config" "gitlab.com/gitlab-org/gitaly/internal/praefect/datastore" @@ -27,7 +24,7 @@ import ( "gitlab.com/gitlab-org/gitaly/internal/praefect/transactions" "gitlab.com/gitlab-org/gitaly/internal/testhelper" "gitlab.com/gitlab-org/gitaly/internal/testhelper/promtest" - "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" + "gitlab.com/gitlab-org/gitaly/internal/testhelper/testserver" correlation "gitlab.com/gitlab-org/labkit/correlation/grpc" "google.golang.org/grpc" "google.golang.org/grpc/health" @@ -144,7 +141,7 @@ func flattenVirtualStoragesToStoragePath(virtualStorages []*config.VirtualStorag func withRealGitalyShared(t testing.TB) func([]*config.VirtualStorage) []testhelper.Cleanup { return func(virtualStorages []*config.VirtualStorage) []testhelper.Cleanup { gStorages := flattenVirtualStoragesToStoragePath(virtualStorages, testhelper.GitlabTestStoragePath()) - _, backendAddr, cleanupGitaly := runInternalGitalyServer(t, gStorages, virtualStorages[0].Nodes[0].Token) + _, backendAddr, cleanupGitaly := testserver.RunInternalGitalyServer(t, gStorages, virtualStorages[0].Nodes[0].Token) for _, vs := range virtualStorages { for i, node := range vs.Nodes { @@ -263,66 +260,6 @@ func runPraefectServer(t testing.TB, conf config.Config, opt buildOptions) (*grp return cc, prf, cleanup } -// partialGitaly is a subset of Gitaly's behavior needed to properly test -// Praefect -type partialGitaly interface { - gitalypb.ServerServiceServer - gitalypb.RepositoryServiceServer - gitalypb.InternalGitalyServer - healthpb.HealthServer -} - -func registerGitalyServices(server *grpc.Server, pg partialGitaly) { - gitalypb.RegisterServerServiceServer(server, pg) - gitalypb.RegisterRepositoryServiceServer(server, pg) - gitalypb.RegisterInternalGitalyServer(server, pg) - healthpb.RegisterHealthServer(server, pg) -} - -func realGitaly(storages []gconfig.Storage, authToken, internalSocketPath string) partialGitaly { - return struct { - gitalypb.ServerServiceServer - gitalypb.RepositoryServiceServer - gitalypb.InternalGitalyServer - healthpb.HealthServer - }{ - gitalyserver.NewServer(storages), - repository.NewServer(RubyServer, gconfig.NewLocator(gconfig.Config), internalSocketPath), - internalgitaly.NewServer(gconfig.Config.Storages), - health.NewServer(), - } -} - -func runInternalGitalyServer(t testing.TB, storages []gconfig.Storage, token string) (*grpc.Server, string, func()) { - streamInt := []grpc.StreamServerInterceptor{auth.StreamServerInterceptor(internalauth.Config{Token: token})} - unaryInt := []grpc.UnaryServerInterceptor{auth.UnaryServerInterceptor(internalauth.Config{Token: token})} - - server := testhelper.NewTestGrpcServer(t, streamInt, unaryInt) - serverSocketPath := testhelper.GetTemporaryGitalySocketFileName() - - listener, err := net.Listen("unix", serverSocketPath) - require.NoError(t, err) - - internalSocketPath := gconfig.GitalyInternalSocketPath() - internalListener, err := net.Listen("unix", internalSocketPath) - require.NoError(t, err) - - registerGitalyServices(server, realGitaly(storages, token, internalSocketPath)) - - errQ := make(chan error) - - go func() { errQ <- server.Serve(listener) }() - go func() { errQ <- server.Serve(internalListener) }() - - cleanup := func() { - server.Stop() - require.NoError(t, <-errQ) - require.NoError(t, <-errQ) - } - - return server, "unix://" + serverSocketPath, cleanup -} - func mustLoadProtoReg(t testing.TB) *descriptor.FileDescriptorProto { gz, _ := (*mock.SimpleRequest)(nil).Descriptor() fd, err := protoregistry.ExtractFileDescriptor(gz) diff --git a/internal/praefect/replicator_test.go b/internal/praefect/replicator_test.go index 994c51f69..0e531e151 100644 --- a/internal/praefect/replicator_test.go +++ b/internal/praefect/replicator_test.go @@ -979,7 +979,7 @@ func newReplicationService(tb testing.TB) (*grpc.Server, string) { gitalypb.RegisterRepositoryServiceServer(svr, repository.NewServer(RubyServer, locator, internalSocketName)) gitalypb.RegisterObjectPoolServiceServer(svr, objectpoolservice.NewServer(locator)) gitalypb.RegisterRemoteServiceServer(svr, remote.NewServer(RubyServer, locator)) - gitalypb.RegisterSSHServiceServer(svr, ssh.NewServer()) + gitalypb.RegisterSSHServiceServer(svr, ssh.NewServer(locator)) gitalypb.RegisterRefServiceServer(svr, ref.NewServer(locator)) healthpb.RegisterHealthServer(svr, health.NewServer()) reflection.Register(svr) diff --git a/internal/testhelper/testserver/gitaly.go b/internal/testhelper/testserver/gitaly.go new file mode 100644 index 000000000..be38eba67 --- /dev/null +++ b/internal/testhelper/testserver/gitaly.go @@ -0,0 +1,89 @@ +package testserver + +import ( + "net" + "testing" + + "github.com/stretchr/testify/require" + "gitlab.com/gitlab-org/gitaly/internal/gitaly/config" + internalauth "gitlab.com/gitlab-org/gitaly/internal/gitaly/config/auth" + "gitlab.com/gitlab-org/gitaly/internal/gitaly/rubyserver" + "gitlab.com/gitlab-org/gitaly/internal/gitaly/server/auth" + "gitlab.com/gitlab-org/gitaly/internal/gitaly/service/commit" + "gitlab.com/gitlab-org/gitaly/internal/gitaly/service/internalgitaly" + "gitlab.com/gitlab-org/gitaly/internal/gitaly/service/repository" + gitalyserver "gitlab.com/gitlab-org/gitaly/internal/gitaly/service/server" + "gitlab.com/gitlab-org/gitaly/internal/testhelper" + "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" + "google.golang.org/grpc" + "google.golang.org/grpc/health" + healthpb "google.golang.org/grpc/health/grpc_health_v1" +) + +var RubyServer = &rubyserver.Server{} + +// PartialGitaly is a subset of Gitaly's behavior +type PartialGitaly interface { + gitalypb.ServerServiceServer + gitalypb.RepositoryServiceServer + gitalypb.InternalGitalyServer + gitalypb.CommitServiceServer + healthpb.HealthServer +} + +func registerGitalyServices(server *grpc.Server, pg PartialGitaly) { + gitalypb.RegisterServerServiceServer(server, pg) + gitalypb.RegisterRepositoryServiceServer(server, pg) + gitalypb.RegisterInternalGitalyServer(server, pg) + gitalypb.RegisterCommitServiceServer(server, pg) + healthpb.RegisterHealthServer(server, pg) +} + +// RealGitaly instantiates an instance of PartialGitaly that uses the real world +// services. This is intended to be used in integration tests to validate +// Gitaly services. +func RealGitaly(storages []config.Storage, authToken, internalSocketPath string) PartialGitaly { + return struct { + gitalypb.ServerServiceServer + gitalypb.RepositoryServiceServer + gitalypb.InternalGitalyServer + gitalypb.CommitServiceServer + healthpb.HealthServer + }{ + gitalyserver.NewServer(storages), + repository.NewServer(RubyServer, config.NewLocator(config.Config), internalSocketPath), + internalgitaly.NewServer(config.Config.Storages), + commit.NewServer(config.NewLocator(config.Config)), + health.NewServer(), + } +} + +func RunInternalGitalyServer(t testing.TB, storages []config.Storage, token string) (*grpc.Server, string, func()) { + streamInt := []grpc.StreamServerInterceptor{auth.StreamServerInterceptor(internalauth.Config{Token: token})} + unaryInt := []grpc.UnaryServerInterceptor{auth.UnaryServerInterceptor(internalauth.Config{Token: token})} + + server := testhelper.NewTestGrpcServer(t, streamInt, unaryInt) + serverSocketPath := testhelper.GetTemporaryGitalySocketFileName() + + listener, err := net.Listen("unix", serverSocketPath) + require.NoError(t, err) + + internalSocketPath := config.GitalyInternalSocketPath() + internalListener, err := net.Listen("unix", internalSocketPath) + require.NoError(t, err) + + registerGitalyServices(server, RealGitaly(storages, token, internalSocketPath)) + + errQ := make(chan error) + + go func() { errQ <- server.Serve(listener) }() + go func() { errQ <- server.Serve(internalListener) }() + + cleanup := func() { + server.Stop() + require.NoError(t, <-errQ) + require.NoError(t, <-errQ) + } + + return server, "unix://" + serverSocketPath, cleanup +} diff --git a/proto/go/gitalypb/hook.pb.go b/proto/go/gitalypb/hook.pb.go index 4c24f2859..5adab06d0 100644 --- a/proto/go/gitalypb/hook.pb.go +++ b/proto/go/gitalypb/hook.pb.go @@ -24,6 +24,34 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package +type ReferenceTransactionHookRequest_State int32 + +const ( + ReferenceTransactionHookRequest_PREPARED ReferenceTransactionHookRequest_State = 0 + ReferenceTransactionHookRequest_COMMITTED ReferenceTransactionHookRequest_State = 1 + ReferenceTransactionHookRequest_ABORTED ReferenceTransactionHookRequest_State = 2 +) + +var ReferenceTransactionHookRequest_State_name = map[int32]string{ + 0: "PREPARED", + 1: "COMMITTED", + 2: "ABORTED", +} + +var ReferenceTransactionHookRequest_State_value = map[string]int32{ + "PREPARED": 0, + "COMMITTED": 1, + "ABORTED": 2, +} + +func (x ReferenceTransactionHookRequest_State) String() string { + return proto.EnumName(ReferenceTransactionHookRequest_State_name, int32(x)) +} + +func (ReferenceTransactionHookRequest_State) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_3eef30da1c11ee1b, []int{6, 0} +} + type PreReceiveHookRequest struct { Repository *Repository `protobuf:"bytes,1,opt,name=repository,proto3" json:"repository,omitempty"` EnvironmentVariables []string `protobuf:"bytes,2,rep,name=environment_variables,json=environmentVariables,proto3" json:"environment_variables,omitempty"` @@ -387,12 +415,13 @@ func (m *UpdateHookResponse) GetExitStatus() *ExitStatus { } type ReferenceTransactionHookRequest struct { - Repository *Repository `protobuf:"bytes,1,opt,name=repository,proto3" json:"repository,omitempty"` - EnvironmentVariables []string `protobuf:"bytes,2,rep,name=environment_variables,json=environmentVariables,proto3" json:"environment_variables,omitempty"` - Stdin []byte `protobuf:"bytes,3,opt,name=stdin,proto3" json:"stdin,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Repository *Repository `protobuf:"bytes,1,opt,name=repository,proto3" json:"repository,omitempty"` + EnvironmentVariables []string `protobuf:"bytes,2,rep,name=environment_variables,json=environmentVariables,proto3" json:"environment_variables,omitempty"` + Stdin []byte `protobuf:"bytes,3,opt,name=stdin,proto3" json:"stdin,omitempty"` + State ReferenceTransactionHookRequest_State `protobuf:"varint,4,opt,name=state,proto3,enum=gitaly.ReferenceTransactionHookRequest_State" json:"state,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *ReferenceTransactionHookRequest) Reset() { *m = ReferenceTransactionHookRequest{} } @@ -441,6 +470,13 @@ func (m *ReferenceTransactionHookRequest) GetStdin() []byte { return nil } +func (m *ReferenceTransactionHookRequest) GetState() ReferenceTransactionHookRequest_State { + if m != nil { + return m.State + } + return ReferenceTransactionHookRequest_PREPARED +} + type ReferenceTransactionHookResponse struct { Stdout []byte `protobuf:"bytes,1,opt,name=stdout,proto3" json:"stdout,omitempty"` Stderr []byte `protobuf:"bytes,2,opt,name=stderr,proto3" json:"stderr,omitempty"` @@ -497,6 +533,7 @@ func (m *ReferenceTransactionHookResponse) GetExitStatus() *ExitStatus { } func init() { + proto.RegisterEnum("gitaly.ReferenceTransactionHookRequest_State", ReferenceTransactionHookRequest_State_name, ReferenceTransactionHookRequest_State_value) proto.RegisterType((*PreReceiveHookRequest)(nil), "gitaly.PreReceiveHookRequest") proto.RegisterType((*PreReceiveHookResponse)(nil), "gitaly.PreReceiveHookResponse") proto.RegisterType((*PostReceiveHookRequest)(nil), "gitaly.PostReceiveHookRequest") @@ -510,41 +547,44 @@ func init() { func init() { proto.RegisterFile("hook.proto", fileDescriptor_3eef30da1c11ee1b) } var fileDescriptor_3eef30da1c11ee1b = []byte{ - // 529 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x55, 0xcd, 0x6e, 0x13, 0x31, - 0x10, 0x96, 0xf3, 0xa7, 0x66, 0x12, 0x95, 0x62, 0xb5, 0x61, 0x59, 0x04, 0x8d, 0xf6, 0xc2, 0x1e, - 0x20, 0x89, 0x9a, 0x0b, 0xe7, 0x4a, 0x48, 0x5c, 0x10, 0x91, 0x0b, 0x3d, 0x80, 0x44, 0xe4, 0x64, - 0xa7, 0x89, 0xd5, 0xad, 0xbd, 0xd8, 0xde, 0xb4, 0x39, 0xc0, 0x95, 0x17, 0x40, 0x82, 0x27, 0xe0, - 0x51, 0xca, 0x8d, 0x07, 0xe2, 0x84, 0x76, 0x37, 0xbf, 0x4d, 0x42, 0x6f, 0xa1, 0xb7, 0x99, 0xf9, - 0x3c, 0x5f, 0xf2, 0x7d, 0x1e, 0xcf, 0x02, 0x0c, 0x95, 0x3a, 0x6f, 0x44, 0x5a, 0x59, 0x45, 0x4b, - 0x03, 0x61, 0x79, 0x38, 0x76, 0x21, 0x14, 0xd2, 0x66, 0x35, 0xb7, 0x6a, 0x86, 0x5c, 0x63, 0x90, - 0x65, 0xde, 0x35, 0x81, 0x83, 0x8e, 0x46, 0x86, 0x7d, 0x14, 0x23, 0x7c, 0xa5, 0xd4, 0x39, 0xc3, - 0x4f, 0x31, 0x1a, 0x4b, 0x5f, 0x00, 0x68, 0x8c, 0x94, 0x11, 0x56, 0xe9, 0xb1, 0x43, 0xea, 0xc4, - 0xaf, 0x1c, 0xd1, 0x46, 0x46, 0xd8, 0x60, 0x33, 0xe4, 0xb8, 0xf0, 0xe3, 0xfa, 0x19, 0x61, 0x0b, - 0x67, 0x69, 0x1b, 0x0e, 0x50, 0x8e, 0x84, 0x56, 0xf2, 0x02, 0xa5, 0xed, 0x8e, 0xb8, 0x16, 0xbc, - 0x17, 0xa2, 0x71, 0x72, 0xf5, 0xbc, 0x5f, 0x66, 0xfb, 0x0b, 0xe0, 0xe9, 0x14, 0xa3, 0xfb, 0x50, - 0x34, 0x36, 0x10, 0xd2, 0x29, 0xd4, 0x89, 0x5f, 0x65, 0x59, 0x42, 0x7d, 0xd8, 0x1b, 0x08, 0xdb, - 0x8d, 0x62, 0x33, 0xec, 0xaa, 0xc8, 0x0a, 0x25, 0x8d, 0x53, 0x4c, 0x59, 0x76, 0x07, 0xc2, 0x76, - 0x62, 0x33, 0x7c, 0x93, 0x55, 0xbd, 0xcf, 0x50, 0xbb, 0xa9, 0xc3, 0x44, 0x4a, 0x1a, 0xa4, 0x35, - 0x28, 0x19, 0x1b, 0xa8, 0xd8, 0xa6, 0x22, 0xaa, 0x6c, 0x92, 0x4d, 0xea, 0xa8, 0xb5, 0x93, 0x9b, - 0xd5, 0x51, 0x6b, 0xda, 0x86, 0x0a, 0x5e, 0x09, 0xdb, 0x35, 0x96, 0xdb, 0xd8, 0x38, 0xf9, 0x65, - 0xe5, 0x2f, 0xaf, 0x84, 0x3d, 0x49, 0x11, 0x06, 0x38, 0x8b, 0xbd, 0x5f, 0x04, 0x6a, 0x1d, 0x65, - 0xec, 0x1d, 0x32, 0x32, 0x7f, 0x9b, 0x91, 0x85, 0xb5, 0x46, 0x7e, 0x81, 0x07, 0x2b, 0x42, 0xb6, - 0xe9, 0xe4, 0x6f, 0x02, 0xf7, 0xdf, 0x45, 0x01, 0xb7, 0xff, 0xd3, 0xc4, 0x3d, 0xc8, 0x6b, 0x3c, - 0x9b, 0x58, 0x98, 0x84, 0xf4, 0x11, 0x94, 0x55, 0x18, 0x74, 0x47, 0x3c, 0x8c, 0x31, 0x9d, 0xd1, - 0x32, 0xdb, 0x51, 0x61, 0x70, 0x9a, 0xe4, 0x09, 0x28, 0xf1, 0x72, 0x02, 0x16, 0x33, 0x50, 0xe2, - 0x65, 0x0a, 0x7a, 0x63, 0xa0, 0x8b, 0x7a, 0xb6, 0xe9, 0xe5, 0x4f, 0x02, 0x87, 0x0c, 0xcf, 0x50, - 0xa3, 0xec, 0xe3, 0x5b, 0xcd, 0xa5, 0xe1, 0xfd, 0xe4, 0x96, 0xef, 0xda, 0x78, 0x7a, 0x5f, 0x09, - 0xd4, 0x37, 0xff, 0xd1, 0x2d, 0x5a, 0x76, 0xf4, 0x2d, 0x0f, 0x95, 0xe4, 0x57, 0x4f, 0x50, 0x8f, - 0x44, 0x1f, 0xe9, 0x07, 0xd8, 0x5d, 0xde, 0x2b, 0xf4, 0xf1, 0x94, 0x61, 0xed, 0xde, 0x74, 0x9f, - 0x6c, 0x82, 0x33, 0x15, 0x5e, 0xe9, 0xcf, 0x77, 0x3f, 0xb7, 0x93, 0xf3, 0x49, 0x8b, 0xd0, 0x8f, - 0x70, 0xef, 0xc6, 0x5b, 0xa3, 0xf3, 0xf6, 0xb5, 0xdb, 0xc4, 0x3d, 0xdc, 0x88, 0xaf, 0xe1, 0x7f, - 0x0d, 0x30, 0x1f, 0x3d, 0xfa, 0x70, 0xda, 0xba, 0xf2, 0xbc, 0x5c, 0x77, 0x1d, 0xb4, 0x4c, 0xd8, - 0x22, 0x74, 0x0c, 0xce, 0xa6, 0x4b, 0xa2, 0x4f, 0xe7, 0x23, 0xf3, 0xcf, 0x79, 0x73, 0xfd, 0xdb, - 0x0f, 0xae, 0x2a, 0x39, 0x6e, 0xbd, 0x4f, 0xda, 0x42, 0xde, 0x6b, 0xf4, 0xd5, 0x45, 0x33, 0x0b, - 0x9f, 0x2b, 0x3d, 0x68, 0x66, 0x64, 0xcd, 0xf4, 0x6b, 0xd6, 0x1c, 0xa8, 0x49, 0x1e, 0xf5, 0x7a, - 0xa5, 0xb4, 0xd4, 0xfe, 0x1b, 0x00, 0x00, 0xff, 0xff, 0x7c, 0x57, 0x17, 0x8c, 0x10, 0x07, 0x00, - 0x00, + // 591 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x55, 0x4f, 0x6f, 0xd3, 0x4e, + 0x10, 0xfd, 0xad, 0xd3, 0xe4, 0xd7, 0x4c, 0x42, 0x09, 0xab, 0xb6, 0x18, 0x23, 0x68, 0xe4, 0x0b, + 0x3e, 0xd0, 0xa4, 0xb4, 0x17, 0xae, 0xfd, 0x13, 0x09, 0x0e, 0x55, 0xa3, 0x6d, 0xe9, 0x01, 0x24, + 0x22, 0x27, 0x9e, 0x26, 0xab, 0xba, 0x5e, 0xb3, 0xbb, 0x4e, 0x9b, 0x03, 0x5c, 0xf9, 0x02, 0x48, + 0x70, 0xe2, 0xe3, 0x94, 0x1b, 0x1f, 0x88, 0x13, 0xb2, 0x9d, 0xbf, 0x4d, 0x42, 0x39, 0x85, 0xde, + 0xf6, 0xcd, 0xdb, 0x99, 0xf5, 0x7b, 0x3b, 0x3b, 0x06, 0xe8, 0x08, 0x71, 0x5e, 0x09, 0xa5, 0xd0, + 0x82, 0xe6, 0xda, 0x5c, 0xbb, 0x7e, 0xcf, 0x02, 0x9f, 0x07, 0x3a, 0x8d, 0x59, 0x45, 0xd5, 0x71, + 0x25, 0x7a, 0x29, 0xb2, 0xaf, 0x09, 0xac, 0xd5, 0x25, 0x32, 0x6c, 0x21, 0xef, 0xe2, 0x2b, 0x21, + 0xce, 0x19, 0x7e, 0x88, 0x50, 0x69, 0xfa, 0x12, 0x40, 0x62, 0x28, 0x14, 0xd7, 0x42, 0xf6, 0x4c, + 0x52, 0x26, 0x4e, 0x61, 0x9b, 0x56, 0xd2, 0x82, 0x15, 0x36, 0x64, 0xf6, 0x96, 0xbe, 0x5d, 0x3f, + 0x27, 0x6c, 0x6c, 0x2f, 0xdd, 0x81, 0x35, 0x0c, 0xba, 0x5c, 0x8a, 0xe0, 0x02, 0x03, 0xdd, 0xe8, + 0xba, 0x92, 0xbb, 0x4d, 0x1f, 0x95, 0x69, 0x94, 0x33, 0x4e, 0x9e, 0xad, 0x8e, 0x91, 0xa7, 0x03, + 0x8e, 0xae, 0x42, 0x56, 0x69, 0x8f, 0x07, 0xe6, 0x52, 0x99, 0x38, 0x45, 0x96, 0x02, 0xea, 0x40, + 0xa9, 0xcd, 0x75, 0x23, 0x8c, 0x54, 0xa7, 0x21, 0x42, 0xcd, 0x45, 0xa0, 0xcc, 0x6c, 0x52, 0x65, + 0xa5, 0xcd, 0x75, 0x3d, 0x52, 0x9d, 0xa3, 0x34, 0x6a, 0x7f, 0x84, 0xf5, 0x9b, 0x3a, 0x54, 0x28, + 0x02, 0x85, 0x74, 0x1d, 0x72, 0x4a, 0x7b, 0x22, 0xd2, 0x89, 0x88, 0x22, 0xeb, 0xa3, 0x7e, 0x1c, + 0xa5, 0x34, 0x8d, 0x61, 0x1c, 0xa5, 0xa4, 0x3b, 0x50, 0xc0, 0x2b, 0xae, 0x1b, 0x4a, 0xbb, 0x3a, + 0x52, 0x66, 0x66, 0x52, 0x79, 0xed, 0x8a, 0xeb, 0xe3, 0x84, 0x61, 0x80, 0xc3, 0xb5, 0xfd, 0x83, + 0xc0, 0x7a, 0x5d, 0x28, 0x7d, 0x87, 0x8c, 0xcc, 0xdc, 0x66, 0xe4, 0xd2, 0x4c, 0x23, 0x3f, 0xc1, + 0xc3, 0x29, 0x21, 0x8b, 0x74, 0xf2, 0x27, 0x81, 0x07, 0x6f, 0x42, 0xcf, 0xd5, 0xff, 0xd2, 0xc4, + 0x12, 0x64, 0x24, 0x9e, 0xf5, 0x2d, 0x8c, 0x97, 0xf4, 0x31, 0xe4, 0x85, 0xef, 0x35, 0xba, 0xae, + 0x1f, 0x61, 0xd2, 0xa3, 0x79, 0xb6, 0x2c, 0x7c, 0xef, 0x34, 0xc6, 0x31, 0x19, 0xe0, 0x65, 0x9f, + 0xcc, 0xa6, 0x64, 0x80, 0x97, 0x09, 0x69, 0xf7, 0x80, 0x8e, 0xeb, 0x59, 0xa4, 0x97, 0xdf, 0x0d, + 0xd8, 0x60, 0x78, 0x86, 0x12, 0x83, 0x16, 0x9e, 0x48, 0x37, 0x50, 0x6e, 0x2b, 0xbe, 0xe5, 0x3b, + 0xd7, 0x9e, 0xfb, 0x71, 0xd4, 0xd5, 0xa9, 0xb3, 0x2b, 0xdb, 0x9b, 0xa3, 0xf3, 0xff, 0xf8, 0xf1, + 0x95, 0x58, 0x27, 0xb2, 0x34, 0xd7, 0x7e, 0x01, 0xd9, 0x04, 0xd3, 0x22, 0x2c, 0xd7, 0x59, 0xad, + 0xbe, 0xcb, 0x6a, 0x07, 0xa5, 0xff, 0xe8, 0x3d, 0xc8, 0xef, 0x1f, 0x1d, 0x1e, 0xbe, 0x3e, 0x39, + 0xa9, 0x1d, 0x94, 0x08, 0x2d, 0xc0, 0xff, 0xbb, 0x7b, 0x47, 0x2c, 0x06, 0x86, 0xfd, 0x99, 0x40, + 0x79, 0xfe, 0x19, 0x0b, 0xbc, 0xaa, 0xed, 0x2f, 0x19, 0x28, 0xc4, 0xa7, 0x1e, 0xa3, 0xec, 0xf2, + 0x16, 0xd2, 0x77, 0xb0, 0x32, 0x39, 0xcf, 0xe8, 0x93, 0x41, 0x85, 0x99, 0xf3, 0xda, 0x7a, 0x3a, + 0x8f, 0x4e, 0x55, 0xd8, 0xb9, 0x5f, 0x5f, 0x1d, 0x63, 0xd9, 0x70, 0xc8, 0x16, 0xa1, 0xef, 0xe1, + 0xfe, 0x8d, 0x37, 0x4e, 0x47, 0xe9, 0x33, 0xa7, 0x98, 0xb5, 0x31, 0x97, 0x9f, 0x51, 0xff, 0x10, + 0x60, 0xd4, 0xf2, 0xf4, 0xd1, 0x20, 0x75, 0xea, 0x59, 0x5b, 0xd6, 0x2c, 0x6a, 0xb2, 0xe0, 0x16, + 0xa1, 0x3d, 0x30, 0xe7, 0x5d, 0x12, 0x7d, 0xf6, 0x97, 0xad, 0x62, 0x39, 0xb7, 0x6f, 0x9c, 0x56, + 0xb2, 0xb7, 0xf5, 0x36, 0x4e, 0xf3, 0xdd, 0x66, 0xa5, 0x25, 0x2e, 0xaa, 0xe9, 0x72, 0x53, 0xc8, + 0x76, 0x35, 0x2d, 0x56, 0x4d, 0xfe, 0xa2, 0xd5, 0xb6, 0xe8, 0xe3, 0xb0, 0xd9, 0xcc, 0x25, 0xa1, + 0x9d, 0xdf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x1e, 0xa9, 0xde, 0x88, 0x88, 0x07, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/proto/hook.proto b/proto/hook.proto index ef31a6616..0a67247fb 100644 --- a/proto/hook.proto +++ b/proto/hook.proto @@ -74,6 +74,12 @@ message ReferenceTransactionHookRequest { Repository repository = 1 [(target_repository)=true]; repeated string environment_variables = 2; bytes stdin = 3; + enum State { + PREPARED = 0; + COMMITTED = 1; + ABORTED = 2; + } + State state = 4; } message ReferenceTransactionHookResponse { diff --git a/ruby/proto/gitaly/hook_pb.rb b/ruby/proto/gitaly/hook_pb.rb index 616b061f7..bf32ab7ec 100644 --- a/ruby/proto/gitaly/hook_pb.rb +++ b/ruby/proto/gitaly/hook_pb.rb @@ -45,6 +45,12 @@ Google::Protobuf::DescriptorPool.generated_pool.build do optional :repository, :message, 1, "gitaly.Repository" repeated :environment_variables, :string, 2 optional :stdin, :bytes, 3 + optional :state, :enum, 4, "gitaly.ReferenceTransactionHookRequest.State" + end + add_enum "gitaly.ReferenceTransactionHookRequest.State" do + value :PREPARED, 0 + value :COMMITTED, 1 + value :ABORTED, 2 end add_message "gitaly.ReferenceTransactionHookResponse" do optional :stdout, :bytes, 1 @@ -62,5 +68,6 @@ module Gitaly UpdateHookRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gitaly.UpdateHookRequest").msgclass UpdateHookResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gitaly.UpdateHookResponse").msgclass ReferenceTransactionHookRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gitaly.ReferenceTransactionHookRequest").msgclass + ReferenceTransactionHookRequest::State = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gitaly.ReferenceTransactionHookRequest.State").enummodule ReferenceTransactionHookResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gitaly.ReferenceTransactionHookResponse").msgclass end |