Skip to content

Commit 56575f9

Browse files
authored
Merge pull request #9498 from gitbutlerapp/split-branches-loading-ui
Split branches loading UI
2 parents 7d296ba + 75c891b commit 56575f9

File tree

2 files changed

+126
-86
lines changed

2 files changed

+126
-86
lines changed

apps/desktop/src/components/v3/FileContextMenu.svelte

Lines changed: 114 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
<!-- This is a V3 replacement for `FileContextMenu.svelte` -->
22
<script lang="ts">
3+
import ReduxResult from '$components/ReduxResult.svelte';
34
import { ActionService } from '$lib/actions/actionService.svelte';
45
import { AIService } from '$lib/ai/service';
56
import { writeClipboard } from '$lib/backend/clipboard';
67
import { changesToDiffSpec } from '$lib/commits/utils';
78
import { projectAiExperimentalFeaturesEnabled, projectAiGenEnabled } from '$lib/config/config';
89
import { isTreeChange, type TreeChange } from '$lib/hunks/change';
9-
import { showToast } from '$lib/notifications/toasts';
1010
import { vscodePath } from '$lib/project/project';
1111
import { ProjectsService } from '$lib/project/projectsService';
1212
import { IdSelection } from '$lib/selection/idSelection.svelte';
@@ -76,6 +76,10 @@
7676
const userSettings = getContextStoreBySymbol<Settings, Writable<Settings>>(SETTINGS);
7777
const isUncommitted = $derived(selectionId.type === 'worktree');
7878
const isBranchFiles = $derived(selectionId.type === 'branch');
79+
const selectionBranchName = $derived(
80+
selectionId.type === 'branch' ? selectionId.branchName : undefined
81+
);
82+
7983
const user = getContextStore(User);
8084
const isAdmin = $derived($user.role === 'admin');
8185
@@ -168,19 +172,19 @@
168172
return;
169173
}
170174
171-
showToast({
172-
style: 'neutral',
173-
title: 'Figuring out where to commit the changes',
174-
message: 'This may take a few seconds.'
175-
});
176-
177-
await autoCommit({ projectId, changes });
178-
179-
showToast({
180-
style: 'success',
181-
title: 'And... done!',
182-
message: `Now, you're free to continue`
183-
});
175+
await toasts.promise(
176+
(async () => {
177+
await autoCommit({
178+
projectId,
179+
changes
180+
});
181+
})(),
182+
{
183+
loading: 'Started auto commit',
184+
success: 'Auto commit succeded',
185+
error: (error: Error) => `Auto commit failed: ${error.message}`
186+
}
187+
);
184188
}
185189
186190
async function triggerBranchChanges(changes: TreeChange[]) {
@@ -189,19 +193,16 @@
189193
return;
190194
}
191195
192-
showToast({
193-
style: 'neutral',
194-
title: 'Creating a branch and committing the changes',
195-
message: 'This may take a few seconds.'
196-
});
197-
198-
await branchChanges({ projectId, changes });
199-
200-
showToast({
201-
style: 'success',
202-
title: 'And... done!',
203-
message: `Now, you're free to continue`
204-
});
196+
await toasts.promise(
197+
(async () => {
198+
await branchChanges({ projectId, changes });
199+
})(),
200+
{
201+
loading: 'Creating a branch and committing changes',
202+
success: 'Branching changes succeded',
203+
error: (error: Error) => `Branching changes failed: ${error.message}`
204+
}
205+
);
205206
}
206207
207208
async function triggerAbsorbChanges(changes: TreeChange[]) {
@@ -210,19 +211,16 @@
210211
return;
211212
}
212213
213-
showToast({
214-
style: 'neutral',
215-
title: 'Looking for the best place to absorb the changes',
216-
message: 'This may take a few seconds.'
217-
});
218-
219-
await absorbChanges({ projectId, changes });
220-
221-
showToast({
222-
style: 'success',
223-
title: 'And... done!',
224-
message: `Now, you're free to continue`
225-
});
214+
await toasts.promise(
215+
(async () => {
216+
await absorbChanges({ projectId, changes });
217+
})(),
218+
{
219+
loading: 'Looking for the best place to absorb the changes',
220+
success: 'Absorbing changes succeded',
221+
error: (error: Error) => `Absorbing changes failed: ${error.message}`
222+
}
223+
);
226224
}
227225
228226
async function split(changes: TreeChange[]) {
@@ -239,20 +237,29 @@
239237
const branchName = selectionId.branchName;
240238
241239
const fileNames = changes.map((change) => change.path);
242-
const newBranchName = await stackService.fetchNewBranchName(projectId);
243240
244-
if (!newBranchName) {
245-
toasts.error('Failed to generate a new branch name.');
246-
return;
247-
}
248-
249-
await splitOffChanges({
250-
projectId,
251-
sourceStackId: stackId,
252-
sourceBranchName: branchName,
253-
fileChangesToSplitOff: fileNames,
254-
newBranchName: newBranchName
255-
});
241+
await toasts.promise(
242+
(async () => {
243+
const newBranchName = await stackService.fetchNewBranchName(projectId);
244+
245+
if (!newBranchName) {
246+
throw new Error('Failed to generate a new branch name.');
247+
}
248+
249+
await splitOffChanges({
250+
projectId,
251+
sourceStackId: stackId,
252+
sourceBranchName: branchName,
253+
fileChangesToSplitOff: fileNames,
254+
newBranchName: newBranchName
255+
});
256+
})(),
257+
{
258+
loading: 'Splitting off changes',
259+
success: 'Changes split off into a new branch',
260+
error: (error: Error) => `Failed to split off changes: ${error.message}`
261+
}
262+
);
256263
}
257264
258265
async function splitIntoDependentBranch(changes: TreeChange[]) {
@@ -269,20 +276,29 @@
269276
const branchName = selectionId.branchName;
270277
271278
const fileNames = changes.map((change) => change.path);
272-
const newBranchName = await stackService.fetchNewBranchName(projectId);
273-
274-
if (!newBranchName) {
275-
toasts.error('Failed to generate a new branch name.');
276-
return;
277-
}
278279
279-
await splitBranchIntoDependentBranch({
280-
projectId,
281-
sourceStackId: stackId,
282-
sourceBranchName: branchName,
283-
fileChangesToSplitOff: fileNames,
284-
newBranchName: newBranchName
285-
});
280+
await toasts.promise(
281+
(async () => {
282+
const newBranchName = await stackService.fetchNewBranchName(projectId);
283+
284+
if (!newBranchName) {
285+
throw new Error('Failed to generate a new branch name.');
286+
}
287+
288+
await splitBranchIntoDependentBranch({
289+
projectId,
290+
sourceStackId: stackId,
291+
sourceBranchName: branchName,
292+
fileChangesToSplitOff: fileNames,
293+
newBranchName: newBranchName
294+
});
295+
})(),
296+
{
297+
loading: 'Splitting into dependent branch',
298+
success: 'Changes split into a dependent branch',
299+
error: (error: Error) => `Failed to split into dependent branch: ${error.message}`
300+
}
301+
);
286302
}
287303
</script>
288304

