package main import ( "fmt" "os" "os/exec" "path/filepath" "reflect" "testing" "github.com/x-motemen/ghq/cmdutil" ) var ( remoteDummyURL = mustParseURL("https://example.com/git/repo") dummySvnInfo = []byte(`Path: trunk URL: https://svn.apache.org/repos/asf/subversion/trunk Relative URL: ^/subversion/trunk Repository Root: https://svn.apache.org/repos/asf Repository UUID: 13f79535-47bb-0310-9956-ffa450edef68 Revision: 1872085 Node Kind: directory Last Changed Author: julianfoad Last Changed Rev: 1872031 Last Changed Date: 2019-08-16 15:16:45 +0900 (Fri, 16 Aug 2019) `) ) func TestVCSBackend(t *testing.T) { tempDir := newTempDir(t) localDir := filepath.Join(tempDir, "repo") var _commands []*exec.Cmd lastCommand := func() *exec.Cmd { return _commands[len(_commands)-1] } defer func(orig func(cmd *exec.Cmd) error) { cmdutil.CommandRunner = orig }(cmdutil.CommandRunner) cmdutil.CommandRunner = func(cmd *exec.Cmd) error { _commands = append(_commands, cmd) if reflect.DeepEqual(cmd.Args, []string{"svn", "info", "https://example.com/git/repo/trunk"}) { return fmt.Errorf("[test] failed to svn info") } return nil } testCases := []struct { name string f func() error expect []string dir string }{{ name: "[git] clone", f: func() error { return GitBackend.Clone(&vcsGetOption{ url: remoteDummyURL, dir: localDir, }) }, expect: []string{"git", "clone", remoteDummyURL.String(), localDir}, }, { name: "[git] shallow clone", f: func() error { return GitBackend.Clone(&vcsGetOption{ url: remoteDummyURL, dir: localDir, shallow: true, silent: true, }) }, expect: []string{"git", "clone", "--depth", "1", remoteDummyURL.String(), localDir}, }, { name: "[git] clone specific branch", f: func() error { return GitBackend.Clone(&vcsGetOption{ url: remoteDummyURL, dir: localDir, branch: "hello", }) }, expect: []string{"git", "clone", "--branch", "hello", "--single-branch", remoteDummyURL.String(), localDir}, }, { name: "[git] update", f: func() error { return GitBackend.Update(&vcsGetOption{ dir: localDir, }) }, expect: []string{"git", "pull", "--ff-only"}, dir: localDir, }, { name: "[git] fetch", f: func() error { defer func(orig func(cmd *exec.Cmd) error) { cmdutil.CommandRunner = orig }(cmdutil.CommandRunner) cmdutil.CommandRunner = func(cmd *exec.Cmd) error { _commands = append(_commands, cmd) if reflect.DeepEqual(cmd.Args, []string{"git", "rev-parse", "@{upstream}"}) { return fmt.Errorf("[test] failed to git rev-parse @{upstream}") } return nil } return GitBackend.Update(&vcsGetOption{ dir: localDir, }) }, expect: []string{"git", "fetch"}, dir: localDir, }, { name: "[git] recursive", f: func() error { return GitBackend.Clone(&vcsGetOption{ url: remoteDummyURL, dir: localDir, recursive: true, }) }, expect: []string{"git", "clone", "--recursive", remoteDummyURL.String(), localDir}, }, { name: "[git] update recursive", f: func() error { return GitBackend.Update(&vcsGetOption{ dir: localDir, recursive: true, }) }, expect: []string{"git", "submodule", "update", "--init", "--recursive"}, dir: localDir, }, { name: "[git] bare clone", f: func() error { return GitBackend.Clone(&vcsGetOption{ url: remoteDummyURL, dir: localDir, bare: true, silent: true, }) }, expect: []string{"git", "clone", "--bare", remoteDummyURL.String(), localDir}, }, { name: "[git] (partial) blobless clone", f: func() error { return GitBackend.Clone(&vcsGetOption{ url: remoteDummyURL, dir: localDir, partial: "blobless", }) }, expect: []string{"git", "clone", "--filter=blob:none", remoteDummyURL.String(), localDir}, }, { name: "[git] (partial) treeless clone", f: func() error { return GitBackend.Clone(&vcsGetOption{ url: remoteDummyURL, dir: localDir, partial: "treeless", }) }, expect: []string{"git", "clone", "--filter=tree:0", remoteDummyURL.String(), localDir}, }, { name: "[git] switch git-svn on update", f: func() error { err := os.MkdirAll(filepath.Join(localDir, ".git", "svn"), 0755) if err != nil { return err } defer os.RemoveAll(filepath.Join(localDir, ".git")) return GitBackend.Update(&vcsGetOption{ dir: localDir, }) }, expect: []string{"git", "svn", "rebase"}, dir: localDir, }, { name: "[svn] checkout", f: func() error { return SubversionBackend.Clone(&vcsGetOption{ url: remoteDummyURL, dir: localDir, }) }, expect: []string{"svn", "checkout", remoteDummyURL.String(), localDir}, }, { name: "[svn] checkout shallow", f: func() error { return SubversionBackend.Clone(&vcsGetOption{ url: remoteDummyURL, dir: localDir, shallow: true, }) }, expect: []string{"svn", "checkout", "--depth", "immediates", remoteDummyURL.String(), localDir}, }, { name: "[svn] checkout specific branch", f: func() error { return SubversionBackend.Clone(&vcsGetOption{ url: remoteDummyURL, dir: localDir, branch: "hello", }) }, expect: []string{"svn", "checkout", remoteDummyURL.String() + "/branches/hello", localDir}, }, { name: "[svn] checkout with filling trunk", f: func() error { defer func(orig func(cmd *exec.Cmd) error) { cmdutil.CommandRunner = orig }(cmdutil.CommandRunner) cmdutil.CommandRunner = func(cmd *exec.Cmd) error { _commands = append(_commands, cmd) if reflect.DeepEqual(cmd.Args, []string{"svn", "info", "https://example.com/git/repo/trunk"}) { cmd.Stdout.Write(dummySvnInfo) } return nil } return SubversionBackend.Clone(&vcsGetOption{ url: remoteDummyURL, dir: localDir, }) }, expect: []string{"svn", "checkout", remoteDummyURL.String() + "/trunk", localDir}, }, { name: "[svn] update", f: func() error { return SubversionBackend.Update(&vcsGetOption{ dir: localDir, silent: true, }) }, expect: []string{"svn", "update"}, dir: localDir, }, { name: "[git-svn] clone", f: func() error { return GitsvnBackend.Clone(&vcsGetOption{ url: remoteDummyURL, dir: localDir, }) }, expect: []string{"git", "svn", "clone", remoteDummyURL.String(), localDir}, }, { name: "[git-svn] update", f: func() error { return GitsvnBackend.Update(&vcsGetOption{ dir: localDir, }) }, expect: []string{"git", "svn", "rebase"}, dir: localDir, }, { name: "[git-svn] clone shallow", f: func() error { defer func(orig func(cmd *exec.Cmd) error) { cmdutil.CommandRunner = orig }(cmdutil.CommandRunner) cmdutil.CommandRunner = func(cmd *exec.Cmd) error { _commands = append(_commands, cmd) if reflect.DeepEqual(cmd.Args, []string{"svn", "info", "https://example.com/git/repo/trunk"}) { cmd.Stdout.Write(dummySvnInfo) } return nil } return GitsvnBackend.Clone(&vcsGetOption{ url: remoteDummyURL, dir: localDir, shallow: true, }) }, expect: []string{"git", "svn", "clone", "-s", "-r1872031:HEAD", remoteDummyURL.String(), localDir}, }, { name: "[git-svn] clone specific branch", f: func() error { return GitsvnBackend.Clone(&vcsGetOption{ url: remoteDummyURL, dir: localDir, branch: "hello", }) }, expect: []string{"git", "svn", "clone", remoteDummyURL.String() + "/branches/hello", localDir}, }, { name: "[git-svn] clone specific branch from tagged URL with shallow", f: func() error { defer func(orig func(cmd *exec.Cmd) error) { cmdutil.CommandRunner = orig }(cmdutil.CommandRunner) cmdutil.CommandRunner = func(cmd *exec.Cmd) error { _commands = append(_commands, cmd) if reflect.DeepEqual( cmd.Args, []string{"svn", "info", "https://example.com/git/repo/branches/develop"}, ) { cmd.Stdout.Write(dummySvnInfo) } return nil } copied := *remoteDummyURL copied.Path += "/tags/v9.9.9" return GitsvnBackend.Clone(&vcsGetOption{ url: &copied, dir: localDir, branch: "develop", shallow: true, }) }, expect: []string{ "git", "svn", "clone", "-r1872031:HEAD", remoteDummyURL.String() + "/branches/develop", localDir}, }, { name: "[hg] clone", f: func() error { return MercurialBackend.Clone(&vcsGetOption{ url: remoteDummyURL, dir: localDir, }) }, expect: []string{"hg", "clone", remoteDummyURL.String(), localDir}, }, { name: "[hg] update", f: func() error { return MercurialBackend.Update(&vcsGetOption{ dir: localDir, }) }, expect: []string{"hg", "pull", "--update"}, dir: localDir, }, { name: "[hg] clone shallow", f: func() error { return MercurialBackend.Clone(&vcsGetOption{ url: remoteDummyURL, dir: localDir, shallow: true, }) }, expect: []string{"hg", "clone", remoteDummyURL.String(), localDir}, }, { name: "[hg] clone specific branch", f: func() error { return MercurialBackend.Clone(&vcsGetOption{ url: remoteDummyURL, dir: localDir, branch: "hello", }) }, expect: []string{"hg", "clone", "--branch", "hello", remoteDummyURL.String(), localDir}, }, { name: "[darcs] clone", f: func() error { return DarcsBackend.Clone(&vcsGetOption{ url: remoteDummyURL, dir: localDir, }) }, expect: []string{"darcs", "get", remoteDummyURL.String(), localDir}, }, { name: "[darcs] clone shallow", f: func() error { return DarcsBackend.Clone(&vcsGetOption{ url: remoteDummyURL, dir: localDir, shallow: true, }) }, expect: []string{"darcs", "get", "--lazy", remoteDummyURL.String(), localDir}, }, { name: "[darcs] update", f: func() error { return DarcsBackend.Update(&vcsGetOption{ dir: localDir, }) }, expect: []string{"darcs", "pull"}, dir: localDir, }, { name: "[pijul] clone", f: func() error { return PijulBackend.Clone(&vcsGetOption{ url: remoteDummyURL, dir: localDir, }) }, expect: []string{"pijul", "clone", remoteDummyURL.String(), localDir}, }, { name: "[pijul] update", f: func() error { return PijulBackend.Update(&vcsGetOption{ dir: localDir, }) }, expect: []string{"pijul", "pull"}, dir: localDir, }, { name: "[bzr] clone", f: func() error { return BazaarBackend.Clone(&vcsGetOption{ url: remoteDummyURL, dir: localDir, }) }, expect: []string{"bzr", "branch", remoteDummyURL.String(), localDir}, }, { name: "[bzr] update", f: func() error { return BazaarBackend.Update(&vcsGetOption{ dir: localDir, }) }, expect: []string{"bzr", "pull", "--overwrite"}, dir: localDir, }, { name: "[bzr] clone shallow", f: func() error { return BazaarBackend.Clone(&vcsGetOption{ url: remoteDummyURL, dir: localDir, shallow: true, }) }, expect: []string{"bzr", "branch", remoteDummyURL.String(), localDir}, }, { name: "[fossil] clone", f: func() error { return FossilBackend.Clone(&vcsGetOption{ url: remoteDummyURL, dir: localDir, }) }, expect: []string{"fossil", "open", fossilRepoName}, dir: localDir, }, { name: "[fossil] update", f: func() error { return FossilBackend.Update(&vcsGetOption{ dir: localDir, }) }, expect: []string{"fossil", "update"}, dir: localDir, }} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { if err := tc.f(); err != nil { t.Errorf("error should be nil, but: %s", err) } c := lastCommand() if !reflect.DeepEqual(c.Args, tc.expect) { t.Errorf("\ngot: %+v\nexpect: %+v", c.Args, tc.expect) } if c.Dir != tc.dir { t.Errorf("got: %s, expect: %s", c.Dir, tc.dir) } }) } } func TestCvsDummyBackend(t *testing.T) { tempDir := newTempDir(t) localDir := filepath.Join(tempDir, "repo") if err := cvsDummyBackend.Clone(&vcsGetOption{ url: remoteDummyURL, dir: localDir, }); err == nil { t.Error("error should be occurred, but nil") } if err := cvsDummyBackend.Clone(&vcsGetOption{ url: remoteDummyURL, dir: localDir, shallow: true, }); err == nil { t.Error("error should be occurred, but nil") } if err := cvsDummyBackend.Update(&vcsGetOption{ dir: localDir, }); err == nil { t.Error("error should be occurred, but nil") } } func TestBranchOptionIgnoredErrors(t *testing.T) { tempDir := newTempDir(t) localDir := filepath.Join(tempDir, "repo") if err := DarcsBackend.Clone(&vcsGetOption{ url: remoteDummyURL, dir: localDir, branch: "hello", }); err == nil { t.Error("error should be occurred, but nil") } if err := FossilBackend.Clone(&vcsGetOption{ url: remoteDummyURL, dir: localDir, branch: "hello", }); err == nil { t.Error("error should be occurred, but nil") } if err := BazaarBackend.Clone(&vcsGetOption{ url: remoteDummyURL, dir: localDir, branch: "hello", }); err == nil { t.Error("error should be occurred, but nil") } }