You've already forked agentic-coding-workflow
bugfix
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
package vcs
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/yan1h/agent-coding-workflow/internal/infra/errs"
|
||||
)
|
||||
|
||||
// ErrUnsupportedHost is returned by ParseRepoRef when the remote points at a
|
||||
// host other than the one the v1 Gitea provider serves.
|
||||
var ErrUnsupportedHost = errs.New(errs.CodeInvalidInput, "vcs: unsupported git host for PR operations")
|
||||
|
||||
// ParseRepoRef derives owner/name from a workspace GitRemoteURL. It accepts:
|
||||
//
|
||||
// https://host/owner/name(.git)
|
||||
// http://host/owner/name(.git)
|
||||
// ssh://git@host/owner/name(.git)
|
||||
// git@host:owner/name(.git)
|
||||
//
|
||||
// wantHost is the bare host the provider serves (e.g. "git.jerryyan.net"); when
|
||||
// non-empty the remote host must match it (port stripped, case-insensitive),
|
||||
// otherwise ErrUnsupportedHost is returned. wantHost empty skips the host
|
||||
// check (host-agnostic parse, used in tests).
|
||||
func ParseRepoRef(remoteURL, wantHost string) (RepoRef, error) {
|
||||
remoteURL = strings.TrimSpace(remoteURL)
|
||||
if remoteURL == "" {
|
||||
return RepoRef{}, errs.New(errs.CodeInvalidInput, "vcs: empty remote url")
|
||||
}
|
||||
|
||||
host, path, err := splitHostPath(remoteURL)
|
||||
if err != nil {
|
||||
return RepoRef{}, err
|
||||
}
|
||||
if wantHost != "" && !sameHost(host, wantHost) {
|
||||
return RepoRef{}, ErrUnsupportedHost
|
||||
}
|
||||
|
||||
owner, name, err := splitOwnerName(path)
|
||||
if err != nil {
|
||||
return RepoRef{}, err
|
||||
}
|
||||
return RepoRef{Owner: owner, Name: name}, nil
|
||||
}
|
||||
|
||||
// splitHostPath extracts the bare host and the repo path from a remote URL,
|
||||
// handling both scheme URLs and the scp-like git@host:owner/name form.
|
||||
func splitHostPath(remoteURL string) (host, path string, err error) {
|
||||
// scp-like: git@host:owner/name.git (no scheme, single colon before path)
|
||||
if !strings.Contains(remoteURL, "://") && strings.Contains(remoteURL, "@") && strings.Contains(remoteURL, ":") {
|
||||
at := strings.Index(remoteURL, "@")
|
||||
rest := remoteURL[at+1:]
|
||||
colon := strings.Index(rest, ":")
|
||||
if colon < 0 {
|
||||
return "", "", errs.New(errs.CodeInvalidInput, "vcs: malformed scp remote url")
|
||||
}
|
||||
return rest[:colon], rest[colon+1:], nil
|
||||
}
|
||||
|
||||
u, perr := url.Parse(remoteURL)
|
||||
if perr != nil || u.Host == "" {
|
||||
return "", "", errs.Wrap(perr, errs.CodeInvalidInput, "vcs: cannot parse remote url")
|
||||
}
|
||||
return u.Hostname(), u.Path, nil
|
||||
}
|
||||
|
||||
// splitOwnerName turns "/owner/name.git" or "owner/name.git" into (owner, name).
|
||||
func splitOwnerName(path string) (owner, name string, err error) {
|
||||
path = strings.Trim(path, "/")
|
||||
path = strings.TrimSuffix(path, ".git")
|
||||
parts := strings.Split(path, "/")
|
||||
if len(parts) < 2 || parts[0] == "" || parts[len(parts)-1] == "" {
|
||||
return "", "", errs.New(errs.CodeInvalidInput, "vcs: remote url must contain owner/name")
|
||||
}
|
||||
// Gitea repos are owner/name; deeper paths are not valid repo refs.
|
||||
return parts[0], parts[len(parts)-1], nil
|
||||
}
|
||||
|
||||
// sameHost compares hosts case-insensitively, ignoring any :port suffix.
|
||||
func sameHost(a, b string) bool {
|
||||
return strings.EqualFold(stripPort(a), stripPort(b))
|
||||
}
|
||||
|
||||
func stripPort(h string) string {
|
||||
if i := strings.LastIndex(h, ":"); i >= 0 {
|
||||
return h[:i]
|
||||
}
|
||||
return h
|
||||
}
|
||||
Reference in New Issue
Block a user