ForgeType

Set the type of repository forge type (e.g. GitHub, GitLab) once the base branch is determined.
Get the available templates with the updated method
This commit is contained in:
estib 2024-10-17 18:14:35 +02:00
parent 4ee01031f6
commit d41e271e99
13 changed files with 78 additions and 52 deletions

View File

@ -0,0 +1,15 @@
import { invoke } from './ipc';
export type ForgeType = 'github' | 'gitlab' | 'bitbucket' | 'azure';
export class ForgeService {
constructor(private projectId: string) {}
async getAvailableReviewTemplates(): Promise<string[]> {
const templates = await invoke<string[]>('get_available_review_templates', {
projectId: this.projectId
});
return templates;
}
}

View File

@ -5,6 +5,7 @@ import { persisted } from '@gitbutler/shared/persisted';
import { open } from '@tauri-apps/api/dialog';
import { plainToInstance } from 'class-transformer';
import { derived, get, writable, type Readable } from 'svelte/store';
import type { ForgeType } from './forge';
import type { HttpClient } from '@gitbutler/shared/httpClient';
import { goto } from '$app/navigation';
@ -15,6 +16,8 @@ export type LocalKey = {
export type Key = Exclude<KeyType, 'local'> | LocalKey;
export type HostType = { type: ForgeType };
export class Project {
id!: string;
title!: string;
@ -28,8 +31,8 @@ export class Project {
snapshot_lines_threshold!: number | undefined;
use_new_locking!: boolean;
git_host!: {
hostType: 'github' | 'gitlab' | 'bitbucket' | 'azure';
pullRequestTemplatePath: string;
hostType: HostType | undefined;
reviewTemplatePath: string | undefined;
};
// Produced just for the frontend to determine if the project is open in any window.
@ -217,6 +220,14 @@ export class ProjectsService {
async getCloudProject(repositoryId: string): Promise<CloudProject> {
return await this.httpClient.get(`projects/${repositoryId}.json`);
}
async setGitHostType(project: Project, type: ForgeType) {
if (project.git_host.hostType?.type === type) return;
const hostType: HostType = { type };
const gitHost = { hostType };
await invoke('update_project_git_host', { projectId: project.id, gitHost });
this.reload();
}
}
/**

View File

@ -1,4 +1,5 @@
import { AzureBranch } from './azureBranch';
import type { ForgeType } from '$lib/backend/forge';
import type { RepoInfo } from '$lib/url/gitUrl';
import type { GitHost } from '../interface/gitHost';
import type { GitHostArguments } from '../interface/types';
@ -12,6 +13,7 @@ export const AZURE_DOMAIN = 'dev.azure.com';
* https://github.com/gitbutlerapp/gitbutler/issues/2651
*/
export class AzureDevOps implements GitHost {
readonly type: ForgeType = 'azure';
private baseUrl: string;
private repo: RepoInfo;
private baseBranch: string;
@ -48,11 +50,6 @@ export class AzureDevOps implements GitHost {
return undefined;
}
async availablePullRequestTemplates(_path?: string) {
// See: https://learn.microsoft.com/en-us/azure/devops/repos/git/pull-request-templates?view=azure-devops#default-pull-request-templates
return undefined;
}
async pullRequestTemplateContent(_path?: string) {
return undefined;
}

View File

@ -1,4 +1,5 @@
import { BitBucketBranch } from './bitbucketBranch';
import type { ForgeType } from '$lib/backend/forge';
import type { RepoInfo } from '$lib/url/gitUrl';
import type { GitHost } from '../interface/gitHost';
import type { DetailedPullRequest, GitHostArguments } from '../interface/types';
@ -16,6 +17,7 @@ export const BITBUCKET_DOMAIN = 'bitbucket.org';
* https://github.com/gitbutlerapp/gitbutler/issues/3252
*/
export class BitBucket implements GitHost {
readonly type: ForgeType = 'bitbucket';
private baseUrl: string;
private repo: RepoInfo;
private baseBranch: string;
@ -52,11 +54,6 @@ export class BitBucket implements GitHost {
return undefined;
}
async availablePullRequestTemplates(_path?: string) {
// See: https://confluence.atlassian.com/bitbucketserver/create-a-pull-request-808488431.html#Createapullrequest-templatePullrequestdescriptiontemplates
return undefined;
}
async pullRequestTemplateContent(_path?: string) {
return undefined;
}

View File

@ -4,6 +4,7 @@ import { GitHubListingService } from './githubListingService';
import { GitHubPrService } from './githubPrService';
import { GitHubIssueService } from '$lib/gitHost/github/issueService';
import { Octokit } from '@octokit/rest';
import type { ForgeType } from '$lib/backend/forge';
import type { ProjectMetrics } from '$lib/metrics/projectMetrics';
import type { RepoInfo } from '$lib/url/gitUrl';
import type { GitHost } from '../interface/gitHost';
@ -12,6 +13,7 @@ import type { GitHostArguments } from '../interface/types';
export const GITHUB_DOMAIN = 'github.com';
export class GitHub implements GitHost {
readonly type: ForgeType = 'github';
private baseUrl: string;
private repo: RepoInfo;
private baseBranch: string;

View File

@ -107,18 +107,4 @@ export class GitHubPrService implements GitHostPrService {
});
}
}
async availablePullRequestTemplates(path: string): Promise<string[] | undefined> {
// TODO: Find a workaround to avoid this dynamic import
// https://github.com/sveltejs/kit/issues/905
const { join } = await import('@tauri-apps/api/path');
const targetPath = await join(path, '.github');
const availableTemplates: string[] | undefined = await invoke(
'available_pull_request_templates',
{ rootPath: targetPath }
);
return availableTemplates;
}
}

View File

@ -1,4 +1,5 @@
import { GitLabBranch } from './gitlabBranch';
import type { ForgeType } from '$lib/backend/forge';
import type { RepoInfo } from '$lib/url/gitUrl';
import type { GitHost } from '../interface/gitHost';
import type { DetailedPullRequest, GitHostArguments } from '../interface/types';
@ -17,6 +18,7 @@ export const GITLAB_SUB_DOMAIN = 'gitlab'; // For self hosted instance of Gitlab
* https://github.com/gitbutlerapp/gitbutler/issues/2511
*/
export class GitLab implements GitHost {
readonly type: ForgeType = 'gitlab';
private baseUrl: string;
private repo: RepoInfo;
private baseBranch: string;
@ -53,11 +55,6 @@ export class GitLab implements GitHost {
return undefined;
}
async availablePullRequestTemplates(_path?: string) {
// See: https://docs.gitlab.com/ee/user/project/description_templates.html
return undefined;
}
async pullRequestTemplateContent(_path?: string) {
return undefined;
}

View File

@ -1,4 +1,5 @@
import { buildContextStore } from '@gitbutler/shared/context';
import type { ForgeType } from '$lib/backend/forge';
import type { GitHostIssueService } from '$lib/gitHost/interface/gitHostIssueService';
import type { GitHostBranch } from './gitHostBranch';
import type { GitHostChecksMonitor } from './gitHostChecksMonitor';
@ -6,6 +7,7 @@ import type { GitHostListingService } from './gitHostListingService';
import type { GitHostPrService } from './gitHostPrService';
export interface GitHost {
readonly type: ForgeType;
// Lists PRs for the repo.
listService(): GitHostListingService | undefined;

View File

@ -17,7 +17,6 @@ export interface GitHostPrService {
baseBranchName,
upstreamName
}: CreatePullRequestArgs): Promise<PullRequest>;
availablePullRequestTemplates(path: string): Promise<string[] | undefined>;
pullRequestTemplateContent(path: string, projectId: string): Promise<string | undefined>;
merge(method: MergeMethod, prNumber: number): Promise<void>;
prMonitor(prNumber: number): GitHostPrMonitor;

View File

@ -82,7 +82,7 @@
props.type === 'preview-series' ? props.upstreamName : branch.upstreamName
);
const baseBranchName = $derived($baseBranch.shortName);
const prTemplatePath = $derived(project.git_host.pullRequestTemplatePath);
const prTemplatePath = $derived(project.git_host.reviewTemplatePath);
let isDraft = $state<boolean>($preferredPRAction === PRAction.CreateDraft);
let modal = $state<ReturnType<typeof Modal>>();

View File

@ -1,9 +1,8 @@
<script lang="ts">
import notFoundSvg from '$lib/assets/empty-state/not-found.svg?raw';
import { Project, ProjectsService } from '$lib/backend/projects';
import { ForgeService } from '$lib/backend/forge';
import { ProjectService, ProjectsService } from '$lib/backend/projects';
import SectionCard from '$lib/components/SectionCard.svelte';
import { getGitHost } from '$lib/gitHost/interface/gitHost';
import { createGitHostPrServiceStore } from '$lib/gitHost/interface/gitHostPrService';
import Select from '$lib/select/Select.svelte';
import SelectItem from '$lib/select/SelectItem.svelte';
import Section from '$lib/settings/Section.svelte';
@ -14,25 +13,27 @@
import EmptyStatePlaceholder from '@gitbutler/ui/EmptyStatePlaceholder.svelte';
const projectsService = getContext(ProjectsService);
const project = getContext(Project);
const gitHost = getGitHost();
const prService = createGitHostPrServiceStore(undefined);
$effect(() => prService.set($gitHost?.prService()));
const projectService = getContext(ProjectService);
const forgeService = getContext(ForgeService);
let useTemplate = $state(!!project.git_host?.pullRequestTemplatePath);
let selectedTemplate = $state(project.git_host?.pullRequestTemplatePath ?? '');
const projectStore = projectService.project;
const project = $derived($projectStore);
const useTemplate = $derived(!!project?.git_host.reviewTemplatePath);
const selectedTemplate = $derived(project?.git_host.reviewTemplatePath);
let allAvailableTemplates = $state<{ label: string; value: string }[]>([]);
let isTemplatesAvailable = $state(false);
$effect(() => {
if (!project.path) return;
$prService?.availablePullRequestTemplates(project.path).then((availableTemplates) => {
if (!project) {
return;
}
forgeService.getAvailableReviewTemplates().then((availableTemplates) => {
if (availableTemplates) {
allAvailableTemplates = availableTemplates.map((availableTemplate) => {
const relativePath = availableTemplate.replace(`${project.path}/`, '');
return {
label: relativePath,
value: relativePath
label: availableTemplate,
value: availableTemplate
};
});
@ -42,15 +43,26 @@
});
async function setUsePullRequestTemplate(value: boolean) {
if (!project) return;
setTemplate: {
if (!value) {
project.git_host.pullRequestTemplatePath = '';
project.git_host.reviewTemplatePath = undefined;
break setTemplate;
}
if (allAvailableTemplates[0]) {
project.git_host.reviewTemplatePath = allAvailableTemplates[0].value;
break setTemplate;
}
}
await projectsService.updateProject(project);
}
async function setPullRequestTemplatePath(value: string) {
selectedTemplate = value;
project.git_host.pullRequestTemplatePath = value;
if (!project) return;
project.git_host.reviewTemplatePath = value;
await projectsService.updateProject(project);
}
</script>
@ -66,7 +78,8 @@
<svelte:fragment slot="actions">
<Toggle
id="use-pull-request-template-boolean"
bind:checked={useTemplate}
checked={useTemplate}
disabled={!isTemplatesAvailable}
on:click={(e) => {
setUsePullRequestTemplate(
(e.target as MouseEvent['target'] & { checked: boolean }).checked

View File

@ -1,4 +1,5 @@
<script lang="ts">
import { ForgeService } from '$lib/backend/forge';
import { Project, ProjectService } from '$lib/backend/projects';
import FileMenuAction from '$lib/barmenuActions/FileMenuAction.svelte';
import ProjectSettingsMenuAction from '$lib/barmenuActions/ProjectSettingsMenuAction.svelte';
@ -71,6 +72,7 @@
setContext(BranchController, data.branchController);
setContext(BaseBranchService, data.baseBranchService);
setContext(CommitService, data.commitService);
setContext(ForgeService, data.forgeService);
setContext(BaseBranch, baseBranch);
setContext(Project, project);
setContext(BranchDragActionsFactory, data.branchDragActionsFactory);
@ -146,6 +148,8 @@
const ghListService = gitHost?.listService();
if (gitHost) projectsService.setGitHostType(project, gitHost.type);
listServiceStore.set(ghListService);
gitHostStore.set(gitHost);
});

View File

@ -1,3 +1,4 @@
import { ForgeService } from '$lib/backend/forge';
import { getUserErrorCode, invoke } from '$lib/backend/ipc';
import { ProjectService, type Project } from '$lib/backend/projects';
import { BaseBranchService } from '$lib/baseBranch/baseBranchService';
@ -61,6 +62,7 @@ export const load: LayoutLoad = async ({ params, parent }) => {
const historyService = new HistoryService(projectId);
const baseBranchService = new BaseBranchService(projectId);
const commitService = new CommitService(projectId);
const forgeService = new ForgeService(projectId);
const branchListingService = new BranchListingService(projectId);
const remoteBranchService = new RemoteBranchService(
@ -108,6 +110,7 @@ export const load: LayoutLoad = async ({ params, parent }) => {
authService,
baseBranchService,
commitService,
forgeService,
branchController,
historyService,
projectId,