Enforce locking by making locks part of the public oplog API.

This way, all methods that care about the `oplog` also have to
care about choosing the right lock.
This commit is contained in:
Sebastian Thiel 2024-07-15 14:22:27 +02:00
parent 09ca2d0284
commit 822fd92b9d
No known key found for this signature in database
GPG Key ID: 9CB5EE7895E8268B
11 changed files with 505 additions and 263 deletions

View File

@ -40,7 +40,10 @@ impl VirtualBranchActions {
run_hooks: bool,
) -> Result<git2::Oid> {
let project_repository = open_with_verify(project)?;
let snapshot_tree = project_repository.project().prepare_snapshot();
let mut guard = project.exclusive_worktree_access();
let snapshot_tree = project_repository
.project()
.prepare_snapshot(guard.read_permission());
let result = branch::commit(
&project_repository,
branch_id,
@ -55,6 +58,7 @@ impl VirtualBranchActions {
result.as_ref().err(),
message.to_owned(),
None,
guard.write_permission(),
)
});
result
@ -73,7 +77,11 @@ impl VirtualBranchActions {
&self,
project: &Project,
) -> Result<(Vec<branch::VirtualBranch>, Vec<diff::FileDiff>)> {
branch::list_virtual_branches(&open_with_verify(project)?).map_err(Into::into)
branch::list_virtual_branches(
&open_with_verify(project)?,
project.exclusive_worktree_access().write_permission(),
)
.map_err(Into::into)
}
pub async fn create_virtual_branch(
@ -82,8 +90,11 @@ impl VirtualBranchActions {
create: &BranchCreateRequest,
) -> Result<BranchId> {
let project_repository = open_with_verify(project)?;
let mut guard = project.exclusive_worktree_access();
let branch_manager = project_repository.branch_manager();
let branch_id = branch_manager.create_virtual_branch(create)?.id;
let branch_id = branch_manager
.create_virtual_branch(create, guard.write_permission())?
.id;
Ok(branch_id)
}
@ -109,9 +120,11 @@ impl VirtualBranchActions {
target_branch: &RemoteRefname,
) -> Result<BaseBranch> {
let project_repository = ProjectRepository::open(project)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationKind::SetBaseBranch));
let mut guard = project.exclusive_worktree_access();
let _ = project_repository.project().create_snapshot(
SnapshotDetails::new(OperationKind::SetBaseBranch),
guard.write_permission(),
);
set_base_branch(&project_repository, target_branch)
}
@ -126,18 +139,22 @@ impl VirtualBranchActions {
branch_id: BranchId,
) -> Result<()> {
let project_repository = open_with_verify(project)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationKind::MergeUpstream));
let mut guard = project.exclusive_worktree_access();
let _ = project_repository.project().create_snapshot(
SnapshotDetails::new(OperationKind::MergeUpstream),
guard.write_permission(),
);
branch::integrate_upstream_commits(&project_repository, branch_id).map_err(Into::into)
}
pub async fn update_base_branch(&self, project: &Project) -> Result<Vec<ReferenceName>> {
let project_repository = open_with_verify(project)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationKind::UpdateWorkspaceBase));
update_base_branch(&project_repository).map_err(Into::into)
let mut guard = project.exclusive_worktree_access();
let _ = project_repository.project().create_snapshot(
SnapshotDetails::new(OperationKind::UpdateWorkspaceBase),
guard.write_permission(),
);
update_base_branch(&project_repository, guard.write_permission()).map_err(Into::into)
}
pub async fn update_virtual_branch(
@ -146,7 +163,10 @@ impl VirtualBranchActions {
branch_update: BranchUpdateRequest,
) -> Result<()> {
let project_repository = open_with_verify(project)?;
let snapshot_tree = project_repository.project().prepare_snapshot();
let mut guard = project.exclusive_worktree_access();
let snapshot_tree = project_repository
.project()
.prepare_snapshot(guard.read_permission());
let old_branch = project_repository
.project()
.virtual_branches()
@ -158,6 +178,7 @@ impl VirtualBranchActions {
&old_branch,
&branch_update,
result.as_ref().err(),
guard.write_permission(),
)
});
result?;
@ -170,7 +191,8 @@ impl VirtualBranchActions {
) -> Result<()> {
let project_repository = open_with_verify(project)?;
let branch_manager = project_repository.branch_manager();
branch_manager.delete_branch(branch_id)
let mut guard = project.exclusive_worktree_access();
branch_manager.delete_branch(branch_id, guard.write_permission())
}
pub async fn unapply_ownership(
@ -179,17 +201,22 @@ impl VirtualBranchActions {
ownership: &BranchOwnershipClaims,
) -> Result<()> {
let project_repository = open_with_verify(project)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationKind::DiscardHunk));
branch::unapply_ownership(&project_repository, ownership).map_err(Into::into)
let mut guard = project.exclusive_worktree_access();
let _ = project_repository.project().create_snapshot(
SnapshotDetails::new(OperationKind::DiscardHunk),
guard.write_permission(),
);
branch::unapply_ownership(&project_repository, ownership, guard.write_permission())
.map_err(Into::into)
}
pub async fn reset_files(&self, project: &Project, files: &Vec<String>) -> Result<()> {
let project_repository = open_with_verify(project)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationKind::DiscardFile));
let mut guard = project.exclusive_worktree_access();
let _ = project_repository.project().create_snapshot(
SnapshotDetails::new(OperationKind::DiscardFile),
guard.write_permission(),
);
branch::reset_files(&project_repository, files).map_err(Into::into)
}
@ -201,10 +228,18 @@ impl VirtualBranchActions {
ownership: &BranchOwnershipClaims,
) -> Result<git2::Oid> {
let project_repository = open_with_verify(project)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationKind::AmendCommit));
branch::amend(&project_repository, branch_id, commit_oid, ownership)
let mut guard = project.exclusive_worktree_access();
let _ = project_repository.project().create_snapshot(
SnapshotDetails::new(OperationKind::AmendCommit),
guard.write_permission(),
);
branch::amend(
&project_repository,
branch_id,
commit_oid,
ownership,
guard.write_permission(),
)
}
pub async fn move_commit_file(
@ -216,9 +251,11 @@ impl VirtualBranchActions {
ownership: &BranchOwnershipClaims,
) -> Result<git2::Oid> {
let project_repository = open_with_verify(project)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationKind::MoveCommitFile));
let mut guard = project.exclusive_worktree_access();
let _ = project_repository.project().create_snapshot(
SnapshotDetails::new(OperationKind::MoveCommitFile),
guard.write_permission(),
);
branch::move_commit_file(
&project_repository,
branch_id,
@ -236,7 +273,10 @@ impl VirtualBranchActions {
commit_oid: git2::Oid,
) -> Result<()> {
let project_repository = open_with_verify(project)?;
let snapshot_tree = project_repository.project().prepare_snapshot();
let mut guard = project.exclusive_worktree_access();
let snapshot_tree = project_repository
.project()
.prepare_snapshot(guard.read_permission());
let result: Result<()> =
branch::undo_commit(&project_repository, branch_id, commit_oid).map_err(Into::into);
let _ = snapshot_tree.and_then(|snapshot_tree| {
@ -244,6 +284,7 @@ impl VirtualBranchActions {
snapshot_tree,
result.as_ref(),
commit_oid,
guard.write_permission(),
)
});
result
@ -257,9 +298,11 @@ impl VirtualBranchActions {
offset: i32,
) -> Result<()> {
let project_repository = open_with_verify(project)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationKind::InsertBlankCommit));
let mut guard = project.exclusive_worktree_access();
let _ = project_repository.project().create_snapshot(
SnapshotDetails::new(OperationKind::InsertBlankCommit),
guard.write_permission(),
);
branch::insert_blank_commit(&project_repository, branch_id, commit_oid, offset)
.map_err(Into::into)
}
@ -272,9 +315,11 @@ impl VirtualBranchActions {
offset: i32,
) -> Result<()> {
let project_repository = open_with_verify(project)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationKind::ReorderCommit));
let mut guard = project.exclusive_worktree_access();
let _ = project_repository.project().create_snapshot(
SnapshotDetails::new(OperationKind::ReorderCommit),
guard.write_permission(),
);
branch::reorder_commit(&project_repository, branch_id, commit_oid, offset)
.map_err(Into::into)
}
@ -286,9 +331,11 @@ impl VirtualBranchActions {
target_commit_oid: git2::Oid,
) -> Result<()> {
let project_repository = open_with_verify(project)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationKind::UndoCommit));
let mut guard = project.exclusive_worktree_access();
let _ = project_repository.project().create_snapshot(
SnapshotDetails::new(OperationKind::UndoCommit),
guard.write_permission(),
);
branch::reset_branch(&project_repository, branch_id, target_commit_oid).map_err(Into::into)
}
@ -299,14 +346,23 @@ impl VirtualBranchActions {
name_conflict_resolution: branch::NameConflictResolution,
) -> Result<ReferenceName> {
let project_repository = open_with_verify(project)?;
let snapshot_tree = project_repository.project().prepare_snapshot();
let mut guard = project.exclusive_worktree_access();
let snapshot_tree = project_repository
.project()
.prepare_snapshot(guard.read_permission());
let branch_manager = project_repository.branch_manager();
let result = branch_manager.convert_to_real_branch(branch_id, name_conflict_resolution);
let result = branch_manager.convert_to_real_branch(
branch_id,
name_conflict_resolution,
guard.write_permission(),
);
let _ = snapshot_tree.and_then(|snapshot_tree| {
project_repository
.project()
.snapshot_branch_unapplied(snapshot_tree, result.as_ref())
project_repository.project().snapshot_branch_unapplied(
snapshot_tree,
result.as_ref(),
guard.write_permission(),
)
});
result
@ -345,9 +401,11 @@ impl VirtualBranchActions {
commit_oid: git2::Oid,
) -> Result<()> {
let project_repository = open_with_verify(project)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationKind::SquashCommit));
let mut guard = project.exclusive_worktree_access();
let _ = project_repository.project().create_snapshot(
SnapshotDetails::new(OperationKind::SquashCommit),
guard.write_permission(),
);
branch::squash(&project_repository, branch_id, commit_oid).map_err(Into::into)
}
@ -359,9 +417,11 @@ impl VirtualBranchActions {
message: &str,
) -> Result<()> {
let project_repository = open_with_verify(project)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationKind::UpdateCommitMessage));
let mut guard = project.exclusive_worktree_access();
let _ = project_repository.project().create_snapshot(
SnapshotDetails::new(OperationKind::UpdateCommitMessage),
guard.write_permission(),
);
branch::update_commit_message(&project_repository, branch_id, commit_oid, message)
.map_err(Into::into)
}
@ -408,10 +468,18 @@ impl VirtualBranchActions {
commit_oid: git2::Oid,
) -> Result<()> {
let project_repository = open_with_verify(project)?;
let _ = project_repository
.project()
.create_snapshot(SnapshotDetails::new(OperationKind::MoveCommit));
branch::move_commit(&project_repository, target_branch_id, commit_oid).map_err(Into::into)
let mut guard = project.exclusive_worktree_access();
let _ = project_repository.project().create_snapshot(
SnapshotDetails::new(OperationKind::MoveCommit),
guard.write_permission(),
);
branch::move_commit(
&project_repository,
target_branch_id,
commit_oid,
guard.write_permission(),
)
.map_err(Into::into)
}
pub async fn create_virtual_branch_from_branch(
@ -421,14 +489,16 @@ impl VirtualBranchActions {
) -> Result<BranchId> {
let project_repository = open_with_verify(project)?;
let branch_manager = project_repository.branch_manager();
let mut guard = project.exclusive_worktree_access();
branch_manager
.create_virtual_branch_from_branch(branch)
.create_virtual_branch_from_branch(branch, guard.write_permission())
.map_err(Into::into)
}
}
fn open_with_verify(project: &Project) -> Result<ProjectRepository> {
let project_repository = ProjectRepository::open(project)?;
crate::integration::verify_branch(&project_repository)?;
let mut guard = project.exclusive_worktree_access();
crate::integration::verify_branch(&project_repository, guard.write_permission())?;
Ok(project_repository)
}

