Skip to content

Commit b266aea

Browse files
committed
Add ability to remove branches
1 parent 2e935d4 commit b266aea

4 files changed

Lines changed: 135 additions & 10 deletions

File tree

apps/lite/ui/src/api/mutations.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ export const moveBranchMutationOptions = mutationOptions({
5454
},
5555
});
5656

57+
export const removeBranchMutationOptions = mutationOptions({
58+
mutationFn: window.lite.removeBranch,
59+
onSuccess: async (_data, _input, _ctx, { client }) => {
60+
await client.invalidateQueries();
61+
},
62+
});
63+
5764
export const updateBranchNameMutationOptions = mutationOptions({
5865
mutationFn: window.lite.updateBranchName,
5966
onSuccess: async (_data, _input, _ctx, { client }) => {

apps/lite/ui/src/routes/project/$id/workspace/-WorkspaceShortcuts.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ const commitDetailsBindings: Array<ShortcutBinding<CommitDetailsAction>> = [
135135
closeCommitDetailsBinding,
136136
];
137137

138-
type BranchSegmentAction = SelectionAction | { _tag: "RenameBranch" };
138+
type BranchSegmentAction = SelectionAction | { _tag: "RenameBranch" } | { _tag: "RemoveBranch" };
139139

140140
const branchSegmentBindings: Array<ShortcutBinding<BranchSegmentAction>> = [
141141
...selectionBindings,
@@ -146,6 +146,13 @@ const branchSegmentBindings: Array<ShortcutBinding<BranchSegmentAction>> = [
146146
action: { _tag: "RenameBranch" },
147147
repeat: false,
148148
},
149+
{
150+
id: "segment-remove-branch",
151+
description: "Remove branch",
152+
keys: ["Backspace"],
153+
action: { _tag: "RemoveBranch" },
154+
repeat: false,
155+
},
149156
];
150157

151158
type FullscreenPreviewAction = { _tag: "Close" };
@@ -409,6 +416,7 @@ export const useWorkspaceShortcuts = ({
409416
setEditing,
410417
commonBaseCommitId,
411418
onAbsorbChanges,
419+
onRemoveBranch,
412420
}: {
413421
projectId: string;
414422
showFullscreenPreview: boolean;
@@ -418,6 +426,7 @@ export const useWorkspaceShortcuts = ({
418426
setEditing: (selection: Editing | null) => void;
419427
commonBaseCommitId?: string;
420428
onAbsorbChanges: (changes: Array<TreeChange>, stackId: string | null) => void;
429+
onRemoveBranch: (selection: SegmentItem) => void;
421430
}) => {
422431
const { data: headInfo } = useSuspenseQuery(headInfoQueryOptions(projectId));
423432
const { data: worktreeChanges } = useSuspenseQuery(changesInWorktreeQueryOptions(projectId));
@@ -535,6 +544,7 @@ export const useWorkspaceShortcuts = ({
535544
},
536545
});
537546
},
547+
RemoveBranch: () => onRemoveBranch(selection),
538548
}),
539549
Match.orElse((action) => handleSelectionAction(action, { _tag: "Segment", ...selection })),
540550
);

apps/lite/ui/src/routes/project/$id/workspace/route.module.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,12 @@
178178
font-family: monospace;
179179
}
180180

