Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"@gitbeaker/rest": "^42.2.0",
"@tauri-apps/plugin-clipboard-manager": "^2.2.2",
"dayjs": "^1.11.13",
"gitea-js": "^1.23.0",
"ollama": "^0.5.15",
"openai": "^4.87.3",
"reconnecting-websocket": "^4.4.0",
Expand Down
90 changes: 79 additions & 11 deletions apps/desktop/src/components/ForgeForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,22 @@
import SelectItem from '@gitbutler/ui/select/SelectItem.svelte';
import type { ForgeName } from '$lib/forge/interface/forge';
import type { Project } from '$lib/project/project';
import { GiteaState } from '$lib/forge/gitea/giteaState.svelte';

const forge = getContext(DefaultForgeFactory);
const gitLabState = getContext(GitLabState);
const giteaState = getContext(GiteaState);

const determinedForgeType = forge.determinedForgeType;
const token = gitLabState.token;
const forkProjectId = gitLabState.forkProjectId;
const upstreamProjectId = gitLabState.upstreamProjectId;
const instanceUrl = gitLabState.instanceUrl;
const gitLabToken = gitLabState.token;
const gitLabForkProjectId = gitLabState.forkProjectId;
const gitLabUpstreamProjectId = gitLabState.upstreamProjectId;
const gitLabInstanceUrl = gitLabState.instanceUrl;

const giteaToken = giteaState.token;
const giteaForkProjectId = giteaState.forkProjectId;
const giteaUpstreamProjectId = giteaState.upstreamProjectId;
const giteaInstanceUrl = giteaState.instanceUrl;

const projectsService = getContext(ProjectsService);
const projectService = getContext(ProjectService);
Expand All @@ -45,6 +53,10 @@
{
label: 'BitBucket',
value: 'bitbucket'
},
{
label: 'Gitea',
value: 'gitea'
}
];
let selectedOption = $derived($project?.forge_override || 'default');
Expand Down Expand Up @@ -120,21 +132,25 @@
<br />
{/snippet}

<Textbox label="Personal token" value={$token} oninput={(value) => ($token = value)} />
<Textbox
label="Personal token"
value={$gitLabToken}
oninput={(value) => ($gitLabToken = value)}
/>
<Textbox
label="Your fork's project ID"
value={$forkProjectId}
oninput={(value) => ($forkProjectId = value)}
value={$gitLabForkProjectId}
oninput={(value) => ($gitLabForkProjectId = value)}
/>
<Textbox
label="Upstream project ID"
value={$upstreamProjectId}
oninput={(value) => ($upstreamProjectId = value)}
value={$gitLabUpstreamProjectId}
oninput={(value) => ($gitLabUpstreamProjectId = value)}
/>
<Textbox
label="Instance URL"
value={$instanceUrl}
oninput={(value) => ($instanceUrl = value)}
value={$gitLabInstanceUrl}
oninput={(value) => ($gitLabInstanceUrl = value)}
/>
</SectionCard>

Expand All @@ -147,5 +163,57 @@
{/snippet}
</SectionCard>
{/if}

