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  }