github.com/Benchkram/bob@v0.0.0-20220321080157-7c8f3876e225/bob/repo_provider.go (about) 1 package bob 2 3 import ( 4 "fmt" 5 "net/url" 6 "path" 7 "path/filepath" 8 "strings" 9 10 giturls "github.com/whilp/git-urls" 11 12 "github.com/Benchkram/errz" 13 ) 14 15 var generalGitProvider = GitProvider{Name: "general"} 16 17 // special providers with non trival to parse urls 18 var azureGitProvider = GitProvider{Name: "azure", Domain: "dev.azure.com"} 19 var localGitProvider = GitProvider{Name: "file://"} 20 21 var parsers = NewParsers() 22 23 type GitProvider struct { 24 Name string 25 Domain string 26 27 Parse Parser 28 } 29 30 type GitRepo struct { 31 Provider GitProvider 32 33 // SSH stores git@ 34 SSH *GitURL 35 // HTTPS stores https:// 36 HTTPS *GitURL 37 // Local stores file:// 38 Local string 39 } 40 41 // Name of the repository, usually part 42 // after the last "/". 43 func (gr *GitRepo) Name() string { 44 45 if gr.SSH != nil && gr.SSH.URL != nil { 46 return Name(gr.SSH.URL.String()) 47 } 48 if gr.HTTPS != nil && gr.HTTPS.URL != nil { 49 return Name(gr.HTTPS.URL.String()) 50 } 51 if gr.Local != "" { 52 return Name(gr.Local) 53 } 54 55 return "" 56 57 } 58 59 // RepoName returns the base part of the repo 60 // as the name. Suffix excluded. 61 func Name(repoURL string) (name string) { 62 63 base := path.Base(repoURL) 64 ext := path.Ext(repoURL) 65 66 name = base 67 if ext == ".git" { 68 name = strings.TrimSuffix(base, ext) 69 } 70 71 return name 72 } 73 74 type Parsers map[string]Parser 75 76 func NewParsers() Parsers { 77 providers := make(map[string]Parser) 78 providers[azureGitProvider.Name] = ParseAzure 79 providers[localGitProvider.Name] = ParseLocal 80 return providers 81 } 82 83 type Parser func(string) (*GitRepo, error) 84 85 // Parse a rawurl and return a GitRepo object containing 86 // the specific https and ssh protocol urls. 87 func Parse(rawurl string) (repo *GitRepo, err error) { 88 for _, parser := range parsers { 89 repo, err := parser(rawurl) 90 if err == nil { 91 return repo, nil 92 } 93 } 94 return ParseGeneral(rawurl) 95 } 96 97 // ParseAzure parses a git repo url and return the corresponding git & https protocol links 98 // corresponding to azure-devops domain specifications. 99 // https://xxx@dev.azure.com/xxx/Yyy/_git/zzz.zzz.zzz", 100 // git@ssh.dev.azure.com:v3/xxx/Yyy/zzz.zzz.zzz", 101 func ParseAzure(rawurl string) (repo *GitRepo, err error) { 102 defer errz.Recover(&err) 103 104 if !strings.Contains(rawurl, azureGitProvider.Name) { 105 return nil, fmt.Errorf("Could not parse %s as %s-repo", rawurl, azureGitProvider.Name) 106 } 107 108 repo = &GitRepo{ 109 Provider: azureGitProvider, 110 } 111 112 u, err := giturls.Parse(rawurl) 113 errz.Fatal(err) 114 115 // Construct git url from https rawurl 116 if strings.Contains(rawurl, "https") && strings.Contains(rawurl, "_git") { 117 // Detected a http input url 118 path := strings.Replace(u.Path, "/_git/", "/", 1) 119 ssh := &url.URL{ 120 Host: "ssh." + u.Host + ":v3", 121 Path: path, 122 User: url.User("git"), 123 } 124 125 repo.HTTPS = FromURL(u) 126 repo.SSH = FromURL(ssh) 127 128 return repo, nil 129 } 130 131 // Construct https url from git rawurl 132 if strings.Contains(rawurl, "git@") { 133 // Detected a http input url 134 host := strings.TrimPrefix(u.Host, "ssh.") 135 136 // Trim `v3` and add `_git` in path 137 // /v3/xxx/xxx/base => /xxx/xxx/_git/base 138 path := strings.TrimPrefix(u.Path, "v3") 139 base := filepath.Base(path) 140 path = strings.TrimSuffix(path, base) 141 path = filepath.Join(path, "_git", base) 142 143 // Username is the first part of path. 144 // Path starts with `/` => username is on splits[1]. 145 splits := strings.Split(path, "/") 146 var user string 147 if len(splits) > 1 { 148 user = splits[1] 149 } 150 151 https := &url.URL{ 152 Scheme: "https", 153 Host: host, 154 Path: path, 155 User: url.User(user), 156 } 157 158 // Adjust ssh url 159 u.Scheme = "" 160 u.Path = strings.TrimPrefix(u.Path, "v3") 161 u.Host = u.Host + ":v3" 162 163 repo.HTTPS = FromURL(https) 164 repo.SSH = FromURL(u) 165 166 return repo, nil 167 } 168 169 return nil, fmt.Errorf("Could not detect a valid %s url", azureGitProvider.Name) 170 } 171 172 // ParseGeneral parses a git repo url and return the corresponding git & https protocol links 173 // corresponding to most (github, gitlab) domain specifications. 174 // 175 // github 176 // https://github.com/Benchkram/bob.git 177 // git@github.com:Benchkram/bob.git 178 // gitlab 179 // git@gitlab.com:gitlab-org/gitlab.git 180 // https://gitlab.com/gitlab-org/gitlab.git 181 // 182 func ParseGeneral(rawurl string) (repo *GitRepo, err error) { 183 defer errz.Recover(&err) 184 repo = &GitRepo{ 185 Provider: generalGitProvider, 186 } 187 188 u, err := giturls.Parse(rawurl) 189 errz.Fatal(err) 190 191 // Construct git url from https rawurl 192 if strings.Contains(rawurl, "https") { 193 // Username is the first part of path. 194 // Path starts with `/` => username is on splits[1]. 195 splits := strings.Split(u.Path, "/") 196 var user string 197 if len(splits) > 1 { 198 user = splits[1] 199 } 200 201 host := u.Host + ":" + user 202 path := strings.TrimPrefix(u.Path, "/"+user) 203 204 ssh := &url.URL{ 205 Host: host, 206 Path: path, 207 User: url.User("git"), 208 } 209 210 repo.HTTPS = FromURL(u) 211 repo.SSH = FromURL(ssh) 212 213 return repo, nil 214 } 215 216 // Construct https url from git rawurl 217 if strings.Contains(rawurl, "git@") { 218 https := &url.URL{ 219 Scheme: "https", 220 Host: u.Host, 221 Path: u.Path, 222 } 223 224 // Username is the first part of path. 225 splits := strings.Split(u.Path, "/") 226 var user string 227 if len(splits) > 0 { 228 user = splits[0] 229 } 230 231 host := u.Host + ":" + user 232 path := strings.TrimPrefix(u.Path, user) 233 234 // Adjust ssh url 235 u.Scheme = "" 236 u.Host = host 237 u.Path = path 238 239 repo.HTTPS = FromURL(https) 240 repo.SSH = FromURL(u) 241 242 return repo, nil 243 } 244 245 return nil, ErrInvalidGitUrl 246 } 247 248 func ParseLocal(rawurl string) (repo *GitRepo, err error) { 249 defer errz.Recover(&err) 250 if !strings.Contains(rawurl, localGitProvider.Name) { 251 return nil, fmt.Errorf("Could not parse %s as %s-repo", rawurl, localGitProvider.Name) 252 } 253 254 repo = &GitRepo{ 255 Provider: localGitProvider, 256 } 257 258 // u, err := giturls.Parse(rawurl) 259 // errz.Fatal(err) 260 261 // repo.HTTPS = FromURL(u) 262 // repo.SSH = FromURL(u) 263 repo.Local = strings.TrimPrefix(rawurl, "file://") 264 265 return repo, nil 266 } 267 268 // GitURL overlays `url.URL` to handle git clone urls correctly. 269 type GitURL struct { 270 URL *url.URL 271 } 272 273 func FromURL(u *url.URL) *GitURL { 274 return &GitURL{URL: u} 275 } 276 func (g *GitURL) String() string { 277 if g.URL.Scheme == "" { 278 return strings.TrimPrefix(g.URL.String(), "//") 279 } 280 return g.URL.String() 281 }