View File

@ -22,6 +22,7 @@ use crate::remote::{commit_to_remote_commit, RemoteCommit};
use crate::{VirtualBranchHunk, VirtualBranchesExt};
use gitbutler_branch::GITBUTLER_INTEGRATION_REFERENCE;
use gitbutler_error::error::Marker;
use gitbutler_project::access::WorktreeWritePermission;
use gitbutler_repo::rebase::cherry_rebase;
#[derive(Debug, Serialize, PartialEq, Clone)]
@ -329,6 +330,7 @@ fn _print_tree(repo: &git2::Repository, tree: &git2::Tree) -> Result<()> {
// update the target sha
pub(crate) fn update_base_branch(
project_repository: &ProjectRepository,
perm: &mut WorktreeWritePermission,
) -> anyhow::Result<Vec<ReferenceName>> {
project_repository.assure_resolved()?;
@ -418,8 +420,11 @@ pub(crate) fn update_base_branch(
if branch_tree_merge_index.has_conflicts() {
// branch tree conflicts with new target, unapply branch for now. we'll handle it later, when user applies it back.
let branch_manager = project_repository.branch_manager();
let unapplied_real_branch =
branch_manager.convert_to_real_branch(branch.id, Default::default())?;
let unapplied_real_branch = branch_manager.convert_to_real_branch(
branch.id,
Default::default(),
perm,
)?;
unapplied_branch_names.push(unapplied_real_branch);
@ -452,8 +457,11 @@ pub(crate) fn update_base_branch(
// branch commits conflict with new target, make sure the branch is
// unapplied. conflicts witll be dealt with when applying it back.
let branch_manager = project_repository.branch_manager();
let unapplied_real_branch =
branch_manager.convert_to_real_branch(branch.id, Default::default())?;
let unapplied_real_branch = branch_manager.convert_to_real_branch(
branch.id,
Default::default(),
perm,
)?;
unapplied_branch_names.push(unapplied_real_branch);
return Ok(None);

View File

@ -12,14 +12,18 @@ use gitbutler_branch::{
use gitbutler_commit::commit_headers::HasCommitHeaders;
use gitbutler_error::error::Marker;
use gitbutler_oplog::SnapshotExt;
use gitbutler_project::access::WorktreeWritePermission;
use gitbutler_reference::Refname;
use gitbutler_repo::{rebase::cherry_rebase, RepoActionsExt, RepositoryExt};
use gitbutler_time::time::now_since_unix_epoch_ms;
impl BranchManager<'_> {
pub fn create_virtual_branch(&self, create: &BranchCreateRequest) -> Result<Branch> {
pub fn create_virtual_branch(
&self,
create: &BranchCreateRequest,
perm: &mut WorktreeWritePermission,
) -> Result<Branch> {
let vb_state = self.project_repository.project().virtual_branches();
let default_target = vb_state.get_default_target()?;
let commit = self
@ -50,7 +54,7 @@ impl BranchManager<'_> {
_ = self
.project_repository
.project()
.snapshot_branch_creation(name.clone());
.snapshot_branch_creation(name.clone(), perm);
all_virtual_branches.sort_by_key(|branch| branch.order);
@ -118,7 +122,11 @@ impl BranchManager<'_> {
Ok(branch)
}
pub fn create_virtual_branch_from_branch(&self, upstream: &Refname) -> Result<BranchId> {
pub fn create_virtual_branch_from_branch(
&self,
upstream: &Refname,
perm: &mut WorktreeWritePermission,
) -> Result<BranchId> {
// only set upstream if it's not the default target
let upstream_branch = match upstream {
Refname::Other(_) | Refname::Virtual(_) => {
@ -137,7 +145,7 @@ impl BranchManager<'_> {
let _ = self
.project_repository
.project()
.snapshot_branch_creation(branch_name.clone());
.snapshot_branch_creation(branch_name.clone(), perm);
let vb_state = self.project_repository.project().virtual_branches();
@ -249,7 +257,7 @@ impl BranchManager<'_> {
vb_state.set_branch(branch.clone())?;
self.project_repository.add_branch_reference(&branch)?;
match self.apply_branch(branch.id) {
match self.apply_branch(branch.id, perm) {
Ok(_) => Ok(branch.id),
Err(err)
if err
@ -266,7 +274,11 @@ impl BranchManager<'_> {
/// Holding private methods associated to branch creation
impl BranchManager<'_> {
fn apply_branch(&self, branch_id: BranchId) -> Result<String> {
fn apply_branch(
&self,
branch_id: BranchId,
perm: &mut WorktreeWritePermission,
) -> Result<String> {
self.project_repository.assure_resolved()?;
self.project_repository.assure_unconflicted()?;
let repo = self.project_repository.repo();
@ -313,7 +325,7 @@ impl BranchManager<'_> {
.iter()
.filter(|branch| branch.id != branch_id)
{
self.convert_to_real_branch(branch.id, Default::default())?;
self.convert_to_real_branch(branch.id, Default::default(), perm)?;
}
// apply the branch

View File

@ -9,6 +9,7 @@ use git2::build::TreeUpdateBuilder;
use gitbutler_branch::{Branch, BranchExt, BranchId};
use gitbutler_commit::commit_headers::CommitHeadersV2;
use gitbutler_oplog::SnapshotExt;
use gitbutler_project::access::WorktreeWritePermission;
use gitbutler_reference::ReferenceName;
use gitbutler_reference::{normalize_branch_name, Refname};
use gitbutler_repo::{RepoActionsExt, RepositoryExt};
@ -21,6 +22,7 @@ impl BranchManager<'_> {
&self,
branch_id: BranchId,
name_conflict_resolution: NameConflictResolution,
perm: &mut WorktreeWritePermission,
) -> Result<ReferenceName> {
let vb_state = self.project_repository.project().virtual_branches();
@ -29,7 +31,7 @@ impl BranchManager<'_> {
// Convert the vbranch to a real branch
let real_branch = self.build_real_branch(&mut target_branch, name_conflict_resolution)?;
self.delete_branch(branch_id)?;
self.delete_branch(branch_id, perm)?;
// If we were conflicting, it means that it was the only branch applied. Since we've now unapplied it we can clear all conflicts
if conflicts::is_conflicting(self.project_repository, None)? {
@ -46,7 +48,11 @@ impl BranchManager<'_> {
real_branch.reference_name()
}
pub(crate) fn delete_branch(&self, branch_id: BranchId) -> Result<()> {
pub(crate) fn delete_branch(
&self,
branch_id: BranchId,
perm: &mut WorktreeWritePermission,
) -> Result<()> {
let vb_state = self.project_repository.project().virtual_branches();
let Some(branch) = vb_state.try_branch(branch_id)? else {
return Ok(());
@ -60,7 +66,7 @@ impl BranchManager<'_> {
_ = self
.project_repository
.project()
.snapshot_branch_deletion(branch.name.clone());
.snapshot_branch_deletion(branch.name.clone(), perm);
let repo = self.project_repository.repo();
@ -76,6 +82,7 @@ impl BranchManager<'_> {
self.project_repository,
&integration_commit.id(),
virtual_branches,
Some(perm),
)
.context("failed to get status by branch")?;

View File

@ -12,6 +12,7 @@ use gitbutler_branch::{
use gitbutler_command_context::ProjectRepository;
use gitbutler_commit::commit_ext::CommitExt;
use gitbutler_error::error::Marker;
use gitbutler_project::access::WorktreeWritePermission;
use gitbutler_repo::{LogUntil, RepoActionsExt, RepositoryExt};
use crate::branch_manager::BranchManagerExt;
@ -281,17 +282,17 @@ pub fn update_gitbutler_integration(
Ok(final_commit)
}
pub fn verify_branch(ctx: &ProjectRepository) -> Result<()> {
pub fn verify_branch(ctx: &ProjectRepository, perm: &mut WorktreeWritePermission) -> Result<()> {
verify_current_branch_name(ctx)
.and_then(verify_head_is_set)
.and_then(verify_head_is_clean)
.and_then(|()| verify_head_is_clean(ctx, perm))
.context(Marker::VerificationFailure)?;
Ok(())
}
fn verify_head_is_set(ctx: &ProjectRepository) -> Result<&ProjectRepository> {
fn verify_head_is_set(ctx: &ProjectRepository) -> Result<()> {
match ctx.repo().head().context("failed to get head")?.name() {
Some(refname) if *refname == GITBUTLER_INTEGRATION_REFERENCE.to_string() => Ok(ctx),
Some(refname) if *refname == GITBUTLER_INTEGRATION_REFERENCE.to_string() => Ok(()),
Some(head_name) => Err(invalid_head_err(head_name)),
None => Err(anyhow!(
"project in detached head state. Please checkout {} to continue",
@ -314,7 +315,8 @@ fn verify_current_branch_name(ctx: &ProjectRepository) -> Result<&ProjectReposit
}
}
fn verify_head_is_clean(ctx: &ProjectRepository) -> Result<&ProjectRepository> {
// TODO(ST): Probably there should not be an implicit vbranch creation here.
fn verify_head_is_clean(ctx: &ProjectRepository, perm: &mut WorktreeWritePermission) -> Result<()> {
let head_commit = ctx
.repo()
.head()
@ -340,7 +342,7 @@ fn verify_head_is_clean(ctx: &ProjectRepository) -> Result<&ProjectRepository> {
if extra_commits.is_empty() {
// no extra commits found, so we're good
return Ok(ctx);
return Ok(());
}
ctx.repo()
@ -353,12 +355,15 @@ fn verify_head_is_clean(ctx: &ProjectRepository) -> Result<&ProjectRepository> {
let branch_manager = ctx.branch_manager();
let mut new_branch = branch_manager
.create_virtual_branch(&BranchCreateRequest {
.create_virtual_branch(
&BranchCreateRequest {
name: extra_commits
.last()
.map(|commit| commit.message_bstr().to_string()),
..Default::default()
})
},
perm,
)
.context("failed to create virtual branch")?;
// rebasing the extra commits onto the new branch
@ -400,7 +405,7 @@ fn verify_head_is_clean(ctx: &ProjectRepository) -> Result<&ProjectRepository> {
head = rebased_commit.id();
}
Ok(ctx)
Ok(())
}
fn invalid_head_err(head_name: &str) -> anyhow::Error {

View File

@ -35,6 +35,7 @@ use crate::remote::{branch_to_remote_branch, RemoteBranch};
use crate::VirtualBranchesExt;
use gitbutler_error::error::Code;
use gitbutler_error::error::Marker;
use gitbutler_project::access::WorktreeWritePermission;
use gitbutler_repo::rebase::{cherry_rebase, cherry_rebase_group};
use gitbutler_time::time::now_since_unix_epoch_ms;
@ -200,6 +201,7 @@ pub enum NameConflictResolution {
pub fn unapply_ownership(
project_repository: &ProjectRepository,
ownership: &BranchOwnershipClaims,
perm: &mut WorktreeWritePermission,
) -> Result<()> {
project_repository.assure_resolved()?;
@ -211,8 +213,12 @@ pub fn unapply_ownership(
let integration_commit_id = get_workspace_head(&vb_state, project_repository)?;
let (applied_statuses, _) =
get_applied_status(project_repository, &integration_commit_id, virtual_branches)
let (applied_statuses, _) = get_applied_status(
project_repository,
&integration_commit_id,
virtual_branches,
Some(perm),
)
.context("failed to get status by branch")?;
let hunks_to_unapply = applied_statuses
@ -346,6 +352,7 @@ fn find_base_tree<'a>(
fn resolve_old_applied_state(
project_repository: &ProjectRepository,
vb_state: &VirtualBranchesHandle,
perm: &mut WorktreeWritePermission,
) -> Result<()> {
let branches = vb_state.list_all_branches()?;
@ -353,7 +360,7 @@ fn resolve_old_applied_state(
for mut branch in branches {
if branch.is_old_unapplied() {
branch_manager.convert_to_real_branch(branch.id, Default::default())?;
branch_manager.convert_to_real_branch(branch.id, Default::default(), perm)?;
} else {
branch.applied = branch.in_workspace;
vb_state.set_branch(branch)?;
@ -365,12 +372,15 @@ fn resolve_old_applied_state(
pub fn list_virtual_branches(
ctx: &ProjectRepository,
// TODO(ST): this should really only shared access, but there is some internals
// that conditionally write things.
perm: &mut WorktreeWritePermission,
) -> Result<(Vec<VirtualBranch>, Vec<diff::FileDiff>)> {
let mut branches: Vec<VirtualBranch> = Vec::new();
let vb_state = ctx.project().virtual_branches();
resolve_old_applied_state(ctx, &vb_state)?;
resolve_old_applied_state(ctx, &vb_state, perm)?;
let default_target = vb_state
.get_default_target()
@ -1045,6 +1055,7 @@ pub fn get_status_by_branch(
// TODO: Keep this optional or update lots of tests?
integration_commit.unwrap_or(&default_target.sha),
virtual_branches,
None,
)?;
Ok((applied_status, skipped_files))
@ -1128,6 +1139,7 @@ pub(crate) fn get_applied_status(
project_repository: &ProjectRepository,
integration_commit: &git2::Oid,
mut virtual_branches: Vec<Branch>,
perm: Option<&mut WorktreeWritePermission>,
) -> Result<(AppliedStatuses, Vec<diff::FileDiff>)> {
let base_file_diffs = diff::workdir(project_repository.repo(), &integration_commit.to_owned())
.context("failed to diff workdir")?;
@ -1146,9 +1158,13 @@ pub(crate) fn get_applied_status(
let branch_manager = project_repository.branch_manager();
if virtual_branches.is_empty() && !base_diffs.is_empty() {
if let Some(perm) = perm {
virtual_branches = vec![branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(&BranchCreateRequest::default(), perm)
.context("failed to create default branch")?];
} else {
bail!("Would have wanted to create a virtual branch but wasn't allowed to make changes")
}
}
let mut diffs_by_branch: HashMap<BranchId, BranchStatus> = virtual_branches
@ -2115,6 +2131,7 @@ pub(crate) fn amend(
branch_id: BranchId,
commit_oid: git2::Oid,
target_ownership: &BranchOwnershipClaims,
perm: &mut WorktreeWritePermission,
) -> Result<git2::Oid> {
project_repository.assure_resolved()?;
let vb_state = project_repository.project().virtual_branches();
@ -2129,11 +2146,14 @@ pub(crate) fn amend(
let default_target = vb_state.get_default_target()?;
let integration_commit_id =
crate::integration::get_workspace_head(&vb_state, project_repository)?;
let integration_commit_id = get_workspace_head(&vb_state, project_repository)?;
let (mut applied_statuses, _) =
get_applied_status(project_repository, &integration_commit_id, virtual_branches)?;
let (mut applied_statuses, _) = get_applied_status(
project_repository,
&integration_commit_id,
virtual_branches,
Some(perm),
)?;
let (ref mut target_branch, target_status) = applied_statuses
.iter_mut()
@ -2601,6 +2621,7 @@ pub(crate) fn move_commit(
project_repository: &ProjectRepository,
target_branch_id: BranchId,
commit_id: git2::Oid,
perm: &mut WorktreeWritePermission,
) -> Result<()> {
project_repository.assure_resolved()?;
let vb_state = project_repository.project().virtual_branches();
@ -2613,11 +2634,14 @@ pub(crate) fn move_commit(
bail!("branch {target_branch_id} is not among applied branches")
}
let integration_commit_id =
crate::integration::get_workspace_head(&vb_state, project_repository)?;
let integration_commit_id = get_workspace_head(&vb_state, project_repository)?;
let (mut applied_statuses, _) =
get_applied_status(project_repository, &integration_commit_id, applied_branches)?;
let (mut applied_statuses, _) = get_applied_status(
project_repository,
&integration_commit_id,
applied_branches,
Some(perm),
)?;
let (ref mut source_branch, source_status) = applied_statuses
.iter_mut()

View File

@ -43,9 +43,10 @@ fn commit_on_branch_then_change_file_then_get_status() -> Result<()> {
set_test_target(project_repository)?;
let mut guard = project.exclusive_worktree_access();
let branch1_id = project_repository
.branch_manager()
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission())
.expect("failed to create virtual branch")
.id;
@ -54,7 +55,7 @@ fn commit_on_branch_then_change_file_then_get_status() -> Result<()> {
"line0\nline1\nline2\nline3\nline4\n",
)?;
let (branches, _) = list_virtual_branches(project_repository)?;
let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?;
let branch = &branches[0];
assert_eq!(branch.files.len(), 1);
assert_eq!(branch.commits.len(), 0);
@ -63,7 +64,7 @@ fn commit_on_branch_then_change_file_then_get_status() -> Result<()> {
commit(project_repository, branch1_id, "test commit", None, false)?;
// status (no files)
let (branches, _) = list_virtual_branches(project_repository)?;
let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?;
let branch = &branches[0];
assert_eq!(branch.files.len(), 0);
assert_eq!(branch.commits.len(), 1);
@ -74,7 +75,7 @@ fn commit_on_branch_then_change_file_then_get_status() -> Result<()> {
)?;
// should have just the last change now, the other line is committed
let (branches, _) = list_virtual_branches(project_repository)?;
let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?;
let branch = &branches[0];
assert_eq!(branch.files.len(), 1);
assert_eq!(branch.commits.len(), 1);
@ -114,9 +115,10 @@ fn track_binary_files() -> Result<()> {
set_test_target(project_repository)?;
let mut guard = project.exclusive_worktree_access();
let branch1_id = project_repository
.branch_manager()
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission())
.expect("failed to create virtual branch")
.id;
@ -136,7 +138,7 @@ fn track_binary_files() -> Result<()> {
let mut file = std::fs::File::create(Path::new(&project.path).join("image.bin"))?;
file.write_all(&image_data)?;
let (branches, _) = list_virtual_branches(project_repository)?;
let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?;
let branch = &branches[0];
assert_eq!(branch.files.len(), 2);
let img_file = &branch
@ -156,7 +158,8 @@ fn track_binary_files() -> Result<()> {
commit(project_repository, branch1_id, "test commit", None, false)?;
// status (no files)
let (branches, _) = list_virtual_branches(project_repository).unwrap();
let (branches, _) =
list_virtual_branches(project_repository, guard.write_permission()).unwrap();
let commit_id = &branches[0].commits[0].id;
let commit_obj = project_repository
.repo()
@ -181,7 +184,8 @@ fn track_binary_files() -> Result<()> {
// commit
commit(project_repository, branch1_id, "test commit", None, false)?;
let (branches, _) = list_virtual_branches(project_repository).unwrap();
let (branches, _) =
list_virtual_branches(project_repository, guard.write_permission()).unwrap();
let commit_id = &branches[0].commits[0].id;
// get tree from commit_id
let commit_obj = project_repository
@ -212,7 +216,10 @@ fn create_branch_with_ownership() -> Result<()> {
let branch_manager = project_repository.branch_manager();
let branch0 = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(
&BranchCreateRequest::default(),
project.exclusive_worktree_access().write_permission(),
)
.expect("failed to create virtual branch");
get_status_by_branch(project_repository, None).expect("failed to get status");
@ -221,10 +228,13 @@ fn create_branch_with_ownership() -> Result<()> {
let branch0 = vb_state.get_branch_in_workspace(branch0.id).unwrap();
let branch1 = branch_manager
.create_virtual_branch(&BranchCreateRequest {
.create_virtual_branch(
&BranchCreateRequest {
ownership: Some(branch0.ownership),
..Default::default()
})
},
project.exclusive_worktree_access().write_permission(),
)
.expect("failed to create virtual branch");
let statuses = get_status_by_branch(project_repository, None)
@ -247,23 +257,34 @@ fn create_branch_with_ownership() -> Result<()> {
fn create_branch_in_the_middle() -> Result<()> {
let suite = Suite::default();
let Case {
project_repository, ..
project_repository,
project,
..
} = &suite.new_case();
set_test_target(project_repository)?;
let branch_manager = project_repository.branch_manager();
branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(
&BranchCreateRequest::default(),
project.exclusive_worktree_access().write_permission(),
)
.expect("failed to create virtual branch");
branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(
&BranchCreateRequest::default(),
project.exclusive_worktree_access().write_permission(),
)
.expect("failed to create virtual branch");
branch_manager
.create_virtual_branch(&BranchCreateRequest {
.create_virtual_branch(
&BranchCreateRequest {
order: Some(1),
..Default::default()
})
},
project.exclusive_worktree_access().write_permission(),
)
.expect("failed to create virtual branch");
let vb_state = VirtualBranchesHandle::new(project_repository.project().gb_dir());
@ -283,14 +304,19 @@ fn create_branch_in_the_middle() -> Result<()> {
fn create_branch_no_arguments() -> Result<()> {
let suite = Suite::default();
let Case {
project_repository, ..
project_repository,
project,
..
} = &suite.new_case();
set_test_target(project_repository)?;
let branch_manager = project_repository.branch_manager();
branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(
&BranchCreateRequest::default(),
project.exclusive_worktree_access().write_permission(),
)
.expect("failed to create virtual branch");
let vb_state = VirtualBranchesHandle::new(project_repository.project().gb_dir());
@ -321,11 +347,17 @@ fn hunk_expantion() -> Result<()> {
let branch_manager = project_repository.branch_manager();
let branch1_id = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(
&BranchCreateRequest::default(),
project.exclusive_worktree_access().write_permission(),
)
.expect("failed to create virtual branch")
.id;
let branch2_id = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(
&BranchCreateRequest::default(),
project.exclusive_worktree_access().write_permission(),
)
.expect("failed to create virtual branch")
.id;
@ -415,11 +447,17 @@ fn get_status_files_by_branch() -> Result<()> {
let branch_manager = project_repository.branch_manager();
let branch1_id = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(
&BranchCreateRequest::default(),
project.exclusive_worktree_access().write_permission(),
)
.expect("failed to create virtual branch")
.id;
let branch2_id = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(
&BranchCreateRequest::default(),
project.exclusive_worktree_access().write_permission(),
)
.expect("failed to create virtual branch")
.id;
@ -454,15 +492,24 @@ fn move_hunks_multiple_sources() -> Result<()> {
let branch_manager = project_repository.branch_manager();
let branch1_id = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(
&BranchCreateRequest::default(),
project.exclusive_worktree_access().write_permission(),
)
.expect("failed to create virtual branch")
.id;
let branch2_id = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(
&BranchCreateRequest::default(),
project.exclusive_worktree_access().write_permission(),
)
.expect("failed to create virtual branch")
.id;
let branch3_id = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(
&BranchCreateRequest::default(),
project.exclusive_worktree_access().write_permission(),
)
.expect("failed to create virtual branch")
.id;
@ -557,12 +604,18 @@ fn move_hunks_partial_explicitly() -> Result<()> {
let branch_manager = project_repository.branch_manager();
let branch1_id = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(
&BranchCreateRequest::default(),
project.exclusive_worktree_access().write_permission(),
)
.expect("failed to create virtual branch")
.id;
let branch2_id = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(
&BranchCreateRequest::default(),
project.exclusive_worktree_access().write_permission(),
)
.expect("failed to create virtual branch")
.id;
@ -642,7 +695,10 @@ fn add_new_hunk_to_the_end() -> Result<()> {
let branch_manager = project_repository.branch_manager();
branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(
&BranchCreateRequest::default(),
project.exclusive_worktree_access().write_permission(),
)
.expect("failed to create virtual branch");
let statuses = get_status_by_branch(project_repository, None)
@ -813,15 +869,16 @@ fn merge_vbranch_upstream_clean_rebase() -> Result<()> {
let remote_branch: RemoteRefname = "refs/remotes/origin/master".parse().unwrap();
let branch_manager = project_repository.branch_manager();
let mut guard = project.exclusive_worktree_access();
let mut branch = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission())
.expect("failed to create virtual branch");
branch.upstream = Some(remote_branch.clone());
branch.head = last_push;
vb_state.set_branch(branch.clone())?;
// create the branch
let (branches, _) = list_virtual_branches(project_repository)?;
let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?;
let branch1 = &branches[0];
assert_eq!(branch1.files.len(), 1);
assert_eq!(branch1.commits.len(), 1);
@ -829,7 +886,7 @@ fn merge_vbranch_upstream_clean_rebase() -> Result<()> {
integrate_upstream_commits(project_repository, branch1.id)?;
let (branches, _) = list_virtual_branches(project_repository)?;
let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?;
let branch1 = &branches[0];
let contents = std::fs::read(Path::new(&project.path).join(file_path))?;
@ -912,8 +969,9 @@ async fn merge_vbranch_upstream_conflict() -> Result<()> {
let remote_branch: RemoteRefname = "refs/remotes/origin/master".parse().unwrap();
let branch_manager = project_repository.branch_manager();
let mut guard = project.exclusive_worktree_access();
let mut branch = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission())
.expect("failed to create virtual branch");
branch.upstream = Some(remote_branch.clone());
branch.head = last_push;
@ -930,7 +988,7 @@ async fn merge_vbranch_upstream_conflict() -> Result<()> {
.unwrap();
// create the branch
let (branches, _) = list_virtual_branches(project_repository)?;
let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?;
let branch1 = &branches[0];
assert_eq!(branch1.files.len(), 1);
@ -939,7 +997,7 @@ async fn merge_vbranch_upstream_conflict() -> Result<()> {
integrate_upstream_commits(project_repository, branch1.id)?;
let (branches, _) = list_virtual_branches(project_repository)?;
let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?;
let branch1 = &branches[0];
let contents = std::fs::read(Path::new(&project.path).join(file_path))?;
@ -959,7 +1017,7 @@ async fn merge_vbranch_upstream_conflict() -> Result<()> {
)?;
// make gb see the conflict resolution
let (branches, _) = list_virtual_branches(project_repository)?;
let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?;
assert!(branches[0].conflicted);
// commit the merge resolution
@ -971,7 +1029,7 @@ async fn merge_vbranch_upstream_conflict() -> Result<()> {
false,
)?;
let (branches, _) = list_virtual_branches(project_repository)?;
let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?;
let branch1 = &branches[0];
assert!(!branch1.conflicted);
assert_eq!(branch1.files.len(), 0);
@ -1005,11 +1063,12 @@ fn unapply_ownership_partial() -> Result<()> {
)?;
let branch_manager = project_repository.branch_manager();
let mut guard = project.exclusive_worktree_access();
branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission())
.expect("failed to create virtual branch");
let (branches, _) = list_virtual_branches(project_repository)?;
let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?;
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].files.len(), 1);
assert_eq!(branches[0].ownership.claims.len(), 1);
@ -1020,9 +1079,14 @@ fn unapply_ownership_partial() -> Result<()> {
"line1\nline2\nline3\nline4\nbranch1\n"
);
unapply_ownership(project_repository, &"test.txt:2-6".parse().unwrap()).unwrap();
unapply_ownership(
project_repository,
&"test.txt:2-6".parse().unwrap(),
guard.write_permission(),
)
.unwrap();
let (branches, _) = list_virtual_branches(project_repository)?;
let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?;
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].files.len(), 0);
assert_eq!(branches[0].ownership.claims.len(), 0);
@ -1061,12 +1125,13 @@ fn unapply_branch() -> Result<()> {
std::fs::write(Path::new(&project.path).join(file_path2), "line5\nline6\n")?;
let branch_manager = project_repository.branch_manager();
let mut guard = project.exclusive_worktree_access();
let branch1_id = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission())
.expect("failed to create virtual branch")
.id;
let branch2_id = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission())
.expect("failed to create virtual branch")
.id;
@ -1087,25 +1152,31 @@ fn unapply_branch() -> Result<()> {
let contents = std::fs::read(Path::new(&project.path).join(file_path2))?;
assert_eq!("line5\nline6\n", String::from_utf8(contents)?);
let (branches, _) = list_virtual_branches(project_repository)?;
let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?;
let branch = &branches.iter().find(|b| b.id == branch1_id).unwrap();
assert_eq!(branch.files.len(), 1);
assert!(branch.active);
let branch_manager = project_repository.branch_manager();
let real_branch = branch_manager.convert_to_real_branch(branch1_id, Default::default())?;
let real_branch = branch_manager.convert_to_real_branch(
branch1_id,
Default::default(),
guard.write_permission(),
)?;
let contents = std::fs::read(Path::new(&project.path).join(file_path))?;
assert_eq!("line1\nline2\nline3\nline4\n", String::from_utf8(contents)?);
let contents = std::fs::read(Path::new(&project.path).join(file_path2))?;
assert_eq!("line5\nline6\n", String::from_utf8(contents)?);
let (branches, _) = list_virtual_branches(project_repository)?;
let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?;
assert!(!branches.iter().any(|b| b.id == branch1_id));
let branch_manager = project_repository.branch_manager();
let branch1_id =
branch_manager.create_virtual_branch_from_branch(&Refname::from_str(&real_branch)?)?;
let branch1_id = branch_manager.create_virtual_branch_from_branch(
&Refname::from_str(&real_branch)?,
guard.write_permission(),
)?;
let contents = std::fs::read(Path::new(&project.path).join(file_path))?;
assert_eq!(
"line1\nline2\nline3\nline4\nbranch1\n",
@ -1114,7 +1185,7 @@ fn unapply_branch() -> Result<()> {
let contents = std::fs::read(Path::new(&project.path).join(file_path2))?;
assert_eq!("line5\nline6\n", String::from_utf8(contents)?);
let (branches, _) = list_virtual_branches(project_repository)?;
let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?;
let branch = &branches.iter().find(|b| b.id == branch1_id).unwrap();
// TODO: expect there to be 0 branches
assert_eq!(branch.files.len(), 0);
@ -1147,12 +1218,13 @@ fn apply_unapply_added_deleted_files() -> Result<()> {
std::fs::write(Path::new(&project.path).join(file_path3), "file3\n")?;
let branch_manager = project_repository.branch_manager();
let mut guard = project.exclusive_worktree_access();
let branch2_id = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission())
.expect("failed to create virtual branch")
.id;
let branch3_id = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission())
.expect("failed to create virtual branch")
.id;
@ -1173,28 +1245,42 @@ fn apply_unapply_added_deleted_files() -> Result<()> {
},
)?;
list_virtual_branches(project_repository).unwrap();
list_virtual_branches(project_repository, guard.write_permission()).unwrap();
let branch_manager = project_repository.branch_manager();
let real_branch_2 = branch_manager.convert_to_real_branch(branch2_id, Default::default())?;
let real_branch_2 = branch_manager.convert_to_real_branch(
branch2_id,
Default::default(),
guard.write_permission(),
)?;
// check that file2 is back
let contents = std::fs::read(Path::new(&project.path).join(file_path2))?;
assert_eq!("file2\n", String::from_utf8(contents)?);
let real_branch_3 = branch_manager.convert_to_real_branch(branch3_id, Default::default())?;
let real_branch_3 = branch_manager.convert_to_real_branch(
branch3_id,
Default::default(),
guard.write_permission(),
)?;
// check that file3 is gone
assert!(!Path::new(&project.path).join(file_path3).exists());
branch_manager
.create_virtual_branch_from_branch(&Refname::from_str(&real_branch_2).unwrap())
.create_virtual_branch_from_branch(
&Refname::from_str(&real_branch_2).unwrap(),
guard.write_permission(),
)
.unwrap();
// check that file2 is gone
assert!(!Path::new(&project.path).join(file_path2).exists());
branch_manager
.create_virtual_branch_from_branch(&Refname::from_str(&real_branch_3).unwrap())
.create_virtual_branch_from_branch(
&Refname::from_str(&real_branch_3).unwrap(),
guard.write_permission(),
)
.unwrap();
// check that file3 is back
@ -1232,12 +1318,13 @@ fn detect_mergeable_branch() -> Result<()> {
std::fs::write(Path::new(&project.path).join(file_path4), "line5\nline6\n")?;
let branch_manager = project_repository.branch_manager();
let mut guard = project.exclusive_worktree_access();
let branch1_id = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission())
.expect("failed to create virtual branch")
.id;
let branch2_id = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission())
.expect("failed to create virtual branch")
.id;
@ -1253,8 +1340,16 @@ fn detect_mergeable_branch() -> Result<()> {
// unapply both branches and create some conflicting ones
let branch_manager = project_repository.branch_manager();
branch_manager.convert_to_real_branch(branch1_id, Default::default())?;
branch_manager.convert_to_real_branch(branch2_id, Default::default())?;
branch_manager.convert_to_real_branch(
branch1_id,
Default::default(),
guard.write_permission(),
)?;
branch_manager.convert_to_real_branch(
branch2_id,
Default::default(),
guard.write_permission(),
)?;
project_repository.repo().set_head("refs/heads/master")?;
project_repository
@ -1303,10 +1398,10 @@ fn detect_mergeable_branch() -> Result<()> {
// create branches that conflict with our earlier branches
let branch_manager = project_repository.branch_manager();
branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission())
.expect("failed to create virtual branch");
let branch4_id = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission())
.expect("failed to create virtual branch")
.id;
@ -1405,16 +1500,17 @@ fn upstream_integrated_vbranch() -> Result<()> {
// create vbranches, one integrated, one not
let branch_manager = project_repository.branch_manager();
let mut guard = project.exclusive_worktree_access();
let branch1_id = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission())
.expect("failed to create virtual branch")
.id;
let branch2_id = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission())
.expect("failed to create virtual branch")
.id;
let branch3_id = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission())
.expect("failed to create virtual branch")
.id;
@ -1474,7 +1570,7 @@ fn upstream_integrated_vbranch() -> Result<()> {
false,
)?;
let (branches, _) = list_virtual_branches(project_repository)?;
let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?;
let branch1 = &branches.iter().find(|b| b.id == branch1_id).unwrap();
assert!(branch1.commits.iter().any(|c| c.is_integrated));
@ -1509,8 +1605,9 @@ fn commit_same_hunk_twice() -> Result<()> {
set_test_target(project_repository)?;
let branch_manager = project_repository.branch_manager();
let mut guard = project.exclusive_worktree_access();
let branch1_id = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission())
.expect("failed to create virtual branch")
.id;
@ -1519,7 +1616,7 @@ fn commit_same_hunk_twice() -> Result<()> {
"line1\npatch1\nline2\nline3\nline4\nline5\nmiddle\nmiddle\nmiddle\nmiddle\nline6\nline7\nline8\nline9\nline10\nmiddle\nmiddle\nmiddle\nline11\nline12\n",
)?;
let (branches, _) = list_virtual_branches(project_repository)?;
let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?;
let branch = &branches.iter().find(|b| b.id == branch1_id).unwrap();
assert_eq!(branch.files.len(), 1);
@ -1535,7 +1632,7 @@ fn commit_same_hunk_twice() -> Result<()> {
false,
)?;
let (branches, _) = list_virtual_branches(project_repository)?;
let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?;
let branch = &branches.iter().find(|b| b.id == branch1_id).unwrap();
assert_eq!(branch.files.len(), 0, "no files expected");
@ -1555,7 +1652,7 @@ fn commit_same_hunk_twice() -> Result<()> {
"line1\nPATCH1\nline2\nline3\nline4\nline5\nmiddle\nmiddle\nmiddle\nmiddle\nline6\nline7\nline8\nline9\nline10\nmiddle\nmiddle\nmiddle\nline11\nline12\n",
)?;
let (branches, _) = list_virtual_branches(project_repository)?;
let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?;
let branch = &branches.iter().find(|b| b.id == branch1_id).unwrap();
assert_eq!(branch.files.len(), 1, "one file should be changed");
@ -1569,7 +1666,7 @@ fn commit_same_hunk_twice() -> Result<()> {
false,
)?;
let (branches, _) = list_virtual_branches(project_repository)?;
let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?;
let branch = &branches.iter().find(|b| b.id == branch1_id).unwrap();
assert_eq!(
@ -1602,8 +1699,9 @@ fn commit_same_file_twice() -> Result<()> {
set_test_target(project_repository)?;
let branch_manager = project_repository.branch_manager();
let mut guard = project.exclusive_worktree_access();
let branch1_id = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission())
.expect("failed to create virtual branch")
.id;
@ -1612,7 +1710,7 @@ fn commit_same_file_twice() -> Result<()> {
"line1\npatch1\nline2\nline3\nline4\nline5\nmiddle\nmiddle\nmiddle\nmiddle\nline6\nline7\nline8\nline9\nline10\nmiddle\nmiddle\nmiddle\nline11\nline12\n",
)?;
let (branches, _) = list_virtual_branches(project_repository)?;
let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?;
let branch = &branches.iter().find(|b| b.id == branch1_id).unwrap();
assert_eq!(branch.files.len(), 1);
@ -1628,7 +1726,7 @@ fn commit_same_file_twice() -> Result<()> {
false,
)?;
let (branches, _) = list_virtual_branches(project_repository)?;
let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?;
let branch = &branches.iter().find(|b| b.id == branch1_id).unwrap();
assert_eq!(branch.files.len(), 0, "no files expected");
@ -1648,7 +1746,7 @@ fn commit_same_file_twice() -> Result<()> {
"line1\npatch1\nline2\nline3\nline4\nline5\nmiddle\nmiddle\nmiddle\nmiddle\nline6\nline7\nline8\nline9\nline10\nmiddle\nmiddle\nmiddle\npatch2\nline11\nline12\n",
)?;
let (branches, _) = list_virtual_branches(project_repository)?;
let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?;
let branch = &branches.iter().find(|b| b.id == branch1_id).unwrap();
assert_eq!(branch.files.len(), 1, "one file should be changed");
@ -1662,7 +1760,7 @@ fn commit_same_file_twice() -> Result<()> {
false,
)?;
let (branches, _) = list_virtual_branches(project_repository)?;
let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?;
let branch = &branches.iter().find(|b| b.id == branch1_id).unwrap();
assert_eq!(
@ -1695,8 +1793,9 @@ fn commit_partial_by_hunk() -> Result<()> {
set_test_target(project_repository)?;
let branch_manager = project_repository.branch_manager();
let mut guard = project.exclusive_worktree_access();
let branch1_id = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission())
.expect("failed to create virtual branch")
.id;
@ -1705,7 +1804,7 @@ fn commit_partial_by_hunk() -> Result<()> {
"line1\npatch1\nline2\nline3\nline4\nline5\nmiddle\nmiddle\nmiddle\nmiddle\nline6\nline7\nline8\nline9\nline10\nmiddle\nmiddle\nmiddle\npatch2\nline11\nline12\n",
)?;
let (branches, _) = list_virtual_branches(project_repository)?;
let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?;
let branch = &branches.iter().find(|b| b.id == branch1_id).unwrap();
assert_eq!(branch.files.len(), 1);
@ -1721,7 +1820,7 @@ fn commit_partial_by_hunk() -> Result<()> {
false,
)?;
let (branches, _) = list_virtual_branches(project_repository)?;
let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?;
let branch = &branches.iter().find(|b| b.id == branch1_id).unwrap();
assert_eq!(branch.files.len(), 1);
@ -1738,7 +1837,7 @@ fn commit_partial_by_hunk() -> Result<()> {
false,
)?;
let (branches, _) = list_virtual_branches(project_repository)?;
let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?;
let branch = &branches.iter().find(|b| b.id == branch1_id).unwrap();
assert_eq!(branch.files.len(), 0);
@ -1775,8 +1874,9 @@ fn commit_partial_by_file() -> Result<()> {
std::fs::write(Path::new(&project.path).join(file_path3), "file3\n")?;
let branch_manager = project_repository.branch_manager();
let mut guard = project.exclusive_worktree_access();
let branch1_id = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission())
.expect("failed to create virtual branch")
.id;
@ -1789,7 +1889,7 @@ fn commit_partial_by_file() -> Result<()> {
false,
)?;
let (branches, _) = list_virtual_branches(project_repository)?;
let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?;
let branch1 = &branches.iter().find(|b| b.id == branch1_id).unwrap();
// branch one test.txt has just the 1st and 3rd hunks applied
@ -1835,8 +1935,9 @@ fn commit_add_and_delete_files() -> Result<()> {
std::fs::write(Path::new(&project.path).join(file_path3), "file3\n")?;
let branch_manager = project_repository.branch_manager();
let mut guard = project.exclusive_worktree_access();
let branch1_id = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission())
.expect("failed to create virtual branch")
.id;
@ -1849,7 +1950,7 @@ fn commit_add_and_delete_files() -> Result<()> {
false,
)?;
let (branches, _) = list_virtual_branches(project_repository)?;
let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?;
let branch1 = &branches.iter().find(|b| b.id == branch1_id).unwrap();
// branch one test.txt has just the 1st and 3rd hunks applied
@ -1901,8 +2002,9 @@ fn commit_executable_and_symlinks() -> Result<()> {
std::fs::set_permissions(&exec, new_permissions)?;
let branch_manager = project_repository.branch_manager();
let mut guard = project.exclusive_worktree_access();
let branch1_id = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission())
.expect("failed to create virtual branch")
.id;
@ -1915,7 +2017,7 @@ fn commit_executable_and_symlinks() -> Result<()> {
false,
)?;
let (branches, _) = list_virtual_branches(project_repository)?;
let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?;
let branch1 = &branches.iter().find(|b| b.id == branch1_id).unwrap();
let commit = &branch1.commits[0].id;
@ -2001,7 +2103,8 @@ fn verify_branch_commits_to_integration() -> Result<()> {
set_test_target(project_repository)?;
verify_branch(project_repository).unwrap();
let mut guard = project.exclusive_worktree_access();
verify_branch(project_repository, guard.write_permission()).unwrap();
// write two commits
let file_path2 = Path::new("test2.txt");
@ -2011,10 +2114,11 @@ fn verify_branch_commits_to_integration() -> Result<()> {
commit_all(project_repository.repo());
// verify puts commits onto the virtual branch
verify_branch(project_repository).unwrap();
verify_branch(project_repository, guard.write_permission()).unwrap();
// one virtual branch with two commits was created
let (virtual_branches, _) = list_virtual_branches(project_repository)?;
let (virtual_branches, _) =
list_virtual_branches(project_repository, guard.write_permission())?;
assert_eq!(virtual_branches.len(), 1);
let branch = &virtual_branches.first().unwrap();
@ -2028,16 +2132,19 @@ fn verify_branch_commits_to_integration() -> Result<()> {
fn verify_branch_not_integration() -> Result<()> {
let suite = Suite::default();
let Case {
project_repository, ..
project_repository,
project,
..
} = &suite.new_case();
set_test_target(project_repository)?;
verify_branch(project_repository).unwrap();
let mut guard = project.exclusive_worktree_access();
verify_branch(project_repository, guard.write_permission()).unwrap();
project_repository.repo().set_head("refs/heads/master")?;
let verify_result = verify_branch(project_repository);
let verify_result = verify_branch(project_repository, guard.write_permission());
assert!(verify_result.is_err());
assert_eq!(
format!("{:#}", verify_result.unwrap_err()),
@ -2063,7 +2170,10 @@ fn pre_commit_hook_rejection() -> Result<()> {
let branch_manager = project_repository.branch_manager();
let branch1_id = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(
&BranchCreateRequest::default(),
project.exclusive_worktree_access().write_permission(),
)
.expect("failed to create virtual branch")
.id;
@ -2103,7 +2213,10 @@ fn post_commit_hook() -> Result<()> {
let branch_manager = project_repository.branch_manager();
let branch1_id = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(
&BranchCreateRequest::default(),
project.exclusive_worktree_access().write_permission(),
)
.expect("failed to create virtual branch")
.id;
@ -2154,7 +2267,10 @@ fn commit_msg_hook_rejection() -> Result<()> {
let branch_manager = project_repository.branch_manager();
let branch1_id = branch_manager
.create_virtual_branch(&BranchCreateRequest::default())
.create_virtual_branch(
&BranchCreateRequest::default(),
project.exclusive_worktree_access().write_permission(),
)
.expect("failed to create virtual branch")
.id;

View File

@ -47,7 +47,7 @@ pub trait OplogExt {
/// Prepares a snapshot of the current state of the working directory as well as GitButler data.
/// Returns a tree hash of the snapshot. The snapshot is not discoverable until it is committed with [`commit_snapshot`](Self::commit_snapshot())
/// If there are files that are untracked and larger than `SNAPSHOT_FILE_LIMIT_BYTES`, they are excluded from snapshot creation and restoring.
fn prepare_snapshot(&self) -> Result<git2::Oid>;
fn prepare_snapshot(&self, perm: &WorktreeReadPermission) -> Result<git2::Oid>;
/// Commits the snapshot tree that is created with the [`prepare_snapshot`](Self::prepare_snapshot) method,
/// which yielded the `snapshot_tree_id` for the entire snapshot state.
@ -62,6 +62,7 @@ pub trait OplogExt {
&self,
snapshot_tree_id: git2::Oid,
details: SnapshotDetails,
perm: &mut WorktreeWritePermission,
) -> Result<Option<git2::Oid>>;
/// Creates a snapshot of the current state of the working directory as well as GitButler data.
@ -72,7 +73,11 @@ pub trait OplogExt {
/// commit and the current one (after comparing trees).
///
/// Note that errors in snapshot creation is typically ignored, so we want to learn about them.
fn create_snapshot(&self, details: SnapshotDetails) -> Result<Option<git2::Oid>>;
fn create_snapshot(
&self,
details: SnapshotDetails,
perm: &mut WorktreeWritePermission,
) -> Result<Option<git2::Oid>>;
/// Lists the snapshots that have been created for the given repository, up to the given limit,
/// and with the most recent snapshot first, and at the end of the vec.
@ -129,25 +134,27 @@ pub trait OplogExt {
}
impl OplogExt for Project {
fn prepare_snapshot(&self) -> Result<git2::Oid> {
let guard = self.shared_worktree_access();
prepare_snapshot(self, guard.read_permission())
fn prepare_snapshot(&self, perm: &WorktreeReadPermission) -> Result<git2::Oid> {
prepare_snapshot(self, perm)
}
fn commit_snapshot(
&self,
snapshot_tree_id: git2::Oid,
details: SnapshotDetails,
perm: &mut WorktreeWritePermission,
) -> Result<Option<git2::Oid>> {
let mut guard = self.exclusive_worktree_access();
commit_snapshot(self, snapshot_tree_id, details, guard.write_permission())
commit_snapshot(self, snapshot_tree_id, details, perm)
}
#[instrument(skip(details), err(Debug))]
fn create_snapshot(&self, details: SnapshotDetails) -> Result<Option<git2::Oid>> {
let mut guard = self.exclusive_worktree_access();
let tree_id = prepare_snapshot(self, guard.read_permission())?;
commit_snapshot(self, tree_id, details, guard.write_permission())
#[instrument(skip(details, perm), err(Debug))]
fn create_snapshot(
&self,
details: SnapshotDetails,
perm: &mut WorktreeWritePermission,
) -> Result<Option<git2::Oid>> {
let tree_id = prepare_snapshot(self, perm.read_permission())?;
commit_snapshot(self, tree_id, details, perm)
}
fn list_snapshots(

View File

@ -1,13 +1,13 @@
use anyhow::Result;
use gitbutler_branch::{Branch, BranchUpdateRequest};
use gitbutler_project::Project;
use gitbutler_reference::ReferenceName;
use std::vec;
use crate::{
entry::{OperationKind, SnapshotDetails},
oplog::OplogExt,
};
use anyhow::Result;
use gitbutler_branch::{Branch, BranchUpdateRequest};
use gitbutler_project::access::WorktreeWritePermission;
use gitbutler_project::Project;
use gitbutler_reference::ReferenceName;
use std::vec;
use super::entry::Trailer;
@ -16,6 +16,7 @@ pub trait SnapshotExt {
&self,
snapshot_tree: git2::Oid,
result: Result<&ReferenceName, &anyhow::Error>,
perm: &mut WorktreeWritePermission,
) -> anyhow::Result<()>;
fn snapshot_commit_undo(
@ -23,6 +24,7 @@ pub trait SnapshotExt {
snapshot_tree: git2::Oid,
result: Result<&(), &anyhow::Error>,
commit_sha: git2::Oid,
perm: &mut WorktreeWritePermission,
) -> anyhow::Result<()>;
fn snapshot_commit_creation(
@ -31,16 +33,26 @@ pub trait SnapshotExt {
error: Option<&anyhow::Error>,
commit_message: String,
sha: Option<git2::Oid>,
perm: &mut WorktreeWritePermission,
) -> anyhow::Result<()>;
fn snapshot_branch_creation(&self, branch_name: String) -> anyhow::Result<()>;
fn snapshot_branch_deletion(&self, branch_name: String) -> anyhow::Result<()>;
fn snapshot_branch_creation(
&self,
branch_name: String,
perm: &mut WorktreeWritePermission,
) -> anyhow::Result<()>;
fn snapshot_branch_deletion(
&self,
branch_name: String,
perm: &mut WorktreeWritePermission,
) -> anyhow::Result<()>;
fn snapshot_branch_update(
&self,
snapshot_tree: git2::Oid,
old_branch: &Branch,
update: &BranchUpdateRequest,
error: Option<&anyhow::Error>,
perm: &mut WorktreeWritePermission,
) -> anyhow::Result<()>;
}
@ -50,11 +62,12 @@ impl SnapshotExt for Project {
&self,
snapshot_tree: git2::Oid,
result: Result<&ReferenceName, &anyhow::Error>,
perm: &mut WorktreeWritePermission,
) -> anyhow::Result<()> {
let result = result.map(|s| Some(s.to_string()));
let details = SnapshotDetails::new(OperationKind::UnapplyBranch)
.with_trailers(result_trailer(result, "name".to_string()));
self.commit_snapshot(snapshot_tree, details)?;
self.commit_snapshot(snapshot_tree, details, perm)?;
Ok(())
}
fn snapshot_commit_undo(
@ -62,11 +75,12 @@ impl SnapshotExt for Project {
snapshot_tree: git2::Oid,
result: Result<&(), &anyhow::Error>,
commit_sha: git2::Oid,
perm: &mut WorktreeWritePermission,
) -> anyhow::Result<()> {
let result = result.map(|_| Some(commit_sha.to_string()));
let details = SnapshotDetails::new(OperationKind::UndoCommit)
.with_trailers(result_trailer(result, "sha".to_string()));
self.commit_snapshot(snapshot_tree, details)?;
self.commit_snapshot(snapshot_tree, details, perm)?;
Ok(())
}
fn snapshot_commit_creation(
@ -75,6 +89,7 @@ impl SnapshotExt for Project {
error: Option<&anyhow::Error>,
commit_message: String,
sha: Option<git2::Oid>,
perm: &mut WorktreeWritePermission,
) -> anyhow::Result<()> {
let details = SnapshotDetails::new(OperationKind::CreateCommit).with_trailers(
[
@ -92,26 +107,34 @@ impl SnapshotExt for Project {
]
.concat(),
);
self.commit_snapshot(snapshot_tree, details)?;
self.commit_snapshot(snapshot_tree, details, perm)?;
Ok(())
}
fn snapshot_branch_creation(&self, branch_name: String) -> anyhow::Result<()> {
fn snapshot_branch_creation(
&self,
branch_name: String,
perm: &mut WorktreeWritePermission,
) -> anyhow::Result<()> {
let details =
SnapshotDetails::new(OperationKind::CreateBranch).with_trailers(vec![Trailer {
key: "name".to_string(),
value: branch_name,
}]);
self.create_snapshot(details)?;
self.create_snapshot(details, perm)?;
Ok(())
}
fn snapshot_branch_deletion(&self, branch_name: String) -> anyhow::Result<()> {
fn snapshot_branch_deletion(
&self,
branch_name: String,
perm: &mut WorktreeWritePermission,
) -> anyhow::Result<()> {
let details =
SnapshotDetails::new(OperationKind::DeleteBranch).with_trailers(vec![Trailer {
key: "name".to_string(),
value: branch_name.to_string(),
}]);
self.create_snapshot(details)?;
self.create_snapshot(details, perm)?;
Ok(())
}
fn snapshot_branch_update(
@ -120,6 +143,7 @@ impl SnapshotExt for Project {
old_branch: &Branch,
update: &BranchUpdateRequest,
error: Option<&anyhow::Error>,
perm: &mut WorktreeWritePermission,
) -> anyhow::Result<()> {
let details = if update.ownership.is_some() {
SnapshotDetails::new(OperationKind::MoveHunk).with_trailers(
@ -212,7 +236,7 @@ impl SnapshotExt for Project {
} else {
SnapshotDetails::new(OperationKind::GenericBranchUpdate)
};
self.commit_snapshot(snapshot_tree, details)?;
self.commit_snapshot(snapshot_tree, details, perm)?;
Ok(())
}
}

View File

@ -44,16 +44,6 @@ impl Project {
}
}
/// Return a guard for shared (read) worktree access, and block while waiting for writers to disappear.
/// There can be multiple readers, but only a single writer. Waiting writers will be handled with priority,
/// thus block readers to prevent writer starvation.
/// The guard can be upgraded to allow for writes, which is useful if a mutation is prepared by various reads
/// first, followed by conclusive writes.
pub fn shared_upgradable_worktree_access(&self) -> UpgradableWorkspaceReadGuard {
let mut map = WORKTREE_LOCKS.lock();
UpgradableWorkspaceReadGuard(map.entry(self.id).or_default().upgradable_read_arc())
}
/// Return a guard for shared (read) worktree access, and block while waiting for writers to disappear.
/// There can be multiple readers, but only a single writer. Waiting writers will be handled with priority,
/// thus block readers to prevent writer starvation.
@ -82,26 +72,6 @@ impl WriteWorkspaceGuard {
}
}
pub struct UpgradableWorkspaceReadGuard(parking_lot::ArcRwLockUpgradableReadGuard<RawRwLock, ()>);
impl UpgradableWorkspaceReadGuard {
/// Wait until a write-lock for exclusive access can be acquired, and return a handle to it.
/// It must be kept alive until the write operation completes.
pub fn upgrade_to_exclusive_worktree_access(self) -> WriteWorkspaceGuard {
WriteWorkspaceGuard {
_inner: parking_lot::ArcRwLockUpgradableReadGuard::upgrade(self.0),
perm: WorktreeWritePermission(()),
}
}
/// Signal that a read-permission is available - useful as API-marker to assure these
/// can only be called when the respective protection/permission is present.
pub fn read_permission(&self) -> &WorktreeReadPermission {
static READ: WorktreeReadPermission = WorktreeReadPermission(());
&READ
}
}
pub struct WorkspaceReadGuard(#[allow(dead_code)] parking_lot::ArcRwLockReadGuard<RawRwLock, ()>);
impl WorkspaceReadGuard {

View File

@ -122,13 +122,8 @@ impl Handler {
paths: Vec<PathBuf>,
project_id: ProjectId,
) -> Result<()> {
// Create a snapshot every time there are more than a configurable number of new lines of code (default 20)
let handle_snapshots = tokio::task::spawn_blocking({
let this = self.clone();
move || this.maybe_create_snapshot(project_id)
});
self.maybe_create_snapshot(project_id).ok();
self.calculate_virtual_branches(project_id).await?;
let _ = handle_snapshots.await;
Ok(())
}
@ -141,7 +136,11 @@ impl Handler {
.should_auto_snapshot(std::time::Duration::from_secs(300))
.unwrap_or_default()
{
project.create_snapshot(SnapshotDetails::new(OperationKind::FileChanges))?;
let mut guard = project.exclusive_worktree_access();
project.create_snapshot(
SnapshotDetails::new(OperationKind::FileChanges),
guard.write_permission(),
)?;
}
Ok(())
}