Skip to content

Commit 0a2a280

Browse files
committed
Use safe-checkout for BranchManager::apply_branch().
1 parent bbfdea7 commit 0a2a280

File tree

9 files changed

+85
-46
lines changed

9 files changed

+85
-46
lines changed

crates/but-workspace/src/head.rs

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
use anyhow::{Context, Result};
33
use gitbutler_cherry_pick::RepositoryExt as _;
44
use gitbutler_command_context::CommandContext;
5-
use gitbutler_oxidize::{GixRepositoryExt, ObjectIdExt, OidExt, RepoExt};
5+
use gitbutler_oxidize::{GixRepositoryExt, ObjectIdExt, OidExt};
66
use gitbutler_repo::{RepositoryExt, SignaturePurpose};
77
use gitbutler_stack::{Stack, VirtualBranchesHandle};
88
use gix::merge::tree::TreatAsUnresolved;
@@ -22,7 +22,7 @@ pub fn merge_worktree_with_workspace<'a>(
2222

2323
// The tree of where the gitbutler workspace is at
2424
let workspace_tree = gix_repo
25-
.find_commit(super::remerged_head_commit(ctx)?.to_gix())?
25+
.find_commit(super::remerged_workspace_commit_v2(ctx)?.to_gix())?
2626
.tree_id()?
2727
.detach();
2828

@@ -40,16 +40,12 @@ pub fn merge_worktree_with_workspace<'a>(
4040
Ok((outcome, conflict_kind))
4141
}
4242

