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:
authorPatrick Steinhardt <psteinhardt@gitlab.com>2020-10-15 15:16:34 +0300
committerPatrick Steinhardt <psteinhardt@gitlab.com>2020-10-15 15:16:34 +0300
commit67bb4e402505ce3fdf151db638a992545a858d82 (patch)
tree5c1e95e9f3f0ccb117dec6e63f0dd579fece5687
parentedc556107506a698e6bf95110d458e9eb07d311f (diff)
parent2f2e703cf4a535ed8b007b3d6123751a9a2baf78 (diff)
Merge branch 'pks-go-list-conflict-files' into 'master'
conflicts: Port ListConflictFiles to Go Closes #3060 See merge request gitlab-org/gitaly!2598
-rw-r--r--changelogs/unreleased/pks-go-list-conflict-files.yml5
-rw-r--r--cmd/gitaly-git2go/conflicts.go66
-rw-r--r--cmd/gitaly-git2go/conflicts_test.go63
-rw-r--r--cmd/gitaly-git2go/util.go6
-rw-r--r--internal/git2go/conflicts.go37
-rw-r--r--internal/git2go/conflicts_test.go16
-rw-r--r--internal/gitaly/service/conflicts/list_conflict_files.go123
-rw-r--r--internal/gitaly/service/conflicts/list_conflict_files_test.go120
-rw-r--r--internal/gitaly/service/conflicts/server.go14
-rw-r--r--internal/gitaly/service/conflicts/testhelper_test.go5
-rw-r--r--internal/gitaly/service/register.go2
-rw-r--r--internal/metadata/featureflag/feature_flags.go3
12 files changed, 380 insertions, 80 deletions
diff --git a/changelogs/unreleased/pks-go-list-conflict-files.yml b/changelogs/unreleased/pks-go-list-conflict-files.yml
new file mode 100644
index 000000000..bbbb37487
--- /dev/null
+++ b/changelogs/unreleased/pks-go-list-conflict-files.yml
@@ -0,0 +1,5 @@
+---
+title: 'conflicts: Port ListConflictFiles to Go'
+merge_request: 2598
+author:
+type: performance
diff --git a/cmd/gitaly-git2go/conflicts.go b/cmd/gitaly-git2go/conflicts.go
index 2a96c34a7..f2ba28abd 100644
--- a/cmd/gitaly-git2go/conflicts.go
+++ b/cmd/gitaly-git2go/conflicts.go
@@ -10,6 +10,9 @@ import (
git "github.com/libgit2/git2go/v30"
"gitlab.com/gitlab-org/gitaly/internal/git2go"
+ "gitlab.com/gitlab-org/gitaly/internal/helper"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/status"
)
type conflictsSubcommand struct {
@@ -22,14 +25,17 @@ func (cmd *conflictsSubcommand) Flags() *flag.FlagSet {
return flags
}
-func indexEntryPath(entry *git.IndexEntry) string {
+func conflictEntryFromIndex(entry *git.IndexEntry) git2go.ConflictEntry {
if entry == nil {
- return ""
+ return git2go.ConflictEntry{}
+ }
+ return git2go.ConflictEntry{
+ Path: entry.Path,
+ Mode: int32(entry.Mode),
}
- return entry.Path
}
-func conflictContent(repo *git.Repository, conflict git.IndexConflict) (string, error) {
+func conflictContent(repo *git.Repository, conflict git.IndexConflict) ([]byte, error) {
var ancestor, our, their git.MergeFileInput
for entry, input := range map[*git.IndexEntry]*git.MergeFileInput{
@@ -43,7 +49,7 @@ func conflictContent(repo *git.Repository, conflict git.IndexConflict) (string,
blob, err := repo.LookupBlob(entry.Id)
if err != nil {
- return "", fmt.Errorf("could not get conflicting blob: %w", err)
+ return nil, helper.ErrPreconditionFailedf("could not get conflicting blob: %w", err)
}
input.Path = entry.Path
@@ -53,10 +59,25 @@ func conflictContent(repo *git.Repository, conflict git.IndexConflict) (string,
merge, err := git.MergeFile(ancestor, our, their, nil)
if err != nil {
- return "", fmt.Errorf("could not compute conflicts: %w", err)
+ return nil, fmt.Errorf("could not compute conflicts: %w", err)
}
- return string(merge.Contents), nil
+ return merge.Contents, nil
+}
+
+func conflictError(code codes.Code, message string) error {
+ result := git2go.ConflictsResult{
+ Error: git2go.ConflictError{
+ Code: code,
+ Message: message,
+ },
+ }
+
+ if err := result.SerializeTo(os.Stdout); err != nil {
+ return err
+ }
+
+ return nil
}
// Run performs a merge and prints resulting conflicts to stdout.
@@ -71,19 +92,29 @@ func (cmd *conflictsSubcommand) Run() error {
return fmt.Errorf("could not open repository: %w", err)
}
- ours, err := lookupCommit(repo, request.Ours)
+ oursOid, err := git.NewOid(request.Ours)
if err != nil {
- return fmt.Errorf("could not lookup commit %q: %w", request.Ours, err)
+ return err
}
- theirs, err := lookupCommit(repo, request.Theirs)
+ ours, err := repo.LookupCommit(oursOid)
if err != nil {
- return fmt.Errorf("could not lookup commit %q: %w", request.Theirs, err)
+ return err
+ }
+
+ theirsOid, err := git.NewOid(request.Theirs)
+ if err != nil {
+ return err
+ }
+
+ theirs, err := repo.LookupCommit(theirsOid)
+ if err != nil {
+ return err
}
index, err := repo.MergeCommits(ours, theirs, nil)
if err != nil {
- return fmt.Errorf("could not merge commits: %w", err)
+ return conflictError(codes.FailedPrecondition, fmt.Sprintf("could not merge commits: %v", err))
}
conflicts, err := index.ConflictIterator()
@@ -104,14 +135,17 @@ func (cmd *conflictsSubcommand) Run() error {
content, err := conflictContent(repo, conflict)
if err != nil {
+ if status, ok := status.FromError(err); ok {
+ return conflictError(status.Code(), status.Message())
+ }
return err
}
result.Conflicts = append(result.Conflicts, git2go.Conflict{
- AncestorPath: indexEntryPath(conflict.Ancestor),
- OurPath: indexEntryPath(conflict.Our),
- TheirPath: indexEntryPath(conflict.Their),
- Content: content,
+ Ancestor: conflictEntryFromIndex(conflict.Ancestor),
+ Our: conflictEntryFromIndex(conflict.Our),
+ Their: conflictEntryFromIndex(conflict.Their),
+ Content: content,
})
}
diff --git a/cmd/gitaly-git2go/conflicts_test.go b/cmd/gitaly-git2go/conflicts_test.go
index e1310bc1e..4cef16c12 100644
--- a/cmd/gitaly-git2go/conflicts_test.go
+++ b/cmd/gitaly-git2go/conflicts_test.go
@@ -45,10 +45,10 @@ func TestConflicts(t *testing.T) {
},
conflicts: []git2go.Conflict{
{
- AncestorPath: "file",
- OurPath: "file",
- TheirPath: "file",
- Content: "<<<<<<< file\nb\n=======\nc\n>>>>>>> file\n",
+ Ancestor: git2go.ConflictEntry{Path: "file", Mode: 0100644},
+ Our: git2go.ConflictEntry{Path: "file", Mode: 0100644},
+ Their: git2go.ConflictEntry{Path: "file", Mode: 0100644},
+ Content: []byte("<<<<<<< file\nb\n=======\nc\n>>>>>>> file\n"),
},
},
},
@@ -68,10 +68,10 @@ func TestConflicts(t *testing.T) {
},
conflicts: []git2go.Conflict{
{
- AncestorPath: "file-2",
- OurPath: "file-2",
- TheirPath: "file-2",
- Content: "<<<<<<< file-2\nb\n=======\nc\n>>>>>>> file-2\n",
+ Ancestor: git2go.ConflictEntry{Path: "file-2", Mode: 0100644},
+ Our: git2go.ConflictEntry{Path: "file-2", Mode: 0100644},
+ Their: git2go.ConflictEntry{Path: "file-2", Mode: 0100644},
+ Content: []byte("<<<<<<< file-2\nb\n=======\nc\n>>>>>>> file-2\n"),
},
},
},
@@ -91,16 +91,16 @@ func TestConflicts(t *testing.T) {
},
conflicts: []git2go.Conflict{
{
- AncestorPath: "file-1",
- OurPath: "file-1",
- TheirPath: "file-1",
- Content: "<<<<<<< file-1\nb\n=======\nc\n>>>>>>> file-1\n",
+ Ancestor: git2go.ConflictEntry{Path: "file-1", Mode: 0100644},
+ Our: git2go.ConflictEntry{Path: "file-1", Mode: 0100644},
+ Their: git2go.ConflictEntry{Path: "file-1", Mode: 0100644},
+ Content: []byte("<<<<<<< file-1\nb\n=======\nc\n>>>>>>> file-1\n"),
},
{
- AncestorPath: "file-2",
- OurPath: "file-2",
- TheirPath: "file-2",
- Content: "<<<<<<< file-2\nb\n=======\nc\n>>>>>>> file-2\n",
+ Ancestor: git2go.ConflictEntry{Path: "file-2", Mode: 0100644},
+ Our: git2go.ConflictEntry{Path: "file-2", Mode: 0100644},
+ Their: git2go.ConflictEntry{Path: "file-2", Mode: 0100644},
+ Content: []byte("<<<<<<< file-2\nb\n=======\nc\n>>>>>>> file-2\n"),
},
},
},
@@ -117,10 +117,10 @@ func TestConflicts(t *testing.T) {
},
conflicts: []git2go.Conflict{
{
- AncestorPath: "file",
- OurPath: "file",
- TheirPath: "",
- Content: "<<<<<<< file\nchanged\n=======\n>>>>>>> \n",
+ Ancestor: git2go.ConflictEntry{Path: "file", Mode: 0100644},
+ Our: git2go.ConflictEntry{Path: "file", Mode: 0100644},
+ Their: git2go.ConflictEntry{},
+ Content: []byte("<<<<<<< file\nchanged\n=======\n>>>>>>> \n"),
},
},
},
@@ -140,21 +140,22 @@ func TestConflicts(t *testing.T) {
},
conflicts: []git2go.Conflict{
{
- AncestorPath: "file",
- OurPath: "",
- TheirPath: "",
+ Ancestor: git2go.ConflictEntry{Path: "file", Mode: 0100644},
+ Our: git2go.ConflictEntry{},
+ Their: git2go.ConflictEntry{},
+ Content: []byte{},
},
{
- AncestorPath: "",
- OurPath: "renamed-1",
- TheirPath: "",
- Content: "a\nb\nc\nd\ne\nf\ng\n",
+ Ancestor: git2go.ConflictEntry{},
+ Our: git2go.ConflictEntry{Path: "renamed-1", Mode: 0100644},
+ Their: git2go.ConflictEntry{},
+ Content: []byte("a\nb\nc\nd\ne\nf\ng\n"),
},
{
- AncestorPath: "",
- OurPath: "",
- TheirPath: "renamed-2",
- Content: "a\nb\nc\nd\ne\nf\ng\n",
+ Ancestor: git2go.ConflictEntry{},
+ Our: git2go.ConflictEntry{},
+ Their: git2go.ConflictEntry{Path: "renamed-2", Mode: 0100644},
+ Content: []byte("a\nb\nc\nd\ne\nf\ng\n"),
},
},
},
diff --git a/cmd/gitaly-git2go/util.go b/cmd/gitaly-git2go/util.go
index d580582cd..a315c5f3a 100644
--- a/cmd/gitaly-git2go/util.go
+++ b/cmd/gitaly-git2go/util.go
@@ -11,17 +11,17 @@ import (
func lookupCommit(repo *git.Repository, ref string) (*git.Commit, error) {
object, err := repo.RevparseSingle(ref)
if err != nil {
- return nil, fmt.Errorf("could not lookup reference: %w", err)
+ return nil, fmt.Errorf("could not lookup reference %q: %w", ref, err)
}
peeled, err := object.Peel(git.ObjectCommit)
if err != nil {
- return nil, fmt.Errorf("could not peel reference: %w", err)
+ return nil, fmt.Errorf("could not peel reference %q: %w", ref, err)
}
commit, err := peeled.AsCommit()
if err != nil {
- return nil, fmt.Errorf("could not cast to commit: %w", err)
+ return nil, fmt.Errorf("reference %q is not a commit: %w", ref, err)
}
return commit, nil
diff --git a/internal/git2go/conflicts.go b/internal/git2go/conflicts.go
index 101c260ae..c5848c618 100644
--- a/internal/git2go/conflicts.go
+++ b/internal/git2go/conflicts.go
@@ -7,6 +7,8 @@ import (
"io"
"gitlab.com/gitlab-org/gitaly/internal/gitaly/config"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/status"
)
// ConflictsCommand contains parameters to perform a merge and return its conflicts.
@@ -19,22 +21,39 @@ type ConflictsCommand struct {
Theirs string `json:"theirs"`
}
+// ConflictEntry represents a conflict entry which is one of the sides of a conflict.
+type ConflictEntry struct {
+ // Path is the path of the conflicting file.
+ Path string `json:"path"`
+ // Mode is the mode of the conflicting file.
+ Mode int32 `json:"mode"`
+}
+
// Conflict represents a merge conflict for a single file.
type Conflict struct {
- // AncestorPath is the path of the ancestor.
- AncestorPath string `json:"ancestor_path"`
- // OurPath is the path of ours.
- OurPath string `json:"our_path"`
- // TheirPath is the path of theirs.
- TheirPath string `json:"their_path"`
+ // Ancestor is the conflict entry of the merge-base.
+ Ancestor ConflictEntry `json:"ancestor"`
+ // Our is the conflict entry of ours.
+ Our ConflictEntry `json:"our"`
+ // Their is the conflict entry of theirs.
+ Their ConflictEntry `json:"their"`
// Content contains the conflicting merge results.
- Content string `json:"content"`
+ Content []byte `json:"content"`
+}
+
+type ConflictError struct {
+ // Code is the GRPC error code
+ Code codes.Code
+ // Message is the error message
+ Message string
}
// ConflictsResult contains all conflicts resulting from a merge.
type ConflictsResult struct {
// Conflicts
Conflicts []Conflict `json:"conflicts"`
+ // Error is an optional conflict error
+ Error ConflictError `json:"error"`
}
// ConflictsCommandFromSerialized constructs a ConflictsCommand from its serialized representation.
@@ -77,6 +96,10 @@ func (c ConflictsCommand) Run(ctx context.Context, cfg config.Cfg) (ConflictsRes
return ConflictsResult{}, err
}
+ if response.Error.Code != codes.OK {
+ return ConflictsResult{}, status.Error(response.Error.Code, response.Error.Message)
+ }
+
return response, nil
}
diff --git a/internal/git2go/conflicts_test.go b/internal/git2go/conflicts_test.go
index 6aeeb2903..4061598bf 100644
--- a/internal/git2go/conflicts_test.go
+++ b/internal/git2go/conflicts_test.go
@@ -86,20 +86,20 @@ func TestConflictsResult_Serialization(t *testing.T) {
serialized: serializeResult(t, ConflictsResult{
Conflicts: []Conflict{
{
- AncestorPath: "dir/ancestor",
- OurPath: "dir/our",
- TheirPath: "dir/their",
- Content: "content",
+ Ancestor: ConflictEntry{Path: "dir/ancestor", Mode: 0100644},
+ Our: ConflictEntry{Path: "dir/our", Mode: 0100644},
+ Their: ConflictEntry{Path: "dir/their", Mode: 0100644},
+ Content: []byte("content"),
},
},
}),
expected: ConflictsResult{
Conflicts: []Conflict{
{
- AncestorPath: "dir/ancestor",
- OurPath: "dir/our",
- TheirPath: "dir/their",
- Content: "content",
+ Ancestor: ConflictEntry{Path: "dir/ancestor", Mode: 0100644},
+ Our: ConflictEntry{Path: "dir/our", Mode: 0100644},
+ Their: ConflictEntry{Path: "dir/their", Mode: 0100644},
+ Content: []byte("content"),
},
},
},
diff --git a/internal/gitaly/service/conflicts/list_conflict_files.go b/internal/gitaly/service/conflicts/list_conflict_files.go
index 7499c9ab1..b1c9d8cb6 100644
--- a/internal/gitaly/service/conflicts/list_conflict_files.go
+++ b/internal/gitaly/service/conflicts/list_conflict_files.go
@@ -1,17 +1,140 @@
package conflicts
import (
+ "bytes"
+ "errors"
"fmt"
+ "io"
+ "unicode/utf8"
+ "gitlab.com/gitlab-org/gitaly/internal/git"
+ "gitlab.com/gitlab-org/gitaly/internal/git2go"
"gitlab.com/gitlab-org/gitaly/internal/gitaly/rubyserver"
+ "gitlab.com/gitlab-org/gitaly/internal/helper"
+ "gitlab.com/gitlab-org/gitaly/internal/metadata/featureflag"
"gitlab.com/gitlab-org/gitaly/proto/go/gitalypb"
+ "gitlab.com/gitlab-org/gitaly/streamio"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
+func (s *server) listConflictFiles(request *gitalypb.ListConflictFilesRequest, stream gitalypb.ConflictsService_ListConflictFilesServer) error {
+ ctx := stream.Context()
+
+ if err := validateListConflictFilesRequest(request); err != nil {
+ return helper.ErrInvalidArgument(err)
+ }
+
+ repo := git.NewRepository(request.Repository)
+
+ ours, err := repo.ResolveRefish(ctx, request.OurCommitOid+"^{commit}")
+ if err != nil {
+ return helper.ErrPreconditionFailedf("could not lookup 'our' OID: %s", err)
+ }
+
+ theirs, err := repo.ResolveRefish(ctx, request.TheirCommitOid+"^{commit}")
+ if err != nil {
+ return helper.ErrPreconditionFailedf("could not lookup 'their' OID: %s", err)
+ }
+
+ repoPath, err := s.locator.GetPath(request.Repository)
+ if err != nil {
+ return err
+ }
+
+ conflicts, err := git2go.ConflictsCommand{
+ Repository: repoPath,
+ Ours: ours,
+ Theirs: theirs,
+ }.Run(ctx, s.cfg)
+ if err != nil {
+ if errors.Is(err, git2go.ErrInvalidArgument) {
+ return helper.ErrInvalidArgument(err)
+ }
+ return helper.ErrInternal(err)
+ }
+
+ var conflictFiles []*gitalypb.ConflictFile
+ msgSize := 0
+
+ for _, conflict := range conflicts.Conflicts {
+ if conflict.Their.Path == "" || conflict.Our.Path == "" {
+ return helper.ErrPreconditionFailedf("conflict side missing")
+ }
+
+ if !utf8.Valid(conflict.Content) {
+ return helper.ErrPreconditionFailed(errors.New("unsupported encoding"))
+ }
+
+ conflictFiles = append(conflictFiles, &gitalypb.ConflictFile{
+ ConflictFilePayload: &gitalypb.ConflictFile_Header{
+ Header: &gitalypb.ConflictFileHeader{
+ CommitOid: request.OurCommitOid,
+ TheirPath: []byte(conflict.Their.Path),
+ OurPath: []byte(conflict.Our.Path),
+ OurMode: conflict.Our.Mode,
+ },
+ },
+ })
+
+ contentReader := bytes.NewReader(conflict.Content)
+ for {
+ chunk := make([]byte, streamio.WriteBufferSize-msgSize)
+ bytesRead, err := contentReader.Read(chunk)
+ if err != nil && err != io.EOF {
+ return helper.ErrInternal(err)
+ }
+
+ if bytesRead > 0 {
+ conflictFiles = append(conflictFiles, &gitalypb.ConflictFile{
+ ConflictFilePayload: &gitalypb.ConflictFile_Content{
+ Content: chunk[:bytesRead],
+ },
+ })
+ }
+
+ if err == io.EOF {
+ break
+ }
+
+ // We don't send a message for each chunk because the content of
+ // a file may be smaller than the size limit, which means we can
+ // keep adding data to the message
+ msgSize += bytesRead
+ if msgSize < streamio.WriteBufferSize {
+ continue
+ }
+
+ if err := stream.Send(&gitalypb.ListConflictFilesResponse{
+ Files: conflictFiles,
+ }); err != nil {
+ return helper.ErrInternal(err)
+ }
+
+ conflictFiles = conflictFiles[:0]
+ msgSize = 0
+ }
+ }
+
+ // Send leftover data, if any
+ if len(conflictFiles) > 0 {
+ if err := stream.Send(&gitalypb.ListConflictFilesResponse{
+ Files: conflictFiles,
+ }); err != nil {
+ return helper.ErrInternal(err)
+ }
+ }
+
+ return nil
+}
+
func (s *server) ListConflictFiles(in *gitalypb.ListConflictFilesRequest, stream gitalypb.ConflictsService_ListConflictFilesServer) error {
ctx := stream.Context()
+ if featureflag.IsEnabled(ctx, featureflag.GoListConflictFiles) {
+ return s.listConflictFiles(in, stream)
+ }
+
if err := validateListConflictFilesRequest(in); err != nil {
return status.Errorf(codes.InvalidArgument, "ListConflictFiles: %v", err)
}
diff --git a/internal/gitaly/service/conflicts/list_conflict_files_test.go b/internal/gitaly/service/conflicts/list_conflict_files_test.go
index b384f8b8c..56386aba1 100644
--- a/internal/gitaly/service/conflicts/list_conflict_files_test.go
+++ b/internal/gitaly/service/conflicts/list_conflict_files_test.go
@@ -1,11 +1,17 @@
package conflicts
import (
+ "bytes"
+ "context"
"io"
+ "io/ioutil"
+ "path/filepath"
"testing"
"github.com/golang/protobuf/proto"
"github.com/stretchr/testify/require"
+ "gitlab.com/gitlab-org/gitaly/internal/git"
+ "gitlab.com/gitlab-org/gitaly/internal/metadata/featureflag"
"gitlab.com/gitlab-org/gitaly/internal/testhelper"
"gitlab.com/gitlab-org/gitaly/proto/go/gitalypb"
"google.golang.org/grpc/codes"
@@ -16,7 +22,29 @@ type conflictFile struct {
content []byte
}
+func testListConflictFiles(t *testing.T, testcase func(t *testing.T, ctx context.Context)) {
+ featureSets, err := testhelper.NewFeatureSets([]featureflag.FeatureFlag{
+ featureflag.GoListConflictFiles,
+ })
+ require.NoError(t, err)
+
+ for _, featureSet := range featureSets {
+ t.Run("disabled "+featureSet.String(), func(t *testing.T) {
+ ctx, cancel := testhelper.Context()
+ defer cancel()
+
+ ctx = featureSet.Disable(ctx)
+
+ testcase(t, ctx)
+ })
+ }
+}
+
func TestSuccessfulListConflictFilesRequest(t *testing.T) {
+ testListConflictFiles(t, testSuccessfulListConflictFilesRequest)
+}
+
+func testSuccessfulListConflictFilesRequest(t *testing.T, ctx context.Context) {
serverSocketPath, stop := runConflictsServer(t)
defer stop()
@@ -48,9 +76,6 @@ class Conflict
end
`
- ctx, cancel := testhelper.Context()
- defer cancel()
-
request := &gitalypb.ListConflictFilesRequest{
Repository: testRepo,
OurCommitOid: ourCommitOid,
@@ -88,11 +113,88 @@ end
for i := 0; i < len(expectedFiles); i++ {
require.True(t, proto.Equal(receivedFiles[i].header, expectedFiles[i].header))
- require.Equal(t, receivedFiles[i].content, expectedFiles[i].content)
+ require.Equal(t, expectedFiles[i].content, receivedFiles[i].content)
+ }
+}
+
+func TestListConflictFilesHugeDiff(t *testing.T) {
+ testListConflictFiles(t, testListConflictFilesHugeDiff)
+}
+
+func testListConflictFilesHugeDiff(t *testing.T, ctx context.Context) {
+ serverSocketPath, stop := runConflictsServer(t)
+ defer stop()
+
+ client, conn := NewConflictsClient(t, serverSocketPath)
+ defer conn.Close()
+
+ repo, repoPath, cleanupFn := testhelper.NewTestRepoWithWorktree(t)
+ defer cleanupFn()
+
+ our := buildCommit(t, ctx, repo, repoPath, map[string][]byte{
+ "a": bytes.Repeat([]byte("a\n"), 128*1024),
+ "b": bytes.Repeat([]byte("b\n"), 128*1024),
+ })
+
+ their := buildCommit(t, ctx, repo, repoPath, map[string][]byte{
+ "a": bytes.Repeat([]byte("x\n"), 128*1024),
+ "b": bytes.Repeat([]byte("y\n"), 128*1024),
+ })
+
+ request := &gitalypb.ListConflictFilesRequest{
+ Repository: repo,
+ OurCommitOid: our,
+ TheirCommitOid: their,
}
+
+ c, err := client.ListConflictFiles(ctx, request)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ receivedFiles := getConflictFiles(t, c)
+ require.Len(t, receivedFiles, 2)
+ require.True(t,
+ proto.Equal(&gitalypb.ConflictFileHeader{
+ CommitOid: our,
+ OurMode: int32(0100644),
+ OurPath: []byte("a"),
+ TheirPath: []byte("a"),
+ }, receivedFiles[0].header),
+ )
+
+ require.True(t,
+ proto.Equal(&gitalypb.ConflictFileHeader{
+ CommitOid: our,
+ OurMode: int32(0100644),
+ OurPath: []byte("b"),
+ TheirPath: []byte("b"),
+ }, receivedFiles[1].header),
+ )
+}
+
+func buildCommit(t *testing.T, ctx context.Context, repo *gitalypb.Repository, repoPath string, files map[string][]byte) string {
+ for file, contents := range files {
+ filePath := filepath.Join(repoPath, file)
+ require.NoError(t, ioutil.WriteFile(filePath, contents, 0666))
+ testhelper.MustRunCommand(t, nil, "git", "-C", repoPath, "add", filePath)
+ }
+
+ testhelper.MustRunCommand(t, nil, "git", "-C", repoPath, "commit", "-m", "message")
+
+ oid, err := git.NewRepository(repo).ResolveRefish(ctx, "HEAD")
+ require.NoError(t, err)
+
+ testhelper.MustRunCommand(t, nil, "git", "-C", repoPath, "reset", "--hard", "HEAD~")
+
+ return oid
}
func TestListConflictFilesFailedPrecondition(t *testing.T) {
+ testListConflictFiles(t, testListConflictFilesFailedPrecondition)
+}
+
+func testListConflictFilesFailedPrecondition(t *testing.T, ctx context.Context) {
serverSocketPath, stop := runConflictsServer(t)
defer stop()
@@ -102,9 +204,6 @@ func TestListConflictFilesFailedPrecondition(t *testing.T) {
testRepo, _, cleanupFn := testhelper.NewTestRepo(t)
defer cleanupFn()
- ctx, cancel := testhelper.Context()
- defer cancel()
-
testCases := []struct {
desc string
ourCommitOid string
@@ -158,6 +257,10 @@ func TestListConflictFilesFailedPrecondition(t *testing.T) {
}
func TestFailedListConflictFilesRequestDueToValidation(t *testing.T) {
+ testListConflictFiles(t, testFailedListConflictFilesRequestDueToValidation)
+}
+
+func testFailedListConflictFilesRequestDueToValidation(t *testing.T, ctx context.Context) {
serverSocketPath, stop := runConflictsServer(t)
defer stop()
@@ -206,9 +309,6 @@ func TestFailedListConflictFilesRequestDueToValidation(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.desc, func(t *testing.T) {
- ctx, cancel := testhelper.Context()
- defer cancel()
-
c, _ := client.ListConflictFiles(ctx, testCase.request)
testhelper.RequireGrpcError(t, drainListConflictFilesResponse(c), testCase.code)
})
diff --git a/internal/gitaly/service/conflicts/server.go b/internal/gitaly/service/conflicts/server.go
index d339dcca4..91bd935d3 100644
--- a/internal/gitaly/service/conflicts/server.go
+++ b/internal/gitaly/service/conflicts/server.go
@@ -1,15 +1,23 @@
package conflicts
import (
+ "gitlab.com/gitlab-org/gitaly/internal/gitaly/config"
"gitlab.com/gitlab-org/gitaly/internal/gitaly/rubyserver"
+ "gitlab.com/gitlab-org/gitaly/internal/storage"
"gitlab.com/gitlab-org/gitaly/proto/go/gitalypb"
)
type server struct {
- ruby *rubyserver.Server
+ ruby *rubyserver.Server
+ cfg config.Cfg
+ locator storage.Locator
}
// NewServer creates a new instance of a grpc ConflictsServer
-func NewServer(rs *rubyserver.Server) gitalypb.ConflictsServiceServer {
- return &server{ruby: rs}
+func NewServer(rs *rubyserver.Server, cfg config.Cfg, locator storage.Locator) gitalypb.ConflictsServiceServer {
+ return &server{
+ ruby: rs,
+ cfg: cfg,
+ locator: locator,
+ }
}
diff --git a/internal/gitaly/service/conflicts/testhelper_test.go b/internal/gitaly/service/conflicts/testhelper_test.go
index e952327a1..9b3f8fdaa 100644
--- a/internal/gitaly/service/conflicts/testhelper_test.go
+++ b/internal/gitaly/service/conflicts/testhelper_test.go
@@ -26,6 +26,8 @@ var RubyServer = &rubyserver.Server{}
func testMain(m *testing.M) int {
defer testhelper.MustHaveNoChildProcess()
+ testhelper.ConfigureGitalyGit2Go()
+
tempDir, err := ioutil.TempDir("", "gitaly")
if err != nil {
log.Fatal(err)
@@ -45,8 +47,9 @@ func testMain(m *testing.M) int {
func runConflictsServer(t *testing.T) (string, func()) {
srv := testhelper.NewServer(t, nil, nil)
+ locator := config.NewLocator(config.Config)
- gitalypb.RegisterConflictsServiceServer(srv.GrpcServer(), NewServer(RubyServer))
+ gitalypb.RegisterConflictsServiceServer(srv.GrpcServer(), NewServer(RubyServer, config.Config, 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 34f732339..0a6756ba8 100644
--- a/internal/gitaly/service/register.go
+++ b/internal/gitaly/service/register.go
@@ -85,7 +85,7 @@ func RegisterAll(grpcServer *grpc.Server, cfg config.Cfg, rubyServer *rubyserver
smarthttp.WithPackfileNegotiationMetrics(smarthttpPackfileNegotiationMetrics),
))
gitalypb.RegisterWikiServiceServer(grpcServer, wiki.NewServer(rubyServer, locator))
- gitalypb.RegisterConflictsServiceServer(grpcServer, conflicts.NewServer(rubyServer))
+ gitalypb.RegisterConflictsServiceServer(grpcServer, conflicts.NewServer(rubyServer, cfg, locator))
gitalypb.RegisterRemoteServiceServer(grpcServer, remote.NewServer(rubyServer, locator))
gitalypb.RegisterServerServiceServer(grpcServer, server.NewServer(cfg.Storages))
gitalypb.RegisterObjectPoolServiceServer(grpcServer, objectpool.NewServer(locator))
diff --git a/internal/metadata/featureflag/feature_flags.go b/internal/metadata/featureflag/feature_flags.go
index d0f9f8eb9..9f1af69e4 100644
--- a/internal/metadata/featureflag/feature_flags.go
+++ b/internal/metadata/featureflag/feature_flags.go
@@ -36,6 +36,8 @@ var (
GoUserDeleteBranch = FeatureFlag{Name: "go_user_delete_branch", OnByDefault: false}
// GoUserSquash enable the Go implementation of UserSquash
GoUserSquash = FeatureFlag{Name: "go_user_squash", OnByDefault: false}
+ // GoListConflictFiles enables the Go implementation of ListConflictFiles
+ GoListConflictFiles = FeatureFlag{Name: "go_list_conflict_files", OnByDefault: false}
)
// All includes all feature flags.
@@ -51,6 +53,7 @@ var All = []FeatureFlag{
GoUserFFBranch,
GoUserDeleteBranch,
GoUserSquash,
+ GoListConflictFiles,
}
const (