mirror of
https://github.com/x-motemen/ghq.git
synced 2026-09-10 07:26:27 -04:00
introduce Songmu/gitconfig
This commit is contained in:
parent
8f9989e7d9
commit
2f79e3b379
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
12
url.go
|
|
@ -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 == "" {
|
||||
|
|
|
|||
11
url_test.go
11
url_test.go
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in a new issue