diff options
author | Will Chandler <wchandler@gitlab.com> | 2023-07-05 20:17:48 +0300 |
---|---|---|
committer | Will Chandler <wchandler@gitlab.com> | 2023-07-05 20:17:48 +0300 |
commit | 514b8bf2ee9db51fb23f52efbcb93ef6decb3f75 (patch) | |
tree | 18d21e2cf92930691fee486186925b98866778ec | |
parent | 93b22126b838c5f4eb8a7c978bd6a9a1945d4ef3 (diff) | |
parent | 2cd79754f57f8549d568ab01905c5fc8ea515f98 (diff) |
Merge branch 'pks-write-ref-sha256' into 'master'
repository: Make WriteRef compatible with SHA256 object format
See merge request https://gitlab.com/gitlab-org/gitaly/-/merge_requests/6001
Merged-by: Will Chandler <wchandler@gitlab.com>
Approved-by: Justin Tobler <jtobler@gitlab.com>
Approved-by: Will Chandler <wchandler@gitlab.com>
Co-authored-by: Patrick Steinhardt <psteinhardt@gitlab.com>
-rw-r--r-- | internal/backup/backup_test.go | 12 | ||||
-rw-r--r-- | internal/backup/repository.go | 4 | ||||
-rw-r--r-- | internal/git/decoder.go | 2 | ||||
-rw-r--r-- | internal/git/localrepo/bundle_test.go | 8 | ||||
-rw-r--r-- | internal/git/localrepo/refs.go | 8 | ||||
-rw-r--r-- | internal/git/localrepo/refs_test.go | 12 | ||||
-rw-r--r-- | internal/git/reference.go | 8 | ||||
-rw-r--r-- | internal/gitaly/service/repository/write_ref.go | 13 | ||||
-rw-r--r-- | internal/gitaly/service/repository/write_ref_test.go | 516 |
9 files changed, 334 insertions, 249 deletions
diff --git a/internal/backup/backup_test.go b/internal/backup/backup_test.go index 2a27465bc..70cea1550 100644 --- a/internal/backup/backup_test.go +++ b/internal/backup/backup_test.go @@ -570,9 +570,9 @@ func TestManager_Restore_latest(t *testing.T) { ) checksum := new(git.Checksum) - checksum.Add(git.NewReference("HEAD", master2.String())) - checksum.Add(git.NewReference("refs/heads/master", master2.String())) - checksum.Add(git.NewReference("refs/heads/other", other.String())) + checksum.Add(git.NewReference("HEAD", master2)) + checksum.Add(git.NewReference("refs/heads/master", master2)) + checksum.Add(git.NewReference("refs/heads/other", other)) return repo, checksum }, @@ -763,9 +763,9 @@ func TestManager_Restore_specific(t *testing.T) { ) checksum := new(git.Checksum) - checksum.Add(git.NewReference("HEAD", master2.String())) - checksum.Add(git.NewReference("refs/heads/master", master2.String())) - checksum.Add(git.NewReference("refs/heads/other", other.String())) + checksum.Add(git.NewReference("HEAD", master2)) + checksum.Add(git.NewReference("refs/heads/master", master2)) + checksum.Add(git.NewReference("refs/heads/other", other)) return repo, checksum }, diff --git a/internal/backup/repository.go b/internal/backup/repository.go index 220637c0c..6732edb21 100644 --- a/internal/backup/repository.go +++ b/internal/backup/repository.go @@ -72,7 +72,7 @@ func (rr *remoteRepository) ListRefs(ctx context.Context) ([]git.Reference, erro return nil, fmt.Errorf("remote repository: list refs: %w", err) } for _, ref := range resp.GetReferences() { - refs = append(refs, git.NewReference(git.ReferenceName(ref.GetName()), ref.GetTarget())) + refs = append(refs, git.NewReference(git.ReferenceName(ref.GetName()), git.ObjectID(ref.GetTarget()))) } } @@ -295,7 +295,7 @@ func (r *localRepository) ListRefs(ctx context.Context) ([]git.Reference, error) case err != nil: return nil, structerr.NewInternal("local repository: list refs: %w", err) default: - head := git.NewReference("HEAD", headOID.String()) + head := git.NewReference("HEAD", headOID) refs = append([]git.Reference{head}, refs...) } diff --git a/internal/git/decoder.go b/internal/git/decoder.go index 5d960110f..da5591202 100644 --- a/internal/git/decoder.go +++ b/internal/git/decoder.go @@ -42,7 +42,7 @@ func (d *ShowRefDecoder) Decode(ref *Reference) error { return fmt.Errorf("show-ref decoder: invalid line: %q", line) } - *ref = NewReference(ReferenceName(splits[1]), splits[0]) + *ref = NewReference(ReferenceName(splits[1]), ObjectID(splits[0])) return nil } diff --git a/internal/git/localrepo/bundle_test.go b/internal/git/localrepo/bundle_test.go index bff66a5de..fa8615323 100644 --- a/internal/git/localrepo/bundle_test.go +++ b/internal/git/localrepo/bundle_test.go @@ -161,8 +161,8 @@ func TestRepo_FetchBundle(t *testing.T) { reader: createBundle(t, cfg, sourceRepoPath), expectedHeadRef: git.DefaultRef, expectedRefs: []git.Reference{ - git.NewReference(git.NewReferenceNameFromBranchName("feature"), featureOid.String()), - git.NewReference(git.DefaultRef, mainOid.String()), + git.NewReference(git.NewReferenceNameFromBranchName("feature"), featureOid), + git.NewReference(git.DefaultRef, mainOid), }, } }, @@ -185,8 +185,8 @@ func TestRepo_FetchBundle(t *testing.T) { reader: createBundle(t, cfg, sourceRepoPath), expectedHeadRef: git.NewReferenceNameFromBranchName("feature"), expectedRefs: []git.Reference{ - git.NewReference(git.NewReferenceNameFromBranchName("feature"), featureOid.String()), - git.NewReference(git.DefaultRef, mainOid.String()), + git.NewReference(git.NewReferenceNameFromBranchName("feature"), featureOid), + git.NewReference(git.DefaultRef, mainOid), }, } }, diff --git a/internal/git/localrepo/refs.go b/internal/git/localrepo/refs.go index dd3248f02..8878be895 100644 --- a/internal/git/localrepo/refs.go +++ b/internal/git/localrepo/refs.go @@ -126,9 +126,9 @@ func (repo *Repo) getReferences(ctx context.Context, limit uint, patterns ...str } if len(line[2]) == 0 { - refs = append(refs, git.NewReference(git.ReferenceName(line[0]), string(line[1]))) + refs = append(refs, git.NewReference(git.ReferenceName(line[0]), git.ObjectID(line[1]))) } else { - refs = append(refs, git.NewSymbolicReference(git.ReferenceName(line[0]), string(line[1]))) + refs = append(refs, git.NewSymbolicReference(git.ReferenceName(line[0]), git.ReferenceName(line[1]))) } } @@ -310,11 +310,11 @@ func (repo *Repo) GetRemoteReferences(ctx context.Context, remote string, opts . return nil, fmt.Errorf("expected dereferenced symbolic ref %q but got reference %q", symRef, split[1]) } - refs = append(refs, git.NewSymbolicReference(git.ReferenceName(symRef), split[0])) + refs = append(refs, git.NewSymbolicReference(git.ReferenceName(symRef), git.ReferenceName(split[0]))) continue } - refs = append(refs, git.NewReference(git.ReferenceName(split[1]), split[0])) + refs = append(refs, git.NewReference(git.ReferenceName(split[1]), git.ObjectID(split[0]))) } if err := scanner.Err(); err != nil { diff --git a/internal/git/localrepo/refs_test.go b/internal/git/localrepo/refs_test.go index ff092e640..b4ba959c7 100644 --- a/internal/git/localrepo/refs_test.go +++ b/internal/git/localrepo/refs_test.go @@ -82,7 +82,7 @@ func TestRepo_GetReference(t *testing.T) { { desc: "fully qualified master branch", ref: "refs/heads/master", - expected: git.NewReference("refs/heads/master", commitID.String()), + expected: git.NewReference("refs/heads/master", commitID), }, { desc: "unqualified master branch fails", @@ -694,7 +694,7 @@ func TestGuessHead(t *testing.T) { {"update-ref", "refs/heads/feature", commit1.String()}, {"update-ref", "refs/heads/zucchini", commit1.String()}, }, - head: git.NewReference("HEAD", commit1.String()), + head: git.NewReference("HEAD", commit1), expected: git.DefaultRef, }, { @@ -706,7 +706,7 @@ func TestGuessHead(t *testing.T) { {"update-ref", "refs/heads/feature", commit1.String()}, {"update-ref", "refs/heads/zucchini", commit1.String()}, }, - head: git.NewReference("HEAD", commit1.String()), + head: git.NewReference("HEAD", commit1), expected: git.LegacyDefaultRef, }, { @@ -718,7 +718,7 @@ func TestGuessHead(t *testing.T) { {"update-ref", "refs/heads/feature", commit1.String()}, {"update-ref", "refs/heads/zucchini", commit1.String()}, }, - head: git.NewReference("HEAD", commit1.String()), + head: git.NewReference("HEAD", commit1), expected: "refs/heads/apple", }, { @@ -730,7 +730,7 @@ func TestGuessHead(t *testing.T) { {"update-ref", "refs/heads/feature", commit1.String()}, {"update-ref", "refs/heads/zucchini", commit1.String()}, }, - head: git.NewReference("HEAD", commit1.String()), + head: git.NewReference("HEAD", commit1), expected: "refs/heads/apple", }, { @@ -742,7 +742,7 @@ func TestGuessHead(t *testing.T) { {"update-ref", "refs/heads/feature", commit2.String()}, {"update-ref", "refs/heads/zucchini", commit2.String()}, }, - head: git.NewReference("HEAD", commit1.String()), + head: git.NewReference("HEAD", commit1), expectedErr: fmt.Errorf("guess head: %w", git.ErrReferenceNotFound), }, } { diff --git a/internal/git/reference.go b/internal/git/reference.go index 8ac168dd3..79f998abc 100644 --- a/internal/git/reference.go +++ b/internal/git/reference.go @@ -109,19 +109,19 @@ type Reference struct { } // NewReference creates a direct reference to an object. -func NewReference(name ReferenceName, target string) Reference { +func NewReference(name ReferenceName, target ObjectID) Reference { return Reference{ Name: name, - Target: target, + Target: string(target), IsSymbolic: false, } } // NewSymbolicReference creates a symbolic reference to another reference. -func NewSymbolicReference(name ReferenceName, target string) Reference { +func NewSymbolicReference(name ReferenceName, target ReferenceName) Reference { return Reference{ Name: name, - Target: target, + Target: string(target), IsSymbolic: true, } } diff --git a/internal/gitaly/service/repository/write_ref.go b/internal/gitaly/service/repository/write_ref.go index 83d4b7e72..76c6d4190 100644 --- a/internal/gitaly/service/repository/write_ref.go +++ b/internal/gitaly/service/repository/write_ref.go @@ -39,11 +39,16 @@ func (s *server) writeRef(ctx context.Context, req *gitalypb.WriteRefRequest) er } func updateRef(ctx context.Context, repo *localrepo.Repo, req *gitalypb.WriteRefRequest) (returnedErr error) { + objectHash, err := repo.ObjectHash(ctx) + if err != nil { + return fmt.Errorf("detecting object format: %w", err) + } + var newObjectID git.ObjectID - if git.ObjectHashSHA1.IsZeroOID(git.ObjectID(req.GetRevision())) { + if objectHash.IsZeroOID(git.ObjectID(req.GetRevision())) { // Passing the all-zeroes object ID as new value means that we should delete the // reference. - newObjectID = git.ObjectHashSHA1.ZeroOID + newObjectID = objectHash.ZeroOID } else { // We need to resolve the new revision in order to make sure that we're actually // passing an object ID to git-update-ref(1), but more importantly this will also @@ -58,10 +63,10 @@ func updateRef(ctx context.Context, repo *localrepo.Repo, req *gitalypb.WriteRef var oldObjectID git.ObjectID if len(req.GetOldRevision()) > 0 { - if git.ObjectHashSHA1.IsZeroOID(git.ObjectID(req.GetOldRevision())) { + if objectHash.IsZeroOID(git.ObjectID(req.GetOldRevision())) { // Passing an all-zeroes object ID indicates that we should only update the // reference if it didn't previously exist. - oldObjectID = git.ObjectHashSHA1.ZeroOID + oldObjectID = objectHash.ZeroOID } else { var err error oldObjectID, err = repo.ResolveRevision(ctx, git.Revision(req.GetOldRevision())+"^{object}") diff --git a/internal/gitaly/service/repository/write_ref_test.go b/internal/gitaly/service/repository/write_ref_test.go index 7e525c8a8..b9f029c24 100644 --- a/internal/gitaly/service/repository/write_ref_test.go +++ b/internal/gitaly/service/repository/write_ref_test.go @@ -1,293 +1,373 @@ -//go:build !gitaly_test_sha256 - package repository import ( "bytes" - "path/filepath" + "fmt" "testing" "github.com/stretchr/testify/require" "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/transaction" "gitlab.com/gitlab-org/gitaly/v16/internal/grpc/metadata" - "gitlab.com/gitlab-org/gitaly/v16/internal/helper/text" "gitlab.com/gitlab-org/gitaly/v16/internal/structerr" "gitlab.com/gitlab-org/gitaly/v16/internal/testhelper" "gitlab.com/gitlab-org/gitaly/v16/internal/testhelper/testserver" "gitlab.com/gitlab-org/gitaly/v16/internal/transaction/txinfo" "gitlab.com/gitlab-org/gitaly/v16/internal/transaction/voting" "gitlab.com/gitlab-org/gitaly/v16/proto/go/gitalypb" - "google.golang.org/grpc/codes" ) -func TestWriteRef_successful(t *testing.T) { +func TestWriteRef(t *testing.T) { t.Parallel() + ctx := testhelper.Context(t) txManager := transaction.NewTrackingManager() - cfg, repo, repoPath, client := setupRepositoryService(t, testhelper.Context(t), testserver.WithTransactionManager(txManager)) + cfg, client := setupRepositoryServiceWithoutRepo(t, testserver.WithTransactionManager(txManager)) - testCases := []struct { - desc string - req *gitalypb.WriteRefRequest + type setupData struct { + request *gitalypb.WriteRefRequest + expectedErr error + expectedRefs []git.Reference expectedVotes []transaction.PhasedVote - }{ - { - desc: "shell update HEAD to refs/heads/master", - req: &gitalypb.WriteRefRequest{ - Repository: repo, - Ref: []byte("HEAD"), - Revision: []byte("refs/heads/master"), + } + + votes := func(ref git.ReferenceName, oldID, newID git.ObjectID) []transaction.PhasedVote { + return []transaction.PhasedVote{ + { + Phase: voting.Prepared, + Vote: voting.VoteFromData([]byte(fmt.Sprintf("%s %s %s\n", oldID, newID, ref))), }, - expectedVotes: []transaction.PhasedVote{ - { - Phase: voting.Prepared, - Vote: voting.Vote{0xac, 0xba, 0xef, 0x27, 0x5e, 0x46, 0xa7, 0xf1, 0x4c, 0x1e, 0xf4, 0x56, 0xff, 0xf2, 0xc8, 0xbb, 0xe8, 0xc8, 0x47, 0x24}, - }, - { - Phase: voting.Committed, - Vote: voting.Vote{0xac, 0xba, 0xef, 0x27, 0x5e, 0x46, 0xa7, 0xf1, 0x4c, 0x1e, 0xf4, 0x56, 0xff, 0xf2, 0xc8, 0xbb, 0xe8, 0xc8, 0x47, 0x24}, - }, + { + Phase: voting.Committed, + Vote: voting.VoteFromData([]byte(fmt.Sprintf("%s %s %s\n", oldID, newID, ref))), }, - }, + } + } + + for _, tc := range []struct { + desc string + setup func(t *testing.T) setupData + }{ { - desc: "shell update refs/heads/master", - req: &gitalypb.WriteRefRequest{ - Repository: repo, - Ref: []byte("refs/heads/master"), - Revision: []byte("b83d6e391c22777fca1ed3012fce84f633d7fed0"), - }, - expectedVotes: []transaction.PhasedVote{ - { - Phase: voting.Prepared, - Vote: voting.Vote{0xd4, 0x6c, 0x98, 0x49, 0xde, 0x17, 0x55, 0xae, 0xb, 0x48, 0x7f, 0xac, 0x57, 0x77, 0x7d, 0xae, 0xb0, 0xf9, 0x64, 0x72}, - }, - { - Phase: voting.Committed, - Vote: voting.Vote{0xd4, 0x6c, 0x98, 0x49, 0xde, 0x17, 0x55, 0xae, 0xb, 0x48, 0x7f, 0xac, 0x57, 0x77, 0x7d, 0xae, 0xb0, 0xf9, 0x64, 0x72}, - }, + desc: "empty revision", + setup: func(t *testing.T) setupData { + repo, _ := gittest.CreateRepository(t, ctx, cfg) + + return setupData{ + request: &gitalypb.WriteRefRequest{ + Repository: repo, + Ref: []byte("refs/heads/master"), + }, + expectedErr: structerr.NewInvalidArgument("invalid revision: empty revision"), + expectedRefs: []git.Reference{ + git.NewSymbolicReference("HEAD", git.DefaultRef), + }, + expectedVotes: []transaction.PhasedVote{}, + } }, }, { - desc: "shell update refs/heads/master w/ validation", - req: &gitalypb.WriteRefRequest{ - Repository: repo, - Ref: []byte("refs/heads/master"), - Revision: []byte("498214de67004b1da3d820901307bed2a68a8ef6"), - OldRevision: []byte("b83d6e391c22777fca1ed3012fce84f633d7fed0"), - }, - expectedVotes: []transaction.PhasedVote{ - { - Phase: voting.Prepared, - Vote: voting.Vote{0xcb, 0xfc, 0x7f, 0x79, 0x80, 0x7a, 0x33, 0x8e, 0xd5, 0x6a, 0xb7, 0x8e, 0x7f, 0xf8, 0xe, 0xf8, 0x8, 0xb5, 0x52, 0x1a}, - }, - { - Phase: voting.Committed, - Vote: voting.Vote{0xcb, 0xfc, 0x7f, 0x79, 0x80, 0x7a, 0x33, 0x8e, 0xd5, 0x6a, 0xb7, 0x8e, 0x7f, 0xf8, 0xe, 0xf8, 0x8, 0xb5, 0x52, 0x1a}, - }, + desc: "empty ref name", + setup: func(t *testing.T) setupData { + repo, repoPath := gittest.CreateRepository(t, ctx, cfg) + + commitID := gittest.WriteCommit(t, cfg, repoPath) + + return setupData{ + request: &gitalypb.WriteRefRequest{ + Repository: repo, + Revision: []byte(commitID), + }, + expectedErr: structerr.NewInvalidArgument("invalid ref: empty revision"), + expectedRefs: []git.Reference{ + git.NewSymbolicReference("HEAD", git.DefaultRef), + }, + expectedVotes: []transaction.PhasedVote{}, + } }, }, { - desc: "race-free creation of reference", - req: &gitalypb.WriteRefRequest{ - Repository: repo, - Ref: []byte("refs/heads/branch"), - Revision: []byte("498214de67004b1da3d820901307bed2a68a8ef6"), - OldRevision: []byte("0000000000000000000000000000000000000000"), - }, - expectedVotes: []transaction.PhasedVote{ - { - Phase: voting.Prepared, - Vote: voting.Vote{0xb0, 0xcc, 0xb4, 0x9a, 0x88, 0xc3, 0x67, 0x63, 0xd0, 0xfb, 0x94, 0xd5, 0x84, 0x43, 0x67, 0x18, 0x1e, 0xdb, 0xa0, 0x1e}, - }, - { - Phase: voting.Committed, - Vote: voting.Vote{0xb0, 0xcc, 0xb4, 0x9a, 0x88, 0xc3, 0x67, 0x63, 0xd0, 0xfb, 0x94, 0xd5, 0x84, 0x43, 0x67, 0x18, 0x1e, 0xdb, 0xa0, 0x1e}, - }, + desc: "non-prefixed ref name", + setup: func(t *testing.T) setupData { + repo, repoPath := gittest.CreateRepository(t, ctx, cfg) + + commitID := gittest.WriteCommit(t, cfg, repoPath) + + return setupData{ + request: &gitalypb.WriteRefRequest{ + Repository: repo, + Ref: []byte("master"), + Revision: []byte(commitID), + }, + expectedErr: structerr.NewInvalidArgument("ref has to be a full reference"), + expectedRefs: []git.Reference{ + git.NewSymbolicReference("HEAD", git.DefaultRef), + }, + expectedVotes: []transaction.PhasedVote{}, + } }, }, { - desc: "race-free delete of reference", - req: &gitalypb.WriteRefRequest{ - Repository: repo, - Ref: []byte("refs/heads/branch"), - Revision: []byte("0000000000000000000000000000000000000000"), - OldRevision: []byte("498214de67004b1da3d820901307bed2a68a8ef6"), - }, - expectedVotes: []transaction.PhasedVote{ - { - Phase: voting.Prepared, - Vote: voting.Vote{0xa8, 0x2f, 0xc7, 0x22, 0xac, 0x82, 0xe1, 0x12, 0xb7, 0x78, 0xa6, 0x3f, 0xd6, 0x4b, 0x68, 0xc0, 0x12, 0xb6, 0x17, 0x8c}, - }, - { - Phase: voting.Committed, - Vote: voting.Vote{0xa8, 0x2f, 0xc7, 0x22, 0xac, 0x82, 0xe1, 0x12, 0xb7, 0x78, 0xa6, 0x3f, 0xd6, 0x4b, 0x68, 0xc0, 0x12, 0xb6, 0x17, 0x8c}, - }, - }, - }, - } - - ctx, err := txinfo.InjectTransaction(testhelper.Context(t), 1, "node", true) - require.NoError(t, err) - ctx = metadata.IncomingToOutgoing(ctx) - - for _, tc := range testCases { - t.Run(tc.desc, func(t *testing.T) { - txManager.Reset() - _, err = client.WriteRef(ctx, tc.req) - require.NoError(t, err) - - require.Equal(t, tc.expectedVotes, txManager.Votes()) - - if bytes.Equal(tc.req.Ref, []byte("HEAD")) { - content := testhelper.MustReadFile(t, filepath.Join(repoPath, "HEAD")) - - refRevision := bytes.Join([][]byte{[]byte("ref: "), tc.req.Revision, []byte("\n")}, nil) - - require.EqualValues(t, refRevision, content) - return - } - - revParseCmd := gittest.NewCommand(t, cfg, "-C", repoPath, "rev-parse", "--verify", string(tc.req.Ref)) - output, err := revParseCmd.CombinedOutput() - - if git.ObjectHashSHA1.IsZeroOID(git.ObjectID(tc.req.GetRevision())) { - // If the new OID is the all-zeroes object ID then it indicates we - // should delete the branch. It's thus expected to get an error when - // we try to resolve the reference here given that it should not - // exist anymore. - require.Error(t, err) - require.Equal(t, "fatal: Needed a single revision\n", string(output)) - } else { - require.NoError(t, err) - require.Equal(t, string(tc.req.Revision), text.ChompBytes(output)) - } - }) - } -} - -func TestWriteRef_validation(t *testing.T) { - t.Parallel() + desc: "revision contains \\x00", + setup: func(t *testing.T) setupData { + repo, repoPath := gittest.CreateRepository(t, ctx, cfg) - ctx := testhelper.Context(t) - _, repo, _, client := setupRepositoryService(t, ctx) + commitID := []byte(gittest.WriteCommit(t, cfg, repoPath).String()) + commitID[10] = '\x00' - testCases := []struct { - desc string - req *gitalypb.WriteRefRequest - }{ - { - desc: "empty revision", - req: &gitalypb.WriteRefRequest{ - Repository: repo, - Ref: []byte("refs/heads/master"), + return setupData{ + request: &gitalypb.WriteRefRequest{ + Repository: repo, + Ref: []byte("refs/heads/master"), + Revision: commitID, + }, + expectedErr: structerr.NewInvalidArgument("invalid revision: revision can't contain NUL"), + expectedRefs: []git.Reference{ + git.NewSymbolicReference("HEAD", git.DefaultRef), + }, + expectedVotes: []transaction.PhasedVote{}, + } }, }, { - desc: "empty ref name", - req: &gitalypb.WriteRefRequest{ - Repository: repo, - Revision: []byte("498214de67004b1da3d820901307bed2a68a8ef6"), + desc: "ref contains whitespace", + setup: func(t *testing.T) setupData { + repo, repoPath := gittest.CreateRepository(t, ctx, cfg) + + commitID := gittest.WriteCommit(t, cfg, repoPath).String() + + return setupData{ + request: &gitalypb.WriteRefRequest{ + Repository: repo, + Ref: []byte("refs/heads /master"), + Revision: []byte(commitID), + }, + expectedErr: structerr.NewInvalidArgument("invalid ref: revision can't contain whitespace"), + expectedRefs: []git.Reference{ + git.NewSymbolicReference("HEAD", git.DefaultRef), + }, + expectedVotes: []transaction.PhasedVote{}, + } }, }, { - desc: "non-prefixed ref name for shell", - req: &gitalypb.WriteRefRequest{ - Repository: repo, - Ref: []byte("master"), - Revision: []byte("498214de67004b1da3d820901307bed2a68a8ef6"), + desc: "invalid revision", + setup: func(t *testing.T) setupData { + repo, _ := gittest.CreateRepository(t, ctx, cfg) + + return setupData{ + request: &gitalypb.WriteRefRequest{ + Repository: repo, + Ref: []byte("refs/heads/master"), + Revision: []byte("--output=/meow"), + }, + expectedErr: structerr.NewInvalidArgument("invalid revision: revision can't start with '-'"), + expectedRefs: []git.Reference{ + git.NewSymbolicReference("HEAD", git.DefaultRef), + }, + expectedVotes: []transaction.PhasedVote{}, + } }, }, { - desc: "revision contains \\x00", - req: &gitalypb.WriteRefRequest{ - Repository: repo, - Ref: []byte("refs/heads/master"), - Revision: []byte("012301230123\x001243"), + desc: "revision refers to missing reference", + setup: func(t *testing.T) setupData { + repo, _ := gittest.CreateRepository(t, ctx, cfg) + + return setupData{ + request: &gitalypb.WriteRefRequest{ + Repository: repo, + Ref: []byte("refs/heads/main"), + Revision: []byte("refs/heads/missing"), + }, + expectedErr: structerr.NewInternal("resolving new revision: reference not found"), + expectedRefs: []git.Reference{ + git.NewSymbolicReference("HEAD", git.DefaultRef), + }, + expectedVotes: []transaction.PhasedVote{}, + } }, }, { - desc: "ref contains \\x00", - req: &gitalypb.WriteRefRequest{ - Repository: repo, - Ref: []byte("refs/head\x00s/master\x00"), - Revision: []byte("0123012301231243"), + desc: "revision refers to missing object", + setup: func(t *testing.T) setupData { + repo, _ := gittest.CreateRepository(t, ctx, cfg) + + return setupData{ + request: &gitalypb.WriteRefRequest{ + Repository: repo, + Ref: []byte("refs/heads/main"), + Revision: bytes.Repeat([]byte("1"), gittest.DefaultObjectHash.EncodedLen()), + }, + expectedErr: structerr.NewInternal("resolving new revision: reference not found"), + expectedRefs: []git.Reference{ + git.NewSymbolicReference("HEAD", git.DefaultRef), + }, + expectedVotes: []transaction.PhasedVote{}, + } }, }, { - desc: "ref contains whitespace", - req: &gitalypb.WriteRefRequest{ - Repository: repo, - Ref: []byte("refs/heads /master"), - Revision: []byte("0123012301231243"), + desc: "old revision refers to missing reference", + setup: func(t *testing.T) setupData { + repo, repoPath := gittest.CreateRepository(t, ctx, cfg) + + commitID := gittest.WriteCommit(t, cfg, repoPath) + + return setupData{ + request: &gitalypb.WriteRefRequest{ + Repository: repo, + Ref: []byte("refs/heads/main"), + Revision: []byte(commitID), + OldRevision: bytes.Repeat([]byte("1"), gittest.DefaultObjectHash.EncodedLen()), + }, + expectedErr: structerr.NewInternal("resolving old revision: reference not found"), + expectedRefs: []git.Reference{ + git.NewSymbolicReference("HEAD", git.DefaultRef), + }, + expectedVotes: []transaction.PhasedVote{}, + } }, }, { - desc: "invalid revision", - req: &gitalypb.WriteRefRequest{ - Repository: repo, - Ref: []byte("refs/heads/master"), - Revision: []byte("--output=/meow"), - }, - }, - } + desc: "update default branch", + setup: func(t *testing.T) setupData { + repo, repoPath := gittest.CreateRepository(t, ctx, cfg) - for _, tc := range testCases { - t.Run(tc.desc, func(t *testing.T) { - _, err := client.WriteRef(ctx, tc.req) + defaultCommit := gittest.WriteCommit(t, cfg, repoPath, gittest.WithBranch(git.DefaultBranch)) + newCommit := gittest.WriteCommit(t, cfg, repoPath, gittest.WithBranch("new-default")) - testhelper.RequireGrpcCode(t, err, codes.InvalidArgument) - }) - } -} + return setupData{ + request: &gitalypb.WriteRefRequest{ + Repository: repo, + Ref: []byte("HEAD"), + Revision: []byte("refs/heads/new-default"), + }, + expectedRefs: []git.Reference{ + git.NewSymbolicReference("HEAD", "refs/heads/new-default"), + git.NewReference(git.DefaultRef, defaultCommit), + git.NewReference("refs/heads/new-default", newCommit), + }, + expectedVotes: []transaction.PhasedVote{ + {Phase: voting.Prepared, Vote: voting.VoteFromData([]byte("ref: refs/heads/new-default\n"))}, + {Phase: voting.Committed, Vote: voting.VoteFromData([]byte("ref: refs/heads/new-default\n"))}, + }, + } + }, + }, + { + desc: "reference update without expected commit ID", + setup: func(t *testing.T) setupData { + repo, repoPath := gittest.CreateRepository(t, ctx, cfg) -func TestWriteRef_missingRevisions(t *testing.T) { - t.Parallel() + oldCommit := gittest.WriteCommit(t, cfg, repoPath, gittest.WithBranch("branch")) + newCommit := gittest.WriteCommit(t, cfg, repoPath, gittest.WithParents(oldCommit)) - ctx := testhelper.Context(t) - cfg, client := setupRepositoryServiceWithoutRepo(t) + return setupData{ + request: &gitalypb.WriteRefRequest{ + Repository: repo, + Ref: []byte("refs/heads/branch"), + Revision: []byte(newCommit), + }, + expectedRefs: []git.Reference{ + git.NewSymbolicReference("HEAD", git.DefaultRef), + git.NewReference("refs/heads/branch", newCommit), + }, + expectedVotes: votes("refs/heads/branch", gittest.DefaultObjectHash.ZeroOID, newCommit), + } + }, + }, + { + desc: "reference update with expected commit ID", + setup: func(t *testing.T) setupData { + repo, repoPath := gittest.CreateRepository(t, ctx, cfg) - repo, repoPath := gittest.CreateRepository(t, ctx, cfg) - commitID := gittest.WriteCommit(t, cfg, repoPath) + oldCommit := gittest.WriteCommit(t, cfg, repoPath, gittest.WithBranch("branch")) + newCommit := gittest.WriteCommit(t, cfg, repoPath, gittest.WithParents(oldCommit)) - for _, tc := range []struct { - desc string - request *gitalypb.WriteRefRequest - expectedErr error - }{ - { - desc: "revision refers to missing reference", - request: &gitalypb.WriteRefRequest{ - Repository: repo, - Ref: []byte("refs/heads/main"), - Revision: []byte("refs/heads/missing"), + return setupData{ + request: &gitalypb.WriteRefRequest{ + Repository: repo, + Ref: []byte("refs/heads/branch"), + Revision: []byte(newCommit), + OldRevision: []byte(oldCommit), + }, + expectedRefs: []git.Reference{ + git.NewSymbolicReference("HEAD", git.DefaultRef), + git.NewReference("refs/heads/branch", newCommit), + }, + expectedVotes: votes("refs/heads/branch", oldCommit, newCommit), + } }, - expectedErr: structerr.NewInternal("resolving new revision: reference not found"), }, { - desc: "revision refers to missing object", - request: &gitalypb.WriteRefRequest{ - Repository: repo, - Ref: []byte("refs/heads/main"), - Revision: bytes.Repeat([]byte("1"), gittest.DefaultObjectHash.EncodedLen()), + desc: "reference creation with expected commit ID", + setup: func(t *testing.T) setupData { + repo, repoPath := gittest.CreateRepository(t, ctx, cfg) + + commitID := gittest.WriteCommit(t, cfg, repoPath) + + return setupData{ + request: &gitalypb.WriteRefRequest{ + Repository: repo, + Ref: []byte("refs/heads/branch"), + Revision: []byte(commitID), + OldRevision: []byte(gittest.DefaultObjectHash.ZeroOID), + }, + expectedRefs: []git.Reference{ + git.NewSymbolicReference("HEAD", git.DefaultRef), + git.NewReference("refs/heads/branch", commitID), + }, + expectedVotes: votes("refs/heads/branch", gittest.DefaultObjectHash.ZeroOID, commitID), + } }, - expectedErr: structerr.NewInternal("resolving new revision: reference not found"), }, { - desc: "old revision refers to missing reference", - request: &gitalypb.WriteRefRequest{ - Repository: repo, - Ref: []byte("refs/heads/main"), - Revision: []byte(commitID), - OldRevision: bytes.Repeat([]byte("1"), gittest.DefaultObjectHash.EncodedLen()), + desc: "reference deletion with expected commit ID", + setup: func(t *testing.T) setupData { + repo, repoPath := gittest.CreateRepository(t, ctx, cfg) + + commitID := gittest.WriteCommit(t, cfg, repoPath, gittest.WithBranch("branch")) + + return setupData{ + request: &gitalypb.WriteRefRequest{ + Repository: repo, + Ref: []byte("refs/heads/branch"), + Revision: []byte(gittest.DefaultObjectHash.ZeroOID), + OldRevision: []byte(commitID), + }, + expectedRefs: []git.Reference{ + git.NewSymbolicReference("HEAD", git.DefaultRef), + }, + expectedVotes: votes("refs/heads/branch", commitID, gittest.DefaultObjectHash.ZeroOID), + } }, - expectedErr: structerr.NewInternal("resolving old revision: reference not found"), }, } { t.Run(tc.desc, func(t *testing.T) { - _, err := client.WriteRef(ctx, tc.request) - testhelper.RequireGrpcError(t, tc.expectedErr, err) + setup := tc.setup(t) + + txManager.Reset() + + ctx, err := txinfo.InjectTransaction(ctx, 1, "node", true) + require.NoError(t, err) + ctx = metadata.IncomingToOutgoing(ctx) + + _, err = client.WriteRef(ctx, setup.request) + testhelper.RequireGrpcError(t, setup.expectedErr, err) + + repo := localrepo.NewTestRepo(t, cfg, setup.request.GetRepository()) + refs, err := repo.GetReferences(ctx) + require.NoError(t, err) + defaultBranch, err := repo.HeadReference(ctx) + require.NoError(t, err) + require.Equal(t, setup.expectedRefs, append([]git.Reference{ + git.NewSymbolicReference("HEAD", defaultBranch), + }, refs...)) + + require.Equal(t, setup.expectedVotes, txManager.Votes()) }) } } |