Skip to content

Commit 7a89c61

Browse files
committed
Single branch project starting
1 parent 08aa662 commit 7a89c61

26 files changed

Lines changed: 894 additions & 219 deletions

File tree

apps/desktop/src/components/forge/BaseBranchSwitch.svelte

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script lang="ts">
22
import { BASE_BRANCH_SERVICE } from "$lib/baseBranch/baseBranchService.svelte";
3+
import { SETTINGS_SERVICE } from "$lib/settings/appSettings";
34
import { STACK_SERVICE } from "$lib/stacks/stackService.svelte";
45
import { inject } from "@gitbutler/core/context";
56
import { Button, CardGroup, InfoMessage, Select, SelectItem } from "@gitbutler/ui";
@@ -8,10 +9,12 @@
89
910
const stackService = inject(STACK_SERVICE);
1011
const baseBranchService = inject(BASE_BRANCH_SERVICE);
12+
const settingsStore = inject(SETTINGS_SERVICE).appSettings;
1113
const baseBranchQuery = $derived(baseBranchService.baseBranch(projectId));
1214
const baseBranch = $derived(baseBranchQuery.response);
1315
const remoteBranchesQuery = $derived(baseBranchService.remoteBranches(projectId));
1416
const [setBaseBranchTarget, targetBranchSwitch] = baseBranchService.setTarget;
17+
const [setBaseBranchTargetRef, targetRefSwitch] = baseBranchService.setTargetRef;
1518
1619
let selectedBranch = $derived(baseBranch?.branchName);
1720
let selectedRemote = $derived(baseBranch?.pushRemoteName);
@@ -28,8 +31,22 @@
2831
}));
2932
}
3033
34+
const switching = $derived(
35+
targetBranchSwitch.current.isLoading || targetRefSwitch.current.isLoading,
36+
);
37+
// With the singleBranch feature flag, only the target metadata is rewritten
38+
// and no branch is checked out, so avoid claiming a branch switch.
39+
const switchingLabel = $derived(
40+
$settingsStore?.featureFlags.singleBranch ? "Updating target..." : "Switching branches...",
41+
);
42+
3143
async function switchTarget(branch: string, pushRemote?: string) {
32-
await setBaseBranchTarget({ projectId, branch, pushRemote });
44+
if ($settingsStore?.featureFlags.singleBranch) {
45+
// Only update the target; the user keeps working on their current branch.
46+
await setBaseBranchTargetRef({ projectId, targetRef: `refs/remotes/${branch}`, pushRemote });
47+
} else {
48+
await setBaseBranchTarget({ projectId, branch, pushRemote });
49+
}
3350
}
3451
3552
async function onSetBaseBranchClick() {
@@ -113,14 +130,12 @@
113130
kind="outline"
114131
onclick={onSetBaseBranchClick}
115132
id="set-base-branch"
116-
loading={targetBranchSwitch.current.isLoading}
133+
loading={switching}
117134
disabled={(selectedBranch === baseBranch?.branchName &&
118135
selectedRemote === baseBranch?.pushRemoteName) ||
119136
targetChangeDisabled}
120137
>
121-
{targetBranchSwitch.current.isLoading
122-
? "Switching branches..."
123-
: "Update configuration"}
138+
{switching ? switchingLabel : "Update configuration"}
124139
</Button>
125140
{/if}
126141
</CardGroup.Item>

apps/desktop/src/components/onboarding/ProjectSetup.svelte

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import newZenSvg from "$lib/assets/illustrations/new-zen.svg?raw";
77
import { BASE_BRANCH_SERVICE } from "$lib/baseBranch/baseBranchService.svelte";
88
import { PROJECTS_SERVICE } from "$lib/project/projectsService";
9+
import { SETTINGS_SERVICE } from "$lib/settings/appSettings";
910
import { OnboardingEvent, POSTHOG_WRAPPER } from "$lib/telemetry/posthog";
1011
import { inject } from "@gitbutler/core/context";
1112
import { TestId } from "@gitbutler/ui";
@@ -21,18 +22,29 @@
2122
const projectsService = inject(PROJECTS_SERVICE);
2223
const baseService = inject(BASE_BRANCH_SERVICE);
2324
const posthog = inject(POSTHOG_WRAPPER);
25+
const settingsStore = inject(SETTINGS_SERVICE).appSettings;
2426
const projectQuery = $derived(projectsService.getProject(projectId));
2527
const [setBaseBranchTarget] = baseService.setTarget;
28+
const [setBaseBranchTargetRef] = baseService.setTargetRef;
2629
2730
async function setTarget(branch: string[]) {
2831
if (!branch[0] || branch[0] === "") return;
2932
3033
try {
31-
await setBaseBranchTarget({
32-
projectId: projectId,
33-
branch: branch[0],
34-
pushRemote: branch[1],
35-
});
34+
if ($settingsStore?.featureFlags.singleBranch) {
35+
// Only set the target; the user keeps working on their current branch.
36+
await setBaseBranchTargetRef({
37+
projectId: projectId,
38+
targetRef: `refs/remotes/${branch[0]}`,
39+
pushRemote: branch[1],
40+
});
41+
} else {
42+
await setBaseBranchTarget({
43+
projectId: projectId,
44+
branch: branch[0],
45+
pushRemote: branch[1],
46+
});
47+
}
3648
posthog.captureOnboarding(OnboardingEvent.SetTargetBranch);
3749
goto(`/${projectId}/`, { invalidateAll: true });
3850
} catch (e: unknown) {

apps/desktop/src/components/onboarding/ProjectSetupTarget.svelte

Lines changed: 54 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { projectLandDirectly } from "$lib/config/config";
77
import { GIT_CONFIG_SERVICE } from "$lib/config/gitConfigService";
88
import { PROJECTS_SERVICE } from "$lib/project/projectsService";
9+
import { SETTINGS_SERVICE } from "$lib/settings/appSettings";
910
import { combineResults } from "$lib/state/helpers";
1011
import { OnboardingEvent, POSTHOG_WRAPPER } from "$lib/telemetry/posthog";
1112
import { unique } from "$lib/utils/array";
@@ -35,6 +36,7 @@
3536
3637
const posthog = inject(POSTHOG_WRAPPER);
3738
const gitConfig = inject(GIT_CONFIG_SERVICE);
39+
const settingsStore = inject(SETTINGS_SERVICE).appSettings;
3840
3941
const gbConfig = $derived(gitConfig.gbConfig(projectId));
4042
const gerritMode = $derived(gbConfig.response?.gitbutlerGerritMode ?? false);
@@ -180,56 +182,62 @@
180182
<span class="text-12 clr-text-2">Push to main / Skip pull requests mode</span>
181183
</label>
182184

183-
<div
184-
class="project-setup__info"
185-
role="presentation"
186-
onclick={() => (showMoreInfo = !showMoreInfo)}
187-
>
188-
<div class="project-setup__fold-icon" class:rotate-icon={showMoreInfo}>
189-
<Icon name="chevron-right" />
190-
</div>
191-
192-
<div class="stack-v gap-6 full-width">
193-
<div class="project-setup__info__title">
194-
<svg
195-
width="16"
196-
height="13"
197-
viewBox="0 0 16 13"
198-
fill="none"
199-
xmlns="http://www.w3.org/2000/svg"
200-
>
201-
<path
202-
d="M2 12L3.5 7.5M14 12L12.5 7.5M12.5 7.5L11 3H5L3.5 7.5M12.5 7.5H3.5"
203-
stroke="#D96842"
204-
stroke-width="1.5"
205-
/>
206-
<path
207-
d="M1.24142 3H14.7586C14.8477 3 14.8923 2.89229 14.8293 2.82929L13.0293 1.02929C13.0105 1.01054 12.9851 1 12.9586 1H3.04142C3.0149 1 2.98946 1.01054 2.97071 1.02929L1.17071 2.82929C1.10771 2.89229 1.15233 3 1.24142 3Z"
208-
fill="#FF9774"
209-
stroke="#FF9774"
210-
stroke-width="1.5"
211-
/>
212-
</svg>
213-
214-
<h3 class="text-13 text-body text-semibold">
215-
GitButler switches your active branch to <span class="text-bold">gitbutler/workspace</span
216-
>
217-
</h3>
185+
<!-- With the singleBranch feature flag, setting the target only updates project
186+
metadata and the user stays on their current branch, so don't promise a
187+
switch to gitbutler/workspace. -->
188+
{#if !$settingsStore?.featureFlags.singleBranch}
189+
<div
190+
class="project-setup__info"
191+
role="presentation"
192+
onclick={() => (showMoreInfo = !showMoreInfo)}
193+
>
194+
<div class="project-setup__fold-icon" class:rotate-icon={showMoreInfo}>
195+
<Icon name="chevron-right" />
218196
</div>
219197

220-
{#if showMoreInfo}
221-
<p class="text-12 text-body" transition:slide={{ duration: 200 }}>
222-
In order to support working on multiple branches simultaneously, GitButler creates and
223-
automatically manages a special branch <span class="text-bold">gitbutler/workspace</span>.
224-
You can always switch back and forth as needed between normal git branches and the
225-
Gitbutler workspace.
226-
<Link href="https://docs.gitbutler.com/features/branch-management/integration-branch"
227-
>Learn more</Link
198+
<div class="stack-v gap-6 full-width">
199+
<div class="project-setup__info__title">
200+
<svg
201+
width="16"
202+
height="13"
203+
viewBox="0 0 16 13"
204+
fill="none"
205+
xmlns="http://www.w3.org/2000/svg"
228206
>
229-
</p>
230-
{/if}
207+
<path
208+
d="M2 12L3.5 7.5M14 12L12.5 7.5M12.5 7.5L11 3H5L3.5 7.5M12.5 7.5H3.5"
209+
stroke="#D96842"
210+
stroke-width="1.5"
211+
/>
212+
<path
213+
d="M1.24142 3H14.7586C14.8477 3 14.8923 2.89229 14.8293 2.82929L13.0293 1.02929C13.0105 1.01054 12.9851 1 12.9586 1H3.04142C3.0149 1 2.98946 1.01054 2.97071 1.02929L1.17071 2.82929C1.10771 2.89229 1.15233 3 1.24142 3Z"
214+
fill="#FF9774"
215+
stroke="#FF9774"
216+
stroke-width="1.5"
217+
/>
218+
</svg>
219+
220+
<h3 class="text-13 text-body text-semibold">
221+
GitButler switches your active branch to <span class="text-bold"
222+
>gitbutler/workspace</span
223+
>
224+
</h3>
225+
</div>
226+
227+
{#if showMoreInfo}
228+
<p class="text-12 text-body" transition:slide={{ duration: 200 }}>
229+
In order to support working on multiple branches simultaneously, GitButler creates and
230+
automatically manages a special branch <span class="text-bold">gitbutler/workspace</span
231+
>. You can always switch back and forth as needed between normal git branches and the
232+
Gitbutler workspace.
233+
<Link href="https://docs.gitbutler.com/features/branch-management/integration-branch"
234+
>Learn more</Link
235+
>
236+
</p>
237+
{/if}
238+
</div>
231239
</div>
232-
</div>
240+
{/if}
233241

234242
<div class="action-buttons">
235243
<Button kind="outline" onclick={deleteProjectAndGoBack}>Cancel</Button>

apps/desktop/src/lib/baseBranch/baseBranchService.svelte.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ export default class BaseBranchService {
8686
return this.backendApi.endpoints.setTarget.useMutation();
8787
}
8888

89+
get setTargetRef() {
90+
return this.backendApi.endpoints.setTargetRef.useMutation();
91+
}
92+
8993
get switchBackToWorkspace() {
9094
return this.backendApi.endpoints.switchBackToWorkspace.useMutation();
9195
}

apps/desktop/src/lib/branches/branchEndpoints.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,24 @@ export function buildBranchEndpoints(build: BackendEndpointBuilder) {
5757
invalidatesList(ReduxTag.StackDetails),
5858
],
5959
}),
60+
// Like setTarget, but only writes project metadata: the user stays on the
61+
// current branch instead of being moved into the GitButler workspace.
62+
setTargetRef: build.mutation<
63+
void,
64+
{ projectId: string; targetRef: string; pushRemote?: string }
65+
>({
66+
extraOptions: { command: "set_target_ref_and_init_project" },
67+
query: (args) => args,
68+
invalidatesTags: [
69+
invalidatesType(ReduxTag.ForgeProvider),
70+
invalidatesType(ReduxTag.BaseBranchData),
71+
invalidatesList(ReduxTag.Stacks),
72+
invalidatesList(ReduxTag.StackDetails),
73+
// No branch is checked out, so no `git/head` event refreshes the
74+
// operating mode - invalidate it explicitly.
75+
invalidatesList(ReduxTag.HeadMetadata),
76+
],
77+
}),
6078
switchBackToWorkspace: build.mutation<BaseBranch, { projectId: string }>({
6179
extraOptions: { command: "switch_back_to_workspace" },
6280
query: (args) => args,

apps/lite/electron/src/ipc.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,12 @@ export interface SetReviewTemplateParams {
325325
templatePath: string | null;
326326
}
327327

328+
export interface SetTargetRefAndInitProjectParams {
329+
projectId: string;
330+
targetRef: string;
331+
pushRemote?: string;
332+
}
333+
328334
export interface TearOffBranchParams {
329335
projectId: string;
330336
subjectBranch: string;
@@ -476,6 +482,7 @@ export interface LiteElectronApi {
476482
setReviewAutoMerge: (params: SetReviewAutoMergeParams) => Promise<void>;
477483
setReviewDraftiness: (params: SetReviewDraftinessParams) => Promise<void>;
478484
setReviewTemplate: (params: SetReviewTemplateParams) => Promise<void>;
485+
setTargetRefAndInitProject: (params: SetTargetRefAndInitProjectParams) => Promise<void>;
479486
showNativeMenu: (params: ShowNativeMenuParams) => Promise<string | null>;
480487
treeChangeDiffs: (params: TreeChangeDiffParams) => Promise<UnifiedPatch | null>;
481488
unapplyStack: (params: UnapplyStackParams) => Promise<void>;
@@ -556,6 +563,7 @@ export const liteIpcChannels = {
556563
setReviewAutoMerge: "workspace:set-review-auto-merge",
557564
setReviewDraftiness: "workspace:set-review-draftiness",
558565
setReviewTemplate: "workspace:set-review-template",
566+
setTargetRefAndInitProject: "workspace:set-target-ref-and-init-project",
559567
showNativeMenu: "lite:show-native-menu",
560568
treeChangeDiffs: "workspace:tree-change-diffs",
561569
unapplyStack: "workspace:unapply-stack",

apps/lite/electron/src/main.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import {
5252
type SetReviewAutoMergeParams,
5353
type SetReviewDraftinessParams,
5454
type SetReviewTemplateParams,
55+
type SetTargetRefAndInitProjectParams,
5556
type PeelRestoreSnapshotParams,
5657
type WorkspaceIntegrateUpstreamParams,
5758
type UpdateReviewFootersParams,
@@ -114,6 +115,7 @@ import {
114115
setReviewAutoMerge,
115116
setReviewDraftiness,
116117
setReviewTemplate,
118+
setTargetRefAndInitProject,
117119
getUndoTargetSnapshot,
118120
getRedoTargetSnapshot,
119121
peelRestoreSnapshot,
@@ -657,6 +659,11 @@ const registerIpcHandlers = (): void => {
657659
(_e, { projectId, templatePath }: SetReviewTemplateParams) =>
658660
setReviewTemplate(projectId, templatePath),
659661
);
662+
senderValidatingHandle(
663+
liteIpcChannels.setTargetRefAndInitProject,
664+
(_e, { projectId, targetRef, pushRemote }: SetTargetRefAndInitProjectParams) =>
665+
setTargetRefAndInitProject(projectId, targetRef, pushRemote ?? null),
666+
);
660667
senderValidatingHandle(
661668
liteIpcChannels.showNativeMenu,
662669
async (event, { items, position }: ShowNativeMenuParams) => {

apps/lite/electron/src/preload.cts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,8 @@ const api: LiteElectronApi = {
195195
ipcRenderer.invoke("workspace:set-review-draftiness", params) as Promise<void>,
196196
setReviewTemplate: (params) =>
197197
ipcRenderer.invoke("workspace:set-review-template", params) as Promise<void>,
198+
setTargetRefAndInitProject: (params) =>
199+
ipcRenderer.invoke("workspace:set-target-ref-and-init-project", params) as Promise<void>,
198200
showNativeMenu: (params) =>
199201
ipcRenderer.invoke("lite:show-native-menu", params) as Promise<string | null>,
200202
treeChangeDiffs: (params) =>

0 commit comments

Comments
 (0)