6
0
mirror of https://github.com/grdl/git-get.git synced 2026-02-11 16:58:59 +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. -h, --help Print this help and exit.
-t, --host Host to use when <REPO> doesn't have a specified host. (default "github.com") -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") -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. -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. 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: Here's an example of a working snippet from `.gitconfig` file:
``` ```
[gitget] [gitget]
root = /path/to/my/repos root = /path/to/my/repos
host = gitlab.com 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.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.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().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().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("help", "h", false, "Print this help and exit.")
cmd.PersistentFlags().BoolP("version", "v", false, "Print version 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.KeyDefaultHost, cmd.PersistentFlags().Lookup(cfg.KeyDefaultHost))
viper.BindPFlag(cfg.KeyDump, cmd.PersistentFlags().Lookup(cfg.KeyDump)) viper.BindPFlag(cfg.KeyDump, cmd.PersistentFlags().Lookup(cfg.KeyDump))
viper.BindPFlag(cfg.KeyReposRoot, cmd.PersistentFlags().Lookup(cfg.KeyReposRoot)) viper.BindPFlag(cfg.KeyReposRoot, cmd.PersistentFlags().Lookup(cfg.KeyReposRoot))
viper.BindPFlag(cfg.KeySkipHost, cmd.PersistentFlags().Lookup(cfg.KeySkipHost))
cfg.Init(&git.ConfigGlobal{}) cfg.Init(&git.ConfigGlobal{})
} }
@@ -49,11 +51,12 @@ func run(cmd *cobra.Command, args []string) error {
cfg.Expand(cfg.KeyReposRoot) cfg.Expand(cfg.KeyReposRoot)
config := &pkg.GetCfg{ config := &pkg.GetCfg{
Branch: viper.GetString(cfg.KeyBranch), Branch: viper.GetString(cfg.KeyBranch),
DefHost: viper.GetString(cfg.KeyDefaultHost), DefHost: viper.GetString(cfg.KeyDefaultHost),
Dump: viper.GetString(cfg.KeyDump), Dump: viper.GetString(cfg.KeyDump),
Root: viper.GetString(cfg.KeyReposRoot), SkipHost: viper.GetBool(cfg.KeySkipHost),
URL: url, Root: viper.GetString(cfg.KeyReposRoot),
URL: url,
} }
return pkg.Get(config) return pkg.Get(config)
} }

View File

@@ -22,6 +22,7 @@ var (
KeyDefaultHost = "host" KeyDefaultHost = "host"
KeyFetch = "fetch" KeyFetch = "fetch"
KeyOutput = "out" KeyOutput = "out"
KeySkipHost = "skip-host"
KeyReposRoot = "root" KeyReposRoot = "root"
) )
@@ -30,6 +31,7 @@ var Defaults = map[string]string{
KeyDefaultHost: "github.com", KeyDefaultHost: "github.com",
KeyOutput: OutTree, KeyOutput: OutTree,
KeyReposRoot: fmt.Sprintf("~%c%s", filepath.Separator, "repositories"), KeyReposRoot: fmt.Sprintf("~%c%s", filepath.Separator, "repositories"),
// KeySkipHost: "false",
} }
// Values for the --out flag. // Values for the --out flag.
@@ -78,6 +80,7 @@ func Init(cfg Gitconfig) {
func readGitconfig(cfg Gitconfig) { func readGitconfig(cfg Gitconfig) {
var lines []string var lines []string
// TODO: Can we somehow iterate over all possible flags?
for key := range Defaults { for key := range Defaults {
if val := cfg.Get(fmt.Sprintf("%s.%s", GitgetPrefix, key)); val != "" { if val := cfg.Get(fmt.Sprintf("%s.%s", GitgetPrefix, key)); val != "" {
lines = append(lines, fmt.Sprintf("%s=%s", key, val)) lines = append(lines, fmt.Sprintf("%s=%s", key, val))
@@ -86,6 +89,11 @@ func readGitconfig(cfg Gitconfig) {
viper.SetConfigType("env") viper.SetConfigType("env")
viper.ReadConfig(bytes.NewBuffer([]byte(strings.Join(lines, "\n")))) 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. // 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. // GetCfg provides configuration for the Get command.
type GetCfg struct { type GetCfg struct {
Branch string Branch string
DefHost string DefHost string
Dump string Dump string
Root string Root string
URL string SkipHost bool
URL string
} }
// Get executes the "git get" command. // Get executes the "git get" command.
@@ -40,7 +41,7 @@ func cloneSingleRepo(c *GetCfg) error {
opts := &git.CloneOpts{ opts := &git.CloneOpts{
URL: url, URL: url,
Path: filepath.Join(c.Root, URLToPath(url)), Path: filepath.Join(c.Root, URLToPath(*url, c.SkipHost)),
Branch: c.Branch, Branch: c.Branch,
} }
@@ -63,7 +64,7 @@ func cloneDumpFile(c *GetCfg) error {
opts := &git.CloneOpts{ opts := &git.CloneOpts{
URL: url, URL: url,
Path: filepath.Join(c.Root, URLToPath(url)), Path: filepath.Join(c.Root, URLToPath(*url, c.SkipHost)),
Branch: line.branch, Branch: line.branch,
} }

View File

@@ -2,6 +2,7 @@ package pkg
import ( import (
urlpkg "net/url" urlpkg "net/url"
"path"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings" "strings"
@@ -52,6 +53,12 @@ func ParseURL(rawURL string, defaultHost string) (url *urlpkg.URL, err error) {
url.Host = defaultHost 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 // Default to https when scheme is empty
if url.Scheme == "" { if url.Scheme == "" {
url.Scheme = "https" url.Scheme = "https"
@@ -60,18 +67,30 @@ func ParseURL(rawURL string, defaultHost string) (url *urlpkg.URL, err error) {
return url, nil 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 // 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 // If skipHost is true, it removes the host part from the path.
repoHost := strings.Split(url.Host, ":")[0] // 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 // Remove tilde (~) char from username.
repoPath = filepath.Join(repoHost, url.Path) url.Path = strings.ReplaceAll(url.Path, "~", "")
repoPath = strings.TrimSuffix(repoPath, ".git")
// Remove tilde (~) char from username // Remove leading and trailing slashes (correct separator is added by the filepath.Join() below).
repoPath = strings.ReplaceAll(repoPath, "~", "") 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 { for _, test := range tests {
url, err := ParseURL(test.in, cfg.Defaults[cfg.KeyDefaultHost]) url, err := ParseURL(test.in, cfg.Defaults[cfg.KeyDefaultHost])
if err != nil { 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 { 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", //"git@github.com:1234:grdl/git-get.git",
} }
for _, in := range invalidURLs { for _, test := range invalidURLs {
got, err := ParseURL(in, cfg.Defaults[cfg.KeyDefaultHost]) got, err := ParseURL(test, cfg.Defaults[cfg.KeyDefaultHost])
if err == nil { 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)
} }
} }
} }