Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions apps/desktop/src/components/forge/BaseBranchSwitch.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts">
import { BASE_BRANCH_SERVICE } from "$lib/baseBranch/baseBranchService.svelte";
import { SETTINGS_SERVICE } from "$lib/settings/appSettings";
import { STACK_SERVICE } from "$lib/stacks/stackService.svelte";
import { inject } from "@gitbutler/core/context";
import { Button, CardGroup, InfoMessage, Select, SelectItem } from "@gitbutler/ui";
Expand All @@ -8,10 +9,12 @@

const stackService = inject(STACK_SERVICE);
const baseBranchService = inject(BASE_BRANCH_SERVICE);
const settingsStore = inject(SETTINGS_SERVICE).appSettings;
const baseBranchQuery = $derived(baseBranchService.baseBranch(projectId));
const baseBranch = $derived(baseBranchQuery.response);
const remoteBranchesQuery = $derived(baseBranchService.remoteBranches(projectId));
const [setBaseBranchTarget, targetBranchSwitch] = baseBranchService.setTarget;
const [setBaseBranchTargetRef, targetRefSwitch] = baseBranchService.setTargetRef;

let selectedBranch = $derived(baseBranch?.branchName);
let selectedRemote = $derived(baseBranch?.pushRemoteName);
Expand All @@ -28,8 +31,22 @@
}));
}

const switching = $derived(
targetBranchSwitch.current.isLoading || targetRefSwitch.current.isLoading,
);
// With the singleBranch feature flag, only the target metadata is rewritten
// and no branch is checked out, so avoid claiming a branch switch.
const switchingLabel = $derived(
$settingsStore?.featureFlags.singleBranch ? "Updating target..." : "Switching branches...",
);

async function switchTarget(branch: string, pushRemote?: string) {
await setBaseBranchTarget({ projectId, branch, pushRemote });
if ($settingsStore?.featureFlags.singleBranch) {
// Only update the target; the user keeps working on their current branch.
await setBaseBranchTargetRef({ projectId, targetRef: `refs/remotes/${branch}`, pushRemote });
} else {
await setBaseBranchTarget({ projectId, branch, pushRemote });
}
}

