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:
authorkarthik nayak <knayak@gitlab.com>2023-06-16 16:57:42 +0300
committerkarthik nayak <knayak@gitlab.com>2023-06-16 16:57:42 +0300
commitceafa16c0204bd3b155299731c8b7da661ce3511 (patch)
tree0221796091f4d1e65f17b7ae826d16314ac1048d
parent4a7c0b66c9e8f70a0b24103ab530edaff0f82943 (diff)
parentec67f0aed83fbbceaa5b0d75c6b7395140342f72 (diff)
Merge branch '4580-reimplement-resolveconflicts-without-git2go' into 'master'
conflicts: Modernize the tests in `resolve_conflicts_test` Closes #4580 See merge request https://gitlab.com/gitlab-org/gitaly/-/merge_requests/5924 Merged-by: karthik nayak <knayak@gitlab.com> Approved-by: Quang-Minh Nguyen <qmnguyen@gitlab.com> Reviewed-by: Quang-Minh Nguyen <qmnguyen@gitlab.com>
-rw-r--r--internal/gitaly/service/conflicts/list_conflict_files_test.go26
-rw-r--r--internal/gitaly/service/conflicts/resolve_conflicts_test.go1858
-rw-r--r--internal/gitaly/service/conflicts/testhelper_test.go14
3 files changed, 1068 insertions, 830 deletions
diff --git a/internal/gitaly/service/conflicts/list_conflict_files_test.go b/internal/gitaly/service/conflicts/list_conflict_files_test.go
index adc2c932a..e0921cc72 100644
--- a/internal/gitaly/service/conflicts/list_conflict_files_test.go
+++ b/internal/gitaly/service/conflicts/list_conflict_files_test.go
@@ -45,7 +45,7 @@ func testListConflictFiles(t *testing.T, ctx context.Context) {
{
"Lists the expected conflict files",
func(tb testing.TB, ctx context.Context) setupData {
- cfg, client := setupConflictsServiceWithoutRepo(tb, nil)
+ cfg, client := setupConflictsService(tb, nil)
repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
ourCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithTreeEntries(
@@ -92,7 +92,7 @@ func testListConflictFiles(t *testing.T, ctx context.Context) {
{
"Lists the expected conflict files with short OIDs",
func(tb testing.TB, ctx context.Context) setupData {
- cfg, client := setupConflictsServiceWithoutRepo(tb, nil)
+ cfg, client := setupConflictsService(tb, nil)
repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
ourCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithTreeEntries(
@@ -139,7 +139,7 @@ func testListConflictFiles(t *testing.T, ctx context.Context) {
{
"conflict in submodules commits are not handled",
func(tb testing.TB, ctx context.Context) setupData {
- cfg, client := setupConflictsServiceWithoutRepo(tb, nil)
+ cfg, client := setupConflictsService(tb, nil)
repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
_, subRepoPath := gittest.CreateRepository(tb, ctx, cfg)
@@ -192,7 +192,7 @@ func testListConflictFiles(t *testing.T, ctx context.Context) {
{
"Lists the expected conflict files with ancestor path",
func(tb testing.TB, ctx context.Context) setupData {
- cfg, client := setupConflictsServiceWithoutRepo(tb, nil)
+ cfg, client := setupConflictsService(tb, nil)
repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
commonCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithTreeEntries(
@@ -249,7 +249,7 @@ func testListConflictFiles(t *testing.T, ctx context.Context) {
{
"Lists the expected conflict files with huge diff",
func(tb testing.TB, ctx context.Context) setupData {
- cfg, client := setupConflictsServiceWithoutRepo(tb, nil)
+ cfg, client := setupConflictsService(tb, nil)
repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
ourCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithTreeEntries(
@@ -302,7 +302,7 @@ func testListConflictFiles(t *testing.T, ctx context.Context) {
{
"invalid commit id on 'our' side",
func(tb testing.TB, ctx context.Context) setupData {
- cfg, client := setupConflictsServiceWithoutRepo(tb, nil)
+ cfg, client := setupConflictsService(tb, nil)
repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
theirCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithTreeEntries(
@@ -326,7 +326,7 @@ func testListConflictFiles(t *testing.T, ctx context.Context) {
{
"invalid commit id on 'their' side",
func(tb testing.TB, ctx context.Context) setupData {
- cfg, client := setupConflictsServiceWithoutRepo(tb, nil)
+ cfg, client := setupConflictsService(tb, nil)
repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
ourCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithTreeEntries(
@@ -350,7 +350,7 @@ func testListConflictFiles(t *testing.T, ctx context.Context) {
{
"conflict side missing",
func(tb testing.TB, ctx context.Context) setupData {
- cfg, client := setupConflictsServiceWithoutRepo(tb, nil)
+ cfg, client := setupConflictsService(tb, nil)
repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
commonCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithTreeEntries(
@@ -384,7 +384,7 @@ func testListConflictFiles(t *testing.T, ctx context.Context) {
{
"allow tree conflicts",
func(tb testing.TB, ctx context.Context) setupData {
- cfg, client := setupConflictsServiceWithoutRepo(tb, nil)
+ cfg, client := setupConflictsService(tb, nil)
repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
commonCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithTreeEntries(
@@ -446,7 +446,7 @@ func testListConflictFiles(t *testing.T, ctx context.Context) {
{
"encoding error",
func(tb testing.TB, ctx context.Context) setupData {
- cfg, client := setupConflictsServiceWithoutRepo(tb, nil)
+ cfg, client := setupConflictsService(tb, nil)
repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
ourCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithTreeEntries(
@@ -472,7 +472,7 @@ func testListConflictFiles(t *testing.T, ctx context.Context) {
{
"empty repo",
func(tb testing.TB, ctx context.Context) setupData {
- cfg, client := setupConflictsServiceWithoutRepo(tb, nil)
+ cfg, client := setupConflictsService(tb, nil)
_, repoPath := gittest.CreateRepository(tb, ctx, cfg)
ourCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithTreeEntries(
@@ -501,7 +501,7 @@ func testListConflictFiles(t *testing.T, ctx context.Context) {
{
"empty OurCommitId field",
func(tb testing.TB, ctx context.Context) setupData {
- cfg, client := setupConflictsServiceWithoutRepo(tb, nil)
+ cfg, client := setupConflictsService(tb, nil)
repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
theirCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithTreeEntries(
@@ -524,7 +524,7 @@ func testListConflictFiles(t *testing.T, ctx context.Context) {
{
"empty TheirCommitId field",
func(tb testing.TB, ctx context.Context) setupData {
- cfg, client := setupConflictsServiceWithoutRepo(tb, nil)
+ cfg, client := setupConflictsService(tb, nil)
repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
ourCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithTreeEntries(
diff --git a/internal/gitaly/service/conflicts/resolve_conflicts_test.go b/internal/gitaly/service/conflicts/resolve_conflicts_test.go
index 265ed36e7..d501ca540 100644
--- a/internal/gitaly/service/conflicts/resolve_conflicts_test.go
+++ b/internal/gitaly/service/conflicts/resolve_conflicts_test.go
@@ -3,15 +3,10 @@
package conflicts
import (
- "bytes"
"context"
+ "crypto/sha1"
"encoding/json"
"fmt"
- "io"
- "os"
- "os/exec"
- "path/filepath"
- "regexp"
"strings"
"testing"
@@ -19,16 +14,13 @@ import (
"gitlab.com/gitlab-org/gitaly/v16/internal/git"
"gitlab.com/gitlab-org/gitaly/v16/internal/git/gittest"
"gitlab.com/gitlab-org/gitaly/v16/internal/git/localrepo"
- "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/hook"
- "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/storage"
- "gitlab.com/gitlab-org/gitaly/v16/internal/helper/perm"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/config"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/helper/text"
"gitlab.com/gitlab-org/gitaly/v16/internal/structerr"
"gitlab.com/gitlab-org/gitaly/v16/internal/testhelper"
"gitlab.com/gitlab-org/gitaly/v16/internal/testhelper/testcfg"
"gitlab.com/gitlab-org/gitaly/v16/proto/go/gitalypb"
- "google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
- "google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
)
@@ -39,891 +31,1149 @@ var (
GlId: "user-1",
}
conflictResolutionCommitMessage = "Solve conflicts"
-
- files = []map[string]interface{}{
- {
- "old_path": "files/ruby/popen.rb",
- "new_path": "files/ruby/popen.rb",
- "sections": map[string]string{
- "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14": "head",
- },
- },
- {
- "old_path": "files/ruby/regex.rb",
- "new_path": "files/ruby/regex.rb",
- "sections": map[string]string{
- "6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9": "head",
- "6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21": "origin",
- "6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49": "origin",
- },
- },
- }
)
-func TestSuccessfulResolveConflictsRequestHelper(t *testing.T) {
- var verifyFunc func(tb testing.TB, pushOptions []string, stdin io.Reader)
- verifyFuncProxy := func(t *testing.T, ctx context.Context, repo *gitalypb.Repository, pushOptions, env []string, stdin io.Reader, stdout, stderr io.Writer) error {
- // We use a proxy func here as we need to provide the hookManager dependency while creating the service but we only
- // know the commit IDs after the service is created. The proxy allows us to modify the verifyFunc after the service
- // is already built.
- verifyFunc(t, pushOptions, stdin)
- return nil
+func TestResolveConflicts(t *testing.T) {
+ type setupData struct {
+ cfg config.Cfg
+ requestHeader *gitalypb.ResolveConflictsRequest_Header
+ requestsFilesJSON []*gitalypb.ResolveConflictsRequest_FilesJson
+ client gitalypb.ConflictsServiceClient
+ repo *gitalypb.Repository
+ repoPath string
+ expectedContent map[string]map[string][]byte
+ expectedResponse *gitalypb.ResolveConflictsResponse
+ expectedError error
+ skipCommitCheck bool
+ additionalChecks func()
}
- ctx := testhelper.Context(t)
- hookManager := hook.NewMockManager(t, verifyFuncProxy, verifyFuncProxy, hook.NopUpdate, hook.NopReferenceTransaction)
- cfg, repoProto, repoPath, client := setupConflictsService(t, ctx, hookManager)
+ for _, tc := range []struct {
+ desc string
+ setup func(testing.TB, context.Context) setupData
+ }{
+ {
+ "single file conflict, pick ours",
+ func(tb testing.TB, ctx context.Context) setupData {
+ cfg, client := setupConflictsService(tb, nil)
+ repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
+
+ baseCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithTreeEntries(
+ gittest.TreeEntry{Path: "a", Mode: "100644", Content: "apple"},
+ ))
+
+ ourCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithParents(baseCommitID), gittest.WithBranch("ours"),
+ gittest.WithTreeEntries(gittest.TreeEntry{Path: "a", Mode: "100644", Content: "apricot"}))
+ theirCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithParents(baseCommitID), gittest.WithBranch("theirs"),
+ gittest.WithTreeEntries(gittest.TreeEntry{Path: "a", Mode: "100644", Content: "acai"}))
+
+ files := []map[string]interface{}{
+ {
+ "old_path": "a",
+ "new_path": "a",
+ "sections": map[string]string{
+ fmt.Sprintf("%x_%d_%d", sha1.Sum([]byte("a")), 1, 1): "head",
+ },
+ },
+ }
- repo := localrepo.NewTestRepo(t, cfg, repoProto)
+ filesJSON, err := json.Marshal(files)
+ require.NoError(t, err)
- missingAncestorPath := "files/missing_ancestor.txt"
- files := []map[string]interface{}{
- {
- "old_path": "files/ruby/popen.rb",
- "new_path": "files/ruby/popen.rb",
- "sections": map[string]string{
- "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14": "head",
+ return setupData{
+ cfg: cfg,
+ client: client,
+ repoPath: repoPath,
+ repo: repo,
+ requestHeader: &gitalypb.ResolveConflictsRequest_Header{
+ Header: &gitalypb.ResolveConflictsRequestHeader{
+ Repository: repo,
+ TargetRepository: repo,
+ OurCommitOid: ourCommitID.String(),
+ TheirCommitOid: theirCommitID.String(),
+ TargetBranch: []byte("theirs"),
+ SourceBranch: []byte("ours"),
+ CommitMessage: []byte(conflictResolutionCommitMessage),
+ User: user,
+ Timestamp: &timestamppb.Timestamp{},
+ },
+ },
+ requestsFilesJSON: []*gitalypb.ResolveConflictsRequest_FilesJson{
+ {FilesJson: filesJSON[:50]},
+ {FilesJson: filesJSON[50:]},
+ },
+ expectedResponse: &gitalypb.ResolveConflictsResponse{},
+ expectedContent: map[string]map[string][]byte{
+ "refs/heads/ours": {
+ "a": []byte("apricot"),
+ },
+ },
+ }
},
},
{
- "old_path": "files/ruby/regex.rb",
- "new_path": "files/ruby/regex.rb",
- "sections": map[string]string{
- "6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9": "head",
- "6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21": "origin",
- "6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49": "origin",
+ "single file conflict, pick theirs",
+ func(tb testing.TB, ctx context.Context) setupData {
+ cfg, client := setupConflictsService(tb, nil)
+ repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
+
+ baseCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithTreeEntries(
+ gittest.TreeEntry{Path: "a", Mode: "100644", Content: "apple"},
+ ))
+
+ ourCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithParents(baseCommitID), gittest.WithBranch("ours"),
+ gittest.WithTreeEntries(gittest.TreeEntry{Path: "a", Mode: "100644", Content: "apricot"}))
+ theirCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithParents(baseCommitID), gittest.WithBranch("theirs"),
+ gittest.WithTreeEntries(gittest.TreeEntry{Path: "a", Mode: "100644", Content: "acai"}))
+
+ files := []map[string]interface{}{
+ {
+ "old_path": "a",
+ "new_path": "a",
+ "sections": map[string]string{
+ fmt.Sprintf("%x_%d_%d", sha1.Sum([]byte("a")), 1, 1): "origin",
+ },
+ },
+ }
+
+ filesJSON, err := json.Marshal(files)
+ require.NoError(t, err)
+
+ return setupData{
+ cfg: cfg,
+ client: client,
+ repoPath: repoPath,
+ repo: repo,
+ requestHeader: &gitalypb.ResolveConflictsRequest_Header{
+ Header: &gitalypb.ResolveConflictsRequestHeader{
+ Repository: repo,
+ TargetRepository: repo,
+ OurCommitOid: ourCommitID.String(),
+ TheirCommitOid: theirCommitID.String(),
+ TargetBranch: []byte("theirs"),
+ SourceBranch: []byte("ours"),
+ CommitMessage: []byte(conflictResolutionCommitMessage),
+ User: user,
+ Timestamp: &timestamppb.Timestamp{},
+ },
+ },
+ requestsFilesJSON: []*gitalypb.ResolveConflictsRequest_FilesJson{
+ {FilesJson: filesJSON[:50]},
+ {FilesJson: filesJSON[50:]},
+ },
+ expectedResponse: &gitalypb.ResolveConflictsResponse{},
+ expectedContent: map[string]map[string][]byte{
+ "refs/heads/ours": {
+ "a": []byte("acai"),
+ },
+ },
+ }
},
},
{
- "old_path": missingAncestorPath,
- "new_path": missingAncestorPath,
- "sections": map[string]string{
- "b760bfd3b1b1da380b4276eb30fb3b2b7e4f08e1_1_1": "origin",
+ "single file conflict without ancestor, pick ours",
+ func(tb testing.TB, ctx context.Context) setupData {
+ cfg, client := setupConflictsService(tb, nil)
+ repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
+
+ ourCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithBranch("ours"),
+ gittest.WithTreeEntries(gittest.TreeEntry{Path: "a", Mode: "100644", Content: "apricot"}))
+ theirCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithBranch("theirs"),
+ gittest.WithTreeEntries(gittest.TreeEntry{Path: "a", Mode: "100644", Content: "acai"}))
+
+ files := []map[string]interface{}{
+ {
+ "old_path": "a",
+ "new_path": "a",
+ "sections": map[string]string{
+ fmt.Sprintf("%x_%d_%d", sha1.Sum([]byte("a")), 1, 1): "head",
+ },
+ },
+ }
+
+ filesJSON, err := json.Marshal(files)
+ require.NoError(t, err)
+
+ return setupData{
+ cfg: cfg,
+ client: client,
+ repoPath: repoPath,
+ repo: repo,
+ requestHeader: &gitalypb.ResolveConflictsRequest_Header{
+ Header: &gitalypb.ResolveConflictsRequestHeader{
+ Repository: repo,
+ TargetRepository: repo,
+ OurCommitOid: ourCommitID.String(),
+ TheirCommitOid: theirCommitID.String(),
+ TargetBranch: []byte("theirs"),
+ SourceBranch: []byte("ours"),
+ CommitMessage: []byte(conflictResolutionCommitMessage),
+ User: user,
+ Timestamp: &timestamppb.Timestamp{},
+ },
+ },
+ requestsFilesJSON: []*gitalypb.ResolveConflictsRequest_FilesJson{
+ {FilesJson: filesJSON[:50]},
+ {FilesJson: filesJSON[50:]},
+ },
+ expectedResponse: &gitalypb.ResolveConflictsResponse{},
+ expectedContent: map[string]map[string][]byte{
+ "refs/heads/ours": {
+ "a": []byte("apricot"),
+ },
+ },
+ }
},
},
- }
-
- filesJSON, err := json.Marshal(files)
- require.NoError(t, err)
-
- sourceBranch := "conflict-resolvable"
- targetBranch := "conflict-start"
- ourCommitOID := "1450cd639e0bc6721eb02800169e464f212cde06" // part of branch conflict-resolvable
- theirCommitOID := "824be604a34828eb682305f0d963056cfac87b2d" // part of branch conflict-start
- ancestorCommitOID := "6907208d755b60ebeacb2e9dfea74c92c3449a1f"
-
- // introduce a conflict that exists on both branches, but not the
- // ancestor
- commitConflict := func(parentCommitID, branch, blob string) string {
- blobID, err := repo.WriteBlob(ctx, "", strings.NewReader(blob))
- require.NoError(t, err)
- gittest.Exec(t, cfg, "-C", repoPath, "read-tree", branch)
- gittest.Exec(t, cfg, "-C", repoPath,
- "update-index", "--add", "--cacheinfo", "100644", blobID.String(), missingAncestorPath,
- )
- treeID := bytes.TrimSpace(
- gittest.Exec(t, cfg, "-C", repoPath, "write-tree"),
- )
- commitID := bytes.TrimSpace(
- gittest.Exec(t, cfg, "-C", repoPath,
- "commit-tree", string(treeID), "-p", parentCommitID,
- ),
- )
- gittest.Exec(t, cfg, "-C", repoPath, "update-ref", "refs/heads/"+branch, string(commitID))
- return string(commitID)
- }
+ {
+ "single file multi conflict",
+ func(tb testing.TB, ctx context.Context) setupData {
+ cfg, client := setupConflictsService(tb, nil)
+ repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
+
+ baseCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithTreeEntries(
+ gittest.TreeEntry{Path: "a", Mode: "100644", Content: "apple\n" + strings.Repeat("filler\n", 10) + "banana"},
+ ))
+
+ ourCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithParents(baseCommitID), gittest.WithBranch("ours"),
+ gittest.WithTreeEntries(gittest.TreeEntry{
+ Path: "a", Mode: "100644",
+ Content: "apricot\n" + strings.Repeat("filler\n", 10) + "berries",
+ }))
+ theirCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithParents(baseCommitID), gittest.WithBranch("theirs"),
+ gittest.WithTreeEntries(gittest.TreeEntry{
+ Path: "a", Mode: "100644",
+ Content: "acai\n" + strings.Repeat("filler\n", 10) + "birne",
+ }))
+
+ files := []map[string]interface{}{
+ {
+ "old_path": "a",
+ "new_path": "a",
+ "sections": map[string]string{
+ fmt.Sprintf("%x_%d_%d", sha1.Sum([]byte("a")), 1, 1): "head",
+ fmt.Sprintf("%x_%d_%d", sha1.Sum([]byte("a")), 12, 12): "origin",
+ },
+ },
+ }
- // sanity check: make sure the conflict file does not exist on the
- // common ancestor
- cmd := exec.CommandContext(ctx, "git", "cat-file", "-e", ancestorCommitOID+":"+missingAncestorPath)
- require.Error(t, cmd.Run())
-
- ourCommitOID = commitConflict(ourCommitOID, sourceBranch, "content-1")
- theirCommitOID = commitConflict(theirCommitOID, targetBranch, "content-2")
- hookCount := 0
-
- verifyFunc = func(tb testing.TB, pushOptions []string, stdin io.Reader) {
- changes, err := io.ReadAll(stdin)
- require.NoError(tb, err)
- pattern := fmt.Sprintf("%s .* refs/heads/%s\n", ourCommitOID, sourceBranch)
- require.Regexp(tb, regexp.MustCompile(pattern), string(changes))
- require.Empty(tb, pushOptions)
- hookCount++
- }
+ filesJSON, err := json.Marshal(files)
+ require.NoError(t, err)
- mdGS := testcfg.GitalyServersMetadataFromCfg(t, cfg)
- mdFF, _ := metadata.FromOutgoingContext(ctx)
- ctx = metadata.NewOutgoingContext(ctx, metadata.Join(mdGS, mdFF))
-
- headerRequest := &gitalypb.ResolveConflictsRequest{
- ResolveConflictsRequestPayload: &gitalypb.ResolveConflictsRequest_Header{
- Header: &gitalypb.ResolveConflictsRequestHeader{
- Repository: repoProto,
- TargetRepository: repoProto,
- CommitMessage: []byte(conflictResolutionCommitMessage),
- OurCommitOid: ourCommitOID,
- TheirCommitOid: theirCommitOID,
- SourceBranch: []byte(sourceBranch),
- TargetBranch: []byte(targetBranch),
- User: user,
+ return setupData{
+ cfg: cfg,
+ client: client,
+ repoPath: repoPath,
+ repo: repo,
+ requestHeader: &gitalypb.ResolveConflictsRequest_Header{
+ Header: &gitalypb.ResolveConflictsRequestHeader{
+ Repository: repo,
+ TargetRepository: repo,
+ OurCommitOid: ourCommitID.String(),
+ TheirCommitOid: theirCommitID.String(),
+ TargetBranch: []byte("theirs"),
+ SourceBranch: []byte("ours"),
+ CommitMessage: []byte(conflictResolutionCommitMessage),
+ User: user,
+ Timestamp: &timestamppb.Timestamp{},
+ },
+ },
+ requestsFilesJSON: []*gitalypb.ResolveConflictsRequest_FilesJson{
+ {FilesJson: filesJSON[:50]},
+ {FilesJson: filesJSON[50:]},
+ },
+ expectedResponse: &gitalypb.ResolveConflictsResponse{},
+ expectedContent: map[string]map[string][]byte{
+ "refs/heads/ours": {
+ "a": []byte("apricot\n" + strings.Repeat("filler\n", 10) + "birne"),
+ },
+ },
+ }
},
},
- }
- filesRequest1 := &gitalypb.ResolveConflictsRequest{
- ResolveConflictsRequestPayload: &gitalypb.ResolveConflictsRequest_FilesJson{
- FilesJson: filesJSON[:50],
- },
- }
- filesRequest2 := &gitalypb.ResolveConflictsRequest{
- ResolveConflictsRequestPayload: &gitalypb.ResolveConflictsRequest_FilesJson{
- FilesJson: filesJSON[50:],
- },
- }
+ {
+ "multi file multi conflict",
+ func(tb testing.TB, ctx context.Context) setupData {
+ cfg, client := setupConflictsService(tb, nil)
+ repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
+
+ baseCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithTreeEntries(
+ gittest.TreeEntry{Path: "a", Mode: "100644", Content: "apple\n" + strings.Repeat("filler\n", 10) + "banana"},
+ gittest.TreeEntry{Path: "b", Mode: "100644", Content: "strawberry"},
+ ))
+
+ ourCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithParents(baseCommitID), gittest.WithBranch("ours"),
+ gittest.WithTreeEntries(gittest.TreeEntry{
+ Path: "a", Mode: "100644",
+ Content: "apricot\n" + strings.Repeat("filler\n", 10) + "berries",
+ }, gittest.TreeEntry{Path: "b", Mode: "100644", Content: "blueberry"}))
+ theirCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithParents(baseCommitID), gittest.WithBranch("theirs"),
+ gittest.WithTreeEntries(gittest.TreeEntry{
+ Path: "a", Mode: "100644",
+ Content: "acai\n" + strings.Repeat("filler\n", 10) + "birne",
+ }, gittest.TreeEntry{Path: "b", Mode: "100644", Content: "raspberry"}))
+
+ files := []map[string]interface{}{
+ {
+ "old_path": "a",
+ "new_path": "a",
+ "sections": map[string]string{
+ fmt.Sprintf("%x_%d_%d", sha1.Sum([]byte("a")), 1, 1): "head",
+ fmt.Sprintf("%x_%d_%d", sha1.Sum([]byte("a")), 12, 12): "origin",
+ },
+ },
+ {
+ "old_path": "b",
+ "new_path": "b",
+ "sections": map[string]string{
+ fmt.Sprintf("%x_%d_%d", sha1.Sum([]byte("b")), 1, 1): "head",
+ },
+ },
+ }
- stream, err := client.ResolveConflicts(ctx)
- require.NoError(t, err)
- require.NoError(t, stream.Send(headerRequest))
- require.NoError(t, stream.Send(filesRequest1))
- require.NoError(t, stream.Send(filesRequest2))
-
- r, err := stream.CloseAndRecv()
- require.NoError(t, err)
- require.Empty(t, r.GetResolutionError())
-
- headCommit, err := repo.ReadCommit(ctx, git.Revision(sourceBranch))
- require.NoError(t, err)
- require.Contains(t, headCommit.ParentIds, ourCommitOID)
- require.Contains(t, headCommit.ParentIds, theirCommitOID)
- require.Equal(t, string(headCommit.Author.Email), "johndoe@gitlab.com")
- require.Equal(t, string(headCommit.Committer.Email), "johndoe@gitlab.com")
- require.Equal(t, string(headCommit.Subject), conflictResolutionCommitMessage)
-
- require.Equal(t, 2, hookCount)
-}
+ filesJSON, err := json.Marshal(files)
+ require.NoError(t, err)
-func TestResolveConflictsWithRemoteRepo(t *testing.T) {
- t.Parallel()
-
- ctx := testhelper.Context(t)
- hookManager := hook.NewMockManager(t, hook.NopPreReceive, hook.NopPostReceive, hook.NopUpdate, hook.NopReferenceTransaction)
- cfg, sourceRepo, sourceRepoPath, client := setupConflictsService(t, ctx, hookManager)
-
- testcfg.BuildGitalySSH(t, cfg)
- testcfg.BuildGitalyHooks(t, cfg)
-
- sourceBlobOID := gittest.WriteBlob(t, cfg, sourceRepoPath, []byte("contents-1\n"))
- sourceCommitOID := gittest.WriteCommit(t, cfg, sourceRepoPath,
- gittest.WithTreeEntries(gittest.TreeEntry{
- Path: "file.txt", OID: sourceBlobOID, Mode: "100644",
- }),
- )
- gittest.Exec(t, cfg, "-C", sourceRepoPath, "update-ref", "refs/heads/source", sourceCommitOID.String())
-
- targetRepo, targetRepoPath := gittest.CreateRepository(t, ctx, cfg, gittest.CreateRepositoryConfig{
- Seed: gittest.SeedGitLabTest,
- })
- targetBlobOID := gittest.WriteBlob(t, cfg, targetRepoPath, []byte("contents-2\n"))
- targetCommitOID := gittest.WriteCommit(t, cfg, targetRepoPath,
- gittest.WithTreeEntries(gittest.TreeEntry{
- OID: targetBlobOID, Path: "file.txt", Mode: "100644",
- }),
- )
- gittest.Exec(t, cfg, "-C", targetRepoPath, "update-ref", "refs/heads/target", targetCommitOID.String())
-
- ctx = testhelper.MergeOutgoingMetadata(ctx, testcfg.GitalyServersMetadataFromCfg(t, cfg))
-
- stream, err := client.ResolveConflicts(ctx)
- require.NoError(t, err)
-
- filesJSON, err := json.Marshal([]map[string]interface{}{
- {
- "old_path": "file.txt",
- "new_path": "file.txt",
- "sections": map[string]string{
- "5436437fa01a7d3e41d46741da54b451446774ca_1_1": "origin",
+ return setupData{
+ cfg: cfg,
+ client: client,
+ repoPath: repoPath,
+ repo: repo,
+ requestHeader: &gitalypb.ResolveConflictsRequest_Header{
+ Header: &gitalypb.ResolveConflictsRequestHeader{
+ Repository: repo,
+ TargetRepository: repo,
+ OurCommitOid: ourCommitID.String(),
+ TheirCommitOid: theirCommitID.String(),
+ TargetBranch: []byte("theirs"),
+ SourceBranch: []byte("ours"),
+ CommitMessage: []byte(conflictResolutionCommitMessage),
+ User: user,
+ Timestamp: &timestamppb.Timestamp{},
+ },
+ },
+ requestsFilesJSON: []*gitalypb.ResolveConflictsRequest_FilesJson{
+ {FilesJson: filesJSON[:50]},
+ {FilesJson: filesJSON[50:]},
+ },
+ expectedResponse: &gitalypb.ResolveConflictsResponse{},
+ expectedContent: map[string]map[string][]byte{
+ "refs/heads/ours": {
+ "a": []byte("apricot\n" + strings.Repeat("filler\n", 10) + "birne"),
+ "b": []byte("blueberry"),
+ },
+ },
+ }
},
},
- })
- require.NoError(t, err)
-
- require.NoError(t, stream.Send(&gitalypb.ResolveConflictsRequest{
- ResolveConflictsRequestPayload: &gitalypb.ResolveConflictsRequest_Header{
- Header: &gitalypb.ResolveConflictsRequestHeader{
- Repository: sourceRepo,
- TargetRepository: targetRepo,
- CommitMessage: []byte(conflictResolutionCommitMessage),
- OurCommitOid: sourceCommitOID.String(),
- TheirCommitOid: targetCommitOID.String(),
- SourceBranch: []byte("source"),
- TargetBranch: []byte("target"),
- User: user,
+ {
+ "single file conflict, remote repo",
+ func(tb testing.TB, ctx context.Context) setupData {
+ cfg, client := setupConflictsService(tb, nil)
+ testcfg.BuildGitalySSH(t, cfg)
+
+ repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
+ targetRepo, targetRepoPath := gittest.CreateRepository(tb, ctx, cfg)
+
+ ourCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithBranch("ours"),
+ gittest.WithTreeEntries(gittest.TreeEntry{Path: "a", Mode: "100644", Content: "apricot"}))
+ theirCommitID := gittest.WriteCommit(tb, cfg, targetRepoPath, gittest.WithBranch("theirs"),
+ gittest.WithTreeEntries(gittest.TreeEntry{Path: "a", Mode: "100644", Content: "acai"}))
+
+ files := []map[string]interface{}{
+ {
+ "old_path": "a",
+ "new_path": "a",
+ "sections": map[string]string{
+ fmt.Sprintf("%x_%d_%d", sha1.Sum([]byte("a")), 1, 1): "head",
+ },
+ },
+ }
+
+ filesJSON, err := json.Marshal(files)
+ require.NoError(t, err)
+
+ return setupData{
+ cfg: cfg,
+ client: client,
+ repoPath: repoPath,
+ repo: repo,
+ requestHeader: &gitalypb.ResolveConflictsRequest_Header{
+ Header: &gitalypb.ResolveConflictsRequestHeader{
+ Repository: repo,
+ TargetRepository: targetRepo,
+ OurCommitOid: ourCommitID.String(),
+ TheirCommitOid: theirCommitID.String(),
+ TargetBranch: []byte("theirs"),
+ SourceBranch: []byte("ours"),
+ CommitMessage: []byte(conflictResolutionCommitMessage),
+ User: user,
+ Timestamp: &timestamppb.Timestamp{},
+ },
+ },
+ requestsFilesJSON: []*gitalypb.ResolveConflictsRequest_FilesJson{
+ {FilesJson: filesJSON[:50]},
+ {FilesJson: filesJSON[50:]},
+ },
+ expectedResponse: &gitalypb.ResolveConflictsResponse{},
+ expectedContent: map[string]map[string][]byte{
+ "refs/heads/ours": {
+ "a": []byte("apricot"),
+ },
+ },
+ }
},
},
- }))
- require.NoError(t, stream.Send(&gitalypb.ResolveConflictsRequest{
- ResolveConflictsRequestPayload: &gitalypb.ResolveConflictsRequest_FilesJson{
- FilesJson: filesJSON,
- },
- }))
-
- response, err := stream.CloseAndRecv()
- require.NoError(t, err)
- require.Empty(t, response.GetResolutionError())
+ {
+ "single file with only newline",
+ func(tb testing.TB, ctx context.Context) setupData {
+ cfg, client := setupConflictsService(tb, nil)
+ repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
- require.Equal(t, []byte("contents-2\n"), gittest.Exec(t, cfg, "-C", sourceRepoPath, "cat-file", "-p", "refs/heads/source:file.txt"))
-}
+ ourCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithBranch("ours"),
+ gittest.WithTreeEntries(gittest.TreeEntry{Path: "a", Mode: "100644", Content: "\n"}))
+ theirCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithBranch("theirs"),
+ gittest.WithTreeEntries(gittest.TreeEntry{Path: "a", Mode: "100644", Content: "\n"}))
-func TestResolveConflictsLineEndings(t *testing.T) {
- ctx := testhelper.Context(t)
- hookManager := hook.NewMockManager(t, hook.NopPreReceive, hook.NopPostReceive, hook.NopUpdate, hook.NopReferenceTransaction)
- cfg, repo, repoPath, client := setupConflictsService(t, ctx, hookManager)
+ files := []map[string]interface{}{}
- ctx = testhelper.MergeOutgoingMetadata(ctx, testcfg.GitalyServersMetadataFromCfg(t, cfg))
+ filesJSON, err := json.Marshal(files)
+ require.NoError(t, err)
- for _, tc := range []struct {
- desc string
- ourContent string
- theirContent string
- resolutions []map[string]interface{}
- expectedContents string
- expectedError string
- }{
- {
- desc: "only newline",
- ourContent: "\n",
- theirContent: "\n",
- resolutions: []map[string]interface{}{},
- expectedContents: "\n",
- },
- {
- desc: "conflicting newline with embedded character",
- ourContent: "\nA\n",
- theirContent: "\nB\n",
- resolutions: []map[string]interface{}{
- {
- "old_path": "file.txt",
- "new_path": "file.txt",
- "sections": map[string]string{
- "5436437fa01a7d3e41d46741da54b451446774ca_2_2": "head",
- },
- },
- },
- expectedContents: "\nA\n",
- },
- {
- desc: "conflicting carriage-return newlines",
- ourContent: "A\r\nB\r\nC\r\nD\r\nE\r\n",
- theirContent: "A\r\nB\r\nX\r\nD\r\nE\r\n",
- resolutions: []map[string]interface{}{
- {
- "old_path": "file.txt",
- "new_path": "file.txt",
- "sections": map[string]string{
- "5436437fa01a7d3e41d46741da54b451446774ca_3_3": "origin",
- },
- },
+ return setupData{
+ cfg: cfg,
+ client: client,
+ repoPath: repoPath,
+ repo: repo,
+ requestHeader: &gitalypb.ResolveConflictsRequest_Header{
+ Header: &gitalypb.ResolveConflictsRequestHeader{
+ Repository: repo,
+ TargetRepository: repo,
+ OurCommitOid: ourCommitID.String(),
+ TheirCommitOid: theirCommitID.String(),
+ TargetBranch: []byte("theirs"),
+ SourceBranch: []byte("ours"),
+ CommitMessage: []byte(conflictResolutionCommitMessage),
+ User: user,
+ Timestamp: &timestamppb.Timestamp{},
+ },
+ },
+ requestsFilesJSON: []*gitalypb.ResolveConflictsRequest_FilesJson{
+ {FilesJson: filesJSON},
+ },
+ expectedResponse: &gitalypb.ResolveConflictsResponse{},
+ expectedContent: map[string]map[string][]byte{
+ "refs/heads/ours": {
+ "a": []byte("\n"),
+ },
+ },
+ }
},
- expectedContents: "A\r\nB\r\nX\r\nD\r\nE\r\n",
},
{
- desc: "conflict with no trailing newline",
- ourContent: "A\nB",
- theirContent: "X\nB",
- resolutions: []map[string]interface{}{
- {
- "old_path": "file.txt",
- "new_path": "file.txt",
- "sections": map[string]string{
- "5436437fa01a7d3e41d46741da54b451446774ca_1_1": "head",
- },
- },
+ "conflicting newline with embedded character",
+ func(tb testing.TB, ctx context.Context) setupData {
+ cfg, client := setupConflictsService(tb, nil)
+ repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
+
+ ourCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithBranch("ours"),
+ gittest.WithTreeEntries(gittest.TreeEntry{Path: "a", Mode: "100644", Content: "\nA\n"}))
+ theirCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithBranch("theirs"),
+ gittest.WithTreeEntries(gittest.TreeEntry{Path: "a", Mode: "100644", Content: "\nB\n"}))
+
+ files := []map[string]interface{}{
+ {
+ "old_path": "a",
+ "new_path": "a",
+ "sections": map[string]string{
+ fmt.Sprintf("%x_%d_%d", sha1.Sum([]byte("a")), 2, 2): "head",
+ },
+ },
+ }
+
+ filesJSON, err := json.Marshal(files)
+ require.NoError(t, err)
+
+ return setupData{
+ cfg: cfg,
+ client: client,
+ repoPath: repoPath,
+ repo: repo,
+ requestHeader: &gitalypb.ResolveConflictsRequest_Header{
+ Header: &gitalypb.ResolveConflictsRequestHeader{
+ Repository: repo,
+ TargetRepository: repo,
+ OurCommitOid: ourCommitID.String(),
+ TheirCommitOid: theirCommitID.String(),
+ TargetBranch: []byte("theirs"),
+ SourceBranch: []byte("ours"),
+ CommitMessage: []byte(conflictResolutionCommitMessage),
+ User: user,
+ Timestamp: &timestamppb.Timestamp{},
+ },
+ },
+ requestsFilesJSON: []*gitalypb.ResolveConflictsRequest_FilesJson{
+ {FilesJson: filesJSON},
+ },
+ expectedResponse: &gitalypb.ResolveConflictsResponse{},
+ expectedContent: map[string]map[string][]byte{
+ "refs/heads/ours": {
+ "a": []byte("\nA\n"),
+ },
+ },
+ }
},
- expectedContents: "A\nB",
},
{
- desc: "conflict with existing conflict markers",
- ourContent: "<<<<<<< HEAD\nA\nB\n=======",
- theirContent: "X\nB",
- resolutions: []map[string]interface{}{
- {
- "old_path": "file.txt",
- "new_path": "file.txt",
- "sections": map[string]string{
- "5436437fa01a7d3e41d46741da54b451446774ca_1_1": "head",
- },
- },
- },
- expectedError: `resolve: parse conflict for "file.txt": unexpected conflict delimiter`,
- },
- } {
- t.Run(tc.desc, func(t *testing.T) {
- ourOID := gittest.WriteBlob(t, cfg, repoPath, []byte(tc.ourContent))
- ourCommit := gittest.WriteCommit(t, cfg, repoPath,
- gittest.WithTreeEntries(gittest.TreeEntry{
- OID: ourOID, Path: "file.txt", Mode: "100644",
- }),
- )
- gittest.Exec(t, cfg, "-C", repoPath, "update-ref", "refs/heads/ours", ourCommit.String())
-
- theirOID := gittest.WriteBlob(t, cfg, repoPath, []byte(tc.theirContent))
- theirCommit := gittest.WriteCommit(t, cfg, repoPath,
- gittest.WithTreeEntries(gittest.TreeEntry{
- OID: theirOID, Path: "file.txt", Mode: "100644",
- }),
- )
- gittest.Exec(t, cfg, "-C", repoPath, "update-ref", "refs/heads/theirs", theirCommit.String())
-
- stream, err := client.ResolveConflicts(ctx)
- require.NoError(t, err)
-
- filesJSON, err := json.Marshal(tc.resolutions)
- require.NoError(t, err)
+ "conflicting carriage-return newlines",
+ func(tb testing.TB, ctx context.Context) setupData {
+ cfg, client := setupConflictsService(tb, nil)
+ repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
+
+ ourCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithBranch("ours"),
+ gittest.WithTreeEntries(gittest.TreeEntry{Path: "a", Mode: "100644", Content: "A\r\nB\r\nC\r\nD\r\nE\r\n"}))
+ theirCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithBranch("theirs"),
+ gittest.WithTreeEntries(gittest.TreeEntry{Path: "a", Mode: "100644", Content: "A\r\nB\r\nX\r\nD\r\nE\r\n"}))
+
+ files := []map[string]interface{}{
+ {
+ "old_path": "a",
+ "new_path": "a",
+ "sections": map[string]string{
+ fmt.Sprintf("%x_%d_%d", sha1.Sum([]byte("a")), 3, 3): "origin",
+ },
+ },
+ }
- require.NoError(t, stream.Send(&gitalypb.ResolveConflictsRequest{
- ResolveConflictsRequestPayload: &gitalypb.ResolveConflictsRequest_Header{
- Header: &gitalypb.ResolveConflictsRequestHeader{
- Repository: repo,
- TargetRepository: repo,
- CommitMessage: []byte(conflictResolutionCommitMessage),
- OurCommitOid: ourCommit.String(),
- TheirCommitOid: theirCommit.String(),
- SourceBranch: []byte("ours"),
- TargetBranch: []byte("theirs"),
- User: user,
- },
- },
- }))
- require.NoError(t, stream.Send(&gitalypb.ResolveConflictsRequest{
- ResolveConflictsRequestPayload: &gitalypb.ResolveConflictsRequest_FilesJson{
- FilesJson: filesJSON,
- },
- }))
-
- response, err := stream.CloseAndRecv()
-
- if tc.expectedError == "" {
+ filesJSON, err := json.Marshal(files)
require.NoError(t, err)
- require.Empty(t, response.GetResolutionError())
- oursFile := gittest.Exec(t, cfg, "-C", repoPath, "cat-file", "-p", "refs/heads/ours:file.txt")
- require.Equal(t, []byte(tc.expectedContents), oursFile)
- } else {
- require.Equal(t, status.Error(codes.Internal, tc.expectedError), err)
- }
- })
- }
-}
-
-func TestResolveConflictsNonOIDRequests(t *testing.T) {
- ctx := testhelper.Context(t)
- hookManager := hook.NewMockManager(t, hook.NopPreReceive, hook.NopPostReceive, hook.NopUpdate, hook.NopReferenceTransaction)
- cfg, repoProto, _, client := setupConflictsService(t, ctx, hookManager)
-
- ctx = testhelper.MergeOutgoingMetadata(ctx, testcfg.GitalyServersMetadataFromCfg(t, cfg))
-
- stream, err := client.ResolveConflicts(ctx)
- require.NoError(t, err)
-
- require.NoError(t, stream.Send(&gitalypb.ResolveConflictsRequest{
- ResolveConflictsRequestPayload: &gitalypb.ResolveConflictsRequest_Header{
- Header: &gitalypb.ResolveConflictsRequestHeader{
- Repository: repoProto,
- TargetRepository: repoProto,
- CommitMessage: []byte(conflictResolutionCommitMessage),
- OurCommitOid: "conflict-resolvable",
- TheirCommitOid: "conflict-start",
- SourceBranch: []byte("conflict-resolvable"),
- TargetBranch: []byte("conflict-start"),
- User: user,
+ return setupData{
+ cfg: cfg,
+ client: client,
+ repoPath: repoPath,
+ repo: repo,
+ requestHeader: &gitalypb.ResolveConflictsRequest_Header{
+ Header: &gitalypb.ResolveConflictsRequestHeader{
+ Repository: repo,
+ TargetRepository: repo,
+ OurCommitOid: ourCommitID.String(),
+ TheirCommitOid: theirCommitID.String(),
+ TargetBranch: []byte("theirs"),
+ SourceBranch: []byte("ours"),
+ CommitMessage: []byte(conflictResolutionCommitMessage),
+ User: user,
+ Timestamp: &timestamppb.Timestamp{},
+ },
+ },
+ requestsFilesJSON: []*gitalypb.ResolveConflictsRequest_FilesJson{
+ {FilesJson: filesJSON},
+ },
+ expectedResponse: &gitalypb.ResolveConflictsResponse{},
+ expectedContent: map[string]map[string][]byte{
+ "refs/heads/ours": {
+ "a": []byte("A\r\nB\r\nX\r\nD\r\nE\r\n"),
+ },
+ },
+ }
},
},
- }))
-
- filesJSON, err := json.Marshal(files)
- require.NoError(t, err)
- require.NoError(t, stream.Send(&gitalypb.ResolveConflictsRequest{
- ResolveConflictsRequestPayload: &gitalypb.ResolveConflictsRequest_FilesJson{
- FilesJson: filesJSON,
- },
- }))
-
- _, err = stream.CloseAndRecv()
- testhelper.RequireGrpcError(t, status.Errorf(codes.Internal, "Rugged::InvalidError: unable to parse OID - contains invalid characters"), err)
-}
-
-func TestResolveConflictsIdenticalContent(t *testing.T) {
- ctx := testhelper.Context(t)
-
- hookManager := hook.NewMockManager(t, hook.NopPreReceive, hook.NopPostReceive, hook.NopUpdate, hook.NopReferenceTransaction)
- cfg, repoProto, repoPath, client := setupConflictsService(t, ctx, hookManager)
-
- repo := localrepo.NewTestRepo(t, cfg, repoProto)
-
- sourceBranch := "conflict-resolvable"
- sourceOID, err := repo.ResolveRevision(ctx, git.Revision(sourceBranch))
- require.NoError(t, err)
-
- targetBranch := "conflict-start"
- targetOID, err := repo.ResolveRevision(ctx, git.Revision(targetBranch))
- require.NoError(t, err)
-
- tempDir := testhelper.TempDir(t)
+ {
+ "conflict with no trailing newline",
+ func(tb testing.TB, ctx context.Context) setupData {
+ cfg, client := setupConflictsService(tb, nil)
+ repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
+
+ ourCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithBranch("ours"),
+ gittest.WithTreeEntries(gittest.TreeEntry{Path: "a", Mode: "100644", Content: "A\nB"}))
+ theirCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithBranch("theirs"),
+ gittest.WithTreeEntries(gittest.TreeEntry{Path: "a", Mode: "100644", Content: "X\nB"}))
+
+ files := []map[string]interface{}{
+ {
+ "old_path": "a",
+ "new_path": "a",
+ "sections": map[string]string{
+ fmt.Sprintf("%x_%d_%d", sha1.Sum([]byte("a")), 1, 1): "head",
+ },
+ },
+ }
- var conflictingPaths []string
- for _, rev := range []string{
- sourceOID.String(),
- "6907208d755b60ebeacb2e9dfea74c92c3449a1f",
- targetOID.String(),
- } {
- contents := gittest.Exec(t, cfg, "-C", repoPath, "cat-file", "-p", rev+":files/ruby/popen.rb")
- path := filepath.Join(tempDir, rev)
- require.NoError(t, os.WriteFile(path, contents, perm.SharedFile))
- conflictingPaths = append(conflictingPaths, path)
- }
+ filesJSON, err := json.Marshal(files)
+ require.NoError(t, err)
- var conflictContents bytes.Buffer
- err = repo.ExecAndWait(ctx, git.Command{
- Name: "merge-file",
- Flags: []git.Option{
- git.Flag{Name: "--quiet"},
- git.Flag{Name: "--stdout"},
- // We pass `-L` three times for each of the conflicting files.
- git.ValueFlag{Name: "-L", Value: "files/ruby/popen.rb"},
- git.ValueFlag{Name: "-L", Value: "files/ruby/popen.rb"},
- git.ValueFlag{Name: "-L", Value: "files/ruby/popen.rb"},
+ return setupData{
+ cfg: cfg,
+ client: client,
+ repoPath: repoPath,
+ repo: repo,
+ requestHeader: &gitalypb.ResolveConflictsRequest_Header{
+ Header: &gitalypb.ResolveConflictsRequestHeader{
+ Repository: repo,
+ TargetRepository: repo,
+ OurCommitOid: ourCommitID.String(),
+ TheirCommitOid: theirCommitID.String(),
+ TargetBranch: []byte("theirs"),
+ SourceBranch: []byte("ours"),
+ CommitMessage: []byte(conflictResolutionCommitMessage),
+ User: user,
+ Timestamp: &timestamppb.Timestamp{},
+ },
+ },
+ requestsFilesJSON: []*gitalypb.ResolveConflictsRequest_FilesJson{
+ {FilesJson: filesJSON},
+ },
+ expectedResponse: &gitalypb.ResolveConflictsResponse{},
+ expectedContent: map[string]map[string][]byte{
+ "refs/heads/ours": {
+ "a": []byte("A\nB"),
+ },
+ },
+ }
+ },
},
- Args: conflictingPaths,
- }, git.WithStdout(&conflictContents))
+ {
+ "conflict with existing conflict markers",
+ func(tb testing.TB, ctx context.Context) setupData {
+ cfg, client := setupConflictsService(tb, nil)
+ repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
+
+ ourCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithBranch("ours"),
+ gittest.WithTreeEntries(gittest.TreeEntry{Path: "a", Mode: "100644", Content: "<<<<<<< HEAD\nA\nB\n======="}))
+ theirCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithBranch("theirs"),
+ gittest.WithTreeEntries(gittest.TreeEntry{Path: "a", Mode: "100644", Content: "X\nB"}))
+
+ files := []map[string]interface{}{
+ {
+ "old_path": "a",
+ "new_path": "a",
+ "sections": map[string]string{
+ fmt.Sprintf("%x_%d_%d", sha1.Sum([]byte("a")), 1, 1): "head",
+ },
+ },
+ }
- // The merge will result in a merge conflict and thus cause the command to fail.
- require.Error(t, err)
- require.Contains(t, conflictContents.String(), "<<<<<<")
+ filesJSON, err := json.Marshal(files)
+ require.NoError(t, err)
- filesJSON, err := json.Marshal([]map[string]interface{}{
- {
- "old_path": "files/ruby/popen.rb",
- "new_path": "files/ruby/popen.rb",
- "content": conflictContents.String(),
- },
- {
- "old_path": "files/ruby/regex.rb",
- "new_path": "files/ruby/regex.rb",
- "sections": map[string]string{
- "6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9": "head",
- "6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21": "origin",
- "6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49": "origin",
- },
- },
- })
- require.NoError(t, err)
-
- ctx = testhelper.MergeOutgoingMetadata(ctx, testcfg.GitalyServersMetadataFromCfg(t, cfg))
- stream, err := client.ResolveConflicts(ctx)
- require.NoError(t, err)
-
- require.NoError(t, stream.Send(&gitalypb.ResolveConflictsRequest{
- ResolveConflictsRequestPayload: &gitalypb.ResolveConflictsRequest_Header{
- Header: &gitalypb.ResolveConflictsRequestHeader{
- Repository: repoProto,
- TargetRepository: repoProto,
- CommitMessage: []byte(conflictResolutionCommitMessage),
- OurCommitOid: sourceOID.String(),
- TheirCommitOid: targetOID.String(),
- SourceBranch: []byte(sourceBranch),
- TargetBranch: []byte(targetBranch),
- User: user,
+ return setupData{
+ cfg: cfg,
+ client: client,
+ repoPath: repoPath,
+ repo: repo,
+ requestHeader: &gitalypb.ResolveConflictsRequest_Header{
+ Header: &gitalypb.ResolveConflictsRequestHeader{
+ Repository: repo,
+ TargetRepository: repo,
+ OurCommitOid: ourCommitID.String(),
+ TheirCommitOid: theirCommitID.String(),
+ TargetBranch: []byte("theirs"),
+ SourceBranch: []byte("ours"),
+ CommitMessage: []byte(conflictResolutionCommitMessage),
+ User: user,
+ Timestamp: &timestamppb.Timestamp{},
+ },
+ },
+ requestsFilesJSON: []*gitalypb.ResolveConflictsRequest_FilesJson{
+ {FilesJson: filesJSON},
+ },
+ expectedError: structerr.NewInternal(`resolve: parse conflict for "a": unexpected conflict delimiter`),
+ skipCommitCheck: true,
+ }
},
},
- }))
- require.NoError(t, stream.Send(&gitalypb.ResolveConflictsRequest{
- ResolveConflictsRequestPayload: &gitalypb.ResolveConflictsRequest_FilesJson{
- FilesJson: filesJSON,
- },
- }))
-
- response, err := stream.CloseAndRecv()
- require.NoError(t, err)
- testhelper.ProtoEqual(t, &gitalypb.ResolveConflictsResponse{
- ResolutionError: "Resolved content has no changes for file files/ruby/popen.rb",
- }, response)
-}
-
-func TestResolveConflictsStableID(t *testing.T) {
- ctx := testhelper.Context(t)
-
- hookManager := hook.NewMockManager(t, hook.NopPreReceive, hook.NopPostReceive, hook.NopUpdate, hook.NopReferenceTransaction)
- cfg, repoProto, _, client := setupConflictsService(t, ctx, hookManager)
+ {
+ "invalid OID",
+ func(tb testing.TB, ctx context.Context) setupData {
+ cfg, client := setupConflictsService(tb, nil)
+ repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
- repo := localrepo.NewTestRepo(t, cfg, repoProto)
+ gittest.WriteCommit(tb, cfg, repoPath, gittest.WithBranch("ours"),
+ gittest.WithTreeEntries(gittest.TreeEntry{Path: "a", Mode: "100644", Content: "apricot"}))
+ gittest.WriteCommit(tb, cfg, repoPath, gittest.WithBranch("theirs"),
+ gittest.WithTreeEntries(gittest.TreeEntry{Path: "a", Mode: "100644", Content: "acai"}))
- md := testcfg.GitalyServersMetadataFromCfg(t, cfg)
- ctx = testhelper.MergeOutgoingMetadata(ctx, md)
+ files := []map[string]interface{}{}
- stream, err := client.ResolveConflicts(ctx)
- require.NoError(t, err)
+ filesJSON, err := json.Marshal(files)
+ require.NoError(t, err)
- require.NoError(t, stream.Send(&gitalypb.ResolveConflictsRequest{
- ResolveConflictsRequestPayload: &gitalypb.ResolveConflictsRequest_Header{
- Header: &gitalypb.ResolveConflictsRequestHeader{
- Repository: repoProto,
- TargetRepository: repoProto,
- CommitMessage: []byte(conflictResolutionCommitMessage),
- OurCommitOid: "1450cd639e0bc6721eb02800169e464f212cde06",
- TheirCommitOid: "824be604a34828eb682305f0d963056cfac87b2d",
- SourceBranch: []byte("conflict-resolvable"),
- TargetBranch: []byte("conflict-start"),
- User: user,
- Timestamp: &timestamppb.Timestamp{Seconds: 12345},
+ return setupData{
+ cfg: cfg,
+ client: client,
+ repoPath: repoPath,
+ repo: repo,
+ requestHeader: &gitalypb.ResolveConflictsRequest_Header{
+ Header: &gitalypb.ResolveConflictsRequestHeader{
+ Repository: repo,
+ TargetRepository: repo,
+ OurCommitOid: "conflict-resolvable",
+ TheirCommitOid: "conflict-start",
+ SourceBranch: []byte("ours"),
+ TargetBranch: []byte("theirs"),
+ CommitMessage: []byte(conflictResolutionCommitMessage),
+ User: user,
+ Timestamp: &timestamppb.Timestamp{},
+ },
+ },
+ requestsFilesJSON: []*gitalypb.ResolveConflictsRequest_FilesJson{
+ {FilesJson: filesJSON},
+ },
+ expectedError: structerr.NewInternal("Rugged::InvalidError: unable to parse OID - contains invalid characters"),
+ skipCommitCheck: true,
+ }
},
},
- }))
-
- filesJSON, err := json.Marshal(files)
- require.NoError(t, err)
- require.NoError(t, stream.Send(&gitalypb.ResolveConflictsRequest{
- ResolveConflictsRequestPayload: &gitalypb.ResolveConflictsRequest_FilesJson{
- FilesJson: filesJSON,
- },
- }))
-
- response, err := stream.CloseAndRecv()
- require.NoError(t, err)
- require.Empty(t, response.GetResolutionError())
-
- resolvedCommit, err := repo.ReadCommit(ctx, git.Revision("conflict-resolvable"))
- require.NoError(t, err)
- require.Equal(t, &gitalypb.GitCommit{
- Id: "a5ad028fd739d7a054b07c293e77c5b7aecc2435",
- TreeId: "febd97e4a09e71355a513d7e0b0b3808e2dabd28",
- ParentIds: []string{
- "1450cd639e0bc6721eb02800169e464f212cde06",
- "824be604a34828eb682305f0d963056cfac87b2d",
- },
- Subject: []byte(conflictResolutionCommitMessage),
- Body: []byte(conflictResolutionCommitMessage),
- BodySize: 15,
- Author: &gitalypb.CommitAuthor{
- Name: user.Name,
- Email: user.Email,
- Date: &timestamppb.Timestamp{Seconds: 12345},
- Timezone: []byte("+0000"),
- },
- Committer: &gitalypb.CommitAuthor{
- Name: user.Name,
- Email: user.Email,
- Date: &timestamppb.Timestamp{Seconds: 12345},
- Timezone: []byte("+0000"),
- },
- }, resolvedCommit)
-}
-
-func TestFailedResolveConflictsRequestDueToResolutionError(t *testing.T) {
- ctx := testhelper.Context(t)
- hookManager := hook.NewMockManager(t, hook.NopPreReceive, hook.NopPostReceive, hook.NopUpdate, hook.NopReferenceTransaction)
- cfg, repo, _, client := setupConflictsService(t, ctx, hookManager)
+ {
+ "resolved content is same as the conflict",
+ func(tb testing.TB, ctx context.Context) setupData {
+ cfg, client := setupConflictsService(tb, nil)
+ repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
+
+ ourCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithBranch("ours"),
+ gittest.WithTreeEntries(gittest.TreeEntry{Path: "a", Mode: "100644", Content: "apricot"}))
+ theirCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithBranch("theirs"),
+ gittest.WithTreeEntries(gittest.TreeEntry{Path: "a", Mode: "100644", Content: "acai"}))
+
+ files := []map[string]interface{}{
+ {
+ "old_path": "a",
+ "new_path": "a",
+ "content": "<<<<<<< a\napricot\n=======\nacai\n>>>>>>> a\n",
+ },
+ }
- mdGS := testcfg.GitalyServersMetadataFromCfg(t, cfg)
- mdFF, _ := metadata.FromOutgoingContext(ctx)
- ctx = metadata.NewOutgoingContext(ctx, metadata.Join(mdGS, mdFF))
+ filesJSON, err := json.Marshal(files)
+ require.NoError(t, err)
- files := []map[string]interface{}{
- {
- "old_path": "files/ruby/popen.rb",
- "new_path": "files/ruby/popen.rb",
- "content": "",
+ return setupData{
+ cfg: cfg,
+ client: client,
+ repoPath: repoPath,
+ repo: repo,
+ requestHeader: &gitalypb.ResolveConflictsRequest_Header{
+ Header: &gitalypb.ResolveConflictsRequestHeader{
+ Repository: repo,
+ TargetRepository: repo,
+ OurCommitOid: ourCommitID.String(),
+ TheirCommitOid: theirCommitID.String(),
+ TargetBranch: []byte("theirs"),
+ SourceBranch: []byte("ours"),
+ CommitMessage: []byte(conflictResolutionCommitMessage),
+ User: user,
+ Timestamp: &timestamppb.Timestamp{},
+ },
+ },
+ requestsFilesJSON: []*gitalypb.ResolveConflictsRequest_FilesJson{
+ {FilesJson: filesJSON},
+ },
+ expectedResponse: &gitalypb.ResolveConflictsResponse{ResolutionError: "Resolved content has no changes for file a"},
+ skipCommitCheck: true,
+ }
+ },
},
{
- "old_path": "files/ruby/regex.rb",
- "new_path": "files/ruby/regex.rb",
- "sections": map[string]string{
- "6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9": "head",
+ "missing resolution for section",
+ func(tb testing.TB, ctx context.Context) setupData {
+ cfg, client := setupConflictsService(tb, nil)
+ repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
+
+ ourCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithBranch("ours"),
+ gittest.WithTreeEntries(gittest.TreeEntry{Path: "a", Mode: "100644", Content: "apricot"}))
+ theirCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithBranch("theirs"),
+ gittest.WithTreeEntries(gittest.TreeEntry{Path: "a", Mode: "100644", Content: "acai"}))
+
+ files := []map[string]interface{}{
+ {
+ "old_path": "a",
+ "new_path": "a",
+ "sections": map[string]string{
+ fmt.Sprintf("%x_%d_%d", sha1.Sum([]byte("a")), 4, 4): "head",
+ },
+ },
+ }
+
+ filesJSON, err := json.Marshal(files)
+ require.NoError(t, err)
+
+ return setupData{
+ cfg: cfg,
+ client: client,
+ repoPath: repoPath,
+ repo: repo,
+ requestHeader: &gitalypb.ResolveConflictsRequest_Header{
+ Header: &gitalypb.ResolveConflictsRequestHeader{
+ Repository: repo,
+ TargetRepository: repo,
+ OurCommitOid: ourCommitID.String(),
+ TheirCommitOid: theirCommitID.String(),
+ TargetBranch: []byte("theirs"),
+ SourceBranch: []byte("ours"),
+ CommitMessage: []byte(conflictResolutionCommitMessage),
+ User: user,
+ Timestamp: &timestamppb.Timestamp{},
+ },
+ },
+ requestsFilesJSON: []*gitalypb.ResolveConflictsRequest_FilesJson{
+ {FilesJson: filesJSON},
+ },
+ expectedResponse: &gitalypb.ResolveConflictsResponse{
+ ResolutionError: fmt.Sprintf("Missing resolution for section ID: %x_%d_%d", sha1.Sum([]byte("a")), 1, 1),
+ },
+ skipCommitCheck: true,
+ }
},
},
- }
- filesJSON, err := json.Marshal(files)
- require.NoError(t, err)
-
- headerRequest := &gitalypb.ResolveConflictsRequest{
- ResolveConflictsRequestPayload: &gitalypb.ResolveConflictsRequest_Header{
- Header: &gitalypb.ResolveConflictsRequestHeader{
- Repository: repo,
- TargetRepository: repo,
- CommitMessage: []byte(conflictResolutionCommitMessage),
- OurCommitOid: "1450cd639e0bc6721eb02800169e464f212cde06",
- TheirCommitOid: "824be604a34828eb682305f0d963056cfac87b2d",
- SourceBranch: []byte("conflict-resolvable"),
- TargetBranch: []byte("conflict-start"),
- User: user,
+ {
+ "empty User",
+ func(tb testing.TB, ctx context.Context) setupData {
+ cfg, client := setupConflictsService(tb, nil)
+ repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
+
+ return setupData{
+ cfg: cfg,
+ client: client,
+ repoPath: repoPath,
+ repo: repo,
+ requestHeader: &gitalypb.ResolveConflictsRequest_Header{
+ Header: &gitalypb.ResolveConflictsRequestHeader{
+ Repository: repo,
+ TargetRepository: repo,
+ OurCommitOid: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ TheirCommitOid: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ TargetBranch: []byte("theirs"),
+ SourceBranch: []byte("ours"),
+ CommitMessage: []byte(conflictResolutionCommitMessage),
+ Timestamp: &timestamppb.Timestamp{},
+ },
+ },
+ skipCommitCheck: true,
+ expectedError: structerr.NewInvalidArgument("empty User"),
+ }
},
},
- }
- filesRequest := &gitalypb.ResolveConflictsRequest{
- ResolveConflictsRequestPayload: &gitalypb.ResolveConflictsRequest_FilesJson{
- FilesJson: filesJSON,
- },
- }
-
- stream, err := client.ResolveConflicts(ctx)
- require.NoError(t, err)
- require.NoError(t, stream.Send(headerRequest))
- require.NoError(t, stream.Send(filesRequest))
-
- r, err := stream.CloseAndRecv()
- require.NoError(t, err)
- require.Equal(t, r.GetResolutionError(), "Missing resolution for section ID: 6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21")
-}
-
-func TestFailedResolveConflictsRequestDueToValidation(t *testing.T) {
- ctx := testhelper.Context(t)
- hookManager := hook.NewMockManager(t, hook.NopPreReceive, hook.NopPostReceive, hook.NopUpdate, hook.NopReferenceTransaction)
- cfg, repo, _, client := setupConflictsService(t, ctx, hookManager)
-
- mdGS := testcfg.GitalyServersMetadataFromCfg(t, cfg)
- ourCommitOid := "1450cd639e0bc6721eb02800169e464f212cde06"
- theirCommitOid := "824be604a34828eb682305f0d963056cfac87b2d"
- commitMsg := []byte(conflictResolutionCommitMessage)
- sourceBranch := []byte("conflict-resolvable")
- targetBranch := []byte("conflict-start")
-
- testCases := []struct {
- desc string
- header *gitalypb.ResolveConflictsRequestHeader
- expectedErr error
- }{
{
- desc: "empty user",
- header: &gitalypb.ResolveConflictsRequestHeader{
- User: nil,
- Repository: repo,
- OurCommitOid: ourCommitOid,
- TargetRepository: repo,
- TheirCommitOid: theirCommitOid,
- CommitMessage: commitMsg,
- SourceBranch: sourceBranch,
- TargetBranch: targetBranch,
+ "empty Repository",
+ func(tb testing.TB, ctx context.Context) setupData {
+ cfg, client := setupConflictsService(tb, nil)
+ repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
+
+ return setupData{
+ cfg: cfg,
+ client: client,
+ repoPath: repoPath,
+ repo: repo,
+ requestHeader: &gitalypb.ResolveConflictsRequest_Header{
+ Header: &gitalypb.ResolveConflictsRequestHeader{
+ User: user,
+ TargetRepository: repo,
+ OurCommitOid: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ TheirCommitOid: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ TargetBranch: []byte("theirs"),
+ SourceBranch: []byte("ours"),
+ CommitMessage: []byte(conflictResolutionCommitMessage),
+ Timestamp: &timestamppb.Timestamp{},
+ },
+ },
+ skipCommitCheck: true,
+ expectedError: structerr.NewInvalidArgument(testhelper.GitalyOrPraefect(
+ "repository not set",
+ "repo scoped: repository not set",
+ )),
+ }
},
- expectedErr: status.Error(codes.InvalidArgument, "empty User"),
},
{
- desc: "empty repo",
- header: &gitalypb.ResolveConflictsRequestHeader{
- User: user,
- Repository: nil,
- OurCommitOid: ourCommitOid,
- TargetRepository: repo,
- TheirCommitOid: theirCommitOid,
- CommitMessage: commitMsg,
- SourceBranch: sourceBranch,
- TargetBranch: targetBranch,
+ "empty TargetRepository",
+ func(tb testing.TB, ctx context.Context) setupData {
+ cfg, client := setupConflictsService(tb, nil)
+ repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
+
+ return setupData{
+ cfg: cfg,
+ client: client,
+ repoPath: repoPath,
+ repo: repo,
+ requestHeader: &gitalypb.ResolveConflictsRequest_Header{
+ Header: &gitalypb.ResolveConflictsRequestHeader{
+ User: user,
+ Repository: repo,
+ OurCommitOid: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ TheirCommitOid: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ TargetBranch: []byte("theirs"),
+ SourceBranch: []byte("ours"),
+ CommitMessage: []byte(conflictResolutionCommitMessage),
+ Timestamp: &timestamppb.Timestamp{},
+ },
+ },
+ skipCommitCheck: true,
+ expectedError: structerr.NewInvalidArgument("empty TargetRepository"),
+ }
},
- expectedErr: testhelper.GitalyOrPraefect(
- structerr.NewInvalidArgument("%w", storage.ErrRepositoryNotSet),
- structerr.NewInvalidArgument("repo scoped: %w", storage.ErrRepositoryNotSet),
- ),
},
{
- desc: "empty target repo",
- header: &gitalypb.ResolveConflictsRequestHeader{
- User: user,
- Repository: repo,
- OurCommitOid: ourCommitOid,
- TargetRepository: nil,
- TheirCommitOid: theirCommitOid,
- CommitMessage: commitMsg,
- SourceBranch: sourceBranch,
- TargetBranch: targetBranch,
+ "empty OurCommitID",
+ func(tb testing.TB, ctx context.Context) setupData {
+ cfg, client := setupConflictsService(tb, nil)
+ repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
+
+ return setupData{
+ cfg: cfg,
+ client: client,
+ repoPath: repoPath,
+ repo: repo,
+ requestHeader: &gitalypb.ResolveConflictsRequest_Header{
+ Header: &gitalypb.ResolveConflictsRequestHeader{
+ User: user,
+ Repository: repo,
+ TargetRepository: repo,
+ TheirCommitOid: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ TargetBranch: []byte("theirs"),
+ SourceBranch: []byte("ours"),
+ CommitMessage: []byte(conflictResolutionCommitMessage),
+ Timestamp: &timestamppb.Timestamp{},
+ },
+ },
+ skipCommitCheck: true,
+ expectedError: structerr.NewInvalidArgument("empty OurCommitOid"),
+ }
},
- expectedErr: status.Error(codes.InvalidArgument, "empty TargetRepository"),
},
{
- desc: "empty OurCommitId repo",
- header: &gitalypb.ResolveConflictsRequestHeader{
- User: user,
- Repository: repo,
- OurCommitOid: "",
- TargetRepository: repo,
- TheirCommitOid: theirCommitOid,
- CommitMessage: commitMsg,
- SourceBranch: sourceBranch,
- TargetBranch: targetBranch,
+ "empty TheirCommitID",
+ func(tb testing.TB, ctx context.Context) setupData {
+ cfg, client := setupConflictsService(tb, nil)
+ repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
+
+ return setupData{
+ cfg: cfg,
+ client: client,
+ repoPath: repoPath,
+ repo: repo,
+ requestHeader: &gitalypb.ResolveConflictsRequest_Header{
+ Header: &gitalypb.ResolveConflictsRequestHeader{
+ User: user,
+ Repository: repo,
+ TargetRepository: repo,
+ OurCommitOid: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ TargetBranch: []byte("theirs"),
+ SourceBranch: []byte("ours"),
+ CommitMessage: []byte(conflictResolutionCommitMessage),
+ Timestamp: &timestamppb.Timestamp{},
+ },
+ },
+ skipCommitCheck: true,
+ expectedError: structerr.NewInvalidArgument("empty TheirCommitOid"),
+ }
},
- expectedErr: status.Error(codes.InvalidArgument, "empty OurCommitOid"),
},
{
- desc: "empty TheirCommitId repo",
- header: &gitalypb.ResolveConflictsRequestHeader{
- User: user,
- Repository: repo,
- OurCommitOid: ourCommitOid,
- TargetRepository: repo,
- TheirCommitOid: "",
- CommitMessage: commitMsg,
- SourceBranch: sourceBranch,
- TargetBranch: targetBranch,
+ "empty CommitMessage",
+ func(tb testing.TB, ctx context.Context) setupData {
+ cfg, client := setupConflictsService(tb, nil)
+ repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
+
+ return setupData{
+ cfg: cfg,
+ client: client,
+ repoPath: repoPath,
+ repo: repo,
+ requestHeader: &gitalypb.ResolveConflictsRequest_Header{
+ Header: &gitalypb.ResolveConflictsRequestHeader{
+ User: user,
+ Repository: repo,
+ TargetRepository: repo,
+ OurCommitOid: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ TheirCommitOid: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ TargetBranch: []byte("theirs"),
+ SourceBranch: []byte("ours"),
+ Timestamp: &timestamppb.Timestamp{},
+ },
+ },
+ skipCommitCheck: true,
+ expectedError: structerr.NewInvalidArgument("empty CommitMessage"),
+ }
},
- expectedErr: status.Error(codes.InvalidArgument, "empty TheirCommitOid"),
},
{
- desc: "empty CommitMessage repo",
- header: &gitalypb.ResolveConflictsRequestHeader{
- User: user,
- Repository: repo,
- OurCommitOid: ourCommitOid,
- TargetRepository: repo,
- TheirCommitOid: theirCommitOid,
- CommitMessage: nil,
- SourceBranch: sourceBranch,
- TargetBranch: targetBranch,
+ "empty SourceBranch",
+ func(tb testing.TB, ctx context.Context) setupData {
+ cfg, client := setupConflictsService(tb, nil)
+ repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
+
+ return setupData{
+ cfg: cfg,
+ client: client,
+ repoPath: repoPath,
+ repo: repo,
+ requestHeader: &gitalypb.ResolveConflictsRequest_Header{
+ Header: &gitalypb.ResolveConflictsRequestHeader{
+ User: user,
+ Repository: repo,
+ TargetRepository: repo,
+ OurCommitOid: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ TheirCommitOid: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ TargetBranch: []byte("theirs"),
+ CommitMessage: []byte(conflictResolutionCommitMessage),
+ Timestamp: &timestamppb.Timestamp{},
+ },
+ },
+ skipCommitCheck: true,
+ expectedError: structerr.NewInvalidArgument("empty SourceBranch"),
+ }
},
- expectedErr: status.Error(codes.InvalidArgument, "empty CommitMessage"),
},
{
- desc: "empty SourceBranch repo",
- header: &gitalypb.ResolveConflictsRequestHeader{
- User: user,
- Repository: repo,
- OurCommitOid: ourCommitOid,
- TargetRepository: repo,
- TheirCommitOid: theirCommitOid,
- CommitMessage: commitMsg,
- SourceBranch: nil,
- TargetBranch: targetBranch,
+ "empty TargetBranch",
+ func(tb testing.TB, ctx context.Context) setupData {
+ cfg, client := setupConflictsService(tb, nil)
+ repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
+
+ return setupData{
+ cfg: cfg,
+ client: client,
+ repoPath: repoPath,
+ repo: repo,
+ requestHeader: &gitalypb.ResolveConflictsRequest_Header{
+ Header: &gitalypb.ResolveConflictsRequestHeader{
+ User: user,
+ Repository: repo,
+ TargetRepository: repo,
+ OurCommitOid: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ TheirCommitOid: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ SourceBranch: []byte("ours"),
+ CommitMessage: []byte(conflictResolutionCommitMessage),
+ Timestamp: &timestamppb.Timestamp{},
+ },
+ },
+ skipCommitCheck: true,
+ expectedError: structerr.NewInvalidArgument("empty TargetBranch"),
+ }
},
- expectedErr: status.Error(codes.InvalidArgument, "empty SourceBranch"),
},
{
- desc: "empty TargetBranch repo",
- header: &gitalypb.ResolveConflictsRequestHeader{
- User: user,
- Repository: repo,
- OurCommitOid: ourCommitOid,
- TargetRepository: repo,
- TheirCommitOid: theirCommitOid,
- CommitMessage: commitMsg,
- SourceBranch: sourceBranch,
- TargetBranch: nil,
+ "uses quarantine repo",
+ func(tb testing.TB, ctx context.Context) setupData {
+ cfg, client := setupConflictsService(tb, nil)
+ repo, repoPath := gittest.CreateRepository(tb, ctx, cfg)
+
+ testcfg.BuildGitalySSH(t, cfg)
+ testcfg.BuildGitalyHooks(t, cfg)
+
+ // We set up a custom "pre-receive" hook which simply prints the commits content to stdout and
+ // then exits with an error. Like this, we can both assert that the hook can see the
+ // quarantined tag, and it allows us to fail the RPC before we migrate quarantined objects.
+ gittest.WriteCustomHook(t, repoPath, "pre-receive", []byte(
+ `#!/bin/sh
+ read oldval newval ref &&
+ git cat-file -p $newval^{commit}:a &&
+ exit 1
+ `))
+
+ baseCommit := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithBranch("ours"),
+ gittest.WithTreeEntries(gittest.TreeEntry{Path: "a", Mode: "100644", Content: "apple"}))
+ ourCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithParents(baseCommit),
+ gittest.WithTreeEntries(gittest.TreeEntry{Path: "a", Mode: "100644", Content: "apricot"}))
+ theirCommitID := gittest.WriteCommit(tb, cfg, repoPath, gittest.WithBranch("theirs"),
+ gittest.WithParents(baseCommit),
+ gittest.WithTreeEntries(gittest.TreeEntry{Path: "a", Mode: "100644", Content: "acai"}))
+
+ files := []map[string]interface{}{
+ {
+ "old_path": "a",
+ "new_path": "a",
+ "sections": map[string]string{
+ fmt.Sprintf("%x_%d_%d", sha1.Sum([]byte("a")), 1, 1): "head",
+ },
+ },
+ }
+
+ filesJSON, err := json.Marshal(files)
+ require.NoError(t, err)
+
+ objectsBefore := len(strings.Split(text.ChompBytes(gittest.Exec(t, cfg, "-C", repoPath, "rev-list", "--objects", "--all")), "\n"))
+
+ return setupData{
+ cfg: cfg,
+ client: client,
+ repoPath: repoPath,
+ repo: repo,
+ requestHeader: &gitalypb.ResolveConflictsRequest_Header{
+ Header: &gitalypb.ResolveConflictsRequestHeader{
+ Repository: repo,
+ TargetRepository: repo,
+ OurCommitOid: ourCommitID.String(),
+ TheirCommitOid: theirCommitID.String(),
+ TargetBranch: []byte("theirs"),
+ SourceBranch: []byte("ours"),
+ CommitMessage: []byte(conflictResolutionCommitMessage),
+ User: user,
+ Timestamp: &timestamppb.Timestamp{},
+ },
+ },
+ requestsFilesJSON: []*gitalypb.ResolveConflictsRequest_FilesJson{
+ {FilesJson: filesJSON},
+ },
+ expectedContent: map[string]map[string][]byte{
+ "refs/heads/ours": {
+ "a": []byte("apple"),
+ },
+ },
+ expectedError: structerr.NewInternal("running pre-receive hooks: apricot"),
+ skipCommitCheck: true,
+ additionalChecks: func() {
+ objectsAfter := len(strings.Split(text.ChompBytes(gittest.Exec(t, cfg, "-C", repoPath, "rev-list", "--objects", "--all")), "\n"))
+ require.Equal(t, objectsBefore, objectsAfter, "No new objets should've been added")
+ },
+ }
},
- expectedErr: status.Error(codes.InvalidArgument, "empty TargetBranch"),
},
- }
+ } {
+ tc := tc
- for _, testCase := range testCases {
- t.Run(testCase.desc, func(t *testing.T) {
+ t.Run(tc.desc, func(t *testing.T) {
+ t.Parallel()
+
+ ctx := testhelper.Context(t)
+ setup := tc.setup(t, ctx)
+
+ mdGS := testcfg.GitalyServersMetadataFromCfg(t, setup.cfg)
mdFF, _ := metadata.FromOutgoingContext(ctx)
ctx = metadata.NewOutgoingContext(ctx, metadata.Join(mdGS, mdFF))
- stream, err := client.ResolveConflicts(ctx)
+ stream, err := setup.client.ResolveConflicts(ctx)
require.NoError(t, err)
- headerRequest := &gitalypb.ResolveConflictsRequest{
- ResolveConflictsRequestPayload: &gitalypb.ResolveConflictsRequest_Header{
- Header: testCase.header,
- },
+ require.NoError(t, stream.Send(&gitalypb.ResolveConflictsRequest{ResolveConflictsRequestPayload: setup.requestHeader}))
+ for _, req := range setup.requestsFilesJSON {
+ require.NoError(t, stream.Send(&gitalypb.ResolveConflictsRequest{ResolveConflictsRequestPayload: req}))
+ }
+
+ r, err := stream.CloseAndRecv()
+ testhelper.RequireGrpcError(t, setup.expectedError, err)
+ testhelper.ProtoEqual(t, setup.expectedResponse, r)
+
+ for branch, pathAndContent := range setup.expectedContent {
+ for path, content := range pathAndContent {
+ actual := gittest.Exec(t, setup.cfg, "-C", setup.repoPath, "cat-file", "-p", fmt.Sprintf("%s:%s", branch, path))
+ require.Equal(t, content, actual)
+ }
}
- require.NoError(t, stream.Send(headerRequest))
- _, err = stream.CloseAndRecv()
- testhelper.RequireGrpcError(t, testCase.expectedErr, err)
+ if setup.requestHeader.Header != nil && !setup.skipCommitCheck {
+ repo := localrepo.NewTestRepo(t, setup.cfg, setup.repo)
+ headCommit, err := repo.ReadCommit(ctx, git.Revision(setup.requestHeader.Header.SourceBranch))
+ require.NoError(t, err)
+ require.Contains(t, headCommit.ParentIds, setup.requestHeader.Header.OurCommitOid)
+ require.Contains(t, headCommit.ParentIds, setup.requestHeader.Header.TheirCommitOid)
+ require.Equal(t, headCommit.Author.Email, user.Email)
+ require.Equal(t, headCommit.Committer.Email, user.Email)
+ require.Equal(t, string(headCommit.Subject), conflictResolutionCommitMessage)
+ }
+
+ if setup.additionalChecks != nil {
+ setup.additionalChecks()
+ }
})
}
}
-
-func TestResolveConflictsQuarantine(t *testing.T) {
- t.Parallel()
-
- ctx := testhelper.Context(t)
- cfg, sourceRepoProto, sourceRepoPath, client := setupConflictsService(t, ctx, nil)
-
- testcfg.BuildGitalySSH(t, cfg)
- testcfg.BuildGitalyHooks(t, cfg)
-
- sourceBlobOID := gittest.WriteBlob(t, cfg, sourceRepoPath, []byte("contents-1\n"))
- sourceCommitOID := gittest.WriteCommit(t, cfg, sourceRepoPath,
- gittest.WithParents("1a0b36b3cdad1d2ee32457c102a8c0b7056fa863"),
- gittest.WithTreeEntries(gittest.TreeEntry{
- Path: "file.txt", OID: sourceBlobOID, Mode: "100644",
- }),
- )
- gittest.Exec(t, cfg, "-C", sourceRepoPath, "update-ref", "refs/heads/source", sourceCommitOID.String())
-
- // We set up a custom "pre-receive" hook which simply prints the new commit to stdout and
- // then exits with an error. Like this, we can both assert that the hook can see the
- // quarantined tag, and it allows us to fail the RPC before we migrate quarantined objects.
- gittest.WriteCustomHook(t, sourceRepoPath, "pre-receive", []byte(
- `#!/bin/sh
- read oldval newval ref &&
- echo $newval &&
- git cat-file -p $newval^{commit} &&
- exit 1
- `))
-
- targetRepoProto, targetRepoPath := gittest.CreateRepository(t, ctx, cfg, gittest.CreateRepositoryConfig{
- Seed: gittest.SeedGitLabTest,
- })
- targetBlobOID := gittest.WriteBlob(t, cfg, targetRepoPath, []byte("contents-2\n"))
- targetCommitOID := gittest.WriteCommit(t, cfg, targetRepoPath,
- gittest.WithParents("1a0b36b3cdad1d2ee32457c102a8c0b7056fa863"),
- gittest.WithTreeEntries(gittest.TreeEntry{
- Path: "file.txt", OID: targetBlobOID, Mode: "100644",
- }),
- )
- gittest.Exec(t, cfg, "-C", targetRepoPath, "update-ref", "refs/heads/target", targetCommitOID.String())
-
- ctx = testhelper.MergeOutgoingMetadata(ctx, testcfg.GitalyServersMetadataFromCfg(t, cfg))
-
- stream, err := client.ResolveConflicts(ctx)
- require.NoError(t, err)
-
- filesJSON, err := json.Marshal([]map[string]interface{}{
- {
- "old_path": "file.txt",
- "new_path": "file.txt",
- "sections": map[string]string{
- "5436437fa01a7d3e41d46741da54b451446774ca_1_1": "origin",
- },
- },
- })
- require.NoError(t, err)
-
- require.NoError(t, stream.Send(&gitalypb.ResolveConflictsRequest{
- ResolveConflictsRequestPayload: &gitalypb.ResolveConflictsRequest_Header{
- Header: &gitalypb.ResolveConflictsRequestHeader{
- Repository: sourceRepoProto,
- TargetRepository: targetRepoProto,
- CommitMessage: []byte(conflictResolutionCommitMessage),
- OurCommitOid: sourceCommitOID.String(),
- TheirCommitOid: targetCommitOID.String(),
- SourceBranch: []byte("source"),
- TargetBranch: []byte("target"),
- User: user,
- Timestamp: &timestamppb.Timestamp{Seconds: 12345},
- },
- },
- }))
- require.NoError(t, stream.Send(&gitalypb.ResolveConflictsRequest{
- ResolveConflictsRequestPayload: &gitalypb.ResolveConflictsRequest_FilesJson{
- FilesJson: filesJSON,
- },
- }))
-
- response, err := stream.CloseAndRecv()
- require.EqualError(t, err, `rpc error: code = Internal desc = running pre-receive hooks: af339cb882d1e3cf8d6751651e58bbaff0265d6e
-tree 89fad81bbfa38070b90ca8f4c404625bf0999013
-parent 29449b1d52cd77fd060a083a1de691bbaf12d8af
-parent 26dac52be85c92742b2c0c19eb7303de9feccb63
-author John Doe <johndoe@gitlab.com> 12345 +0000
-committer John Doe <johndoe@gitlab.com> 12345 +0000
-
-Solve conflicts`)
- require.Empty(t, response.GetResolutionError())
-
- // The file shouldn't have been updated and is thus expected to still have the same old
- // contents.
- require.Equal(t, []byte("contents-1\n"), gittest.Exec(t, cfg, "-C", sourceRepoPath, "cat-file", "-p", "refs/heads/source:file.txt"))
-
- // In case we use an object quarantine directory, the tag should not exist in the target
- // repository because the RPC failed to update the revision.
- exists, err := localrepo.NewTestRepo(t, cfg, sourceRepoProto).HasRevision(ctx, "af339cb882d1e3cf8d6751651e58bbaff0265d6e^{commit}")
- require.NoError(t, err)
- require.False(t, exists, "object should have not been migrated")
-}
diff --git a/internal/gitaly/service/conflicts/testhelper_test.go b/internal/gitaly/service/conflicts/testhelper_test.go
index 02c45f3d3..75eaf1cfa 100644
--- a/internal/gitaly/service/conflicts/testhelper_test.go
+++ b/internal/gitaly/service/conflicts/testhelper_test.go
@@ -3,10 +3,8 @@
package conflicts
import (
- "context"
"testing"
- "gitlab.com/gitlab-org/gitaly/v16/internal/git/gittest"
"gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/config"
"gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/hook"
"gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/service"
@@ -26,7 +24,7 @@ func TestMain(m *testing.M) {
testhelper.Run(m)
}
-func setupConflictsServiceWithoutRepo(tb testing.TB, hookManager hook.Manager) (config.Cfg, gitalypb.ConflictsServiceClient) {
+func setupConflictsService(tb testing.TB, hookManager hook.Manager) (config.Cfg, gitalypb.ConflictsServiceClient) {
cfg := testcfg.Build(tb)
testcfg.BuildGitalyGit2Go(tb, cfg)
@@ -40,16 +38,6 @@ func setupConflictsServiceWithoutRepo(tb testing.TB, hookManager hook.Manager) (
return cfg, client
}
-func setupConflictsService(tb testing.TB, ctx context.Context, hookManager hook.Manager) (config.Cfg, *gitalypb.Repository, string, gitalypb.ConflictsServiceClient) {
- cfg, client := setupConflictsServiceWithoutRepo(tb, hookManager)
-
- repo, repoPath := gittest.CreateRepository(tb, ctx, cfg, gittest.CreateRepositoryConfig{
- Seed: gittest.SeedGitLabTest,
- })
-
- return cfg, repo, repoPath, client
-}
-
func runConflictsServer(tb testing.TB, cfg config.Cfg, hookManager hook.Manager) string {
return testserver.RunGitalyServer(tb, cfg, func(srv *grpc.Server, deps *service.Dependencies) {
gitalypb.RegisterConflictsServiceServer(srv, NewServer(