mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-10-07 16:38:34 +08:00
Co-authored-by: Vasco Schiavo <115561717+VascoSch92@users.noreply.github.com>
92 lines
2.6 KiB
TypeScript
92 lines
2.6 KiB
TypeScript
import { Provider } from "#/types/settings";
|
|
|
|
export interface ParsedGitRemoteUrl {
|
|
/** Original URL, trimmed. */
|
|
url: string;
|
|
/** Hostname of the remote (e.g. `github.com`, `git.example.com`). */
|
|
host: string | null;
|
|
/** Path-style identifier, normalized to `owner/repo` (no leading slash, no `.git` suffix). */
|
|
repository: string | null;
|
|
/** Best-effort provider detection from the host. `null` for unrecognized/self-hosted hosts. */
|
|
provider: Provider | null;
|
|
}
|
|
|
|
const KNOWN_HOSTS: Record<string, Provider> = {
|
|
"github.com": "github",
|
|
"gitlab.com": "gitlab",
|
|
"bitbucket.org": "bitbucket",
|
|
"dev.azure.com": "azure_devops",
|
|
};
|
|
|
|
function stripGitSuffix(path: string): string {
|
|
return path.replace(/\.git$/, "");
|
|
}
|
|
|
|
function detectProvider(host: string | null): Provider | null {
|
|
if (!host) return null;
|
|
return KNOWN_HOSTS[host.toLowerCase()] ?? null;
|
|
}
|
|
|
|
function normalizeAzureDevOpsPath(path: string): string {
|
|
// Azure paths look like `org/project/_git/repo` or `org/_git/repo`.
|
|
// Normalize to `org/project/repo` (or `org/repo`) so it lines up with
|
|
// constructBranchUrl's expectations.
|
|
const segments = path.split("/").filter(Boolean);
|
|
const gitIndex = segments.indexOf("_git");
|
|
if (gitIndex === -1) return segments.join("/");
|
|
return [...segments.slice(0, gitIndex), ...segments.slice(gitIndex + 1)].join(
|
|
"/",
|
|
);
|
|
}
|
|
|
|
function buildParsedGitRemoteUrl(
|
|
url: string,
|
|
host: string | null,
|
|
rawPath: string,
|
|
): ParsedGitRemoteUrl {
|
|
const path = stripGitSuffix(rawPath.replace(/^\/+|\/+$/g, ""));
|
|
const provider = detectProvider(host);
|
|
const repository =
|
|
provider === "azure_devops" ? normalizeAzureDevOpsPath(path) : path;
|
|
|
|
return {
|
|
url,
|
|
host,
|
|
repository: repository || null,
|
|
provider,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Parse a git remote URL (HTTPS, SSH, or `git@host:path` shorthand) into its
|
|
* host, repository (`owner/repo`), and best-effort provider.
|
|
*
|
|
* Returns `null` if the URL is empty or unparseable. Unknown hosts still
|
|
* resolve `host` and `repository`; only `provider` is left `null`.
|
|
*/
|
|
export function parseGitRemoteUrl(
|
|
remoteUrl: string | null | undefined,
|
|
): ParsedGitRemoteUrl | null {
|
|
const url = remoteUrl?.trim();
|
|
if (!url) return null;
|
|
|
|
// git@host:owner/repo(.git)
|
|
const scpMatch = url.match(/^[^@\s]+@([^:\s]+):(.+)$/);
|
|
if (scpMatch) {
|
|
const host = scpMatch[1];
|
|
return buildParsedGitRemoteUrl(url, host, scpMatch[2]);
|
|
}
|
|
|
|
// ssh://, https://, http://, git://
|
|
try {
|
|
const parsed = new URL(url);
|
|
return buildParsedGitRemoteUrl(
|
|
url,
|
|
parsed.hostname || null,
|
|
parsed.pathname,
|
|
);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|