From 71b7eb14010be0c7799b4d5394798c89e379891b Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Sun, 27 Sep 2020 00:54:14 +0200 Subject: repo: implement local/global/any config everywhere --- repository/config.go | 64 ++++++++++ repository/config_test.go | 54 ++++++++ repository/git.go | 83 ++++--------- repository/git_cli.go | 56 +++++++++ repository/git_config.go | 18 +-- repository/gogit.go | 25 +++- repository/mock_repo.go | 11 +- repository/repo.go | 6 + repository/repo_testing.go | 299 +++++++++++++++++++++++---------------------- 9 files changed, 400 insertions(+), 216 deletions(-) create mode 100644 repository/config_test.go create mode 100644 repository/git_cli.go (limited to 'repository') diff --git a/repository/config.go b/repository/config.go index 70d51f11..4ea326b5 100644 --- a/repository/config.go +++ b/repository/config.go @@ -59,3 +59,67 @@ func ParseTimestamp(s string) (time.Time, error) { return time.Unix(int64(timestamp), 0), nil } + +// mergeConfig is a helper to easily support RepoConfig.AnyConfig() +// from two separate local and global Config +func mergeConfig(local ConfigRead, global ConfigRead) *mergedConfig { + return &mergedConfig{ + local: local, + global: global, + } +} + +var _ ConfigRead = &mergedConfig{} + +type mergedConfig struct { + local ConfigRead + global ConfigRead +} + +func (m *mergedConfig) ReadAll(keyPrefix string) (map[string]string, error) { + values, err := m.global.ReadAll(keyPrefix) + if err != nil { + return nil, err + } + locals, err := m.local.ReadAll(keyPrefix) + if err != nil { + return nil, err + } + for k, val := range locals { + values[k] = val + } + return values, nil +} + +func (m *mergedConfig) ReadBool(key string) (bool, error) { + v, err := m.local.ReadBool(key) + if err == nil { + return v, nil + } + if err != ErrNoConfigEntry && err != ErrMultipleConfigEntry { + return false, err + } + return m.global.ReadBool(key) +} + +func (m *mergedConfig) ReadString(key string) (string, error) { + val, err := m.local.ReadString(key) + if err == nil { + return val, nil + } + if err != ErrNoConfigEntry && err != ErrMultipleConfigEntry { + return "", err + } + return m.global.ReadString(key) +} + +func (m *mergedConfig) ReadTimestamp(key string) (time.Time, error) { + val, err := m.local.ReadTimestamp(key) + if err == nil { + return val, nil + } + if err != ErrNoConfigEntry && err != ErrMultipleConfigEntry { + return time.Time{}, err + } + return m.global.ReadTimestamp(key) +} diff --git a/repository/config_test.go b/repository/config_test.go new file mode 100644 index 00000000..2a763540 --- /dev/null +++ b/repository/config_test.go @@ -0,0 +1,54 @@ +package repository + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestMergedConfig(t *testing.T) { + local := NewMemConfig() + global := NewMemConfig() + merged := mergeConfig(local, global) + + require.NoError(t, global.StoreBool("bool", true)) + require.NoError(t, global.StoreString("string", "foo")) + require.NoError(t, global.StoreTimestamp("timestamp", time.Unix(1234, 0))) + + val1, err := merged.ReadBool("bool") + require.NoError(t, err) + require.Equal(t, val1, true) + + val2, err := merged.ReadString("string") + require.NoError(t, err) + require.Equal(t, val2, "foo") + + val3, err := merged.ReadTimestamp("timestamp") + require.NoError(t, err) + require.Equal(t, val3, time.Unix(1234, 0)) + + require.NoError(t, local.StoreBool("bool", false)) + require.NoError(t, local.StoreString("string", "bar")) + require.NoError(t, local.StoreTimestamp("timestamp", time.Unix(5678, 0))) + + val1, err = merged.ReadBool("bool") + require.NoError(t, err) + require.Equal(t, val1, false) + + val2, err = merged.ReadString("string") + require.NoError(t, err) + require.Equal(t, val2, "bar") + + val3, err = merged.ReadTimestamp("timestamp") + require.NoError(t, err) + require.Equal(t, val3, time.Unix(5678, 0)) + + all, err := merged.ReadAll("") + require.NoError(t, err) + require.Equal(t, all, map[string]string{ + "bool": "false", + "string": "bar", + "timestamp": "5678", + }) +} diff --git a/repository/git.go b/repository/git.go index 37b79556..dba2d29d 100644 --- a/repository/git.go +++ b/repository/git.go @@ -4,8 +4,6 @@ package repository import ( "bytes" "fmt" - "io" - "os/exec" "path" "strings" "sync" @@ -22,6 +20,7 @@ var _ TestedRepo = &GitRepo{} // GitRepo represents an instance of a (local) git repository. type GitRepo struct { + gitCli path string clocksMutex sync.Mutex @@ -30,62 +29,6 @@ type GitRepo struct { keyring Keyring } -// LocalConfig give access to the repository scoped configuration -func (repo *GitRepo) LocalConfig() Config { - return newGitConfig(repo, false) -} - -// GlobalConfig give access to the git global configuration -func (repo *GitRepo) GlobalConfig() Config { - return newGitConfig(repo, true) -} - -func (repo *GitRepo) Keyring() Keyring { - return repo.keyring -} - -// Run the given git command with the given I/O reader/writers, returning an error if it fails. -func (repo *GitRepo) runGitCommandWithIO(stdin io.Reader, stdout, stderr io.Writer, args ...string) error { - // make sure that the working directory for the command - // always exist, in particular when running "git init". - path := strings.TrimSuffix(repo.path, ".git") - - // fmt.Printf("[%s] Running git %s\n", path, strings.Join(args, " ")) - - cmd := exec.Command("git", args...) - cmd.Dir = path - cmd.Stdin = stdin - cmd.Stdout = stdout - cmd.Stderr = stderr - - return cmd.Run() -} - -// Run the given git command and return its stdout, or an error if the command fails. -func (repo *GitRepo) runGitCommandRaw(stdin io.Reader, args ...string) (string, string, error) { - var stdout bytes.Buffer - var stderr bytes.Buffer - err := repo.runGitCommandWithIO(stdin, &stdout, &stderr, args...) - return strings.TrimSpace(stdout.String()), strings.TrimSpace(stderr.String()), err -} - -// Run the given git command and return its stdout, or an error if the command fails. -func (repo *GitRepo) runGitCommandWithStdin(stdin io.Reader, args ...string) (string, error) { - stdout, stderr, err := repo.runGitCommandRaw(stdin, args...) - if err != nil { - if stderr == "" { - stderr = "Error running git command: " + strings.Join(args, " ") - } - err = fmt.Errorf(stderr) - } - return stdout, err -} - -// Run the given git command and return its stdout, or an error if the command fails. -func (repo *GitRepo) runGitCommand(args ...string) (string, error) { - return repo.runGitCommandWithStdin(nil, args...) -} - // NewGitRepo determines if the given working directory is inside of a git repository, // and returns the corresponding GitRepo instance if it is. func NewGitRepo(path string, clockLoaders []ClockLoader) (*GitRepo, error) { @@ -95,6 +38,7 @@ func NewGitRepo(path string, clockLoaders []ClockLoader) (*GitRepo, error) { } repo := &GitRepo{ + gitCli: gitCli{path: path}, path: path, clocks: make(map[string]lamport.Clock), keyring: k, @@ -112,6 +56,7 @@ func NewGitRepo(path string, clockLoaders []ClockLoader) (*GitRepo, error) { // Fix the path to be sure we are at the root repo.path = stdout + repo.gitCli.path = stdout for _, loader := range clockLoaders { allExist := true @@ -135,6 +80,7 @@ func NewGitRepo(path string, clockLoaders []ClockLoader) (*GitRepo, error) { // InitGitRepo create a new empty git repo at the given path func InitGitRepo(path string) (*GitRepo, error) { repo := &GitRepo{ + gitCli: gitCli{path: path}, path: path + "/.git", clocks: make(map[string]lamport.Clock), } @@ -150,6 +96,7 @@ func InitGitRepo(path string) (*GitRepo, error) { // InitBareGitRepo create a new --bare empty git repo at the given path func InitBareGitRepo(path string) (*GitRepo, error) { repo := &GitRepo{ + gitCli: gitCli{path: path}, path: path, clocks: make(map[string]lamport.Clock), } @@ -162,6 +109,26 @@ func InitBareGitRepo(path string) (*GitRepo, error) { return repo, nil } +// LocalConfig give access to the repository scoped configuration +func (repo *GitRepo) LocalConfig() Config { + return newGitConfig(repo.gitCli, false) +} + +// GlobalConfig give access to the global scoped configuration +func (repo *GitRepo) GlobalConfig() Config { + return newGitConfig(repo.gitCli, true) +} + +// AnyConfig give access to a merged local/global configuration +func (repo *GitRepo) AnyConfig() ConfigRead { + return mergeConfig(repo.LocalConfig(), repo.GlobalConfig()) +} + +// Keyring give access to a user-wide storage for secrets +func (repo *GitRepo) Keyring() Keyring { + return repo.keyring +} + // GetPath returns the path to the repo. func (repo *GitRepo) GetPath() string { return repo.path diff --git a/repository/git_cli.go b/repository/git_cli.go new file mode 100644 index 00000000..085b1cda --- /dev/null +++ b/repository/git_cli.go @@ -0,0 +1,56 @@ +package repository + +import ( + "bytes" + "fmt" + "io" + "os/exec" + "strings" +) + +// gitCli is a helper to launch CLI git commands +type gitCli struct { + path string +} + +// Run the given git command with the given I/O reader/writers, returning an error if it fails. +func (cli gitCli) runGitCommandWithIO(stdin io.Reader, stdout, stderr io.Writer, args ...string) error { + // make sure that the working directory for the command + // always exist, in particular when running "git init". + path := strings.TrimSuffix(cli.path, ".git") + + // fmt.Printf("[%s] Running git %s\n", path, strings.Join(args, " ")) + + cmd := exec.Command("git", args...) + cmd.Dir = path + cmd.Stdin = stdin + cmd.Stdout = stdout + cmd.Stderr = stderr + + return cmd.Run() +} + +// Run the given git command and return its stdout, or an error if the command fails. +func (cli gitCli) runGitCommandRaw(stdin io.Reader, args ...string) (string, string, error) { + var stdout bytes.Buffer + var stderr bytes.Buffer + err := cli.runGitCommandWithIO(stdin, &stdout, &stderr, args...) + return strings.TrimSpace(stdout.String()), strings.TrimSpace(stderr.String()), err +} + +// Run the given git command and return its stdout, or an error if the command fails. +func (cli gitCli) runGitCommandWithStdin(stdin io.Reader, args ...string) (string, error) { + stdout, stderr, err := cli.runGitCommandRaw(stdin, args...) + if err != nil { + if stderr == "" { + stderr = "Error running git command: " + strings.Join(args, " ") + } + err = fmt.Errorf(stderr) + } + return stdout, err +} + +// Run the given git command and return its stdout, or an error if the command fails. +func (cli gitCli) runGitCommand(args ...string) (string, error) { + return cli.runGitCommandWithStdin(nil, args...) +} diff --git a/repository/git_config.go b/repository/git_config.go index 987cf195..b46cc69b 100644 --- a/repository/git_config.go +++ b/repository/git_config.go @@ -14,24 +14,24 @@ import ( var _ Config = &gitConfig{} type gitConfig struct { - repo *GitRepo + cli gitCli localityFlag string } -func newGitConfig(repo *GitRepo, global bool) *gitConfig { +func newGitConfig(cli gitCli, global bool) *gitConfig { localityFlag := "--local" if global { localityFlag = "--global" } return &gitConfig{ - repo: repo, + cli: cli, localityFlag: localityFlag, } } // StoreString store a single key/value pair in the config of the repo func (gc *gitConfig) StoreString(key string, value string) error { - _, err := gc.repo.runGitCommand("config", gc.localityFlag, "--replace-all", key, value) + _, err := gc.cli.runGitCommand("config", gc.localityFlag, "--replace-all", key, value) return err } @@ -45,7 +45,7 @@ func (gc *gitConfig) StoreTimestamp(key string, value time.Time) error { // ReadAll read all key/value pair matching the key prefix func (gc *gitConfig) ReadAll(keyPrefix string) (map[string]string, error) { - stdout, err := gc.repo.runGitCommand("config", gc.localityFlag, "--includes", "--get-regexp", keyPrefix) + stdout, err := gc.cli.runGitCommand("config", gc.localityFlag, "--includes", "--get-regexp", keyPrefix) // / \ // / ! \ @@ -74,7 +74,7 @@ func (gc *gitConfig) ReadAll(keyPrefix string) (map[string]string, error) { } func (gc *gitConfig) ReadString(key string) (string, error) { - stdout, err := gc.repo.runGitCommand("config", gc.localityFlag, "--includes", "--get-all", key) + stdout, err := gc.cli.runGitCommand("config", gc.localityFlag, "--includes", "--get-all", key) // / \ // / ! \ @@ -116,12 +116,12 @@ func (gc *gitConfig) ReadTimestamp(key string) (time.Time, error) { } func (gc *gitConfig) rmSection(keyPrefix string) error { - _, err := gc.repo.runGitCommand("config", gc.localityFlag, "--remove-section", keyPrefix) + _, err := gc.cli.runGitCommand("config", gc.localityFlag, "--remove-section", keyPrefix) return err } func (gc *gitConfig) unsetAll(keyPrefix string) error { - _, err := gc.repo.runGitCommand("config", gc.localityFlag, "--unset-all", keyPrefix) + _, err := gc.cli.runGitCommand("config", gc.localityFlag, "--unset-all", keyPrefix) return err } @@ -180,7 +180,7 @@ func (gc *gitConfig) RemoveAll(keyPrefix string) error { } func (gc *gitConfig) gitVersion() (*semver.Version, error) { - versionOut, err := gc.repo.runGitCommand("version") + versionOut, err := gc.cli.runGitCommand("version") if err != nil { return nil, err } diff --git a/repository/gogit.go b/repository/gogit.go index b0a4672b..f248235c 100644 --- a/repository/gogit.go +++ b/repository/gogit.go @@ -158,14 +158,25 @@ func InitBareGoGitRepo(path string) (*GoGitRepo, error) { }, nil } +// LocalConfig give access to the repository scoped configuration func (repo *GoGitRepo) LocalConfig() Config { return newGoGitConfig(repo.r) } +// GlobalConfig give access to the global scoped configuration func (repo *GoGitRepo) GlobalConfig() Config { - panic("go-git doesn't support writing global config") + // TODO: replace that with go-git native implementation once it's supported + // see: https://github.com/go-git/go-git + // see: https://github.com/src-d/go-git/issues/760 + return newGitConfig(gitCli{repo.path}, true) } +// AnyConfig give access to a merged local/global configuration +func (repo *GoGitRepo) AnyConfig() ConfigRead { + return mergeConfig(repo.LocalConfig(), repo.GlobalConfig()) +} + +// Keyring give access to a user-wide storage for secrets func (repo *GoGitRepo) Keyring() Keyring { return repo.keyring } @@ -288,6 +299,7 @@ func (repo *GoGitRepo) ReadData(hash Hash) ([]byte, error) { return ioutil.ReadAll(r) } +// StoreTree will store a mapping key-->Hash as a Git tree func (repo *GoGitRepo) StoreTree(mapping []TreeEntry) (Hash, error) { var tree object.Tree @@ -319,6 +331,7 @@ func (repo *GoGitRepo) StoreTree(mapping []TreeEntry) (Hash, error) { return Hash(hash.String()), nil } +// ReadTree will return the list of entries in a Git tree func (repo *GoGitRepo) ReadTree(hash Hash) ([]TreeEntry, error) { obj, err := repo.r.TreeObject(plumbing.NewHash(hash.String())) if err != nil { @@ -342,10 +355,12 @@ func (repo *GoGitRepo) ReadTree(hash Hash) ([]TreeEntry, error) { return treeEntries, nil } +// StoreCommit will store a Git commit with the given Git tree func (repo *GoGitRepo) StoreCommit(treeHash Hash) (Hash, error) { return repo.StoreCommitWithParent(treeHash, "") } +// StoreCommit will store a Git commit with the given Git tree func (repo *GoGitRepo) StoreCommitWithParent(treeHash Hash, parent Hash) (Hash, error) { cfg, err := repo.r.Config() if err != nil { @@ -386,6 +401,7 @@ func (repo *GoGitRepo) StoreCommitWithParent(treeHash Hash, parent Hash) (Hash, return Hash(hash.String()), nil } +// GetTreeHash return the git tree hash referenced in a commit func (repo *GoGitRepo) GetTreeHash(commit Hash) (Hash, error) { obj, err := repo.r.CommitObject(plumbing.NewHash(commit.String())) if err != nil { @@ -395,6 +411,7 @@ func (repo *GoGitRepo) GetTreeHash(commit Hash) (Hash, error) { return Hash(obj.TreeHash.String()), nil } +// FindCommonAncestor will return the last common ancestor of two chain of commit func (repo *GoGitRepo) FindCommonAncestor(commit1 Hash, commit2 Hash) (Hash, error) { obj1, err := repo.r.CommitObject(plumbing.NewHash(commit1.String())) if err != nil { @@ -413,14 +430,17 @@ func (repo *GoGitRepo) FindCommonAncestor(commit1 Hash, commit2 Hash) (Hash, err return Hash(commits[0].Hash.String()), nil } +// UpdateRef will create or update a Git reference func (repo *GoGitRepo) UpdateRef(ref string, hash Hash) error { return repo.r.Storer.SetReference(plumbing.NewHashReference(plumbing.ReferenceName(ref), plumbing.NewHash(hash.String()))) } +// RemoveRef will remove a Git reference func (repo *GoGitRepo) RemoveRef(ref string) error { return repo.r.Storer.RemoveReference(plumbing.ReferenceName(ref)) } +// ListRefs will return a list of Git ref matching the given refspec func (repo *GoGitRepo) ListRefs(refPrefix string) ([]string, error) { refIter, err := repo.r.References() if err != nil { @@ -442,6 +462,7 @@ func (repo *GoGitRepo) ListRefs(refPrefix string) ([]string, error) { return refs, nil } +// RefExist will check if a reference exist in Git func (repo *GoGitRepo) RefExist(ref string) (bool, error) { _, err := repo.r.Reference(plumbing.ReferenceName(ref), false) if err == nil { @@ -452,6 +473,7 @@ func (repo *GoGitRepo) RefExist(ref string) (bool, error) { return false, err } +// CopyRef will create a new reference with the same value as another one func (repo *GoGitRepo) CopyRef(source string, dest string) error { r, err := repo.r.Reference(plumbing.ReferenceName(source), false) if err != nil { @@ -460,6 +482,7 @@ func (repo *GoGitRepo) CopyRef(source string, dest string) error { return repo.r.Storer.SetReference(plumbing.NewHashReference(plumbing.ReferenceName(dest), r.Hash())) } +// ListCommits will return the list of tree hashes of a ref, in chronological order func (repo *GoGitRepo) ListCommits(ref string) ([]Hash, error) { r, err := repo.r.Reference(plumbing.ReferenceName(ref), false) if err != nil { diff --git a/repository/mock_repo.go b/repository/mock_repo.go index 07425765..628939aa 100644 --- a/repository/mock_repo.go +++ b/repository/mock_repo.go @@ -35,20 +35,20 @@ func NewMockRepoForTest() *mockRepoForTest { var _ RepoConfig = &mockRepoConfig{} type mockRepoConfig struct { - config *MemConfig + localConfig *MemConfig globalConfig *MemConfig } func NewMockRepoConfig() *mockRepoConfig { return &mockRepoConfig{ - config: NewMemConfig(), + localConfig: NewMemConfig(), globalConfig: NewMemConfig(), } } // LocalConfig give access to the repository scoped configuration func (r *mockRepoConfig) LocalConfig() Config { - return r.config + return r.localConfig } // GlobalConfig give access to the git global configuration @@ -56,6 +56,11 @@ func (r *mockRepoConfig) GlobalConfig() Config { return r.globalConfig } +// AnyConfig give access to a merged local/global configuration +func (r *mockRepoConfig) AnyConfig() ConfigRead { + return mergeConfig(r.localConfig, r.globalConfig) +} + var _ RepoKeyring = &mockRepoKeyring{} type mockRepoKeyring struct { diff --git a/repository/repo.go b/repository/repo.go index 6349007b..2eb27e82 100644 --- a/repository/repo.go +++ b/repository/repo.go @@ -32,6 +32,12 @@ type ClockedRepo interface { type RepoConfig interface { // LocalConfig give access to the repository scoped configuration LocalConfig() Config + + // GlobalConfig give access to the global scoped configuration + GlobalConfig() Config + + // AnyConfig give access to a merged local/global configuration + AnyConfig() ConfigRead } // RepoKeyring give access to a user-wide storage for secrets diff --git a/repository/repo_testing.go b/repository/repo_testing.go index 53375a5b..cfa26631 100644 --- a/repository/repo_testing.go +++ b/repository/repo_testing.go @@ -53,160 +53,169 @@ func RepoTest(t *testing.T, creator RepoCreator, cleaner RepoCleaner) { true: "Bare", } { t.Run(name, func(t *testing.T) { - t.Run("Blob-Tree-Commit-Ref", func(t *testing.T) { - repo := creator(bare) - defer cleaner(repo) - - // Blob - - data := randomData() - - blobHash1, err := repo.StoreData(data) - require.NoError(t, err) - require.True(t, blobHash1.IsValid()) - - blob1Read, err := repo.ReadData(blobHash1) - require.NoError(t, err) - require.Equal(t, data, blob1Read) - - // Tree - - blobHash2, err := repo.StoreData(randomData()) - require.NoError(t, err) - blobHash3, err := repo.StoreData(randomData()) - require.NoError(t, err) - - tree1 := []TreeEntry{ - { - ObjectType: Blob, - Hash: blobHash1, - Name: "blob1", - }, - { - ObjectType: Blob, - Hash: blobHash2, - Name: "blob2", - }, - } - - treeHash1, err := repo.StoreTree(tree1) - require.NoError(t, err) - require.True(t, treeHash1.IsValid()) - - tree1Read, err := repo.ReadTree(treeHash1) - require.NoError(t, err) - require.ElementsMatch(t, tree1, tree1Read) - - tree2 := []TreeEntry{ - { - ObjectType: Tree, - Hash: treeHash1, - Name: "tree1", - }, - { - ObjectType: Blob, - Hash: blobHash3, - Name: "blob3", - }, - } - - treeHash2, err := repo.StoreTree(tree2) - require.NoError(t, err) - require.True(t, treeHash2.IsValid()) - - tree2Read, err := repo.ReadTree(treeHash2) - require.NoError(t, err) - require.ElementsMatch(t, tree2, tree2Read) - - // Commit - - commit1, err := repo.StoreCommit(treeHash1) - require.NoError(t, err) - require.True(t, commit1.IsValid()) - - treeHash1Read, err := repo.GetTreeHash(commit1) - require.NoError(t, err) - require.Equal(t, treeHash1, treeHash1Read) - - commit2, err := repo.StoreCommitWithParent(treeHash2, commit1) - require.NoError(t, err) - require.True(t, commit2.IsValid()) - - treeHash2Read, err := repo.GetTreeHash(commit2) - require.NoError(t, err) - require.Equal(t, treeHash2, treeHash2Read) - - // Ref - - exist1, err := repo.RefExist("refs/bugs/ref1") - require.NoError(t, err) - require.False(t, exist1) - - err = repo.UpdateRef("refs/bugs/ref1", commit2) - require.NoError(t, err) - - exist1, err = repo.RefExist("refs/bugs/ref1") - require.NoError(t, err) - require.True(t, exist1) - - ls, err := repo.ListRefs("refs/bugs") - require.NoError(t, err) - require.ElementsMatch(t, []string{"refs/bugs/ref1"}, ls) - - err = repo.CopyRef("refs/bugs/ref1", "refs/bugs/ref2") - require.NoError(t, err) - - ls, err = repo.ListRefs("refs/bugs") - require.NoError(t, err) - require.ElementsMatch(t, []string{"refs/bugs/ref1", "refs/bugs/ref2"}, ls) - - commits, err := repo.ListCommits("refs/bugs/ref2") - require.NoError(t, err) - require.ElementsMatch(t, []Hash{commit1, commit2}, commits) - - // Graph - - commit3, err := repo.StoreCommitWithParent(treeHash1, commit1) - require.NoError(t, err) - - ancestorHash, err := repo.FindCommonAncestor(commit2, commit3) - require.NoError(t, err) - require.Equal(t, commit1, ancestorHash) - - err = repo.RemoveRef("refs/bugs/ref1") - require.NoError(t, err) - }) + repo := creator(bare) + defer cleaner(repo) - t.Run("Local config", func(t *testing.T) { - repo := creator(bare) - defer cleaner(repo) + t.Run("Data", func(t *testing.T) { + RepoDataTest(t, repo) + }) - testConfig(t, repo.LocalConfig()) + t.Run("Config", func(t *testing.T) { + RepoConfigTest(t, repo) }) t.Run("Clocks", func(t *testing.T) { - repo := creator(bare) - defer cleaner(repo) - - clock, err := repo.GetOrCreateClock("foo") - require.NoError(t, err) - require.Equal(t, lamport.Time(1), clock.Time()) + RepoClockTest(t, repo) + }) + }) + } +} - time, err := clock.Increment() - require.NoError(t, err) - require.Equal(t, lamport.Time(1), time) - require.Equal(t, lamport.Time(2), clock.Time()) +// helper to test a RepoConfig +func RepoConfigTest(t *testing.T, repo RepoConfig) { + testConfig(t, repo.LocalConfig()) +} - clock2, err := repo.GetOrCreateClock("foo") - require.NoError(t, err) - require.Equal(t, lamport.Time(2), clock2.Time()) +// helper to test a RepoData +func RepoDataTest(t *testing.T, repo RepoData) { + // Blob + + data := randomData() + + blobHash1, err := repo.StoreData(data) + require.NoError(t, err) + require.True(t, blobHash1.IsValid()) + + blob1Read, err := repo.ReadData(blobHash1) + require.NoError(t, err) + require.Equal(t, data, blob1Read) + + // Tree + + blobHash2, err := repo.StoreData(randomData()) + require.NoError(t, err) + blobHash3, err := repo.StoreData(randomData()) + require.NoError(t, err) + + tree1 := []TreeEntry{ + { + ObjectType: Blob, + Hash: blobHash1, + Name: "blob1", + }, + { + ObjectType: Blob, + Hash: blobHash2, + Name: "blob2", + }, + } - clock3, err := repo.GetOrCreateClock("bar") - require.NoError(t, err) - require.Equal(t, lamport.Time(1), clock3.Time()) - }) - }) + treeHash1, err := repo.StoreTree(tree1) + require.NoError(t, err) + require.True(t, treeHash1.IsValid()) + + tree1Read, err := repo.ReadTree(treeHash1) + require.NoError(t, err) + require.ElementsMatch(t, tree1, tree1Read) + + tree2 := []TreeEntry{ + { + ObjectType: Tree, + Hash: treeHash1, + Name: "tree1", + }, + { + ObjectType: Blob, + Hash: blobHash3, + Name: "blob3", + }, } + + treeHash2, err := repo.StoreTree(tree2) + require.NoError(t, err) + require.True(t, treeHash2.IsValid()) + + tree2Read, err := repo.ReadTree(treeHash2) + require.NoError(t, err) + require.ElementsMatch(t, tree2, tree2Read) + + // Commit + + commit1, err := repo.StoreCommit(treeHash1) + require.NoError(t, err) + require.True(t, commit1.IsValid()) + + treeHash1Read, err := repo.GetTreeHash(commit1) + require.NoError(t, err) + require.Equal(t, treeHash1, treeHash1Read) + + commit2, err := repo.StoreCommitWithParent(treeHash2, commit1) + require.NoError(t, err) + require.True(t, commit2.IsValid()) + + treeHash2Read, err := repo.GetTreeHash(commit2) + require.NoError(t, err) + require.Equal(t, treeHash2, treeHash2Read) + + // Ref + + exist1, err := repo.RefExist("refs/bugs/ref1") + require.NoError(t, err) + require.False(t, exist1) + + err = repo.UpdateRef("refs/bugs/ref1", commit2) + require.NoError(t, err) + + exist1, err = repo.RefExist("refs/bugs/ref1") + require.NoError(t, err) + require.True(t, exist1) + + ls, err := repo.ListRefs("refs/bugs") + require.NoError(t, err) + require.ElementsMatch(t, []string{"refs/bugs/ref1"}, ls) + + err = repo.CopyRef("refs/bugs/ref1", "refs/bugs/ref2") + require.NoError(t, err) + + ls, err = repo.ListRefs("refs/bugs") + require.NoError(t, err) + require.ElementsMatch(t, []string{"refs/bugs/ref1", "refs/bugs/ref2"}, ls) + + commits, err := repo.ListCommits("refs/bugs/ref2") + require.NoError(t, err) + require.ElementsMatch(t, []Hash{commit1, commit2}, commits) + + // Graph + + commit3, err := repo.StoreCommitWithParent(treeHash1, commit1) + require.NoError(t, err) + + ancestorHash, err := repo.FindCommonAncestor(commit2, commit3) + require.NoError(t, err) + require.Equal(t, commit1, ancestorHash) + + err = repo.RemoveRef("refs/bugs/ref1") + require.NoError(t, err) +} + +// helper to test a RepoClock +func RepoClockTest(t *testing.T, repo RepoClock) { + clock, err := repo.GetOrCreateClock("foo") + require.NoError(t, err) + require.Equal(t, lamport.Time(1), clock.Time()) + + time, err := clock.Increment() + require.NoError(t, err) + require.Equal(t, lamport.Time(1), time) + require.Equal(t, lamport.Time(2), clock.Time()) + + clock2, err := repo.GetOrCreateClock("foo") + require.NoError(t, err) + require.Equal(t, lamport.Time(2), clock2.Time()) + + clock3, err := repo.GetOrCreateClock("bar") + require.NoError(t, err) + require.Equal(t, lamport.Time(1), clock3.Time()) } func randomData() []byte { -- cgit