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 }