fix(gitcode): handle ssh:// URLs with port + persist clone creds for fetch

Address PR #376 review:
- parseGitCodeRepoInput now strips a leading ssh://[user@]host[:port]/ prefix,
  so `ssh://git@gitcode.com:2222/owner/repo.git` parses to owner/repo instead
  of owner=ssh:. Adds unit tests for the port and no-port forms.
- shallowClone (import cache) embeds oauth2:<token>@ in the clone URL instead of
  a one-shot http.extraHeader, so the origin remote persists credentials and the
  twin shallowFetch()'s plain `git fetch` authenticates on private repos without
  a credential helper. Mirrors the tgit branch; token is redacted in errors via
  sanitizeGitUrl. Verified live with credential.helper disabled.
This commit is contained in:
jeffyxu
2026-09-01 17:42:01 +08:00
parent 715064cde2
commit 6582e3c6bb
3 changed files with 34 additions and 9 deletions
+13
View File
@@ -31,6 +31,19 @@ describe('GitCode repo-url parsing', () => {
expect(info.repo).toBe('repo');
});
it('parses ssh:// URL with a non-default port', () => {
const info = parseGitCodeRepoInput('ssh://git@gitcode.com:2222/owner/repo.git');
expect(info.owner).toBe('owner');
expect(info.repo).toBe('repo');
expect(info.httpsUrl).toBe('https://gitcode.com/owner/repo.git');
});
it('parses ssh:// URL without a port', () => {
const info = parseGitCodeRepoInput('ssh://git@gitcode.com/owner/repo.git');
expect(info.owner).toBe('owner');
expect(info.repo).toBe('repo');
});
it('ignores trailing web-route segments (Gitee-style /pulls/1)', () => {
const info = parseGitCodeRepoInput('https://gitcode.com/owner/repo/pulls/1');
expect(info.owner).toBe('owner');
+8 -5
View File
@@ -218,16 +218,19 @@ export async function shallowClone(
log.debug(`shallowClone: 无 GITLAB_TOKEN,尝试匿名 HTTPS 克隆`);
}
} else if (provider === 'gitcode') {
// GitCode: PAT 走 HTTP Basic(用户名固定 oauth2),用 http.extraHeader 注入,
// token 不进 URL。已实机验证:GitCode git 端点拒绝 Bearer,仅认 Basic oauth2。
// 公有云只有 https,故强制 http→https。
// GitCode: 把 PAT 以 oauth2:<token>@ 内嵌进 clone URL(而非仅用一次性
// http.extraHeader),让 origin remote 持久化凭据——否则后续 shallowFetch()
// 的普通 `git fetch` 在无 credential helper 的环境里会对私有仓报
// "could not read Username"。已实机验证 GitCode git 端点仅认 Basic oauth2、
// 拒绝 Bearer。与 tgit 分支一致。公有云只有 https,故强制 http→https。
const token = getGitCodeToken();
cloneUrl = url.replace(/^http:\/\//, 'https://');
const httpsUrl = url.replace(/^http:\/\//, 'https://');
if (token) {
extraAuthHeader = buildAuthHeader(token, 'oauth2');
cloneUrl = httpsUrl.replace(/^https:\/\//, `https://oauth2:${token}@`);
cloneMethod = 'https-token';
log.debug(`shallowClone: 使用 HTTPS+token 克隆 gitcode 仓库`);
} else {
cloneUrl = httpsUrl;
cloneMethod = 'https-anonymous';
log.debug(`shallowClone: 无 GITCODE_TOKEN,尝试匿名 HTTPS 克隆`);
}
+13 -4
View File
@@ -21,14 +21,22 @@ export const GITCODE_HOST = 'gitcode.com';
* two leading path segments are taken as `owner/repo`; anything after is a web
* route and is ignored. Unlike GitLab there is no subgroup nesting and no `/-/`
* route separator (GitCode uses GitHub/Gitee-style `/owner/repo/pulls/1`).
*
* Accepted forms: `owner/repo`, `https://gitcode.com/owner/repo(.git)`,
* scp-style `git@gitcode.com:owner/repo(.git)`, and `ssh://` URLs including the
* port-qualified form GitCode's UI hands out (`ssh://git@gitcode.com:2222/owner/repo.git`).
*/
export function parseGitCodeRepoInput(input: string): RepoInfo {
const trimmed = input.trim();
// Strip a leading scheme+host (HTTPS) or scp-style `git@host:` prefix, then
// split into path segments. The first two segments are owner/repo; the rest
// are web-route noise.
// Strip a leading scheme+host prefix, then split into path segments. The first
// two segments are owner/repo; the rest are web-route noise.
// - `ssh://[user@]host[:port]/…` → drop through the first slash after host
// (handled before the scp rule; the `[:port]` never contains a `/`)
// - `https?://host/…` → drop scheme+host
// - scp-style `git@host:…` → drop `git@host:`
const withoutPrefix = trimmed
.replace(/^ssh:\/\/[^/]+\//i, '')
.replace(/^https?:\/\/[^/]+\//i, '')
.replace(/^git@[^:]+:/i, '')
.replace(/^\/+/, '');
@@ -47,7 +55,8 @@ export function parseGitCodeRepoInput(input: string): RepoInfo {
' Supported formats:\n' +
' owner/repo\n' +
` https://${GITCODE_HOST}/owner/repo.git\n` +
` git@${GITCODE_HOST}:owner/repo.git`,
` git@${GITCODE_HOST}:owner/repo.git\n` +
` ssh://git@${GITCODE_HOST}:2222/owner/repo.git`,
);
}