{#if forge.current.name === 'gitea'}
<SectionCard roundedTop={false} roundedBottom={false}>
{#snippet title()}
Configure Gitea integration
{/snippet}

{#snippet caption()}
Learn how find your Gitea Personal Token and Project ID in our <Link
href="https://docs.gitbutler.com/features/gitlab-integration">docs</Link
>
<br />
The Fork Project ID is where your branches will be pushed, and the Upstream Project ID is where
you want merge requests to be created.
<br />
{/snippet}

<Textbox
label="Personal token"
value={$giteaToken}
oninput={(value) => ($giteaToken = value)}
/>
<Textbox
label="Your fork's project ID"
value={$giteaForkProjectId}
oninput={(value) => {
// $giteaForkProjectId = value
}}
helperText="Must be a valid Gitea ID including owner/repo"
/>
<Textbox
label="Upstream project ID"
value={$giteaUpstreamProjectId}
oninput={(value) => ($giteaUpstreamProjectId = value)}
helperText="Must be a valid Gitea ID including owner/repo"
/>
<Textbox
label="Instance URL"
value={$giteaInstanceUrl}
oninput={(value) => ($giteaInstanceUrl = value)}
/>
</SectionCard>

<SectionCard roundedTop={false}>
{#snippet caption()}
If you use a custom Gitea instance (not gitea.com), you will need to add it as a custom CSP
entry so that GitButler trusts connecting to that host. Read more in the <Link
href="https://docs.gitbutler.com/troubleshooting/custom-csp">docs</Link
>
{/snippet}
</SectionCard>
{/if}
</div>
<Spacer />
39 changes: 36 additions & 3 deletions apps/desktop/src/lib/forge/forgeFactory.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,22 @@ import type { PostHogWrapper } from '$lib/analytics/posthog';
import type { GitLabClient } from '$lib/forge/gitlab/gitlabClient.svelte';
import type { Forge, ForgeName } from '$lib/forge/interface/forge';
import type { ReadonlyBehaviorSubject } from '$lib/rxjs';
import type { GitHubApi, GitLabApi } from '$lib/state/clientState.svelte';
import type { GiteaApi, GitHubApi, GitLabApi } from '$lib/state/clientState.svelte';
import type { ReduxTag } from '$lib/state/tags';
import type { RepoInfo } from '$lib/url/gitUrl';
import type { Reactive } from '@gitbutler/shared/storeUtils';
import type { ThunkDispatch, UnknownAction } from '@reduxjs/toolkit';
import type { TagDescription } from '@reduxjs/toolkit/query';
import { Gitea, GITEA_DOMAIN, GITEA_SUB_DOMAIN } from '$lib/forge/gitea/gitea';
import type { GiteaClient } from '$lib/forge/gitea/giteaClient.svelte';

export type ForgeConfig = {
repo?: RepoInfo;
pushRepo?: RepoInfo;
baseBranch?: string;
githubAuthenticated?: boolean;
gitlabAuthenticated?: boolean;
giteaAuthenticated?: boolean;
forgeOverride?: ForgeName;
};

Expand All @@ -37,6 +40,8 @@ export class DefaultForgeFactory implements Reactive<Forge> {
gitHubApi: GitHubApi;
gitLabClient: GitLabClient;
gitLabApi: GitLabApi;
giteaClient: GiteaClient;
giteaApi: GiteaApi;
posthog: PostHogWrapper;
projectMetrics: ProjectMetrics;
dispatch: ThunkDispatch<any, any, UnknownAction>;
Expand All @@ -52,8 +57,15 @@ export class DefaultForgeFactory implements Reactive<Forge> {
}

setConfig(config: ForgeConfig) {
const { repo, pushRepo, baseBranch, githubAuthenticated, gitlabAuthenticated, forgeOverride } =
config;
const {
repo,
pushRepo,
baseBranch,
githubAuthenticated,
gitlabAuthenticated,
giteaAuthenticated,
forgeOverride
} = config;
if (repo && baseBranch) {
this._determinedForgeType.next(this.determineForgeType(repo));
this._forge = this.build({
Expand All @@ -62,6 +74,7 @@ export class DefaultForgeFactory implements Reactive<Forge> {
baseBranch,
githubAuthenticated,
gitlabAuthenticated,
giteaAuthenticated,
forgeOverride
});
} else {
Expand All @@ -75,13 +88,15 @@ export class DefaultForgeFactory implements Reactive<Forge> {
baseBranch,
githubAuthenticated,
gitlabAuthenticated,
giteaAuthenticated,
forgeOverride
}: {
repo: RepoInfo;
pushRepo?: RepoInfo;
baseBranch: string;
githubAuthenticated?: boolean;
gitlabAuthenticated?: boolean;
giteaAuthenticated?: boolean;
forgeOverride: ForgeName | undefined;
}): Forge {
let forgeType = this.determineForgeType(repo);
Expand Down Expand Up @@ -119,6 +134,17 @@ export class DefaultForgeFactory implements Reactive<Forge> {
authenticated: !!gitlabAuthenticated
});
}
if (forgeType === 'gitea') {
const { giteaClient, giteaApi, posthog } = this.params;
return new Gitea({
...baseParams,
api: giteaApi,
client: giteaClient,
posthog: posthog,
authenticated: !!giteaAuthenticated
});
}

if (forgeType === 'bitbucket') {
return new BitBucket(baseParams);
}
Expand Down Expand Up @@ -147,6 +173,13 @@ export class DefaultForgeFactory implements Reactive<Forge> {
if (domain.includes(AZURE_DOMAIN)) {
return 'azure';
}
if (
domain.includes(GITEA_DOMAIN) ||
domain.startsWith(GITEA_SUB_DOMAIN + '.') ||
domain.startsWith('xy' + GITEA_SUB_DOMAIN + '.')
) {
return 'gitea';
}

return 'default';
}
Expand Down
60 changes: 60 additions & 0 deletions apps/desktop/src/lib/forge/forgeFactory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { expect, test, describe } from 'vitest';
import type { GitHubClient } from '$lib/forge/github/githubClient';
import type { GitLabClient } from '$lib/forge/gitlab/gitlabClient.svelte';
import type { ThunkDispatch, UnknownAction } from '@reduxjs/toolkit';
import type { GiteaClient } from '$lib/forge/gitea/giteaClient.svelte';
import { Gitea } from '$lib/forge/gitea/gitea';

describe.concurrent('DefaultforgeFactory', () => {
const MockSettingsService = getSettingsdServiceMock();
Expand All @@ -27,17 +29,21 @@ describe.concurrent('DefaultforgeFactory', () => {
};
const gitHubClient = { onReset: () => {} } as any as GitHubClient;
const gitLabClient = { onReset: () => {} } as any as GitLabClient;
const giteaClient = { onReset: () => {} } as any as GiteaClient;

// TODO: Replace with a better mock.
const dispatch = (() => {}) as ThunkDispatch<any, any, UnknownAction>;
const gitLabApi: any = {};
const giteaApi: any = {};

test('Create GitHub service', async () => {
const factory = new DefaultForgeFactory({
gitHubClient,
gitHubApi,
gitLabClient,
gitLabApi,
giteaClient,
giteaApi,
posthog,
projectMetrics,
dispatch
Expand All @@ -61,6 +67,8 @@ describe.concurrent('DefaultforgeFactory', () => {
gitHubApi,
gitLabClient,
gitLabApi,
giteaClient,
giteaApi,
posthog,
projectMetrics,
dispatch
Expand All @@ -84,6 +92,8 @@ describe.concurrent('DefaultforgeFactory', () => {
gitHubApi,
gitLabClient,
gitLabApi,
giteaClient,
giteaApi,
posthog,
projectMetrics,
dispatch
Expand All @@ -100,4 +110,54 @@ describe.concurrent('DefaultforgeFactory', () => {
})
).instanceOf(GitLab);
});

test('Create self hosted Gitea service', async () => {
const factory = new DefaultForgeFactory({
gitHubClient,
gitHubApi,
gitLabClient,
gitLabApi,
giteaClient,
giteaApi,
posthog,
projectMetrics,
dispatch
});
expect(
factory.build({
repo: {
domain: 'gitea.domain.com',
name: 'test-repo',
owner: 'test-owner'
},
baseBranch: 'some-base',
forgeOverride: undefined
})
).instanceOf(Gitea);
});

test('Create Gitea service', async () => {
const factory = new DefaultForgeFactory({
gitHubClient,
gitHubApi,
gitLabClient,
gitLabApi,
giteaClient,
giteaApi,
posthog,
projectMetrics,
dispatch
});
expect(
factory.build({
repo: {
domain: 'gitea.com',
name: 'test-repo',
owner: 'test-owner'
},
baseBranch: 'some-base',
forgeOverride: undefined
})
).instanceOf(Gitea);
});
});
Loading
Loading