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:
authorQuang-Minh Nguyen <qmnguyen@gitlab.com>2023-11-27 19:14:43 +0300
committerQuang-Minh Nguyen <qmnguyen@gitlab.com>2023-12-06 10:32:56 +0300
commit7235df79904dce2f733f15f42a307109865629b5 (patch)
treeda01035dd84a798e6ef5daa5ee0309ab839baa6b
parentfc304279221060c327e8b87465b70ec67c988d0a (diff)
Split tests related reference modification out of TestTransactionManager
-rw-r--r--internal/gitaly/storage/storagemgr/transaction_manager_refs_test.go1442
-rw-r--r--internal/gitaly/storage/storagemgr/transaction_manager_test.go1416
2 files changed, 1447 insertions, 1411 deletions
diff --git a/internal/gitaly/storage/storagemgr/transaction_manager_refs_test.go b/internal/gitaly/storage/storagemgr/transaction_manager_refs_test.go
new file mode 100644
index 000000000..4749bba26
--- /dev/null
+++ b/internal/gitaly/storage/storagemgr/transaction_manager_refs_test.go
@@ -0,0 +1,1442 @@
+package storagemgr
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "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/updateref"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/config"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/helper/perm"
+)
+
+func generateInvalidReferencesTests(t *testing.T, setup testTransactionSetup) []transactionTestCase {
+ type invalidReferenceTestCase struct {
+ desc string
+ referenceName git.ReferenceName
+ }
+
+ commit := setup.Commits.First
+ testCases := []transactionTestCase{
+ {
+ desc: "invalid reference aborts the entire transaction",
+ steps: steps{
+ StartManager{},
+ Begin{
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ SkipVerificationFailures: true,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: commit.OID},
+ "refs/heads/../main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: commit.OID},
+ },
+ ExpectedError: InvalidReferenceFormatError{ReferenceName: "refs/heads/../main"},
+ },
+ },
+ },
+ {
+ desc: "continues processing after aborting due to an invalid reference",
+ steps: steps{
+ StartManager{},
+ Begin{
+ TransactionID: 1,
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ TransactionID: 1,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/../main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: commit.OID},
+ },
+ ExpectedError: InvalidReferenceFormatError{ReferenceName: "refs/heads/../main"},
+ },
+ Begin{
+ TransactionID: 2,
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ TransactionID: 2,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: commit.OID},
+ },
+ },
+ },
+ expectedState: StateAssertion{
+ Database: DatabaseState{
+ string(keyAppliedLSN(setup.PartitionID)): LSN(1).toProto(),
+ },
+ Repositories: RepositoryStates{
+ setup.RelativePath: {
+ DefaultBranch: "refs/heads/main",
+ References: []git.Reference{{Name: "refs/heads/main", Target: commit.OID.String()}},
+ },
+ },
+ },
+ },
+ }
+
+ appendInvalidReferenceTestCase := func(tc invalidReferenceTestCase) {
+ testCases = append(testCases, transactionTestCase{
+ desc: fmt.Sprintf("invalid reference %s", tc.desc),
+ steps: steps{
+ StartManager{},
+ Begin{
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ ReferenceUpdates: ReferenceUpdates{
+ tc.referenceName: {OldOID: setup.ObjectHash.ZeroOID, NewOID: commit.OID},
+ },
+ ExpectedError: InvalidReferenceFormatError{ReferenceName: tc.referenceName},
+ },
+ },
+ })
+ }
+
+ // Generate test cases for the reference format rules according to https://git-scm.com/docs/git-check-ref-format.
+ // This is to ensure the references are correctly validated prior to logging so they are guaranteed to apply later.
+ // We also have two levels for catching invalid refs, the first is part of the transaction_manager, the second is
+ // the errors thrown by git-update-ref(1) itself.
+ for _, tc := range []invalidReferenceTestCase{
+ // 1. They can include slash / for hierarchical (directory) grouping, but no slash-separated
+ // component can begin with a dot . or end with the sequence .lock.
+ {"starting with a period", ".refs/heads/main"},
+ {"subcomponent starting with a period", "refs/heads/.main"},
+ {"ending in .lock", "refs/heads/main.lock"},
+ {"subcomponent ending in .lock", "refs/heads/main.lock/main"},
+ // 2. They must contain at least one /. This enforces the presence of a category like heads/,
+ // tags/ etc. but the actual names are not restricted.
+ {"without a /", "one-level"},
+ {"with refs without a /", "refs"},
+ // We restrict this further by requiring a 'refs/' prefix to ensure loose references only end up
+ // in the 'refs/' folder.
+ {"without refs/ prefix ", "nonrefs/main"},
+ // 3. They cannot have two consecutive dots .. anywhere.
+ {"containing two consecutive dots", "refs/heads/../main"},
+ // 4. They cannot have ASCII control characters ... (\177 DEL), space, tilde ~, caret ^, or colon : anywhere.
+ //
+ // Tests for control characters < \040 generated further down.
+ {"containing DEL", "refs/heads/ma\177in"},
+ {"containing space", "refs/heads/ma in"},
+ {"containing ~", "refs/heads/ma~in"},
+ {"containing ^", "refs/heads/ma^in"},
+ {"containing :", "refs/heads/ma:in"},
+ // 5. They cannot have question-mark ?, asterisk *, or open bracket [ anywhere.
+ {"containing ?", "refs/heads/ma?in"},
+ {"containing *", "refs/heads/ma*in"},
+ {"containing [", "refs/heads/ma[in"},
+ // 6. They cannot begin or end with a slash / or contain multiple consecutive slashes
+ {"begins with /", "/refs/heads/main"},
+ {"ends with /", "refs/heads/main/"},
+ {"contains consecutive /", "refs/heads//main"},
+ // 7. They cannot end with a dot.
+ {"ending in a dot", "refs/heads/main."},
+ // 8. They cannot contain a sequence @{.
+ {"invalid reference contains @{", "refs/heads/m@{n"},
+ // 9. They cannot be the single character @.
+ {"is a single character @", "@"},
+ // 10. They cannot contain a \.
+ {`containing \`, `refs/heads\main`},
+ } {
+ appendInvalidReferenceTestCase(tc)
+ }
+
+ // Rule 4. They cannot have ASCII control characters i.e. bytes whose values are lower than \040,
+ for i := byte(0); i < '\040'; i++ {
+ appendInvalidReferenceTestCase(invalidReferenceTestCase{
+ desc: fmt.Sprintf(`containing ASCII control character %d`, i),
+ referenceName: git.ReferenceName(fmt.Sprintf("refs/heads/ma%sin", []byte{i})),
+ })
+ }
+
+ return testCases
+}
+
+func generateModifyReferencesTests(t *testing.T, setup testTransactionSetup) []transactionTestCase {
+ return []transactionTestCase{
+ {
+ desc: "create reference",
+ steps: steps{
+ StartManager{},
+ Begin{
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ },
+ },
+ },
+ expectedState: StateAssertion{
+ Database: DatabaseState{
+ string(keyAppliedLSN(setup.PartitionID)): LSN(1).toProto(),
+ },
+ Repositories: RepositoryStates{
+ setup.RelativePath: {
+ DefaultBranch: "refs/heads/main",
+ References: []git.Reference{{Name: "refs/heads/main", Target: setup.Commits.First.OID.String()}},
+ },
+ },
+ },
+ },
+ {
+ desc: "create reference with existing reference lock",
+ steps: steps{
+ StartManager{
+ ModifyStorage: func(_ testing.TB, _ config.Cfg, storagePath string) {
+ err := os.WriteFile(filepath.Join(storagePath, setup.RelativePath, "refs", "heads", "main.lock"), []byte{}, 0o666)
+ require.NoError(t, err)
+ },
+ },
+ Begin{
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ },
+ },
+ },
+ expectedState: StateAssertion{
+ Database: DatabaseState{
+ string(keyAppliedLSN(setup.PartitionID)): LSN(1).toProto(),
+ },
+ Repositories: RepositoryStates{
+ setup.RelativePath: {
+ DefaultBranch: "refs/heads/main",
+ References: []git.Reference{{Name: "refs/heads/main", Target: setup.Commits.First.OID.String()}},
+ },
+ },
+ },
+ },
+ {
+ desc: "delete packed reference with existing packed-refs.lock",
+ steps: steps{
+ StartManager{},
+ Begin{
+ TransactionID: 1,
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ TransactionID: 1,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ },
+ },
+ CloseManager{},
+ StartManager{
+ ModifyStorage: func(tb testing.TB, cfg config.Cfg, storagePath string) {
+ repoPath := filepath.Join(storagePath, setup.RelativePath)
+ // Pack the reference and create a stale lockfile for it.
+ gittest.Exec(tb, cfg, "-C", repoPath, "pack-refs", "--all")
+
+ // Add packed-refs.lock. The reference deletion will fail if this
+ // isn't cleaned up.
+ require.NoError(t, os.WriteFile(
+ filepath.Join(repoPath, "packed-refs.lock"),
+ []byte{},
+ perm.PrivateFile,
+ ))
+ },
+ },
+ Begin{
+ TransactionID: 2,
+ RelativePath: setup.RelativePath,
+ ExpectedSnapshotLSN: 1,
+ },
+ Commit{
+ TransactionID: 2,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.Commits.First.OID, NewOID: setup.ObjectHash.ZeroOID},
+ },
+ },
+ },
+ expectedState: StateAssertion{
+ Database: DatabaseState{
+ string(keyAppliedLSN(setup.PartitionID)): LSN(2).toProto(),
+ },
+ },
+ },
+ {
+ desc: "delete packed reference with existing packed-refs.new",
+ steps: steps{
+ StartManager{},
+ Begin{
+ TransactionID: 1,
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ TransactionID: 1,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ },
+ },
+ CloseManager{},
+ StartManager{
+ ModifyStorage: func(tb testing.TB, cfg config.Cfg, storagePath string) {
+ repoPath := filepath.Join(storagePath, setup.RelativePath)
+ // Pack the reference and create a stale lockfile for it.
+ gittest.Exec(tb, cfg, "-C", repoPath, "pack-refs", "--all")
+
+ // Add packed-refs.new. The reference deletion will fail if this
+ // isn't cleaned up.
+ require.NoError(t, os.WriteFile(
+ filepath.Join(repoPath, "packed-refs.new"),
+ []byte{},
+ perm.PrivateFile,
+ ))
+ },
+ },
+ Begin{
+ TransactionID: 2,
+ RelativePath: setup.RelativePath,
+ ExpectedSnapshotLSN: 1,
+ },
+ Commit{
+ TransactionID: 2,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.Commits.First.OID, NewOID: setup.ObjectHash.ZeroOID},
+ },
+ },
+ },
+ expectedState: StateAssertion{
+ Database: DatabaseState{
+ string(keyAppliedLSN(setup.PartitionID)): LSN(2).toProto(),
+ },
+ },
+ },
+ {
+ desc: "create a file-directory reference conflict different transaction",
+ steps: steps{
+ StartManager{},
+ Begin{
+ TransactionID: 1,
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ TransactionID: 1,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/parent": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ },
+ },
+ Begin{
+ TransactionID: 2,
+ RelativePath: setup.RelativePath,
+ ExpectedSnapshotLSN: 1,
+ },
+ Commit{
+ TransactionID: 2,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/parent/child": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ },
+ ExpectedError: updateref.FileDirectoryConflictError{
+ ExistingReferenceName: "refs/heads/parent",
+ ConflictingReferenceName: "refs/heads/parent/child",
+ },
+ },
+ },
+ expectedState: StateAssertion{
+ Database: DatabaseState{
+ string(keyAppliedLSN(setup.PartitionID)): LSN(1).toProto(),
+ },
+ Repositories: RepositoryStates{
+ setup.RelativePath: {
+ References: []git.Reference{{Name: "refs/heads/parent", Target: setup.Commits.First.OID.String()}},
+ },
+ },
+ },
+ },
+ {
+ desc: "create a file-directory reference conflict in same transaction",
+ steps: steps{
+ StartManager{},
+ Begin{
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/parent": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ "refs/heads/parent/child": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ },
+ ExpectedError: updateref.InTransactionConflictError{
+ FirstReferenceName: "refs/heads/parent",
+ SecondReferenceName: "refs/heads/parent/child",
+ },
+ },
+ },
+ },
+ {
+ desc: "file-directory conflict aborts the transaction with verification failures skipped",
+ steps: steps{
+ StartManager{},
+ Begin{
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ SkipVerificationFailures: true,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ "refs/heads/parent": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ "refs/heads/parent/child": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ },
+ ExpectedError: updateref.InTransactionConflictError{
+ FirstReferenceName: "refs/heads/parent",
+ SecondReferenceName: "refs/heads/parent/child",
+ },
+ },
+ },
+ },
+ {
+ desc: "delete file-directory conflict in different transaction",
+ steps: steps{
+ StartManager{},
+ Begin{
+ TransactionID: 1,
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ TransactionID: 1,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/parent/child": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ },
+ },
+ Begin{
+ TransactionID: 2,
+ RelativePath: setup.RelativePath,
+ ExpectedSnapshotLSN: 1,
+ },
+ Commit{
+ TransactionID: 2,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/parent": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.ObjectHash.ZeroOID},
+ },
+ ExpectedError: updateref.FileDirectoryConflictError{
+ ExistingReferenceName: "refs/heads/parent/child",
+ ConflictingReferenceName: "refs/heads/parent",
+ },
+ },
+ },
+ expectedState: StateAssertion{
+ Database: DatabaseState{
+ string(keyAppliedLSN(setup.PartitionID)): LSN(1).toProto(),
+ },
+ Repositories: RepositoryStates{
+ setup.RelativePath: {
+ References: []git.Reference{{Name: "refs/heads/parent/child", Target: setup.Commits.First.OID.String()}},
+ },
+ },
+ },
+ },
+ {
+ desc: "delete file-directory conflict in same transaction",
+ steps: steps{
+ StartManager{},
+ Begin{
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/parent/child": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ "refs/heads/parent": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.ObjectHash.ZeroOID},
+ },
+ ExpectedError: updateref.InTransactionConflictError{
+ FirstReferenceName: "refs/heads/parent",
+ SecondReferenceName: "refs/heads/parent/child",
+ },
+ },
+ },
+ },
+ {
+ desc: "file-directory conflict solved in the same transaction",
+ steps: steps{
+ StartManager{},
+ Begin{
+ TransactionID: 1,
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ TransactionID: 1,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/parent": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ },
+ },
+ Begin{
+ TransactionID: 2,
+ RelativePath: setup.RelativePath,
+ ExpectedSnapshotLSN: 1,
+ },
+ UpdateReferences{
+ TransactionID: 2,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/parent": {OldOID: setup.Commits.First.OID, NewOID: setup.ObjectHash.ZeroOID},
+ },
+ },
+ Commit{
+ TransactionID: 2,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/parent/child": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ },
+ },
+ },
+ expectedState: StateAssertion{
+ Database: DatabaseState{
+ string(keyAppliedLSN(setup.PartitionID)): LSN(2).toProto(),
+ },
+ Repositories: RepositoryStates{
+ setup.RelativePath: {
+ References: []git.Reference{{Name: "refs/heads/parent/child", Target: setup.Commits.First.OID.String()}},
+ },
+ },
+ },
+ },
+ {
+ desc: "create a branch to a non-commit object",
+ steps: steps{
+ StartManager{},
+ Begin{
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ SkipVerificationFailures: true,
+ ReferenceUpdates: ReferenceUpdates{
+ // The error should abort the entire transaction.
+ "refs/heads/branch-1": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ "refs/heads/branch-2": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.ObjectHash.EmptyTreeOID},
+ },
+ ExpectedError: updateref.NonCommitObjectError{
+ ReferenceName: "refs/heads/branch-2",
+ ObjectID: setup.ObjectHash.EmptyTreeOID.String(),
+ },
+ },
+ },
+ },
+ {
+ desc: "create a tag to a non-commit object",
+ steps: steps{
+ StartManager{},
+ Begin{
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/tags/v1.0.0": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.ObjectHash.EmptyTreeOID},
+ },
+ },
+ },
+ expectedState: StateAssertion{
+ Database: DatabaseState{
+ string(keyAppliedLSN(setup.PartitionID)): LSN(1).toProto(),
+ },
+ Repositories: RepositoryStates{
+ setup.RelativePath: {
+ References: []git.Reference{{Name: "refs/tags/v1.0.0", Target: setup.ObjectHash.EmptyTreeOID.String()}},
+ },
+ },
+ },
+ },
+ {
+ desc: "create a reference to non-existent object",
+ steps: steps{
+ StartManager{},
+ Begin{
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.NonExistentOID},
+ },
+ ExpectedError: updateref.NonExistentObjectError{
+ ReferenceName: "refs/heads/main",
+ ObjectID: setup.NonExistentOID.String(),
+ },
+ },
+ },
+ },
+ {
+ desc: "create reference ignoring verification failure",
+ steps: steps{
+ StartManager{},
+ Begin{
+ TransactionID: 1,
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ TransactionID: 1,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ },
+ },
+ Begin{
+ TransactionID: 2,
+ RelativePath: setup.RelativePath,
+ ExpectedSnapshotLSN: 1,
+ },
+ Commit{
+ TransactionID: 2,
+ SkipVerificationFailures: true,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.Second.OID},
+ "refs/heads/non-conflicting": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.Second.OID},
+ },
+ },
+ },
+ expectedState: StateAssertion{
+ Database: DatabaseState{
+ string(keyAppliedLSN(setup.PartitionID)): LSN(2).toProto(),
+ },
+ Repositories: RepositoryStates{
+ setup.RelativePath: {
+ DefaultBranch: "refs/heads/main",
+ References: []git.Reference{
+ {Name: "refs/heads/main", Target: setup.Commits.First.OID.String()},
+ {Name: "refs/heads/non-conflicting", Target: setup.Commits.Second.OID.String()},
+ },
+ },
+ },
+ },
+ },
+ {
+ desc: "create reference that already exists",
+ steps: steps{
+ StartManager{},
+ Begin{
+ TransactionID: 1,
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ TransactionID: 1,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ },
+ },
+ Begin{
+ TransactionID: 2,
+ RelativePath: setup.RelativePath,
+ ExpectedSnapshotLSN: 1,
+ },
+ Commit{
+ TransactionID: 2,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.Second.OID},
+ "refs/heads/non-conflicting": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.Second.OID},
+ },
+ ExpectedError: ReferenceVerificationError{
+ ReferenceName: "refs/heads/main",
+ ExpectedOID: setup.ObjectHash.ZeroOID,
+ ActualOID: setup.Commits.First.OID,
+ },
+ },
+ },
+ expectedState: StateAssertion{
+ Database: DatabaseState{
+ string(keyAppliedLSN(setup.PartitionID)): LSN(1).toProto(),
+ },
+ Repositories: RepositoryStates{
+ setup.RelativePath: {
+ DefaultBranch: "refs/heads/main",
+ References: []git.Reference{{Name: "refs/heads/main", Target: setup.Commits.First.OID.String()}},
+ },
+ },
+ },
+ },
+ {
+ desc: "create reference no-op",
+ steps: steps{
+ StartManager{},
+ Begin{
+ TransactionID: 1,
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ TransactionID: 1,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ },
+ },
+ Begin{
+ TransactionID: 2,
+ RelativePath: setup.RelativePath,
+ ExpectedSnapshotLSN: 1,
+ },
+ Commit{
+ TransactionID: 2,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ },
+ ExpectedError: ReferenceVerificationError{
+ ReferenceName: "refs/heads/main",
+ ExpectedOID: setup.ObjectHash.ZeroOID,
+ ActualOID: setup.Commits.First.OID,
+ },
+ },
+ },
+ expectedState: StateAssertion{
+ Database: DatabaseState{
+ string(keyAppliedLSN(setup.PartitionID)): LSN(1).toProto(),
+ },
+ Repositories: RepositoryStates{
+ setup.RelativePath: {
+ DefaultBranch: "refs/heads/main",
+ References: []git.Reference{{Name: "refs/heads/main", Target: setup.Commits.First.OID.String()}},
+ },
+ },
+ },
+ },
+ {
+ desc: "update reference",
+ steps: steps{
+ StartManager{},
+ Begin{
+ TransactionID: 1,
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ TransactionID: 1,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ },
+ },
+ Begin{
+ TransactionID: 2,
+ RelativePath: setup.RelativePath,
+ ExpectedSnapshotLSN: 1,
+ },
+ Commit{
+ TransactionID: 2,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.Commits.First.OID, NewOID: setup.Commits.Second.OID},
+ },
+ },
+ },
+ expectedState: StateAssertion{
+ Database: DatabaseState{
+ string(keyAppliedLSN(setup.PartitionID)): LSN(2).toProto(),
+ },
+ Repositories: RepositoryStates{
+ setup.RelativePath: {
+ DefaultBranch: "refs/heads/main",
+ References: []git.Reference{{Name: "refs/heads/main", Target: setup.Commits.Second.OID.String()}},
+ },
+ },
+ },
+ },
+ {
+ desc: "update reference ignoring verification failures",
+ steps: steps{
+ StartManager{},
+ Begin{
+ TransactionID: 1,
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ TransactionID: 1,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ "refs/heads/non-conflicting": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ },
+ },
+ Begin{
+ TransactionID: 2,
+ RelativePath: setup.RelativePath,
+ ExpectedSnapshotLSN: 1,
+ },
+ Commit{
+ TransactionID: 2,
+ SkipVerificationFailures: true,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.Commits.Second.OID, NewOID: setup.Commits.Third.OID},
+ "refs/heads/non-conflicting": {OldOID: setup.Commits.First.OID, NewOID: setup.Commits.Third.OID},
+ },
+ },
+ },
+ expectedState: StateAssertion{
+ Database: DatabaseState{
+ string(keyAppliedLSN(setup.PartitionID)): LSN(2).toProto(),
+ },
+ Repositories: RepositoryStates{
+ setup.RelativePath: {
+ DefaultBranch: "refs/heads/main",
+ References: []git.Reference{
+ {Name: "refs/heads/main", Target: setup.Commits.First.OID.String()},
+ {Name: "refs/heads/non-conflicting", Target: setup.Commits.Third.OID.String()},
+ },
+ },
+ },
+ },
+ },
+ {
+ desc: "update reference with incorrect old tip",
+ steps: steps{
+ StartManager{},
+ Begin{
+ TransactionID: 1,
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ TransactionID: 1,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ "refs/heads/non-conflicting": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ },
+ },
+ Begin{
+ TransactionID: 2,
+ RelativePath: setup.RelativePath,
+ ExpectedSnapshotLSN: 1,
+ },
+ Commit{
+ TransactionID: 2,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.Commits.Second.OID, NewOID: setup.Commits.Third.OID},
+ "refs/heads/non-conflicting": {OldOID: setup.Commits.First.OID, NewOID: setup.Commits.Third.OID},
+ },
+ ExpectedError: ReferenceVerificationError{
+ ReferenceName: "refs/heads/main",
+ ExpectedOID: setup.Commits.Second.OID,
+ ActualOID: setup.Commits.First.OID,
+ },
+ },
+ },
+ expectedState: StateAssertion{
+ Database: DatabaseState{
+ string(keyAppliedLSN(setup.PartitionID)): LSN(1).toProto(),
+ },
+ Repositories: RepositoryStates{
+ setup.RelativePath: {
+ DefaultBranch: "refs/heads/main",
+ References: []git.Reference{
+ {Name: "refs/heads/main", Target: setup.Commits.First.OID.String()},
+ {Name: "refs/heads/non-conflicting", Target: setup.Commits.First.OID.String()},
+ },
+ },
+ },
+ },
+ },
+ {
+ desc: "update non-existent reference",
+ steps: steps{
+ StartManager{},
+ Begin{
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.Commits.Second.OID, NewOID: setup.Commits.Third.OID},
+ },
+ ExpectedError: ReferenceVerificationError{
+ ReferenceName: "refs/heads/main",
+ ExpectedOID: setup.Commits.Second.OID,
+ ActualOID: setup.ObjectHash.ZeroOID,
+ },
+ },
+ },
+ },
+ {
+ desc: "update reference no-op",
+ steps: steps{
+ StartManager{},
+ Begin{
+ TransactionID: 1,
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ TransactionID: 1,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ },
+ },
+ Begin{
+ TransactionID: 2,
+ RelativePath: setup.RelativePath,
+ ExpectedSnapshotLSN: 1,
+ },
+ Commit{
+ TransactionID: 2,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.Commits.First.OID, NewOID: setup.Commits.First.OID},
+ },
+ },
+ },
+ expectedState: StateAssertion{
+ Database: DatabaseState{
+ string(keyAppliedLSN(setup.PartitionID)): LSN(2).toProto(),
+ },
+ Repositories: RepositoryStates{
+ setup.RelativePath: {
+ DefaultBranch: "refs/heads/main",
+ References: []git.Reference{{Name: "refs/heads/main", Target: setup.Commits.First.OID.String()}},
+ },
+ },
+ },
+ },
+ {
+ desc: "delete reference",
+ steps: steps{
+ StartManager{},
+ Begin{
+ TransactionID: 1,
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ TransactionID: 1,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ },
+ },
+ Begin{
+ TransactionID: 2,
+ RelativePath: setup.RelativePath,
+ ExpectedSnapshotLSN: 1,
+ },
+ Commit{
+ TransactionID: 2,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.Commits.First.OID, NewOID: setup.ObjectHash.ZeroOID},
+ },
+ },
+ },
+ expectedState: StateAssertion{
+ Database: DatabaseState{
+ string(keyAppliedLSN(setup.PartitionID)): LSN(2).toProto(),
+ },
+ },
+ },
+ {
+ desc: "delete symbolic reference pointing to non-existent reference",
+ steps: steps{
+ StartManager{
+ ModifyStorage: func(tb testing.TB, cfg config.Cfg, storagePath string) {
+ gittest.Exec(tb, cfg,
+ "-C", filepath.Join(storagePath, setup.RelativePath),
+ "symbolic-ref", "refs/heads/symbolic", "refs/heads/main",
+ )
+ },
+ },
+ Begin{
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/symbolic": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.ObjectHash.ZeroOID},
+ },
+ },
+ },
+ expectedState: StateAssertion{
+ Database: DatabaseState{
+ string(keyAppliedLSN(setup.PartitionID)): LSN(1).toProto(),
+ },
+ },
+ },
+ {
+ desc: "delete symbolic reference",
+ steps: steps{
+ StartManager{
+ ModifyStorage: func(tb testing.TB, cfg config.Cfg, storagePath string) {
+ gittest.Exec(tb, cfg,
+ "-C", filepath.Join(storagePath, setup.RelativePath),
+ "symbolic-ref", "refs/heads/symbolic", "refs/heads/main",
+ )
+ },
+ },
+ Begin{
+ TransactionID: 1,
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ TransactionID: 1,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ },
+ },
+ Begin{
+ TransactionID: 2,
+ RelativePath: setup.RelativePath,
+ ExpectedSnapshotLSN: 1,
+ },
+ Commit{
+ TransactionID: 2,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/symbolic": {OldOID: setup.Commits.First.OID, NewOID: setup.ObjectHash.ZeroOID},
+ },
+ },
+ },
+ expectedState: StateAssertion{
+ Database: DatabaseState{
+ string(keyAppliedLSN(setup.PartitionID)): LSN(2).toProto(),
+ },
+ Repositories: RepositoryStates{
+ setup.RelativePath: {
+ References: []git.Reference{
+ {Name: "refs/heads/main", Target: setup.Commits.First.OID.String()},
+ },
+ },
+ },
+ },
+ },
+ {
+ desc: "update symbolic reference",
+ steps: steps{
+ StartManager{
+ ModifyStorage: func(tb testing.TB, cfg config.Cfg, storagePath string) {
+ gittest.Exec(tb, cfg,
+ "-C", filepath.Join(storagePath, setup.RelativePath),
+ "symbolic-ref", "refs/heads/symbolic", "refs/heads/main",
+ )
+ },
+ },
+ Begin{
+ TransactionID: 1,
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ TransactionID: 1,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ },
+ },
+ Begin{
+ TransactionID: 2,
+ RelativePath: setup.RelativePath,
+ ExpectedSnapshotLSN: 1,
+ },
+ Commit{
+ TransactionID: 2,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/symbolic": {OldOID: setup.Commits.First.OID, NewOID: setup.Commits.Second.OID},
+ },
+ },
+ },
+ expectedState: StateAssertion{
+ Database: DatabaseState{
+ string(keyAppliedLSN(setup.PartitionID)): LSN(2).toProto(),
+ },
+ Repositories: RepositoryStates{
+ setup.RelativePath: {
+ References: []git.Reference{
+ {Name: "refs/heads/main", Target: setup.Commits.First.OID.String()},
+ // The symbolic reference should be converted to a normal reference if it is
+ // updated.
+ {Name: "refs/heads/symbolic", Target: setup.Commits.Second.OID.String()},
+ },
+ },
+ },
+ },
+ },
+ {
+ desc: "delete reference ignoring verification failures",
+ steps: steps{
+ StartManager{},
+ Begin{
+ TransactionID: 1,
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ TransactionID: 1,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ "refs/heads/non-conflicting": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ },
+ },
+ Begin{
+ TransactionID: 2,
+ RelativePath: setup.RelativePath,
+ ExpectedSnapshotLSN: 1,
+ },
+ Commit{
+ TransactionID: 2,
+ SkipVerificationFailures: true,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.Commits.Second.OID, NewOID: setup.ObjectHash.ZeroOID},
+ "refs/heads/non-conflicting": {OldOID: setup.Commits.First.OID, NewOID: setup.ObjectHash.ZeroOID},
+ },
+ },
+ },
+ expectedState: StateAssertion{
+ Database: DatabaseState{
+ string(keyAppliedLSN(setup.PartitionID)): LSN(2).toProto(),
+ },
+ Repositories: RepositoryStates{
+ setup.RelativePath: {
+ DefaultBranch: "refs/heads/main",
+ References: []git.Reference{{Name: "refs/heads/main", Target: setup.Commits.First.OID.String()}},
+ },
+ },
+ },
+ },
+ {
+ desc: "delete reference with incorrect old tip",
+ steps: steps{
+ StartManager{},
+ Begin{
+ TransactionID: 1,
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ TransactionID: 1,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ "refs/heads/non-conflicting": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ },
+ },
+ Begin{
+ TransactionID: 2,
+ RelativePath: setup.RelativePath,
+ ExpectedSnapshotLSN: 1,
+ },
+ Commit{
+ TransactionID: 2,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.Commits.Second.OID, NewOID: setup.ObjectHash.ZeroOID},
+ "refs/heads/non-conflicting": {OldOID: setup.Commits.First.OID, NewOID: setup.ObjectHash.ZeroOID},
+ },
+ ExpectedError: ReferenceVerificationError{
+ ReferenceName: "refs/heads/main",
+ ExpectedOID: setup.Commits.Second.OID,
+ ActualOID: setup.Commits.First.OID,
+ },
+ },
+ },
+ expectedState: StateAssertion{
+ Database: DatabaseState{
+ string(keyAppliedLSN(setup.PartitionID)): LSN(1).toProto(),
+ },
+ Repositories: RepositoryStates{
+ setup.RelativePath: {
+ DefaultBranch: "refs/heads/main",
+ References: []git.Reference{
+ {Name: "refs/heads/main", Target: setup.Commits.First.OID.String()},
+ {Name: "refs/heads/non-conflicting", Target: setup.Commits.First.OID.String()},
+ },
+ },
+ },
+ },
+ },
+ {
+ desc: "delete non-existent reference",
+ steps: steps{
+ StartManager{},
+ Begin{
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.Commits.First.OID, NewOID: setup.ObjectHash.ZeroOID},
+ },
+ ExpectedError: ReferenceVerificationError{
+ ReferenceName: "refs/heads/main",
+ ExpectedOID: setup.Commits.First.OID,
+ ActualOID: setup.ObjectHash.ZeroOID,
+ },
+ },
+ },
+ },
+ {
+ desc: "delete reference no-op",
+ steps: steps{
+ StartManager{},
+ Begin{
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.ObjectHash.ZeroOID},
+ },
+ },
+ },
+ expectedState: StateAssertion{
+ Database: DatabaseState{
+ string(keyAppliedLSN(setup.PartitionID)): LSN(1).toProto(),
+ },
+ },
+ },
+ {
+ desc: "update reference multiple times successfully in a transaction",
+ steps: steps{
+ StartManager{},
+ Begin{
+ RelativePath: setup.RelativePath,
+ },
+ UpdateReferences{
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ },
+ },
+ UpdateReferences{
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.Commits.First.OID, NewOID: setup.Commits.Second.OID},
+ },
+ },
+ Commit{},
+ },
+ expectedState: StateAssertion{
+ Database: DatabaseState{
+ string(keyAppliedLSN(setup.PartitionID)): LSN(1).toProto(),
+ },
+ Repositories: RepositoryStates{
+ setup.RelativePath: {
+ References: []git.Reference{{Name: "refs/heads/main", Target: setup.Commits.Second.OID.String()}},
+ },
+ },
+ },
+ },
+ {
+ desc: "update reference multiple times fails due to wrong initial value",
+ steps: steps{
+ StartManager{},
+ Begin{
+ RelativePath: setup.RelativePath,
+ },
+ UpdateReferences{
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.Commits.First.OID, NewOID: setup.Commits.Second.OID},
+ },
+ },
+ UpdateReferences{
+ ReferenceUpdates: ReferenceUpdates{
+ // The old oid should be ignored since there's already a recorded initial value for the
+ // reference.
+ "refs/heads/main": {NewOID: setup.Commits.Third.OID},
+ },
+ },
+ Commit{
+ ExpectedError: ReferenceVerificationError{
+ ReferenceName: "refs/heads/main",
+ ExpectedOID: setup.Commits.First.OID,
+ ActualOID: setup.ObjectHash.ZeroOID,
+ },
+ },
+ },
+ },
+ {
+ desc: "recording initial value of a reference stages no updates",
+ steps: steps{
+ StartManager{},
+ Begin{
+ RelativePath: setup.RelativePath,
+ },
+ RecordInitialReferenceValues{
+ InitialValues: map[git.ReferenceName]git.ObjectID{
+ "refs/heads/main": setup.Commits.First.OID,
+ },
+ },
+ Commit{},
+ },
+ expectedState: StateAssertion{
+ Database: DatabaseState{
+ string(keyAppliedLSN(setup.PartitionID)): LSN(1).toProto(),
+ },
+ },
+ },
+ {
+ desc: "update reference with non-existent initial value",
+ steps: steps{
+ StartManager{},
+ Begin{
+ RelativePath: setup.RelativePath,
+ },
+ RecordInitialReferenceValues{
+ InitialValues: map[git.ReferenceName]git.ObjectID{
+ "refs/heads/main": setup.ObjectHash.ZeroOID,
+ },
+ },
+ UpdateReferences{
+ // The old oid is ignored as the references old value was already recorded.
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {NewOID: setup.Commits.First.OID},
+ },
+ },
+ Commit{},
+ },
+ expectedState: StateAssertion{
+ Database: DatabaseState{
+ string(keyAppliedLSN(setup.PartitionID)): LSN(1).toProto(),
+ },
+ Repositories: RepositoryStates{
+ setup.RelativePath: {
+ References: []git.Reference{{Name: "refs/heads/main", Target: setup.Commits.First.OID.String()}},
+ },
+ },
+ },
+ },
+ {
+ desc: "update reference with the zero oid initial value",
+ steps: steps{
+ StartManager{},
+ Begin{
+ TransactionID: 1,
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ TransactionID: 1,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ },
+ },
+ Begin{
+ TransactionID: 2,
+ RelativePath: setup.RelativePath,
+ ExpectedSnapshotLSN: 1,
+ },
+ RecordInitialReferenceValues{
+ TransactionID: 2,
+ InitialValues: map[git.ReferenceName]git.ObjectID{
+ "refs/heads/main": setup.ObjectHash.ZeroOID,
+ },
+ },
+ UpdateReferences{
+ TransactionID: 2,
+ // The old oid is ignored as the references old value was already recorded.
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {NewOID: setup.Commits.Second.OID},
+ },
+ },
+ Commit{
+ TransactionID: 2,
+ },
+ },
+ expectedState: StateAssertion{
+ Database: DatabaseState{
+ string(keyAppliedLSN(setup.PartitionID)): LSN(2).toProto(),
+ },
+ Repositories: RepositoryStates{
+ setup.RelativePath: {
+ References: []git.Reference{{Name: "refs/heads/main", Target: setup.Commits.Second.OID.String()}},
+ },
+ },
+ },
+ },
+ {
+ desc: "update reference with the correct initial value",
+ steps: steps{
+ StartManager{},
+ Begin{
+ TransactionID: 1,
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ TransactionID: 1,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ },
+ },
+ Begin{
+ TransactionID: 2,
+ RelativePath: setup.RelativePath,
+ ExpectedSnapshotLSN: 1,
+ },
+ RecordInitialReferenceValues{
+ TransactionID: 2,
+ InitialValues: map[git.ReferenceName]git.ObjectID{
+ "refs/heads/main": setup.Commits.First.OID,
+ },
+ },
+ UpdateReferences{
+ TransactionID: 2,
+ // The old oid is ignored as the references old value was already recorded.
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {NewOID: setup.Commits.Second.OID},
+ },
+ },
+ Commit{
+ TransactionID: 2,
+ },
+ },
+ expectedState: StateAssertion{
+ Database: DatabaseState{
+ string(keyAppliedLSN(setup.PartitionID)): LSN(2).toProto(),
+ },
+ Repositories: RepositoryStates{
+ setup.RelativePath: {
+ References: []git.Reference{{Name: "refs/heads/main", Target: setup.Commits.Second.OID.String()}},
+ },
+ },
+ },
+ },
+ {
+ desc: "update reference with the incorrect initial value",
+ steps: steps{
+ StartManager{},
+ Begin{
+ TransactionID: 1,
+ RelativePath: setup.RelativePath,
+ },
+ Commit{
+ TransactionID: 1,
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ },
+ },
+ Begin{
+ TransactionID: 2,
+ RelativePath: setup.RelativePath,
+ ExpectedSnapshotLSN: 1,
+ },
+ RecordInitialReferenceValues{
+ TransactionID: 2,
+ InitialValues: map[git.ReferenceName]git.ObjectID{
+ "refs/heads/main": setup.Commits.Third.OID,
+ },
+ },
+ UpdateReferences{
+ TransactionID: 2,
+ // The old oid is ignored as the references old value was already recorded.
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {NewOID: setup.Commits.Second.OID},
+ },
+ },
+ Commit{
+ TransactionID: 2,
+ ExpectedError: ReferenceVerificationError{
+ ReferenceName: "refs/heads/main",
+ ExpectedOID: setup.Commits.Third.OID,
+ ActualOID: setup.Commits.First.OID,
+ },
+ },
+ },
+ expectedState: StateAssertion{
+ Database: DatabaseState{
+ string(keyAppliedLSN(setup.PartitionID)): LSN(1).toProto(),
+ },
+ Repositories: RepositoryStates{
+ setup.RelativePath: {
+ References: []git.Reference{{Name: "refs/heads/main", Target: setup.Commits.First.OID.String()}},
+ },
+ },
+ },
+ },
+ {
+ desc: "initial value is set on the first update",
+ steps: steps{
+ StartManager{},
+ Begin{
+ RelativePath: setup.RelativePath,
+ },
+ UpdateReferences{
+ ReferenceUpdates: ReferenceUpdates{
+ "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
+ },
+ },
+ RecordInitialReferenceValues{
+ InitialValues: map[git.ReferenceName]git.ObjectID{
+ "refs/heads/main": setup.Commits.Third.OID,
+ },
+ },
+ Commit{},
+ },
+ expectedState: StateAssertion{
+ Database: DatabaseState{
+ string(keyAppliedLSN(setup.PartitionID)): LSN(1).toProto(),
+ },
+ Repositories: RepositoryStates{
+ setup.RelativePath: {
+ References: []git.Reference{{Name: "refs/heads/main", Target: setup.Commits.First.OID.String()}},
+ },
+ },
+ },
+ },
+ }
+}
diff --git a/internal/gitaly/storage/storagemgr/transaction_manager_test.go b/internal/gitaly/storage/storagemgr/transaction_manager_test.go
index 733165fa8..b6f47ccc1 100644
--- a/internal/gitaly/storage/storagemgr/transaction_manager_test.go
+++ b/internal/gitaly/storage/storagemgr/transaction_manager_test.go
@@ -251,1341 +251,6 @@ func TestTransactionManager(t *testing.T) {
testCases := []transactionTestCase{
{
- desc: "invalid reference aborts the entire transaction",
- steps: steps{
- StartManager{},
- Begin{
- RelativePath: relativePath,
- },
- Commit{
- SkipVerificationFailures: true,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- "refs/heads/../main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- ExpectedError: InvalidReferenceFormatError{ReferenceName: "refs/heads/../main"},
- },
- },
- },
- {
- desc: "continues processing after aborting due to an invalid reference",
- steps: steps{
- StartManager{},
- Begin{
- TransactionID: 1,
- RelativePath: relativePath,
- },
- Commit{
- TransactionID: 1,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/../main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- ExpectedError: InvalidReferenceFormatError{ReferenceName: "refs/heads/../main"},
- },
- Begin{
- TransactionID: 2,
- RelativePath: relativePath,
- },
- Commit{
- TransactionID: 2,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- },
- },
- expectedState: StateAssertion{
- Database: DatabaseState{
- string(keyAppliedLSN(partitionID)): LSN(1).toProto(),
- },
- Repositories: RepositoryStates{
- relativePath: {
- DefaultBranch: "refs/heads/main",
- References: []git.Reference{{Name: "refs/heads/main", Target: setup.Commits.First.OID.String()}},
- },
- },
- },
- },
- {
- desc: "create reference",
- steps: steps{
- StartManager{},
- Begin{
- RelativePath: relativePath,
- },
- Commit{
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- },
- },
- expectedState: StateAssertion{
- Database: DatabaseState{
- string(keyAppliedLSN(partitionID)): LSN(1).toProto(),
- },
- Repositories: RepositoryStates{
- relativePath: {
- DefaultBranch: "refs/heads/main",
- References: []git.Reference{{Name: "refs/heads/main", Target: setup.Commits.First.OID.String()}},
- },
- },
- },
- },
- {
- desc: "create reference with existing reference lock",
- steps: steps{
- StartManager{
- ModifyStorage: func(_ testing.TB, _ config.Cfg, storagePath string) {
- err := os.WriteFile(filepath.Join(storagePath, relativePath, "refs", "heads", "main.lock"), []byte{}, 0o666)
- require.NoError(t, err)
- },
- },
- Begin{
- RelativePath: relativePath,
- },
- Commit{
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- },
- },
- expectedState: StateAssertion{
- Database: DatabaseState{
- string(keyAppliedLSN(partitionID)): LSN(1).toProto(),
- },
- Repositories: RepositoryStates{
- relativePath: {
- DefaultBranch: "refs/heads/main",
- References: []git.Reference{{Name: "refs/heads/main", Target: setup.Commits.First.OID.String()}},
- },
- },
- },
- },
- {
- desc: "delete packed reference with existing packed-refs.lock",
- steps: steps{
- StartManager{},
- Begin{
- TransactionID: 1,
- RelativePath: relativePath,
- },
- Commit{
- TransactionID: 1,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- },
- CloseManager{},
- StartManager{
- ModifyStorage: func(tb testing.TB, cfg config.Cfg, storagePath string) {
- repoPath := filepath.Join(storagePath, relativePath)
- // Pack the reference and create a stale lockfile for it.
- gittest.Exec(tb, cfg, "-C", repoPath, "pack-refs", "--all")
-
- // Add packed-refs.lock. The reference deletion will fail if this
- // isn't cleaned up.
- require.NoError(t, os.WriteFile(
- filepath.Join(repoPath, "packed-refs.lock"),
- []byte{},
- perm.PrivateFile,
- ))
- },
- },
- Begin{
- TransactionID: 2,
- RelativePath: relativePath,
- ExpectedSnapshotLSN: 1,
- },
- Commit{
- TransactionID: 2,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.Commits.First.OID, NewOID: setup.ObjectHash.ZeroOID},
- },
- },
- },
- expectedState: StateAssertion{
- Database: DatabaseState{
- string(keyAppliedLSN(partitionID)): LSN(2).toProto(),
- },
- },
- },
- {
- desc: "delete packed reference with existing packed-refs.new",
- steps: steps{
- StartManager{},
- Begin{
- TransactionID: 1,
- RelativePath: relativePath,
- },
- Commit{
- TransactionID: 1,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- },
- CloseManager{},
- StartManager{
- ModifyStorage: func(tb testing.TB, cfg config.Cfg, storagePath string) {
- repoPath := filepath.Join(storagePath, relativePath)
- // Pack the reference and create a stale lockfile for it.
- gittest.Exec(tb, cfg, "-C", repoPath, "pack-refs", "--all")
-
- // Add packed-refs.new. The reference deletion will fail if this
- // isn't cleaned up.
- require.NoError(t, os.WriteFile(
- filepath.Join(repoPath, "packed-refs.new"),
- []byte{},
- perm.PrivateFile,
- ))
- },
- },
- Begin{
- TransactionID: 2,
- RelativePath: relativePath,
- ExpectedSnapshotLSN: 1,
- },
- Commit{
- TransactionID: 2,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.Commits.First.OID, NewOID: setup.ObjectHash.ZeroOID},
- },
- },
- },
- expectedState: StateAssertion{
- Database: DatabaseState{
- string(keyAppliedLSN(partitionID)): LSN(2).toProto(),
- },
- },
- },
- {
- desc: "create a file-directory reference conflict different transaction",
- steps: steps{
- StartManager{},
- Begin{
- TransactionID: 1,
- RelativePath: relativePath,
- },
- Commit{
- TransactionID: 1,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/parent": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- },
- Begin{
- TransactionID: 2,
- RelativePath: relativePath,
- ExpectedSnapshotLSN: 1,
- },
- Commit{
- TransactionID: 2,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/parent/child": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- ExpectedError: updateref.FileDirectoryConflictError{
- ExistingReferenceName: "refs/heads/parent",
- ConflictingReferenceName: "refs/heads/parent/child",
- },
- },
- },
- expectedState: StateAssertion{
- Database: DatabaseState{
- string(keyAppliedLSN(partitionID)): LSN(1).toProto(),
- },
- Repositories: RepositoryStates{
- relativePath: {
- References: []git.Reference{{Name: "refs/heads/parent", Target: setup.Commits.First.OID.String()}},
- },
- },
- },
- },
- {
- desc: "create a file-directory reference conflict in same transaction",
- steps: steps{
- StartManager{},
- Begin{
- RelativePath: relativePath,
- },
- Commit{
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/parent": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- "refs/heads/parent/child": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- ExpectedError: updateref.InTransactionConflictError{
- FirstReferenceName: "refs/heads/parent",
- SecondReferenceName: "refs/heads/parent/child",
- },
- },
- },
- },
- {
- desc: "file-directory conflict aborts the transaction with verification failures skipped",
- steps: steps{
- StartManager{},
- Begin{
- RelativePath: relativePath,
- },
- Commit{
- SkipVerificationFailures: true,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- "refs/heads/parent": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- "refs/heads/parent/child": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- ExpectedError: updateref.InTransactionConflictError{
- FirstReferenceName: "refs/heads/parent",
- SecondReferenceName: "refs/heads/parent/child",
- },
- },
- },
- },
- {
- desc: "delete file-directory conflict in different transaction",
- steps: steps{
- StartManager{},
- Begin{
- TransactionID: 1,
- RelativePath: relativePath,
- },
- Commit{
- TransactionID: 1,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/parent/child": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- },
- Begin{
- TransactionID: 2,
- RelativePath: relativePath,
- ExpectedSnapshotLSN: 1,
- },
- Commit{
- TransactionID: 2,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/parent": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.ObjectHash.ZeroOID},
- },
- ExpectedError: updateref.FileDirectoryConflictError{
- ExistingReferenceName: "refs/heads/parent/child",
- ConflictingReferenceName: "refs/heads/parent",
- },
- },
- },
- expectedState: StateAssertion{
- Database: DatabaseState{
- string(keyAppliedLSN(partitionID)): LSN(1).toProto(),
- },
- Repositories: RepositoryStates{
- relativePath: {
- References: []git.Reference{{Name: "refs/heads/parent/child", Target: setup.Commits.First.OID.String()}},
- },
- },
- },
- },
- {
- desc: "delete file-directory conflict in same transaction",
- steps: steps{
- StartManager{},
- Begin{
- RelativePath: relativePath,
- },
- Commit{
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/parent/child": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- "refs/heads/parent": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.ObjectHash.ZeroOID},
- },
- ExpectedError: updateref.InTransactionConflictError{
- FirstReferenceName: "refs/heads/parent",
- SecondReferenceName: "refs/heads/parent/child",
- },
- },
- },
- },
- {
- desc: "file-directory conflict solved in the same transaction",
- steps: steps{
- StartManager{},
- Begin{
- TransactionID: 1,
- RelativePath: relativePath,
- },
- Commit{
- TransactionID: 1,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/parent": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- },
- Begin{
- TransactionID: 2,
- RelativePath: relativePath,
- ExpectedSnapshotLSN: 1,
- },
- UpdateReferences{
- TransactionID: 2,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/parent": {OldOID: setup.Commits.First.OID, NewOID: setup.ObjectHash.ZeroOID},
- },
- },
- Commit{
- TransactionID: 2,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/parent/child": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- },
- },
- expectedState: StateAssertion{
- Database: DatabaseState{
- string(keyAppliedLSN(partitionID)): LSN(2).toProto(),
- },
- Repositories: RepositoryStates{
- relativePath: {
- References: []git.Reference{{Name: "refs/heads/parent/child", Target: setup.Commits.First.OID.String()}},
- },
- },
- },
- },
- {
- desc: "create a branch to a non-commit object",
- steps: steps{
- StartManager{},
- Begin{
- RelativePath: relativePath,
- },
- Commit{
- SkipVerificationFailures: true,
- ReferenceUpdates: ReferenceUpdates{
- // The error should abort the entire transaction.
- "refs/heads/branch-1": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- "refs/heads/branch-2": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.ObjectHash.EmptyTreeOID},
- },
- ExpectedError: updateref.NonCommitObjectError{
- ReferenceName: "refs/heads/branch-2",
- ObjectID: setup.ObjectHash.EmptyTreeOID.String(),
- },
- },
- },
- },
- {
- desc: "create a tag to a non-commit object",
- steps: steps{
- StartManager{},
- Begin{
- RelativePath: relativePath,
- },
- Commit{
- ReferenceUpdates: ReferenceUpdates{
- "refs/tags/v1.0.0": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.ObjectHash.EmptyTreeOID},
- },
- },
- },
- expectedState: StateAssertion{
- Database: DatabaseState{
- string(keyAppliedLSN(partitionID)): LSN(1).toProto(),
- },
- Repositories: RepositoryStates{
- relativePath: {
- References: []git.Reference{{Name: "refs/tags/v1.0.0", Target: setup.ObjectHash.EmptyTreeOID.String()}},
- },
- },
- },
- },
- {
- desc: "create a reference to non-existent object",
- steps: steps{
- StartManager{},
- Begin{
- RelativePath: relativePath,
- },
- Commit{
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.NonExistentOID},
- },
- ExpectedError: updateref.NonExistentObjectError{
- ReferenceName: "refs/heads/main",
- ObjectID: setup.NonExistentOID.String(),
- },
- },
- },
- },
- {
- desc: "create reference ignoring verification failure",
- steps: steps{
- StartManager{},
- Begin{
- TransactionID: 1,
- RelativePath: relativePath,
- },
- Commit{
- TransactionID: 1,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- },
- Begin{
- TransactionID: 2,
- RelativePath: relativePath,
- ExpectedSnapshotLSN: 1,
- },
- Commit{
- TransactionID: 2,
- SkipVerificationFailures: true,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.Second.OID},
- "refs/heads/non-conflicting": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.Second.OID},
- },
- },
- },
- expectedState: StateAssertion{
- Database: DatabaseState{
- string(keyAppliedLSN(partitionID)): LSN(2).toProto(),
- },
- Repositories: RepositoryStates{
- relativePath: {
- DefaultBranch: "refs/heads/main",
- References: []git.Reference{
- {Name: "refs/heads/main", Target: setup.Commits.First.OID.String()},
- {Name: "refs/heads/non-conflicting", Target: setup.Commits.Second.OID.String()},
- },
- },
- },
- },
- },
- {
- desc: "create reference that already exists",
- steps: steps{
- StartManager{},
- Begin{
- TransactionID: 1,
- RelativePath: relativePath,
- },
- Commit{
- TransactionID: 1,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- },
- Begin{
- TransactionID: 2,
- RelativePath: relativePath,
- ExpectedSnapshotLSN: 1,
- },
- Commit{
- TransactionID: 2,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.Second.OID},
- "refs/heads/non-conflicting": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.Second.OID},
- },
- ExpectedError: ReferenceVerificationError{
- ReferenceName: "refs/heads/main",
- ExpectedOID: setup.ObjectHash.ZeroOID,
- ActualOID: setup.Commits.First.OID,
- },
- },
- },
- expectedState: StateAssertion{
- Database: DatabaseState{
- string(keyAppliedLSN(partitionID)): LSN(1).toProto(),
- },
- Repositories: RepositoryStates{
- relativePath: {
- DefaultBranch: "refs/heads/main",
- References: []git.Reference{{Name: "refs/heads/main", Target: setup.Commits.First.OID.String()}},
- },
- },
- },
- },
- {
- desc: "create reference no-op",
- steps: steps{
- StartManager{},
- Begin{
- TransactionID: 1,
- RelativePath: relativePath,
- },
- Commit{
- TransactionID: 1,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- },
- Begin{
- TransactionID: 2,
- RelativePath: relativePath,
- ExpectedSnapshotLSN: 1,
- },
- Commit{
- TransactionID: 2,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- ExpectedError: ReferenceVerificationError{
- ReferenceName: "refs/heads/main",
- ExpectedOID: setup.ObjectHash.ZeroOID,
- ActualOID: setup.Commits.First.OID,
- },
- },
- },
- expectedState: StateAssertion{
- Database: DatabaseState{
- string(keyAppliedLSN(partitionID)): LSN(1).toProto(),
- },
- Repositories: RepositoryStates{
- relativePath: {
- DefaultBranch: "refs/heads/main",
- References: []git.Reference{{Name: "refs/heads/main", Target: setup.Commits.First.OID.String()}},
- },
- },
- },
- },
- {
- desc: "update reference",
- steps: steps{
- StartManager{},
- Begin{
- TransactionID: 1,
- RelativePath: relativePath,
- },
- Commit{
- TransactionID: 1,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- },
- Begin{
- TransactionID: 2,
- RelativePath: relativePath,
- ExpectedSnapshotLSN: 1,
- },
- Commit{
- TransactionID: 2,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.Commits.First.OID, NewOID: setup.Commits.Second.OID},
- },
- },
- },
- expectedState: StateAssertion{
- Database: DatabaseState{
- string(keyAppliedLSN(partitionID)): LSN(2).toProto(),
- },
- Repositories: RepositoryStates{
- relativePath: {
- DefaultBranch: "refs/heads/main",
- References: []git.Reference{{Name: "refs/heads/main", Target: setup.Commits.Second.OID.String()}},
- },
- },
- },
- },
- {
- desc: "update reference ignoring verification failures",
- steps: steps{
- StartManager{},
- Begin{
- TransactionID: 1,
- RelativePath: relativePath,
- },
- Commit{
- TransactionID: 1,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- "refs/heads/non-conflicting": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- },
- Begin{
- TransactionID: 2,
- RelativePath: relativePath,
- ExpectedSnapshotLSN: 1,
- },
- Commit{
- TransactionID: 2,
- SkipVerificationFailures: true,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.Commits.Second.OID, NewOID: setup.Commits.Third.OID},
- "refs/heads/non-conflicting": {OldOID: setup.Commits.First.OID, NewOID: setup.Commits.Third.OID},
- },
- },
- },
- expectedState: StateAssertion{
- Database: DatabaseState{
- string(keyAppliedLSN(partitionID)): LSN(2).toProto(),
- },
- Repositories: RepositoryStates{
- relativePath: {
- DefaultBranch: "refs/heads/main",
- References: []git.Reference{
- {Name: "refs/heads/main", Target: setup.Commits.First.OID.String()},
- {Name: "refs/heads/non-conflicting", Target: setup.Commits.Third.OID.String()},
- },
- },
- },
- },
- },
- {
- desc: "update reference with incorrect old tip",
- steps: steps{
- StartManager{},
- Begin{
- TransactionID: 1,
- RelativePath: relativePath,
- },
- Commit{
- TransactionID: 1,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- "refs/heads/non-conflicting": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- },
- Begin{
- TransactionID: 2,
- RelativePath: relativePath,
- ExpectedSnapshotLSN: 1,
- },
- Commit{
- TransactionID: 2,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.Commits.Second.OID, NewOID: setup.Commits.Third.OID},
- "refs/heads/non-conflicting": {OldOID: setup.Commits.First.OID, NewOID: setup.Commits.Third.OID},
- },
- ExpectedError: ReferenceVerificationError{
- ReferenceName: "refs/heads/main",
- ExpectedOID: setup.Commits.Second.OID,
- ActualOID: setup.Commits.First.OID,
- },
- },
- },
- expectedState: StateAssertion{
- Database: DatabaseState{
- string(keyAppliedLSN(partitionID)): LSN(1).toProto(),
- },
- Repositories: RepositoryStates{
- relativePath: {
- DefaultBranch: "refs/heads/main",
- References: []git.Reference{
- {Name: "refs/heads/main", Target: setup.Commits.First.OID.String()},
- {Name: "refs/heads/non-conflicting", Target: setup.Commits.First.OID.String()},
- },
- },
- },
- },
- },
- {
- desc: "update non-existent reference",
- steps: steps{
- StartManager{},
- Begin{
- RelativePath: relativePath,
- },
- Commit{
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.Commits.Second.OID, NewOID: setup.Commits.Third.OID},
- },
- ExpectedError: ReferenceVerificationError{
- ReferenceName: "refs/heads/main",
- ExpectedOID: setup.Commits.Second.OID,
- ActualOID: setup.ObjectHash.ZeroOID,
- },
- },
- },
- },
- {
- desc: "update reference no-op",
- steps: steps{
- StartManager{},
- Begin{
- TransactionID: 1,
- RelativePath: relativePath,
- },
- Commit{
- TransactionID: 1,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- },
- Begin{
- TransactionID: 2,
- RelativePath: relativePath,
- ExpectedSnapshotLSN: 1,
- },
- Commit{
- TransactionID: 2,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.Commits.First.OID, NewOID: setup.Commits.First.OID},
- },
- },
- },
- expectedState: StateAssertion{
- Database: DatabaseState{
- string(keyAppliedLSN(partitionID)): LSN(2).toProto(),
- },
- Repositories: RepositoryStates{
- relativePath: {
- DefaultBranch: "refs/heads/main",
- References: []git.Reference{{Name: "refs/heads/main", Target: setup.Commits.First.OID.String()}},
- },
- },
- },
- },
- {
- desc: "delete reference",
- steps: steps{
- StartManager{},
- Begin{
- TransactionID: 1,
- RelativePath: relativePath,
- },
- Commit{
- TransactionID: 1,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- },
- Begin{
- TransactionID: 2,
- RelativePath: relativePath,
- ExpectedSnapshotLSN: 1,
- },
- Commit{
- TransactionID: 2,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.Commits.First.OID, NewOID: setup.ObjectHash.ZeroOID},
- },
- },
- },
- expectedState: StateAssertion{
- Database: DatabaseState{
- string(keyAppliedLSN(partitionID)): LSN(2).toProto(),
- },
- },
- },
- {
- desc: "delete symbolic reference pointing to non-existent reference",
- steps: steps{
- StartManager{
- ModifyStorage: func(tb testing.TB, cfg config.Cfg, storagePath string) {
- gittest.Exec(tb, cfg,
- "-C", filepath.Join(storagePath, relativePath),
- "symbolic-ref", "refs/heads/symbolic", "refs/heads/main",
- )
- },
- },
- Begin{
- RelativePath: relativePath,
- },
- Commit{
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/symbolic": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.ObjectHash.ZeroOID},
- },
- },
- },
- expectedState: StateAssertion{
- Database: DatabaseState{
- string(keyAppliedLSN(partitionID)): LSN(1).toProto(),
- },
- },
- },
- {
- desc: "delete symbolic reference",
- steps: steps{
- StartManager{
- ModifyStorage: func(tb testing.TB, cfg config.Cfg, storagePath string) {
- gittest.Exec(tb, cfg,
- "-C", filepath.Join(storagePath, relativePath),
- "symbolic-ref", "refs/heads/symbolic", "refs/heads/main",
- )
- },
- },
- Begin{
- TransactionID: 1,
- RelativePath: relativePath,
- },
- Commit{
- TransactionID: 1,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- },
- Begin{
- TransactionID: 2,
- RelativePath: relativePath,
- ExpectedSnapshotLSN: 1,
- },
- Commit{
- TransactionID: 2,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/symbolic": {OldOID: setup.Commits.First.OID, NewOID: setup.ObjectHash.ZeroOID},
- },
- },
- },
- expectedState: StateAssertion{
- Database: DatabaseState{
- string(keyAppliedLSN(partitionID)): LSN(2).toProto(),
- },
- Repositories: RepositoryStates{
- relativePath: {
- References: []git.Reference{
- {Name: "refs/heads/main", Target: setup.Commits.First.OID.String()},
- },
- },
- },
- },
- },
- {
- desc: "update symbolic reference",
- steps: steps{
- StartManager{
- ModifyStorage: func(tb testing.TB, cfg config.Cfg, storagePath string) {
- gittest.Exec(tb, cfg,
- "-C", filepath.Join(storagePath, relativePath),
- "symbolic-ref", "refs/heads/symbolic", "refs/heads/main",
- )
- },
- },
- Begin{
- TransactionID: 1,
- RelativePath: relativePath,
- },
- Commit{
- TransactionID: 1,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- },
- Begin{
- TransactionID: 2,
- RelativePath: relativePath,
- ExpectedSnapshotLSN: 1,
- },
- Commit{
- TransactionID: 2,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/symbolic": {OldOID: setup.Commits.First.OID, NewOID: setup.Commits.Second.OID},
- },
- },
- },
- expectedState: StateAssertion{
- Database: DatabaseState{
- string(keyAppliedLSN(partitionID)): LSN(2).toProto(),
- },
- Repositories: RepositoryStates{
- relativePath: {
- References: []git.Reference{
- {Name: "refs/heads/main", Target: setup.Commits.First.OID.String()},
- // The symbolic reference should be converted to a normal reference if it is
- // updated.
- {Name: "refs/heads/symbolic", Target: setup.Commits.Second.OID.String()},
- },
- },
- },
- },
- },
- {
- desc: "delete reference ignoring verification failures",
- steps: steps{
- StartManager{},
- Begin{
- TransactionID: 1,
- RelativePath: relativePath,
- },
- Commit{
- TransactionID: 1,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- "refs/heads/non-conflicting": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- },
- Begin{
- TransactionID: 2,
- RelativePath: relativePath,
- ExpectedSnapshotLSN: 1,
- },
- Commit{
- TransactionID: 2,
- SkipVerificationFailures: true,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.Commits.Second.OID, NewOID: setup.ObjectHash.ZeroOID},
- "refs/heads/non-conflicting": {OldOID: setup.Commits.First.OID, NewOID: setup.ObjectHash.ZeroOID},
- },
- },
- },
- expectedState: StateAssertion{
- Database: DatabaseState{
- string(keyAppliedLSN(partitionID)): LSN(2).toProto(),
- },
- Repositories: RepositoryStates{
- relativePath: {
- DefaultBranch: "refs/heads/main",
- References: []git.Reference{{Name: "refs/heads/main", Target: setup.Commits.First.OID.String()}},
- },
- },
- },
- },
- {
- desc: "delete reference with incorrect old tip",
- steps: steps{
- StartManager{},
- Begin{
- TransactionID: 1,
- RelativePath: relativePath,
- },
- Commit{
- TransactionID: 1,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- "refs/heads/non-conflicting": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- },
- Begin{
- TransactionID: 2,
- RelativePath: relativePath,
- ExpectedSnapshotLSN: 1,
- },
- Commit{
- TransactionID: 2,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.Commits.Second.OID, NewOID: setup.ObjectHash.ZeroOID},
- "refs/heads/non-conflicting": {OldOID: setup.Commits.First.OID, NewOID: setup.ObjectHash.ZeroOID},
- },
- ExpectedError: ReferenceVerificationError{
- ReferenceName: "refs/heads/main",
- ExpectedOID: setup.Commits.Second.OID,
- ActualOID: setup.Commits.First.OID,
- },
- },
- },
- expectedState: StateAssertion{
- Database: DatabaseState{
- string(keyAppliedLSN(partitionID)): LSN(1).toProto(),
- },
- Repositories: RepositoryStates{
- relativePath: {
- DefaultBranch: "refs/heads/main",
- References: []git.Reference{
- {Name: "refs/heads/main", Target: setup.Commits.First.OID.String()},
- {Name: "refs/heads/non-conflicting", Target: setup.Commits.First.OID.String()},
- },
- },
- },
- },
- },
- {
- desc: "delete non-existent reference",
- steps: steps{
- StartManager{},
- Begin{
- RelativePath: relativePath,
- },
- Commit{
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.Commits.First.OID, NewOID: setup.ObjectHash.ZeroOID},
- },
- ExpectedError: ReferenceVerificationError{
- ReferenceName: "refs/heads/main",
- ExpectedOID: setup.Commits.First.OID,
- ActualOID: setup.ObjectHash.ZeroOID,
- },
- },
- },
- },
- {
- desc: "delete reference no-op",
- steps: steps{
- StartManager{},
- Begin{
- RelativePath: relativePath,
- },
- Commit{
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.ObjectHash.ZeroOID},
- },
- },
- },
- expectedState: StateAssertion{
- Database: DatabaseState{
- string(keyAppliedLSN(partitionID)): LSN(1).toProto(),
- },
- },
- },
- {
- desc: "update reference multiple times successfully in a transaction",
- steps: steps{
- StartManager{},
- Begin{
- RelativePath: relativePath,
- },
- UpdateReferences{
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- },
- UpdateReferences{
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.Commits.First.OID, NewOID: setup.Commits.Second.OID},
- },
- },
- Commit{},
- },
- expectedState: StateAssertion{
- Database: DatabaseState{
- string(keyAppliedLSN(partitionID)): LSN(1).toProto(),
- },
- Repositories: RepositoryStates{
- relativePath: {
- References: []git.Reference{{Name: "refs/heads/main", Target: setup.Commits.Second.OID.String()}},
- },
- },
- },
- },
- {
- desc: "update reference multiple times fails due to wrong initial value",
- steps: steps{
- StartManager{},
- Begin{
- RelativePath: relativePath,
- },
- UpdateReferences{
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.Commits.First.OID, NewOID: setup.Commits.Second.OID},
- },
- },
- UpdateReferences{
- ReferenceUpdates: ReferenceUpdates{
- // The old oid should be ignored since there's already a recorded initial value for the
- // reference.
- "refs/heads/main": {NewOID: setup.Commits.Third.OID},
- },
- },
- Commit{
- ExpectedError: ReferenceVerificationError{
- ReferenceName: "refs/heads/main",
- ExpectedOID: setup.Commits.First.OID,
- ActualOID: setup.ObjectHash.ZeroOID,
- },
- },
- },
- },
- {
- desc: "recording initial value of a reference stages no updates",
- steps: steps{
- StartManager{},
- Begin{
- RelativePath: relativePath,
- },
- RecordInitialReferenceValues{
- InitialValues: map[git.ReferenceName]git.ObjectID{
- "refs/heads/main": setup.Commits.First.OID,
- },
- },
- Commit{},
- },
- expectedState: StateAssertion{
- Database: DatabaseState{
- string(keyAppliedLSN(partitionID)): LSN(1).toProto(),
- },
- },
- },
- {
- desc: "update reference with non-existent initial value",
- steps: steps{
- StartManager{},
- Begin{
- RelativePath: relativePath,
- },
- RecordInitialReferenceValues{
- InitialValues: map[git.ReferenceName]git.ObjectID{
- "refs/heads/main": setup.ObjectHash.ZeroOID,
- },
- },
- UpdateReferences{
- // The old oid is ignored as the references old value was already recorded.
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {NewOID: setup.Commits.First.OID},
- },
- },
- Commit{},
- },
- expectedState: StateAssertion{
- Database: DatabaseState{
- string(keyAppliedLSN(partitionID)): LSN(1).toProto(),
- },
- Repositories: RepositoryStates{
- relativePath: {
- References: []git.Reference{{Name: "refs/heads/main", Target: setup.Commits.First.OID.String()}},
- },
- },
- },
- },
- {
- desc: "update reference with the zero oid initial value",
- steps: steps{
- StartManager{},
- Begin{
- TransactionID: 1,
- RelativePath: relativePath,
- },
- Commit{
- TransactionID: 1,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- },
- Begin{
- TransactionID: 2,
- RelativePath: relativePath,
- ExpectedSnapshotLSN: 1,
- },
- RecordInitialReferenceValues{
- TransactionID: 2,
- InitialValues: map[git.ReferenceName]git.ObjectID{
- "refs/heads/main": setup.ObjectHash.ZeroOID,
- },
- },
- UpdateReferences{
- TransactionID: 2,
- // The old oid is ignored as the references old value was already recorded.
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {NewOID: setup.Commits.Second.OID},
- },
- },
- Commit{
- TransactionID: 2,
- },
- },
- expectedState: StateAssertion{
- Database: DatabaseState{
- string(keyAppliedLSN(partitionID)): LSN(2).toProto(),
- },
- Repositories: RepositoryStates{
- relativePath: {
- References: []git.Reference{{Name: "refs/heads/main", Target: setup.Commits.Second.OID.String()}},
- },
- },
- },
- },
- {
- desc: "update reference with the correct initial value",
- steps: steps{
- StartManager{},
- Begin{
- TransactionID: 1,
- RelativePath: relativePath,
- },
- Commit{
- TransactionID: 1,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- },
- Begin{
- TransactionID: 2,
- RelativePath: relativePath,
- ExpectedSnapshotLSN: 1,
- },
- RecordInitialReferenceValues{
- TransactionID: 2,
- InitialValues: map[git.ReferenceName]git.ObjectID{
- "refs/heads/main": setup.Commits.First.OID,
- },
- },
- UpdateReferences{
- TransactionID: 2,
- // The old oid is ignored as the references old value was already recorded.
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {NewOID: setup.Commits.Second.OID},
- },
- },
- Commit{
- TransactionID: 2,
- },
- },
- expectedState: StateAssertion{
- Database: DatabaseState{
- string(keyAppliedLSN(partitionID)): LSN(2).toProto(),
- },
- Repositories: RepositoryStates{
- relativePath: {
- References: []git.Reference{{Name: "refs/heads/main", Target: setup.Commits.Second.OID.String()}},
- },
- },
- },
- },
- {
- desc: "update reference with the incorrect initial value",
- steps: steps{
- StartManager{},
- Begin{
- TransactionID: 1,
- RelativePath: relativePath,
- },
- Commit{
- TransactionID: 1,
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- },
- Begin{
- TransactionID: 2,
- RelativePath: relativePath,
- ExpectedSnapshotLSN: 1,
- },
- RecordInitialReferenceValues{
- TransactionID: 2,
- InitialValues: map[git.ReferenceName]git.ObjectID{
- "refs/heads/main": setup.Commits.Third.OID,
- },
- },
- UpdateReferences{
- TransactionID: 2,
- // The old oid is ignored as the references old value was already recorded.
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {NewOID: setup.Commits.Second.OID},
- },
- },
- Commit{
- TransactionID: 2,
- ExpectedError: ReferenceVerificationError{
- ReferenceName: "refs/heads/main",
- ExpectedOID: setup.Commits.Third.OID,
- ActualOID: setup.Commits.First.OID,
- },
- },
- },
- expectedState: StateAssertion{
- Database: DatabaseState{
- string(keyAppliedLSN(partitionID)): LSN(1).toProto(),
- },
- Repositories: RepositoryStates{
- relativePath: {
- References: []git.Reference{{Name: "refs/heads/main", Target: setup.Commits.First.OID.String()}},
- },
- },
- },
- },
- {
- desc: "initial value is set on the first update",
- steps: steps{
- StartManager{},
- Begin{
- RelativePath: relativePath,
- },
- UpdateReferences{
- ReferenceUpdates: ReferenceUpdates{
- "refs/heads/main": {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- },
- RecordInitialReferenceValues{
- InitialValues: map[git.ReferenceName]git.ObjectID{
- "refs/heads/main": setup.Commits.Third.OID,
- },
- },
- Commit{},
- },
- expectedState: StateAssertion{
- Database: DatabaseState{
- string(keyAppliedLSN(partitionID)): LSN(1).toProto(),
- },
- Repositories: RepositoryStates{
- relativePath: {
- References: []git.Reference{{Name: "refs/heads/main", Target: setup.Commits.First.OID.String()}},
- },
- },
- },
- },
- {
desc: "set custom hooks successfully",
steps: steps{
StartManager{},
@@ -6031,83 +4696,12 @@ func TestTransactionManager(t *testing.T) {
},
}
- type invalidReferenceTestCase struct {
- desc string
- referenceName git.ReferenceName
+ subTests := [][]transactionTestCase{
+ generateInvalidReferencesTests(t, setup),
+ generateModifyReferencesTests(t, setup),
}
-
- appendInvalidReferenceTestCase := func(tc invalidReferenceTestCase) {
- testCases = append(testCases, transactionTestCase{
- desc: fmt.Sprintf("invalid reference %s", tc.desc),
- steps: steps{
- StartManager{},
- Begin{
- RelativePath: relativePath,
- },
- Commit{
- ReferenceUpdates: ReferenceUpdates{
- tc.referenceName: {OldOID: setup.ObjectHash.ZeroOID, NewOID: setup.Commits.First.OID},
- },
- ExpectedError: InvalidReferenceFormatError{ReferenceName: tc.referenceName},
- },
- },
- })
- }
-
- // Generate test cases for the reference format rules according to https://git-scm.com/docs/git-check-ref-format.
- // This is to ensure the references are correctly validated prior to logging so they are guaranteed to apply later.
- // We also have two levels for catching invalid refs, the first is part of the transaction_manager, the second is
- // the errors thrown by git-update-ref(1) itself.
- for _, tc := range []invalidReferenceTestCase{
- // 1. They can include slash / for hierarchical (directory) grouping, but no slash-separated
- // component can begin with a dot . or end with the sequence .lock.
- {"starting with a period", ".refs/heads/main"},
- {"subcomponent starting with a period", "refs/heads/.main"},
- {"ending in .lock", "refs/heads/main.lock"},
- {"subcomponent ending in .lock", "refs/heads/main.lock/main"},
- // 2. They must contain at least one /. This enforces the presence of a category like heads/,
- // tags/ etc. but the actual names are not restricted.
- {"without a /", "one-level"},
- {"with refs without a /", "refs"},
- // We restrict this further by requiring a 'refs/' prefix to ensure loose references only end up
- // in the 'refs/' folder.
- {"without refs/ prefix ", "nonrefs/main"},
- // 3. They cannot have two consecutive dots .. anywhere.
- {"containing two consecutive dots", "refs/heads/../main"},
- // 4. They cannot have ASCII control characters ... (\177 DEL), space, tilde ~, caret ^, or colon : anywhere.
- //
- // Tests for control characters < \040 generated further down.
- {"containing DEL", "refs/heads/ma\177in"},
- {"containing space", "refs/heads/ma in"},
- {"containing ~", "refs/heads/ma~in"},
- {"containing ^", "refs/heads/ma^in"},
- {"containing :", "refs/heads/ma:in"},
- // 5. They cannot have question-mark ?, asterisk *, or open bracket [ anywhere.
- {"containing ?", "refs/heads/ma?in"},
- {"containing *", "refs/heads/ma*in"},
- {"containing [", "refs/heads/ma[in"},
- // 6. They cannot begin or end with a slash / or contain multiple consecutive slashes
- {"begins with /", "/refs/heads/main"},
- {"ends with /", "refs/heads/main/"},
- {"contains consecutive /", "refs/heads//main"},
- // 7. They cannot end with a dot.
- {"ending in a dot", "refs/heads/main."},
- // 8. They cannot contain a sequence @{.
- {"invalid reference contains @{", "refs/heads/m@{n"},
- // 9. They cannot be the single character @.
- {"is a single character @", "@"},
- // 10. They cannot contain a \.
- {`containing \`, `refs/heads\main`},
- } {
- appendInvalidReferenceTestCase(tc)
- }
-
- // Rule 4. They cannot have ASCII control characters i.e. bytes whose values are lower than \040,
- for i := byte(0); i < '\040'; i++ {
- appendInvalidReferenceTestCase(invalidReferenceTestCase{
- desc: fmt.Sprintf(`containing ASCII control character %d`, i),
- referenceName: git.ReferenceName(fmt.Sprintf("refs/heads/ma%sin", []byte{i})),
- })
+ for _, subCases := range subTests {
+ testCases = append(testCases, subCases...)
}
for _, tc := range testCases {