github.com/x-motemen/ghq@v1.6.1/remote_repository.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"net/url"
     6  	"regexp"
     7  	"strings"
     8  
     9  	"github.com/Songmu/gitconfig"
    10  	"github.com/x-motemen/ghq/cmdutil"
    11  	"github.com/x-motemen/ghq/logger"
    12  )
    13  
    14  // A RemoteRepository represents a remote repository.
    15  type RemoteRepository interface {
    16  	// URL returns the repository URL.
    17  	URL() *url.URL
    18  	// IsValid checks if the URL is valid.
    19  	IsValid() bool
    20  	// VCS returns the VCS backend that hosts the repository.
    21  	VCS() (*VCSBackend, *url.URL, error)
    22  }
    23  
    24  // A GitHubRepository represents a GitHub repository. Implements RemoteRepository.
    25  type GitHubRepository struct {
    26  	url *url.URL
    27  }
    28  
    29  // URL returns URL of the repository
    30  func (repo *GitHubRepository) URL() *url.URL {
    31  	return repo.url
    32  }
    33  
    34  // IsValid determine if the repository is valid or not
    35  func (repo *GitHubRepository) IsValid() bool {
    36  	if strings.HasPrefix(repo.url.Path, "/blog/") {
    37  		logger.Log("github", `the user or organization named "blog" is invalid on github, "https://github.com/blog" is redirected to "https://github.blog".`)
    38  		return false
    39  	}
    40  	pathComponents := strings.Split(strings.Trim(repo.url.Path, "/"), "/")
    41  	return len(pathComponents) >= 2
    42  }
    43  
    44  // VCS returns VCSBackend of the repository
    45  func (repo *GitHubRepository) VCS() (*VCSBackend, *url.URL, error) {
    46  	u := *repo.url
    47  	pathComponents := strings.Split(strings.Trim(strings.TrimSuffix(u.Path, ".git"), "/"), "/")
    48  	path := "/" + strings.Join(pathComponents[0:2], "/")
    49  	if strings.HasSuffix(u.String(), ".git") {
    50  		path += ".git"
    51  	}
    52  	u.Path = path
    53  	return GitBackend, &u, nil
    54  }
    55  
    56  // A GitHubGistRepository represents a GitHub Gist repository.
    57  type GitHubGistRepository struct {
    58  	url *url.URL
    59  }
    60  
    61  // URL returns URL of the GistRepository
    62  func (repo *GitHubGistRepository) URL() *url.URL {
    63  	return repo.url
    64  }
    65  
    66  // IsValid determine if the gist repository is valid or not
    67  func (repo *GitHubGistRepository) IsValid() bool {
    68  	return true
    69  }
    70  
    71  // VCS returns VCSBackend of the gist
    72  func (repo *GitHubGistRepository) VCS() (*VCSBackend, *url.URL, error) {
    73  	return GitBackend, repo.URL(), nil
    74  }
    75  
    76  // DarksHubRepository represents DarcsHub Repository
    77  type DarksHubRepository struct {
    78  	url *url.URL
    79  }
    80  
    81  // URL returns URL of darks repository
    82  func (repo *DarksHubRepository) URL() *url.URL {
    83  	return repo.url
    84  }
    85  
    86  // IsValid determine if the DarcsHub repository is valid or not
    87  func (repo *DarksHubRepository) IsValid() bool {
    88  	return strings.Count(repo.url.Path, "/") == 2
    89  }
    90  
    91  // VCS returns VCSBackend of the DarcsHub repository
    92  func (repo *DarksHubRepository) VCS() (*VCSBackend, *url.URL, error) {
    93  	return DarcsBackend, repo.URL(), nil
    94  }
    95  
    96  // NestPijulRepository represents the Nest repository
    97  type NestPijulRepository struct {
    98  	url *url.URL
    99  }
   100  
   101  // URL returns URL of the Nest repository
   102  func (repo *NestPijulRepository) URL() *url.URL {
   103  	return repo.url
   104  }
   105  
   106  // IsValid determine if the Nest repository is valid or not
   107  func (repo *NestPijulRepository) IsValid() bool {
   108  	return strings.Count(repo.url.Path, "/") == 2
   109  }
   110  
   111  // VCS returns VCSBackend of the Nest repository
   112  func (repo *NestPijulRepository) VCS() (*VCSBackend, *url.URL, error) {
   113  	return PijulBackend, repo.URL(), nil
   114  }
   115  
   116  // A CodeCommitRepository represents a CodeCommit repository. Implements RemoteRepository.
   117  type CodeCommitRepository struct {
   118  	url *url.URL
   119  }
   120  
   121  // URL returns URL of the repository
   122  func (repo *CodeCommitRepository) URL() *url.URL {
   123  	return repo.url
   124  }
   125  
   126  // IsValid determine if the repository is valid or not
   127  func (repo *CodeCommitRepository) IsValid() bool {
   128  	return true
   129  }
   130  
   131  // VCS returns VCSBackend of the repository
   132  func (repo *CodeCommitRepository) VCS() (*VCSBackend, *url.URL, error) {
   133  	u := *repo.url
   134  	return GitBackend, &u, nil
   135  }
   136  
   137  // OtherRepository represents other repository
   138  type OtherRepository struct {
   139  	url *url.URL
   140  }
   141  
   142  // URL returns URL of the repository
   143  func (repo *OtherRepository) URL() *url.URL {
   144  	return repo.url
   145  }
   146  
   147  // IsValid determine if the repository is valid or not
   148  func (repo *OtherRepository) IsValid() bool {
   149  	return true
   150  }
   151  
   152  var (
   153  	vcsSchemeReg = regexp.MustCompile(`^(git|svn|bzr|codecommit)(?:\+|$)`)
   154  	scheme2vcs   = map[string]*VCSBackend{
   155  		"git":        GitBackend,
   156  		"codecommit": GitBackend,
   157  		"svn":        SubversionBackend,
   158  		"bzr":        BazaarBackend,
   159  	}
   160  )
   161  
   162  // VCS detects VCSBackend of the OtherRepository
   163  func (repo *OtherRepository) VCS() (*VCSBackend, *url.URL, error) {
   164  	// Respect 'ghq.url.https://ghe.example.com/.vcs' config variable
   165  	// (in gitconfig:)
   166  	//     [ghq "https://ghe.example.com/"]
   167  	//     vcs = github
   168  	vcs, err := gitconfig.Do("--path", "--get-urlmatch", "ghq.vcs", repo.URL().String())
   169  	if err != nil && !gitconfig.IsNotFound(err) {
   170  		logger.Log("error", err.Error())
   171  	}
   172  	if backend, ok := vcsRegistry[vcs]; ok {
   173  		return backend, repo.URL(), nil
   174  	}
   175  
   176  	if m := vcsSchemeReg.FindStringSubmatch(repo.url.Scheme); len(m) > 1 {
   177  		return scheme2vcs[m[1]], repo.URL(), nil
   178  	}
   179  
   180  	mayBeSvn := strings.HasPrefix(repo.url.Host, "svn.")
   181  	if mayBeSvn && cmdutil.RunSilently("svn", "info", repo.url.String()) == nil {
   182  		return SubversionBackend, repo.URL(), nil
   183  	}
   184  
   185  	// Detect VCS backend automatically
   186  	if cmdutil.RunSilently("git", "ls-remote", repo.url.String()) == nil {
   187  		return GitBackend, repo.URL(), nil
   188  	}
   189  
   190  	vcs, repoURL, err := detectGoImport(repo.url)
   191  	if err == nil {
   192  		// vcs == "mod" (modproxy) not supported yet
   193  		return vcsRegistry[vcs], repoURL, nil
   194  	}
   195  
   196  	if cmdutil.RunSilently("hg", "identify", repo.url.String()) == nil {
   197  		return MercurialBackend, repo.URL(), nil
   198  	}
   199  
   200  	if !mayBeSvn && cmdutil.RunSilently("svn", "info", repo.url.String()) == nil {
   201  		return SubversionBackend, repo.URL(), nil
   202  	}
   203  
   204  	return nil, nil, fmt.Errorf("unsupported VCS, url=%s: %w", repo.URL(), err)
   205  }
   206  
   207  // NewRemoteRepository returns new RemoteRepository object from URL
   208  func NewRemoteRepository(u *url.URL) (RemoteRepository, error) {
   209  	repo := func() RemoteRepository {
   210  		if u.Scheme == "codecommit" {
   211  			return &CodeCommitRepository{u}
   212  		}
   213  		switch u.Host {
   214  		case "github.com":
   215  			return &GitHubRepository{u}
   216  		case "gist.github.com":
   217  			return &GitHubGistRepository{u}
   218  		case "hub.darcs.net":
   219  			return &DarksHubRepository{u}
   220  		case "nest.pijul.com":
   221  			return &NestPijulRepository{u}
   222  		default:
   223  			return &OtherRepository{u}
   224  		}
   225  	}()
   226  	if !repo.IsValid() {
   227  		return nil, fmt.Errorf("not a valid repository: %s", u)
   228  	}
   229  	return repo, nil
   230  }