-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmain.go
127 lines (106 loc) · 3.32 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// Command git-get clones Git repositories with an implicitly relative URL
// and always to a path under source root regardless of working directory.
package main
import (
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/mitchellh/go-homedir"
)
const (
// sourceRoot is the target prefix where we clone to,
// can be overridden with environment variable GIT_GET_ROOT.
defaultTargetPath = "~/src"
// defaultPrefix is prefixed to implicitly relative clone URLs,
// can be overriden with environment variable GIT_GET_PREFIX.
defaultPrefix = "[email protected]:"
)
const usage = `
Usage: git-get (URL|PROJECT/REPOSITORY)
$ git get joneskoo/git-get # PROJECT/REPOSITORY
$ git get [email protected]:joneskoo/git-get # URL
Regardless of working directory where git get is executed, this expands to:
$ git clone [email protected]:joneskoo/git-get ~/src/github.com/joneskoo/git-get
This allows easy cloning of repositories into an uniform directory structure.
`
func main() {
logger := log.New(os.Stderr, "git-get: ", 0)
if len(os.Args) != 2 {
logger.Fatalln(usage)
}
relativeCloneURL := os.Args[1]
targetPath := defaultTargetPath
if s := os.Getenv("GIT_GET_ROOT"); s != "" {
targetPath = s
}
var err error
targetPath, err = homedir.Expand(targetPath)
if err != nil {
logger.Fatalf("failed to expand target path: %v", err)
}
prefix := defaultPrefix
if s := os.Getenv("GIT_GET_PREFIX"); s != "" {
prefix = s
}
cloneURL := expand(relativeCloneURL, prefix)
td, err := targetDir(cloneURL)
if err != nil {
logger.Fatal(err)
}
// Replace current process with git
cmd := exec.Command("git", "clone", cloneURL, filepath.Join(targetPath, td))
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
err = cmd.Run()
if ee, ok := err.(*exec.ExitError); ok {
os.Exit(ee.ExitCode())
}
if err != nil {
logger.Fatalf("calling git failed: %v", err)
}
}
// expand completes the implicitly relative clone URL s of form "project/repo" into
// absolute clone URL.
//
// If s does not have the required form, it is returned unchanged.
// unmodified.
func expand(s, defaultPrefix string) string {
if strings.Contains(s, ":") {
return s
}
parts := strings.SplitN(s, "/", 2)
if len(parts) < 2 {
return s
}
return defaultPrefix + parts[0] + "/" + parts[1] + ".git"
}
// targetDir resolves the cloneURL to a relative directory path.
func targetDir(cloneURL string) (string, error) {
cleanedCloneURL := strings.TrimSuffix(cloneURL, ".git")
var hostname, path string
// URLs like https:// and ssh://
if parts := strings.SplitN(cleanedCloneURL, "://", 2); len(parts) == 2 {
if addressParts := strings.SplitN(parts[1], "/", 2); len(addressParts) == 2 {
hostname = addressParts[0]
path = addressParts[1]
} else {
return "", fmt.Errorf(`expected path in URL, got %q`, cloneURL)
}
// URLs like user@hostname:project/repo
} else if parts := strings.SplitN(cleanedCloneURL, ":", 2); len(parts) == 2 {
hostname = parts[0]
path = parts[1]
} else {
return "", fmt.Errorf(`expected PROJECT/REPO or absolute git clone URL, got %q`, cloneURL)
}
// ignore username
parts := strings.Split(hostname, "@")
hostname = parts[len(parts)-1]
pathparts := strings.Split(path, "/")
target := append([]string{hostname}, pathparts...)
return strings.ToLower(filepath.Join(target...)), nil
}