Skip to content

Commit 1a47f5e

Browse files
authored
Merge pull request #4850 from gitbutlerapp/ndom91/use-template-dropdown
fix: refactor pr template path input to pre-filled `Select` instead of `TextBox`
2 parents 56fb839 + c952ffe commit 1a47f5e

File tree

23 files changed

+292
-158
lines changed

23 files changed

+292
-158
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/desktop/src/lib/backend/projects.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ export class Project {
2727
use_diff_context: boolean | undefined;
2828
snapshot_lines_threshold!: number | undefined;
2929
use_new_locking!: boolean;
30+
git_host!: {
31+
hostType: 'github' | 'gitlab' | 'bitbucket' | 'azure';
32+
pullRequestTemplatePath: string;
33+
};
3034

3135
private succeeding_rebases!: boolean;
3236
get succeedingRebases() {

apps/desktop/src/lib/branch/BranchHeader.svelte

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import BranchLaneContextMenu from './BranchLaneContextMenu.svelte';
55
import DefaultTargetButton from './DefaultTargetButton.svelte';
66
import PullRequestButton from '../pr/PullRequestButton.svelte';
7+
import { Project } from '$lib/backend/projects';
78
import { BaseBranch } from '$lib/baseBranch/baseBranch';
89
import { BaseBranchService } from '$lib/baseBranch/baseBranchService';
910
import ContextMenu from '$lib/components/contextmenu/ContextMenu.svelte';
@@ -41,6 +42,7 @@
4142
const branchStore = getContextStore(VirtualBranch);
4243
const prMonitor = getGitHostPrMonitor();
4344
const gitHost = getGitHost();
45+
const project = getContext(Project);
4446
4547
const baseBranchName = $derived($baseBranch.shortName);
4648
const branch = $derived($branchStore);
@@ -87,15 +89,30 @@
8789
let title: string;
8890
let body: string;
8991
90-
// In case of a single commit, use the commit summary and description for the title and
91-
// description of the PR.
92-
if (branch.commits.length === 1) {
93-
const commit = branch.commits[0];
94-
title = commit?.descriptionTitle ?? '';
95-
body = commit?.descriptionBody ?? '';
96-
} else {
92+
let pullRequestTemplateBody: string | undefined;
93+
const prTemplatePath = project.git_host.pullRequestTemplatePath;
94+
95+
if (prTemplatePath) {
96+
pullRequestTemplateBody = await $prService?.pullRequestTemplateContent(
97+
prTemplatePath,
98+
project.id
99+
);
100+
}
101+
102+
if (pullRequestTemplateBody) {
97103
title = branch.name;
98-
body = '';
104+
body = pullRequestTemplateBody;
105+
} else {
106+
// In case of a single commit, use the commit summary and description for the title and
107+
// description of the PR.
108+
if (branch.commits.length === 1) {
109+
const commit = branch.commits[0];
110+
title = commit?.descriptionTitle ?? '';
111+
body = commit?.descriptionBody ?? '';
112+
} else {
113+
title = branch.name;
114+
body = '';
115+
}
99116
}
100117
101118
isLoading = true;

apps/desktop/src/lib/config/config.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,3 @@ export function projectLaneCollapsed(projectId: string, laneId: string): Persist
4949
export function persistedCommitMessage(projectId: string, branchId: string): Persisted<string> {
5050
return persisted('', 'projectCurrentCommitMessage_' + projectId + '_' + branchId);
5151
}
52-
53-
export function gitHostUsePullRequestTemplate(): Persisted<boolean> {
54-
return persisted(false, 'gitHostUsePullRequestTemplate');
55-
}
56-
57-
export function gitHostPullRequestTemplatePath(): Persisted<string> {
58-
return persisted('', 'gitHostPullRequestTemplatePath');
59-
}

apps/desktop/src/lib/gitHost/azure/azure.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,13 @@ export class AzureDevOps implements GitHost {
4747
checksMonitor(_sourceBranch: string) {
4848
return undefined;
4949
}
50+
51+
async availablePullRequestTemplates(_path?: string) {
52+
// See: https://learn.microsoft.com/en-us/azure/devops/repos/git/pull-request-templates?view=azure-devops#default-pull-request-templates
53+
return undefined;
54+
}
55+
56+
async pullRequestTemplateContent(_path?: string) {
57+
return undefined;
58+
}
5059
}

apps/desktop/src/lib/gitHost/bitbucket/bitbucket.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,13 @@ export class BitBucket implements GitHost {
5151
checksMonitor(_sourceBranch: string) {
5252
return undefined;
5353
}
54+
55+
async availablePullRequestTemplates(_path?: string) {
56+
// See: https://confluence.atlassian.com/bitbucketserver/create-a-pull-request-808488431.html#Createapullrequest-templatePullrequestdescriptiontemplates
57+
return undefined;
58+
}
59+
60+
async pullRequestTemplateContent(_path?: string) {
61+
return undefined;
62+
}
5463
}

apps/desktop/src/lib/gitHost/gitHostFactory.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { BitBucket, BITBUCKET_DOMAIN } from './bitbucket/bitbucket';
33
import { GitHub, GITHUB_DOMAIN } from './github/github';
44
import { GitLab, GITLAB_DOMAIN, GITLAB_SUB_DOMAIN } from './gitlab/gitlab';
55
import { ProjectMetrics } from '$lib/metrics/projectMetrics';
6-
import type { Persisted } from '$lib/persisted/persisted';
76
import type { RepoInfo } from '$lib/url/gitUrl';
87
import type { GitHost } from './interface/gitHost';
98
import type { Octokit } from '@octokit/rest';
@@ -17,13 +16,7 @@ export interface GitHostFactory {
1716
export class DefaultGitHostFactory implements GitHostFactory {
1817
constructor(private octokit: Octokit | undefined) {}
1918

20-
build(
21-
repo: RepoInfo,
22-
baseBranch: string,
23-
fork?: RepoInfo,
24-
usePullRequestTemplate?: Persisted<boolean>,
25-
pullRequestTemplatePath?: Persisted<string>
26-
) {
19+
build(repo: RepoInfo, baseBranch: string, fork?: RepoInfo) {
2720
const domain = repo.domain;
2821
const forkStr = fork ? `${fork.owner}:${fork.name}` : undefined;
2922

@@ -33,9 +26,7 @@ export class DefaultGitHostFactory implements GitHostFactory {
3326
baseBranch,
3427
forkStr,
3528
octokit: this.octokit,
36-
projectMetrics: new ProjectMetrics(),
37-
usePullRequestTemplate,
38-
pullRequestTemplatePath
29+
projectMetrics: new ProjectMetrics()
3930
});
4031
}
4132
if (domain === GITLAB_DOMAIN || domain.startsWith(GITLAB_SUB_DOMAIN + '.')) {

apps/desktop/src/lib/gitHost/github/github.ts

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { GitHubPrService } from './githubPrService';
55
import { GitHubIssueService } from '$lib/gitHost/github/issueService';
66
import { Octokit } from '@octokit/rest';
77
import type { ProjectMetrics } from '$lib/metrics/projectMetrics';
8-
import type { Persisted } from '$lib/persisted/persisted';
98
import type { RepoInfo } from '$lib/url/gitUrl';
109
import type { GitHost } from '../interface/gitHost';
1110
import type { GitHostArguments } from '../interface/types';
@@ -19,31 +18,23 @@ export class GitHub implements GitHost {
1918
private forkStr?: string;
2019
private octokit?: Octokit;
2120
private projectMetrics?: ProjectMetrics;
22-
private usePullRequestTemplate?: Persisted<boolean>;
23-
private pullRequestTemplatePath?: Persisted<string>;
2421

2522
constructor({
2623
repo,
2724
baseBranch,
2825
forkStr,
2926
octokit,
30-
projectMetrics,
31-
usePullRequestTemplate,
32-
pullRequestTemplatePath
27+
projectMetrics
3328
}: GitHostArguments & {
3429
octokit?: Octokit;
3530
projectMetrics?: ProjectMetrics;
36-
usePullRequestTemplate?: Persisted<boolean>;
37-
pullRequestTemplatePath?: Persisted<string>;
3831
}) {
3932
this.baseUrl = `https://${GITHUB_DOMAIN}/${repo.owner}/${repo.name}`;
4033
this.repo = repo;
4134
this.baseBranch = baseBranch;
4235
this.forkStr = forkStr;
4336
this.octokit = octokit;
4437
this.projectMetrics = projectMetrics;
45-
this.usePullRequestTemplate = usePullRequestTemplate;
46-
this.pullRequestTemplatePath = pullRequestTemplatePath;
4738
}
4839

4940
listService() {
@@ -57,12 +48,7 @@ export class GitHub implements GitHost {
5748
if (!this.octokit) {
5849
return;
5950
}
60-
return new GitHubPrService(
61-
this.octokit,
62-
this.repo,
63-
this.usePullRequestTemplate,
64-
this.pullRequestTemplatePath
65-
);
51+
return new GitHubPrService(this.octokit, this.repo);
6652
}
6753

6854
issueService() {
Lines changed: 40 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { GitHubPrMonitor } from './githubPrMonitor';
22
import { DEFAULT_HEADERS } from './headers';
33
import { ghResponseToInstance, parseGitHubDetailedPullRequest } from './types';
4+
import { invoke } from '$lib/backend/ipc';
45
import { showToast } from '$lib/notifications/toasts';
56
import { sleep } from '$lib/utils/sleep';
67
import posthog from 'posthog-js';
7-
import { get, writable } from 'svelte/store';
8-
import type { Persisted } from '$lib/persisted/persisted';
8+
import { writable } from 'svelte/store';
9+
import type { GitHostPrService } from '$lib/gitHost/interface/gitHostPrService';
910
import type { RepoInfo } from '$lib/url/gitUrl';
10-
import type { GitHostPrService } from '../interface/gitHostPrService';
1111
import type {
1212
CreatePullRequestArgs,
1313
DetailedPullRequest,
@@ -16,16 +16,12 @@ import type {
1616
} from '../interface/types';
1717
import type { Octokit } from '@octokit/rest';
1818

19-
const DEFAULT_PULL_REQUEST_TEMPLATE_PATH = '.github/PULL_REQUEST_TEMPLATE.md';
20-
2119
export class GitHubPrService implements GitHostPrService {
2220
loading = writable(false);
2321

2422
constructor(
2523
private octokit: Octokit,
26-
private repo: RepoInfo,
27-
private usePullRequestTemplate?: Persisted<boolean>,
28-
private pullRequestTemplatePath?: Persisted<string>
24+
private repo: RepoInfo
2925
) {}
3026

3127
async createPr({
@@ -36,33 +32,28 @@ export class GitHubPrService implements GitHostPrService {
3632
upstreamName
3733
}: CreatePullRequestArgs): Promise<PullRequest> {
3834
this.loading.set(true);
39-
const request = async (pullRequestTemplate: string | undefined = '') => {
35+
const request = async () => {
4036
const resp = await this.octokit.rest.pulls.create({
4137
owner: this.repo.owner,
4238
repo: this.repo.name,
4339
head: upstreamName,
4440
base: baseBranchName,
4541
title,
46-
body: body ? body : pullRequestTemplate,
42+
body,
4743
draft
4844
});
45+
4946
return ghResponseToInstance(resp.data);
5047
};
5148

5249
let attempts = 0;
5350
let lastError: any;
5451
let pr: PullRequest | undefined;
55-
let pullRequestTemplate: string | undefined;
56-
const usePrTemplate = this.usePullRequestTemplate ? get(this.usePullRequestTemplate) : null;
57-
58-
if (!body && usePrTemplate) {
59-
pullRequestTemplate = await this.fetchPrTemplate();
60-
}
6152

6253
// Use retries since request can fail right after branch push.
6354
while (attempts < 4) {
6455
try {
65-
pr = await request(pullRequestTemplate);
56+
pr = await request();
6657
posthog.capture('PR Successful');
6758
return pr;
6859
} catch (err: any) {
@@ -76,32 +67,6 @@ export class GitHubPrService implements GitHostPrService {
7667
throw lastError;
7768
}
7869

79-
async fetchPrTemplate() {
80-
const path = this.pullRequestTemplatePath
81-
? get(this.pullRequestTemplatePath)
82-
: DEFAULT_PULL_REQUEST_TEMPLATE_PATH;
83-
84-
try {
85-
const response = await this.octokit.rest.repos.getContent({
86-
owner: this.repo.owner,
87-
repo: this.repo.name,
88-
path
89-
});
90-
const b64Content = (response.data as any)?.content;
91-
if (b64Content) {
92-
return decodeURIComponent(escape(atob(b64Content)));
93-
}
94-
} catch (err) {
95-
console.error(`Error fetching pull request template at path: ${path}`, err);
96-
97-
showToast({
98-
title: 'Failed to fetch pull request template',
99-
message: `Template not found at path: \`${path}\`.`,
100-
style: 'neutral'
101-
});
102-
}
103-
}
104-
10570
async get(prNumber: number): Promise<DetailedPullRequest> {
10671
const resp = await this.octokit.pulls.get({
10772
headers: DEFAULT_HEADERS,
@@ -124,4 +89,36 @@ export class GitHubPrService implements GitHostPrService {
12489
prMonitor(prNumber: number): GitHubPrMonitor {
12590
return new GitHubPrMonitor(this, prNumber);
12691
}
92+
93+
async pullRequestTemplateContent(path: string, projectId: string) {
94+
try {
95+
const fileContents: string | undefined = await invoke('get_pr_template_contents', {
96+
relativePath: path,
97+
projectId
98+
});
99+
return fileContents;
100+
} catch (err) {
101+
console.error(`Error reading pull request template at path: ${path}`, err);
102+
103+
showToast({
104+
title: 'Failed to read pull request template',
105+
message: `Could not read: \`${path}\`.`,
106+
style: 'neutral'
107+
});
108+
}
109+
}
110+
111+
async availablePullRequestTemplates(path: string): Promise<string[] | undefined> {
112+
// TODO: Find a workaround to avoid this dynamic import
113+
// https://github.com/sveltejs/kit/issues/905
114+
const { join } = await import('@tauri-apps/api/path');
115+
const targetPath = await join(path, '.github');
116+
117+
const availableTemplates: string[] | undefined = await invoke(
118+
'available_pull_request_templates',
119+
{ rootPath: targetPath }
120+
);
121+
122+
return availableTemplates;
123+
}
127124
}

apps/desktop/src/lib/gitHost/gitlab/gitlab.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,13 @@ export class GitLab implements GitHost {
5252
checksMonitor(_sourceBranch: string) {
5353
return undefined;
5454
}
55+
56+
async availablePullRequestTemplates(_path?: string) {
57+
// See: https://docs.gitlab.com/ee/user/project/description_templates.html
58+
return undefined;
59+
}
60+
61+
async pullRequestTemplateContent(_path?: string) {
62+
return undefined;
63+
}
5564
}

0 commit comments

Comments
 (0)