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 }