github.com/motemen/ghq@v1.0.3/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/motemen/ghq/cmdutil"
    11  	"github.com/motemen/ghq/logger"
    12  )
    13  
    14  // A RemoteRepository represents a remote repository.
    15  type RemoteRepository interface {
    16  	// The repository URL.
    17  	URL() *url.URL
    18  	// Checks if the URL is valid.
    19  	IsValid() bool
    20  	// The VCS backend that hosts the repository.
    21  	VCS() (*VCSBackend, *url.URL)
    22  }
    23  
    24  // A GitHubRepository represents a GitHub repository. Impliments RemoteRepository.
    25  type GitHubRepository struct {
    26  	url *url.URL
    27  }
    28  
    29  // URL reutrns 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  		return false
    38  	}
    39  	pathComponents := strings.Split(strings.Trim(repo.url.Path, "/"), "/")
    40  	return len(pathComponents) >= 2
    41  }
    42  
    43  // VCS returns VCSBackend of the repository
    44  func (repo *GitHubRepository) VCS() (*VCSBackend, *url.URL) {
    45  	origU := repo.URL()
    46  	u := &url.URL{ // clone
    47  		Scheme:   origU.Scheme,
    48  		User:     origU.User,
    49  		Host:     origU.Host,
    50  		Path:     origU.Path,
    51  		RawQuery: origU.RawQuery,
    52  	}
    53  	pathComponents := strings.Split(strings.Trim(strings.TrimSuffix(u.Path, ".git"), "/"), "/")
    54  	path := "/" + strings.Join(pathComponents[0:2], "/")
    55  	if strings.HasSuffix(u.String(), ".git") {
    56  		path += ".git"
    57  	}
    58  	u.Path = path
    59  	return GitBackend, u
    60  }
    61  
    62  // A GitHubGistRepository represents a GitHub Gist repository.
    63  type GitHubGistRepository struct {
    64  	url *url.URL
    65  }
    66  
    67  // URL returns URL of the GistRepositroy
    68  func (repo *GitHubGistRepository) URL() *url.URL {
    69  	return repo.url
    70  }
    71  
    72  // IsValid determin if the gist rpository is valid or not
    73  func (repo *GitHubGistRepository) IsValid() bool {
    74  	return true
    75  }
    76  
    77  // VCS returns VCSBackend of the gist
    78  func (repo *GitHubGistRepository) VCS() (*VCSBackend, *url.URL) {
    79  	return GitBackend, repo.URL()
    80  }
    81  
    82  // DarksHubRepository represents DarcsHub Repository
    83  type DarksHubRepository struct {
    84  	url *url.URL
    85  }
    86  
    87  // URL returns URL of darks repository
    88  func (repo *DarksHubRepository) URL() *url.URL {
    89  	return repo.url
    90  }
    91  
    92  // IsValid determine if the darcshub repositroy is valid or not
    93  func (repo *DarksHubRepository) IsValid() bool {
    94  	return strings.Count(repo.url.Path, "/") == 2
    95  }
    96  
    97  // VCS returns VCSBackend of the DarcsHub repository
    98  func (repo *DarksHubRepository) VCS() (*VCSBackend, *url.URL) {
    99  	return DarcsBackend, repo.URL()
   100  }
   101  
   102  // OtherRepository represents other repository
   103  type OtherRepository struct {
   104  	url *url.URL
   105  }
   106  
   107  // URL returns URL of the repository
   108  func (repo *OtherRepository) URL() *url.URL {
   109  	return repo.url
   110  }
   111  
   112  // IsValid determine if the repository is valid or not
   113  func (repo *OtherRepository) IsValid() bool {
   114  	return true
   115  }
   116  
   117  var (
   118  	vcsSchemeReg = regexp.MustCompile(`^(git|svn|bzr)(?:\+|$)`)
   119  	scheme2vcs   = map[string]*VCSBackend{
   120  		"git": GitBackend,
   121  		"svn": SubversionBackend,
   122  		"bzr": BazaarBackend,
   123  	}
   124  )
   125  
   126  // VCS detects VCSBackend of the OtherRepository
   127  func (repo *OtherRepository) VCS() (*VCSBackend, *url.URL) {
   128  	// Respect 'ghq.url.https://ghe.example.com/.vcs' config variable
   129  	// (in gitconfig:)
   130  	//     [ghq "https://ghe.example.com/"]
   131  	//     vcs = github
   132  	vcs, err := gitconfig.Do("--path", "--get-urlmatch", "ghq.vcs", repo.URL().String())
   133  	if err != nil && !gitconfig.IsNotFound(err) {
   134  		logger.Log("error", err.Error())
   135  	}
   136  	if backend, ok := vcsRegistry[vcs]; ok {
   137  		return backend, repo.URL()
   138  	}
   139  
   140  	if m := vcsSchemeReg.FindStringSubmatch(repo.url.Scheme); len(m) > 1 {
   141  		return scheme2vcs[m[1]], repo.URL()
   142  	}
   143  
   144  	mayBeSvn := strings.HasPrefix(repo.url.Host, "svn.")
   145  	if mayBeSvn && cmdutil.RunSilently("svn", "info", repo.url.String()) == nil {
   146  		return SubversionBackend, repo.URL()
   147  	}
   148  
   149  	// Detect VCS backend automatically
   150  	if cmdutil.RunSilently("git", "ls-remote", repo.url.String()) == nil {
   151  		return GitBackend, repo.URL()
   152  	}
   153  
   154  	vcs, repoURL, err := detectGoImport(repo.url)
   155  	if err == nil {
   156  		// vcs == "mod" (modproxy) not supported yet
   157  		return vcsRegistry[vcs], repoURL
   158  	}
   159  
   160  	if cmdutil.RunSilently("hg", "identify", repo.url.String()) == nil {
   161  		return MercurialBackend, repo.URL()
   162  	}
   163  
   164  	if !mayBeSvn && cmdutil.RunSilently("svn", "info", repo.url.String()) == nil {
   165  		return SubversionBackend, repo.URL()
   166  	}
   167  
   168  	return nil, nil
   169  }
   170  
   171  // NewRemoteRepository returns new RemoteRepository object from URL
   172  func NewRemoteRepository(u *url.URL) (RemoteRepository, error) {
   173  	repo := func() RemoteRepository {
   174  		switch u.Host {
   175  		case "github.com":
   176  			return &GitHubRepository{u}
   177  		case "gist.github.com":
   178  			return &GitHubGistRepository{u}
   179  		case "hub.darcs.net":
   180  			return &DarksHubRepository{u}
   181  		default:
   182  			return &OtherRepository{u}
   183  		}
   184  	}()
   185  	if !repo.IsValid() {
   186  		return nil, fmt.Errorf("Not a valid repository: %s", u)
   187  	}
   188  	return repo, nil
   189  }