6
0
mirror of https://github.com/grdl/git-get.git synced 2026-02-04 22:04:41 +00:00

Add --skip-host flag to get command (#7)

When set, git-get won't create a directory for the repo host.
So instead of `<root>/<host>/<user>/<repo>`, a repo will be cloned into `<root>/<user>/<repo>`.
It's useful if all repos some from the same host and that additional folder feels redundant.
This commit is contained in:
Grzegorz Dlugoszewski
2020-07-07 18:35:30 +02:00
committed by GitHub
parent 0097681f89
commit 0064fc3b8e
6 changed files with 108 additions and 28 deletions

View File

@@ -67,6 +67,7 @@ Flags:
-h, --help Print this help and exit.
-t, --host Host to use when <REPO> doesn't have a specified host. (default "github.com")
-r, --root Path to repos root where repositories are cloned. (default "~/repositories")
-s, --skip-host Don't create a directory for host.
-v, --version Print version and exit.
```
@@ -143,11 +144,14 @@ export GITGET_ROOT=/path/to/my/repos
You can define a `[gitget]` section inside your global `.gitconfig` file and set the configuration flags there. A recommended pattern is to set `root` and `host` variables there if you don't want to use the defaults.
If all of your repos come from the same host and you find creating directory for it redundant, you can use the `skip-host` flag to skip creating it.
Here's an example of a working snippet from `.gitconfig` file:
```
[gitget]
root = /path/to/my/repos
host = gitlab.com
skip-host = true
```

View File

@@ -28,6 +28,7 @@ func init() {
cmd.PersistentFlags().StringP(cfg.KeyBranch, "b", "", "Branch (or tag) to checkout after cloning.")
cmd.PersistentFlags().StringP(cfg.KeyDefaultHost, "t", cfg.Defaults[cfg.KeyDefaultHost], "Host to use when <REPO> doesn't have a specified host.")
cmd.PersistentFlags().StringP(cfg.KeyDump, "d", "", "Path to a dump file listing repos to clone. Ignored when <REPO> argument is used.")
cmd.PersistentFlags().BoolP(cfg.KeySkipHost, "s", false, "Don't create a directory for host.")
cmd.PersistentFlags().StringP(cfg.KeyReposRoot, "r", cfg.Defaults[cfg.KeyReposRoot], "Path to repos root where repositories are cloned.")
cmd.PersistentFlags().BoolP("help", "h", false, "Print this help and exit.")
cmd.PersistentFlags().BoolP("version", "v", false, "Print version and exit.")
@@ -36,6 +37,7 @@ func init() {
viper.BindPFlag(cfg.KeyDefaultHost, cmd.PersistentFlags().Lookup(cfg.KeyDefaultHost))
viper.BindPFlag(cfg.KeyDump, cmd.PersistentFlags().Lookup(cfg.KeyDump))
viper.BindPFlag(cfg.KeyReposRoot, cmd.PersistentFlags().Lookup(cfg.KeyReposRoot))
viper.BindPFlag(cfg.KeySkipHost, cmd.PersistentFlags().Lookup(cfg.KeySkipHost))
cfg.Init(&git.ConfigGlobal{})
}
@@ -49,11 +51,12 @@ func run(cmd *cobra.Command, args []string) error {
cfg.Expand(cfg.KeyReposRoot)
config := &pkg.GetCfg{
Branch: viper.GetString(cfg.KeyBranch),
DefHost: viper.GetString(cfg.KeyDefaultHost),
Dump: viper.GetString(cfg.KeyDump),
Root: viper.GetString(cfg.KeyReposRoot),
URL: url,
Branch: viper.GetString(cfg.KeyBranch),
DefHost: viper.GetString(cfg.KeyDefaultHost),
Dump: viper.GetString(cfg.KeyDump),
SkipHost: viper.GetBool(cfg.KeySkipHost),
Root: viper.GetString(cfg.KeyReposRoot),
URL: url,
}
return pkg.Get(config)
}

View File

@@ -22,6 +22,7 @@ var (
KeyDefaultHost = "host"
KeyFetch = "fetch"
KeyOutput = "out"
KeySkipHost = "skip-host"
KeyReposRoot = "root"
)
@@ -30,6 +31,7 @@ var Defaults = map[string]string{
KeyDefaultHost: "github.com",
KeyOutput: OutTree,
KeyReposRoot: fmt.Sprintf("~%c%s", filepath.Separator, "repositories"),
// KeySkipHost: "false",
}
// Values for the --out flag.
@@ -78,6 +80,7 @@ func Init(cfg Gitconfig) {
func readGitconfig(cfg Gitconfig) {
var lines []string
// TODO: Can we somehow iterate over all possible flags?
for key := range Defaults {
if val := cfg.Get(fmt.Sprintf("%s.%s", GitgetPrefix, key)); val != "" {
lines = append(lines, fmt.Sprintf("%s=%s", key, val))
@@ -86,6 +89,11 @@ func readGitconfig(cfg Gitconfig) {
viper.SetConfigType("env")
viper.ReadConfig(bytes.NewBuffer([]byte(strings.Join(lines, "\n"))))
// TODO: A hacky way to read boolean flag from gitconfig. Find a cleaner way.
if val := cfg.Get(fmt.Sprintf("%s.%s", GitgetPrefix, KeySkipHost)); strings.ToLower(val) == "true" {
viper.Set(KeySkipHost, true)
}
}
// Expand applies the variables expansion to a viper config of given key.

View File

@@ -9,11 +9,12 @@ import (
// GetCfg provides configuration for the Get command.
type GetCfg struct {
Branch string
DefHost string
Dump string
Root string
URL string
Branch string
DefHost string
Dump string
Root string
SkipHost bool
URL string
}
// Get executes the "git get" command.
@@ -40,7 +41,7 @@ func cloneSingleRepo(c *GetCfg) error {
opts := &git.CloneOpts{
URL: url,
Path: filepath.Join(c.Root, URLToPath(url)),
Path: filepath.Join(c.Root, URLToPath(*url, c.SkipHost)),
Branch: c.Branch,
}
@@ -63,7 +64,7 @@ func cloneDumpFile(c *GetCfg) error {
opts := &git.CloneOpts{
URL: url,
Path: filepath.Join(c.Root, URLToPath(url)),
Path: filepath.Join(c.Root, URLToPath(*url, c.SkipHost)),
Branch: line.branch,
}

View File

@@ -2,6 +2,7 @@ package pkg
import (
urlpkg "net/url"
"path"
"path/filepath"
"regexp"
"strings"
@@ -52,6 +53,12 @@ func ParseURL(rawURL string, defaultHost string) (url *urlpkg.URL, err error) {
url.Host = defaultHost
}
// Don't use host when scheme is file://. The fragment detected as url.Host should be a first directory of url.Path
if url.Scheme == "file" && url.Host != "" {
url.Path = path.Join(url.Host, url.Path)
url.Host = ""
}
// Default to https when scheme is empty
if url.Scheme == "" {
url.Scheme = "https"
@@ -60,18 +67,30 @@ func ParseURL(rawURL string, defaultHost string) (url *urlpkg.URL, err error) {
return url, nil
}
// URLToPath cleans up the URL and converts it into a path string.
// URLToPath cleans up the URL and converts it into a path string with correct separators for the current OS.
// Eg, ssh://git@github.com:22/~user/repo.git => github.com/user/repo
func URLToPath(url *urlpkg.URL) (repoPath string) {
// Remove port numbers from host
repoHost := strings.Split(url.Host, ":")[0]
//
// If skipHost is true, it removes the host part from the path.
// Eg, ssh://git@github.com:22/~user/repo.git => user/repo
func URLToPath(url urlpkg.URL, skipHost bool) string {
// Remove port numbers from host.
url.Host = strings.Split(url.Host, ":")[0]
// Remove trailing ".git" from repo name
repoPath = filepath.Join(repoHost, url.Path)
repoPath = strings.TrimSuffix(repoPath, ".git")
// Remove tilde (~) char from username.
url.Path = strings.ReplaceAll(url.Path, "~", "")
// Remove tilde (~) char from username
repoPath = strings.ReplaceAll(repoPath, "~", "")
// Remove leading and trailing slashes (correct separator is added by the filepath.Join() below).
url.Path = strings.Trim(url.Path, "/")
return repoPath
// Remove trailing ".git" from repo name.
url.Path = strings.TrimSuffix(url.Path, ".git")
// Replace slashes with separator correct for the current OS.
url.Path = strings.ReplaceAll(url.Path, "/", string(filepath.Separator))
if skipHost {
url.Host = ""
}
return filepath.Join(url.Host, url.Path)
}

View File

@@ -52,13 +52,58 @@ func TestURLParse(t *testing.T) {
for _, test := range tests {
url, err := ParseURL(test.in, cfg.Defaults[cfg.KeyDefaultHost])
if err != nil {
t.Errorf("Error parsing Path: %+v", err)
t.Fatalf("got error: %+v", err)
}
got := URLToPath(url)
got := URLToPath(*url, false)
if got != test.want {
t.Errorf("Wrong result of parsing Path: %s, got: %s; wantBranch: %s", test.in, got, test.want)
t.Errorf("wrong result for %q; expected %q; got %q", test.in, test.want, got)
}
}
}
func TestURLParseSkipHost(t *testing.T) {
tests := []struct {
in string
want string
}{
{"ssh://github.com/grdl/git-get.git", "grdl/git-get"},
{"ssh://user@github.com/grdl/git-get.git", "grdl/git-get"},
{"ssh://user@github.com:1234/grdl/git-get.git", "grdl/git-get"},
{"ssh://user@github.com/~user/grdl/git-get.git", "user/grdl/git-get"},
{"git+ssh://github.com/grdl/git-get.git", "grdl/git-get"},
{"git@github.com:grdl/git-get.git", "grdl/git-get"},
{"git@github.com:/~user/grdl/git-get.git", "user/grdl/git-get"},
{"git://github.com/grdl/git-get.git", "grdl/git-get"},
{"git://github.com/~user/grdl/git-get.git", "user/grdl/git-get"},
{"https://github.com/grdl/git-get.git", "grdl/git-get"},
{"http://github.com/grdl/git-get.git", "grdl/git-get"},
{"https://github.com/grdl/git-get", "grdl/git-get"},
{"https://github.com/git-get.git", "git-get"},
{"https://github.com/git-get", "git-get"},
{"https://github.com/grdl/sub/path/git-get.git", "grdl/sub/path/git-get"},
{"https://github.com:1234/grdl/git-get.git", "grdl/git-get"},
{"https://github.com/grdl/git-get.git/", "grdl/git-get"},
{"https://github.com/grdl/git-get/", "grdl/git-get"},
{"https://github.com/grdl/git-get/////", "grdl/git-get"},
{"https://github.com/grdl/git-get.git/////", "grdl/git-get"},
{"ftp://github.com/grdl/git-get.git", "grdl/git-get"},
{"ftps://github.com/grdl/git-get.git", "grdl/git-get"},
{"rsync://github.com/grdl/git-get.git", "grdl/git-get"},
{"local/grdl/git-get/", "local/grdl/git-get"},
{"file://local/grdl/git-get", "local/grdl/git-get"},
}
for _, test := range tests {
url, err := ParseURL(test.in, cfg.Defaults[cfg.KeyDefaultHost])
if err != nil {
t.Fatalf("got error: %+v", err)
}
got := URLToPath(*url, true)
if got != test.want {
t.Errorf("wrong result for %q; expected %q; got %q", test.in, test.want, got)
}
}
}
@@ -73,10 +118,10 @@ func TestInvalidURLParse(t *testing.T) {
//"git@github.com:1234:grdl/git-get.git",
}
for _, in := range invalidURLs {
got, err := ParseURL(in, cfg.Defaults[cfg.KeyDefaultHost])
for _, test := range invalidURLs {
got, err := ParseURL(test, cfg.Defaults[cfg.KeyDefaultHost])
if err == nil {
t.Errorf("Wrong result of parsing invalid Path: %s, got: %s, wantBranch: error", in, got)
t.Errorf("expected error; got %q", got)
}
}
}