# How GitButler Commit Editing Works: Amend, Reword, and Squash Explained

> Explore GitButler's commit editing workflow. Learn how amend, reword, and squash commands are processed through Svelte and RTK-Query for efficient Git command translation. See how it works.

- Repository: [GitButler/gitbutler](https://github.com/gitbutlerapp/gitbutler)
- Tags: internals
- Published: 2026-02-16

---

**GitButler routes commit editing operations through a unified pipeline of Svelte drop-handlers and RTK-Query mutations, translating UI interactions into Git commands via the `amend_virtual_branch`, `commit_reword`, and `squash_commits` backend operations.**

GitButler's visual branch management interface simplifies complex Git workflows by abstracting amend, reword, and squash operations into drag-and-drop interactions. Under the hood, these actions follow a consistent architecture: UI components trigger specialized drop-handler classes that validate operations before invoking the central `StackService` to execute Rust-backed Git commands.

## The Architecture of GitButler Commit Editing

GitButler commit editing relies on three distinct layers: the UI drop-handlers that validate user interactions, the `StackService` mutation layer that manages API calls, and the Rust backend that executes raw Git operations.

### From Drag-and-Drop to Git Command

When a user initiates a commit editing action, the UI routes the interaction through specific drop-handler classes defined in [`apps/desktop/src/lib/commits/dropHandler.ts`](https://github.com/gitbutlerapp/gitbutler/blob/main/apps/desktop/src/lib/commits/dropHandler.ts). These handlers validate whether the dragged item can legally modify the target commit.

For **amend operations**, the `AmendCommitWithChangeDzHandler` class accepts `FileChangeDropData` or `FolderChangeDropData` instances and verifies that the target commit has no conflicts. For **squash operations**, the `SquashCommitDzHandler` class accepts `CommitDropData` and validates that the commits can be combined.

### The StackService Mutation Layer

After validation, drop-handlers invoke methods from [`apps/desktop/src/lib/stacks/stackService.svelte.ts`](https://github.com/gitbutlerapp/gitbutler/blob/main/apps/desktop/src/lib/stacks/stackService.svelte.ts), which exposes RTK-Query mutations for each commit editing operation. The service translates TypeScript calls into commands sent to the Rust backend:

- `amendCommit` → `amend_virtual_branch`
- `commitReword` → `commit_reword`
- `squashCommits` → `squash_commits`

## How Amend Works in GitButler

Amending allows users to add new file changes or hunks to an existing commit without creating a new one. GitButler implements this through specialized drop-handlers that support both full file changes and individual hunks.

### The AmendCommitWithChangeDzHandler Class

Located in [`apps/desktop/src/lib/commits/dropHandler.ts`](https://github.com/gitbutlerapp/gitbutler/blob/main/apps/desktop/src/lib/commits/dropHandler.ts) at lines 83-101, the `AmendCommitWithChangeDzHandler` class manages the validation and execution of amend operations. The handler checks that the target commit has no conflicts before allowing the drop operation to proceed.

When a user drags a file or folder onto a commit, the handler packages the change data and calls `stackService.amendCommit()`, which triggers the `amend_virtual_branch` backend command.

### Code Example: Amending a Commit with File Changes

```typescript
import { AmendCommitWithChangeDzHandler } from '$lib/commits/dropHandler';

const amendHandler = new AmendCommitWithChangeDzHandler(
  projectId,
  stackService,
  hooksService,
  stackId,
  true,
  { id: commitId, hasConflicts: false, isRemote: false, isIntegrated: false },
  (result) => console.log('Amend result:', result),
  uiState
);

amendHandler.ondrop(fileChangeDropData);

```

## How Reword Works in GitButler

Rewording changes a commit's message without modifying its content or tree. Unlike amend and squash, rewording does not use a drop-handler because it involves direct text input rather than drag-and-drop interactions.

### Direct Mutation Without Drop Handling

The reword functionality is exposed directly through the `commitReword` mutation in [`apps/desktop/src/lib/stacks/stackService.svelte.ts`](https://github.com/gitbutlerapp/gitbutler/blob/main/apps/desktop/src/lib/stacks/stackService.svelte.ts) at line 1223. When a user edits a commit message in the UI and confirms the change, the component calls this mutation with the new message content.

The mutation maps to the `commit_reword` backend command, which executes the equivalent of `git commit --amend --message="new message"` while preserving the original tree.

### Code Example: Changing a Commit Message

```typescript
await stackService
  .commitReword({
    projectId,
    stackId,
    commitId,
    message: 'Refactor authentication logic for OAuth2 compatibility'
  })
  .unwrap();

```

## How Squash Works in GitButler

Squashing combines multiple consecutive commits into a single commit, preserving the changes but collapsing the history. GitButler implements this through the `SquashCommitDzHandler` class, which manages the drag-and-drop interaction when users combine commits.

### The SquashCommitDzHandler Class

Defined in [`apps/desktop/src/lib/commits/dropHandler.ts`](https://github.com/gitbutlerapp/gitbutler/blob/main/apps/desktop/src/lib/commits/dropHandler.ts) at lines 361-380, the `SquashCommitDzHandler` class accepts `CommitDropData` instances representing the commits to be combined. The handler validates that the commits can be squashed, typically checking that they are consecutive and belong to the same stack, before invoking the service method.

When validation passes, the handler calls `stackService.squashCommits()`, which triggers the `squash_commits` backend command.

### Code Example: Combining Consecutive Commits

```typescript
await stackService
  .squashCommits({
    projectId,
    stackId,
    sourceCommitIds: [olderCommitId, newerCommitId],
    targetCommitId: olderCommitId
  })
  .unwrap();

```

## Key Source Files for GitButler Commit Editing

Understanding the commit editing implementation requires familiarity with these specific files in the `gitbutlerapp/gitbutler` repository:

- **[`apps/desktop/src/lib/commits/dropHandler.ts`](https://github.com/gitbutlerapp/gitbutler/blob/main/apps/desktop/src/lib/commits/dropHandler.ts)** – Implements `AmendCommitWithChangeDzHandler` and `SquashCommitDzHandler` for drag-and-drop validation and execution.
- **[`apps/desktop/src/lib/stacks/stackService.svelte.ts`](https://github.com/gitbutlerapp/gitbutler/blob/main/apps/desktop/src/lib/stacks/stackService.svelte.ts)** – Defines RTK-Query mutations including `amendCommit`, `commitReword`, and `squashCommits` that interface with the Rust backend.
- **[`apps/desktop/src/lib/history/types.ts`](https://github.com/gitbutlerapp/gitbutler/blob/main/apps/desktop/src/lib/history/types.ts)** – Enumerates history actions such as `AmendCommit` used by the UI to determine available operations.
- **[`apps/desktop/src/lib/commits/commit.ts`](https://github.com/gitbutlerapp/gitbutler/blob/main/apps/desktop/src/lib/commits/commit.ts)** – Contains helper functions like `getMoveCommitIllegalActionMessage` for error handling in commit operations.
- **[`apps/desktop/src/lib/stacks/stack.ts`](https://github.com/gitbutlerapp/gitbutler/blob/main/apps/desktop/src/lib/stacks/stack.ts)** – Defines TypeScript types for `StackAction` and `Commit` objects used throughout the commit editing pipeline.
- **[`crates/gitbutler-workspace/src/lib.rs`](https://github.com/gitbutlerapp/gitbutler/blob/main/crates/gitbutler-workspace/src/lib.rs)** – Rust backend that executes the actual Git commands (`amend_virtual_branch`, `commit_reword`, `squash_commits`).

## Summary

GitButler commit editing operations follow a consistent three-layer architecture that separates UI interactions from Git execution:

- **Amend operations** use `AmendCommitWithChangeDzHandler` or `AmendCommitWithHunkDzHandler` to validate drag-and-drop actions before calling `stackService.amendCommit()`, which executes the `amend_virtual_branch` backend command.
- **Reword operations** bypass drop-handlers and directly invoke `stackService.commitReword()` to trigger the `commit_reword` command, changing commit messages without modifying content.
- **Squash operations** utilize `SquashCommitDzHandler` to validate that commits can be combined, then call `stackService.squashCommits()` to execute the `squash_commits` command.

All operations route through the `StackService` RTK-Query mutations defined in [`apps/desktop/src/lib/stacks/stackService.svelte.ts`](https://github.com/gitbutlerapp/gitbutler/blob/main/apps/desktop/src/lib/stacks/stackService.svelte.ts), which communicate with the Rust backend in [`crates/gitbutler-workspace/src/lib.rs`](https://github.com/gitbutlerapp/gitbutler/blob/main/crates/gitbutler-workspace/src/lib.rs) to perform the actual Git repository manipulation.

## Frequently Asked Questions

### How does GitButler handle conflicts during an amend operation?

The `AmendCommitWithChangeDzHandler` class in [`apps/desktop/src/lib/commits/dropHandler.ts`](https://github.com/gitbutlerapp/gitbutler/blob/main/apps/desktop/src/lib/commits/dropHandler.ts) checks the `hasConflicts` property of the target commit before allowing the drop operation. If conflicts exist, the handler prevents the amend action and surfaces an error message through the `getMoveCommitIllegalActionMessage` helper, ensuring users resolve conflicts before modifying commit history.

### What is the difference between amend and squash in GitButler's implementation?

While both operations use drag-and-drop handlers, they serve different purposes and use distinct validation logic. **Amend** (`AmendCommitWithChangeDzHandler`) adds new file changes or hunks to an existing commit's content without creating new history entries. **Squash** (`SquashCommitDzHandler`) combines two or more existing commits into one, collapsing the commit graph. Amend modifies a commit's tree, while squash modifies the commit graph structure by merging histories.

### Can GitButler reword merge commits or only regular commits?

According to the source code in [`apps/desktop/src/lib/stacks/stackService.svelte.ts`](https://github.com/gitbutlerapp/gitbutler/blob/main/apps/desktop/src/lib/stacks/stackService.svelte.ts), the `commitReword` mutation sends the `commit_reword` command to the backend without specific restrictions on commit type. However, the UI typically exposes the reword action through commit card interfaces that check properties like `isRemote` and `isIntegrated` to determine eligibility. Merge commits would be subject to the same backend capabilities as regular commits, though the UI may filter them based on stack state and remote synchronization status.

### Where does the actual Git execution happen in GitButler's commit editing flow?

The actual Git operations occur in the Rust backend, specifically within the `gitbutler-workspace` crate at [`crates/gitbutler-workspace/src/lib.rs`](https://github.com/gitbutlerapp/gitbutler/blob/main/crates/gitbutler-workspace/src/lib.rs). When the TypeScript frontend calls mutations like `amendCommit`, `commitReword`, or `squashCommits` through [`stackService.svelte.ts`](https://github.com/gitbutlerapp/gitbutler/blob/main/stackService.svelte.ts), these map to commands (`amend_virtual_branch`, `commit_reword`, `squash_commits`) sent to the Rust layer. The backend then executes the equivalent Git plumbing commands directly on the repository.