feat(gitea): add Gitea/Forgejo as a git provider
Full parity with the existing GitLab provider: - Server module packages/web/server/lib/gitea (auth/client/repo/routes + docs + tests) with Gitea REST v1 API, PAT + base URL auth, multi-account storage - Shared GiteaAPI types and web API client - Provider detection generalized with user-configurable custom domains per provider (github/gitlab/gitea), additive with built-in defaults (github.com, gitlab.com) and connected-account hosts; precedence github -> gitlab -> gitea - Gitea PR view, issues section, pickers, integration dialog, branch PR status helper, settings UI (PAT + base URL + custom domains) - Magic prompts (gitea.pr.review, gitea.issue.review) and full 11-locale i18n parity
This commit is contained in:
@@ -1,45 +1,112 @@
|
||||
import { describe, expect, test } from 'bun:test';
|
||||
import { detectGitProvider } from './gitProvider';
|
||||
import { detectGitProvider, type GitProviderHosts } from './gitProvider';
|
||||
|
||||
const EMPTY_HOSTS: GitProviderHosts = { github: [], gitlab: [], gitea: [] };
|
||||
|
||||
describe('detectGitProvider', () => {
|
||||
test('returns null with no remotes', () => {
|
||||
expect(detectGitProvider([], [])).toBeNull();
|
||||
expect(detectGitProvider([], EMPTY_HOSTS)).toBeNull();
|
||||
});
|
||||
|
||||
test('classifies github.com remotes across URL forms', () => {
|
||||
expect(detectGitProvider(['git@github.com:owner/repo.git'], [])).toBe('github');
|
||||
expect(detectGitProvider(['ssh://git@github.com/owner/repo.git'], [])).toBe('github');
|
||||
expect(detectGitProvider(['https://github.com/owner/repo.git'], [])).toBe('github');
|
||||
expect(detectGitProvider(['git@github.com:owner/repo.git'], EMPTY_HOSTS)).toBe('github');
|
||||
expect(detectGitProvider(['ssh://git@github.com/owner/repo.git'], EMPTY_HOSTS)).toBe('github');
|
||||
expect(detectGitProvider(['https://github.com/owner/repo.git'], EMPTY_HOSTS)).toBe('github');
|
||||
});
|
||||
|
||||
test('classifies gitlab.com remotes across URL forms', () => {
|
||||
expect(detectGitProvider(['git@gitlab.com:group/project.git'], [])).toBe('gitlab');
|
||||
expect(detectGitProvider(['ssh://git@gitlab.com/group/sub/project.git'], [])).toBe('gitlab');
|
||||
expect(detectGitProvider(['https://gitlab.com/group/project'], [])).toBe('gitlab');
|
||||
expect(detectGitProvider(['git@gitlab.com:group/project.git'], EMPTY_HOSTS)).toBe('gitlab');
|
||||
expect(detectGitProvider(['ssh://git@gitlab.com/group/sub/project.git'], EMPTY_HOSTS)).toBe('gitlab');
|
||||
expect(detectGitProvider(['https://gitlab.com/group/project'], EMPTY_HOSTS)).toBe('gitlab');
|
||||
});
|
||||
|
||||
test('classifies a self-hosted GitLab remote through connected account hosts', () => {
|
||||
expect(detectGitProvider(['git@git.example.com:group/project.git'], ['https://git.example.com'])).toBe('gitlab');
|
||||
expect(detectGitProvider(['https://git.example.com/group/project.git'], ['https://git.example.com'])).toBe('gitlab');
|
||||
test('classifies a self-hosted GitLab remote through configured hosts', () => {
|
||||
const hosts: GitProviderHosts = { ...EMPTY_HOSTS, gitlab: ['git.example.com'] };
|
||||
expect(detectGitProvider(['git@git.example.com:group/project.git'], hosts)).toBe('gitlab');
|
||||
expect(detectGitProvider(['https://git.example.com/group/project.git'], hosts)).toBe('gitlab');
|
||||
});
|
||||
|
||||
test('does not classify an unknown self-hosted host as gitlab without an account', () => {
|
||||
expect(detectGitProvider(['git@git.example.com:group/project.git'], [])).toBe('other');
|
||||
test('does not classify an unknown self-hosted host as gitlab without a configured host', () => {
|
||||
expect(detectGitProvider(['git@git.example.com:group/project.git'], EMPTY_HOSTS)).toBe('other');
|
||||
});
|
||||
|
||||
test('classifies other hosts as other', () => {
|
||||
expect(detectGitProvider(['git@gitea.example.com:owner/repo.git'], [])).toBe('other');
|
||||
expect(detectGitProvider(['https://bitbucket.org/owner/repo.git'], [])).toBe('other');
|
||||
expect(detectGitProvider(['git@gitea.example.com:owner/repo.git'], EMPTY_HOSTS)).toBe('other');
|
||||
expect(detectGitProvider(['https://bitbucket.org/owner/repo.git'], EMPTY_HOSTS)).toBe('other');
|
||||
});
|
||||
|
||||
test('github wins when both github and gitlab remotes are present', () => {
|
||||
expect(detectGitProvider([
|
||||
'git@github.com:owner/repo.git',
|
||||
'git@gitlab.com:owner/repo.git',
|
||||
], [])).toBe('github');
|
||||
], EMPTY_HOSTS)).toBe('github');
|
||||
});
|
||||
|
||||
test('ignores malformed remotes', () => {
|
||||
expect(detectGitProvider(['', 'not a url', ' '], [])).toBeNull();
|
||||
expect(detectGitProvider(['', 'not a url', ' '], EMPTY_HOSTS)).toBeNull();
|
||||
});
|
||||
|
||||
test('classifies a self-hosted Gitea remote across URL forms through configured hosts', () => {
|
||||
const hosts: GitProviderHosts = { ...EMPTY_HOSTS, gitea: ['gitea.example.com'] };
|
||||
expect(detectGitProvider(['git@gitea.example.com:owner/repo.git'], hosts)).toBe('gitea');
|
||||
expect(detectGitProvider(['ssh://git@gitea.example.com/owner/repo.git'], hosts)).toBe('gitea');
|
||||
expect(detectGitProvider(['https://gitea.example.com/owner/repo.git'], hosts)).toBe('gitea');
|
||||
});
|
||||
|
||||
test('classifies codeberg.org remotes as gitea through configured hosts', () => {
|
||||
const hosts: GitProviderHosts = { ...EMPTY_HOSTS, gitea: ['codeberg.org'] };
|
||||
expect(detectGitProvider(['git@codeberg.org:owner/repo.git'], hosts)).toBe('gitea');
|
||||
});
|
||||
|
||||
test('does not classify codeberg.org as gitea without a configured host', () => {
|
||||
expect(detectGitProvider(['git@codeberg.org:owner/repo.git'], EMPTY_HOSTS)).toBe('other');
|
||||
});
|
||||
|
||||
test('github wins over a configured gitea host when both remotes are present', () => {
|
||||
const hosts: GitProviderHosts = { ...EMPTY_HOSTS, gitea: ['gitea.example.com'] };
|
||||
expect(detectGitProvider([
|
||||
'git@github.com:owner/repo.git',
|
||||
'git@gitea.example.com:owner/repo.git',
|
||||
], hosts)).toBe('github');
|
||||
});
|
||||
|
||||
test('gitlab wins over a configured gitea host when both remotes are present', () => {
|
||||
const hosts: GitProviderHosts = { ...EMPTY_HOSTS, gitea: ['gitea.example.com'] };
|
||||
expect(detectGitProvider([
|
||||
'git@gitlab.com:group/project.git',
|
||||
'git@gitea.example.com:owner/repo.git',
|
||||
], hosts)).toBe('gitlab');
|
||||
});
|
||||
|
||||
test('custom github host wins over gitlab.com', () => {
|
||||
const hosts: GitProviderHosts = { ...EMPTY_HOSTS, github: ['github.example.com'] };
|
||||
expect(detectGitProvider([
|
||||
'git@github.example.com:owner/repo.git',
|
||||
'git@gitlab.com:group/project.git',
|
||||
], hosts)).toBe('github');
|
||||
});
|
||||
|
||||
test('normalizes host entries (scheme, port, and path stripped)', () => {
|
||||
const hosts: GitProviderHosts = {
|
||||
github: ['https://github.example.com/some/path', 'ssh://git@gh.other.example.com:2222/org'],
|
||||
gitlab: ['git@git.example.com:group/repo.git'],
|
||||
gitea: [],
|
||||
};
|
||||
expect(detectGitProvider(['https://github.example.com/owner/repo.git'], hosts)).toBe('github');
|
||||
expect(detectGitProvider(['https://gh.other.example.com/owner/repo.git'], hosts)).toBe('github');
|
||||
expect(detectGitProvider(['https://git.example.com/group/project.git'], hosts)).toBe('gitlab');
|
||||
});
|
||||
|
||||
test('dedupes host entries', () => {
|
||||
const hosts: GitProviderHosts = {
|
||||
github: ['https://github.example.com/a', 'https://github.example.com/b'],
|
||||
gitlab: [],
|
||||
gitea: [],
|
||||
};
|
||||
expect(detectGitProvider(['git@github.example.com:owner/repo.git'], hosts)).toBe('github');
|
||||
});
|
||||
|
||||
test('normalizes github.com built-in case-insensitively', () => {
|
||||
expect(detectGitProvider(['https://GITHUB.com/owner/repo.git'], EMPTY_HOSTS)).toBe('github');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user