@@ -369,21 +385,33 @@
369385
}
370386
}}
371387
/>
372-
{#if isBranchFiles && isAdmin}
373-
<ContextMenuItem
374-
label="Split off changes"
375-
onclick={async () => {
376-
await split(changes);
377-
contextMenu.close();
378-
}}
379-
/>
380-
<ContextMenuItem
381-
label="Split into dependent branch"
382-
onclick={async () => {
383-
await splitIntoDependentBranch(changes);
384-
contextMenu.close();
385-
}}
386-
/>
388+
389+
{#if isBranchFiles && stackId && selectionBranchName && isAdmin}
390+
{@const branchIsConflicted = stackService.isBranchConflicted(
391+
projectId,
392+
stackId,
393+
selectionBranchName
394+
)}
395+
<ReduxResult {projectId} result={branchIsConflicted?.current}>
396+
{#snippet children(isConflicted)}
397+
{#if isConflicted === false}
398+
<ContextMenuItem
399+
label="Split off changes"
400+
onclick={() => {
401+
split(changes);
402+
contextMenu.close();
403+
}}
404+
/>
405+
<ContextMenuItem
406+
label="Split into dependent branch"
407+
onclick={() => {
408+
splitIntoDependentBranch(changes);
409+
contextMenu.close();
410+
}}
411+
/>
412+
{/if}
413+
{/snippet}
414+
</ReduxResult>
387415
{/if}
388416
{/if}
389417
</ContextMenuSection>
@@ -394,25 +422,25 @@
394422
tooltip="Try to figure out where to commit the changes. Can create new branches too."
395423
onclick={async () => {
396424
contextMenu.close();
397-
await triggerAutoCommit(item.changes);
425+
triggerAutoCommit(item.changes);
398426
}}
399427
disabled={autoCommitting.current.isLoading}
400428
/>
401429
<ContextMenuItem
402430
label="Branch changes 🧪"
403431
tooltip="Create a new branch and commit the changes into it."
404-
onclick={async () => {
432+
onclick={() => {
405433
contextMenu.close();
406-
await triggerBranchChanges(item.changes);
434+
triggerBranchChanges(item.changes);
407435
}}
408436
disabled={branchingChanges.current.isLoading}
409437
/>
410438
<ContextMenuItem
411439
label="Absorb changes 🧪"
412440
tooltip="Try to find the best place to absorb the changes into."
413-
onclick={async () => {
441+
onclick={() => {
414442
contextMenu.close();
415-
await triggerAbsorbChanges(item.changes);
443+
triggerAbsorbChanges(item.changes);
416444
}}
417445
disabled={absorbingChanges.current.isLoading}
418446
/>

apps/desktop/src/lib/stacks/stackService.svelte.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,18 @@ export class StackService {
755755
return await this.api.endpoints.newBranchName.fetch({ projectId }, { forceRefetch: true });
756756
}
757757

758+
isBranchConflicted(projectId: string, stackId: string, branchName: string) {
759+
return this.api.endpoints.stackDetails.useQuery(
760+
{ projectId, stackId },
761+
{
762+
transform: ({ branchDetails }) => {
763+
const branch = branchDetailsSelectors.selectById(branchDetails, branchName);
764+
return branch?.isConflicted ?? false;
765+
}
766+
}
767+
);
768+
}
769+
758770
async normalizeBranchName(name: string) {
759771
return await this.api.endpoints.normalizeBranchName.fetch({ name }, { forceRefetch: true });
760772
}

0 commit comments

Comments
 (0)