181+
.dialogActions {
182+
display: flex;
183+
column-gap: 8px;
184+
justify-content: flex-end;
185+
}
186+
181187
.editCommitMessageInput {
182188
field-sizing: content;
183189
box-sizing: content-box;

apps/lite/ui/src/routes/project/$id/workspace/route.tsx

Lines changed: 111 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
commitCreateMutationOptions,
33
commitInsertBlankMutationOptions,
44
commitRewordMutationOptions,
5+
removeBranchMutationOptions,
56
updateBranchNameMutationOptions,
67
unapplyStackMutationOptions,
78
} from "#ui/api/mutations.ts";
@@ -65,7 +66,15 @@ import {
6566
attachInstruction,
6667
extractInstruction,
6768
} from "@atlaskit/pragmatic-drag-and-drop-hitbox/list-item";
68-
import { ContextMenu, Menu, mergeProps, Tooltip, Toast, useRender } from "@base-ui/react";
69+
import {
70+
AlertDialog,
71+
ContextMenu,
72+
Menu,
73+
mergeProps,
74+
Tooltip,
75+
Toast,
76+
useRender,
77+
} from "@base-ui/react";
6978
import {
7079
Commit,
7180
DiffHunk,
@@ -122,6 +131,7 @@ import {
122131
import { PositionedShortcutsBar } from "../-ShortcutsBar.tsx";
123132
import { formatShortcutKeys, ShortcutActionBase, type ShortcutBinding } from "#ui/shortcuts.ts";
124133
import styles from "./route.module.css";
134+
import { RemoveBranchParams } from "#electron/ipc.ts";
125135

126136
// https://linear.app/gitbutler/issue/GB-1161/refsbranches-should-use-bytes-instead-of-strings
127137
const decodeRefName = (fullNameBytes: Array<number>): string =>
@@ -1428,21 +1438,58 @@ const TearOffBranchTarget: FC<useRender.ComponentProps<"div">> = ({ render, ...p
14281438
};
14291439

14301440
const SegmentMenuPopup: FC<{
1431-
canRename: boolean;
1432-
onRename: () => void;
1441+
canRemoveBranch: boolean;
1442+
canRenameBranch: boolean;
1443+
onRemoveBranch: () => void;
1444+
onRenameBranch: () => void;
14331445
parts: typeof Menu | typeof ContextMenu;
1434-
}> = ({ canRename, onRename, parts }) => {
1446+
}> = ({ canRemoveBranch, canRenameBranch, onRemoveBranch, onRenameBranch, parts }) => {
14351447
const { Popup, Item } = parts;
14361448

14371449
return (
14381450
<Popup className={classes(uiStyles.popup, uiStyles.menuPopup)}>
1439-
<Item className={uiStyles.menuItem} disabled={!canRename} onClick={onRename}>
1451+
<Item className={uiStyles.menuItem} disabled={!canRenameBranch} onClick={onRenameBranch}>
14401452
Rename branch
14411453
</Item>
1454+
<Item className={uiStyles.menuItem} disabled={!canRemoveBranch} onClick={onRemoveBranch}>
1455+
Remove branch
1456+
</Item>
14421457
</Popup>
14431458
);
14441459
};
14451460

1461+
const RemoveBranchDialog: FC<{
1462+
branchName: string;
1463+
isPending: boolean;
1464+
onConfirm: () => void;
1465+
onOpenChange: (open: boolean) => void;
1466+
}> = ({ branchName, isPending, onConfirm, onOpenChange }) => (
1467+
<AlertDialog.Root open onOpenChange={onOpenChange}>
1468+
<AlertDialog.Portal>
1469+
<AlertDialog.Backdrop className={uiStyles.dialogBackdrop} />
1470+
<AlertDialog.Popup className={uiStyles.dialogPopup}>
1471+
<AlertDialog.Title>Remove branch</AlertDialog.Title>
1472+
<p>
1473+
Are you sure you want to remove <code>{branchName}</code>?
1474+
</p>
1475+
<div className={styles.dialogActions}>
1476+
<AlertDialog.Close className={uiStyles.button} disabled={isPending}>
1477+
Cancel
1478+
</AlertDialog.Close>
1479+
<button
1480+
type="button"
1481+
className={uiStyles.button}
1482+
onClick={onConfirm}
1483+
disabled={isPending}
1484+
>
1485+
Remove branch
1486+
</button>
1487+
</div>
1488+
</AlertDialog.Popup>
1489+
</AlertDialog.Portal>
1490+
</AlertDialog.Root>
1491+
);
1492+
14461493
const InlineBranchNameEditor: FC<{
14471494
branchName: string;
14481495
onSubmit: (value: string) => void;
@@ -1484,6 +1531,7 @@ const SegmentRow: FC<
14841531
{
14851532
projectId: string;
14861533
editing: Editing | null;
1534+
onRequestRemoveBranch: (params: { stackId: string; branchName: string }) => void;
14871535
segment: Segment;
14881536
stackId: string;
14891537
segmentIndex: number;
@@ -1494,6 +1542,7 @@ const SegmentRow: FC<
14941542
> = ({
14951543
projectId,
14961544
editing,
1545+
onRequestRemoveBranch,
14971546
segment,
14981547
stackId,
14991548
segmentIndex,
@@ -1560,6 +1609,11 @@ const SegmentRow: FC<
15601609
});
15611610
};
15621611

1612+
const requestRemoveBranch = () => {
1613+
if (branchName === null) return;
1614+
onRequestRemoveBranch({ stackId, branchName });
1615+
};
1616+
15631617
const children = (
15641618
<div
15651619
{...restProps}
@@ -1594,8 +1648,10 @@ const SegmentRow: FC<
15941648
<ContextMenu.Portal>
15951649
<ContextMenu.Positioner>
15961650
<SegmentMenuPopup
1597-
canRename={branchName !== null && !isRenamePending}
1598-
onRename={startEditing}
1651+
canRemoveBranch={branchName !== null}
1652+
canRenameBranch={branchName !== null && !isRenamePending}
1653+
onRemoveBranch={requestRemoveBranch}
1654+
onRenameBranch={startEditing}
15991655
parts={ContextMenu}
16001656
/>
16011657
</ContextMenu.Positioner>
@@ -1612,8 +1668,10 @@ const SegmentRow: FC<
16121668
<Menu.Portal>
16131669
<Menu.Positioner align="end">
16141670
<SegmentMenuPopup
1615-
canRename={branchName !== null && !isRenamePending}
1616-
onRename={startEditing}
1671+
canRemoveBranch={branchName !== null}
1672+
canRenameBranch={branchName !== null && !isRenamePending}
1673+
onRemoveBranch={requestRemoveBranch}
1674+
onRenameBranch={startEditing}
16171675
parts={Menu}
16181676
/>
16191677
</Menu.Positioner>
@@ -1641,6 +1699,7 @@ const SegmentRow: FC<
16411699

16421700
const SegmentC: FC<{
16431701
highlightedCommitIds: Set<string>;
1702+
onRequestRemoveBranch: (params: { stackId: string; branchName: string }) => void;
16441703
projectId: string;
16451704
segment: Segment;
16461705
segmentIndex: number;
@@ -1652,6 +1711,7 @@ const SegmentC: FC<{
16521711
}> = ({
16531712
editing,
16541713
highlightedCommitIds,
1714+
onRequestRemoveBranch,
16551715
projectId,
16561716
segment,
16571717
segmentIndex,
@@ -1673,6 +1733,7 @@ const SegmentC: FC<{
16731733
<SegmentRow
16741734
projectId={projectId}
16751735
editing={editing}
1736+
onRequestRemoveBranch={onRequestRemoveBranch}
16761737
segment={segment}
16771738
stackId={stackId}
16781739
segmentIndex={segmentIndex}
@@ -1708,6 +1769,7 @@ const StackC: FC<{
17081769
highlightedCommitIds: Set<string>;
17091770
onAbsorbChanges: (changes: Array<TreeChange>, stackId: string | null) => void;
17101771
onDependencyHover: (commitIds: Array<string> | null) => void;
1772+
onRequestRemoveBranch: (params: Omit<RemoveBranchParams, "projectId">) => void;
17111773
projectId: string;
17121774
selection: Item | null;
17131775
select: (selection: Item | null) => void;
@@ -1719,6 +1781,7 @@ const StackC: FC<{
17191781
highlightedCommitIds,
17201782
onAbsorbChanges,
17211783
onDependencyHover,
1784+
onRequestRemoveBranch,
17221785
projectId,
17231786
selection,
17241787
select,
@@ -1770,6 +1833,7 @@ const StackC: FC<{
17701833
<SegmentC
17711834
editing={editing}
17721835
highlightedCommitIds={highlightedCommitIds}
1836+
onRequestRemoveBranch={onRequestRemoveBranch}
17731837
projectId={projectId}
17741838
segment={segment}
17751839
segmentIndex={segmentIndex}
@@ -1832,6 +1896,28 @@ const ProjectPage: FC = () => {
18321896
} = useAbsorption(projectId);
18331897

18341898
useMonitorDraggedSourceItem({ projectId });
1899+
1900+
const removeBranch = useMutation(removeBranchMutationOptions);
1901+
const [branchPendingRemoval, setBranchPendingRemoval] = useState<Omit<
1902+
RemoveBranchParams,
1903+
"projectId"
1904+
> | null>(null);
1905+
const confirmRemoveBranch = () => {
1906+
if (branchPendingRemoval === null) return;
1907+
1908+
removeBranch.mutate(
1909+
{
1910+
projectId,
1911+
stackId: branchPendingRemoval.stackId,
1912+
branchName: branchPendingRemoval.branchName,
1913+
},
1914+
{
1915+
onSuccess: () => {
1916+
setBranchPendingRemoval(null);
1917+
},
1918+
},
1919+
);
1920+
};
18351921
useWorkspaceShortcuts({
18361922
projectId,
18371923
showFullscreenPreview,
@@ -1841,6 +1927,10 @@ const ProjectPage: FC = () => {
18411927
setEditing,
18421928
commonBaseCommitId,
18431929
onAbsorbChanges: requestAbsorptionPlan,
1930+
onRemoveBranch: ({ stackId, branchName }) => {
1931+
if (branchName === null) return;
1932+
setBranchPendingRemoval({ stackId, branchName });
1933+
},
18441934
});
18451935

18461936
// TODO: dedupe
@@ -1884,6 +1974,7 @@ const ProjectPage: FC = () => {
18841974
highlightedCommitIds={highlightedCommitIds}
18851975
onAbsorbChanges={requestAbsorptionPlan}
18861976
onDependencyHover={highlightCommits}
1977+
onRequestRemoveBranch={setBranchPendingRemoval}
18871978
projectId={project.id}
18881979
selection={selection}
18891980
select={select}
@@ -1926,6 +2017,7 @@ const ProjectPage: FC = () => {
19262017
label={shortcutScope ? getLabel(shortcutScope) : null}
19272018
items={shortcutScope?.bindings ?? []}
19282019
/>
2020+
19292021
{absorptionPlan !== null && (
19302022
<AbsorptionDialog
19312023
absorptionPlan={absorptionPlan}
@@ -1936,6 +2028,16 @@ const ProjectPage: FC = () => {
19362028
}}
19372029
/>
19382030
)}
2031+
{branchPendingRemoval !== null && (
2032+
<RemoveBranchDialog
2033+
branchName={branchPendingRemoval.branchName}
2034+
isPending={removeBranch.isPending}
2035+
onConfirm={confirmRemoveBranch}
2036+
onOpenChange={(open) => {
2037+
if (!open) setBranchPendingRemoval(null);
2038+
}}
2039+
/>
2040+
)}
19392041
</ProjectPreviewLayout>
19402042
);
19412043
};

0 commit comments

Comments
 (0)