diff --git a/apps/desktop/src/components/commit/CommitView.svelte b/apps/desktop/src/components/commit/CommitView.svelte index 5618fa14157..541aceadb65 100644 --- a/apps/desktop/src/components/commit/CommitView.svelte +++ b/apps/desktop/src/components/commit/CommitView.svelte @@ -134,7 +134,6 @@ await stackService.uncommit({ projectId, stackId: targetStackId, - branchName, commitId: commitKey.commitId, }); }, diff --git a/apps/desktop/src/components/views/BranchCommitList.svelte b/apps/desktop/src/components/views/BranchCommitList.svelte index e50ba452a2d..2bb6245d768 100644 --- a/apps/desktop/src/components/views/BranchCommitList.svelte +++ b/apps/desktop/src/components/views/BranchCommitList.svelte @@ -107,7 +107,6 @@ await stackService.uncommit({ projectId, stackId: targetStackId, - branchName, commitId, }); }); diff --git a/apps/desktop/src/lib/stacks/stackEndpoints.test.ts b/apps/desktop/src/lib/stacks/stackEndpoints.test.ts new file mode 100644 index 00000000000..728c61dbed6 --- /dev/null +++ b/apps/desktop/src/lib/stacks/stackEndpoints.test.ts @@ -0,0 +1,35 @@ +import { buildStackEndpoints } from "$lib/stacks/stackEndpoints"; +import { describe, expect, test } from "vitest"; +import type { BackendEndpointBuilder } from "$lib/state/backendApi"; + +function createEndpointBuilder(): BackendEndpointBuilder { + return { + mutation: (definition) => definition, + query: (definition) => definition, + } as BackendEndpointBuilder; +} + +describe("buildStackEndpoints", () => { + test("maps uncommit to commit_undo with the new request shape", () => { + const endpoints = buildStackEndpoints(createEndpointBuilder()); + const query = endpoints.uncommit.query; + + expect(endpoints.uncommit.extraOptions).toEqual({ + command: "commit_undo", + actionName: "Uncommit", + }); + expect(query).toBeDefined(); + expect( + query?.({ + projectId: "project-1", + stackId: "stack-1", + commitId: "commit-1", + }), + ).toEqual({ + projectId: "project-1", + subjectCommitId: "commit-1", + assignTo: "stack-1", + dryRun: false, + }); + }); +}); diff --git a/apps/desktop/src/lib/stacks/stackEndpoints.ts b/apps/desktop/src/lib/stacks/stackEndpoints.ts index c1556aa0c42..6fd9eab84bb 100644 --- a/apps/desktop/src/lib/stacks/stackEndpoints.ts +++ b/apps/desktop/src/lib/stacks/stackEndpoints.ts @@ -36,6 +36,7 @@ import type { CommitRewordResult, CommitInsertBlankResult, RejectionReason, + CommitUndoResult, } from "@gitbutler/but-sdk"; export type BranchParams = { @@ -469,12 +470,20 @@ export function buildStackEndpoints(build: BackendEndpointBuilder) { invalidatesList(ReduxTag.BranchListing), ], }), - uncommit: build.mutation({ + uncommit: build.mutation< + CommitUndoResult, + { projectId: string; stackId: string; commitId: string } + >({ extraOptions: { - command: "undo_commit", + command: "commit_undo", actionName: "Uncommit", }, - query: (args) => args, + query: ({ projectId, stackId, commitId }) => ({ + projectId, + subjectCommitId: commitId, + assignTo: stackId, + dryRun: false, + }), invalidatesTags: (_result, _error, args) => [ invalidatesItem(ReduxTag.BranchChanges, args.stackId), invalidatesList(ReduxTag.WorktreeChanges), diff --git a/apps/desktop/src/lib/stacks/stackService.svelte.ts b/apps/desktop/src/lib/stacks/stackService.svelte.ts index 075882e3e99..23441ba42a6 100644 --- a/apps/desktop/src/lib/stacks/stackService.svelte.ts +++ b/apps/desktop/src/lib/stacks/stackService.svelte.ts @@ -561,12 +561,7 @@ export class StackService { return this.backendApi.endpoints.newBranch.useMutation(); } - async uncommit(args: { - projectId: string; - stackId: string; - branchName: string; - commitId: string; - }) { + async uncommit(args: { projectId: string; stackId: string; commitId: string }) { const result = await this.backendApi.endpoints.uncommit.mutate(args); const selection = this.uiState.lane(args.stackId).selection; if (args.commitId === selection.current?.commitId) { diff --git a/apps/desktop/src/lib/stacks/stackService.test.ts b/apps/desktop/src/lib/stacks/stackService.test.ts new file mode 100644 index 00000000000..0c88547949a --- /dev/null +++ b/apps/desktop/src/lib/stacks/stackService.test.ts @@ -0,0 +1,69 @@ +import { StackService } from "$lib/stacks/stackService.svelte"; +import { describe, expect, test, vi } from "vitest"; +import type { BackendApi } from "$lib/state/backendApi"; +import type { StackSelection, UiState } from "$lib/state/uiState.svelte"; + +function makeMockUiState(initialSelection: StackSelection | undefined): { + uiState: UiState; + getSelection: () => StackSelection | undefined; +} { + let selection = initialSelection; + const laneState = { + selection: { + get current() { + return selection; + }, + set: (value: StackSelection | undefined) => { + selection = value; + }, + }, + }; + + return { + uiState: { + lane: () => laneState, + } as unknown as UiState, + getSelection: () => selection, + }; +} + +describe("StackService.uncommit", () => { + test("clears the selected commit after a successful uncommit", async () => { + const mutate = vi.fn().mockResolvedValue({ + undoneCommit: "commit-1", + workspace: { + headInfo: { + reference: "refs/heads/branch", + workspaceChanges: [], + }, + replacedCommits: {}, + }, + }); + const { uiState, getSelection } = makeMockUiState({ + branchName: "branch", + commitId: "commit-1", + previewOpen: true, + }); + const backendApi = { + endpoints: { + uncommit: { + mutate, + }, + }, + } as unknown as BackendApi; + const service = new StackService(backendApi, {} as never, {} as never, uiState); + + await service.uncommit({ + projectId: "project-1", + stackId: "stack-1", + commitId: "commit-1", + }); + + expect(mutate).toHaveBeenCalledWith({ + projectId: "project-1", + stackId: "stack-1", + commitId: "commit-1", + }); + expect(getSelection()).toBeUndefined(); + }); +}); diff --git a/crates/but-server/src/lib.rs b/crates/but-server/src/lib.rs index df9b149528e..2ed9aaf2b72 100644 --- a/crates/but-server/src/lib.rs +++ b/crates/but-server/src/lib.rs @@ -1191,6 +1191,7 @@ pub async fn run(config: Config) -> anyhow::Result<()> { "/commit_uncommit_changes", but_post(commit::uncommit::commit_uncommit_changes_cmd), ) + .route("/commit_undo", but_post(commit::undo::commit_undo_cmd)) .route("/build_type", but_post(platform::build_type_cmd)); // IRC commands — only registered when the `irc` feature is enabled. diff --git a/crates/gitbutler-tauri/permissions/default.toml b/crates/gitbutler-tauri/permissions/default.toml index 4c6a00b0d02..eb3eb31a73c 100644 --- a/crates/gitbutler-tauri/permissions/default.toml +++ b/crates/gitbutler-tauri/permissions/default.toml @@ -53,6 +53,7 @@ commands.allow = [ "commit_insert_blank", "commit_move_changes_between", "commit_reword", + "commit_undo", "commit_uncommit_changes", "create_branch", "create_commit_from_worktree_changes", diff --git a/crates/gitbutler-tauri/src/main.rs b/crates/gitbutler-tauri/src/main.rs index f09b1711681..668bd7b9ee6 100644 --- a/crates/gitbutler-tauri/src/main.rs +++ b/crates/gitbutler-tauri/src/main.rs @@ -548,6 +548,7 @@ fn main() -> anyhow::Result<()> { commit::amend::tauri_commit_amend::commit_amend, commit::move_changes::tauri_commit_move_changes_between::commit_move_changes_between, commit::uncommit::tauri_commit_uncommit_changes::commit_uncommit_changes, + commit::undo::tauri_commit_undo::commit_undo, platform::tauri_build_type::build_type, ]) .menu(move |handle| menu::build(handle, &app_settings_for_menu))