introduce Songmu/gitconfig

This commit is contained in:
Songmu 2019-05-16 02:08:41 +09:00
parent 8f9989e7d9
commit 2f79e3b379
9 changed files with 50 additions and 260 deletions

View file

@ -11,8 +11,9 @@ import (
"strings"
"testing"
"github.com/Songmu/gitconfig"
"github.com/motemen/ghq/cmdutil"
"github.com/motemen/ghq/gitutil"
"github.com/urfave/cli"
)
@ -218,7 +219,7 @@ func TestDoRoot(t *testing.T) {
setup: func() func() {
orig := os.Getenv(ghqrootEnv)
os.Setenv(ghqrootEnv, "")
teardown := gitutil.WithConfig(t, `[ghq]
teardown := gitconfig.WithConfig(t, `[ghq]
root = /path/to/ghqroot11
root = /path/to/ghqroot12
`)
@ -232,17 +233,14 @@ func TestDoRoot(t *testing.T) {
}, {
name: "default home",
setup: func() func() {
origRoot := os.Getenv(ghqrootEnv)
os.Setenv(ghqrootEnv, "")
origGitconfig := os.Getenv("GIT_CONFIG")
os.Setenv("GIT_CONFIG", "/tmp/unknown-ghq-dummy")
origHome := os.Getenv("HOME")
os.Setenv("HOME", "/path/to/ghqhome")
restore1 := tmpEnv(ghqrootEnv, "")
restore2 := tmpEnv("GIT_CONFIG", "/tmp/unknown-ghq-dummy")
restore3 := tmpEnv("HOME", "/path/to/ghqhome")
return func() {
os.Setenv(ghqrootEnv, origRoot)
os.Setenv("GIT_CONFIG", origGitconfig)
os.Setenv("HOME", origHome)
restore1()
restore2()
restore3()
}
},
expect: "/path/to/ghqhome/.ghq\n",

View file

@ -1,88 +0,0 @@
package gitutil
import (
"fmt"
"os"
"os/exec"
"regexp"
"strings"
"syscall"
"github.com/blang/semver"
)
// ConfigSingle fetches single git-config variable.
// returns an empty string and no error if no variable is found with the given key.
func ConfigSingle(key string) (string, error) {
// --path option expands tilde(~)
return Config("--path", "--get", key)
}
// ConfigAll fetches git-config variable of multiple values.
func ConfigAll(key string) ([]string, error) {
value, err := Config("--path", "--get-all", key)
if err != nil {
return nil, err
}
// No results found, return an empty slice
if value == "" {
return nil, nil
}
return strings.Split(value, "\000"), nil
}
// Config invokes 'git config' and handles some errors properly.
func Config(args ...string) (string, error) {
gitArgs := append([]string{"config", "--null"}, args...)
cmd := exec.Command("git", gitArgs...)
cmd.Stderr = os.Stderr
buf, err := cmd.Output()
if exitError, ok := err.(*exec.ExitError); ok {
if waitStatus, ok := exitError.Sys().(syscall.WaitStatus); ok {
if waitStatus.ExitStatus() == 1 {
// The key was not found, do not treat as an error
return "", nil
}
}
return "", err
}
return strings.TrimRight(string(buf), "\000"), nil
}
var (
versionRx = regexp.MustCompile(`((?:\d+)\.(?:\d+)\.(?:\d+))`)
featureConfigURLMatchVersion = semver.MustParse("1.8.5")
)
// HasFeatureConfigURLMatch checks has url-match feature or not
func HasFeatureConfigURLMatch() error {
cmd := exec.Command("git", "--version")
buf, err := cmd.Output()
if err != nil {
return fmt.Errorf("failed to execute %q: %s", "git --version", err)
}
return gitVersionOutputSatisfies(string(buf), featureConfigURLMatchVersion)
}
func gitVersionOutputSatisfies(gitVersionOutput string, baseVersion semver.Version) error {
versionStrings := versionRx.FindStringSubmatch(gitVersionOutput)
if len(versionStrings) == 0 {
return fmt.Errorf("failed to detect git version from %q", gitVersionOutput)
}
ver, err := semver.Parse(versionStrings[1])
if err != nil {
return fmt.Errorf("failed to parse version string %q: %s", versionStrings[1], err)
}
if ver.LT(baseVersion) {
return fmt.Errorf("This version of Git does not support `config --get-urlmatch`; per-URL settings are not available")
}
return nil
}

View file

@ -1,97 +0,0 @@
package gitutil
import (
"fmt"
"os"
"strings"
"testing"
)
func TestConfigAll(t *testing.T) {
dummyKey := "ghq.non.existent.key"
confs, err := ConfigAll(dummyKey)
if err != nil {
t.Errorf("error should be nil but: %s", err)
}
if len(confs) > 0 {
t.Errorf("ConfigAll(%q) = %v; want %v", dummyKey, confs, nil)
}
}
func TestConfigURL(t *testing.T) {
if HasFeatureConfigURLMatch() != nil {
t.Skip("Git does not have config --get-urlmatch feature")
}
defer WithConfig(t, `[ghq "https://ghe.example.com/"]
vcs = github
[ghq "https://ghe.example.com/hg/"]
vcs = hg
`)()
testCases := []struct {
name string
config []string
expect string
}{{
name: "github",
config: []string{"--get-urlmatch", "ghq.vcs", "https://ghe.example.com/foo/bar"},
expect: "github",
}, {
name: "hg",
config: []string{"--get-urlmatch", "ghq.vcs", "https://ghe.example.com/hg/repo"},
expect: "hg",
}, {
name: "empty",
config: []string{"--get-urlmatch", "ghq.vcs", "https://example.com"},
expect: "",
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
value, err := Config(tc.config...)
if err != nil {
t.Errorf("error should be nil but: %s", err)
}
if value != tc.expect {
t.Errorf("got: %s, expect: %s", value, tc.expect)
}
})
}
}
func TestHasFeatureConfigURLMatch_err(t *testing.T) {
defer func(orig string) { os.Setenv("PATH", orig) }(os.Getenv("PATH"))
os.Setenv("PATH", "")
err := HasFeatureConfigURLMatch()
const wantSub = `failed to execute "git --version": `
if got := fmt.Sprint(err); !strings.HasPrefix(got, wantSub) {
t.Errorf("HasFeatureConfigURLMatch() error = %q; want substring %q", got, wantSub)
}
}
func TestGitVersionOutputSatisfies_err(t *testing.T) {
testCases := []struct {
in, wantSub string
}{{
in: "brahbrah",
wantSub: `failed to detect git version from "brahbrah"`,
}, {
in: "18446744073709551616.0.0",
wantSub: "failed to parse version string",
}, {
in: "1.8.4",
wantSub: "This version of Git does not support `config --get-urlmatch`; per-URL settings are not available",
}}
for _, tc := range testCases {
t.Run(tc.in, func(t *testing.T) {
err := gitVersionOutputSatisfies(tc.in, featureConfigURLMatchVersion)
if got := fmt.Sprint(err); !strings.HasPrefix(got, tc.wantSub) {
t.Errorf("gitVersionOutputSatisfies(%s, 1.8.5) error = %q; want substring %q",
tc.in, got, tc.wantSub)
}
})
}
}

View file

@ -1,32 +0,0 @@
package gitutil
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
)
// WithConfig is test helper to replace gitconfig temporarily
func WithConfig(t *testing.T, configContent string) func() {
tmpdir, err := ioutil.TempDir("", "ghq-test")
if err != nil {
t.Fatal(err)
}
tmpGitconfigFile := filepath.Join(tmpdir, "gitconfig")
ioutil.WriteFile(
tmpGitconfigFile,
[]byte(configContent),
0644,
)
prevGitConfigEnv := os.Getenv("GIT_CONFIG")
os.Setenv("GIT_CONFIG", tmpGitconfigFile)
return func() {
os.Setenv("GIT_CONFIG", prevGitConfigEnv)
os.RemoveAll(tmpdir)
}
}

View file

@ -114,3 +114,16 @@ func newTempDir(t *testing.T) string {
return tmpdir
}
func tmpEnv(key, val string) func() {
orig, ok := os.LookupEnv(key)
os.Setenv(key, val)
return func() {
if ok {
os.Setenv(key, orig)
} else {
os.Unsetenv(key)
}
}
}

View file

@ -9,7 +9,7 @@ import (
"sort"
"strings"
"github.com/motemen/ghq/gitutil"
"github.com/Songmu/gitconfig"
)
type LocalRepository struct {
@ -259,7 +259,8 @@ func localRepositoryRoots() ([]string, error) {
_localRepositoryRoots = filepath.SplitList(envRoot)
} else {
var err error
if _localRepositoryRoots, err = gitutil.ConfigAll("ghq.root"); err != nil {
_localRepositoryRoots, err = gitconfig.PathAll("ghq.root")
if err != nil && !gitconfig.IsNotFound(err) {
return nil, err
}
}

View file

@ -5,8 +5,9 @@ import (
"net/url"
"strings"
"github.com/Songmu/gitconfig"
"github.com/motemen/ghq/cmdutil"
"github.com/motemen/ghq/gitutil"
"github.com/motemen/ghq/logger"
)
@ -101,20 +102,16 @@ func (repo *OtherRepository) IsValid() bool {
}
func (repo *OtherRepository) VCS() (*VCSBackend, *url.URL) {
if err := gitutil.HasFeatureConfigURLMatch(); err != nil {
logger.Log("warning", err.Error())
} else {
// Respect 'ghq.url.https://ghe.example.com/.vcs' config variable
// (in gitconfig:)
// [ghq "https://ghe.example.com/"]
// vcs = github
vcs, err := gitutil.Config("--path", "--get-urlmatch", "ghq.vcs", repo.URL().String())
if err != nil {
logger.Log("error", err.Error())
}
if backend, ok := vcsRegistry[vcs]; ok {
return backend, repo.URL()
}
// Respect 'ghq.url.https://ghe.example.com/.vcs' config variable
// (in gitconfig:)
// [ghq "https://ghe.example.com/"]
// vcs = github
vcs, err := gitconfig.Do("--path", "--get-urlmatch", "ghq.vcs", repo.URL().String())
if err != nil && !gitconfig.IsNotFound(err) {
logger.Log("error", err.Error())
}
if backend, ok := vcsRegistry[vcs]; ok {
return backend, repo.URL()
}
// Detect VCS backend automatically
@ -152,9 +149,8 @@ func NewRemoteRepository(url *url.URL) (RemoteRepository, error) {
return &DarksHubRepository{url}, nil
}
gheHosts, err := gitutil.ConfigAll("ghq.ghe.host")
if err != nil {
gheHosts, err := gitconfig.GetAll("ghq.ghe.host")
if err != nil && !gitconfig.IsNotFound(err) {
return nil, fmt.Errorf("failed to retrieve GH:E hostname from .gitconfig: %s", err)
}

12
url.go
View file

@ -8,7 +8,7 @@ import (
"runtime"
"strings"
"github.com/motemen/ghq/gitutil"
"github.com/Songmu/gitconfig"
)
// Convert SCP-like URL to SSH URL(e.g. [user@]host.xz:path/to/repo.git/)
@ -74,16 +74,16 @@ func convertGitURLHTTPToSSH(url *url.URL) (*url.URL, error) {
}
func fillUsernameToPath(path string) (string, error) {
completeUser, err := gitutil.Config("--bool", "--get", "ghq.completeUser")
if err != nil {
completeUser, err := gitconfig.Bool("ghq.completeUser")
if err != nil && !gitconfig.IsNotFound(err) {
return path, err
}
if completeUser == "false" {
if err == nil && !completeUser {
return path + "/" + path, nil
}
user, err := gitutil.ConfigSingle("ghq.user")
if err != nil {
user, err := gitconfig.Get("ghq.user")
if err != nil && !gitconfig.IsNotFound(err) {
return path, err
}
if user == "" {

View file

@ -7,7 +7,7 @@ import (
"strings"
"testing"
"github.com/motemen/ghq/gitutil"
"github.com/Songmu/gitconfig"
"golang.org/x/xerrors"
)
@ -64,7 +64,7 @@ func TestNewURL(t *testing.T) {
}, {
name: "same name repository",
setup: func() func() {
return gitutil.WithConfig(t, `[ghq]
return gitconfig.WithConfig(t, `[ghq]
completeUser = false`)
},
url: "peco",
@ -129,7 +129,7 @@ func TestNewURL_err(t *testing.T) {
if got := fmt.Sprint(err); !strings.Contains(got, wantSub) {
t.Errorf("newURL(%q) error = %q; want substring %q", invalidURL, got, wantSub)
}
defer gitutil.WithConfig(t, `[[[`)()
defer gitconfig.WithConfig(t, `[[[`)()
var exitError *exec.ExitError
_, err = newURL("peco")
@ -140,10 +140,9 @@ func TestNewURL_err(t *testing.T) {
func TestFillUsernameToPath_err(t *testing.T) {
for _, envStr := range []string{"GITHUB_USER", "USER", "USERNAME"} {
defer func(orig string) { os.Setenv(envStr, orig) }(os.Getenv(envStr))
os.Setenv(envStr, "")
defer tmpEnv(envStr, "")()
}
defer gitutil.WithConfig(t, "")()
defer gitconfig.WithConfig(t, "")()
_, err := fillUsernameToPath("peco")
const wantSub = "set ghq.user to your gitconfig"