From 052713d133120ee98726472937d381d81c7f5d6b Mon Sep 17 00:00:00 2001 From: Sam Autrey Date: Mon, 6 Jul 2026 16:55:52 -0500 Subject: [PATCH] feat(desktop): add setting to disable PR stack footer Adds a Reviews.enableStackFooter setting (default true) that gates the GitButler stack list footer appended to PR/MR descriptions. Exposed as a toggle in integration settings and honored at every footer writer: PR creation and stack/branch drag operations. --- .../components/forge/ReviewCreation.svelte | 3 ++- .../settings/IntegrationsSettings.svelte | 21 +++++++++++++++++++ .../dropHandlers/branchDropHandler.ts | 8 ++++++- .../dragging/dropHandlers/stackDropHandler.ts | 7 +++++++ crates/but-settings/assets/defaults.jsonc | 4 +++- crates/but-settings/src/api.rs | 4 ++++ crates/but-settings/src/app_settings.rs | 2 ++ crates/but-testsupport/src/sandbox.rs | 1 + .../but-sdk/src/generated/graph/index.d.ts | 3 +++ .../but-sdk/src/generated/linear/index.d.ts | 3 +++ 10 files changed, 53 insertions(+), 3 deletions(-) diff --git a/apps/desktop/src/components/forge/ReviewCreation.svelte b/apps/desktop/src/components/forge/ReviewCreation.svelte index aee97304454..50238f7a20d 100644 --- a/apps/desktop/src/components/forge/ReviewCreation.svelte +++ b/apps/desktop/src/components/forge/ReviewCreation.svelte @@ -302,7 +302,8 @@ // If we now have two or more pull requests we add a stack table to the description. prNumbers[currentIndex] = pr.number; const definedPrNumbers = prNumbers.filter(isDefined); - if (definedPrNumbers.length > 0) { + const enableStackFooter = $appSettings?.reviews.enableStackFooter ?? true; + if (enableStackFooter && definedPrNumbers.length > 0) { updatePrStackInfo(prService, projectId, definedPrNumbers, forgeInfo?.unit.symbol); } diff --git a/apps/desktop/src/components/settings/IntegrationsSettings.svelte b/apps/desktop/src/components/settings/IntegrationsSettings.svelte index aedd75addc6..4d5f7b55f24 100644 --- a/apps/desktop/src/components/settings/IntegrationsSettings.svelte +++ b/apps/desktop/src/components/settings/IntegrationsSettings.svelte @@ -14,6 +14,12 @@ autoFillPrDescriptionFromCommit: !$appSettings?.reviews.autoFillPrDescriptionFromCommit, }); } + + async function toggleStackFooter() { + await settingsService.updateReviews({ + enableStackFooter: !($appSettings?.reviews.enableStackFooter ?? true), + }); + } @@ -36,4 +42,19 @@ /> {/snippet} + + {#snippet title()} + Add GitButler stack footer to PR/MR descriptions + {/snippet} + {#snippet caption()} + Append the list of pull requests in the same stack to the description. + {/snippet} + {#snippet actions()} + + {/snippet} + diff --git a/apps/desktop/src/lib/dragging/dropHandlers/branchDropHandler.ts b/apps/desktop/src/lib/dragging/dropHandlers/branchDropHandler.ts index 387f61f9ffd..c0b0a550bd0 100644 --- a/apps/desktop/src/lib/dragging/dropHandlers/branchDropHandler.ts +++ b/apps/desktop/src/lib/dragging/dropHandlers/branchDropHandler.ts @@ -1,10 +1,12 @@ import { FileChangeDropData, FolderChangeDropData, HunkDropDataV3 } from "$lib/dragging/draggables"; import { updateStackPrs } from "$lib/forge/shared/prFooter"; import { UNCOMMITTED_SERVICE } from "$lib/selection/uncommittedService.svelte"; +import { SETTINGS_SERVICE } from "$lib/settings/appSettings"; import { normalizeReferenceSubject } from "$lib/stacks/commitMovePlacement"; import { STACK_SERVICE } from "$lib/stacks/stackService.svelte"; import { UI_STATE } from "$lib/state/uiState.svelte"; import { inject } from "@gitbutler/core/context"; +import { get } from "svelte/store"; import type { DropResult } from "$lib/dragging/dropResult"; import type { DropzoneHandler } from "$lib/dragging/handler"; import type { PrService } from "$lib/forge/prService.svelte"; @@ -27,6 +29,7 @@ export class BranchDropData { export class MoveBranchDzHandler implements DropzoneHandler { private readonly stackService = inject(STACK_SERVICE); + private readonly settingsService = inject(SETTINGS_SERVICE); constructor( private readonly prService: PrService | undefined, @@ -58,7 +61,10 @@ export class MoveBranchDzHandler implements DropzoneHandler { targetBranch: normalizeReferenceSubject(this.branchName), }); - if (this.prService && this.baseBranchName) { + const enableStackFooter = + get(this.settingsService.appSettings)?.reviews.enableStackFooter ?? true; + + if (enableStackFooter && this.prService && this.baseBranchName) { if (!sourceStackDeleted) { const branchDetails = await this.stackService.fetchBranches(this.projectId, data.stackId); await updateStackPrs( diff --git a/apps/desktop/src/lib/dragging/dropHandlers/stackDropHandler.ts b/apps/desktop/src/lib/dragging/dropHandlers/stackDropHandler.ts index f7a03f5119b..5faed2a0f88 100644 --- a/apps/desktop/src/lib/dragging/dropHandlers/stackDropHandler.ts +++ b/apps/desktop/src/lib/dragging/dropHandlers/stackDropHandler.ts @@ -9,11 +9,14 @@ import { BranchDropData } from "$lib/dragging/dropHandlers/branchDropHandler"; import { CommitDropData } from "$lib/dragging/dropHandlers/commitDropHandler"; import { classify } from "$lib/error/errorClassification"; import { unstackPRs, updateStackPrs } from "$lib/forge/shared/prFooter"; +import { SETTINGS_SERVICE } from "$lib/settings/appSettings"; import { toCommitMovePlacement } from "$lib/stacks/commitMovePlacement"; import StackMacros from "$lib/stacks/macros"; import { toMoveBranchWarning } from "$lib/stacks/stack"; import { withStackBusy } from "$lib/state/uiState.svelte"; +import { inject } from "@gitbutler/core/context"; import { untrack } from "svelte"; +import { get } from "svelte/store"; import type { DropResult } from "$lib/dragging/dropResult"; import type { DropzoneHandler } from "$lib/dragging/handler"; import type { PrService } from "$lib/forge/prService.svelte"; @@ -26,6 +29,7 @@ import type { HunkAssignmentTarget } from "@gitbutler/but-sdk"; /** Handler when drop changes on a special outside lanes dropzone. */ export class OutsideLaneDzHandler implements DropzoneHandler { private macros: StackMacros; + private readonly settingsService = inject(SETTINGS_SERVICE); constructor( private stackService: StackService, @@ -282,6 +286,9 @@ export class OutsideLaneDzHandler implements DropzoneHandler { } await unstackPRs(this.prService, this.projectId, [data.prNumber], this.baseBranchName); + const enableStackFooter = + get(this.settingsService.appSettings)?.reviews.enableStackFooter ?? true; + if (!enableStackFooter) return; const branchDetails = await this.stackService.fetchBranches(this.projectId, data.stackId); await updateStackPrs( this.prService, diff --git a/crates/but-settings/assets/defaults.jsonc b/crates/but-settings/assets/defaults.jsonc index c56402d5d7f..bb1879d5ad7 100644 --- a/crates/but-settings/assets/defaults.jsonc +++ b/crates/but-settings/assets/defaults.jsonc @@ -61,7 +61,9 @@ // Settings related to code reviews and pull requests. "reviews": { // Whether to auto-fill PR title and description from the first commit when a branch has only one commit. - "autoFillPrDescriptionFromCommit": true + "autoFillPrDescriptionFromCommit": true, + // Whether to append the GitButler stack footer to pull request descriptions. + "enableStackFooter": true }, // UI settings. "ui": { diff --git a/crates/but-settings/src/api.rs b/crates/but-settings/src/api.rs index 74ce1801610..7431ef3be43 100644 --- a/crates/but-settings/src/api.rs +++ b/crates/but-settings/src/api.rs @@ -38,6 +38,7 @@ pub struct ClaudeUpdate { /// Update request for [`crate::app_settings::Reviews`]. pub struct ReviewsUpdate { pub auto_fill_pr_description_from_commit: Option, + pub enable_stack_footer: Option, } #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] @@ -163,6 +164,9 @@ impl AppSettingsWithDiskSync { settings.reviews.auto_fill_pr_description_from_commit = auto_fill_pr_description_from_commit; } + if let Some(enable_stack_footer) = update.enable_stack_footer { + settings.reviews.enable_stack_footer = enable_stack_footer; + } settings.save() } diff --git a/crates/but-settings/src/app_settings.rs b/crates/but-settings/src/app_settings.rs index c7c3ec4ab16..da3c67e2684 100644 --- a/crates/but-settings/src/app_settings.rs +++ b/crates/but-settings/src/app_settings.rs @@ -96,6 +96,8 @@ but_schemars::register_sdk_type!(Claude); pub struct Reviews { /// Whether to auto-fill PR title and description from the first commit when a branch has only one commit. pub auto_fill_pr_description_from_commit: bool, + /// Whether to append the GitButler stack footer to pull request descriptions. + pub enable_stack_footer: bool, } but_schemars::register_sdk_type!(Reviews); diff --git a/crates/but-testsupport/src/sandbox.rs b/crates/but-testsupport/src/sandbox.rs index 1c7d9b692c4..d30d3897c6f 100644 --- a/crates/but-testsupport/src/sandbox.rs +++ b/crates/but-testsupport/src/sandbox.rs @@ -532,6 +532,7 @@ impl Sandbox { }, reviews: Reviews { auto_fill_pr_description_from_commit: false, + enable_stack_footer: true, }, ui: UiSettings { use_native_title_bar: false, diff --git a/packages/but-sdk/src/generated/graph/index.d.ts b/packages/but-sdk/src/generated/graph/index.d.ts index 92ec51d5253..38375fa7b1a 100644 --- a/packages/but-sdk/src/generated/graph/index.d.ts +++ b/packages/but-sdk/src/generated/graph/index.d.ts @@ -630,6 +630,7 @@ export interface WatcherEvent { * `callback` receives watcher events shaped as `{ name, payload }`. */ export declare function watcherStart(projectId: string, callback: ((err: Error | null, arg: WatcherEvent) => any)): Promise + // Auto-generated by but-ts. Do not edit manually. // Generated from JSON schemas registered by #[but_api] functions. @@ -2416,6 +2417,8 @@ export type ReviewTemplateInfo = { export type Reviews = { /** Whether to auto-fill PR title and description from the first commit when a branch has only one commit. */ autoFillPrDescriptionFromCommit: boolean; + /** Whether to append the GitButler stack footer to pull request descriptions. */ + enableStackFooter: boolean; }; /** A segment of a commit graph, representing a set of commits exclusively. */ diff --git a/packages/but-sdk/src/generated/linear/index.d.ts b/packages/but-sdk/src/generated/linear/index.d.ts index b874fd1df14..b1ed6f4d3a1 100644 --- a/packages/but-sdk/src/generated/linear/index.d.ts +++ b/packages/but-sdk/src/generated/linear/index.d.ts @@ -630,6 +630,7 @@ export interface WatcherEvent { * `callback` receives watcher events shaped as `{ name, payload }`. */ export declare function watcherStart(projectId: string, callback: ((err: Error | null, arg: WatcherEvent) => any)): Promise + // Auto-generated by but-ts. Do not edit manually. // Generated from JSON schemas registered by #[but_api] functions. @@ -2416,6 +2417,8 @@ export type ReviewTemplateInfo = { export type Reviews = { /** Whether to auto-fill PR title and description from the first commit when a branch has only one commit. */ autoFillPrDescriptionFromCommit: boolean; + /** Whether to append the GitButler stack footer to pull request descriptions. */ + enableStackFooter: boolean; }; /** A segment of a commit graph, representing a set of commits exclusively. */