43-
/// Creates and returns a merge commit of all active branch heads.
44-
///
45-
/// This is the base against which we diff the working directory to understand
46-
/// what files have been modified.
47-
///
48-
/// This should be used to update the `gitbutler/workspace` ref with, which is usually
49-
/// done from [`update_workspace_commit()`], after any of its input changes.
50-
/// This is namely the conflicting state, or any head of the virtual branches.
51-
#[instrument(level = tracing::Level::DEBUG, skip(ctx))]
52-
pub fn remerged_head_commit(ctx: &CommandContext) -> Result<git2::Oid> {
43+
/// Merge all currently stored stacks together into a new tree and return `(merged_tree, stacks, target_commit)` id accordingly.
44+
/// `gix_repo` should be optimised for merging.
45+
pub fn remerged_workspace_tree_v2<'git2_repo>(
46+
ctx: &'git2_repo CommandContext,
47+
gix_repo: &gix::Repository,
48+
) -> Result<(git2::Oid, Vec<Stack>, git2::Commit<'git2_repo>)> {
5349
let vb_state = VirtualBranchesHandle::new(ctx.project().gb_dir());
5450
let target = vb_state
5551
.get_default_target()
@@ -58,15 +54,14 @@ pub fn remerged_head_commit(ctx: &CommandContext) -> Result<git2::Oid> {
5854
let mut stacks: Vec<Stack> = vb_state.list_stacks_in_workspace()?;
5955

6056
let target_commit = repo.find_commit(target.sha)?;
61-
let mut workspace_tree = repo.find_real_tree(&target_commit, Default::default())?;
57+
let workspace_tree = repo.find_real_tree(&target_commit, Default::default())?;
6258
let mut workspace_tree_id = workspace_tree.id().to_gix();
6359

64-
let gix_repo = ctx.gix_repo_for_merging()?;
6560
let (merge_options_fail_fast, conflict_kind) = gix_repo.merge_options_fail_fast()?;
6661
let merge_tree_id = repo.find_commit(target.sha)?.tree_id().to_gix();
6762
for stack in stacks.iter_mut() {
6863
stack.migrate_change_ids(ctx).ok(); // If it fails that's ok - best effort migration
69-
let branch_head = repo.find_commit(stack.head_oid(&gix_repo)?.to_git2())?;
64+
let branch_head = repo.find_commit(stack.head_oid(gix_repo)?.to_git2())?;
7065
let branch_tree_id = repo
7166
.find_real_tree(&branch_head, Default::default())?
7267
.id()
@@ -89,11 +84,26 @@ pub fn remerged_head_commit(ctx: &CommandContext) -> Result<git2::Oid> {
8984
vb_state.set_stack(stack.clone())?;
9085
}
9186
}
92-
workspace_tree = repo.find_tree(workspace_tree_id.to_git2())?;
87+
Ok((workspace_tree_id.to_git2(), stacks, target_commit))
88+
}
89+
90+
/// Creates and returns a merge commit of all active branch heads.
91+
///
92+
/// This is the base against which we diff the working directory to understand
93+
/// what files have been modified.
94+
///
95+
/// This should be used to update the `gitbutler/workspace` ref with, which is usually
96+
/// done from [`update_workspace_commit()`], after any of its input changes.
97+
/// This is namely the conflicting state, or any head of the virtual branches.
98+
#[instrument(level = tracing::Level::DEBUG, skip(ctx))]
99+
pub fn remerged_workspace_commit_v2(ctx: &CommandContext) -> Result<git2::Oid> {
100+
let repo = ctx.repo();
101+
let gix_repo = ctx.gix_repo_for_merging()?;
102+
let (workspace_tree_id, stacks, target_commit) = remerged_workspace_tree_v2(ctx, &gix_repo)?;
103+
let workspace_tree = repo.find_tree(workspace_tree_id)?;
93104

94105
let committer = gitbutler_repo::signature(SignaturePurpose::Committer)?;
95106
let author = gitbutler_repo::signature(SignaturePurpose::Author)?;
96-
let gix_repo = repo.to_gix()?;
97107
let mut heads: Vec<git2::Commit<'_>> = stacks
98108
.iter()
99109
.filter_map(|stack| stack.head_oid(&gix_repo).ok())

crates/but-workspace/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ pub use tree_manipulation::{
4949
split_commit::{CommitFiles, CommmitSplitOutcome, split_commit},
5050
};
5151
pub mod head;
52-
pub use head::{merge_worktree_with_workspace, remerged_head_commit};
52+
pub use head::{
53+
merge_worktree_with_workspace, remerged_workspace_commit_v2, remerged_workspace_tree_v2,
54+
};
5355

5456
/// 🚧utilities for applying and unapplying branches 🚧.
5557
/// Ignore the name of this module; it's just a place to put code by now.

crates/gitbutler-branch-actions/src/base.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ pub fn get_base_branch_data(ctx: &CommandContext) -> Result<BaseBranch> {
5858
fn go_back_to_integration(ctx: &CommandContext, default_target: &Target) -> Result<BaseBranch> {
5959
let gix_repo = ctx.gix_repo_for_merging()?;
6060
if ctx.app_settings().feature_flags.cv3 {
61-
let workspace_commit_to_checkout = but_workspace::remerged_head_commit(ctx)?;
61+
let workspace_commit_to_checkout = but_workspace::remerged_workspace_commit_v2(ctx)?;
6262
let tree_to_checkout_to_avoid_ref_update = gix_repo
6363
.find_commit(workspace_commit_to_checkout.to_gix())?
6464
.tree_id()?;

crates/gitbutler-branch-actions/src/branch_manager/branch_creation.rs

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::BranchManager;
1+
use super::{checkout_remerged_head, BranchManager};
22
use crate::r#virtual as vbranch;
33
use crate::{hunk::VirtualBranchHunk, integration::update_workspace_commit, VirtualBranchesExt};
44
use anyhow::{anyhow, bail, Context, Result};
@@ -345,13 +345,19 @@ impl BranchManager<'_> {
345345
.context("failed to merge trees")?;
346346

347347
if !merges_cleanly {
348-
for stack in vb_state
348+
for stack_to_unapply in vb_state
349349
.list_stacks_in_workspace()?
350350
.iter()
351351
.filter(|branch| branch.id != stack_id)
352352
{
353-
unapplied_stacks.push(stack.id);
354-
self.unapply(stack.id, perm, false, Vec::new(), safe_checkout)?;
353+
unapplied_stacks.push(stack_to_unapply.id);
354+
let res =
355+
self.unapply(stack_to_unapply.id, perm, false, Vec::new(), safe_checkout);
356+
if res.is_err() {
357+
stack.in_workspace = false;
358+
vb_state.set_stack(stack.clone())?;
359+
}
360+
res?;
355361
}
356362
}
357363
}
@@ -429,16 +435,25 @@ impl BranchManager<'_> {
429435
}
430436
}
431437

438+
// Permissions here might be wonky, just go with it though.
432439
let new_workspace = WorkspaceState::create(self.ctx, perm.read_permission())?;
433-
434-
update_uncommited_changes_with_tree(
435-
self.ctx,
436-
workspace_state,
437-
new_workspace,
438-
old_cwd,
439-
Some(true),
440-
perm,
441-
)?;
440+
if safe_checkout {
441+
let res = checkout_remerged_head(self.ctx, &gix_repo);
442+
if res.is_err() {
443+
stack.in_workspace = false;
444+
vb_state.set_stack(stack.clone())?;
445+
}
446+
res?;
447+
} else {
448+
update_uncommited_changes_with_tree(
449+
self.ctx,
450+
workspace_state,
451+
new_workspace,
452+
old_cwd,
453+
Some(true),
454+
perm,
455+
)?;
456+
}
442457

443458
update_workspace_commit(&vb_state, self.ctx)?;
444459

crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use gitbutler_stack::StackId;
1010
use gitbutler_workspace::workspace_base;
1111
use tracing::instrument;
1212

13-
use super::BranchManager;
13+
use super::{checkout_remerged_head, BranchManager};
1414
use crate::r#virtual as vbranch;
1515
use crate::VirtualBranchesExt;
1616

@@ -63,16 +63,7 @@ impl BranchManager<'_> {
6363
let gix_repo = self.ctx.gix_repo_for_merging()?;
6464
if safe_checkout {
6565
// This reads the just stored data and re-merges it all stacks, excluding the unapplied one.
66-
let res = (|| -> Result<()> {
67-
let workspace_without_stack = but_workspace::remerged_head_commit(self.ctx)?;
68-
but_workspace::branch::safe_checkout_from_head(
69-
workspace_without_stack.to_gix(),
70-
&gix_repo,
71-
but_workspace::branch::checkout::Options::default(),
72-
)?;
73-
Ok(())
74-
})();
75-
66+
let res = checkout_remerged_head(self.ctx, &gix_repo);
7667
// Undo the removal, stack is still there officially now.
7768
if res.is_err() {
7869
stack.in_workspace = true;

crates/gitbutler-branch-actions/src/branch_manager/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,24 @@
11
pub use branch_creation::CreateBranchFromBranchOutcome;
22
use gitbutler_command_context::CommandContext;
3+
use gitbutler_oxidize::OidExt;
34

45
mod branch_creation;
56
mod branch_removal;
67

8+
/// Note that this checks out the commit and sets the HEAD ref to point to it.
9+
pub(crate) fn checkout_remerged_head(
10+
ctx: &CommandContext,
11+
repo: &gix::Repository,
12+
) -> anyhow::Result<()> {
13+
let (workspace_tree_id, _, _) = but_workspace::remerged_workspace_tree_v2(ctx, repo)?;
14+
but_workspace::branch::safe_checkout_from_head(
15+
workspace_tree_id.to_gix(),
16+
repo,
17+
but_workspace::branch::checkout::Options::default(),
18+
)?;
19+
Ok(())
20+
}
21+
722
pub struct BranchManager<'l> {
823
ctx: &'l CommandContext,
924
}

crates/gitbutler-branch-actions/src/integration.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ pub fn update_workspace_commit(
8484
.list_stacks_in_workspace()
8585
.context("failed to list virtual branches")?;
8686

87-
let workspace_head = repo.find_commit(but_workspace::remerged_head_commit(ctx)?)?;
87+
let workspace_head = repo.find_commit(but_workspace::remerged_workspace_commit_v2(ctx)?)?;
8888

8989
// message that says how to get back to where they were
9090
let mut message = GITBUTLER_WORKSPACE_COMMIT_TITLE.to_string();

crates/gitbutler-branch-actions/src/status.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ pub fn get_applied_status(
3333
ctx: &CommandContext,
3434
perm: Option<&mut WorktreeWritePermission>,
3535
) -> Result<VirtualBranchesStatus> {
36-
let diffs = gitbutler_diff::workdir(ctx.repo(), but_workspace::remerged_head_commit(ctx)?)?;
36+
let diffs = gitbutler_diff::workdir(
37+
ctx.repo(),
38+
but_workspace::remerged_workspace_commit_v2(ctx)?,
39+
)?;
3740
get_applied_status_cached(ctx, perm, &diffs)
3841
}
3942

crates/gitbutler-branch-actions/src/virtual.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,10 @@ pub fn commit(
226226
ownership: Option<&BranchOwnershipClaims>,
227227
) -> Result<git2::Oid> {
228228
// get the files to commit
229-
let diffs = gitbutler_diff::workdir(ctx.repo(), but_workspace::remerged_head_commit(ctx)?)?;
229+
let diffs = gitbutler_diff::workdir(
230+
ctx.repo(),
231+
but_workspace::remerged_workspace_commit_v2(ctx)?,
232+
)?;
230233
let statuses = get_applied_status_cached(ctx, None, &diffs)
231234
.context("failed to get status by branch")?
232235
.branches;

0 commit comments

Comments
 (0)