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
7 changes: 5 additions & 2 deletions apps/desktop/src/components/BranchesView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,11 @@
import { focusable } from "@gitbutler/ui/focus/focusable";
import { getTimeAgo } from "@gitbutler/ui/utils/timeAgo";
import type { BranchFilterOption, SidebarEntrySubject } from "$lib/branches/branchListing";

type Props = {
projectId: string;
};

const { projectId }: Props = $props();

type BranchesSelection =
| {
type: "branch";
Expand All @@ -46,6 +45,9 @@
| { type: "pr"; prNumber: number }
| { type: "target"; commitId?: string };

const { projectId }: Props = $props();

const addToLeftmost = persisted<boolean>(false, "branch-placement-leftmost");
const stackService = inject(STACK_SERVICE);
const baseBranchService = inject(BASE_BRANCH_SERVICE);
const forge = inject(DEFAULT_FORGE_FACTORY);
Expand Down Expand Up @@ -80,6 +82,7 @@
branch: branchRef,
remote: remoteRef,
prNumber,
order: $addToLeftmost ? 0 : undefined,
});
handleCreateBranchFromBranchOutcome(outcome);
await baseBranchService.refreshBaseBranch(projectId);
Expand Down
4 changes: 4 additions & 0 deletions apps/desktop/src/components/BranchesViewPR.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import { STACK_SERVICE } from "$lib/stacks/stackService.svelte";

import { inject } from "@gitbutler/core/context";
import { persisted } from "@gitbutler/shared/persisted";
import { Button, Modal, TestId, Textbox } from "@gitbutler/ui";
import type { DetailedPullRequest } from "$lib/forge/interface/types";

Expand All @@ -34,6 +35,8 @@
const remotesService = inject(REMOTES_SERVICE);
const stackService = inject(STACK_SERVICE);

const addToLeftmost = persisted<boolean>(false, "branch-placement-leftmost");

let createRemoteModal = $state<Modal>();
let inputRemoteName = $state<string>();
let loading = $state(false);
Expand Down Expand Up @@ -71,6 +74,7 @@
branch: remoteRef,
remote: remoteRef,
prNumber,
order: $addToLeftmost ? 0 : undefined,
});

handleCreateBranchFromBranchOutcome(outcome);
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/lib/stacks/stackService.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1727,7 +1727,7 @@ function injectEndpoints(api: ClientState["backendApi"], uiState: UiState) {
}),
createVirtualBranchFromBranch: build.mutation<
CreateBranchFromBranchOutcome,
{ projectId: string; branch: string; remote?: string; prNumber?: number }
{ projectId: string; branch: string; remote?: string; prNumber?: number; order?: number }
>({
extraOptions: {
command: "create_virtual_branch_from_branch",
Expand Down
3 changes: 2 additions & 1 deletion crates/but-api/src/legacy/virtual_branches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,10 @@ pub fn create_virtual_branch_from_branch(
branch: Refname,
remote: Option<RemoteRefname>,
pr_number: Option<usize>,
order: Option<usize>,
) -> Result<gitbutler_branch_actions::CreateBranchFromBranchOutcome> {
let outcome = gitbutler_branch_actions::create_virtual_branch_from_branch(
ctx, &branch, remote, pr_number,
ctx, &branch, remote, pr_number, order,
)?;
Ok(outcome.into())
}
Expand Down
1 change: 1 addition & 0 deletions crates/but-api/src/legacy/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,7 @@ pub fn split_branch(
&refname,
None,
None,
None,
guard.write_permission(),
)?;

Expand Down
1 change: 1 addition & 0 deletions crates/but-testing/src/command/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ pub mod stacks {
&ref_name,
Some(remote_ref_name),
None,
None,
)?;

let repo = ctx.repo.get()?;
Expand Down
1 change: 1 addition & 0 deletions crates/but-tools/src/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1155,6 +1155,7 @@ pub fn split_branch(
&refname,
None,
None,
None,
guard.write_permission(),
)?;

Expand Down
4 changes: 4 additions & 0 deletions crates/but/src/command/legacy/branch/apply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub fn apply(ctx: &mut Context, branch_name: &str, out: &mut OutputChannel) -> a
ref_name,
remote_ref_name,
None,
None,
)?;
r
} else if let Some((remote_ref, r)) = find_remote_reference(&repo, branch_name)? {
Expand All @@ -48,6 +49,7 @@ pub fn apply(ctx: &mut Context, branch_name: &str, out: &mut OutputChannel) -> a
ref_name,
Some(remote_ref.clone()),
None,
None,
)?;
r
} else {
Expand Down Expand Up @@ -90,6 +92,7 @@ pub fn apply(ctx: &mut Context, branch_name: &str, out: &mut OutputChannel) -> a
ref_name,
remote_ref_name,
None,
None,
)?;
r
}
Expand All @@ -110,6 +113,7 @@ pub fn apply(ctx: &mut Context, branch_name: &str, out: &mut OutputChannel) -> a
ref_name,
Some(remote_ref),
None,
None,
)?;
r
}
Expand Down
2 changes: 2 additions & 0 deletions crates/gitbutler-branch-actions/src/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@ pub fn create_virtual_branch_from_branch(
branch: &Refname,
remote: Option<RemoteRefname>,
pr_number: Option<usize>,
order: Option<usize>,
) -> Result<(StackId, Vec<StackId>, Vec<String>)> {
let mut guard = ctx.exclusive_worktree_access();
ctx.verify(guard.write_permission())?;
Expand All @@ -431,6 +432,7 @@ pub fn create_virtual_branch_from_branch(
branch,
remote,
pr_number,
order,
guard.write_permission(),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ impl BranchManager<'_> {
target: &Refname,
upstream_branch: Option<RemoteRefname>,
pr_number: Option<usize>,
order: Option<usize>,
perm: &mut RepoExclusive,
) -> Result<(StackId, Vec<StackId>, Vec<String>)> {
Comment on lines 103 to 106
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

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

The new order input isn’t covered by tests yet. It would be good to add a create_virtual_branch_from_branch(..., order: Some(0)) test (and possibly another non-zero case) asserting that the created stack ends up at the requested position and that existing stacks are shifted/renumbered appropriately.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

@PavelLaptev PavelLaptev Mar 5, 2026

Choose a reason for hiding this comment

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

The shadowing is real. However, this is idiomatic Rust — shadowing an Option with its unwrapped value is a common and accepted pattern. It's not a bug or a readability problem in Rust conventions, unlike in other languages. The suggestion is a matter of style preference, not a correctness issue.

let branch_name = target
Expand Down Expand Up @@ -129,7 +130,7 @@ impl BranchManager<'_> {
OnWorkspaceMergeConflict::MaterializeAndReportConflictingStacks,
workspace_reference_naming: WorkspaceReferenceNaming::Default,
uncommitted_changes: UncommitedWorktreeChanges::KeepAndAbortOnConflict,
order: None,
order,
new_stack_id: None,
},
)?;
Expand Down Expand Up @@ -211,7 +212,7 @@ impl BranchManager<'_> {
.peel_to_commit()
.context("failed to peel to commit")?;

