Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
<script lang="ts">
import { SETTINGS_SERVICE } from '$lib/config/appSettingsV2';
import { ircEnabled, ircServer, fModeEnabled } from '$lib/config/uiFeatureFlags';
import {
ircEnabled,
ircServer,
fModeEnabled,
useNewRebaseEngine
} from '$lib/config/uiFeatureFlags';
import { USER } from '$lib/user/user';
import { inject } from '@gitbutler/core/context';
import { CardGroup, Textbox, Toggle } from '@gitbutler/ui';
Expand Down Expand Up @@ -83,6 +88,21 @@
/>
{/snippet}
</CardGroup.Item>
<CardGroup.Item labelFor="new-rebase-engine">
{#snippet title()}
New rebase engine
{/snippet}
{#snippet caption()}
Use the new graph-based rebase engine for stack operations.
{/snippet}
{#snippet actions()}
<Toggle
id="new-rebase-engine"
checked={$useNewRebaseEngine}
onclick={() => useNewRebaseEngine.set(!$useNewRebaseEngine)}
/>
{/snippet}
</CardGroup.Item>

{#if $user?.role === 'admin'}
<CardGroup.Item labelFor="single-branch">
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src/lib/config/uiFeatureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ export type StagingBehavior = 'all' | 'selection' | 'none';
export const stagingBehaviorFeature = persisted<StagingBehavior>('all', 'feature-staging-behavior');
export const fModeEnabled = persisted(true, 'f-mode');
export const newlineOnEnter = persisted(false, 'feature-newline-on-enter');
export const useNewRebaseEngine = persisted(false, 'feature-use-new-rebase-engine');
21 changes: 19 additions & 2 deletions apps/desktop/src/lib/stacks/stackService.svelte.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useNewRebaseEngine } from '$lib/config/uiFeatureFlags';
import { ConflictEntries, type ConflictEntriesObj } from '$lib/files/conflicts';
import { sortLikeFileTree } from '$lib/files/filetreeV3';
import { showToast } from '$lib/notifications/toasts';
Expand Down Expand Up @@ -28,6 +29,7 @@ import {
type ThunkDispatch,
type UnknownAction
} from '@reduxjs/toolkit';
import { get } from 'svelte/store';
import type { StackOrder } from '$lib/branches/branch';
import type { Commit, CommitDetails, UpstreamCommit } from '$lib/branches/v3';
import type { MoveCommitIllegalAction } from '$lib/commits/commit';
Expand Down Expand Up @@ -630,7 +632,11 @@ export class StackService {
}

get updateCommitMessage() {
return this.api.endpoints.updateCommitMessage.useMutation();
if (get(useNewRebaseEngine)) {
return this.api.endpoints.updateCommitMessage.useMutation();
} else {
return this.api.endpoints.legacyUpdateCommitMessage.useMutation();
}
}

get newBranch() {
Expand Down Expand Up @@ -1181,7 +1187,7 @@ function injectEndpoints(api: ClientState['backendApi'], uiState: UiState) {
};
}
}),
updateCommitMessage: build.mutation<
legacyUpdateCommitMessage: build.mutation<
string,
{ projectId: string; stackId: string; commitId: string; message: string }
>({
Expand All @@ -1192,6 +1198,17 @@ function injectEndpoints(api: ClientState['backendApi'], uiState: UiState) {
query: (args) => args,
invalidatesTags: () => [invalidatesList(ReduxTag.HeadSha)]
}),
updateCommitMessage: build.mutation<
string,
{ projectId: string; commitId: string; message: string }
>({
extraOptions: {
command: 'reword_commit',
actionName: 'Update Commit Message'
},
query: (args) => args,
invalidatesTags: () => [invalidatesList(ReduxTag.HeadSha)]
}),
newBranch: build.mutation<
void,
{ projectId: string; stackId: string; request: { targetPatch?: string; name: string } }
Expand Down
38 changes: 20 additions & 18 deletions crates/but-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ default = []
tauri = ["dep:tauri", "legacy"]
## When paths are turned into strings they are lossy. Also provide a `_bytes` variant of such fields with the original value
## for lossless use.
path-bytes = ["dep:bstr"]
path-bytes = []
## Make legacy functionality available.
legacy = [
"but-ctx/legacy",
Expand Down Expand Up @@ -86,33 +86,35 @@ but-rules = { workspace = true, optional = true }
but-gerrit = { workspace = true, optional = true }
but-worktrees = { workspace = true, optional = true }

gitbutler-user = {workspace = true, optional = true}
gitbutler-project = {workspace = true, optional = true}
gitbutler-branch = {workspace = true, optional = true}
gitbutler-branch-actions = {workspace = true, optional = true}
gitbutler-commit = {workspace = true, optional = true}
gitbutler-reference = {workspace = true, optional = true}
gitbutler-stack = {workspace = true, optional = true}
gitbutler-repo = {workspace = true, optional = true}
gitbutler-repo-actions = {workspace = true, optional = true}
gitbutler-edit-mode = {workspace = true, optional = true}
gitbutler-operating-modes = {workspace = true, optional = true}
gitbutler-sync = {workspace = true, optional = true}
gitbutler-oplog = {workspace = true, optional = true}
gitbutler-git = {workspace = true, optional = true}
gitbutler-user = { workspace = true, optional = true }
gitbutler-project = { workspace = true, optional = true }
gitbutler-branch = { workspace = true, optional = true }
gitbutler-branch-actions = { workspace = true, optional = true }
gitbutler-commit = { workspace = true, optional = true }
gitbutler-reference = { workspace = true, optional = true }
gitbutler-stack = { workspace = true, optional = true }
gitbutler-repo = { workspace = true, optional = true }
gitbutler-repo-actions = { workspace = true, optional = true }
gitbutler-edit-mode = { workspace = true, optional = true }
gitbutler-operating-modes = { workspace = true, optional = true }
gitbutler-sync = { workspace = true, optional = true }
gitbutler-oplog = { workspace = true, optional = true }
gitbutler-git = { workspace = true, optional = true }

tauri = { version = "^2.9.3", features = ["unstable"], optional = true } # For the #[tauri::command(async)] macro only
tauri = { version = "^2.9.3", features = [
"unstable",
], optional = true } # For the #[tauri::command(async)] macro only

boolean-enums = { version = "0.4.1", features = ["serde"] }
bstr = { workspace = true, optional = true }
bstr = { workspace = true }
schemars.workspace = true
serde.workspace = true
tokio = { workspace = true, features = ["full"] }
anyhow.workspace = true
serde_json.workspace = true
tracing.workspace = true
serde-error = "0.1.3"
gix = { workspace = true, features = ["worktree-mutation" ] }
gix = { workspace = true, features = ["worktree-mutation"] }
git2.workspace = true
open.workspace = true
url = { version = "2.5", optional = true }
Expand Down
44 changes: 44 additions & 0 deletions crates/but-api/src/commit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use bstr::{BString, ByteSlice};
use but_api_macros::but_api;
use but_oplog::legacy::{OperationKind, SnapshotDetails};
use tracing::instrument;

/// Rewords a commit, but without updating the oplog.
///
/// Returns the ID of the newly renamed commit
#[but_api]
#[instrument(err(Debug))]
pub fn reword_commit_only(
ctx: &but_ctx::Context,
commit_id: gix::ObjectId,
message: BString,
) -> anyhow::Result<gix::ObjectId> {
let mut guard = ctx.exclusive_worktree_access();
let (repo, _, graph) = ctx.graph_and_meta_mut_and_repo_from_head(guard.write_permission())?;

but_workspace::commit::reword(&graph, &repo, commit_id, message.as_bstr())
}

/// Apply `existing_branch` to the workspace in the repository that `ctx` refers to, or create the workspace with default name.
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy-paste error in documentation: This docstring describes "Apply existing_branch to the workspace..." which is unrelated to rewording commits. This appears to be copied from another function and not updated.

The documentation should describe what reword_commit actually does, for example:

/// Rewords a commit message and updates the oplog.
///
/// Returns the ID of the newly renamed commit
Suggested change
/// Apply `existing_branch` to the workspace in the repository that `ctx` refers to, or create the workspace with default name.
/// Rewords a commit message and updates the oplog.

Copilot uses AI. Check for mistakes.
///
/// Returns the ID of the newly renamed commit
#[but_api]
#[instrument(err(Debug))]
pub fn reword_commit(
ctx: &but_ctx::Context,
commit_id: gix::ObjectId,
message: BString,
) -> anyhow::Result<gix::ObjectId> {
// NOTE: since this is optional by nature, the same would be true if snapshotting/undo would be disabled via `ctx` app settings, for instance.
let maybe_oplog_entry = but_oplog::UnmaterializedOplogSnapshot::from_details(
ctx,
SnapshotDetails::new(OperationKind::UpdateCommitMessage),
)
.ok();

let res = reword_commit_only(ctx, commit_id, message);
if let Some(snapshot) = maybe_oplog_entry.filter(|_| res.is_ok()) {
snapshot.commit(ctx).ok();
};
res
}
3 changes: 3 additions & 0 deletions crates/but-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ pub mod github;
/// Functions that take a branch as input.
pub mod branch;

/// Functions that operate commits
pub mod commit;

/// Functions that show what changed in various Git entities, like trees, commits and the worktree.
pub mod diff;

Expand Down
15 changes: 13 additions & 2 deletions crates/but-rebase/src/graph_rebase/materialize.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
//! Functions for materializing a rebase
use std::collections::HashMap;

use crate::graph_rebase::{Checkouts, rebase::SuccessfulRebase};
use anyhow::Result;
use but_core::{
Expand All @@ -9,9 +11,16 @@ use but_core::{
},
};

/// The outcome of a materialize
#[derive(Debug, Clone)]
pub struct MaterializeOutcome {
/// A mapping of any commits that were rewritten as part of the rebase
pub commit_mapping: HashMap<gix::ObjectId, gix::ObjectId>,
}

impl SuccessfulRebase {
/// Materializes a history rewrite
pub fn materialize(mut self) -> Result<()> {
pub fn materialize(mut self) -> Result<MaterializeOutcome> {
let repo = self.repo.clone();
if let Some(memory) = self.repo.objects.take_object_memory() {
memory.persist(self.repo)?;
Expand Down Expand Up @@ -40,6 +49,8 @@ impl SuccessfulRebase {

repo.edit_references(self.ref_edits.clone())?;

Ok(())
Ok(MaterializeOutcome {
commit_mapping: self.commit_mapping,
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ use but_core::ref_metadata::MaybeDebug;

use crate::WorkspaceCommit;

pub mod reword;
pub use reword::function::reword;

/// A minimal stack for use by [WorkspaceCommit::new_from_stacks()].
#[derive(Clone)]
pub struct Stack {
Expand Down
35 changes: 35 additions & 0 deletions crates/but-workspace/src/commit/reword.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//! An action to perform a reword of a commit

pub(crate) mod function {
use anyhow::Result;
use bstr::BStr;
use but_rebase::{
commit::DateMode,
graph_rebase::{GraphExt, Step},
};

/// This action will rewrite a commit and any relevant history so it uses
/// the new name.
///
/// Returns the ID of the newly renamed commit
pub fn reword(
graph: &but_graph::Graph,
repo: &gix::Repository,
commit_id: gix::ObjectId,
new_message: &BStr,
) -> Result<gix::ObjectId> {
let mut editor = graph.to_editor(repo)?;
let target_selector = editor.select_commit(commit_id)?;

let mut commit = editor.find_commit(commit_id)?;
commit.message = new_message.to_owned();
let new_id = editor.write_commit(commit, DateMode::CommitterUpdateAuthorKeep)?;

editor.replace(&target_selector, Step::new_pick(new_id));

let outcome = editor.rebase()?;
outcome.materialize()?;

Ok(new_id)
}
}
2 changes: 1 addition & 1 deletion crates/but-workspace/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ pub mod branch;
mod changeset;

/// Utility types for the [`WorkspaceCommit`].
pub(crate) mod commit;
pub mod commit;

/// Types used only when obtaining head-information.
///
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/usr/bin/env bash

set -eu -o pipefail

echo "/remote/" > .gitignore
mkdir remote
pushd remote
git init
popd

git init

git remote add origin ./remote

git checkout -b one
echo "foo" > one.txt
git add .
git commit -m "commit one"

git checkout -b two
echo "foo" > two.txt
git add .
git commit -m "commit two"

git push -u origin two

git checkout -b three
echo "foo" > three.txt
git add .
git commit -m "commit three"
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
mod reword;

mod from_new_merge_with_metadata {
use bstr::ByteSlice;
use but_graph::init::{Options, Overlay};
Expand Down
Loading
Loading