Skip to content

Commit dc4fb0b

Browse files
CopilotByron
andcommitted
Implement bypass merge rules checkbox feature
Co-authored-by: Byron <[email protected]>
1 parent 4e5f42c commit dc4fb0b

File tree

5 files changed

+82
-28
lines changed

5 files changed

+82
-28
lines changed

apps/desktop/src/components/MergeButton.svelte

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
import { MergeMethod } from '$lib/forge/interface/types';
33
import { persisted, type Persisted } from '@gitbutler/shared/persisted';
44
5-
import { ContextMenuItem, ContextMenuSection, DropdownButton } from '@gitbutler/ui';
5+
import { ContextMenuItem, ContextMenuSection, DropdownButton, Checkbox } from '@gitbutler/ui';
66
import type { ButtonProps } from '@gitbutler/ui';
77
88
interface Props {
99
projectId: string;
10-
onclick: (method: MergeMethod) => Promise<void>;
10+
onclick: (method: MergeMethod, bypassRules?: boolean) => Promise<void>;
1111
disabled?: boolean;
1212
wide?: boolean;
1313
tooltip?: string;
@@ -30,7 +30,13 @@
3030
return persisted<MergeMethod>(MergeMethod.Merge, key + projectId);
3131
}
3232
33+
function persistedBypassRules(projectId: string): Persisted<boolean> {
34+
const key = 'projectMergeBypassRules';
35+
return persisted<boolean>(false, key + projectId);
36+
}
37+
3338
const action = persistedAction(projectId);
39+
const bypassRules = persistedBypassRules(projectId);
3440
3541
let dropDown: ReturnType<typeof DropdownButton> | undefined;
3642
let loading = $state(false);
@@ -47,7 +53,7 @@
4753
onclick={async () => {
4854
loading = true;
4955
try {
50-
await onclick?.($action);
56+
await onclick?.($action, $bypassRules);
5157
} finally {
5258
loading = false;
5359
}
@@ -72,5 +78,32 @@
7278
/>
7379
{/each}
7480
</ContextMenuSection>
81+
<ContextMenuSection>
82+
<div class="bypass-checkbox-container">
83+
<Checkbox
84+
small
85+
bind:checked={$bypassRules}
86+
onchange={() => {
87+
// The reactive store will handle the change
88+
}}
89+
/>
90+
<span class="text-12 text-light bypass-label">Bypass branch protection rules</span>
91+
</div>
92+
</ContextMenuSection>
7593
{/snippet}
7694
</DropdownButton>
95+
96+
<style lang="postcss">
97+
.bypass-checkbox-container {
98+
display: flex;
99+
align-items: center;
100+
padding: 8px 12px;
101+
gap: 8px;
102+
cursor: pointer;
103+
}
104+
105+
.bypass-label {
106+
cursor: pointer;
107+
user-select: none;
108+
}
109+
</style>

apps/desktop/src/components/StackedPullRequestCard.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@
7070
await prService?.reopen(pr.number);
7171
}
7272
73-
async function handleMerge(method: MergeMethod) {
73+
async function handleMerge(method: MergeMethod, bypassRules?: boolean) {
7474
if (!pr) return;
75-
await prService?.merge(method, pr.number);
75+
await prService?.merge(method, pr.number, bypassRules);
7676
7777
// In a stack, after merging, update the new bottom PR target
7878
// base branch to master if necessary

apps/desktop/src/lib/forge/github/githubPrService.svelte.ts

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@ export class GitHubPrService implements ForgePrService {
8585
return this.api.endpoints.getPr.useQuery({ number }, options);
8686
}
8787

88-
async merge(method: MergeMethod, number: number) {
89-
await this.api.endpoints.mergePr.mutate({ method, number });
88+
async merge(method: MergeMethod, number: number, bypassRules?: boolean) {
89+
await this.api.endpoints.mergePr.mutate({ method, number, bypassRules });
9090
}
9191

9292
async reopen(number: number) {
@@ -194,23 +194,32 @@ function injectEndpoints(api: GitHubApi) {
194194
}),
195195
invalidatesTags: (result) => [invalidatesItem(ReduxTag.PullRequests, result?.number)]
196196
}),
197-
mergePr: build.mutation<void, { number: number; method: MergeMethod }>({
198-
queryFn: async ({ number, method: method }, api) => {
199-
const result = await ghQuery({
200-
domain: 'pulls',
201-
action: 'merge',
202-
parameters: { pull_number: number, merge_method: method },
203-
extra: api.extra
204-
});
197+
mergePr: build.mutation<void, { number: number; method: MergeMethod; bypassRules?: boolean }>(
198+
{
199+
queryFn: async ({ number, method: method, bypassRules }, api) => {
200+
const parameters: any = { pull_number: number, merge_method: method };
205201

206-
if (result.error) {
207-
return { error: result.error };
208-
}
202+
// Add bypass parameter if requested and available
203+
if (bypassRules) {
204+
parameters.bypass_required_pr_reviews = true;
205+
}
209206

210-
return { data: undefined };
211-
},
212-
invalidatesTags: [invalidatesList(ReduxTag.PullRequests)]
213-
}),
207+
const result = await ghQuery({
208+
domain: 'pulls',
209+
action: 'merge',
210+
parameters,
211+
extra: api.extra
212+
});
213+
214+
if (result.error) {
215+
return { error: result.error };
216+
}
217+
218+
return { data: undefined };
219+
},
220+
invalidatesTags: [invalidatesList(ReduxTag.PullRequests)]
221+
}
222+
),
214223
updatePr: build.mutation<
215224
void,
216225
{

apps/desktop/src/lib/forge/gitlab/gitlabPrService.svelte.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,8 @@ export class GitLabPrService implements ForgePrService {
7878
return this.api.endpoints.getPr.useQuery({ number }, options);
7979
}
8080

81-
async merge(method: MergeMethod, number: number) {
82-
await this.api.endpoints.mergePr.mutate({ method, number });
81+
async merge(method: MergeMethod, number: number, bypassRules?: boolean) {
82+
await this.api.endpoints.mergePr.mutate({ method, number, bypassRules });
8383
}
8484

8585
async reopen(number: number) {
@@ -141,11 +141,23 @@ function injectEndpoints(api: GitLabApi) {
141141
},
142142
invalidatesTags: (result) => [invalidatesItem(ReduxTag.GitLabPullRequests, result?.number)]
143143
}),
144-
mergePr: build.mutation<undefined, { number: number; method: MergeMethod }>({
145-
queryFn: async ({ number }, query) => {
144+
mergePr: build.mutation<
145+
undefined,
146+
{ number: number; method: MergeMethod; bypassRules?: boolean }
147+
>({
148+
queryFn: async ({ number, bypassRules }, query) => {
146149
try {
147150
const { api, upstreamProjectId } = gitlab(query.extra);
148-
await api.MergeRequests.merge(upstreamProjectId, number);
151+
const options: any = {};
152+
153+
// GitLab supports bypassing merge checks with force_remove_source_branch
154+
// and should_remove_source_branch options
155+
if (bypassRules) {
156+
options.skip_ci = true;
157+
options.merge_when_pipeline_succeeds = false;
158+
}
159+
160+
await api.MergeRequests.merge(upstreamProjectId, number, options);
149161
return { data: undefined };
150162
} catch (e: unknown) {
151163
return { error: toSerializable(e) };

apps/desktop/src/lib/forge/interface/forgePrService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export interface ForgePrService {
3232
baseBranchName,
3333
upstreamName
3434
}: CreatePullRequestArgs): Promise<PullRequest>;
35-
merge(method: MergeMethod, prNumber: number): Promise<void>;
35+
merge(method: MergeMethod, prNumber: number, bypassRules?: boolean): Promise<void>;
3636
reopen(prNumber: number): Promise<void>;
3737
update(
3838
prNumber: number,

0 commit comments

Comments
 (0)