async function onSetBaseBranchClick() {
Expand Down Expand Up @@ -113,14 +130,12 @@
kind="outline"
onclick={onSetBaseBranchClick}
id="set-base-branch"
loading={targetBranchSwitch.current.isLoading}
loading={switching}
disabled={(selectedBranch === baseBranch?.branchName &&
selectedRemote === baseBranch?.pushRemoteName) ||
targetChangeDisabled}
>
{targetBranchSwitch.current.isLoading
? "Switching branches..."
: "Update configuration"}
{switching ? switchingLabel : "Update configuration"}
</Button>
{/if}
</CardGroup.Item>
Expand Down
22 changes: 17 additions & 5 deletions apps/desktop/src/components/onboarding/ProjectSetup.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import newZenSvg from "$lib/assets/illustrations/new-zen.svg?raw";
import { BASE_BRANCH_SERVICE } from "$lib/baseBranch/baseBranchService.svelte";
import { PROJECTS_SERVICE } from "$lib/project/projectsService";
import { SETTINGS_SERVICE } from "$lib/settings/appSettings";
import { OnboardingEvent, POSTHOG_WRAPPER } from "$lib/telemetry/posthog";
import { inject } from "@gitbutler/core/context";
import { TestId } from "@gitbutler/ui";
Expand All @@ -21,18 +22,29 @@
const projectsService = inject(PROJECTS_SERVICE);
const baseService = inject(BASE_BRANCH_SERVICE);
const posthog = inject(POSTHOG_WRAPPER);
const settingsStore = inject(SETTINGS_SERVICE).appSettings;
const projectQuery = $derived(projectsService.getProject(projectId));
const [setBaseBranchTarget] = baseService.setTarget;
const [setBaseBranchTargetRef] = baseService.setTargetRef;

async function setTarget(branch: string[]) {
if (!branch[0] || branch[0] === "") return;

try {
await setBaseBranchTarget({
projectId: projectId,
branch: branch[0],
pushRemote: branch[1],
});
if ($settingsStore?.featureFlags.singleBranch) {
// Only set the target; the user keeps working on their current branch.
await setBaseBranchTargetRef({
projectId: projectId,
targetRef: `refs/remotes/${branch[0]}`,
pushRemote: branch[1],
});
} else {
await setBaseBranchTarget({
projectId: projectId,
branch: branch[0],
pushRemote: branch[1],
});
}
posthog.captureOnboarding(OnboardingEvent.SetTargetBranch);
goto(`/${projectId}/`, { invalidateAll: true });
} catch (e: unknown) {
Expand Down
100 changes: 54 additions & 46 deletions apps/desktop/src/components/onboarding/ProjectSetupTarget.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import { projectLandDirectly } from "$lib/config/config";
import { GIT_CONFIG_SERVICE } from "$lib/config/gitConfigService";
import { PROJECTS_SERVICE } from "$lib/project/projectsService";
import { SETTINGS_SERVICE } from "$lib/settings/appSettings";
import { combineResults } from "$lib/state/helpers";
import { OnboardingEvent, POSTHOG_WRAPPER } from "$lib/telemetry/posthog";
import { unique } from "$lib/utils/array";
Expand Down Expand Up @@ -35,6 +36,7 @@

const posthog = inject(POSTHOG_WRAPPER);
const gitConfig = inject(GIT_CONFIG_SERVICE);
const settingsStore = inject(SETTINGS_SERVICE).appSettings;

const gbConfig = $derived(gitConfig.gbConfig(projectId));
const gerritMode = $derived(gbConfig.response?.gitbutlerGerritMode ?? false);
Expand Down Expand Up @@ -180,56 +182,62 @@
<span class="text-12 clr-text-2">Push to main / Skip pull requests mode</span>
</label>

<div
class="project-setup__info"
role="presentation"
onclick={() => (showMoreInfo = !showMoreInfo)}
>
<div class="project-setup__fold-icon" class:rotate-icon={showMoreInfo}>
<Icon name="chevron-right" />
</div>

<div class="stack-v gap-6 full-width">
<div class="project-setup__info__title">
<svg
width="16"
height="13"
viewBox="0 0 16 13"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2 12L3.5 7.5M14 12L12.5 7.5M12.5 7.5L11 3H5L3.5 7.5M12.5 7.5H3.5"
stroke="#D96842"
stroke-width="1.5"
/>
<path
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"
fill="#FF9774"
stroke="#FF9774"
stroke-width="1.5"
/>
</svg>

<h3 class="text-13 text-body text-semibold">
GitButler switches your active branch to <span class="text-bold">gitbutler/workspace</span
>
</h3>
<!-- With the singleBranch feature flag, setting the target only updates project
metadata and the user stays on their current branch, so don't promise a
switch to gitbutler/workspace. -->
{#if !$settingsStore?.featureFlags.singleBranch}
<div
class="project-setup__info"
role="presentation"
onclick={() => (showMoreInfo = !showMoreInfo)}
>
<div class="project-setup__fold-icon" class:rotate-icon={showMoreInfo}>
<Icon name="chevron-right" />
</div>

{#if showMoreInfo}
<p class="text-12 text-body" transition:slide={{ duration: 200 }}>
In order to support working on multiple branches simultaneously, GitButler creates and
automatically manages a special branch <span class="text-bold">gitbutler/workspace</span>.
You can always switch back and forth as needed between normal git branches and the
Gitbutler workspace.
<Link href="https://docs.gitbutler.com/features/branch-management/integration-branch"
>Learn more</Link
<div class="stack-v gap-6 full-width">
<div class="project-setup__info__title">
<svg
width="16"
height="13"
viewBox="0 0 16 13"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
</p>
{/if}
<path
d="M2 12L3.5 7.5M14 12L12.5 7.5M12.5 7.5L11 3H5L3.5 7.5M12.5 7.5H3.5"
stroke="#D96842"
stroke-width="1.5"
/>
<path
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"
fill="#FF9774"
stroke="#FF9774"
stroke-width="1.5"
/>
</svg>

<h3 class="text-13 text-body text-semibold">
GitButler switches your active branch to <span class="text-bold"
>gitbutler/workspace</span
>
</h3>
</div>

{#if showMoreInfo}
<p class="text-12 text-body" transition:slide={{ duration: 200 }}>
In order to support working on multiple branches simultaneously, GitButler creates and
automatically manages a special branch <span class="text-bold">gitbutler/workspace</span
>. You can always switch back and forth as needed between normal git branches and the
Gitbutler workspace.
<Link href="https://docs.gitbutler.com/features/branch-management/integration-branch"
>Learn more</Link
>
</p>
{/if}
</div>
</div>
</div>
{/if}

<div class="action-buttons">
<Button kind="outline" onclick={deleteProjectAndGoBack}>Cancel</Button>
Expand Down
4 changes: 4 additions & 0 deletions apps/desktop/src/lib/baseBranch/baseBranchService.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ export default class BaseBranchService {
return this.backendApi.endpoints.setTarget.useMutation();
}

get setTargetRef() {
return this.backendApi.endpoints.setTargetRef.useMutation();
}

get switchBackToWorkspace() {
return this.backendApi.endpoints.switchBackToWorkspace.useMutation();
}
Expand Down
18 changes: 18 additions & 0 deletions apps/desktop/src/lib/branches/branchEndpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,24 @@ export function buildBranchEndpoints(build: BackendEndpointBuilder) {
invalidatesList(ReduxTag.StackDetails),
],
}),
// Like setTarget, but only writes project metadata: the user stays on the
// current branch instead of being moved into the GitButler workspace.
setTargetRef: build.mutation<
void,
{ projectId: string; targetRef: string; pushRemote?: string }
>({
extraOptions: { command: "set_target_ref_and_init_project" },
query: (args) => args,
invalidatesTags: [
invalidatesType(ReduxTag.ForgeProvider),
invalidatesType(ReduxTag.BaseBranchData),
invalidatesList(ReduxTag.Stacks),
invalidatesList(ReduxTag.StackDetails),
// No branch is checked out, so no `git/head` event refreshes the
// operating mode - invalidate it explicitly.
invalidatesList(ReduxTag.HeadMetadata),
],
}),
switchBackToWorkspace: build.mutation<BaseBranch, { projectId: string }>({
extraOptions: { command: "switch_back_to_workspace" },
query: (args) => args,
Expand Down
8 changes: 8 additions & 0 deletions apps/lite/electron/src/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,12 @@ export interface SetReviewTemplateParams {
templatePath: string | null;
}

export interface SetTargetRefAndInitProjectParams {
projectId: string;
targetRef: string;
pushRemote?: string;
}

export interface TearOffBranchParams {
projectId: string;
subjectBranch: string;
Expand Down Expand Up @@ -476,6 +482,7 @@ export interface LiteElectronApi {
setReviewAutoMerge: (params: SetReviewAutoMergeParams) => Promise<void>;
setReviewDraftiness: (params: SetReviewDraftinessParams) => Promise<void>;
setReviewTemplate: (params: SetReviewTemplateParams) => Promise<void>;
setTargetRefAndInitProject: (params: SetTargetRefAndInitProjectParams) => Promise<void>;
showNativeMenu: (params: ShowNativeMenuParams) => Promise<string | null>;
treeChangeDiffs: (params: TreeChangeDiffParams) => Promise<UnifiedPatch | null>;
unapplyStack: (params: UnapplyStackParams) => Promise<void>;
Expand Down Expand Up @@ -556,6 +563,7 @@ export const liteIpcChannels = {
setReviewAutoMerge: "workspace:set-review-auto-merge",
setReviewDraftiness: "workspace:set-review-draftiness",
setReviewTemplate: "workspace:set-review-template",
setTargetRefAndInitProject: "workspace:set-target-ref-and-init-project",
showNativeMenu: "lite:show-native-menu",
treeChangeDiffs: "workspace:tree-change-diffs",
unapplyStack: "workspace:unapply-stack",
Expand Down
7 changes: 7 additions & 0 deletions apps/lite/electron/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {
type SetReviewAutoMergeParams,
type SetReviewDraftinessParams,
type SetReviewTemplateParams,
type SetTargetRefAndInitProjectParams,
type PeelRestoreSnapshotParams,
type WorkspaceIntegrateUpstreamParams,
type UpdateReviewFootersParams,
Expand Down Expand Up @@ -114,6 +115,7 @@ import {
setReviewAutoMerge,
setReviewDraftiness,
setReviewTemplate,
setTargetRefAndInitProject,
getUndoTargetSnapshot,
getRedoTargetSnapshot,
peelRestoreSnapshot,
Expand Down Expand Up @@ -657,6 +659,11 @@ const registerIpcHandlers = (): void => {
(_e, { projectId, templatePath }: SetReviewTemplateParams) =>
setReviewTemplate(projectId, templatePath),
);
senderValidatingHandle(
liteIpcChannels.setTargetRefAndInitProject,
(_e, { projectId, targetRef, pushRemote }: SetTargetRefAndInitProjectParams) =>
setTargetRefAndInitProject(projectId, targetRef, pushRemote ?? null),
);
senderValidatingHandle(
liteIpcChannels.showNativeMenu,
async (event, { items, position }: ShowNativeMenuParams) => {
Expand Down
2 changes: 2 additions & 0 deletions apps/lite/electron/src/preload.cts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@ const api: LiteElectronApi = {
ipcRenderer.invoke("workspace:set-review-draftiness", params) as Promise<void>,
setReviewTemplate: (params) =>
ipcRenderer.invoke("workspace:set-review-template", params) as Promise<void>,
setTargetRefAndInitProject: (params) =>
ipcRenderer.invoke("workspace:set-target-ref-and-init-project", params) as Promise<void>,
showNativeMenu: (params) =>
ipcRenderer.invoke("lite:show-native-menu", params) as Promise<string | null>,
treeChangeDiffs: (params) =>
Expand Down
Loading
Loading