mirror of
https://github.com/grdl/git-get.git
synced 2026-02-05 07:53:48 +00:00
Remove gogit and major refactoring (#2)
* Fix typo in readme * Reimplement all git methods without go-git * Rename repo pkg to git, add gitconfig methods * Improve tests for configuration reading * Rename package file to io and move RepoFinder there * Refactor printers - Remove smart printer - Decouple printers from git repos with interfaces - Update printer functions - Remove unnecessary flags - Add better remote URL detection * Update readme and go.mod * Add author to git commit in tests Otherwise tests will fail in CI. * Install git before running tests and don't use cgo * Add better error message, revert installing git * Ensure commit message is in quotes * Set up git config before running tests
This commit is contained in:
committed by
GitHub
parent
2ef739ea49
commit
8c132cdafa
20
pkg/git/config.go
Normal file
20
pkg/git/config.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package git
|
||||
|
||||
import "os/exec"
|
||||
|
||||
// ConfigGlobal represents a global gitconfig file.
|
||||
type ConfigGlobal struct{}
|
||||
|
||||
// Get reads a value from global gitconfig file. Returns empty string when key is missing.
|
||||
func (c *ConfigGlobal) Get(key string) string {
|
||||
cmd := exec.Command("git", "config", "--global", key)
|
||||
out, err := cmd.Output()
|
||||
|
||||
// In case of error return an empty string, the missing value will fall back to a default.
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
lines := lines(out)
|
||||
return lines[0]
|
||||
}
|
||||
93
pkg/git/config_test.go
Normal file
93
pkg/git/config_test.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package git
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// cfgStub represents a gitconfig file but instead of using a global one, it creates a temporary git repo and uses its local gitconfig.
|
||||
type cfgStub struct {
|
||||
repo *testRepo
|
||||
}
|
||||
|
||||
func newCfgStub(t *testing.T) *cfgStub {
|
||||
r := testRepoEmpty(t)
|
||||
return &cfgStub{
|
||||
repo: r,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cfgStub) Get(key string) string {
|
||||
cmd := gitCmd(c.repo.path, "config", "--local", key)
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
lines := lines(out)
|
||||
return lines[0]
|
||||
}
|
||||
|
||||
func TestGitConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
configMaker func(t *testing.T) *cfgStub
|
||||
key string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
configMaker: makeConfigEmpty,
|
||||
key: "gitget.host",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "valid",
|
||||
configMaker: makeConfigValid,
|
||||
key: "gitget.host",
|
||||
want: "github.com",
|
||||
}, {
|
||||
name: "only section name",
|
||||
configMaker: makeConfigValid,
|
||||
key: "gitget",
|
||||
want: "",
|
||||
}, {
|
||||
name: "missing key",
|
||||
configMaker: makeConfigValid,
|
||||
key: "gitget.missingkey",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
cfg := test.configMaker(t)
|
||||
|
||||
got := cfg.Get(test.key)
|
||||
|
||||
if got != test.want {
|
||||
t.Errorf("expected %q; got %q", test.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func makeConfigEmpty(t *testing.T) *cfgStub {
|
||||
c := newCfgStub(t)
|
||||
c.repo.writeFile(".git/config", "")
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func makeConfigValid(t *testing.T) *cfgStub {
|
||||
c := newCfgStub(t)
|
||||
|
||||
gitconfig := `
|
||||
[user]
|
||||
name = grdl
|
||||
[gitget]
|
||||
host = github.com
|
||||
`
|
||||
c.repo.writeFile(".git/config", gitconfig)
|
||||
|
||||
return c
|
||||
}
|
||||
245
pkg/git/repo.go
Normal file
245
pkg/git/repo.go
Normal file
@@ -0,0 +1,245 @@
|
||||
package git
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git-get/pkg/io"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
dotgit = ".git"
|
||||
untracked = "??" // Untracked files are marked as "??" in git status output.
|
||||
master = "master"
|
||||
head = "HEAD"
|
||||
)
|
||||
|
||||
// Repo represents a git repository on disk.
|
||||
type Repo struct {
|
||||
path string
|
||||
}
|
||||
|
||||
// CloneOpts specify detail about repository to clone.
|
||||
type CloneOpts struct {
|
||||
URL *url.URL
|
||||
Path string // TODO: should Path be a part of clone opts?
|
||||
Branch string
|
||||
Quiet bool
|
||||
IgnoreExisting bool
|
||||
}
|
||||
|
||||
// Open checks if given path can be accessed and returns a Repo instance pointing to it.
|
||||
func Open(path string) (*Repo, error) {
|
||||
_, err := io.Exists(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Repo{
|
||||
path: path,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Clone clones repository specified with CloneOpts.
|
||||
func Clone(opts *CloneOpts) (*Repo, error) {
|
||||
// TODO: not sure if this check should be here
|
||||
if opts.IgnoreExisting {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
args := []string{"clone", "--progress", "-v"}
|
||||
|
||||
if opts.Branch != "" {
|
||||
args = append(args, "--branch", opts.Branch, "--single-branch")
|
||||
}
|
||||
|
||||
if opts.Quiet {
|
||||
args = append(args, "--quiet")
|
||||
}
|
||||
|
||||
args = append(args, opts.URL.String())
|
||||
args = append(args, opts.Path)
|
||||
|
||||
cmd := exec.Command("git", args...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "git clone failed")
|
||||
}
|
||||
|
||||
repo, err := Open(opts.Path)
|
||||
return repo, err
|
||||
}
|
||||
|
||||
// Fetch preforms a git fetch on all remotes
|
||||
func (r *Repo) Fetch() error {
|
||||
cmd := gitCmd(r.path, "fetch", "--all", "--quiet")
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// Uncommitted returns the number of uncommitted files in the repository.
|
||||
// Only tracked files are not counted.
|
||||
func (r *Repo) Uncommitted() (int, error) {
|
||||
cmd := gitCmd(r.path, "status", "--ignore-submodules", "--porcelain")
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return 0, cmdError(cmd, err)
|
||||
}
|
||||
|
||||
lines := lines(out)
|
||||
count := 0
|
||||
for _, line := range lines {
|
||||
// Don't count lines with untracked files and empty lines.
|
||||
if !strings.HasPrefix(line, untracked) && strings.TrimSpace(line) != "" {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// Untracked returns the number of untracked files in the repository.
|
||||
func (r *Repo) Untracked() (int, error) {
|
||||
cmd := gitCmd(r.path, "status", "--ignore-submodules", "--untracked-files=all", "--porcelain")
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return 0, cmdError(cmd, err)
|
||||
}
|
||||
|
||||
lines := lines(out)
|
||||
count := 0
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, untracked) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// CurrentBranch returns the short name currently checked-out branch for the repository.
|
||||
// If repo is in a detached head state, it will return "HEAD".
|
||||
func (r *Repo) CurrentBranch() (string, error) {
|
||||
cmd := gitCmd(r.path, "rev-parse", "--symbolic-full-name", "--abbrev-ref", "HEAD")
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", cmdError(cmd, err)
|
||||
}
|
||||
|
||||
lines := lines(out)
|
||||
return lines[0], nil
|
||||
}
|
||||
|
||||
// Branches returns a list of local branches in the repository.
|
||||
func (r *Repo) Branches() ([]string, error) {
|
||||
cmd := gitCmd(r.path, "branch", "--format=%(refname:short)")
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, cmdError(cmd, err)
|
||||
}
|
||||
|
||||
lines := lines(out)
|
||||
|
||||
// TODO: Is detached head shown always on the first line? Maybe we don't need to iterate over everything.
|
||||
// Remove the line containing detached head.
|
||||
for i, line := range lines {
|
||||
if strings.Contains(line, "HEAD detached") {
|
||||
lines = append(lines[:i], lines[i+1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
return lines, nil
|
||||
}
|
||||
|
||||
// Upstream returns the name of an upstream branch if a given branch is tracking one.
|
||||
// Otherwise it returns an empty string.
|
||||
func (r *Repo) Upstream(branch string) (string, error) {
|
||||
cmd := gitCmd(r.path, "rev-parse", "--abbrev-ref", "--symbolic-full-name", fmt.Sprintf("%s@{upstream}", branch))
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
|
||||
// TODO: no upstream will also throw an error.
|
||||
return "", nil //cmdError(cmd, err)
|
||||
}
|
||||
|
||||
lines := lines(out)
|
||||
return lines[0], nil
|
||||
}
|
||||
|
||||
// AheadBehind returns the number of commits a given branch is ahead and/or behind the upstream.
|
||||
func (r *Repo) AheadBehind(branch string, upstream string) (int, int, error) {
|
||||
cmd := gitCmd(r.path, "rev-list", "--left-right", "--count", fmt.Sprintf("%s...%s", branch, upstream))
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return 0, 0, cmdError(cmd, err)
|
||||
}
|
||||
|
||||
lines := lines(out)
|
||||
|
||||
// rev-list --left-right --count output is separated by a tab
|
||||
lr := strings.Split(lines[0], "\t")
|
||||
|
||||
ahead, err := strconv.Atoi(lr[0])
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
behind, err := strconv.Atoi(lr[1])
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
return ahead, behind, nil
|
||||
}
|
||||
|
||||
// Remote returns URL of remote repository.
|
||||
func (r *Repo) Remote() (string, error) {
|
||||
// https://stackoverflow.com/a/16880000/1085632
|
||||
cmd := gitCmd(r.path, "ls-remote", "--get-url")
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", cmdError(cmd, err)
|
||||
}
|
||||
|
||||
lines := lines(out)
|
||||
|
||||
// TODO: needs testing. What happens when there are more than 1 remotes?
|
||||
return lines[0], nil
|
||||
}
|
||||
|
||||
// Path returns path to the repository.
|
||||
func (r *Repo) Path() string {
|
||||
return r.path
|
||||
}
|
||||
|
||||
func gitCmd(repoPath string, args ...string) *exec.Cmd {
|
||||
args = append([]string{"--work-tree", repoPath, "--git-dir", path.Join(repoPath, dotgit)}, args...)
|
||||
return exec.Command("git", args...)
|
||||
}
|
||||
|
||||
func lines(output []byte) []string {
|
||||
lines := strings.TrimSuffix(string(output), "\n")
|
||||
return strings.Split(lines, "\n")
|
||||
}
|
||||
|
||||
func cmdError(cmd *exec.Cmd, err error) error {
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "%s failed: %+v", strings.Join(cmd.Args, " "), err) // Show which git command failed (skip "--work-tree and --gitdir flags")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
245
pkg/git/repo_helpers_test.go
Normal file
245
pkg/git/repo_helpers_test.go
Normal file
@@ -0,0 +1,245 @@
|
||||
package git
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git-get/pkg/io"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// testRepo embeds testing.T into a Repo instance to simplify creation of test repos.
|
||||
// Any error thrown while creating a test repo will cause a t.Fatal call.
|
||||
type testRepo struct {
|
||||
*Repo
|
||||
*testing.T
|
||||
}
|
||||
|
||||
// TODO: this should be a method of a tempDir, not a repo
|
||||
// Automatically remove test repo when the test is over
|
||||
func (r *testRepo) cleanup() {
|
||||
err := os.RemoveAll(r.path)
|
||||
if err != nil {
|
||||
r.T.Errorf("failed removing test repo directory %s", r.path)
|
||||
}
|
||||
}
|
||||
|
||||
func testRepoEmpty(t *testing.T) *testRepo {
|
||||
dir, err := io.TempDir()
|
||||
checkFatal(t, err)
|
||||
|
||||
r, err := Open(dir)
|
||||
checkFatal(t, err)
|
||||
|
||||
tr := &testRepo{
|
||||
Repo: r,
|
||||
T: t,
|
||||
}
|
||||
|
||||
t.Cleanup(tr.cleanup)
|
||||
|
||||
tr.init()
|
||||
return tr
|
||||
}
|
||||
|
||||
func testRepoWithUntracked(t *testing.T) *testRepo {
|
||||
r := testRepoEmpty(t)
|
||||
r.writeFile("README.md", "I'm a readme file")
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func testRepoWithStaged(t *testing.T) *testRepo {
|
||||
r := testRepoEmpty(t)
|
||||
r.writeFile("README.md", "I'm a readme file")
|
||||
r.stageFile("README.md")
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func testRepoWithCommit(t *testing.T) *testRepo {
|
||||
r := testRepoEmpty(t)
|
||||
r.writeFile("README.md", "I'm a readme file")
|
||||
r.stageFile("README.md")
|
||||
r.commit("Initial commit")
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func testRepoWithUncommittedAndUntracked(t *testing.T) *testRepo {
|
||||
r := testRepoEmpty(t)
|
||||
r.writeFile("README.md", "I'm a readme file")
|
||||
r.stageFile("README.md")
|
||||
r.commit("Initial commit")
|
||||
r.writeFile("README.md", "These changes won't be committed")
|
||||
r.writeFile("untracked.txt", "I'm untracked")
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func testRepoWithBranch(t *testing.T) *testRepo {
|
||||
r := testRepoWithCommit(t)
|
||||
r.branch("feature/branch")
|
||||
r.checkout("feature/branch")
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func testRepoWithTag(t *testing.T) *testRepo {
|
||||
r := testRepoWithCommit(t)
|
||||
r.tag("v0.0.1")
|
||||
r.checkout("v0.0.1")
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func testRepoWithBranchWithUpstream(t *testing.T) *testRepo {
|
||||
origin := testRepoWithCommit(t)
|
||||
origin.branch("feature/branch")
|
||||
|
||||
r := origin.clone()
|
||||
r.checkout("feature/branch")
|
||||
return r
|
||||
}
|
||||
|
||||
func testRepoWithBranchWithoutUpstream(t *testing.T) *testRepo {
|
||||
origin := testRepoWithCommit(t)
|
||||
|
||||
r := origin.clone()
|
||||
r.branch("feature/branch")
|
||||
r.checkout("feature/branch")
|
||||
return r
|
||||
}
|
||||
|
||||
func testRepoWithBranchAhead(t *testing.T) *testRepo {
|
||||
origin := testRepoWithCommit(t)
|
||||
origin.branch("feature/branch")
|
||||
|
||||
r := origin.clone()
|
||||
r.checkout("feature/branch")
|
||||
|
||||
r.writeFile("local.new", "local.new")
|
||||
r.stageFile("local.new")
|
||||
r.commit("local.new")
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func testRepoWithBranchBehind(t *testing.T) *testRepo {
|
||||
origin := testRepoWithCommit(t)
|
||||
origin.branch("feature/branch")
|
||||
origin.checkout("feature/branch")
|
||||
|
||||
r := origin.clone()
|
||||
r.checkout("feature/branch")
|
||||
|
||||
origin.writeFile("origin.new", "origin.new")
|
||||
origin.stageFile("origin.new")
|
||||
origin.commit("origin.new")
|
||||
|
||||
err := r.Fetch()
|
||||
checkFatal(r.T, err)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// returns a repo with 2 commits ahead and 1 behind
|
||||
func testRepoWithBranchAheadAndBehind(t *testing.T) *testRepo {
|
||||
origin := testRepoWithCommit(t)
|
||||
origin.branch("feature/branch")
|
||||
origin.checkout("feature/branch")
|
||||
|
||||
r := origin.clone()
|
||||
r.checkout("feature/branch")
|
||||
|
||||
origin.writeFile("origin.new", "origin.new")
|
||||
origin.stageFile("origin.new")
|
||||
origin.commit("origin.new")
|
||||
|
||||
r.writeFile("local.new", "local.new")
|
||||
r.stageFile("local.new")
|
||||
r.commit("local.new")
|
||||
|
||||
r.writeFile("local.new2", "local.new2")
|
||||
r.stageFile("local.new2")
|
||||
r.commit("local.new2")
|
||||
|
||||
err := r.Fetch()
|
||||
checkFatal(r.T, err)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *testRepo) writeFile(filename string, content string) {
|
||||
path := path.Join(r.path, filename)
|
||||
err := io.Write(path, content)
|
||||
checkFatal(r.T, err)
|
||||
}
|
||||
|
||||
func (r *testRepo) init() {
|
||||
cmd := exec.Command("git", "init", "--quiet", r.path)
|
||||
runGitCmd(r.T, cmd)
|
||||
}
|
||||
|
||||
func (r *testRepo) stageFile(path string) {
|
||||
cmd := gitCmd(r.path, "add", path)
|
||||
runGitCmd(r.T, cmd)
|
||||
}
|
||||
|
||||
func (r *testRepo) commit(msg string) {
|
||||
cmd := gitCmd(r.path, "commit", "-m", fmt.Sprintf("%q", msg), "--author=\"user <user@example.com>\"")
|
||||
runGitCmd(r.T, cmd)
|
||||
}
|
||||
|
||||
func (r *testRepo) branch(name string) {
|
||||
cmd := gitCmd(r.path, "branch", name)
|
||||
runGitCmd(r.T, cmd)
|
||||
}
|
||||
|
||||
func (r *testRepo) tag(name string) {
|
||||
cmd := gitCmd(r.path, "tag", "-a", name, "-m", name)
|
||||
runGitCmd(r.T, cmd)
|
||||
}
|
||||
|
||||
func (r *testRepo) checkout(name string) {
|
||||
cmd := gitCmd(r.path, "checkout", name)
|
||||
runGitCmd(r.T, cmd)
|
||||
}
|
||||
|
||||
func (r *testRepo) clone() *testRepo {
|
||||
dir, err := io.TempDir()
|
||||
checkFatal(r.T, err)
|
||||
|
||||
url, err := url.Parse(fmt.Sprintf("file://%s/.git", r.path))
|
||||
checkFatal(r.T, err)
|
||||
|
||||
opts := &CloneOpts{
|
||||
URL: url,
|
||||
Quiet: true,
|
||||
Path: dir,
|
||||
}
|
||||
|
||||
repo, err := Clone(opts)
|
||||
checkFatal(r.T, err)
|
||||
|
||||
tr := &testRepo{
|
||||
Repo: repo,
|
||||
T: r.T,
|
||||
}
|
||||
|
||||
tr.T.Cleanup(tr.cleanup)
|
||||
return tr
|
||||
}
|
||||
|
||||
func runGitCmd(t *testing.T, cmd *exec.Cmd) {
|
||||
err := cmd.Run()
|
||||
checkFatal(t, cmdError(cmd, err))
|
||||
}
|
||||
|
||||
func checkFatal(t *testing.T, err error) {
|
||||
if err != nil {
|
||||
t.Fatalf("%+v", err)
|
||||
}
|
||||
}
|
||||
309
pkg/git/repo_test.go
Normal file
309
pkg/git/repo_test.go
Normal file
@@ -0,0 +1,309 @@
|
||||
package git
|
||||
|
||||
import (
|
||||
"git-get/pkg/io"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOpen(t *testing.T) {
|
||||
_, err := Open("/paththatdoesnotexist/repo")
|
||||
|
||||
if err != io.ErrDirectoryAccess {
|
||||
t.Errorf("Opening a repo in non existing path should throw an error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUncommitted(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
repoMaker func(*testing.T) *testRepo
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
repoMaker: testRepoEmpty,
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
name: "single untracked",
|
||||
repoMaker: testRepoWithUntracked,
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
name: "single tracked ",
|
||||
repoMaker: testRepoWithStaged,
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "committed",
|
||||
repoMaker: testRepoWithCommit,
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
name: "untracked and uncommitted",
|
||||
repoMaker: testRepoWithUncommittedAndUntracked,
|
||||
want: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
r := test.repoMaker(t)
|
||||
got, err := r.Uncommitted()
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("got error %q", err)
|
||||
}
|
||||
|
||||
if got != test.want {
|
||||
t.Errorf("expected %d; got %d", test.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
func TestUntracked(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
repoMaker func(*testing.T) *testRepo
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
repoMaker: testRepoEmpty,
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
name: "single untracked",
|
||||
repoMaker: testRepoWithUntracked,
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
name: "single tracked ",
|
||||
repoMaker: testRepoWithStaged,
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "committed",
|
||||
repoMaker: testRepoWithCommit,
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
name: "untracked and uncommitted",
|
||||
repoMaker: testRepoWithUncommittedAndUntracked,
|
||||
want: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
r := test.repoMaker(t)
|
||||
got, err := r.Uncommitted()
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("got error %q", err)
|
||||
}
|
||||
|
||||
if got != test.want {
|
||||
t.Errorf("expected %d; got %d", test.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCurrentBranch(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
repoMaker func(*testing.T) *testRepo
|
||||
want string
|
||||
}{
|
||||
// TODO: maybe add wantErr to check if error is returned correctly?
|
||||
// {
|
||||
// name: "empty",
|
||||
// repoMaker: newTestRepo,
|
||||
// want: "",
|
||||
// },
|
||||
{
|
||||
name: "only master branch",
|
||||
repoMaker: testRepoWithCommit,
|
||||
want: master,
|
||||
},
|
||||
{
|
||||
name: "checked out new branch",
|
||||
repoMaker: testRepoWithBranch,
|
||||
want: "feature/branch",
|
||||
},
|
||||
{
|
||||
name: "checked out new tag",
|
||||
repoMaker: testRepoWithTag,
|
||||
want: head,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
r := test.repoMaker(t)
|
||||
got, err := r.CurrentBranch()
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("got error %q", err)
|
||||
}
|
||||
|
||||
if got != test.want {
|
||||
t.Errorf("expected %q; got %q", test.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
func TestBranches(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
repoMaker func(*testing.T) *testRepo
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
repoMaker: testRepoEmpty,
|
||||
want: []string{""},
|
||||
},
|
||||
{
|
||||
name: "only master branch",
|
||||
repoMaker: testRepoWithCommit,
|
||||
want: []string{"master"},
|
||||
},
|
||||
{
|
||||
name: "new branch",
|
||||
repoMaker: testRepoWithBranch,
|
||||
want: []string{"feature/branch", "master"},
|
||||
},
|
||||
{
|
||||
name: "checked out new tag",
|
||||
repoMaker: testRepoWithTag,
|
||||
want: []string{"master"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
r := test.repoMaker(t)
|
||||
got, err := r.Branches()
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("got error %q", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, test.want) {
|
||||
t.Errorf("expected %+v; got %+v", test.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
func TestUpstream(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
repoMaker func(*testing.T) *testRepo
|
||||
branch string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
repoMaker: testRepoEmpty,
|
||||
branch: "master",
|
||||
want: "",
|
||||
},
|
||||
// TODO: add wantErr
|
||||
{
|
||||
name: "wrong branch name",
|
||||
repoMaker: testRepoWithCommit,
|
||||
branch: "wrong_branch_name",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "master with upstream",
|
||||
repoMaker: testRepoWithBranchWithUpstream,
|
||||
branch: "master",
|
||||
want: "origin/master",
|
||||
},
|
||||
{
|
||||
name: "branch with upstream",
|
||||
repoMaker: testRepoWithBranchWithUpstream,
|
||||
branch: "feature/branch",
|
||||
want: "origin/feature/branch",
|
||||
},
|
||||
{
|
||||
name: "branch without upstream",
|
||||
repoMaker: testRepoWithBranchWithoutUpstream,
|
||||
branch: "feature/branch",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
r := test.repoMaker(t)
|
||||
got, _ := r.Upstream(test.branch)
|
||||
|
||||
// TODO:
|
||||
// if err != nil {
|
||||
// t.Errorf("got error %q", err)
|
||||
// }
|
||||
|
||||
if !reflect.DeepEqual(got, test.want) {
|
||||
t.Errorf("expected %+v; got %+v", test.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
func TestAheadBehind(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
repoMaker func(*testing.T) *testRepo
|
||||
branch string
|
||||
want []int
|
||||
}{
|
||||
{
|
||||
name: "fresh clone",
|
||||
repoMaker: testRepoWithBranchWithUpstream,
|
||||
branch: "master",
|
||||
want: []int{0, 0},
|
||||
},
|
||||
{
|
||||
name: "branch ahead",
|
||||
repoMaker: testRepoWithBranchAhead,
|
||||
branch: "feature/branch",
|
||||
want: []int{1, 0},
|
||||
},
|
||||
|
||||
{
|
||||
name: "branch behind",
|
||||
repoMaker: testRepoWithBranchBehind,
|
||||
branch: "feature/branch",
|
||||
want: []int{0, 1},
|
||||
},
|
||||
{
|
||||
name: "branch ahead and behind",
|
||||
repoMaker: testRepoWithBranchAheadAndBehind,
|
||||
branch: "feature/branch",
|
||||
want: []int{2, 1},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
r := test.repoMaker(t)
|
||||
upstream, err := r.Upstream(test.branch)
|
||||
if err != nil {
|
||||
t.Errorf("got error %q", err)
|
||||
}
|
||||
|
||||
ahead, behind, err := r.AheadBehind(test.branch, upstream)
|
||||
if err != nil {
|
||||
t.Errorf("got error %q", err)
|
||||
}
|
||||
|
||||
if ahead != test.want[0] || behind != test.want[1] {
|
||||
t.Errorf("expected %+v; got [%d, %d]", test.want, ahead, behind)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user