Skip to content

Commit f9dafa0

Browse files
authored
Merge pull request #9988 from gitbutlerapp/feature-staging-behavior
Settings: add staging behavior feature flag and UI
2 parents afecc3d + 2288ce0 commit f9dafa0

File tree

5 files changed

+157
-4
lines changed

5 files changed

+157
-4
lines changed

apps/desktop/src/components/StackView.svelte

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import Resizer from '$components/Resizer.svelte';
1111
import SelectionView from '$components/SelectionView.svelte';
1212
import WorktreeChanges from '$components/WorktreeChanges.svelte';
13+
import { stagingBehaviorFeature } from '$lib/config/uiFeatureFlags';
1314
import { isParsedError } from '$lib/error/parser';
1415
import { DefinedFocusable } from '$lib/focus/focusManager.svelte';
1516
import { focusable } from '$lib/focus/focusable.svelte';
@@ -153,7 +154,7 @@
153154
}
154155
} as const;
155156
156-
function checkFilesForCommit() {
157+
function checkSelectedFilesForCommit() {
157158
const stackAssignments = stackId ? uncommittedService.getAssignmentsByStackId(stackId) : [];
158159
if (stackId && stackAssignments.length > 0) {
159160
// If there are assignments for this stack, we check those.
@@ -182,6 +183,41 @@
182183
}
183184
}
184185
186+
function uncheckAll() {
187+
if (stackId) {
188+
uncommittedService.uncheckAll(stackId);
189+
}
190+
uncommittedService.uncheckAll(null);
191+
}
192+
193+
function checkAllFiles() {
194+
const stackAssignments = stackId ? uncommittedService.getAssignmentsByStackId(stackId) : [];
195+
if (stackId && stackAssignments.length > 0) {
196+
// If there are assignments for this stack, we check those.
197+
uncommittedService.checkAll(stackId);
198+
// Uncheck the unassigned files.
199+
uncommittedService.uncheckAll(null);
200+
return;
201+
}
202+
203+
uncommittedService.checkAll(null);
204+
}
205+
206+
function checkFilesForCommit(): true {
207+
switch ($stagingBehaviorFeature) {
208+
case 'all':
209+
checkAllFiles();
210+
return true;
211+
case 'selection':
212+
// We only check the selected files.
213+
checkSelectedFilesForCommit();
214+
return true;
215+
case 'none':
216+
uncheckAll();
217+
return true;
218+
}
219+
}
220+
185221
function startCommit(branchName: string) {
186222
projectState.exclusiveAction.set({
187223
type: 'commit',

apps/desktop/src/components/UnassignedView.svelte

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import WorktreeTipsFooter from '$components/WorktreeTipsFooter.svelte';
66
import noChanges from '$lib/assets/illustrations/no-changes.svg?raw';
77
import { SETTINGS_SERVICE } from '$lib/config/appSettingsV2';
8+
import { stagingBehaviorFeature } from '$lib/config/uiFeatureFlags';
89
import { DefinedFocusable } from '$lib/focus/focusManager.svelte';
910
import { INTELLIGENT_SCROLLING_SERVICE } from '$lib/intelligentScrolling/service';
1011
import { ID_SELECTION } from '$lib/selection/idSelection.svelte';
@@ -58,7 +59,7 @@
5859
unassignedSidebaFolded.set(true);
5960
}
6061
61-
function checkFilesForCommit() {
62+
function checkSelectedFilesForCommit() {
6263
const selectionId = createWorktreeSelection({});
6364
const selectedPaths = idSelection.values(selectionId).map((entry) => entry.path);
6465
@@ -69,6 +70,29 @@
6970
uncommittedService.checkAll(null);
7071
}
7172
}
73+
74+
function uncheckAll() {
75+
uncommittedService.uncheckAll(null);
76+
}
77+
78+
function checkAllFiles() {
79+
uncommittedService.checkAll(null);
80+
}
81+
82+
function checkFilesForCommit(): true {
83+
switch ($stagingBehaviorFeature) {
84+
case 'all':
85+
checkAllFiles();
86+
return true;
87+
case 'selection':
88+
// We only check the selected files.
89+
checkSelectedFilesForCommit();
90+
return true;
91+
case 'none':
92+
uncheckAll();
93+
return true;
94+
}
95+
}
7296
</script>
7397

7498
{#snippet foldButton()}

apps/desktop/src/components/projectSettings/AppearanceSettings.svelte

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
<script lang="ts">
22
import ThemeSelector from '$components/ThemeSelector.svelte';
3-
import { autoSelectBranchNameFeature } from '$lib/config/uiFeatureFlags';
3+
import {
4+
autoSelectBranchNameFeature,
5+
stagingBehaviorFeature,
6+
type StagingBehavior
7+
} from '$lib/config/uiFeatureFlags';
48
import { SETTINGS, type ScrollbarVisilitySettings } from '$lib/settings/userSettings';
59
import { inject } from '@gitbutler/shared/context';
610
import {
@@ -37,6 +41,13 @@
3741
scrollbarVisibilityState: selectedScrollbarVisibility
3842
}));
3943
}
44+
45+
function onStagingBehaviorFormChange(form: HTMLFormElement) {
46+
const formData = new FormData(form);
47+
const selectedStagingBehavior = formData.get('stagingBehaviorType') as StagingBehavior | null;
48+
if (!selectedStagingBehavior) return;
49+
stagingBehaviorFeature.set(selectedStagingBehavior);
50+
}
4051
</script>
4152

4253
<SectionCard>
@@ -283,3 +294,67 @@
283294
/>
284295
{/snippet}
285296
</SectionCard>
297+
298+
<form class="stack-v" onchange={(e) => onStagingBehaviorFormChange(e.currentTarget)}>
299+
<SectionCard roundedBottom={false} orientation="row" labelFor="stage-all">
300+
{#snippet title()}
301+
Stage all files
302+
{/snippet}
303+
{#snippet caption()}
304+
Stage all files assigned to the stack on commit. If no files are staged, all unassinged files
305+
will be staged.
306+
{/snippet}
307+
{#snippet actions()}
308+
<RadioButton
309+
name="stagingBehaviorType"
310+
value="all"
311+
id="stage-all"
312+
checked={$stagingBehaviorFeature === 'all'}
313+
/>
314+
{/snippet}
315+
</SectionCard>
316+
317+
<SectionCard
318+
roundedTop={false}
319+
roundedBottom={false}
320+
orientation="row"
321+
labelFor="stage-selection"
322+
>
323+
{#snippet title()}
324+
Stage selected files
325+
{/snippet}
326+
{#snippet caption()}
327+
Stage the selected assigned files to the stack on commit. If no files are selected, stage all
328+
files. If there are no assigned files, stage all selected unassigned files.
329+
<br />
330+
Aaand if no files are selected, stage all unassigned files.
331+
{/snippet}
332+
{#snippet actions()}
333+
<RadioButton
334+
name="stagingBehaviorType"
335+
value="selection"
336+
id="stage-selection"
337+
checked={$stagingBehaviorFeature === 'selection'}
338+
/>
339+
{/snippet}
340+
</SectionCard>
341+
342+
<SectionCard roundedTop={false} orientation="row" labelFor="stage-none">
343+
{#snippet title()}
344+
Don't stage files automatically
345+
{/snippet}
346+
{#snippet caption()}
347+
Do not stage any files automatically.
348+
<br />
349+
You're more of a DIY developer in that way.
350+
{/snippet}
351+
{#snippet actions()}
352+
<RadioButton
353+
name="stagingBehaviorType"
354+
value="none"
355+
id="stage-none"
356+
checked={$stagingBehaviorFeature === 'none'}
357+
/>
358+
{/snippet}
359+
</SectionCard>
360+
</form>

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@ export const ircEnabled = persistWithExpiration(false, 'feature-irc', 1440 * 30)
1111
export const ircServer = persistWithExpiration('', 'feature-irc-server', 1440 * 30);
1212
export const rewrapCommitMessage = persistWithExpiration(true, 'rewrap-commit-msg', 1440 * 30);
1313
export const codegenEnabled = persistWithExpiration(false, 'feature-codegen', 1440 * 30);
14+
export type StagingBehavior = 'all' | 'selection' | 'none';
15+
export const stagingBehaviorFeature = persisted<StagingBehavior>('all', 'feature-staging-behavior');

apps/desktop/src/lib/soup/commitAnalytics.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import { autoSelectBranchNameFeature, stagingBehaviorFeature } from '$lib/config/uiFeatureFlags';
12
import { getFilterCountMap, getStackTargetTypeCountMap, type WorkspaceRule } from '$lib/rules/rule';
23
import { StackService } from '$lib/stacks/stackService.svelte';
34
import { UiState } from '$lib/state/uiState.svelte';
45
import { WorktreeService } from '$lib/worktree/worktreeService.svelte';
56
import { InjectionToken } from '@gitbutler/shared/context';
7+
import { get } from 'svelte/store';
68
import type { Commit } from '$lib/branches/v3';
79
import type { HunkAssignment } from '$lib/hunks/hunk';
810
import type RulesService from '$lib/rules/rulesService.svelte';
@@ -85,7 +87,9 @@ export class CommitAnalytics {
8587
// Total number of files that have not been assigned
8688
totalUnassignedFiles: this.getUnassignedFiles(assignments).length,
8789
// Rule metrics
88-
...this.getRuleMetrics(rules)
90+
...this.getRuleMetrics(rules),
91+
// Behavior metrics
92+
...this.getBehaviorMetrics()
8993
};
9094
} catch (error) {
9195
console.error('Failed to fetch commit analytics:', error);
@@ -173,6 +177,18 @@ export class CommitAnalytics {
173177

174178
return namespaceProps(ruleMetrics, 'workspaceRules');
175179
}
180+
181+
private getBehaviorMetrics(): EventProperties {
182+
// Placeholder for future behavior metrics
183+
const stagingBehavior = get(stagingBehaviorFeature);
184+
const autoSelectBranchName = get(autoSelectBranchNameFeature);
185+
const behaviorMetrics = {
186+
stagingBehavior,
187+
autoSelectBranchName
188+
};
189+
190+
return namespaceProps(behaviorMetrics, 'behavior');
191+
}
176192
}
177193

178194
function namespaceProps(props: EventProperties, namespace: string): EventProperties {

0 commit comments

Comments
 (0)