116 lines
3.3 KiB
Go
116 lines
3.3 KiB
Go
// Package forge is what hotdog-cms knows about the places sites are hosted in
|
|
// git: GitHub (and GitHub Enterprise), GitLab, Gitea, Forgejo (Codeberg),
|
|
// Gogs, Bitbucket Cloud, and plain git anywhere else.
|
|
//
|
|
// For each it can verify and read push webhooks, so a push rebuilds at once
|
|
// instead of at the next check, and post a commit status, so a pull request
|
|
// shows its preview's address. Plain git gets a token-checked hook any
|
|
// post-receive script can call; it has no statuses to post.
|
|
package forge
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// Kind names a platform.
|
|
type Kind string
|
|
|
|
const (
|
|
GitHub Kind = "github"
|
|
GitLab Kind = "gitlab"
|
|
Gitea Kind = "gitea"
|
|
Forgejo Kind = "forgejo"
|
|
Gogs Kind = "gogs"
|
|
Bitbucket Kind = "bitbucket"
|
|
Git Kind = "git"
|
|
)
|
|
|
|
// Kinds lists every platform, for flags and help text.
|
|
var Kinds = []Kind{GitHub, GitLab, Gitea, Forgejo, Gogs, Bitbucket, Git}
|
|
|
|
// Repo is a repository on a platform.
|
|
type Repo struct {
|
|
Kind Kind
|
|
Host string // host[:port] of the web/API server
|
|
Path string // owner/name, or group/subgroup/name on GitLab
|
|
API string // API base URL
|
|
}
|
|
|
|
// TokenUser is the user name each platform expects with a token over HTTPS.
|
|
func (k Kind) TokenUser() string {
|
|
switch k {
|
|
case GitHub:
|
|
return "x-access-token"
|
|
case GitLab:
|
|
return "oauth2"
|
|
case Bitbucket:
|
|
return "x-token-auth"
|
|
}
|
|
// Gitea takes the token as the password with any user name (checked on
|
|
// Gitea 28.1). Forgejo is a fork of it; for Gogs, set HOTDOG_GIT_USER to the
|
|
// account's name if the default is refused.
|
|
return "hotdog-cms"
|
|
}
|
|
|
|
var scpRe = regexp.MustCompile(`^(?:[A-Za-z0-9._-]+@)?([A-Za-z0-9.-]+):(.+)$`)
|
|
|
|
// Parse works out the platform and repository from a clone URL. kind may be
|
|
// empty to guess from well-known hosts; a self-hosted Gitea, GitLab or GitHub
|
|
// Enterprise needs it said.
|
|
func Parse(cloneURL string, kind Kind) (Repo, error) {
|
|
var host, p string
|
|
if u, err := url.Parse(cloneURL); err == nil && u.Scheme != "" && u.Host != "" {
|
|
host, p = u.Host, u.Path
|
|
if u.Scheme == "ssh" {
|
|
host = u.Hostname() // an SSH port is not the web server's port
|
|
}
|
|
} else if m := scpRe.FindStringSubmatch(cloneURL); m != nil && !strings.HasPrefix(cloneURL, "/") {
|
|
host, p = m[1], m[2]
|
|
} else {
|
|
// A local path or file:// URL: plain git, nothing to talk to.
|
|
return Repo{Kind: Git}, nil
|
|
}
|
|
p = strings.TrimSuffix(strings.Trim(p, "/"), ".git")
|
|
if kind == "" {
|
|
switch strings.ToLower(host) {
|
|
case "github.com":
|
|
kind = GitHub
|
|
case "gitlab.com":
|
|
kind = GitLab
|
|
case "codeberg.org":
|
|
kind = Forgejo
|
|
case "gitea.com":
|
|
kind = Gitea
|
|
case "bitbucket.org":
|
|
kind = Bitbucket
|
|
default:
|
|
kind = Git
|
|
}
|
|
}
|
|
r := Repo{Kind: kind, Host: host, Path: p}
|
|
switch kind {
|
|
case GitHub:
|
|
if strings.EqualFold(host, "github.com") {
|
|
r.API = "https://api.github.com"
|
|
} else {
|
|
r.API = "https://" + host + "/api/v3"
|
|
}
|
|
case GitLab:
|
|
r.API = "https://" + host + "/api/v4"
|
|
case Gitea, Forgejo, Gogs:
|
|
r.API = "https://" + host + "/api/v1"
|
|
case Bitbucket:
|
|
r.API = "https://api.bitbucket.org/2.0"
|
|
case Git:
|
|
default:
|
|
return Repo{}, fmt.Errorf("unknown platform %q (one of %v)", kind, Kinds)
|
|
}
|
|
if kind != Git && strings.Count(p, "/") < 1 {
|
|
return Repo{}, fmt.Errorf("%s doesn't look like owner/repository", cloneURL)
|
|
}
|
|
return r, nil
|
|
}
|