1170 lines
32 KiB
Go
1170 lines
32 KiB
Go
package git
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestClientCommand(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
repoDir string
|
|
gitPath string
|
|
wantExe string
|
|
wantArgs []string
|
|
}{
|
|
{
|
|
name: "creates command",
|
|
gitPath: "path/to/git",
|
|
wantExe: "path/to/git",
|
|
wantArgs: []string{"path/to/git", "ref-log"},
|
|
},
|
|
{
|
|
name: "adds repo directory configuration",
|
|
repoDir: "path/to/repo",
|
|
gitPath: "path/to/git",
|
|
wantExe: "path/to/git",
|
|
wantArgs: []string{"path/to/git", "-C", "path/to/repo", "ref-log"},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
in, out, errOut := &bytes.Buffer{}, &bytes.Buffer{}, &bytes.Buffer{}
|
|
client := Client{
|
|
Stdin: in,
|
|
Stdout: out,
|
|
Stderr: errOut,
|
|
RepoDir: tt.repoDir,
|
|
GitPath: tt.gitPath,
|
|
}
|
|
cmd, err := client.Command(context.Background(), "ref-log")
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.wantExe, cmd.Path)
|
|
assert.Equal(t, tt.wantArgs, cmd.Args)
|
|
assert.Equal(t, in, cmd.Stdin)
|
|
assert.Equal(t, out, cmd.Stdout)
|
|
assert.Equal(t, errOut, cmd.Stderr)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClientAuthenticatedCommand(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
path string
|
|
wantArgs []string
|
|
}{
|
|
{
|
|
name: "adds credential helper config options",
|
|
path: "path/to/gh",
|
|
wantArgs: []string{"path/to/git", "-c", "credential.helper=", "-c", `credential.helper=!"path/to/gh" auth git-credential`, "fetch"},
|
|
},
|
|
{
|
|
name: "fallback when GhPath is not set",
|
|
wantArgs: []string{"path/to/git", "-c", "credential.helper=", "-c", `credential.helper=!"gh" auth git-credential`, "fetch"},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
client := Client{
|
|
GhPath: tt.path,
|
|
GitPath: "path/to/git",
|
|
}
|
|
cmd, err := client.AuthenticatedCommand(context.Background(), "fetch")
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.wantArgs, cmd.Args)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClientRemotes(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
initRepo(t, tempDir)
|
|
gitDir := filepath.Join(tempDir, ".git")
|
|
remoteFile := filepath.Join(gitDir, "config")
|
|
remotes := `
|
|
[remote "origin"]
|
|
url = git@example.com:monalisa/origin.git
|
|
[remote "test"]
|
|
url = git://github.com/hubot/test.git
|
|
gh-resolved = other
|
|
[remote "upstream"]
|
|
url = https://github.com/monalisa/upstream.git
|
|
gh-resolved = base
|
|
[remote "github"]
|
|
url = git@github.com:hubot/github.git
|
|
`
|
|
f, err := os.OpenFile(remoteFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0755)
|
|
assert.NoError(t, err)
|
|
_, err = f.Write([]byte(remotes))
|
|
assert.NoError(t, err)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
client := Client{
|
|
RepoDir: tempDir,
|
|
}
|
|
rs, err := client.Remotes(context.Background())
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 4, len(rs))
|
|
assert.Equal(t, "upstream", rs[0].Name)
|
|
assert.Equal(t, "base", rs[0].Resolved)
|
|
assert.Equal(t, "github", rs[1].Name)
|
|
assert.Equal(t, "", rs[1].Resolved)
|
|
assert.Equal(t, "origin", rs[2].Name)
|
|
assert.Equal(t, "", rs[2].Resolved)
|
|
assert.Equal(t, "test", rs[3].Name)
|
|
assert.Equal(t, "other", rs[3].Resolved)
|
|
}
|
|
|
|
func TestClientRemotes_no_resolved_remote(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
initRepo(t, tempDir)
|
|
gitDir := filepath.Join(tempDir, ".git")
|
|
remoteFile := filepath.Join(gitDir, "config")
|
|
remotes := `
|
|
[remote "origin"]
|
|
url = git@example.com:monalisa/origin.git
|
|
[remote "test"]
|
|
url = git://github.com/hubot/test.git
|
|
[remote "upstream"]
|
|
url = https://github.com/monalisa/upstream.git
|
|
[remote "github"]
|
|
url = git@github.com:hubot/github.git
|
|
`
|
|
f, err := os.OpenFile(remoteFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0755)
|
|
assert.NoError(t, err)
|
|
_, err = f.Write([]byte(remotes))
|
|
assert.NoError(t, err)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
client := Client{
|
|
RepoDir: tempDir,
|
|
}
|
|
rs, err := client.Remotes(context.Background())
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 4, len(rs))
|
|
assert.Equal(t, "upstream", rs[0].Name)
|
|
assert.Equal(t, "github", rs[1].Name)
|
|
assert.Equal(t, "origin", rs[2].Name)
|
|
assert.Equal(t, "", rs[2].Resolved)
|
|
assert.Equal(t, "test", rs[3].Name)
|
|
}
|
|
|
|
func TestParseRemotes(t *testing.T) {
|
|
remoteList := []string{
|
|
"mona\tgit@github.com:monalisa/myfork.git (fetch)",
|
|
"origin\thttps://github.com/monalisa/octo-cat.git (fetch)",
|
|
"origin\thttps://github.com/monalisa/octo-cat-push.git (push)",
|
|
"upstream\thttps://example.com/nowhere.git (fetch)",
|
|
"upstream\thttps://github.com/hubot/tools (push)",
|
|
"zardoz\thttps://example.com/zed.git (push)",
|
|
"koke\tgit://github.com/koke/grit.git (fetch)",
|
|
"koke\tgit://github.com/koke/grit.git (push)",
|
|
}
|
|
|
|
r := parseRemotes(remoteList)
|
|
assert.Equal(t, 5, len(r))
|
|
|
|
assert.Equal(t, "mona", r[0].Name)
|
|
assert.Equal(t, "ssh://git@github.com/monalisa/myfork.git", r[0].FetchURL.String())
|
|
assert.Nil(t, r[0].PushURL)
|
|
|
|
assert.Equal(t, "origin", r[1].Name)
|
|
assert.Equal(t, "/monalisa/octo-cat.git", r[1].FetchURL.Path)
|
|
assert.Equal(t, "/monalisa/octo-cat-push.git", r[1].PushURL.Path)
|
|
|
|
assert.Equal(t, "upstream", r[2].Name)
|
|
assert.Equal(t, "example.com", r[2].FetchURL.Host)
|
|
assert.Equal(t, "github.com", r[2].PushURL.Host)
|
|
|
|
assert.Equal(t, "zardoz", r[3].Name)
|
|
assert.Nil(t, r[3].FetchURL)
|
|
assert.Equal(t, "https://example.com/zed.git", r[3].PushURL.String())
|
|
|
|
assert.Equal(t, "koke", r[4].Name)
|
|
assert.Equal(t, "/koke/grit.git", r[4].FetchURL.Path)
|
|
assert.Equal(t, "/koke/grit.git", r[4].PushURL.Path)
|
|
}
|
|
|
|
func TestClientUpdateRemoteURL(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cmdExitStatus int
|
|
cmdStdout string
|
|
cmdStderr string
|
|
wantCmdArgs string
|
|
wantErrorMsg string
|
|
}{
|
|
{
|
|
name: "update remote url",
|
|
wantCmdArgs: `path/to/git remote set-url test https://test.com`,
|
|
},
|
|
{
|
|
name: "git error",
|
|
cmdExitStatus: 1,
|
|
cmdStderr: "git error message",
|
|
wantCmdArgs: `path/to/git remote set-url test https://test.com`,
|
|
wantErrorMsg: "failed to run git: git error message",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cmd, cmdCtx := createCommandContext(t, tt.cmdExitStatus, tt.cmdStdout, tt.cmdStderr)
|
|
client := Client{
|
|
GitPath: "path/to/git",
|
|
commandContext: cmdCtx,
|
|
}
|
|
err := client.UpdateRemoteURL(context.Background(), "test", "https://test.com")
|
|
assert.Equal(t, tt.wantCmdArgs, strings.Join(cmd.Args[3:], " "))
|
|
if tt.wantErrorMsg == "" {
|
|
assert.NoError(t, err)
|
|
} else {
|
|
assert.EqualError(t, err, tt.wantErrorMsg)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClientSetRemoteResolution(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cmdExitStatus int
|
|
cmdStdout string
|
|
cmdStderr string
|
|
wantCmdArgs string
|
|
wantErrorMsg string
|
|
}{
|
|
{
|
|
name: "set remote resolution",
|
|
wantCmdArgs: `path/to/git config --add remote.origin.gh-resolved base`,
|
|
},
|
|
{
|
|
name: "git error",
|
|
cmdExitStatus: 1,
|
|
cmdStderr: "git error message",
|
|
wantCmdArgs: `path/to/git config --add remote.origin.gh-resolved base`,
|
|
wantErrorMsg: "failed to run git: git error message",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cmd, cmdCtx := createCommandContext(t, tt.cmdExitStatus, tt.cmdStdout, tt.cmdStderr)
|
|
client := Client{
|
|
GitPath: "path/to/git",
|
|
commandContext: cmdCtx,
|
|
}
|
|
err := client.SetRemoteResolution(context.Background(), "origin", "base")
|
|
assert.Equal(t, tt.wantCmdArgs, strings.Join(cmd.Args[3:], " "))
|
|
if tt.wantErrorMsg == "" {
|
|
assert.NoError(t, err)
|
|
} else {
|
|
assert.EqualError(t, err, tt.wantErrorMsg)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClientCurrentBranch(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cmdExitStatus int
|
|
cmdStdout string
|
|
cmdStderr string
|
|
wantCmdArgs string
|
|
wantErrorMsg string
|
|
wantBranch string
|
|
}{
|
|
{
|
|
name: "branch name",
|
|
cmdStdout: "branch-name\n",
|
|
wantCmdArgs: `path/to/git symbolic-ref --quiet HEAD`,
|
|
wantBranch: "branch-name",
|
|
},
|
|
{
|
|
name: "ref",
|
|
cmdStdout: "refs/heads/branch-name\n",
|
|
wantCmdArgs: `path/to/git symbolic-ref --quiet HEAD`,
|
|
wantBranch: "branch-name",
|
|
},
|
|
{
|
|
name: "escaped ref",
|
|
cmdStdout: "refs/heads/branch\u00A0with\u00A0non\u00A0breaking\u00A0space\n",
|
|
wantCmdArgs: `path/to/git symbolic-ref --quiet HEAD`,
|
|
wantBranch: "branch\u00A0with\u00A0non\u00A0breaking\u00A0space",
|
|
},
|
|
{
|
|
name: "detached head",
|
|
cmdExitStatus: 1,
|
|
wantCmdArgs: `path/to/git symbolic-ref --quiet HEAD`,
|
|
wantErrorMsg: "failed to run git: not on any branch",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cmd, cmdCtx := createCommandContext(t, tt.cmdExitStatus, tt.cmdStdout, tt.cmdStderr)
|
|
client := Client{
|
|
GitPath: "path/to/git",
|
|
commandContext: cmdCtx,
|
|
}
|
|
branch, err := client.CurrentBranch(context.Background())
|
|
assert.Equal(t, tt.wantCmdArgs, strings.Join(cmd.Args[3:], " "))
|
|
if tt.wantErrorMsg == "" {
|
|
assert.NoError(t, err)
|
|
} else {
|
|
assert.EqualError(t, err, tt.wantErrorMsg)
|
|
}
|
|
assert.Equal(t, tt.wantBranch, branch)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClientShowRefs(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cmdExitStatus int
|
|
cmdStdout string
|
|
cmdStderr string
|
|
wantCmdArgs string
|
|
wantRefs []Ref
|
|
wantErrorMsg string
|
|
}{
|
|
{
|
|
name: "show refs with one valid ref and one invalid ref",
|
|
cmdExitStatus: 128,
|
|
cmdStdout: "9ea76237a557015e73446d33268569a114c0649c refs/heads/valid",
|
|
cmdStderr: "fatal: 'refs/heads/invalid' - not a valid ref",
|
|
wantCmdArgs: `path/to/git show-ref --verify -- refs/heads/valid refs/heads/invalid`,
|
|
wantRefs: []Ref{{
|
|
Hash: "9ea76237a557015e73446d33268569a114c0649c",
|
|
Name: "refs/heads/valid",
|
|
}},
|
|
wantErrorMsg: "failed to run git: fatal: 'refs/heads/invalid' - not a valid ref",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cmd, cmdCtx := createCommandContext(t, tt.cmdExitStatus, tt.cmdStdout, tt.cmdStderr)
|
|
client := Client{
|
|
GitPath: "path/to/git",
|
|
commandContext: cmdCtx,
|
|
}
|
|
refs, err := client.ShowRefs(context.Background(), []string{"refs/heads/valid", "refs/heads/invalid"})
|
|
assert.Equal(t, tt.wantCmdArgs, strings.Join(cmd.Args[3:], " "))
|
|
assert.EqualError(t, err, tt.wantErrorMsg)
|
|
assert.Equal(t, tt.wantRefs, refs)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClientConfig(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cmdExitStatus int
|
|
cmdStdout string
|
|
cmdStderr string
|
|
wantCmdArgs string
|
|
wantOut string
|
|
wantErrorMsg string
|
|
}{
|
|
{
|
|
name: "get config key",
|
|
cmdStdout: "test",
|
|
wantCmdArgs: `path/to/git config credential.helper`,
|
|
wantOut: "test",
|
|
},
|
|
{
|
|
name: "get unknown config key",
|
|
cmdExitStatus: 1,
|
|
cmdStderr: "git error message",
|
|
wantCmdArgs: `path/to/git config credential.helper`,
|
|
wantErrorMsg: "failed to run git: unknown config key credential.helper",
|
|
},
|
|
{
|
|
name: "git error",
|
|
cmdExitStatus: 2,
|
|
cmdStderr: "git error message",
|
|
wantCmdArgs: `path/to/git config credential.helper`,
|
|
wantErrorMsg: "failed to run git: git error message",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cmd, cmdCtx := createCommandContext(t, tt.cmdExitStatus, tt.cmdStdout, tt.cmdStderr)
|
|
client := Client{
|
|
GitPath: "path/to/git",
|
|
commandContext: cmdCtx,
|
|
}
|
|
out, err := client.Config(context.Background(), "credential.helper")
|
|
assert.Equal(t, tt.wantCmdArgs, strings.Join(cmd.Args[3:], " "))
|
|
if tt.wantErrorMsg == "" {
|
|
assert.NoError(t, err)
|
|
} else {
|
|
assert.EqualError(t, err, tt.wantErrorMsg)
|
|
}
|
|
assert.Equal(t, tt.wantOut, out)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClientUncommittedChangeCount(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cmdExitStatus int
|
|
cmdStdout string
|
|
cmdStderr string
|
|
wantCmdArgs string
|
|
wantChangeCount int
|
|
}{
|
|
{
|
|
name: "no changes",
|
|
wantCmdArgs: `path/to/git status --porcelain`,
|
|
wantChangeCount: 0,
|
|
},
|
|
{
|
|
name: "one change",
|
|
cmdStdout: " M poem.txt",
|
|
wantCmdArgs: `path/to/git status --porcelain`,
|
|
wantChangeCount: 1,
|
|
},
|
|
{
|
|
name: "untracked file",
|
|
cmdStdout: " M poem.txt\n?? new.txt",
|
|
wantCmdArgs: `path/to/git status --porcelain`,
|
|
wantChangeCount: 2,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cmd, cmdCtx := createCommandContext(t, tt.cmdExitStatus, tt.cmdStdout, tt.cmdStderr)
|
|
client := Client{
|
|
GitPath: "path/to/git",
|
|
commandContext: cmdCtx,
|
|
}
|
|
ucc, err := client.UncommittedChangeCount(context.Background())
|
|
assert.Equal(t, tt.wantCmdArgs, strings.Join(cmd.Args[3:], " "))
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.wantChangeCount, ucc)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClientCommits(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cmdExitStatus int
|
|
cmdStdout string
|
|
cmdStderr string
|
|
wantCmdArgs string
|
|
wantCommits []*Commit
|
|
wantErrorMsg string
|
|
}{
|
|
{
|
|
name: "get commits",
|
|
cmdStdout: "6a6872b918c601a0e730710ad8473938a7516d30,testing testability test",
|
|
wantCmdArgs: `path/to/git -c log.ShowSignature=false log --pretty=format:%H,%s --cherry SHA1...SHA2`,
|
|
wantCommits: []*Commit{{
|
|
Sha: "6a6872b918c601a0e730710ad8473938a7516d30",
|
|
Title: "testing testability test",
|
|
}},
|
|
},
|
|
{
|
|
name: "no commits between SHAs",
|
|
wantCmdArgs: `path/to/git -c log.ShowSignature=false log --pretty=format:%H,%s --cherry SHA1...SHA2`,
|
|
wantErrorMsg: "could not find any commits between SHA1 and SHA2",
|
|
},
|
|
{
|
|
name: "git error",
|
|
cmdExitStatus: 1,
|
|
cmdStderr: "git error message",
|
|
wantCmdArgs: `path/to/git -c log.ShowSignature=false log --pretty=format:%H,%s --cherry SHA1...SHA2`,
|
|
wantErrorMsg: "failed to run git: git error message",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cmd, cmdCtx := createCommandContext(t, tt.cmdExitStatus, tt.cmdStdout, tt.cmdStderr)
|
|
client := Client{
|
|
GitPath: "path/to/git",
|
|
commandContext: cmdCtx,
|
|
}
|
|
commits, err := client.Commits(context.Background(), "SHA1", "SHA2")
|
|
assert.Equal(t, tt.wantCmdArgs, strings.Join(cmd.Args[3:], " "))
|
|
if tt.wantErrorMsg != "" {
|
|
assert.EqualError(t, err, tt.wantErrorMsg)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
assert.Equal(t, tt.wantCommits, commits)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClientLastCommit(t *testing.T) {
|
|
client := Client{
|
|
RepoDir: "./fixtures/simple.git",
|
|
}
|
|
c, err := client.LastCommit(context.Background())
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "6f1a2405cace1633d89a79c74c65f22fe78f9659", c.Sha)
|
|
assert.Equal(t, "Second commit", c.Title)
|
|
}
|
|
|
|
func TestClientCommitBody(t *testing.T) {
|
|
client := Client{
|
|
RepoDir: "./fixtures/simple.git",
|
|
}
|
|
body, err := client.CommitBody(context.Background(), "6f1a2405cace1633d89a79c74c65f22fe78f9659")
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "I'm starting to get the hang of things\n", body)
|
|
}
|
|
|
|
func TestClientReadBranchConfig(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cmdExitStatus int
|
|
cmdStdout string
|
|
cmdStderr string
|
|
wantCmdArgs string
|
|
wantBranchConfig BranchConfig
|
|
}{
|
|
{
|
|
name: "read branch config",
|
|
cmdStdout: "branch.trunk.remote origin\nbranch.trunk.merge refs/heads/trunk",
|
|
wantCmdArgs: `path/to/git config --get-regexp ^branch\.trunk\.(remote|merge)$`,
|
|
wantBranchConfig: BranchConfig{RemoteName: "origin", MergeRef: "refs/heads/trunk"},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cmd, cmdCtx := createCommandContext(t, tt.cmdExitStatus, tt.cmdStdout, tt.cmdStderr)
|
|
client := Client{
|
|
GitPath: "path/to/git",
|
|
commandContext: cmdCtx,
|
|
}
|
|
branchConfig := client.ReadBranchConfig(context.Background(), "trunk")
|
|
assert.Equal(t, tt.wantCmdArgs, strings.Join(cmd.Args[3:], " "))
|
|
assert.Equal(t, tt.wantBranchConfig, branchConfig)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClientDeleteLocalBranch(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cmdExitStatus int
|
|
cmdStdout string
|
|
cmdStderr string
|
|
wantCmdArgs string
|
|
wantErrorMsg string
|
|
}{
|
|
{
|
|
name: "delete local branch",
|
|
wantCmdArgs: `path/to/git branch -D trunk`,
|
|
},
|
|
{
|
|
name: "git error",
|
|
cmdExitStatus: 1,
|
|
cmdStderr: "git error message",
|
|
wantCmdArgs: `path/to/git branch -D trunk`,
|
|
wantErrorMsg: "failed to run git: git error message",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cmd, cmdCtx := createCommandContext(t, tt.cmdExitStatus, tt.cmdStdout, tt.cmdStderr)
|
|
client := Client{
|
|
GitPath: "path/to/git",
|
|
commandContext: cmdCtx,
|
|
}
|
|
err := client.DeleteLocalBranch(context.Background(), "trunk")
|
|
assert.Equal(t, tt.wantCmdArgs, strings.Join(cmd.Args[3:], " "))
|
|
if tt.wantErrorMsg == "" {
|
|
assert.NoError(t, err)
|
|
} else {
|
|
assert.EqualError(t, err, tt.wantErrorMsg)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClientHasLocalBranch(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cmdExitStatus int
|
|
cmdStdout string
|
|
cmdStderr string
|
|
wantCmdArgs string
|
|
wantOut bool
|
|
}{
|
|
{
|
|
name: "has local branch",
|
|
wantCmdArgs: `path/to/git rev-parse --verify refs/heads/trunk`,
|
|
wantOut: true,
|
|
},
|
|
{
|
|
name: "does not have local branch",
|
|
cmdExitStatus: 1,
|
|
wantCmdArgs: `path/to/git rev-parse --verify refs/heads/trunk`,
|
|
wantOut: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cmd, cmdCtx := createCommandContext(t, tt.cmdExitStatus, tt.cmdStdout, tt.cmdStderr)
|
|
client := Client{
|
|
GitPath: "path/to/git",
|
|
commandContext: cmdCtx,
|
|
}
|
|
out := client.HasLocalBranch(context.Background(), "trunk")
|
|
assert.Equal(t, tt.wantCmdArgs, strings.Join(cmd.Args[3:], " "))
|
|
assert.Equal(t, out, tt.wantOut)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClientCheckoutBranch(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cmdExitStatus int
|
|
cmdStdout string
|
|
cmdStderr string
|
|
wantCmdArgs string
|
|
wantErrorMsg string
|
|
}{
|
|
{
|
|
name: "checkout branch",
|
|
wantCmdArgs: `path/to/git checkout trunk`,
|
|
},
|
|
{
|
|
name: "git error",
|
|
cmdExitStatus: 1,
|
|
cmdStderr: "git error message",
|
|
wantCmdArgs: `path/to/git checkout trunk`,
|
|
wantErrorMsg: "failed to run git: git error message",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cmd, cmdCtx := createCommandContext(t, tt.cmdExitStatus, tt.cmdStdout, tt.cmdStderr)
|
|
client := Client{
|
|
GitPath: "path/to/git",
|
|
commandContext: cmdCtx,
|
|
}
|
|
err := client.CheckoutBranch(context.Background(), "trunk")
|
|
assert.Equal(t, tt.wantCmdArgs, strings.Join(cmd.Args[3:], " "))
|
|
if tt.wantErrorMsg == "" {
|
|
assert.NoError(t, err)
|
|
} else {
|
|
assert.EqualError(t, err, tt.wantErrorMsg)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClientCheckoutNewBranch(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cmdExitStatus int
|
|
cmdStdout string
|
|
cmdStderr string
|
|
wantCmdArgs string
|
|
wantErrorMsg string
|
|
}{
|
|
{
|
|
name: "checkout new branch",
|
|
wantCmdArgs: `path/to/git checkout -b trunk --track origin/trunk`,
|
|
},
|
|
{
|
|
name: "git error",
|
|
cmdExitStatus: 1,
|
|
cmdStderr: "git error message",
|
|
wantCmdArgs: `path/to/git checkout -b trunk --track origin/trunk`,
|
|
wantErrorMsg: "failed to run git: git error message",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cmd, cmdCtx := createCommandContext(t, tt.cmdExitStatus, tt.cmdStdout, tt.cmdStderr)
|
|
client := Client{
|
|
GitPath: "path/to/git",
|
|
commandContext: cmdCtx,
|
|
}
|
|
err := client.CheckoutNewBranch(context.Background(), "origin", "trunk")
|
|
assert.Equal(t, tt.wantCmdArgs, strings.Join(cmd.Args[3:], " "))
|
|
if tt.wantErrorMsg == "" {
|
|
assert.NoError(t, err)
|
|
} else {
|
|
assert.EqualError(t, err, tt.wantErrorMsg)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClientToplevelDir(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cmdExitStatus int
|
|
cmdStdout string
|
|
cmdStderr string
|
|
wantCmdArgs string
|
|
wantDir string
|
|
wantErrorMsg string
|
|
}{
|
|
{
|
|
name: "top level dir",
|
|
cmdStdout: "/path/to/repo",
|
|
wantCmdArgs: `path/to/git rev-parse --show-toplevel`,
|
|
wantDir: "/path/to/repo",
|
|
},
|
|
{
|
|
name: "git error",
|
|
cmdExitStatus: 1,
|
|
cmdStderr: "git error message",
|
|
wantCmdArgs: `path/to/git rev-parse --show-toplevel`,
|
|
wantErrorMsg: "failed to run git: git error message",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cmd, cmdCtx := createCommandContext(t, tt.cmdExitStatus, tt.cmdStdout, tt.cmdStderr)
|
|
client := Client{
|
|
GitPath: "path/to/git",
|
|
commandContext: cmdCtx,
|
|
}
|
|
dir, err := client.ToplevelDir(context.Background())
|
|
assert.Equal(t, tt.wantCmdArgs, strings.Join(cmd.Args[3:], " "))
|
|
if tt.wantErrorMsg == "" {
|
|
assert.NoError(t, err)
|
|
} else {
|
|
assert.EqualError(t, err, tt.wantErrorMsg)
|
|
}
|
|
assert.Equal(t, tt.wantDir, dir)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClientGitDir(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cmdExitStatus int
|
|
cmdStdout string
|
|
cmdStderr string
|
|
wantCmdArgs string
|
|
wantDir string
|
|
wantErrorMsg string
|
|
}{
|
|
{
|
|
name: "git dir",
|
|
cmdStdout: "/path/to/repo/.git",
|
|
wantCmdArgs: `path/to/git rev-parse --git-dir`,
|
|
wantDir: "/path/to/repo/.git",
|
|
},
|
|
{
|
|
name: "git error",
|
|
cmdExitStatus: 1,
|
|
cmdStderr: "git error message",
|
|
wantCmdArgs: `path/to/git rev-parse --git-dir`,
|
|
wantErrorMsg: "failed to run git: git error message",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cmd, cmdCtx := createCommandContext(t, tt.cmdExitStatus, tt.cmdStdout, tt.cmdStderr)
|
|
client := Client{
|
|
GitPath: "path/to/git",
|
|
commandContext: cmdCtx,
|
|
}
|
|
dir, err := client.GitDir(context.Background())
|
|
assert.Equal(t, tt.wantCmdArgs, strings.Join(cmd.Args[3:], " "))
|
|
if tt.wantErrorMsg == "" {
|
|
assert.NoError(t, err)
|
|
} else {
|
|
assert.EqualError(t, err, tt.wantErrorMsg)
|
|
}
|
|
assert.Equal(t, tt.wantDir, dir)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClientPathFromRoot(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cmdExitStatus int
|
|
cmdStdout string
|
|
cmdStderr string
|
|
wantCmdArgs string
|
|
wantErrorMsg string
|
|
wantDir string
|
|
}{
|
|
{
|
|
name: "current path from root",
|
|
cmdStdout: "some/path/",
|
|
wantCmdArgs: `path/to/git rev-parse --show-prefix`,
|
|
wantDir: "some/path",
|
|
},
|
|
{
|
|
name: "git error",
|
|
cmdExitStatus: 1,
|
|
cmdStderr: "git error message",
|
|
wantCmdArgs: `path/to/git rev-parse --show-prefix`,
|
|
wantDir: "",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cmd, cmdCtx := createCommandContext(t, tt.cmdExitStatus, tt.cmdStdout, tt.cmdStderr)
|
|
client := Client{
|
|
GitPath: "path/to/git",
|
|
commandContext: cmdCtx,
|
|
}
|
|
dir := client.PathFromRoot(context.Background())
|
|
assert.Equal(t, tt.wantCmdArgs, strings.Join(cmd.Args[3:], " "))
|
|
assert.Equal(t, tt.wantDir, dir)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClientFetch(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
mods []CommandModifier
|
|
cmdExitStatus int
|
|
cmdStdout string
|
|
cmdStderr string
|
|
wantCmdArgs string
|
|
wantErrorMsg string
|
|
}{
|
|
{
|
|
name: "fetch",
|
|
wantCmdArgs: `path/to/git -c credential.helper= -c credential.helper=!"gh" auth git-credential fetch origin trunk`,
|
|
},
|
|
{
|
|
name: "accepts command modifiers",
|
|
mods: []CommandModifier{WithRepoDir("/path/to/repo")},
|
|
wantCmdArgs: `path/to/git -C /path/to/repo -c credential.helper= -c credential.helper=!"gh" auth git-credential fetch origin trunk`,
|
|
},
|
|
{
|
|
name: "git error",
|
|
cmdExitStatus: 1,
|
|
cmdStderr: "git error message",
|
|
wantCmdArgs: `path/to/git -c credential.helper= -c credential.helper=!"gh" auth git-credential fetch origin trunk`,
|
|
wantErrorMsg: "failed to run git: git error message",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cmd, cmdCtx := createCommandContext(t, tt.cmdExitStatus, tt.cmdStdout, tt.cmdStderr)
|
|
client := Client{
|
|
GitPath: "path/to/git",
|
|
commandContext: cmdCtx,
|
|
}
|
|
err := client.Fetch(context.Background(), "origin", "trunk", tt.mods...)
|
|
assert.Equal(t, tt.wantCmdArgs, strings.Join(cmd.Args[3:], " "))
|
|
if tt.wantErrorMsg == "" {
|
|
assert.NoError(t, err)
|
|
} else {
|
|
assert.EqualError(t, err, tt.wantErrorMsg)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClientPull(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
mods []CommandModifier
|
|
cmdExitStatus int
|
|
cmdStdout string
|
|
cmdStderr string
|
|
wantCmdArgs string
|
|
wantErrorMsg string
|
|
}{
|
|
{
|
|
name: "pull",
|
|
wantCmdArgs: `path/to/git -c credential.helper= -c credential.helper=!"gh" auth git-credential pull --ff-only origin trunk`,
|
|
},
|
|
{
|
|
name: "accepts command modifiers",
|
|
mods: []CommandModifier{WithRepoDir("/path/to/repo")},
|
|
wantCmdArgs: `path/to/git -C /path/to/repo -c credential.helper= -c credential.helper=!"gh" auth git-credential pull --ff-only origin trunk`,
|
|
},
|
|
{
|
|
name: "git error",
|
|
cmdExitStatus: 1,
|
|
cmdStderr: "git error message",
|
|
wantCmdArgs: `path/to/git -c credential.helper= -c credential.helper=!"gh" auth git-credential pull --ff-only origin trunk`,
|
|
wantErrorMsg: "failed to run git: git error message",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cmd, cmdCtx := createCommandContext(t, tt.cmdExitStatus, tt.cmdStdout, tt.cmdStderr)
|
|
client := Client{
|
|
GitPath: "path/to/git",
|
|
commandContext: cmdCtx,
|
|
}
|
|
err := client.Pull(context.Background(), "origin", "trunk", tt.mods...)
|
|
assert.Equal(t, tt.wantCmdArgs, strings.Join(cmd.Args[3:], " "))
|
|
if tt.wantErrorMsg == "" {
|
|
assert.NoError(t, err)
|
|
} else {
|
|
assert.EqualError(t, err, tt.wantErrorMsg)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClientPush(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
mods []CommandModifier
|
|
cmdExitStatus int
|
|
cmdStdout string
|
|
cmdStderr string
|
|
wantCmdArgs string
|
|
wantErrorMsg string
|
|
}{
|
|
{
|
|
name: "push",
|
|
wantCmdArgs: `path/to/git -c credential.helper= -c credential.helper=!"gh" auth git-credential push --set-upstream origin trunk`,
|
|
},
|
|
{
|
|
name: "accepts command modifiers",
|
|
mods: []CommandModifier{WithRepoDir("/path/to/repo")},
|
|
wantCmdArgs: `path/to/git -C /path/to/repo -c credential.helper= -c credential.helper=!"gh" auth git-credential push --set-upstream origin trunk`,
|
|
},
|
|
{
|
|
name: "git error",
|
|
cmdExitStatus: 1,
|
|
cmdStderr: "git error message",
|
|
wantCmdArgs: `path/to/git -c credential.helper= -c credential.helper=!"gh" auth git-credential push --set-upstream origin trunk`,
|
|
wantErrorMsg: "failed to run git: git error message",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cmd, cmdCtx := createCommandContext(t, tt.cmdExitStatus, tt.cmdStdout, tt.cmdStderr)
|
|
client := Client{
|
|
GitPath: "path/to/git",
|
|
commandContext: cmdCtx,
|
|
}
|
|
err := client.Push(context.Background(), "origin", "trunk", tt.mods...)
|
|
assert.Equal(t, tt.wantCmdArgs, strings.Join(cmd.Args[3:], " "))
|
|
if tt.wantErrorMsg == "" {
|
|
assert.NoError(t, err)
|
|
} else {
|
|
assert.EqualError(t, err, tt.wantErrorMsg)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClientClone(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
mods []CommandModifier
|
|
cmdExitStatus int
|
|
cmdStdout string
|
|
cmdStderr string
|
|
wantCmdArgs string
|
|
wantTarget string
|
|
wantErrorMsg string
|
|
}{
|
|
{
|
|
name: "clone",
|
|
wantCmdArgs: `path/to/git -c credential.helper= -c credential.helper=!"gh" auth git-credential clone github.com/cli/cli`,
|
|
wantTarget: "cli",
|
|
},
|
|
{
|
|
name: "accepts command modifiers",
|
|
mods: []CommandModifier{WithRepoDir("/path/to/repo")},
|
|
wantCmdArgs: `path/to/git -C /path/to/repo -c credential.helper= -c credential.helper=!"gh" auth git-credential clone github.com/cli/cli`,
|
|
wantTarget: "cli",
|
|
},
|
|
{
|
|
name: "git error",
|
|
cmdExitStatus: 1,
|
|
cmdStderr: "git error message",
|
|
wantCmdArgs: `path/to/git -c credential.helper= -c credential.helper=!"gh" auth git-credential clone github.com/cli/cli`,
|
|
wantErrorMsg: "failed to run git: git error message",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cmd, cmdCtx := createCommandContext(t, tt.cmdExitStatus, tt.cmdStdout, tt.cmdStderr)
|
|
client := Client{
|
|
GitPath: "path/to/git",
|
|
commandContext: cmdCtx,
|
|
}
|
|
target, err := client.Clone(context.Background(), "github.com/cli/cli", []string{}, tt.mods...)
|
|
assert.Equal(t, tt.wantCmdArgs, strings.Join(cmd.Args[3:], " "))
|
|
if tt.wantErrorMsg == "" {
|
|
assert.NoError(t, err)
|
|
} else {
|
|
assert.EqualError(t, err, tt.wantErrorMsg)
|
|
}
|
|
assert.Equal(t, tt.wantTarget, target)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseCloneArgs(t *testing.T) {
|
|
type wanted struct {
|
|
args []string
|
|
dir string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args []string
|
|
want wanted
|
|
}{
|
|
{
|
|
name: "args and target",
|
|
args: []string{"target_directory", "-o", "upstream", "--depth", "1"},
|
|
want: wanted{
|
|
args: []string{"-o", "upstream", "--depth", "1"},
|
|
dir: "target_directory",
|
|
},
|
|
},
|
|
{
|
|
name: "only args",
|
|
args: []string{"-o", "upstream", "--depth", "1"},
|
|
want: wanted{
|
|
args: []string{"-o", "upstream", "--depth", "1"},
|
|
dir: "",
|
|
},
|
|
},
|
|
{
|
|
name: "only target",
|
|
args: []string{"target_directory"},
|
|
want: wanted{
|
|
args: []string{},
|
|
dir: "target_directory",
|
|
},
|
|
},
|
|
{
|
|
name: "no args",
|
|
args: []string{},
|
|
want: wanted{
|
|
args: []string{},
|
|
dir: "",
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
args, dir := parseCloneArgs(tt.args)
|
|
got := wanted{args: args, dir: dir}
|
|
assert.Equal(t, got, tt.want)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClientAddRemote(t *testing.T) {
|
|
tests := []struct {
|
|
title string
|
|
name string
|
|
url string
|
|
branches []string
|
|
dir string
|
|
cmdExitStatus int
|
|
cmdStdout string
|
|
cmdStderr string
|
|
wantCmdArgs string
|
|
wantErrorMsg string
|
|
}{
|
|
{
|
|
title: "fetch all",
|
|
name: "test",
|
|
url: "URL",
|
|
dir: "DIRECTORY",
|
|
branches: []string{},
|
|
wantCmdArgs: `path/to/git -C DIRECTORY remote add test URL`,
|
|
},
|
|
{
|
|
title: "fetch specific branches only",
|
|
name: "test",
|
|
url: "URL",
|
|
dir: "DIRECTORY",
|
|
branches: []string{"trunk", "dev"},
|
|
wantCmdArgs: `path/to/git -C DIRECTORY remote add -t trunk -t dev test URL`,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.title, func(t *testing.T) {
|
|
cmd, cmdCtx := createCommandContext(t, tt.cmdExitStatus, tt.cmdStdout, tt.cmdStderr)
|
|
client := Client{
|
|
GitPath: "path/to/git",
|
|
RepoDir: tt.dir,
|
|
commandContext: cmdCtx,
|
|
}
|
|
_, err := client.AddRemote(context.Background(), tt.name, tt.url, tt.branches)
|
|
assert.Equal(t, tt.wantCmdArgs, strings.Join(cmd.Args[3:], " "))
|
|
assert.NoError(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func initRepo(t *testing.T, dir string) {
|
|
errBuf := &bytes.Buffer{}
|
|
inBuf := &bytes.Buffer{}
|
|
outBuf := &bytes.Buffer{}
|
|
client := Client{
|
|
RepoDir: dir,
|
|
Stderr: errBuf,
|
|
Stdin: inBuf,
|
|
Stdout: outBuf,
|
|
}
|
|
cmd, err := client.Command(context.Background(), []string{"init", "--quiet"}...)
|
|
assert.NoError(t, err)
|
|
_, err = cmd.Output()
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestHelperProcess(t *testing.T) {
|
|
if os.Getenv("GH_WANT_HELPER_PROCESS") != "1" {
|
|
return
|
|
}
|
|
if err := func(args []string) error {
|
|
fmt.Fprint(os.Stdout, os.Getenv("GH_HELPER_PROCESS_STDOUT"))
|
|
exitStatus := os.Getenv("GH_HELPER_PROCESS_EXIT_STATUS")
|
|
if exitStatus != "0" {
|
|
return errors.New("error")
|
|
}
|
|
return nil
|
|
}(os.Args[3:]); err != nil {
|
|
fmt.Fprint(os.Stderr, os.Getenv("GH_HELPER_PROCESS_STDERR"))
|
|
exitStatus := os.Getenv("GH_HELPER_PROCESS_EXIT_STATUS")
|
|
i, err := strconv.Atoi(exitStatus)
|
|
if err != nil {
|
|
os.Exit(1)
|
|
}
|
|
os.Exit(i)
|
|
}
|
|
os.Exit(0)
|
|
}
|
|
|
|
func createCommandContext(t *testing.T, exitStatus int, stdout, stderr string) (*exec.Cmd, commandCtx) {
|
|
cmd := exec.CommandContext(context.Background(), os.Args[0], "-test.run=TestHelperProcess", "--")
|
|
cmd.Env = []string{
|
|
"GH_WANT_HELPER_PROCESS=1",
|
|
fmt.Sprintf("GH_HELPER_PROCESS_STDOUT=%s", stdout),
|
|
fmt.Sprintf("GH_HELPER_PROCESS_STDERR=%s", stderr),
|
|
fmt.Sprintf("GH_HELPER_PROCESS_EXIT_STATUS=%v", exitStatus),
|
|
}
|
|
return cmd, func(ctx context.Context, exe string, args ...string) *exec.Cmd {
|
|
cmd.Args = append(cmd.Args, exe)
|
|
cmd.Args = append(cmd.Args, args...)
|
|
return cmd
|
|
}
|
|
}
|