diff options
author | Justin Tobler <jtobler@gitlab.com> | 2023-06-27 19:08:32 +0300 |
---|---|---|
committer | Justin Tobler <jtobler@gitlab.com> | 2023-06-27 19:08:32 +0300 |
commit | 102e79c5a0d30882cf5185b1b760edaed307afb9 (patch) | |
tree | b6c2bcbf0fbd2623817360a3904ea7949a342c65 | |
parent | f526f0d9c0a3a68429f07c34c075ede4a7faff10 (diff) | |
parent | 316c85c6d32c4cdbb8a6fc37d0bb2fd9e6505b5c (diff) |
Merge branch 'ps-update-reference-reports-cause' into 'master'
Log cause of the failed reference update
Closes #4677
See merge request https://gitlab.com/gitlab-org/gitaly/-/merge_requests/5950
Merged-by: Justin Tobler <jtobler@gitlab.com>
Approved-by: Justin Tobler <jtobler@gitlab.com>
Approved-by: Quang-Minh Nguyen <qmnguyen@gitlab.com>
Reviewed-by: Pavlo Strokov <pstrokov@gitlab.com>
Reviewed-by: Quang-Minh Nguyen <qmnguyen@gitlab.com>
Reviewed-by: Justin Tobler <jtobler@gitlab.com>
Co-authored-by: Pavlo Strokov <pstrokov@gitlab.com>
-rw-r--r-- | internal/git/updateref/update_with_hooks.go | 9 | ||||
-rw-r--r-- | internal/gitaly/service/operations/apply_patch_test.go | 173 | ||||
-rw-r--r-- | internal/gitaly/service/operations/branches_test.go | 15 | ||||
-rw-r--r-- | internal/gitaly/service/operations/cherry_pick_test.go | 154 | ||||
-rw-r--r-- | internal/gitaly/service/operations/commit_files_test.go | 6 | ||||
-rw-r--r-- | internal/gitaly/service/operations/merge_test.go | 40 | ||||
-rw-r--r-- | internal/gitaly/service/operations/revert_test.go | 86 | ||||
-rw-r--r-- | internal/gitaly/service/operations/tags_test.go | 241 |
8 files changed, 445 insertions, 279 deletions
diff --git a/internal/git/updateref/update_with_hooks.go b/internal/git/updateref/update_with_hooks.go index bb8599ff8..6efa375c6 100644 --- a/internal/git/updateref/update_with_hooks.go +++ b/internal/git/updateref/update_with_hooks.go @@ -115,6 +115,13 @@ type Error struct { // OldOID and NewOID are the expected object IDs previous to and after the update if it // would have succeeded. OldOID, NewOID git.ObjectID + // Cause is an actual cause of the error. + Cause error +} + +// Unwrap returns Cause. +func (e Error) Unwrap() error { + return e.Cause } func (e Error) Error() string { @@ -260,6 +267,7 @@ func (u *UpdaterWithHooks) UpdateReference( Reference: reference, OldOID: oldrev, NewOID: newrev, + Cause: err, } } @@ -272,6 +280,7 @@ func (u *UpdaterWithHooks) UpdateReference( Reference: reference, OldOID: oldrev, NewOID: newrev, + Cause: err, } } diff --git a/internal/gitaly/service/operations/apply_patch_test.go b/internal/gitaly/service/operations/apply_patch_test.go index a1e4f94d7..15790e89a 100644 --- a/internal/gitaly/service/operations/apply_patch_test.go +++ b/internal/gitaly/service/operations/apply_patch_test.go @@ -58,6 +58,13 @@ To restore the original branch and stop patching, run "git am --abort". newTree []gittest.TreeEntry } + type expected struct { + oldOID string + err error + branchCreation bool + tree []gittest.TreeEntry + } + for _, tc := range []struct { desc string // sends a request to a non-existent repository @@ -70,8 +77,6 @@ To restore the original branch and stop patching, run "git am --abort". notSentByAuthor bool // targetBranch is the branch where the patched commit goes. targetBranch string - // expectedOldOID is a function which provides the expectedOldOID given the repoPath. - expectedOldOID func(repoPath string) string // extraBranches are created with empty commits for verifying the correct base branch // gets selected. extraBranches []string @@ -81,26 +86,28 @@ To restore the original branch and stop patching, run "git am --abort". // in the series to its parent. // // After the patches are generated, they are applied sequentially on the base commit. - patches []patchDescription - expectedErr error - expectedBranchCreation bool - expectedTree []gittest.TreeEntry + patches []patchDescription + expected func(t *testing.T, repoPath string) expected }{ { desc: "non-existent repository", targetBranch: git.DefaultBranch, nonExistentRepository: true, - expectedErr: testhelper.GitalyOrPraefect( - testhelper.ToInterceptedMetadata( - structerr.New("%w", storage.NewRepositoryNotFoundError(cfg.Storages[0].Name, "doesnt-exist")), - ), - testhelper.ToInterceptedMetadata( - structerr.New( - "mutator call: route repository mutator: get repository id: %w", - storage.NewRepositoryNotFoundError(cfg.Storages[0].Name, "doesnt-exist"), + expected: func(t *testing.T, repoPath string) expected { + return expected{ + err: testhelper.GitalyOrPraefect( + testhelper.ToInterceptedMetadata( + structerr.New("%w", storage.NewRepositoryNotFoundError(cfg.Storages[0].Name, "doesnt-exist")), + ), + testhelper.ToInterceptedMetadata( + structerr.New( + "mutator call: route repository mutator: get repository id: %w", + storage.NewRepositoryNotFoundError(cfg.Storages[0].Name, "doesnt-exist"), + ), + ), ), - ), - ), + } + }, }, { desc: "creating the first branch does not work", @@ -115,7 +122,11 @@ To restore the original branch and stop patching, run "git am --abort". }, }, }, - expectedErr: status.Error(codes.Internal, "no default branch"), + expected: func(t *testing.T, repoPath string) expected { + return expected{ + err: status.Error(codes.Internal, "no default branch"), + } + }, }, { desc: "creating a new branch from HEAD works", @@ -132,9 +143,13 @@ To restore the original branch and stop patching, run "git am --abort". }, }, }, - expectedBranchCreation: true, - expectedTree: []gittest.TreeEntry{ - {Mode: "100644", Path: "file", Content: "patch 1"}, + expected: func(t *testing.T, repoPath string) expected { + return expected{ + branchCreation: true, + tree: []gittest.TreeEntry{ + {Mode: "100644", Path: "file", Content: "patch 1"}, + }, + } }, }, { @@ -152,9 +167,13 @@ To restore the original branch and stop patching, run "git am --abort". }, }, }, - expectedBranchCreation: true, - expectedTree: []gittest.TreeEntry{ - {Mode: "100644", Path: "file", Content: "patch 1"}, + expected: func(t *testing.T, repoPath string) expected { + return expected{ + branchCreation: true, + tree: []gittest.TreeEntry{ + {Mode: "100644", Path: "file", Content: "patch 1"}, + }, + } }, }, { @@ -179,8 +198,12 @@ To restore the original branch and stop patching, run "git am --abort". }, }, }, - expectedTree: []gittest.TreeEntry{ - {Mode: "100644", Path: "file", Content: "patch 2"}, + expected: func(t *testing.T, repoPath string) expected { + return expected{ + tree: []gittest.TreeEntry{ + {Mode: "100644", Path: "file", Content: "patch 2"}, + }, + } }, }, { @@ -198,8 +221,12 @@ To restore the original branch and stop patching, run "git am --abort". }, }, }, - expectedTree: []gittest.TreeEntry{ - {Mode: "100644", Path: "file", Content: "patch 1"}, + expected: func(t *testing.T, repoPath string) expected { + return expected{ + tree: []gittest.TreeEntry{ + {Mode: "100644", Path: "file", Content: "patch 1"}, + }, + } }, }, { @@ -224,8 +251,12 @@ To restore the original branch and stop patching, run "git am --abort". }, }, }, - expectedTree: []gittest.TreeEntry{ - {Mode: "100644", Path: "file", Content: "patch 1"}, + expected: func(t *testing.T, repoPath string) expected { + return expected{ + tree: []gittest.TreeEntry{ + {Mode: "100644", Path: "file", Content: "patch 1"}, + }, + } }, }, { @@ -247,7 +278,11 @@ To restore the original branch and stop patching, run "git am --abort". }, }, }, - expectedErr: errPatchingFailed, + expected: func(t *testing.T, repoPath string) expected { + return expected{ + err: errPatchingFailed, + } + }, }, { desc: "patching fails due to add-add conflict", @@ -268,7 +303,11 @@ To restore the original branch and stop patching, run "git am --abort". }, }, }, - expectedErr: errPatchingFailed, + expected: func(t *testing.T, repoPath string) expected { + return expected{ + err: errPatchingFailed, + } + }, }, { desc: "patch applies using rename detection", @@ -289,8 +328,12 @@ To restore the original branch and stop patching, run "git am --abort". }, }, }, - expectedTree: []gittest.TreeEntry{ - {Mode: "100644", Path: "moved-file", Content: "line 1\nline 2\nline 3\nline 4\nadded\n"}, + expected: func(t *testing.T, repoPath string) expected { + return expected{ + tree: []gittest.TreeEntry{ + {Mode: "100644", Path: "moved-file", Content: "line 1\nline 2\nline 3\nline 4\nadded\n"}, + }, + } }, }, { @@ -310,7 +353,11 @@ To restore the original branch and stop patching, run "git am --abort". }, }, }, - expectedErr: errPatchingFailed, + expected: func(t *testing.T, repoPath string) expected { + return expected{ + err: errPatchingFailed, + } + }, }, { desc: "existing branch + correct expectedOldOID", @@ -326,11 +373,14 @@ To restore the original branch and stop patching, run "git am --abort". }, }, }, - expectedTree: []gittest.TreeEntry{ - {Mode: "100644", Path: "file", Content: "patch 1"}, - }, - expectedOldOID: func(repoPath string) string { - return text.ChompBytes(gittest.Exec(t, cfg, "-C", repoPath, "rev-parse", git.DefaultBranch)) + expected: func(t *testing.T, repoPath string) expected { + oid := text.ChompBytes(gittest.Exec(t, cfg, "-C", repoPath, "rev-parse", git.DefaultBranch)) + return expected{ + tree: []gittest.TreeEntry{ + {Mode: "100644", Path: "file", Content: "patch 1"}, + }, + oldOID: oid, + } }, }, { @@ -347,8 +397,12 @@ To restore the original branch and stop patching, run "git am --abort". }, }, }, - expectedOldOID: func(repoPath string) string { return "foo" }, - expectedErr: structerr.NewInternal(fmt.Sprintf(`expected old object id not expected SHA format: invalid object ID: "foo", expected length %v, got 3`, gittest.DefaultObjectHash.EncodedLen())), + expected: func(t *testing.T, repoPath string) expected { + return expected{ + oldOID: "foo", + err: structerr.NewInternal(fmt.Sprintf(`expected old object id not expected SHA format: invalid object ID: "foo", expected length %v, got 3`, gittest.DefaultObjectHash.EncodedLen())), + } + }, }, { desc: "existing branch + valid but unavailable expectedOldOID", @@ -364,8 +418,12 @@ To restore the original branch and stop patching, run "git am --abort". }, }, }, - expectedOldOID: func(repoPath string) string { return gittest.DefaultObjectHash.ZeroOID.String() }, - expectedErr: structerr.NewInternal("expected old object cannot be resolved: reference not found"), + expected: func(t *testing.T, repoPath string) expected { + return expected{ + oldOID: gittest.DefaultObjectHash.ZeroOID.String(), + err: structerr.NewInternal("expected old object cannot be resolved: reference not found"), + } + }, }, { desc: "existing branch + expectedOldOID set to an old commit OID", @@ -381,15 +439,21 @@ To restore the original branch and stop patching, run "git am --abort". }, }, }, - expectedOldOID: func(repoPath string) string { + expected: func(t *testing.T, repoPath string) expected { currentCommit := text.ChompBytes(gittest.Exec(t, cfg, "-C", repoPath, "rev-parse", git.DefaultBranch)) // add a new commit to the default branch so we can point at // the old one, this is because by default the test only // creates one commit - gittest.WriteCommit(t, cfg, repoPath, gittest.WithParents(git.ObjectID(currentCommit)), gittest.WithBranch(git.DefaultBranch)) - return currentCommit + futureCommit := gittest.WriteCommit(t, cfg, repoPath, gittest.WithParents(git.ObjectID(currentCommit)), gittest.WithBranch(git.DefaultBranch)) + return expected{ + oldOID: currentCommit, + err: testhelper.WithInterceptedMetadata( + structerr.NewInternal(`update reference: Could not update %s. Please refresh and try again.`, git.DefaultRef), + "stderr", + fmt.Sprintf("fatal: prepare: cannot lock ref 'refs/heads/main': is at %s but expected %s\n", futureCommit, currentCommit), + ), + } }, - expectedErr: structerr.NewInternal(`update reference: Could not update %s. Please refresh and try again.`, git.DefaultRef), }, } { tc := tc @@ -459,10 +523,7 @@ To restore the original branch and stop patching, run "git am --abort". repoProto.RelativePath = "doesnt-exist" } - expectedOldOID := "" - if tc.expectedOldOID != nil { - expectedOldOID = tc.expectedOldOID(repoPath) - } + expectedValues := tc.expected(t, repoPath) require.NoError(t, stream.Send(&gitalypb.UserApplyPatchRequest{ UserApplyPatchRequestPayload: &gitalypb.UserApplyPatchRequest_Header_{ @@ -471,7 +532,7 @@ To restore the original branch and stop patching, run "git am --abort". User: gittest.TestUser, TargetBranch: []byte(tc.targetBranch), Timestamp: requestTimestamp, - ExpectedOldOid: expectedOldOID, + ExpectedOldOid: expectedValues.oldOID, }, }, })) @@ -504,8 +565,8 @@ To restore the original branch and stop patching, run "git am --abort". } actualResponse, err := stream.CloseAndRecv() - if tc.expectedErr != nil { - testhelper.RequireGrpcError(t, tc.expectedErr, err) + if expectedValues.err != nil { + testhelper.RequireGrpcError(t, expectedValues.err, err) return } @@ -516,7 +577,7 @@ To restore the original branch and stop patching, run "git am --abort". testhelper.ProtoEqual(t, &gitalypb.UserApplyPatchResponse{ BranchUpdate: &gitalypb.OperationBranchUpdate{ RepoCreated: false, - BranchCreated: tc.expectedBranchCreation, + BranchCreated: expectedValues.branchCreation, }, }, actualResponse) @@ -549,7 +610,7 @@ To restore the original branch and stop patching, run "git am --abort". actualCommit, ) - gittest.RequireTree(t, cfg, repoPath, commitID, tc.expectedTree) + gittest.RequireTree(t, cfg, repoPath, commitID, expectedValues.tree) }) } } diff --git a/internal/gitaly/service/operations/branches_test.go b/internal/gitaly/service/operations/branches_test.go index 0a58649f1..8c93af9f8 100644 --- a/internal/gitaly/service/operations/branches_test.go +++ b/internal/gitaly/service/operations/branches_test.go @@ -28,6 +28,7 @@ import ( "gitlab.com/gitlab-org/gitaly/v16/internal/testhelper/testserver" "gitlab.com/gitlab-org/gitaly/v16/internal/transaction/txinfo" "gitlab.com/gitlab-org/gitaly/v16/proto/go/gitalypb" + "gitlab.com/gitlab-org/gitaly/v16/proto/go/gitalypb/testproto" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -391,7 +392,11 @@ func TestUserCreateBranch_Failure(t *testing.T) { branchName: "master", startPoint: "master", user: gittest.TestUser, - err: status.Errorf(codes.FailedPrecondition, "Could not update refs/heads/master. Please refresh and try again."), + err: testhelper.WithInterceptedMetadata( + structerr.NewFailedPrecondition("Could not update refs/heads/master. Please refresh and try again."), + "stderr", + "fatal: prepare: cannot lock ref 'refs/heads/master': reference already exists\n", + ), }, { desc: "conflicting with refs/heads/improve/awesome", @@ -592,7 +597,7 @@ func TestUserDeleteBranch(t *testing.T) { repoProto, repoPath := gittest.CreateRepository(t, ctx, cfg) firstCommit := gittest.WriteCommit(t, cfg, repoPath) - gittest.WriteCommit(t, cfg, repoPath, gittest.WithBranch("master"), gittest.WithParents(firstCommit)) + secondCommit := gittest.WriteCommit(t, cfg, repoPath, gittest.WithBranch("master"), gittest.WithParents(firstCommit)) gittest.Exec(t, cfg, "-C", repoPath, "branch", branchName, "master") @@ -604,7 +609,11 @@ func TestUserDeleteBranch(t *testing.T) { ExpectedOldOid: firstCommit.String(), }, repoPath: repoPath, - expectedErr: structerr.NewFailedPrecondition("reference update failed: Could not update refs/heads/random-branch. Please refresh and try again."). + expectedErr: structerr.NewFailedPrecondition("reference update failed: Could not update refs/heads/%s. Please refresh and try again.", branchName). + WithDetail(&testproto.ErrorMetadata{ + Key: []byte("stderr"), + Value: []byte(fmt.Sprintf("fatal: prepare: cannot lock ref 'refs/heads/%s': is at %s but expected %s\n", branchName, secondCommit, firstCommit)), + }). WithDetail(&gitalypb.UserDeleteBranchError{ Error: &gitalypb.UserDeleteBranchError_ReferenceUpdate{ ReferenceUpdate: &gitalypb.ReferenceUpdateError{ diff --git a/internal/gitaly/service/operations/cherry_pick_test.go b/internal/gitaly/service/operations/cherry_pick_test.go index 3b487300f..87a30d55e 100644 --- a/internal/gitaly/service/operations/cherry_pick_test.go +++ b/internal/gitaly/service/operations/cherry_pick_test.go @@ -42,13 +42,12 @@ func TestUserCherryPick(t *testing.T) { } testCases := []struct { - desc string - setup func(data setupData) (*gitalypb.UserCherryPickRequest, *gitalypb.UserCherryPickResponse) - expectedErr error + desc string + setup func(data setupData) (*gitalypb.UserCherryPickRequest, *gitalypb.UserCherryPickResponse, error) }{ { desc: "branch exists", - setup: func(data setupData) (*gitalypb.UserCherryPickRequest, *gitalypb.UserCherryPickResponse) { + setup: func(data setupData) (*gitalypb.UserCherryPickRequest, *gitalypb.UserCherryPickResponse, error) { gittest.Exec(t, data.cfg, "-C", data.repoPath, "branch", destinationBranch, "master") return &gitalypb.UserCherryPickRequest{ @@ -57,14 +56,16 @@ func TestUserCherryPick(t *testing.T) { Commit: data.cherryPickedCommit, BranchName: []byte(destinationBranch), Message: []byte("Cherry-picking " + data.cherryPickedCommit.Id), - }, &gitalypb.UserCherryPickResponse{ + }, + &gitalypb.UserCherryPickResponse{ BranchUpdate: &gitalypb.OperationBranchUpdate{}, - } + }, + nil }, }, { desc: "branch exists + expectedOldOID", - setup: func(data setupData) (*gitalypb.UserCherryPickRequest, *gitalypb.UserCherryPickResponse) { + setup: func(data setupData) (*gitalypb.UserCherryPickRequest, *gitalypb.UserCherryPickResponse, error) { gittest.Exec(t, data.cfg, "-C", data.repoPath, "branch", destinationBranch, "master") return &gitalypb.UserCherryPickRequest{ @@ -74,14 +75,16 @@ func TestUserCherryPick(t *testing.T) { BranchName: []byte(destinationBranch), Message: []byte("Cherry-picking " + data.cherryPickedCommit.Id), ExpectedOldOid: data.masterCommit, - }, &gitalypb.UserCherryPickResponse{ + }, + &gitalypb.UserCherryPickResponse{ BranchUpdate: &gitalypb.OperationBranchUpdate{}, - } + }, + nil }, }, { desc: "nonexistent branch + start_repository == repository", - setup: func(data setupData) (*gitalypb.UserCherryPickRequest, *gitalypb.UserCherryPickResponse) { + setup: func(data setupData) (*gitalypb.UserCherryPickRequest, *gitalypb.UserCherryPickResponse, error) { return &gitalypb.UserCherryPickRequest{ Repository: data.repoProto, User: gittest.TestUser, @@ -89,14 +92,16 @@ func TestUserCherryPick(t *testing.T) { BranchName: []byte(destinationBranch), Message: []byte("Cherry-picking " + data.cherryPickedCommit.Id), StartBranchName: []byte("master"), - }, &gitalypb.UserCherryPickResponse{ + }, + &gitalypb.UserCherryPickResponse{ BranchUpdate: &gitalypb.OperationBranchUpdate{BranchCreated: true}, - } + }, + nil }, }, { desc: "nonexistent branch + start_repository != repository", - setup: func(data setupData) (*gitalypb.UserCherryPickRequest, *gitalypb.UserCherryPickResponse) { + setup: func(data setupData) (*gitalypb.UserCherryPickRequest, *gitalypb.UserCherryPickResponse, error) { return &gitalypb.UserCherryPickRequest{ Repository: data.repoProto, User: gittest.TestUser, @@ -105,14 +110,16 @@ func TestUserCherryPick(t *testing.T) { Message: []byte("Cherry-picking " + data.cherryPickedCommit.Id), StartBranchName: []byte("master"), StartRepository: data.copyRepoProto, - }, &gitalypb.UserCherryPickResponse{ + }, + &gitalypb.UserCherryPickResponse{ BranchUpdate: &gitalypb.OperationBranchUpdate{BranchCreated: true}, - } + }, + nil }, }, { desc: "nonexistent branch + empty start_repository", - setup: func(data setupData) (*gitalypb.UserCherryPickRequest, *gitalypb.UserCherryPickResponse) { + setup: func(data setupData) (*gitalypb.UserCherryPickRequest, *gitalypb.UserCherryPickResponse, error) { return &gitalypb.UserCherryPickRequest{ Repository: data.repoProto, User: gittest.TestUser, @@ -120,14 +127,16 @@ func TestUserCherryPick(t *testing.T) { BranchName: []byte(destinationBranch), Message: []byte("Cherry-picking " + data.cherryPickedCommit.Id), StartBranchName: []byte("master"), - }, &gitalypb.UserCherryPickResponse{ + }, + &gitalypb.UserCherryPickResponse{ BranchUpdate: &gitalypb.OperationBranchUpdate{BranchCreated: true}, - } + }, + nil }, }, { desc: "branch exists with dry run", - setup: func(data setupData) (*gitalypb.UserCherryPickRequest, *gitalypb.UserCherryPickResponse) { + setup: func(data setupData) (*gitalypb.UserCherryPickRequest, *gitalypb.UserCherryPickResponse, error) { gittest.Exec(t, data.cfg, "-C", data.repoPath, "branch", destinationBranch, "master") return &gitalypb.UserCherryPickRequest{ @@ -138,14 +147,16 @@ func TestUserCherryPick(t *testing.T) { Message: []byte("Cherry-picking " + data.cherryPickedCommit.Id), StartBranchName: []byte("master"), DryRun: true, - }, &gitalypb.UserCherryPickResponse{ + }, + &gitalypb.UserCherryPickResponse{ BranchUpdate: &gitalypb.OperationBranchUpdate{}, - } + }, + nil }, }, { desc: "nonexistent branch + start_repository == repository with dry run", - setup: func(data setupData) (*gitalypb.UserCherryPickRequest, *gitalypb.UserCherryPickResponse) { + setup: func(data setupData) (*gitalypb.UserCherryPickRequest, *gitalypb.UserCherryPickResponse, error) { return &gitalypb.UserCherryPickRequest{ Repository: data.repoProto, User: gittest.TestUser, @@ -154,14 +165,16 @@ func TestUserCherryPick(t *testing.T) { Message: []byte("Cherry-picking " + data.cherryPickedCommit.Id), StartBranchName: []byte("master"), DryRun: true, - }, &gitalypb.UserCherryPickResponse{ + }, + &gitalypb.UserCherryPickResponse{ BranchUpdate: &gitalypb.OperationBranchUpdate{BranchCreated: true}, - } + }, + nil }, }, { desc: "nonexistent branch + start_repository != repository with dry run", - setup: func(data setupData) (*gitalypb.UserCherryPickRequest, *gitalypb.UserCherryPickResponse) { + setup: func(data setupData) (*gitalypb.UserCherryPickRequest, *gitalypb.UserCherryPickResponse, error) { return &gitalypb.UserCherryPickRequest{ Repository: data.repoProto, User: gittest.TestUser, @@ -171,14 +184,16 @@ func TestUserCherryPick(t *testing.T) { StartBranchName: []byte("master"), StartRepository: data.copyRepoProto, DryRun: true, - }, &gitalypb.UserCherryPickResponse{ + }, + &gitalypb.UserCherryPickResponse{ BranchUpdate: &gitalypb.OperationBranchUpdate{BranchCreated: true}, - } + }, + nil }, }, { desc: "nonexistent branch + empty start_repository with dry run", - setup: func(data setupData) (*gitalypb.UserCherryPickRequest, *gitalypb.UserCherryPickResponse) { + setup: func(data setupData) (*gitalypb.UserCherryPickRequest, *gitalypb.UserCherryPickResponse, error) { return &gitalypb.UserCherryPickRequest{ Repository: data.repoProto, User: gittest.TestUser, @@ -187,53 +202,57 @@ func TestUserCherryPick(t *testing.T) { Message: []byte("Cherry-picking " + data.cherryPickedCommit.Id), StartBranchName: []byte("master"), DryRun: true, - }, &gitalypb.UserCherryPickResponse{ + }, + &gitalypb.UserCherryPickResponse{ BranchUpdate: &gitalypb.OperationBranchUpdate{BranchCreated: true}, - } + }, + nil }, }, { desc: "invalid expectedOldOID", - setup: func(data setupData) (*gitalypb.UserCherryPickRequest, *gitalypb.UserCherryPickResponse) { + setup: func(data setupData) (*gitalypb.UserCherryPickRequest, *gitalypb.UserCherryPickResponse, error) { gittest.Exec(t, data.cfg, "-C", data.repoPath, "branch", destinationBranch, "master") return &gitalypb.UserCherryPickRequest{ - Repository: data.repoProto, - User: gittest.TestUser, - Commit: data.cherryPickedCommit, - BranchName: []byte(destinationBranch), - Message: []byte("Cherry-picking " + data.cherryPickedCommit.Id), - ExpectedOldOid: "foobar", - }, &gitalypb.UserCherryPickResponse{} + Repository: data.repoProto, + User: gittest.TestUser, + Commit: data.cherryPickedCommit, + BranchName: []byte(destinationBranch), + Message: []byte("Cherry-picking " + data.cherryPickedCommit.Id), + ExpectedOldOid: "foobar", + }, + &gitalypb.UserCherryPickResponse{}, + testhelper.WithInterceptedMetadata( + structerr.NewInvalidArgument(fmt.Sprintf("invalid expected old object ID: invalid object ID: \"foobar\", expected length %v, got 6", gittest.DefaultObjectHash.EncodedLen())), + "old_object_id", "foobar") }, - expectedErr: testhelper.WithInterceptedMetadata( - structerr.NewInvalidArgument(fmt.Sprintf("invalid expected old object ID: invalid object ID: \"foobar\", expected length %v, got 6", gittest.DefaultObjectHash.EncodedLen())), - "old_object_id", "foobar"), }, { desc: "valid but non-existent expectedOldOID", - setup: func(data setupData) (*gitalypb.UserCherryPickRequest, *gitalypb.UserCherryPickResponse) { + setup: func(data setupData) (*gitalypb.UserCherryPickRequest, *gitalypb.UserCherryPickResponse, error) { gittest.Exec(t, data.cfg, "-C", data.repoPath, "branch", destinationBranch, "master") return &gitalypb.UserCherryPickRequest{ - Repository: data.repoProto, - User: gittest.TestUser, - Commit: data.cherryPickedCommit, - BranchName: []byte(destinationBranch), - Message: []byte("Cherry-picking " + data.cherryPickedCommit.Id), - ExpectedOldOid: gittest.DefaultObjectHash.ZeroOID.String(), - }, &gitalypb.UserCherryPickResponse{} + Repository: data.repoProto, + User: gittest.TestUser, + Commit: data.cherryPickedCommit, + BranchName: []byte(destinationBranch), + Message: []byte("Cherry-picking " + data.cherryPickedCommit.Id), + ExpectedOldOid: gittest.DefaultObjectHash.ZeroOID.String(), + }, + &gitalypb.UserCherryPickResponse{}, + testhelper.WithInterceptedMetadata( + structerr.NewInvalidArgument("cannot resolve expected old object ID: reference not found"), + "old_object_id", gittest.DefaultObjectHash.ZeroOID) }, - expectedErr: testhelper.WithInterceptedMetadata( - structerr.NewInvalidArgument("cannot resolve expected old object ID: reference not found"), - "old_object_id", gittest.DefaultObjectHash.ZeroOID), }, { desc: "incorrect expectedOldOID", - setup: func(data setupData) (*gitalypb.UserCherryPickRequest, *gitalypb.UserCherryPickResponse) { + setup: func(data setupData) (*gitalypb.UserCherryPickRequest, *gitalypb.UserCherryPickResponse, error) { gittest.Exec(t, data.cfg, "-C", data.repoPath, "branch", destinationBranch, "master") - gittest.WriteCommit(t, data.cfg, data.repoPath, + commit := gittest.WriteCommit(t, data.cfg, data.repoPath, gittest.WithParents(git.ObjectID(data.masterCommit)), gittest.WithBranch("master"), gittest.WithTreeEntries( @@ -242,15 +261,20 @@ func TestUserCherryPick(t *testing.T) { ) return &gitalypb.UserCherryPickRequest{ - Repository: data.repoProto, - User: gittest.TestUser, - Commit: data.cherryPickedCommit, - BranchName: []byte("master"), - Message: []byte("Cherry-picking " + data.cherryPickedCommit.Id), - ExpectedOldOid: data.masterCommit, - }, &gitalypb.UserCherryPickResponse{} + Repository: data.repoProto, + User: gittest.TestUser, + Commit: data.cherryPickedCommit, + BranchName: []byte("master"), + Message: []byte("Cherry-picking " + data.cherryPickedCommit.Id), + ExpectedOldOid: data.masterCommit, + }, + &gitalypb.UserCherryPickResponse{}, + testhelper.WithInterceptedMetadata( + structerr.NewInternal("update reference with hooks: Could not update refs/heads/master. Please refresh and try again."), + "stderr", + fmt.Sprintf("fatal: prepare: cannot lock ref 'refs/heads/master': is at %s but expected %s\n", commit, data.masterCommit), + ) }, - expectedErr: structerr.NewInternal("update reference with hooks: Could not update refs/heads/master. Please refresh and try again."), }, } @@ -292,7 +316,7 @@ func TestUserCherryPick(t *testing.T) { copyCherryPickedCommit, err := copyRepo.ReadCommit(ctx, cherryPickCommitCopyID.Revision()) require.NoError(t, err) - req, expectedResp := tc.setup(setupData{ + req, expectedResp, expectedErr := tc.setup(setupData{ cfg: cfg, repoPath: repoPath, repoProto: repoProto, @@ -304,8 +328,8 @@ func TestUserCherryPick(t *testing.T) { }) resp, err := client.UserCherryPick(ctx, req) - if tc.expectedErr != nil || err != nil { - testhelper.RequireGrpcError(t, tc.expectedErr, err) + if expectedErr != nil || err != nil { + testhelper.RequireGrpcError(t, expectedErr, err) return } diff --git a/internal/gitaly/service/operations/commit_files_test.go b/internal/gitaly/service/operations/commit_files_test.go index 4ad7641b3..77ba2a760 100644 --- a/internal/gitaly/service/operations/commit_files_test.go +++ b/internal/gitaly/service/operations/commit_files_test.go @@ -1164,7 +1164,11 @@ func testSuccessfulUserCommitFilesRequest(t *testing.T, ctx context.Context) { repoPath: repoPath, branchName: "few-commits", expectedOldOID: text.ChompBytes(gittest.Exec(t, cfg, "-C", repoPath, "rev-parse", "refs/heads/few-commits~1")), - expectedError: structerr.NewFailedPrecondition("Could not update refs/heads/few-commits. Please refresh and try again."), + expectedError: testhelper.WithInterceptedMetadata( + structerr.NewFailedPrecondition("Could not update refs/heads/few-commits. Please refresh and try again."), + "stderr", + "fatal: prepare: cannot lock ref 'refs/heads/few-commits': is at 0031876facac3f2b2702a0e53a26e89939a42209 but expected bf6e164cac2dc32b1f391ca4290badcbe4ffc5fb\n", + ), }, { desc: "existing repo, new branch", diff --git a/internal/gitaly/service/operations/merge_test.go b/internal/gitaly/service/operations/merge_test.go index bc5c78e42..c48fbb848 100644 --- a/internal/gitaly/service/operations/merge_test.go +++ b/internal/gitaly/service/operations/merge_test.go @@ -28,6 +28,7 @@ import ( "gitlab.com/gitlab-org/gitaly/v16/internal/testhelper/testcfg" "gitlab.com/gitlab-org/gitaly/v16/internal/testhelper/testserver" "gitlab.com/gitlab-org/gitaly/v16/proto/go/gitalypb" + "gitlab.com/gitlab-org/gitaly/v16/proto/go/gitalypb/testproto" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" @@ -169,7 +170,7 @@ func testUserMergeBranch(t *testing.T, ctx context.Context) { desc: "incorrect expectedOldOID", hooks: []string{}, setup: func(data setupData) setupResponse { - gittest.WriteCommit(t, cfg, data.repoPath, + secondCommit := gittest.WriteCommit(t, cfg, data.repoPath, gittest.WithParents(git.ObjectID(data.masterCommit)), gittest.WithBranch(data.branch), gittest.WithTreeEntries( @@ -190,8 +191,12 @@ func testUserMergeBranch(t *testing.T, ctx context.Context) { secondRequest: &gitalypb.UserMergeBranchRequest{Apply: true}, secondExpectedResponse: &gitalypb.OperationBranchUpdate{}, secondExpectedErr: func(response *gitalypb.UserMergeBranchResponse) error { - return structerr.NewFailedPrecondition("Could not update refs/heads/master. Please refresh and try again.").WithDetail( - &gitalypb.UserMergeBranchError{ + return structerr.NewFailedPrecondition("Could not update refs/heads/master. Please refresh and try again."). + WithDetail(&testproto.ErrorMetadata{ + Key: []byte("stderr"), + Value: []byte(fmt.Sprintf("fatal: prepare: cannot lock ref 'refs/heads/master': is at %s but expected %s\n", secondCommit, data.masterCommit)), + }). + WithDetail(&gitalypb.UserMergeBranchError{ Error: &gitalypb.UserMergeBranchError_ReferenceUpdate{ ReferenceUpdate: &gitalypb.ReferenceUpdateError{ ReferenceName: []byte("refs/heads/" + data.branch), @@ -199,8 +204,7 @@ func testUserMergeBranch(t *testing.T, ctx context.Context) { NewOid: response.GetCommitId(), }, }, - }, - ) + }) }, } }, @@ -717,17 +721,23 @@ func testUserMergeBranchConcurrentUpdate(t *testing.T, ctx context.Context) { require.NoError(t, mergeBidi.CloseSend(), "close send") secondResponse, err := mergeBidi.Recv() - testhelper.RequireGrpcError(t, structerr.NewFailedPrecondition("Could not update refs/heads/gitaly-merge-test-branch. Please refresh and try again.").WithDetail( - &gitalypb.UserMergeBranchError{ - Error: &gitalypb.UserMergeBranchError_ReferenceUpdate{ - ReferenceUpdate: &gitalypb.ReferenceUpdateError{ - ReferenceName: []byte("refs/heads/" + mergeBranchName), - OldOid: "281d3a76f31c812dbf48abce82ccf6860adedd81", - NewOid: "f0165798887392f9148b55d54a832b005f93a38c", + testhelper.RequireGrpcError(t, + structerr.NewFailedPrecondition("Could not update refs/heads/gitaly-merge-test-branch. Please refresh and try again."). + WithDetail(&testproto.ErrorMetadata{ + Key: []byte("stderr"), + Value: []byte("fatal: prepare: cannot lock ref 'refs/heads/gitaly-merge-test-branch': is at 549090fbeacc6607bc70648d3ba554c355e670c5 but expected 281d3a76f31c812dbf48abce82ccf6860adedd81\n"), + }). + WithDetail(&gitalypb.UserMergeBranchError{ + Error: &gitalypb.UserMergeBranchError_ReferenceUpdate{ + ReferenceUpdate: &gitalypb.ReferenceUpdateError{ + ReferenceName: []byte("refs/heads/" + mergeBranchName), + OldOid: "281d3a76f31c812dbf48abce82ccf6860adedd81", + NewOid: "f0165798887392f9148b55d54a832b005f93a38c", + }, }, - }, - }, - ), err) + }), + err, + ) require.Nil(t, secondResponse) commit, err := repo.ReadCommit(ctx, git.Revision(mergeBranchName)) diff --git a/internal/gitaly/service/operations/revert_test.go b/internal/gitaly/service/operations/revert_test.go index 40c84ada0..16f167a9a 100644 --- a/internal/gitaly/service/operations/revert_test.go +++ b/internal/gitaly/service/operations/revert_test.go @@ -31,13 +31,13 @@ func TestUserRevert(t *testing.T) { expectedCommitID string repoPath string request *gitalypb.UserRevertRequest + expectedResponse *gitalypb.UserRevertResponse + expectedError error } testCases := []struct { - desc string - setup func(t *testing.T, repoPath string, repoProto *gitalypb.Repository, repo *localrepo.Repo) setupData - expectedResponse *gitalypb.UserRevertResponse - expectedErr error + desc string + setup func(t *testing.T, repoPath string, repoProto *gitalypb.Repository, repo *localrepo.Repo) setupData }{ { desc: "successful", @@ -57,10 +57,10 @@ func TestUserRevert(t *testing.T) { BranchName: []byte(branchName), Message: []byte("Reverting " + firstCommitID), }, + expectedResponse: &gitalypb.UserRevertResponse{BranchUpdate: &gitalypb.OperationBranchUpdate{}}, + expectedError: nil, } }, - expectedResponse: &gitalypb.UserRevertResponse{BranchUpdate: &gitalypb.OperationBranchUpdate{}}, - expectedErr: nil, }, { desc: "nonexistent branch + start_repository == repository", @@ -81,12 +81,12 @@ func TestUserRevert(t *testing.T) { StartBranchName: []byte("master"), Message: []byte("Reverting " + firstCommitID), }, + expectedResponse: &gitalypb.UserRevertResponse{BranchUpdate: &gitalypb.OperationBranchUpdate{ + BranchCreated: true, + }}, + expectedError: nil, } }, - expectedResponse: &gitalypb.UserRevertResponse{BranchUpdate: &gitalypb.OperationBranchUpdate{ - BranchCreated: true, - }}, - expectedErr: nil, }, { desc: "nonexistent branch + start_repository != repository", @@ -111,10 +111,10 @@ func TestUserRevert(t *testing.T) { StartRepository: startRepoProto, Message: []byte("Reverting " + firstCommitID), }, + expectedResponse: &gitalypb.UserRevertResponse{BranchUpdate: &gitalypb.OperationBranchUpdate{BranchCreated: true, RepoCreated: true}}, + expectedError: nil, } }, - expectedResponse: &gitalypb.UserRevertResponse{BranchUpdate: &gitalypb.OperationBranchUpdate{BranchCreated: true, RepoCreated: true}}, - expectedErr: nil, }, { desc: "successful with dry run", @@ -136,10 +136,10 @@ func TestUserRevert(t *testing.T) { DryRun: true, }, expectedCommitID: firstCommit.Id, + expectedResponse: &gitalypb.UserRevertResponse{BranchUpdate: &gitalypb.OperationBranchUpdate{}}, + expectedError: nil, } }, - expectedResponse: &gitalypb.UserRevertResponse{BranchUpdate: &gitalypb.OperationBranchUpdate{}}, - expectedErr: nil, }, { desc: "nonexistent branch + start_repository == repository with dry run", @@ -162,12 +162,12 @@ func TestUserRevert(t *testing.T) { DryRun: true, }, expectedCommitID: firstCommit.Id, + expectedResponse: &gitalypb.UserRevertResponse{BranchUpdate: &gitalypb.OperationBranchUpdate{ + BranchCreated: true, + }}, + expectedError: nil, } }, - expectedResponse: &gitalypb.UserRevertResponse{BranchUpdate: &gitalypb.OperationBranchUpdate{ - BranchCreated: true, - }}, - expectedErr: nil, }, { desc: "nonexistent branch + start_repository != repository with dry run", @@ -194,10 +194,10 @@ func TestUserRevert(t *testing.T) { DryRun: true, }, expectedCommitID: firstCommitID.String(), + expectedResponse: &gitalypb.UserRevertResponse{BranchUpdate: &gitalypb.OperationBranchUpdate{BranchCreated: true, RepoCreated: true}}, + expectedError: nil, } }, - expectedResponse: &gitalypb.UserRevertResponse{BranchUpdate: &gitalypb.OperationBranchUpdate{BranchCreated: true, RepoCreated: true}}, - expectedErr: nil, }, { @@ -207,12 +207,12 @@ func TestUserRevert(t *testing.T) { request: &gitalypb.UserRevertRequest{ Repository: nil, }, + expectedError: testhelper.GitalyOrPraefect( + structerr.NewInvalidArgument("%w", storage.ErrRepositoryNotSet), + structerr.NewInvalidArgument("repo scoped: %w", storage.ErrRepositoryNotSet), + ), } }, - expectedErr: testhelper.GitalyOrPraefect( - structerr.NewInvalidArgument("%w", storage.ErrRepositoryNotSet), - structerr.NewInvalidArgument("repo scoped: %w", storage.ErrRepositoryNotSet), - ), }, { desc: "empty user", @@ -221,9 +221,9 @@ func TestUserRevert(t *testing.T) { request: &gitalypb.UserRevertRequest{ Repository: repoProto, }, + expectedError: structerr.NewInvalidArgument("empty User"), } }, - expectedErr: structerr.NewInvalidArgument("empty User"), }, { desc: "empty commit", @@ -233,9 +233,9 @@ func TestUserRevert(t *testing.T) { Repository: repoProto, User: gittest.TestUser, }, + expectedError: structerr.NewInvalidArgument("empty Commit"), } }, - expectedErr: structerr.NewInvalidArgument("empty Commit"), }, { desc: "empty branch name", @@ -253,9 +253,9 @@ func TestUserRevert(t *testing.T) { Commit: firstCommit, Message: []byte("Reverting " + firstCommitID), }, + expectedError: structerr.NewInvalidArgument("empty BranchName"), } }, - expectedErr: structerr.NewInvalidArgument("empty BranchName"), }, { desc: "empty message", @@ -273,9 +273,9 @@ func TestUserRevert(t *testing.T) { Commit: firstCommit, BranchName: []byte(branchName), }, + expectedError: structerr.NewInvalidArgument("empty Message"), } }, - expectedErr: structerr.NewInvalidArgument("empty Message"), }, { desc: "successful + expectedOldOID", @@ -296,10 +296,10 @@ func TestUserRevert(t *testing.T) { Message: []byte("Reverting " + firstCommitID), ExpectedOldOid: firstCommitID.String(), }, + expectedResponse: &gitalypb.UserRevertResponse{BranchUpdate: &gitalypb.OperationBranchUpdate{}}, + expectedError: nil, } }, - expectedResponse: &gitalypb.UserRevertResponse{BranchUpdate: &gitalypb.OperationBranchUpdate{}}, - expectedErr: nil, }, { desc: "successful + invalid expectedOldOID", @@ -319,11 +319,11 @@ func TestUserRevert(t *testing.T) { Message: []byte("Reverting " + firstCommitID), ExpectedOldOid: "foobar", }, + expectedError: testhelper.WithInterceptedMetadata( + structerr.NewInvalidArgument(fmt.Sprintf(`invalid expected old object ID: invalid object ID: "foobar", expected length %v, got 6`, gittest.DefaultObjectHash.EncodedLen())), + "old_object_id", "foobar"), } }, - expectedErr: testhelper.WithInterceptedMetadata( - structerr.NewInvalidArgument(fmt.Sprintf(`invalid expected old object ID: invalid object ID: "foobar", expected length %v, got 6`, gittest.DefaultObjectHash.EncodedLen())), - "old_object_id", "foobar"), }, { desc: "expectedOldOID with valid SHA, but not present in repo", @@ -343,11 +343,11 @@ func TestUserRevert(t *testing.T) { Message: []byte("Reverting " + firstCommitID), ExpectedOldOid: gittest.DefaultObjectHash.ZeroOID.String(), }, + expectedError: testhelper.WithInterceptedMetadata( + structerr.NewInvalidArgument("cannot resolve expected old object ID: reference not found"), + "old_object_id", gittest.DefaultObjectHash.ZeroOID), } }, - expectedErr: testhelper.WithInterceptedMetadata( - structerr.NewInvalidArgument("cannot resolve expected old object ID: reference not found"), - "old_object_id", gittest.DefaultObjectHash.ZeroOID), }, { desc: "expectedOldOID pointing to old commit", @@ -370,9 +370,13 @@ func TestUserRevert(t *testing.T) { Message: []byte("Reverting " + secondCommitID), ExpectedOldOid: firstCommitID.String(), }, + expectedError: testhelper.WithInterceptedMetadata( + structerr.NewInternal("update reference with hooks: Could not update refs/heads/%s. Please refresh and try again.", branchName), + "stderr", + fmt.Sprintf("fatal: prepare: cannot lock ref 'refs/heads/%s': is at %s but expected %s\n", branchName, secondCommitID, firstCommitID), + ), } }, - expectedErr: structerr.NewInternal("update reference with hooks: Could not update refs/heads/%s. Please refresh and try again.", branchName), }, } @@ -388,14 +392,14 @@ func TestUserRevert(t *testing.T) { data := tc.setup(t, repoPath, repoProto, repo) response, err := client.UserRevert(ctx, data.request) - testhelper.RequireGrpcError(t, tc.expectedErr, err) + testhelper.RequireGrpcError(t, data.expectedError, err) - if tc.expectedErr != nil { + if data.expectedError != nil { return } branchCommitID := text.ChompBytes(gittest.Exec(t, cfg, "-C", data.repoPath, "rev-parse", branchName)) - tc.expectedResponse.BranchUpdate.CommitId = branchCommitID + data.expectedResponse.BranchUpdate.CommitId = branchCommitID // For dry-run, we only skip the `update-ref` section, so a non-existent branch // will be created by `UserRevert`. But, we need to ensure that the @@ -404,7 +408,7 @@ func TestUserRevert(t *testing.T) { require.Equal(t, data.expectedCommitID, branchCommitID, "dry run should point at expected commit") } - testhelper.ProtoEqual(t, tc.expectedResponse, response) + testhelper.ProtoEqual(t, data.expectedResponse, response) }) } } diff --git a/internal/gitaly/service/operations/tags_test.go b/internal/gitaly/service/operations/tags_test.go index aaf26a39d..4eb891c4b 100644 --- a/internal/gitaly/service/operations/tags_test.go +++ b/internal/gitaly/service/operations/tags_test.go @@ -33,206 +33,251 @@ func TestUserDeleteTag(t *testing.T) { ctx := testhelper.Context(t) ctx, cfg, client := setupOperationsServiceWithoutRepo(t, ctx) - testCases := []struct { - desc string - setup func() (string, *gitalypb.UserDeleteTagRequest) + type setupData struct { + repoPath string + request *gitalypb.UserDeleteTagRequest expectedResponse *gitalypb.UserDeleteTagResponse + expectedError error expectedTags []string - expectedErr error + } + + testCases := []struct { + desc string + setup func(t *testing.T) setupData }{ { desc: "successful deletion", - setup: func() (string, *gitalypb.UserDeleteTagRequest) { + setup: func(t *testing.T) setupData { tagName := "mercury" repoProto, repoPath := gittest.CreateRepository(t, ctx, cfg) gittest.WriteCommit(t, cfg, repoPath, gittest.WithReference("refs/tags/"+tagName)) - return repoPath, &gitalypb.UserDeleteTagRequest{ - Repository: repoProto, - TagName: []byte(tagName), - User: gittest.TestUser, + return setupData{ + repoPath: repoPath, + request: &gitalypb.UserDeleteTagRequest{ + Repository: repoProto, + TagName: []byte(tagName), + User: gittest.TestUser, + }, + expectedResponse: &gitalypb.UserDeleteTagResponse{}, } }, - expectedResponse: &gitalypb.UserDeleteTagResponse{}, }, { desc: "successful deletion + expectedOldOID", - setup: func() (string, *gitalypb.UserDeleteTagRequest) { + setup: func(t *testing.T) setupData { tagName := "venus" repoProto, repoPath := gittest.CreateRepository(t, ctx, cfg) commit := gittest.WriteCommit(t, cfg, repoPath, gittest.WithReference("refs/tags/"+tagName)) - return repoPath, &gitalypb.UserDeleteTagRequest{ - Repository: repoProto, - TagName: []byte(tagName), - User: gittest.TestUser, - ExpectedOldOid: string(commit), + return setupData{ + repoPath: repoPath, + request: &gitalypb.UserDeleteTagRequest{ + Repository: repoProto, + TagName: []byte(tagName), + User: gittest.TestUser, + ExpectedOldOid: string(commit), + }, + expectedResponse: &gitalypb.UserDeleteTagResponse{}, } }, - expectedResponse: &gitalypb.UserDeleteTagResponse{}, }, { desc: "possible to delete a tag called refs/tags/something", - setup: func() (string, *gitalypb.UserDeleteTagRequest) { + setup: func(t *testing.T) setupData { tagName := "refs/tags/earth" repoProto, repoPath := gittest.CreateRepository(t, ctx, cfg) gittest.WriteCommit(t, cfg, repoPath, gittest.WithReference("refs/tags/"+tagName)) - return repoPath, &gitalypb.UserDeleteTagRequest{ - Repository: repoProto, - TagName: []byte(tagName), - User: gittest.TestUser, + return setupData{ + repoPath: repoPath, + request: &gitalypb.UserDeleteTagRequest{ + Repository: repoProto, + TagName: []byte(tagName), + User: gittest.TestUser, + }, + expectedResponse: &gitalypb.UserDeleteTagResponse{}, } }, - expectedResponse: &gitalypb.UserDeleteTagResponse{}, }, { desc: "no repository provided", - setup: func() (string, *gitalypb.UserDeleteTagRequest) { - tagName := "mars" + setup: func(t *testing.T) setupData { _, repoPath := gittest.CreateRepository(t, ctx, cfg) gittest.WriteCommit(t, cfg, repoPath) - return repoPath, &gitalypb.UserDeleteTagRequest{ - TagName: []byte(tagName), - User: gittest.TestUser, + return setupData{ + repoPath: repoPath, + request: &gitalypb.UserDeleteTagRequest{ + TagName: []byte("mars"), + User: gittest.TestUser, + }, + expectedError: testhelper.GitalyOrPraefect( + structerr.NewInvalidArgument("%w", storage.ErrRepositoryNotSet), + structerr.NewInvalidArgument("repo scoped: %w", storage.ErrRepositoryNotSet), + ), } }, - expectedErr: testhelper.GitalyOrPraefect( - structerr.NewInvalidArgument("%w", storage.ErrRepositoryNotSet), - structerr.NewInvalidArgument("repo scoped: %w", storage.ErrRepositoryNotSet), - ), }, { desc: "empty user", - setup: func() (string, *gitalypb.UserDeleteTagRequest) { + setup: func(t *testing.T) setupData { tagName := "jupiter" repoProto, repoPath := gittest.CreateRepository(t, ctx, cfg) gittest.WriteCommit(t, cfg, repoPath) - return repoPath, &gitalypb.UserDeleteTagRequest{ - Repository: repoProto, - TagName: []byte(tagName), + return setupData{ + repoPath: repoPath, + request: &gitalypb.UserDeleteTagRequest{ + Repository: repoProto, + TagName: []byte(tagName), + }, + expectedError: structerr.NewInvalidArgument("empty user"), } }, - expectedErr: structerr.NewInvalidArgument("empty user"), }, { desc: "empty tag name", - setup: func() (string, *gitalypb.UserDeleteTagRequest) { + setup: func(t *testing.T) setupData { repoProto, repoPath := gittest.CreateRepository(t, ctx, cfg) gittest.WriteCommit(t, cfg, repoPath) - return repoPath, &gitalypb.UserDeleteTagRequest{ - Repository: repoProto, - User: gittest.TestUser, + return setupData{ + repoPath: repoPath, + request: &gitalypb.UserDeleteTagRequest{ + Repository: repoProto, + User: gittest.TestUser, + }, + expectedError: structerr.NewInvalidArgument("empty tag name"), } }, - expectedErr: structerr.NewInvalidArgument("empty tag name"), }, { desc: "non-existent tag name", - setup: func() (string, *gitalypb.UserDeleteTagRequest) { + setup: func(t *testing.T) setupData { tagName := "uranus" repoProto, repoPath := gittest.CreateRepository(t, ctx, cfg) gittest.WriteCommit(t, cfg, repoPath, gittest.WithReference("refs/tags/"+tagName)) - return repoPath, &gitalypb.UserDeleteTagRequest{ - Repository: repoProto, - TagName: []byte("neptune"), - User: gittest.TestUser, + return setupData{ + repoPath: repoPath, + request: &gitalypb.UserDeleteTagRequest{ + Repository: repoProto, + TagName: []byte("neptune"), + User: gittest.TestUser, + }, + expectedError: structerr.NewFailedPrecondition("tag not found: %s", "neptune"), + expectedTags: []string{"uranus"}, } }, - expectedErr: structerr.NewFailedPrecondition("tag not found: %s", "neptune"), - expectedTags: []string{"uranus"}, }, { desc: "space in tag name", - setup: func() (string, *gitalypb.UserDeleteTagRequest) { + setup: func(t *testing.T) setupData { tagName := "sun" repoProto, repoPath := gittest.CreateRepository(t, ctx, cfg) gittest.WriteCommit(t, cfg, repoPath, gittest.WithReference("refs/tags/"+tagName)) - return repoPath, &gitalypb.UserDeleteTagRequest{ - Repository: repoProto, - TagName: []byte("milky way"), - User: gittest.TestUser, + return setupData{ + repoPath: repoPath, + request: &gitalypb.UserDeleteTagRequest{ + Repository: repoProto, + TagName: []byte("milky way"), + User: gittest.TestUser, + }, + expectedError: structerr.NewFailedPrecondition("tag not found: %s", "milky way"), + expectedTags: []string{"sun"}, } }, - expectedErr: structerr.NewFailedPrecondition("tag not found: %s", "milky way"), - expectedTags: []string{"sun"}, }, { desc: "newline in tag name", - setup: func() (string, *gitalypb.UserDeleteTagRequest) { + setup: func(t *testing.T) setupData { tagName := "moon" repoProto, repoPath := gittest.CreateRepository(t, ctx, cfg) gittest.WriteCommit(t, cfg, repoPath, gittest.WithReference("refs/tags/"+tagName)) - return repoPath, &gitalypb.UserDeleteTagRequest{ - Repository: repoProto, - TagName: []byte("Dog\nStar"), - User: gittest.TestUser, + return setupData{ + repoPath: repoPath, + request: &gitalypb.UserDeleteTagRequest{ + Repository: repoProto, + TagName: []byte("Dog\nStar"), + User: gittest.TestUser, + }, + expectedError: structerr.NewFailedPrecondition("tag not found: %s", "Dog\nStar"), + expectedTags: []string{"moon"}, } }, - expectedErr: structerr.NewFailedPrecondition("tag not found: %s", "Dog\nStar"), - expectedTags: []string{"moon"}, }, { desc: "invalid expectedOldOID", - setup: func() (string, *gitalypb.UserDeleteTagRequest) { + setup: func(t *testing.T) setupData { tagName := "europa" repoProto, repoPath := gittest.CreateRepository(t, ctx, cfg) gittest.WriteCommit(t, cfg, repoPath, gittest.WithReference("refs/tags/"+tagName)) - return repoPath, &gitalypb.UserDeleteTagRequest{ - Repository: repoProto, - TagName: []byte(tagName), - User: gittest.TestUser, - ExpectedOldOid: "io", + return setupData{ + repoPath: repoPath, + request: &gitalypb.UserDeleteTagRequest{ + Repository: repoProto, + TagName: []byte(tagName), + User: gittest.TestUser, + ExpectedOldOid: "io", + }, + expectedError: testhelper.WithInterceptedMetadata( + structerr.NewInvalidArgument(fmt.Sprintf(`invalid expected old object ID: invalid object ID: "io", expected length %v, got 2`, gittest.DefaultObjectHash.EncodedLen())), + "old_object_id", "io"), + + expectedTags: []string{"europa"}, } }, - expectedErr: testhelper.WithInterceptedMetadata( - structerr.NewInvalidArgument(fmt.Sprintf(`invalid expected old object ID: invalid object ID: "io", expected length %v, got 2`, gittest.DefaultObjectHash.EncodedLen())), - "old_object_id", "io"), - expectedTags: []string{"europa"}, }, { desc: "valid expectedOldOID SHA but not present in repo", - setup: func() (string, *gitalypb.UserDeleteTagRequest) { + setup: func(t *testing.T) setupData { tagName := "europa" repoProto, repoPath := gittest.CreateRepository(t, ctx, cfg) gittest.WriteCommit(t, cfg, repoPath, gittest.WithReference("refs/tags/"+tagName)) - return repoPath, &gitalypb.UserDeleteTagRequest{ - Repository: repoProto, - TagName: []byte(tagName), - User: gittest.TestUser, - ExpectedOldOid: gittest.DefaultObjectHash.ZeroOID.String(), + return setupData{ + repoPath: repoPath, + request: &gitalypb.UserDeleteTagRequest{ + Repository: repoProto, + TagName: []byte(tagName), + User: gittest.TestUser, + ExpectedOldOid: gittest.DefaultObjectHash.ZeroOID.String(), + }, + expectedError: testhelper.WithInterceptedMetadata( + structerr.NewInvalidArgument("cannot resolve expected old object ID: reference not found"), + "old_object_id", gittest.DefaultObjectHash.ZeroOID), + expectedTags: []string{"europa"}, } }, - expectedErr: testhelper.WithInterceptedMetadata( - structerr.NewInvalidArgument("cannot resolve expected old object ID: reference not found"), - "old_object_id", gittest.DefaultObjectHash.ZeroOID), - expectedTags: []string{"europa"}, }, { desc: "old ref expectedOldOID", - setup: func() (string, *gitalypb.UserDeleteTagRequest) { + setup: func(t *testing.T) setupData { tagName := "ganymede" repoProto, repoPath := gittest.CreateRepository(t, ctx, cfg) firstCommit := gittest.WriteCommit(t, cfg, repoPath) - gittest.WriteCommit(t, cfg, repoPath, gittest.WithParents(firstCommit), gittest.WithReference("refs/tags/"+tagName)) + secondCommit := gittest.WriteCommit(t, cfg, repoPath, gittest.WithParents(firstCommit), gittest.WithReference("refs/tags/"+tagName)) - return repoPath, &gitalypb.UserDeleteTagRequest{ - Repository: repoProto, - TagName: []byte(tagName), - User: gittest.TestUser, - ExpectedOldOid: firstCommit.String(), + return setupData{ + repoPath: repoPath, + request: &gitalypb.UserDeleteTagRequest{ + Repository: repoProto, + TagName: []byte(tagName), + User: gittest.TestUser, + ExpectedOldOid: firstCommit.String(), + }, + expectedError: testhelper.WithInterceptedMetadata( + structerr.NewFailedPrecondition("Could not update refs/tags/%s. Please refresh and try again.", tagName), + "stderr", + fmt.Sprintf("fatal: prepare: cannot lock ref 'refs/tags/%s': is at %s but expected %s\n", tagName, secondCommit, firstCommit), + ), + expectedTags: []string{"ganymede"}, } }, - expectedErr: structerr.NewFailedPrecondition("Could not update refs/tags/ganymede. Please refresh and try again."), - expectedTags: []string{"ganymede"}, }, } @@ -241,13 +286,13 @@ func TestUserDeleteTag(t *testing.T) { t.Run(tc.desc, func(t *testing.T) { t.Parallel() - repoPath, request := tc.setup() - response, err := client.UserDeleteTag(ctx, request) - testhelper.RequireGrpcError(t, tc.expectedErr, err) - testhelper.ProtoEqual(t, tc.expectedResponse, response) + setup := tc.setup(t) + response, err := client.UserDeleteTag(ctx, setup.request) + testhelper.RequireGrpcError(t, setup.expectedError, err) + testhelper.ProtoEqual(t, setup.expectedResponse, response) - tags := text.ChompBytes(gittest.Exec(t, cfg, "-C", repoPath, "tag")) - require.ElementsMatchf(t, tc.expectedTags, strings.Fields(tags), "tag name still exists in tags list") + tags := text.ChompBytes(gittest.Exec(t, cfg, "-C", setup.repoPath, "tag")) + require.ElementsMatchf(t, setup.expectedTags, strings.Fields(tags), "tag name still exists in tags list") }) } } |