let order = vb_state.next_order_index()?;
let order = order.unwrap_or(vb_state.next_order_index()?);

Comment on lines 213 to 216
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

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

The new order: Option<usize> behavior for create_virtual_branch_from_branch isn’t currently exercised by tests (all updated call sites pass None). Please add at least one test that passes Some(0) and asserts the resulting stack ordering is correct (including any shifting/renumbering of existing stacks), so regressions in placement logic are caught.

Copilot uses AI. Check for mistakes.
let mut branch = if let Some(mut branch) = vb_state
.find_by_top_reference_name_where_not_in_workspace(&target.to_string())?
Expand Down
1 change: 1 addition & 0 deletions crates/gitbutler-branch-actions/src/move_branch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ pub(crate) fn tear_off_branch(
&Refname::Local(LocalRefname::new(subject_branch_name, None)),
None,
None,
None,
perm,
)?;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ fn rebase_commit() {
&unapplied_branch,
None,
None,
None,
)
.unwrap();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ fn no_conflicts() {
&"refs/remotes/origin/branch".parse().unwrap(),
None,
None,
None,
)
.map(|o| o.0)
.unwrap();
Expand Down Expand Up @@ -90,6 +91,7 @@ fn conflicts_with_uncommited() {
&"refs/remotes/origin/branch".parse().unwrap(),
None,
None,
None,
)
.map(|o| o.0)
.unwrap();
Expand Down Expand Up @@ -148,6 +150,7 @@ fn conflicts_with_commited() {
&"refs/remotes/origin/branch".parse().unwrap(),
None,
None,
None,
)
.map(|o| o.0)
.unwrap();
Expand Down Expand Up @@ -179,6 +182,7 @@ fn from_default_target() {
&"refs/remotes/origin/master".parse().unwrap(),
None,
None,
None,
)
.unwrap_err()
.to_string(),
Expand Down Expand Up @@ -207,6 +211,7 @@ fn from_non_existent_branch() {
&"refs/remotes/origin/branch".parse().unwrap(),
None,
None,
None,
)
.unwrap_err()
.to_string(),
Expand Down Expand Up @@ -247,6 +252,7 @@ fn from_state_remote_branch() {
&"refs/remotes/origin/branch".parse().unwrap(),
None,
None,
None,
)
.unwrap();

Expand Down
2 changes: 2 additions & 0 deletions crates/gitbutler-cli/src/command/vbranch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ fn apply_by_name(ctx: &mut Context, branch_name: String) -> Result<()> {
.context("local reference name was missing")?,
None,
None,
None,
guard.write_permission(),
)?,
)
Expand All @@ -85,6 +86,7 @@ fn apply_from_branch(ctx: &mut Context, branch_name: String) -> Result<()> {
&target,
None,
None,
None,
guard.write_permission(),
)?)
}
Expand Down
Loading