From 6fb6201ab99e1f52ef34a6d3a5395a0500ba5627 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 12 Jul 2024 15:34:45 +0200 Subject: [PATCH 01/13] assure docs can be built without warnings by default. It's a good way to review import paths and the public API. --- crates/gitbutler-repo/src/askpass.rs | 2 +- crates/gitbutler-tauri/src/error.rs | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/crates/gitbutler-repo/src/askpass.rs b/crates/gitbutler-repo/src/askpass.rs index 270059224..2e705d2a5 100644 --- a/crates/gitbutler-repo/src/askpass.rs +++ b/crates/gitbutler-repo/src/askpass.rs @@ -12,7 +12,7 @@ static mut GLOBAL_ASKPASS_BROKER: Option = None; /// /// # Safety /// This function **must** be called **at least once**, from only one thread at a time, -/// before any other function from this module is called. **Calls to [`get`] before [`init`] will panic.** +/// before any other function from this module is called. **Calls to [`get_broker`] before [`init`] will panic.** /// /// This function is **NOT** thread safe. pub unsafe fn init(submit_prompt: impl Fn(PromptEvent) + Send + Sync + 'static) { diff --git a/crates/gitbutler-tauri/src/error.rs b/crates/gitbutler-tauri/src/error.rs index da1620150..c05a4aec3 100644 --- a/crates/gitbutler-tauri/src/error.rs +++ b/crates/gitbutler-tauri/src/error.rs @@ -2,19 +2,16 @@ //! //! ## How to use this //! -//! Just make sure this [`Error`] type is used for each provided `tauri` command. The rest happens automatically -//! such that: +//! Just make sure this `Error` type is used for each provided `tauri` command. The rest happens automatically +//! such that [context](gitbutler_error::error::Context) is handled correctly. //! -//! * The frontend shows the root error as string by default… -//! * …or it shows the provided [`Context`](gitbutler_core::error::Context) as controlled by the `core` crate. -//! -//! ### Interfacing with `tauri` using [`Error`] +//! ### Interfacing with `tauri` using `Error` //! //! `tauri` serializes backend errors and makes these available as JSON objects to the frontend. The format -//! is an implementation detail, but here it's implemented to turn each [`Error`] into a dict with `code` +//! is an implementation detail, but here it's implemented to turn each `Error` into a dict with `code` //! and `messsage` fields. //! -//! The values in these fields are controlled by attaching context, please [see the `core` docs](gitbutler_core::error)) +//! The values in these fields are controlled by attaching context, please [see the `error` docs](gitbutler_error::error)) //! on how to do this. pub(crate) use frontend::Error; From 0079f2bcbe97732139709c2f24fed483fd68270f Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 12 Jul 2024 15:16:37 +0200 Subject: [PATCH 02/13] minimize the public API of `gitbutler-branch-actions` crate. Also avoid private traits where functions would be easier to use. --- .../gitbutler-branch-actions/src/actions.rs | 3 +- crates/gitbutler-branch-actions/src/base.rs | 15 +- .../src/branch_manager/branch_creation.rs | 17 +- .../src/branch_manager/branch_removal.rs | 17 +- .../src/branch_manager/mod.rs | 8 +- .../gitbutler-branch-actions/src/conflicts.rs | 16 +- crates/gitbutler-branch-actions/src/files.rs | 2 +- .../src/integration.rs | 223 +++++++++--------- crates/gitbutler-branch-actions/src/remote.rs | 8 +- .../gitbutler-branch-actions/src/virtual.rs | 51 ++-- .../tests/extra/mod.rs | 15 +- 11 files changed, 169 insertions(+), 206 deletions(-) diff --git a/crates/gitbutler-branch-actions/src/actions.rs b/crates/gitbutler-branch-actions/src/actions.rs index 2dd45293f..2fee2e401 100644 --- a/crates/gitbutler-branch-actions/src/actions.rs +++ b/crates/gitbutler-branch-actions/src/actions.rs @@ -15,13 +15,12 @@ use gitbutler_reference::ReferenceName; use gitbutler_reference::{Refname, RemoteRefname}; use gitbutler_repo::{credentials::Helper, RepoActions, RepositoryExt}; -use crate::branch_manager::branch_removal::BranchRemoval; use crate::{ base::{ get_base_branch_data, set_base_branch, set_target_push_remote, update_base_branch, BaseBranch, }, - branch_manager::{branch_creation::BranchCreation, BranchManagerAccess}, + branch_manager::BranchManagerExt, remote::{get_branch_data, list_remote_branches, RemoteBranch, RemoteBranchData}, VirtualBranchesExt, }; diff --git a/crates/gitbutler-branch-actions/src/base.rs b/crates/gitbutler-branch-actions/src/base.rs index 2acf14709..10e47ff27 100644 --- a/crates/gitbutler-branch-actions/src/base.rs +++ b/crates/gitbutler-branch-actions/src/base.rs @@ -15,9 +15,8 @@ use gitbutler_repo::{LogUntil, RepoActions, RepositoryExt}; use serde::Serialize; use super::r#virtual as vb; -use crate::branch_manager::branch_removal::BranchRemoval; -use crate::branch_manager::BranchManagerAccess; -use crate::conflicts::RepoConflicts; +use crate::branch_manager::BranchManagerExt; +use crate::conflicts::RepoConflictsExt; use crate::integration::{get_workspace_head, update_gitbutler_integration}; use crate::remote::{commit_to_remote_commit, RemoteCommit}; use crate::{VirtualBranchHunk, VirtualBranchesExt}; @@ -43,7 +42,7 @@ pub struct BaseBranch { pub last_fetched_ms: Option, } -pub fn get_base_branch_data(project_repository: &ProjectRepository) -> Result { +pub(crate) fn get_base_branch_data(project_repository: &ProjectRepository) -> Result { let target = default_target(&project_repository.project().gb_dir())?; let base = target_to_base_branch(project_repository, &target)?; Ok(base) @@ -115,7 +114,7 @@ fn go_back_to_integration( Ok(base) } -pub fn set_base_branch( +pub(crate) fn set_base_branch( project_repository: &ProjectRepository, target_branch_ref: &RemoteRefname, ) -> Result { @@ -271,7 +270,7 @@ pub fn set_base_branch( Ok(base) } -pub fn set_target_push_remote( +pub(crate) fn set_target_push_remote( project_repository: &ProjectRepository, push_remote_name: &str, ) -> Result<()> { @@ -328,7 +327,7 @@ fn _print_tree(repo: &git2::Repository, tree: &git2::Tree) -> Result<()> { // determine if what the target branch is now pointing to is mergeable with our current working directory // merge the target branch into our current working directory // update the target sha -pub fn update_base_branch( +pub(crate) fn update_base_branch( project_repository: &ProjectRepository, ) -> anyhow::Result> { project_repository.assure_resolved()?; @@ -569,7 +568,7 @@ pub fn update_base_branch( Ok(unapplied_branch_names) } -pub fn target_to_base_branch( +pub(crate) fn target_to_base_branch( project_repository: &ProjectRepository, target: &Target, ) -> Result { diff --git a/crates/gitbutler-branch-actions/src/branch_manager/branch_creation.rs b/crates/gitbutler-branch-actions/src/branch_manager/branch_creation.rs index 20e9363ad..6a393e40e 100644 --- a/crates/gitbutler-branch-actions/src/branch_manager/branch_creation.rs +++ b/crates/gitbutler-branch-actions/src/branch_manager/branch_creation.rs @@ -1,6 +1,6 @@ -use super::{branch_removal::BranchRemoval, BranchManager}; +use super::BranchManager; use crate::{ - conflicts::{self, RepoConflicts}, + conflicts::{self, RepoConflictsExt}, ensure_selected_for_changes, integration::update_gitbutler_integration, set_ownership, undo_commit, VirtualBranchHunk, VirtualBranchesExt, @@ -19,15 +19,8 @@ use gitbutler_reference::Refname; use gitbutler_repo::{rebase::cherry_rebase, RepoActions, RepositoryExt}; use gitbutler_time::time::now_since_unix_epoch_ms; -pub trait BranchCreation { - /// Create an empty virtual branch - fn create_virtual_branch(&self, create: &BranchCreateRequest) -> Result; - /// Create a virtual branch from a real branch (whether remote or local) - fn create_virtual_branch_from_branch(&self, upstream: &Refname) -> Result; -} - -impl BranchCreation for BranchManager<'_> { - fn create_virtual_branch(&self, create: &BranchCreateRequest) -> Result { +impl BranchManager<'_> { + pub fn create_virtual_branch(&self, create: &BranchCreateRequest) -> Result { let vb_state = self.project_repository.project().virtual_branches(); let default_target = vb_state.get_default_target()?; @@ -128,7 +121,7 @@ impl BranchCreation for BranchManager<'_> { Ok(branch) } - fn create_virtual_branch_from_branch(&self, upstream: &Refname) -> Result { + pub fn create_virtual_branch_from_branch(&self, upstream: &Refname) -> Result { // only set upstream if it's not the default target let upstream_branch = match upstream { Refname::Other(_) | Refname::Virtual(_) => { diff --git a/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs b/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs index 7e44951d9..21ccc6a06 100644 --- a/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs +++ b/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs @@ -18,20 +18,9 @@ use gitbutler_repo::{RepoActions, RepositoryExt}; use super::BranchManager; -pub trait BranchRemoval { - /// Perminently deletes a virtual branch - fn delete_branch(&self, branch_id: BranchId) -> Result<()>; - /// Converts a virtual branch into a real branch - fn convert_to_real_branch( - &self, - branch_id: BranchId, - name_conflict_resolution: NameConflitResolution, - ) -> Result; -} - -impl BranchRemoval for BranchManager<'_> { +impl BranchManager<'_> { // to unapply a branch, we need to write the current tree out, then remove those file changes from the wd - fn convert_to_real_branch( + pub fn convert_to_real_branch( &self, branch_id: BranchId, name_conflict_resolution: NameConflitResolution, @@ -60,7 +49,7 @@ impl BranchRemoval for BranchManager<'_> { real_branch.reference_name() } - fn delete_branch(&self, branch_id: BranchId) -> Result<()> { + pub(crate) fn delete_branch(&self, branch_id: BranchId) -> Result<()> { let vb_state = self.project_repository.project().virtual_branches(); let Some(branch) = vb_state.try_branch(branch_id)? else { return Ok(()); diff --git a/crates/gitbutler-branch-actions/src/branch_manager/mod.rs b/crates/gitbutler-branch-actions/src/branch_manager/mod.rs index c8f503221..269608d36 100644 --- a/crates/gitbutler-branch-actions/src/branch_manager/mod.rs +++ b/crates/gitbutler-branch-actions/src/branch_manager/mod.rs @@ -1,17 +1,17 @@ use gitbutler_command_context::ProjectRepository; -pub mod branch_creation; -pub mod branch_removal; +mod branch_creation; +mod branch_removal; pub struct BranchManager<'l> { project_repository: &'l ProjectRepository, } -pub trait BranchManagerAccess { +pub trait BranchManagerExt { fn branch_manager(&self) -> BranchManager; } -impl BranchManagerAccess for ProjectRepository { +impl BranchManagerExt for ProjectRepository { fn branch_manager(&self) -> BranchManager { BranchManager { project_repository: self, diff --git a/crates/gitbutler-branch-actions/src/conflicts.rs b/crates/gitbutler-branch-actions/src/conflicts.rs index 75c48d4d2..40fc5a334 100644 --- a/crates/gitbutler-branch-actions/src/conflicts.rs +++ b/crates/gitbutler-branch-actions/src/conflicts.rs @@ -16,7 +16,7 @@ use itertools::Itertools; use gitbutler_error::error::Marker; -pub fn mark, A: AsRef<[P]>>( +pub(crate) fn mark, A: AsRef<[P]>>( repository: &ProjectRepository, paths: A, parent: Option, @@ -43,7 +43,7 @@ pub fn mark, A: AsRef<[P]>>( Ok(()) } -pub fn merge_parent(repository: &ProjectRepository) -> Result> { +pub(crate) fn merge_parent(repository: &ProjectRepository) -> Result> { let merge_path = repository.repo().path().join("base_merge_parent"); if !merge_path.exists() { return Ok(None); @@ -84,7 +84,7 @@ pub fn resolve>(repository: &ProjectRepository, path: P) -> Resul Ok(()) } -pub fn conflicting_files(repository: &ProjectRepository) -> Result> { +pub(crate) fn conflicting_files(repository: &ProjectRepository) -> Result> { let conflicts_path = repository.repo().path().join("conflicts"); if !conflicts_path.exists() { return Ok(vec![]); @@ -97,7 +97,7 @@ pub fn conflicting_files(repository: &ProjectRepository) -> Result> /// Check if `path` is conflicting in `repository`, or if `None`, check if there is any conflict. // TODO(ST): Should this not rather check the conflicting state in the index? -pub fn is_conflicting(repository: &ProjectRepository, path: Option<&Path>) -> Result { +pub(crate) fn is_conflicting(repository: &ProjectRepository, path: Option<&Path>) -> Result { let conflicts_path = repository.repo().path().join("conflicts"); if !conflicts_path.exists() { return Ok(false); @@ -124,11 +124,11 @@ pub fn is_conflicting(repository: &ProjectRepository, path: Option<&Path>) -> Re // is this project still in a resolving conflict state? // - could be that there are no more conflicts, but the state is not committed -pub fn is_resolving(repository: &ProjectRepository) -> bool { +pub(crate) fn is_resolving(repository: &ProjectRepository) -> bool { repository.repo().path().join("base_merge_parent").exists() } -pub fn clear(repository: &ProjectRepository) -> Result<()> { +pub(crate) fn clear(repository: &ProjectRepository) -> Result<()> { let merge_path = repository.repo().path().join("base_merge_parent"); std::fs::remove_file(merge_path)?; @@ -139,13 +139,13 @@ pub fn clear(repository: &ProjectRepository) -> Result<()> { Ok(()) } -pub trait RepoConflicts { +pub(crate) trait RepoConflictsExt { fn assure_unconflicted(&self) -> Result<()>; fn assure_resolved(&self) -> Result<()>; fn is_resolving(&self) -> bool; } -impl RepoConflicts for ProjectRepository { +impl RepoConflictsExt for ProjectRepository { fn is_resolving(&self) -> bool { is_resolving(self) } diff --git a/crates/gitbutler-branch-actions/src/files.rs b/crates/gitbutler-branch-actions/src/files.rs index bda0752f4..720ae8ec1 100644 --- a/crates/gitbutler-branch-actions/src/files.rs +++ b/crates/gitbutler-branch-actions/src/files.rs @@ -12,7 +12,7 @@ pub struct RemoteBranchFile { pub binary: bool, } -pub fn list_remote_commit_files( +pub(crate) fn list_remote_commit_files( repository: &git2::Repository, commit_id: git2::Oid, ) -> Result> { diff --git a/crates/gitbutler-branch-actions/src/integration.rs b/crates/gitbutler-branch-actions/src/integration.rs index 47910ec3a..93ada3ff9 100644 --- a/crates/gitbutler-branch-actions/src/integration.rs +++ b/crates/gitbutler-branch-actions/src/integration.rs @@ -14,13 +14,12 @@ use gitbutler_commit::commit_ext::CommitExt; use gitbutler_error::error::Marker; use gitbutler_repo::{LogUntil, RepoActions, RepositoryExt}; -use crate::branch_manager::branch_creation::BranchCreation; -use crate::branch_manager::BranchManagerAccess; +use crate::branch_manager::BranchManagerExt; use crate::{conflicts, VirtualBranchesExt}; const WORKSPACE_HEAD: &str = "Workspace Head"; -pub fn get_integration_commiter<'a>() -> Result> { +pub(crate) fn get_integration_commiter<'a>() -> Result> { Ok(git2::Signature::now( GITBUTLER_INTEGRATION_COMMIT_AUTHOR_NAME, GITBUTLER_INTEGRATION_COMMIT_AUTHOR_EMAIL, @@ -31,7 +30,7 @@ pub fn get_integration_commiter<'a>() -> Result> { // // This is the base against which we diff the working directory to understand // what files have been modified. -pub fn get_workspace_head( +pub(crate) fn get_workspace_head( vb_state: &VirtualBranchesHandle, project_repo: &ProjectRepository, ) -> Result { @@ -282,138 +281,126 @@ pub fn update_gitbutler_integration( Ok(final_commit) } -pub fn verify_branch(project_repository: &ProjectRepository) -> Result<()> { - project_repository - .verify_current_branch_name() - .and_then(|me| me.verify_head_is_set()) - .and_then(|me| me.verify_head_is_clean()) +pub fn verify_branch(ctx: &ProjectRepository) -> Result<()> { + verify_current_branch_name(ctx) + .and_then(verify_head_is_set) + .and_then(verify_head_is_clean) .context(Marker::VerificationFailure)?; Ok(()) } -pub trait Verify { - fn verify_head_is_set(&self) -> Result<&Self>; - fn verify_current_branch_name(&self) -> Result<&Self>; - fn verify_head_is_clean(&self) -> Result<&Self>; +fn verify_head_is_set(ctx: &ProjectRepository) -> Result<&ProjectRepository> { + match ctx.repo().head().context("failed to get head")?.name() { + Some(refname) if *refname == GITBUTLER_INTEGRATION_REFERENCE.to_string() => Ok(ctx), + Some(head_name) => Err(invalid_head_err(head_name)), + None => Err(anyhow!( + "project in detached head state. Please checkout {} to continue", + GITBUTLER_INTEGRATION_REFERENCE.branch() + )), + } } -impl Verify for ProjectRepository { - fn verify_head_is_set(&self) -> Result<&Self> { - match self.repo().head().context("failed to get head")?.name() { - Some(refname) if *refname == GITBUTLER_INTEGRATION_REFERENCE.to_string() => Ok(self), - Some(head_name) => Err(invalid_head_err(head_name)), - None => Err(anyhow!( - "project in detached head state. Please checkout {} to continue", - GITBUTLER_INTEGRATION_REFERENCE.branch() - )), - } - } - - // Returns an error if repo head is not pointing to the integration branch. - fn verify_current_branch_name(&self) -> Result<&Self> { - match self.repo().head()?.name() { - Some(head) => { - let head_name = head.to_string(); - if head_name != GITBUTLER_INTEGRATION_REFERENCE.to_string() { - return Err(invalid_head_err(&head_name)); - } - Ok(self) +// Returns an error if repo head is not pointing to the integration branch. +fn verify_current_branch_name(ctx: &ProjectRepository) -> Result<&ProjectRepository> { + match ctx.repo().head()?.name() { + Some(head) => { + let head_name = head.to_string(); + if head_name != GITBUTLER_INTEGRATION_REFERENCE.to_string() { + return Err(invalid_head_err(&head_name)); } - None => Err(anyhow!("Repo HEAD is unavailable")), + Ok(ctx) } + None => Err(anyhow!("Repo HEAD is unavailable")), + } +} + +fn verify_head_is_clean(ctx: &ProjectRepository) -> Result<&ProjectRepository> { + let head_commit = ctx + .repo() + .head() + .context("failed to get head")? + .peel_to_commit() + .context("failed to peel to commit")?; + + let vb_handle = VirtualBranchesHandle::new(ctx.project().gb_dir()); + let default_target = vb_handle + .get_default_target() + .context("failed to get default target")?; + + let mut extra_commits = ctx + .log(head_commit.id(), LogUntil::Commit(default_target.sha)) + .context("failed to get log")?; + + let integration_commit = extra_commits.pop(); + + if integration_commit.is_none() { + // no integration commit found + bail!("gibButler's integration commit not found on head"); } - fn verify_head_is_clean(&self) -> Result<&Self> { - let head_commit = self + if extra_commits.is_empty() { + // no extra commits found, so we're good + return Ok(ctx); + } + + ctx.repo() + .reset( + integration_commit.as_ref().unwrap().as_object(), + git2::ResetType::Soft, + None, + ) + .context("failed to reset to integration commit")?; + + let branch_manager = ctx.branch_manager(); + let mut new_branch = branch_manager + .create_virtual_branch(&BranchCreateRequest { + name: extra_commits + .last() + .map(|commit| commit.message_bstr().to_string()), + ..Default::default() + }) + .context("failed to create virtual branch")?; + + // rebasing the extra commits onto the new branch + let vb_state = ctx.project().virtual_branches(); + extra_commits.reverse(); + let mut head = new_branch.head; + for commit in extra_commits { + let new_branch_head = ctx .repo() - .head() - .context("failed to get head")? - .peel_to_commit() - .context("failed to peel to commit")?; + .find_commit(head) + .context("failed to find new branch head")?; - let vb_handle = VirtualBranchesHandle::new(self.project().gb_dir()); - let default_target = vb_handle - .get_default_target() - .context("failed to get default target")?; - - let mut extra_commits = self - .log(head_commit.id(), LogUntil::Commit(default_target.sha)) - .context("failed to get log")?; - - let integration_commit = extra_commits.pop(); - - if integration_commit.is_none() { - // no integration commit found - bail!("gibButler's integration commit not found on head"); - } - - if extra_commits.is_empty() { - // no extra commits found, so we're good - return Ok(self); - } - - self.repo() - .reset( - integration_commit.as_ref().unwrap().as_object(), - git2::ResetType::Soft, + let rebased_commit_oid = ctx + .repo() + .commit_with_signature( + None, + &commit.author(), + &commit.committer(), + &commit.message_bstr().to_str_lossy(), + &commit.tree().unwrap(), + &[&new_branch_head], None, ) - .context("failed to reset to integration commit")?; + .context(format!( + "failed to rebase commit {} onto new branch", + commit.id() + ))?; - let branch_manager = self.branch_manager(); - let mut new_branch = branch_manager - .create_virtual_branch(&BranchCreateRequest { - name: extra_commits - .last() - .map(|commit| commit.message_bstr().to_string()), - ..Default::default() - }) - .context("failed to create virtual branch")?; + let rebased_commit = ctx.repo().find_commit(rebased_commit_oid).context(format!( + "failed to find rebased commit {}", + rebased_commit_oid + ))?; - // rebasing the extra commits onto the new branch - let vb_state = self.project().virtual_branches(); - extra_commits.reverse(); - let mut head = new_branch.head; - for commit in extra_commits { - let new_branch_head = self - .repo() - .find_commit(head) - .context("failed to find new branch head")?; + new_branch.head = rebased_commit.id(); + new_branch.tree = rebased_commit.tree_id(); + vb_state + .set_branch(new_branch.clone()) + .context("failed to write branch")?; - let rebased_commit_oid = self - .repo() - .commit_with_signature( - None, - &commit.author(), - &commit.committer(), - &commit.message_bstr().to_str_lossy(), - &commit.tree().unwrap(), - &[&new_branch_head], - None, - ) - .context(format!( - "failed to rebase commit {} onto new branch", - commit.id() - ))?; - - let rebased_commit = self - .repo() - .find_commit(rebased_commit_oid) - .context(format!( - "failed to find rebased commit {}", - rebased_commit_oid - ))?; - - new_branch.head = rebased_commit.id(); - new_branch.tree = rebased_commit.tree_id(); - vb_state - .set_branch(new_branch.clone()) - .context("failed to write branch")?; - - head = rebased_commit.id(); - } - Ok(self) + head = rebased_commit.id(); } + Ok(ctx) } fn invalid_head_err(head_name: &str) -> anyhow::Error { diff --git a/crates/gitbutler-branch-actions/src/remote.rs b/crates/gitbutler-branch-actions/src/remote.rs index 6a6cbd673..2120460bc 100644 --- a/crates/gitbutler-branch-actions/src/remote.rs +++ b/crates/gitbutler-branch-actions/src/remote.rs @@ -88,7 +88,7 @@ pub fn list_remote_branches(project_repository: &ProjectRepository) -> Result Result { @@ -103,7 +103,7 @@ pub fn get_branch_data( .context("failed to get branch data") } -pub fn branch_to_remote_branch(branch: &git2::Branch) -> Result> { +pub(crate) fn branch_to_remote_branch(branch: &git2::Branch) -> Result> { let commit = match branch.get().peel_to_commit() { Ok(c) => c, Err(err) => { @@ -146,7 +146,7 @@ pub fn branch_to_remote_branch(branch: &git2::Branch) -> Result RemoteCommit { +pub(crate) fn commit_to_remote_commit(commit: &git2::Commit) -> RemoteCommit { let parent_ids: Vec = commit.parents().map(|c| c.id()).collect::>(); RemoteCommit { id: commit.id().to_string(), diff --git a/crates/gitbutler-branch-actions/src/virtual.rs b/crates/gitbutler-branch-actions/src/virtual.rs index bf4b3a988..27bede009 100644 --- a/crates/gitbutler-branch-actions/src/virtual.rs +++ b/crates/gitbutler-branch-actions/src/virtual.rs @@ -29,10 +29,8 @@ use hex::ToHex; use serde::{Deserialize, Serialize}; use crate::author::Author; -use crate::branch_manager::branch_creation::BranchCreation; -use crate::branch_manager::branch_removal::BranchRemoval; -use crate::branch_manager::BranchManagerAccess; -use crate::conflicts::{self, RepoConflicts}; +use crate::branch_manager::BranchManagerExt; +use crate::conflicts::{self, RepoConflictsExt}; use crate::integration::get_workspace_head; use crate::remote::{branch_to_remote_branch, RemoteBranch}; use crate::VirtualBranchesExt; @@ -165,7 +163,7 @@ pub struct VirtualBranchHunk { /// Lifecycle impl VirtualBranchHunk { - pub fn gen_id(new_start: u32, new_lines: u32) -> String { + pub(crate) fn gen_id(new_start: u32, new_lines: u32) -> String { format!("{}-{}", new_start, new_start + new_lines) } fn from_git_hunk( @@ -294,7 +292,10 @@ pub fn unapply_ownership( } // reset a file in the project to the index state -pub fn reset_files(project_repository: &ProjectRepository, files: &Vec) -> Result<()> { +pub(crate) fn reset_files( + project_repository: &ProjectRepository, + files: &Vec, +) -> Result<()> { project_repository.assure_resolved()?; // for each tree, we need to checkout the entry from the index at that path @@ -784,7 +785,7 @@ pub fn integrate_upstream_commits( Ok(()) } -pub fn integrate_with_rebase( +pub(crate) fn integrate_with_rebase( project_repository: &ProjectRepository, branch: &mut Branch, unknown_commits: &mut Vec, @@ -796,7 +797,7 @@ pub fn integrate_with_rebase( ) } -pub fn integrate_with_merge( +pub(crate) fn integrate_with_merge( project_repository: &ProjectRepository, branch: &mut Branch, upstream_commit: &git2::Commit, @@ -929,7 +930,7 @@ pub fn update_branch( Ok(branch) } -pub fn ensure_selected_for_changes(vb_state: &VirtualBranchesHandle) -> Result<()> { +pub(crate) fn ensure_selected_for_changes(vb_state: &VirtualBranchesHandle) -> Result<()> { let mut virtual_branches = vb_state .list_branches_in_workspace() .context("failed to list branches")?; @@ -954,7 +955,7 @@ pub fn ensure_selected_for_changes(vb_state: &VirtualBranchesHandle) -> Result<( Ok(()) } -pub fn set_ownership( +pub(crate) fn set_ownership( vb_state: &VirtualBranchesHandle, target_branch: &mut branch::Branch, ownership: &gitbutler_branch::ownership::BranchOwnershipClaims, @@ -1029,7 +1030,7 @@ pub(super) fn virtual_hunks_by_git_hunks<'a>( }) } -pub fn virtual_hunks_by_file_diffs<'a>( +pub(crate) fn virtual_hunks_by_file_diffs<'a>( project_path: &'a Path, diff: impl IntoIterator + 'a, ) -> impl Iterator)> + 'a { @@ -1344,7 +1345,7 @@ fn virtual_hunks_into_virtual_files( } // reset virtual branch to a specific commit -pub fn reset_branch( +pub(crate) fn reset_branch( project_repository: &ProjectRepository, branch_id: BranchId, target_commit_id: git2::Oid, @@ -1426,7 +1427,7 @@ fn diffs_into_virtual_files( // this function takes a list of file ownership, // constructs a tree from those changes on top of the target // and writes it as a new tree for storage -pub fn write_tree( +pub(crate) fn write_tree( project_repository: &ProjectRepository, target: &git2::Oid, files: impl IntoIterator, impl Borrow>)>, @@ -1434,7 +1435,7 @@ pub fn write_tree( write_tree_onto_commit(project_repository, *target, files) } -pub fn write_tree_onto_commit( +pub(crate) fn write_tree_onto_commit( project_repository: &ProjectRepository, commit_oid: git2::Oid, files: impl IntoIterator, impl Borrow>)>, @@ -1448,7 +1449,7 @@ pub fn write_tree_onto_commit( write_tree_onto_tree(project_repository, &base_tree, files) } -pub fn write_tree_onto_tree( +pub(crate) fn write_tree_onto_tree( project_repository: &ProjectRepository, base_tree: &git2::Tree, files: impl IntoIterator, impl Borrow>)>, @@ -1717,7 +1718,7 @@ pub fn commit( Ok(commit_oid) } -pub fn push( +pub(crate) fn push( project_repository: &ProjectRepository, branch_id: BranchId, with_force: bool, @@ -1891,7 +1892,7 @@ pub fn is_remote_branch_mergeable( // and the rebase should be simple. if the "to" commit is above the "from" commit, // the changes need to be removed from the "from" commit, everything rebased, // then added to the "to" commit and everything above that rebased again. -pub fn move_commit_file( +pub(crate) fn move_commit_file( project_repository: &ProjectRepository, branch_id: BranchId, from_commit_id: git2::Oid, @@ -2128,7 +2129,7 @@ pub fn move_commit_file( // takes a list of file ownership and a commit oid and rewrites that commit to // add the file changes. The branch is then rebased onto the new commit // and the respective branch head is updated -pub fn amend( +pub(crate) fn amend( project_repository: &ProjectRepository, branch_id: BranchId, commit_oid: git2::Oid, @@ -2262,7 +2263,7 @@ pub fn amend( // if the offset is positive, move the commit down one // if the offset is negative, move the commit up one // rewrites the branch head to the new head commit -pub fn reorder_commit( +pub(crate) fn reorder_commit( project_repository: &ProjectRepository, branch_id: BranchId, commit_oid: git2::Oid, @@ -2347,7 +2348,7 @@ pub fn reorder_commit( // create and insert a blank commit (no tree change) either above or below a commit // if offset is positive, insert below, if negative, insert above // return the oid of the new head commit of the branch with the inserted blank commit -pub fn insert_blank_commit( +pub(crate) fn insert_blank_commit( project_repository: &ProjectRepository, branch_id: BranchId, commit_oid: git2::Oid, @@ -2401,7 +2402,7 @@ pub fn insert_blank_commit( // remove a commit in a branch by rebasing all commits _except_ for it onto it's parent // if successful, it will update the branch head to the new head commit -pub fn undo_commit( +pub(crate) fn undo_commit( project_repository: &ProjectRepository, branch_id: BranchId, commit_oid: git2::Oid, @@ -2452,7 +2453,7 @@ pub fn undo_commit( } /// squashes a commit from a virtual branch into its parent. -pub fn squash( +pub(crate) fn squash( project_repository: &ProjectRepository, branch_id: BranchId, commit_id: git2::Oid, @@ -2541,7 +2542,7 @@ pub fn squash( } // changes a commit message for commit_oid, rebases everything above it, updates branch head if successful -pub fn update_commit_message( +pub(crate) fn update_commit_message( project_repository: &ProjectRepository, branch_id: BranchId, commit_id: git2::Oid, @@ -2615,7 +2616,7 @@ pub fn update_commit_message( } /// moves commit from the branch it's in to the top of the target branch -pub fn move_commit( +pub(crate) fn move_commit( project_repository: &ProjectRepository, target_branch_id: BranchId, commit_id: git2::Oid, @@ -2747,7 +2748,7 @@ pub fn move_commit( } /// Just like [`diffy::apply()`], but on error it will attach hashes of the input `base_image` and `patch`. -pub fn apply>(base_image: S, patch: &Patch<'_, [u8]>) -> Result { +pub(crate) fn apply>(base_image: S, patch: &Patch<'_, [u8]>) -> Result { fn md5_hash_hex(b: impl AsRef<[u8]>) -> String { md5::compute(b).encode_hex() } diff --git a/crates/gitbutler-branch-actions/tests/extra/mod.rs b/crates/gitbutler-branch-actions/tests/extra/mod.rs index 69221a92c..c0131a3e8 100644 --- a/crates/gitbutler-branch-actions/tests/extra/mod.rs +++ b/crates/gitbutler-branch-actions/tests/extra/mod.rs @@ -18,17 +18,12 @@ use gitbutler_branch::{ ownership::BranchOwnershipClaims, target::Target, }; -use gitbutler_branch_actions::{ - branch_manager::branch_creation::BranchCreation, r#virtual as virtual_branches, +use gitbutler_branch_actions::r#virtual as virtual_branches; +use gitbutler_branch_actions::r#virtual::{ + commit, integrate_upstream_commits, is_remote_branch_mergeable, list_virtual_branches, + unapply_ownership, update_branch, }; -use gitbutler_branch_actions::{ - branch_manager::branch_removal::BranchRemoval, - r#virtual::{ - commit, integrate_upstream_commits, is_remote_branch_mergeable, list_virtual_branches, - unapply_ownership, update_branch, - }, -}; -use gitbutler_branch_actions::{branch_manager::BranchManagerAccess, integration}; +use gitbutler_branch_actions::{branch_manager::BranchManagerExt, integration}; use gitbutler_commit::{commit_ext::CommitExt, commit_headers::CommitHeadersV2}; use gitbutler_reference::{Refname, RemoteRefname}; use gitbutler_repo::RepositoryExt; From 5e408f6b2dbb06a5a12c3e3626bfbbd4f1504cbf Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 12 Jul 2024 16:17:15 +0200 Subject: [PATCH 03/13] Assure a single path to an item (struct, fn) Also, simplify paths and optimize them (to some extend) to seem non-redundant when `use`ing them. --- .../gitbutler-branch-actions/src/actions.rs | 6 +- .../src/branch_manager/branch_removal.rs | 12 +- crates/gitbutler-branch-actions/src/lib.rs | 21 ++-- .../gitbutler-branch-actions/src/virtual.rs | 2 +- .../tests/extra/mod.rs | 114 +++++++++--------- 5 files changed, 80 insertions(+), 75 deletions(-) diff --git a/crates/gitbutler-branch-actions/src/actions.rs b/crates/gitbutler-branch-actions/src/actions.rs index 2fee2e401..e06182545 100644 --- a/crates/gitbutler-branch-actions/src/actions.rs +++ b/crates/gitbutler-branch-actions/src/actions.rs @@ -29,8 +29,8 @@ use super::r#virtual as branch; use crate::files::RemoteBranchFile; -#[derive(Clone, Default)] -pub struct VirtualBranchActions {} +#[derive(Clone, Copy, Default)] +pub struct VirtualBranchActions; impl VirtualBranchActions { pub async fn create_commit( @@ -298,7 +298,7 @@ impl VirtualBranchActions { &self, project: &Project, branch_id: BranchId, - name_conflict_resolution: branch::NameConflitResolution, + name_conflict_resolution: branch::NameConflictResolution, ) -> Result { let project_repository = open_with_verify(project)?; let snapshot_tree = project_repository.project().prepare_snapshot(); diff --git a/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs b/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs index 21ccc6a06..dacedbc3b 100644 --- a/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs +++ b/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs @@ -2,7 +2,7 @@ use crate::{ conflicts::{self}, ensure_selected_for_changes, get_applied_status, integration::get_integration_commiter, - write_tree, NameConflitResolution, VirtualBranchesExt, + write_tree, NameConflictResolution, VirtualBranchesExt, }; use anyhow::{anyhow, Context, Result}; use git2::build::TreeUpdateBuilder; @@ -23,7 +23,7 @@ impl BranchManager<'_> { pub fn convert_to_real_branch( &self, branch_id: BranchId, - name_conflict_resolution: NameConflitResolution, + name_conflict_resolution: NameConflictResolution, ) -> Result { let vb_state = self.project_repository.project().virtual_branches(); @@ -125,7 +125,7 @@ impl BranchManager<'_> { fn build_real_branch( &self, vbranch: &mut branch::Branch, - name_conflict_resolution: NameConflitResolution, + name_conflict_resolution: NameConflictResolution, ) -> Result> { let repo = self.project_repository.repo(); let target_commit = repo.find_commit(vbranch.head)?; @@ -138,7 +138,7 @@ impl BranchManager<'_> { .is_ok() { match name_conflict_resolution { - NameConflitResolution::Suffix => { + NameConflictResolution::Suffix => { let mut suffix = 1; loop { let new_branch_name = format!("{}-{}", branch_name, suffix); @@ -151,7 +151,7 @@ impl BranchManager<'_> { suffix += 1; } } - NameConflitResolution::Rename(new_name) => { + NameConflictResolution::Rename(new_name) => { if repo .find_branch(new_name.as_str(), git2::BranchType::Local) .is_ok() @@ -161,7 +161,7 @@ impl BranchManager<'_> { new_name } } - NameConflitResolution::Overwrite => branch_name, + NameConflictResolution::Overwrite => branch_name, } } else { branch_name diff --git a/crates/gitbutler-branch-actions/src/lib.rs b/crates/gitbutler-branch-actions/src/lib.rs index 5a224fddb..b4e2817c0 100644 --- a/crates/gitbutler-branch-actions/src/lib.rs +++ b/crates/gitbutler-branch-actions/src/lib.rs @@ -1,19 +1,24 @@ -//! GitButler internal library containing functionaliry related to branches, i.e. the virtual branches implementation -pub mod actions; +//! GitButler internal library containing functionality related to branches, i.e. the virtual branches implementation +mod actions; pub use actions::VirtualBranchActions; -pub mod r#virtual; +mod r#virtual; pub use r#virtual::*; -pub mod branch_manager; +mod branch_manager; +pub use branch_manager::{BranchManager, BranchManagerExt}; -pub mod base; +mod base; +pub use base::BaseBranch; -pub mod integration; +mod integration; +pub use integration::{update_gitbutler_integration, verify_branch}; -pub mod files; +mod files; +pub use files::RemoteBranchFile; -pub mod remote; +mod remote; +pub use remote::{list_remote_branches, RemoteBranch, RemoteBranchData, RemoteCommit}; pub mod conflicts; diff --git a/crates/gitbutler-branch-actions/src/virtual.rs b/crates/gitbutler-branch-actions/src/virtual.rs index 27bede009..1e786b1d0 100644 --- a/crates/gitbutler-branch-actions/src/virtual.rs +++ b/crates/gitbutler-branch-actions/src/virtual.rs @@ -192,7 +192,7 @@ impl VirtualBranchHunk { #[derive(Default, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase", tag = "type", content = "value")] -pub enum NameConflitResolution { +pub enum NameConflictResolution { #[default] Suffix, Rename(String), diff --git a/crates/gitbutler-branch-actions/tests/extra/mod.rs b/crates/gitbutler-branch-actions/tests/extra/mod.rs index c0131a3e8..9285116f4 100644 --- a/crates/gitbutler-branch-actions/tests/extra/mod.rs +++ b/crates/gitbutler-branch-actions/tests/extra/mod.rs @@ -18,12 +18,12 @@ use gitbutler_branch::{ ownership::BranchOwnershipClaims, target::Target, }; -use gitbutler_branch_actions::r#virtual as virtual_branches; -use gitbutler_branch_actions::r#virtual::{ - commit, integrate_upstream_commits, is_remote_branch_mergeable, list_virtual_branches, - unapply_ownership, update_branch, +use gitbutler_branch_actions::BranchManagerExt; +use gitbutler_branch_actions::{ + commit, get_status_by_branch, integrate_upstream_commits, is_remote_branch_mergeable, + list_virtual_branches, unapply_ownership, update_branch, update_gitbutler_integration, + verify_branch, }; -use gitbutler_branch_actions::{branch_manager::BranchManagerExt, integration}; use gitbutler_commit::{commit_ext::CommitExt, commit_headers::CommitHeadersV2}; use gitbutler_reference::{Refname, RemoteRefname}; use gitbutler_repo::RepositoryExt; @@ -56,7 +56,7 @@ fn commit_on_branch_then_change_file_then_get_status() -> Result<()> { "line0\nline1\nline2\nline3\nline4\n", )?; - let (branches, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (branches, _) = list_virtual_branches(project_repository)?; let branch = &branches[0]; assert_eq!(branch.files.len(), 1); assert_eq!(branch.commits.len(), 0); @@ -65,7 +65,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, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (branches, _) = list_virtual_branches(project_repository)?; let branch = &branches[0]; assert_eq!(branch.files.len(), 0); assert_eq!(branch.commits.len(), 1); @@ -76,7 +76,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, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (branches, _) = list_virtual_branches(project_repository)?; let branch = &branches[0]; assert_eq!(branch.files.len(), 1); assert_eq!(branch.commits.len(), 1); @@ -138,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, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (branches, _) = list_virtual_branches(project_repository)?; let branch = &branches[0]; assert_eq!(branch.files.len(), 2); let img_file = &branch @@ -158,7 +158,7 @@ fn track_binary_files() -> Result<()> { commit(project_repository, branch1_id, "test commit", None, false)?; // status (no files) - let (branches, _) = virtual_branches::list_virtual_branches(project_repository).unwrap(); + let (branches, _) = list_virtual_branches(project_repository).unwrap(); let commit_id = &branches[0].commits[0].id; let commit_obj = project_repository .repo() @@ -183,7 +183,7 @@ fn track_binary_files() -> Result<()> { // commit commit(project_repository, branch1_id, "test commit", None, false)?; - let (branches, _) = virtual_branches::list_virtual_branches(project_repository).unwrap(); + let (branches, _) = list_virtual_branches(project_repository).unwrap(); let commit_id = &branches[0].commits[0].id; // get tree from commit_id let commit_obj = project_repository @@ -217,7 +217,7 @@ fn create_branch_with_ownership() -> Result<()> { .create_virtual_branch(&BranchCreateRequest::default()) .expect("failed to create virtual branch"); - virtual_branches::get_status_by_branch(project_repository, None).expect("failed to get status"); + get_status_by_branch(project_repository, None).expect("failed to get status"); let vb_state = VirtualBranchesHandle::new(project_repository.project().gb_dir()); let branch0 = vb_state.get_branch_in_workspace(branch0.id).unwrap(); @@ -229,7 +229,7 @@ fn create_branch_with_ownership() -> Result<()> { }) .expect("failed to create virtual branch"); - let statuses = virtual_branches::get_status_by_branch(project_repository, None) + let statuses = get_status_by_branch(project_repository, None) .expect("failed to get status") .0; @@ -331,7 +331,7 @@ fn hunk_expantion() -> Result<()> { .expect("failed to create virtual branch") .id; - let statuses = virtual_branches::get_status_by_branch(project_repository, None) + let statuses = get_status_by_branch(project_repository, None) .expect("failed to get status") .0; @@ -368,7 +368,7 @@ fn hunk_expantion() -> Result<()> { "line1\nline2\nline3\n", )?; - let statuses = virtual_branches::get_status_by_branch(project_repository, None) + let statuses = get_status_by_branch(project_repository, None) .expect("failed to get status") .0; let files_by_branch_id = statuses @@ -392,7 +392,7 @@ fn get_status_files_by_branch_no_hunks_no_branches() -> Result<()> { set_test_target(project_repository)?; - let statuses = virtual_branches::get_status_by_branch(project_repository, None) + let statuses = get_status_by_branch(project_repository, None) .expect("failed to get status") .0; @@ -425,7 +425,7 @@ fn get_status_files_by_branch() -> Result<()> { .expect("failed to create virtual branch") .id; - let statuses = virtual_branches::get_status_by_branch(project_repository, None) + let statuses = get_status_by_branch(project_repository, None) .expect("failed to get status") .0; let files_by_branch_id = statuses @@ -485,7 +485,7 @@ fn move_hunks_multiple_sources() -> Result<()> { }; vb_state.set_branch(branch1.clone())?; - let statuses = virtual_branches::get_status_by_branch(project_repository, None) + let statuses = get_status_by_branch(project_repository, None) .expect("failed to get status") .0; @@ -510,7 +510,7 @@ fn move_hunks_multiple_sources() -> Result<()> { }, )?; - let statuses = virtual_branches::get_status_by_branch(project_repository, None) + let statuses = get_status_by_branch(project_repository, None) .expect("failed to get status") .0; @@ -568,7 +568,7 @@ fn move_hunks_partial_explicitly() -> Result<()> { .expect("failed to create virtual branch") .id; - let statuses = virtual_branches::get_status_by_branch(project_repository, None) + let statuses = get_status_by_branch(project_repository, None) .expect("failed to get status") .0; let files_by_branch_id = statuses @@ -590,7 +590,7 @@ fn move_hunks_partial_explicitly() -> Result<()> { }, )?; - let statuses = virtual_branches::get_status_by_branch(project_repository, None) + let statuses = get_status_by_branch(project_repository, None) .expect("failed to get status") .0; @@ -647,7 +647,7 @@ fn add_new_hunk_to_the_end() -> Result<()> { .create_virtual_branch(&BranchCreateRequest::default()) .expect("failed to create virtual branch"); - let statuses = virtual_branches::get_status_by_branch(project_repository, None) + let statuses = get_status_by_branch(project_repository, None) .expect("failed to get status") .0; assert_eq!( @@ -660,7 +660,7 @@ fn add_new_hunk_to_the_end() -> Result<()> { "line0\nline1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nline9\nline10\nline11\nline12\nline13\nline14\nline15\n", )?; - let statuses = virtual_branches::get_status_by_branch(project_repository, None) + let statuses = get_status_by_branch(project_repository, None) .expect("failed to get status") .0; @@ -811,7 +811,7 @@ fn merge_vbranch_upstream_clean_rebase() -> Result<()> { std::fs::write(Path::new(&project.path).join(file_path2), "file2\n")?; // Update integration commit - integration::update_gitbutler_integration(&vb_state, project_repository)?; + update_gitbutler_integration(&vb_state, project_repository)?; let remote_branch: RemoteRefname = "refs/remotes/origin/master".parse().unwrap(); let branch_manager = project_repository.branch_manager(); @@ -823,7 +823,7 @@ fn merge_vbranch_upstream_clean_rebase() -> Result<()> { vb_state.set_branch(branch.clone())?; // create the branch - let (branches, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (branches, _) = list_virtual_branches(project_repository)?; let branch1 = &branches[0]; assert_eq!(branch1.files.len(), 1); assert_eq!(branch1.commits.len(), 1); @@ -831,7 +831,7 @@ fn merge_vbranch_upstream_clean_rebase() -> Result<()> { integrate_upstream_commits(project_repository, branch1.id)?; - let (branches, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (branches, _) = list_virtual_branches(project_repository)?; let branch1 = &branches[0]; let contents = std::fs::read(Path::new(&project.path).join(file_path))?; @@ -932,7 +932,7 @@ async fn merge_vbranch_upstream_conflict() -> Result<()> { .unwrap(); // create the branch - let (branches, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (branches, _) = list_virtual_branches(project_repository)?; let branch1 = &branches[0]; assert_eq!(branch1.files.len(), 1); @@ -941,7 +941,7 @@ async fn merge_vbranch_upstream_conflict() -> Result<()> { integrate_upstream_commits(project_repository, branch1.id)?; - let (branches, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (branches, _) = list_virtual_branches(project_repository)?; let branch1 = &branches[0]; let contents = std::fs::read(Path::new(&project.path).join(file_path))?; @@ -961,7 +961,7 @@ async fn merge_vbranch_upstream_conflict() -> Result<()> { )?; // make gb see the conflict resolution - let (branches, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (branches, _) = list_virtual_branches(project_repository)?; assert!(branches[0].conflicted); // commit the merge resolution @@ -973,7 +973,7 @@ async fn merge_vbranch_upstream_conflict() -> Result<()> { false, )?; - let (branches, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (branches, _) = list_virtual_branches(project_repository)?; let branch1 = &branches[0]; assert!(!branch1.conflicted); assert_eq!(branch1.files.len(), 0); @@ -1011,7 +1011,7 @@ fn unapply_ownership_partial() -> Result<()> { .create_virtual_branch(&BranchCreateRequest::default()) .expect("failed to create virtual branch"); - let (branches, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (branches, _) = list_virtual_branches(project_repository)?; assert_eq!(branches.len(), 1); assert_eq!(branches[0].files.len(), 1); assert_eq!(branches[0].ownership.claims.len(), 1); @@ -1024,7 +1024,7 @@ fn unapply_ownership_partial() -> Result<()> { unapply_ownership(project_repository, &"test.txt:2-6".parse().unwrap()).unwrap(); - let (branches, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (branches, _) = list_virtual_branches(project_repository)?; assert_eq!(branches.len(), 1); assert_eq!(branches[0].files.len(), 0); assert_eq!(branches[0].ownership.claims.len(), 0); @@ -1089,7 +1089,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, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (branches, _) = list_virtual_branches(project_repository)?; let branch = &branches.iter().find(|b| b.id == branch1_id).unwrap(); assert_eq!(branch.files.len(), 1); assert!(branch.active); @@ -1102,7 +1102,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, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (branches, _) = list_virtual_branches(project_repository)?; assert!(!branches.iter().any(|b| b.id == branch1_id)); let branch_manager = project_repository.branch_manager(); @@ -1116,7 +1116,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, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (branches, _) = list_virtual_branches(project_repository)?; let branch = &branches.iter().find(|b| b.id == branch1_id).unwrap(); // TODO: expect there to be 0 branches assert_eq!(branch.files.len(), 0); @@ -1333,7 +1333,7 @@ fn detect_mergeable_branch() -> Result<()> { }; vb_state.set_branch(branch4.clone())?; - let remotes = gitbutler_branch_actions::remote::list_remote_branches(project_repository) + let remotes = gitbutler_branch_actions::list_remote_branches(project_repository) .expect("failed to list remotes"); let _remote1 = &remotes .iter() @@ -1403,7 +1403,7 @@ fn upstream_integrated_vbranch() -> Result<()> { project_repository .repo() .remote("origin", "http://origin.com/project")?; - integration::update_gitbutler_integration(&vb_state, project_repository)?; + update_gitbutler_integration(&vb_state, project_repository)?; // create vbranches, one integrated, one not let branch_manager = project_repository.branch_manager(); @@ -1476,7 +1476,7 @@ fn upstream_integrated_vbranch() -> Result<()> { false, )?; - let (branches, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (branches, _) = list_virtual_branches(project_repository)?; let branch1 = &branches.iter().find(|b| b.id == branch1_id).unwrap(); assert!(branch1.commits.iter().any(|c| c.is_integrated)); @@ -1521,7 +1521,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, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (branches, _) = list_virtual_branches(project_repository)?; let branch = &branches.iter().find(|b| b.id == branch1_id).unwrap(); assert_eq!(branch.files.len(), 1); @@ -1537,7 +1537,7 @@ fn commit_same_hunk_twice() -> Result<()> { false, )?; - let (branches, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (branches, _) = list_virtual_branches(project_repository)?; let branch = &branches.iter().find(|b| b.id == branch1_id).unwrap(); assert_eq!(branch.files.len(), 0, "no files expected"); @@ -1557,7 +1557,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, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (branches, _) = list_virtual_branches(project_repository)?; let branch = &branches.iter().find(|b| b.id == branch1_id).unwrap(); assert_eq!(branch.files.len(), 1, "one file should be changed"); @@ -1571,7 +1571,7 @@ fn commit_same_hunk_twice() -> Result<()> { false, )?; - let (branches, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (branches, _) = list_virtual_branches(project_repository)?; let branch = &branches.iter().find(|b| b.id == branch1_id).unwrap(); assert_eq!( @@ -1614,7 +1614,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, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (branches, _) = list_virtual_branches(project_repository)?; let branch = &branches.iter().find(|b| b.id == branch1_id).unwrap(); assert_eq!(branch.files.len(), 1); @@ -1630,7 +1630,7 @@ fn commit_same_file_twice() -> Result<()> { false, )?; - let (branches, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (branches, _) = list_virtual_branches(project_repository)?; let branch = &branches.iter().find(|b| b.id == branch1_id).unwrap(); assert_eq!(branch.files.len(), 0, "no files expected"); @@ -1650,7 +1650,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, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (branches, _) = list_virtual_branches(project_repository)?; let branch = &branches.iter().find(|b| b.id == branch1_id).unwrap(); assert_eq!(branch.files.len(), 1, "one file should be changed"); @@ -1664,7 +1664,7 @@ fn commit_same_file_twice() -> Result<()> { false, )?; - let (branches, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (branches, _) = list_virtual_branches(project_repository)?; let branch = &branches.iter().find(|b| b.id == branch1_id).unwrap(); assert_eq!( @@ -1707,7 +1707,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, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (branches, _) = list_virtual_branches(project_repository)?; let branch = &branches.iter().find(|b| b.id == branch1_id).unwrap(); assert_eq!(branch.files.len(), 1); @@ -1723,7 +1723,7 @@ fn commit_partial_by_hunk() -> Result<()> { false, )?; - let (branches, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (branches, _) = list_virtual_branches(project_repository)?; let branch = &branches.iter().find(|b| b.id == branch1_id).unwrap(); assert_eq!(branch.files.len(), 1); @@ -1740,7 +1740,7 @@ fn commit_partial_by_hunk() -> Result<()> { false, )?; - let (branches, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (branches, _) = list_virtual_branches(project_repository)?; let branch = &branches.iter().find(|b| b.id == branch1_id).unwrap(); assert_eq!(branch.files.len(), 0); @@ -1791,7 +1791,7 @@ fn commit_partial_by_file() -> Result<()> { false, )?; - let (branches, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (branches, _) = list_virtual_branches(project_repository)?; let branch1 = &branches.iter().find(|b| b.id == branch1_id).unwrap(); // branch one test.txt has just the 1st and 3rd hunks applied @@ -1851,7 +1851,7 @@ fn commit_add_and_delete_files() -> Result<()> { false, )?; - let (branches, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (branches, _) = list_virtual_branches(project_repository)?; let branch1 = &branches.iter().find(|b| b.id == branch1_id).unwrap(); // branch one test.txt has just the 1st and 3rd hunks applied @@ -1917,7 +1917,7 @@ fn commit_executable_and_symlinks() -> Result<()> { false, )?; - let (branches, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (branches, _) = list_virtual_branches(project_repository)?; let branch1 = &branches.iter().find(|b| b.id == branch1_id).unwrap(); let commit = &branch1.commits[0].id; @@ -2003,7 +2003,7 @@ fn verify_branch_commits_to_integration() -> Result<()> { set_test_target(project_repository)?; - integration::verify_branch(project_repository).unwrap(); + verify_branch(project_repository).unwrap(); // write two commits let file_path2 = Path::new("test2.txt"); @@ -2013,10 +2013,10 @@ fn verify_branch_commits_to_integration() -> Result<()> { commit_all(project_repository.repo()); // verify puts commits onto the virtual branch - integration::verify_branch(project_repository).unwrap(); + verify_branch(project_repository).unwrap(); // one virtual branch with two commits was created - let (virtual_branches, _) = virtual_branches::list_virtual_branches(project_repository)?; + let (virtual_branches, _) = list_virtual_branches(project_repository)?; assert_eq!(virtual_branches.len(), 1); let branch = &virtual_branches.first().unwrap(); @@ -2035,11 +2035,11 @@ fn verify_branch_not_integration() -> Result<()> { set_test_target(project_repository)?; - integration::verify_branch(project_repository).unwrap(); + verify_branch(project_repository).unwrap(); project_repository.repo().set_head("refs/heads/master")?; - let verify_result = integration::verify_branch(project_repository); + let verify_result = verify_branch(project_repository); assert!(verify_result.is_err()); assert_eq!( format!("{:#}", verify_result.unwrap_err()), From 4805f6e7e7618cb5667ff852efad0d641e47621d Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 12 Jul 2024 16:57:25 +0200 Subject: [PATCH 04/13] adapt to changes in `gitbutler-branch-actions` --- .../gitbutler-tauri/src/virtual_branches.rs | 84 ++++++++----------- crates/gitbutler-tauri/src/window.rs | 2 +- crates/gitbutler-testsupport/src/lib.rs | 7 +- 3 files changed, 39 insertions(+), 54 deletions(-) diff --git a/crates/gitbutler-tauri/src/virtual_branches.rs b/crates/gitbutler-tauri/src/virtual_branches.rs index f454d8adc..f030221f9 100644 --- a/crates/gitbutler-tauri/src/virtual_branches.rs +++ b/crates/gitbutler-tauri/src/virtual_branches.rs @@ -3,10 +3,10 @@ pub mod commands { use anyhow::{anyhow, Context}; use gitbutler_branch::branch::{BranchCreateRequest, BranchId, BranchUpdateRequest}; use gitbutler_branch::ownership::BranchOwnershipClaims; - use gitbutler_branch_actions::base::BaseBranch; - use gitbutler_branch_actions::files::RemoteBranchFile; - use gitbutler_branch_actions::remote::{RemoteBranch, RemoteBranchData}; - use gitbutler_branch_actions::{NameConflitResolution, VirtualBranchActions, VirtualBranches}; + use gitbutler_branch_actions::BaseBranch; + use gitbutler_branch_actions::RemoteBranchFile; + use gitbutler_branch_actions::{NameConflictResolution, VirtualBranchActions, VirtualBranches}; + use gitbutler_branch_actions::{RemoteBranch, RemoteBranchData}; use gitbutler_error::error::Code; use gitbutler_project as projects; use gitbutler_project::ProjectId; @@ -28,7 +28,7 @@ pub mod commands { run_hooks: bool, ) -> Result { let project = handle.state::().get(project_id)?; - let oid = VirtualBranchActions::default() + let oid = VirtualBranchActions .create_commit(&project, branch, message, ownership.as_ref(), run_hooks) .await?; emit_vbranches(&handle, project_id).await; @@ -42,9 +42,8 @@ pub mod commands { project_id: ProjectId, ) -> Result { let project = handle.state::().get(project_id)?; - let (branches, skipped_files) = VirtualBranchActions::default() - .list_virtual_branches(&project) - .await?; + let (branches, skipped_files) = + VirtualBranchActions.list_virtual_branches(&project).await?; Ok(VirtualBranches { branches, @@ -60,7 +59,7 @@ pub mod commands { branch: BranchCreateRequest, ) -> Result { let project = handle.state::().get(project_id)?; - let branch_id = VirtualBranchActions::default() + let branch_id = VirtualBranchActions .create_virtual_branch(&project, &branch) .await?; emit_vbranches(&handle, project_id).await; @@ -75,7 +74,7 @@ pub mod commands { branch: Refname, ) -> Result { let project = handle.state::().get(project_id)?; - let branch_id = VirtualBranchActions::default() + let branch_id = VirtualBranchActions .create_virtual_branch_from_branch(&project, &branch) .await?; emit_vbranches(&handle, project_id).await; @@ -90,7 +89,7 @@ pub mod commands { branch: BranchId, ) -> Result<(), Error> { let project = handle.state::().get(project_id)?; - VirtualBranchActions::default() + VirtualBranchActions .integrate_upstream_commits(&project, branch) .await?; emit_vbranches(&handle, project_id).await; @@ -104,10 +103,7 @@ pub mod commands { project_id: ProjectId, ) -> Result, Error> { let project = handle.state::().get(project_id)?; - if let Ok(base_branch) = VirtualBranchActions::default() - .get_base_branch_data(&project) - .await - { + if let Ok(base_branch) = VirtualBranchActions.get_base_branch_data(&project).await { Ok(Some(base_branch)) } else { Ok(None) @@ -126,13 +122,13 @@ pub mod commands { let branch_name = format!("refs/remotes/{}", branch) .parse() .context("Invalid branch name")?; - let base_branch = VirtualBranchActions::default() + let base_branch = VirtualBranchActions .set_base_branch(&project, &branch_name) .await?; // if they also sent a different push remote, set that too if let Some(push_remote) = push_remote { - VirtualBranchActions::default() + VirtualBranchActions .set_target_push_remote(&project, push_remote) .await?; } @@ -147,9 +143,7 @@ pub mod commands { project_id: ProjectId, ) -> Result, Error> { let project = handle.state::().get(project_id)?; - let unapplied_branches = VirtualBranchActions::default() - .update_base_branch(&project) - .await?; + let unapplied_branches = VirtualBranchActions.update_base_branch(&project).await?; emit_vbranches(&handle, project_id).await; Ok(unapplied_branches) } @@ -162,7 +156,7 @@ pub mod commands { branch: BranchUpdateRequest, ) -> Result<(), Error> { let project = handle.state::().get(project_id)?; - VirtualBranchActions::default() + VirtualBranchActions .update_virtual_branch(&project, branch) .await?; @@ -178,7 +172,7 @@ pub mod commands { branch_id: BranchId, ) -> Result<(), Error> { let project = handle.state::().get(project_id)?; - VirtualBranchActions::default() + VirtualBranchActions .delete_virtual_branch(&project, branch_id) .await?; emit_vbranches(&handle, project_id).await; @@ -191,10 +185,10 @@ pub mod commands { handle: AppHandle, project_id: ProjectId, branch: BranchId, - name_conflict_resolution: NameConflitResolution, + name_conflict_resolution: NameConflictResolution, ) -> Result<(), Error> { let project = handle.state::().get(project_id)?; - VirtualBranchActions::default() + VirtualBranchActions .convert_to_real_branch(&project, branch, name_conflict_resolution) .await?; emit_vbranches(&handle, project_id).await; @@ -209,7 +203,7 @@ pub mod commands { ownership: BranchOwnershipClaims, ) -> Result<(), Error> { let project = handle.state::().get(project_id)?; - VirtualBranchActions::default() + VirtualBranchActions .unapply_ownership(&project, &ownership) .await?; emit_vbranches(&handle, project_id).await; @@ -229,9 +223,7 @@ pub mod commands { .split('\n') .map(std::string::ToString::to_string) .collect::>(); - VirtualBranchActions::default() - .reset_files(&project, &files) - .await?; + VirtualBranchActions.reset_files(&project, &files).await?; emit_vbranches(&handle, project_id).await; Ok(()) } @@ -245,7 +237,7 @@ pub mod commands { with_force: bool, ) -> Result<(), Error> { let project = handle.state::().get(project_id)?; - VirtualBranchActions::default() + VirtualBranchActions .push_virtual_branch(&project, branch_id, with_force, Some(Some(branch_id))) .await .map_err(|err| err.context(Code::Unknown))?; @@ -261,7 +253,7 @@ pub mod commands { branch: RemoteRefname, ) -> Result { let project = handle.state::().get(project_id)?; - Ok(VirtualBranchActions::default() + Ok(VirtualBranchActions .can_apply_remote_branch(&project, &branch) .await?) } @@ -275,7 +267,7 @@ pub mod commands { ) -> Result, Error> { let project = handle.state::().get(project_id)?; let commit_oid = git2::Oid::from_str(&commit_oid).map_err(|e| anyhow!(e))?; - VirtualBranchActions::default() + VirtualBranchActions .list_remote_commit_files(&project, commit_oid) .await .map_err(Into::into) @@ -291,7 +283,7 @@ pub mod commands { ) -> Result<(), Error> { let project = handle.state::().get(project_id)?; let target_commit_oid = git2::Oid::from_str(&target_commit_oid).map_err(|e| anyhow!(e))?; - VirtualBranchActions::default() + VirtualBranchActions .reset_virtual_branch(&project, branch_id, target_commit_oid) .await?; emit_vbranches(&handle, project_id).await; @@ -309,7 +301,7 @@ pub mod commands { ) -> Result { let project = handle.state::().get(project_id)?; let commit_oid = git2::Oid::from_str(&commit_oid).map_err(|e| anyhow!(e))?; - let oid = VirtualBranchActions::default() + let oid = VirtualBranchActions .amend(&project, branch_id, commit_oid, &ownership) .await?; emit_vbranches(&handle, project_id).await; @@ -329,7 +321,7 @@ pub mod commands { let project = handle.state::().get(project_id)?; let from_commit_oid = git2::Oid::from_str(&from_commit_oid).map_err(|e| anyhow!(e))?; let to_commit_oid = git2::Oid::from_str(&to_commit_oid).map_err(|e| anyhow!(e))?; - let oid = VirtualBranchActions::default() + let oid = VirtualBranchActions .move_commit_file( &project, branch_id, @@ -352,7 +344,7 @@ pub mod commands { ) -> Result<(), Error> { let project = handle.state::().get(project_id)?; let commit_oid = git2::Oid::from_str(&commit_oid).map_err(|e| anyhow!(e))?; - VirtualBranchActions::default() + VirtualBranchActions .undo_commit(&project, branch_id, commit_oid) .await?; emit_vbranches(&handle, project_id).await; @@ -370,7 +362,7 @@ pub mod commands { ) -> Result<(), Error> { let project = handle.state::().get(project_id)?; let commit_oid = git2::Oid::from_str(&commit_oid).map_err(|e| anyhow!(e))?; - VirtualBranchActions::default() + VirtualBranchActions .insert_blank_commit(&project, branch_id, commit_oid, offset) .await?; emit_vbranches(&handle, project_id).await; @@ -388,7 +380,7 @@ pub mod commands { ) -> Result<(), Error> { let project = handle.state::().get(project_id)?; let commit_oid = git2::Oid::from_str(&commit_oid).map_err(|e| anyhow!(e))?; - VirtualBranchActions::default() + VirtualBranchActions .reorder_commit(&project, branch_id, commit_oid, offset) .await?; emit_vbranches(&handle, project_id).await; @@ -402,9 +394,7 @@ pub mod commands { project_id: ProjectId, ) -> Result, Error> { let project = handle.state::().get(project_id)?; - let branches = VirtualBranchActions::default() - .list_remote_branches(project) - .await?; + let branches = VirtualBranchActions.list_remote_branches(project).await?; Ok(branches) } @@ -416,7 +406,7 @@ pub mod commands { refname: Refname, ) -> Result { let project = handle.state::().get(project_id)?; - let branch_data = VirtualBranchActions::default() + let branch_data = VirtualBranchActions .get_remote_branch_data(&project, &refname) .await?; Ok(branch_data) @@ -432,7 +422,7 @@ pub mod commands { ) -> Result<(), Error> { let project = handle.state::().get(project_id)?; let target_commit_oid = git2::Oid::from_str(&target_commit_oid).map_err(|e| anyhow!(e))?; - VirtualBranchActions::default() + VirtualBranchActions .squash(&project, branch_id, target_commit_oid) .await?; emit_vbranches(&handle, project_id).await; @@ -449,7 +439,7 @@ pub mod commands { let projects = handle.state::(); let project = projects.get(project_id)?; - let project_data_last_fetched = VirtualBranchActions::default() + let project_data_last_fetched = VirtualBranchActions .fetch_from_remotes( &project, Some(action.unwrap_or_else(|| "unknown".to_string())), @@ -468,9 +458,7 @@ pub mod commands { .await .context("failed to update project with last fetched timestamp")?; - let base_branch = VirtualBranchActions::default() - .get_base_branch_data(&project) - .await?; + let base_branch = VirtualBranchActions.get_base_branch_data(&project).await?; Ok(base_branch) } @@ -484,7 +472,7 @@ pub mod commands { ) -> Result<(), Error> { let project = handle.state::().get(project_id)?; let commit_oid = git2::Oid::from_str(&commit_oid).map_err(|e| anyhow!(e))?; - VirtualBranchActions::default() + VirtualBranchActions .move_commit(&project, target_branch_id, commit_oid) .await?; emit_vbranches(&handle, project_id).await; @@ -502,7 +490,7 @@ pub mod commands { ) -> Result<(), Error> { let project = handle.state::().get(project_id)?; let commit_oid = git2::Oid::from_str(&commit_oid).map_err(|e| anyhow!(e))?; - VirtualBranchActions::default() + VirtualBranchActions .update_commit_message(&project, branch_id, commit_oid, message) .await?; emit_vbranches(&handle, project_id).await; diff --git a/crates/gitbutler-tauri/src/window.rs b/crates/gitbutler-tauri/src/window.rs index befabe4b5..13401d7dd 100644 --- a/crates/gitbutler-tauri/src/window.rs +++ b/crates/gitbutler-tauri/src/window.rs @@ -104,7 +104,7 @@ pub(super) mod state { fn handler_from_app(app: &AppHandle) -> Result { let projects = app.state::().inner().clone(); let users = app.state::().inner().clone(); - let vbranches = gitbutler_branch_actions::VirtualBranchActions::default(); + let vbranches = gitbutler_branch_actions::VirtualBranchActions; Ok(gitbutler_watcher::Handler::new( projects, diff --git a/crates/gitbutler-testsupport/src/lib.rs b/crates/gitbutler-testsupport/src/lib.rs index 9945dabc5..9abaa5c02 100644 --- a/crates/gitbutler-testsupport/src/lib.rs +++ b/crates/gitbutler-testsupport/src/lib.rs @@ -42,11 +42,8 @@ pub mod virtual_branches { }) .expect("failed to write target"); - gitbutler_branch_actions::integration::update_gitbutler_integration( - &vb_state, - project_repository, - ) - .expect("failed to update integration"); + gitbutler_branch_actions::update_gitbutler_integration(&vb_state, project_repository) + .expect("failed to update integration"); Ok(()) } From 4501b5dd51a740eab3c9b68e7c5480174f7dd33a Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 13 Jul 2024 09:56:38 +0200 Subject: [PATCH 05/13] minimize the public API of the `branch` crate Only keep public what needs to be public right now, which always helps with refactoring later. --- crates/gitbutler-branch/src/diff.rs | 2 +- crates/gitbutler-branch/src/file_ownership.rs | 20 +----------- crates/gitbutler-branch/src/hunk.rs | 6 +--- crates/gitbutler-branch/src/ownership.rs | 31 ------------------- crates/gitbutler-branch/src/state.rs | 9 +----- 5 files changed, 4 insertions(+), 64 deletions(-) diff --git a/crates/gitbutler-branch/src/diff.rs b/crates/gitbutler-branch/src/diff.rs index ac7ffc209..94993db8b 100644 --- a/crates/gitbutler-branch/src/diff.rs +++ b/crates/gitbutler-branch/src/diff.rs @@ -94,7 +94,7 @@ impl GitHunk { /// Access impl GitHunk { - pub fn contains(&self, line: u32) -> bool { + pub(crate) fn contains(&self, line: u32) -> bool { self.new_start <= line && self.new_start + self.new_lines >= line } diff --git a/crates/gitbutler-branch/src/file_ownership.rs b/crates/gitbutler-branch/src/file_ownership.rs index 83e80546b..b6263edc4 100644 --- a/crates/gitbutler-branch/src/file_ownership.rs +++ b/crates/gitbutler-branch/src/file_ownership.rs @@ -51,28 +51,10 @@ impl<'a> From<&'a OwnershipClaim> for (&'a Path, &'a [Hunk]) { } impl OwnershipClaim { - pub fn is_full(&self) -> bool { + pub(crate) fn is_full(&self) -> bool { self.hunks.is_empty() } - pub fn contains(&self, another: &OwnershipClaim) -> bool { - if !self.file_path.eq(&another.file_path) { - return false; - } - - if self.hunks.is_empty() { - // full ownership contains any partial ownership - return true; - } - - if another.hunks.is_empty() { - // partial ownership contains no full ownership - return false; - } - - another.hunks.iter().all(|hunk| self.hunks.contains(hunk)) - } - // return a copy of self, with another ranges added pub fn plus(&self, another: OwnershipClaim) -> OwnershipClaim { if self.file_path != another.file_path { diff --git a/crates/gitbutler-branch/src/hunk.rs b/crates/gitbutler-branch/src/hunk.rs index 88c4c2fcd..8a93870a7 100644 --- a/crates/gitbutler-branch/src/hunk.rs +++ b/crates/gitbutler-branch/src/hunk.rs @@ -112,7 +112,7 @@ impl Hunk { self } - pub fn contains(&self, line: u32) -> bool { + pub(crate) fn contains(&self, line: u32) -> bool { self.start <= line && self.end >= line } @@ -123,10 +123,6 @@ impl Hunk { || another.contains(self.end) } - pub fn shallow_eq(&self, other: &diff::GitHunk) -> bool { - self.start == other.new_start && self.end == other.new_start + other.new_lines - } - /// Produce a hash from `diff` as hex-string, which is **assumed to have a one-line diff header**! /// `diff` can also be entirely empty, or not contain a diff header which is when it will just be hashed /// with [`Self::hash()`]. diff --git a/crates/gitbutler-branch/src/ownership.rs b/crates/gitbutler-branch/src/ownership.rs index 0986f9403..ea50ae54b 100644 --- a/crates/gitbutler-branch/src/ownership.rs +++ b/crates/gitbutler-branch/src/ownership.rs @@ -49,37 +49,6 @@ impl FromStr for BranchOwnershipClaims { } impl BranchOwnershipClaims { - pub fn is_empty(&self) -> bool { - self.claims.is_empty() - } - - pub fn contains(&self, another: &BranchOwnershipClaims) -> bool { - if another.is_empty() { - return true; - } - - if self.is_empty() { - return false; - } - - for file_ownership in &another.claims { - let mut found = false; - for self_file_ownership in &self.claims { - if self_file_ownership.file_path == file_ownership.file_path - && self_file_ownership.contains(file_ownership) - { - found = true; - break; - } - } - if !found { - return false; - } - } - - true - } - pub fn put(&mut self, ownership: OwnershipClaim) { let target = self .claims diff --git a/crates/gitbutler-branch/src/state.rs b/crates/gitbutler-branch/src/state.rs index 33654cbe4..a890e3a85 100644 --- a/crates/gitbutler-branch/src/state.rs +++ b/crates/gitbutler-branch/src/state.rs @@ -31,7 +31,7 @@ impl VirtualBranches { /// Lists all virtual branches that are in the user's workspace. /// /// Errors if the file cannot be read or written. - pub fn list_all_branches(&self) -> Result> { + pub(crate) fn list_all_branches(&self) -> Result> { let branches: Vec = self.branches.values().cloned().collect(); Ok(branches) } @@ -202,13 +202,6 @@ impl VirtualBranchesHandle { }) } - /// Checks if the state file exists. - /// - /// This would only be false if the application just updated from a very old verion. - pub fn file_exists(&self) -> bool { - self.file_path.exists() - } - /// Reads and parses the state file. /// /// If the file does not exist, it will be created. From 85b4e564cdc3d9ce99802afe1564161c41faec45 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 13 Jul 2024 10:17:11 +0200 Subject: [PATCH 06/13] simplify module paths of `gitbutler-branch` crate --- crates/gitbutler-branch/src/dedup.rs | 37 +++++++++++-------- crates/gitbutler-branch/src/diff.rs | 2 +- crates/gitbutler-branch/src/lib.rs | 21 +++++++---- crates/gitbutler-branch/src/ownership.rs | 2 +- .../gitbutler-branch/tests/file_ownership.rs | 2 +- crates/gitbutler-branch/tests/hunk.rs | 2 +- crates/gitbutler-branch/tests/ownership.rs | 5 +-- 7 files changed, 40 insertions(+), 31 deletions(-) diff --git a/crates/gitbutler-branch/src/dedup.rs b/crates/gitbutler-branch/src/dedup.rs index 368b221be..73be0ae2e 100644 --- a/crates/gitbutler-branch/src/dedup.rs +++ b/crates/gitbutler-branch/src/dedup.rs @@ -25,21 +25,26 @@ pub fn dedup_fmt(existing: &[&str], new: &str, separator: &str) -> String { ) } -#[test] -fn tests() { - for (existing, new, expected) in [ - (vec!["bar", "baz"], "foo", "foo"), - (vec!["foo", "bar", "baz"], "foo", "foo 1"), - (vec!["foo", "foo 2"], "foo", "foo 3"), - (vec!["foo", "foo 1", "foo 2"], "foo", "foo 3"), - (vec!["foo", "foo 1", "foo 2"], "foo 1", "foo 1 1"), - (vec!["foo", "foo 1", "foo 2"], "foo 2", "foo 2 1"), - (vec!["foo", "foo 1", "foo 2"], "foo 3", "foo 3"), - (vec!["foo 2"], "foo", "foo 3"), - (vec!["foo", "foo 1", "foo 2", "foo 4"], "foo", "foo 5"), - (vec!["foo", "foo 0"], "foo", "foo 1"), - (vec!["foo 0"], "foo", "foo 1"), - ] { - assert_eq!(dedup(&existing, new), expected.to_string()); +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_dedup() { + for (existing, new, expected) in [ + (vec!["bar", "baz"], "foo", "foo"), + (vec!["foo", "bar", "baz"], "foo", "foo 1"), + (vec!["foo", "foo 2"], "foo", "foo 3"), + (vec!["foo", "foo 1", "foo 2"], "foo", "foo 3"), + (vec!["foo", "foo 1", "foo 2"], "foo 1", "foo 1 1"), + (vec!["foo", "foo 1", "foo 2"], "foo 2", "foo 2 1"), + (vec!["foo", "foo 1", "foo 2"], "foo 3", "foo 3"), + (vec!["foo 2"], "foo", "foo 3"), + (vec!["foo", "foo 1", "foo 2", "foo 4"], "foo", "foo 5"), + (vec!["foo", "foo 0"], "foo", "foo 1"), + (vec!["foo 0"], "foo", "foo 1"), + ] { + assert_eq!(dedup(&existing, new), expected); + } } } diff --git a/crates/gitbutler-branch/src/diff.rs b/crates/gitbutler-branch/src/diff.rs index 94993db8b..716bfcd00 100644 --- a/crates/gitbutler-branch/src/diff.rs +++ b/crates/gitbutler-branch/src/diff.rs @@ -9,7 +9,7 @@ use tracing::instrument; use gitbutler_id::id::Id; -use crate::branch::Branch; +use crate::Branch; pub type DiffByPathMap = HashMap; diff --git a/crates/gitbutler-branch/src/lib.rs b/crates/gitbutler-branch/src/lib.rs index 3ae02fd76..68a8fecbb 100644 --- a/crates/gitbutler-branch/src/lib.rs +++ b/crates/gitbutler-branch/src/lib.rs @@ -1,12 +1,19 @@ -pub mod branch; -pub mod branch_ext; -pub mod dedup; +mod branch; +pub use branch::{Branch, BranchCreateRequest, BranchId, BranchUpdateRequest}; +mod branch_ext; +pub use branch_ext::BranchExt; +mod dedup; +pub use dedup::{dedup, dedup_fmt}; pub mod diff; -pub mod file_ownership; -pub mod hunk; -pub mod ownership; +mod file_ownership; +pub use file_ownership::OwnershipClaim; +mod hunk; +pub use hunk::{Hunk, HunkHash}; +mod ownership; +pub use ownership::{reconcile_claims, BranchOwnershipClaims, ClaimOutcome}; pub mod serde; -pub mod target; +mod target; +pub use target::Target; mod state; pub use state::VirtualBranches as VirtualBranchesState; diff --git a/crates/gitbutler-branch/src/ownership.rs b/crates/gitbutler-branch/src/ownership.rs index ea50ae54b..9ba662d02 100644 --- a/crates/gitbutler-branch/src/ownership.rs +++ b/crates/gitbutler-branch/src/ownership.rs @@ -4,7 +4,7 @@ use anyhow::Result; use itertools::Itertools; use serde::{Deserialize, Serialize, Serializer}; -use crate::{branch::Branch, file_ownership::OwnershipClaim}; +use crate::{file_ownership::OwnershipClaim, Branch}; #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct BranchOwnershipClaims { diff --git a/crates/gitbutler-branch/tests/file_ownership.rs b/crates/gitbutler-branch/tests/file_ownership.rs index c954a0ba8..d4e1a2bf5 100644 --- a/crates/gitbutler-branch/tests/file_ownership.rs +++ b/crates/gitbutler-branch/tests/file_ownership.rs @@ -1,4 +1,4 @@ -use gitbutler_branch::file_ownership::OwnershipClaim; +use gitbutler_branch::OwnershipClaim; #[test] fn parse_ownership() { diff --git a/crates/gitbutler-branch/tests/hunk.rs b/crates/gitbutler-branch/tests/hunk.rs index e208fec28..0b255e506 100644 --- a/crates/gitbutler-branch/tests/hunk.rs +++ b/crates/gitbutler-branch/tests/hunk.rs @@ -1,4 +1,4 @@ -use gitbutler_branch::hunk::Hunk; +use gitbutler_branch::Hunk; #[test] fn to_from_string() { diff --git a/crates/gitbutler-branch/tests/ownership.rs b/crates/gitbutler-branch/tests/ownership.rs index 726b3df94..cef7b81e4 100644 --- a/crates/gitbutler-branch/tests/ownership.rs +++ b/crates/gitbutler-branch/tests/ownership.rs @@ -1,10 +1,7 @@ use std::{path::PathBuf, vec}; use gitbutler_branch::{ - branch::{Branch, BranchId}, - file_ownership::OwnershipClaim, - hunk::Hunk, - ownership::{reconcile_claims, BranchOwnershipClaims}, + Hunk, OwnershipClaim, {reconcile_claims, BranchOwnershipClaims}, {Branch, BranchId}, }; #[test] From f48d0e274613208206018a5382aa8ad1f3bf9b89 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 13 Jul 2024 10:38:56 +0200 Subject: [PATCH 07/13] adapt to changes in `gitbutler-branch` crate --- Cargo.lock | 4 +- .../gitbutler-branch-actions/src/actions.rs | 4 +- crates/gitbutler-branch-actions/src/base.rs | 299 +++++++++--------- .../src/branch_manager/branch_creation.rs | 13 +- .../src/branch_manager/branch_removal.rs | 9 +- .../src/integration.rs | 8 +- crates/gitbutler-branch-actions/src/remote.rs | 6 +- .../gitbutler-branch-actions/src/virtual.rs | 40 +-- .../tests/extra/mod.rs | 4 +- .../tests/virtual_branches/amend.rs | 15 +- .../virtual_branches/apply_virtual_branch.rs | 5 +- .../convert_to_real_branch.rs | 3 +- .../tests/virtual_branches/create_commit.rs | 10 +- .../create_virtual_branch_from_branch.rs | 3 +- .../virtual_branches/delete_virtual_branch.rs | 3 +- .../virtual_branches/insert_blank_commit.rs | 5 +- .../tests/virtual_branches/mod.rs | 7 +- .../virtual_branches/move_commit_file.rs | 11 +- .../move_commit_to_vbranch.rs | 2 +- .../tests/virtual_branches/oplog.rs | 10 +- .../tests/virtual_branches/references.rs | 26 +- .../tests/virtual_branches/reorder_commit.rs | 5 +- .../virtual_branches/reset_virtual_branch.rs | 2 +- .../virtual_branches/selected_for_changes.rs | 31 +- .../tests/virtual_branches/set_base_branch.rs | 6 +- .../tests/virtual_branches/squash.rs | 13 +- .../virtual_branches/unapply_ownership.rs | 2 +- .../tests/virtual_branches/undo_commit.rs | 3 +- .../virtual_branches/update_base_branch.rs | 37 +-- .../virtual_branches/update_commit_message.rs | 15 +- .../tests/virtual_branches/upstream.rs | 5 +- crates/gitbutler-oplog/src/oplog.rs | 3 +- crates/gitbutler-oplog/src/snapshot.rs | 2 +- crates/gitbutler-repo/src/askpass.rs | 5 +- crates/gitbutler-repo/src/repository.rs | 2 +- crates/gitbutler-sync/src/cloud.rs | 2 +- crates/gitbutler-tauri/src/app.rs | 2 +- .../gitbutler-tauri/src/virtual_branches.rs | 4 +- crates/gitbutler-testsupport/src/lib.rs | 2 +- 39 files changed, 307 insertions(+), 321 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 68e2452d2..9962849ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7195,9 +7195,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.9.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ "getrandom 0.2.15", "rand 0.8.5", diff --git a/crates/gitbutler-branch-actions/src/actions.rs b/crates/gitbutler-branch-actions/src/actions.rs index e06182545..f58595198 100644 --- a/crates/gitbutler-branch-actions/src/actions.rs +++ b/crates/gitbutler-branch-actions/src/actions.rs @@ -1,8 +1,6 @@ use anyhow::Result; use gitbutler_branch::{ - branch::{BranchCreateRequest, BranchId, BranchUpdateRequest}, - diff, - ownership::BranchOwnershipClaims, + diff, BranchOwnershipClaims, {BranchCreateRequest, BranchId, BranchUpdateRequest}, }; use gitbutler_command_context::ProjectRepository; use gitbutler_oplog::{ diff --git a/crates/gitbutler-branch-actions/src/base.rs b/crates/gitbutler-branch-actions/src/base.rs index 10e47ff27..aecd5da1e 100644 --- a/crates/gitbutler-branch-actions/src/base.rs +++ b/crates/gitbutler-branch-actions/src/base.rs @@ -2,11 +2,11 @@ use std::{path::Path, time}; use anyhow::{anyhow, Context, Result}; use git2::Index; -use gitbutler_branch::branch::{self, BranchId}; -use gitbutler_branch::diff; -use gitbutler_branch::ownership::BranchOwnershipClaims; -use gitbutler_branch::target::Target; +use gitbutler_branch::BranchOwnershipClaims; +use gitbutler_branch::Target; use gitbutler_branch::VirtualBranchesHandle; +use gitbutler_branch::{self, BranchId}; +use gitbutler_branch::{diff, Branch}; use gitbutler_command_context::ProjectRepository; use gitbutler_project::FetchResult; use gitbutler_reference::ReferenceName; @@ -234,7 +234,7 @@ pub(crate) fn set_base_branch( (None, None) }; - let branch = branch::Branch { + let branch = Branch { id: BranchId::generate(), name: head_name.to_string().replace("refs/heads/", ""), notes: String::new(), @@ -369,168 +369,161 @@ pub(crate) fn update_base_branch( .0 .into_iter() .map(|(branch, _)| branch) - .map( - |mut branch: branch::Branch| -> Result> { - let branch_tree = repo.find_tree(branch.tree)?; + .map(|mut branch: Branch| -> Result> { + let branch_tree = repo.find_tree(branch.tree)?; - let branch_head_commit = repo.find_commit(branch.head).context(format!( - "failed to find commit {} for branch {}", - branch.head, branch.id - ))?; - let branch_head_tree = branch_head_commit.tree().context(format!( - "failed to find tree for commit {} for branch {}", - branch.head, branch.id + let branch_head_commit = repo.find_commit(branch.head).context(format!( + "failed to find commit {} for branch {}", + branch.head, branch.id + ))?; + let branch_head_tree = branch_head_commit.tree().context(format!( + "failed to find tree for commit {} for branch {}", + branch.head, branch.id + ))?; + + let result_integrated_detected = |mut branch: Branch| -> Result> { + // branch head tree is the same as the new target tree. + // meaning we can safely use the new target commit as the branch head. + + branch.head = new_target_commit.id(); + + // it also means that the branch is fully integrated into the target. + // disconnect it from the upstream + branch.upstream = None; + branch.upstream_head = None; + + let non_commited_files = + diff::trees(project_repository.repo(), &branch_head_tree, &branch_tree)?; + if non_commited_files.is_empty() { + // if there are no commited files, then the branch is fully merged + // and we can delete it. + vb_state.mark_as_not_in_workspace(branch.id)?; + project_repository.delete_branch_reference(&branch)?; + Ok(None) + } else { + vb_state.set_branch(branch.clone())?; + Ok(Some(branch)) + } + }; + + if branch_head_tree.id() == new_target_tree.id() { + return result_integrated_detected(branch); + } + + // try to merge branch head with new target + let mut branch_tree_merge_index = repo + .merge_trees(&old_target_tree, &branch_tree, &new_target_tree, None) + .context(format!("failed to merge trees for branch {}", branch.id))?; + + 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())?; + + unapplied_branch_names.push(unapplied_real_branch); + + return Ok(None); + } + + let branch_merge_index_tree_oid = + branch_tree_merge_index.write_tree_to(project_repository.repo())?; + + if branch_merge_index_tree_oid == new_target_tree.id() { + return result_integrated_detected(branch); + } + + if branch.head == target.sha { + // there are no commits on the branch, so we can just update the head to the new target and calculate the new tree + branch.head = new_target_commit.id(); + branch.tree = branch_merge_index_tree_oid; + vb_state.set_branch(branch.clone())?; + return Ok(Some(branch)); + } + + let mut branch_head_merge_index = repo + .merge_trees(&old_target_tree, &branch_head_tree, &new_target_tree, None) + .context(format!( + "failed to merge head tree for branch {}", + branch.id ))?; - let result_integrated_detected = - |mut branch: branch::Branch| -> Result> { - // branch head tree is the same as the new target tree. - // meaning we can safely use the new target commit as the branch head. + if branch_head_merge_index.has_conflicts() { + // 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())?; + unapplied_branch_names.push(unapplied_real_branch); - branch.head = new_target_commit.id(); + return Ok(None); + } - // it also means that the branch is fully integrated into the target. - // disconnect it from the upstream - branch.upstream = None; - branch.upstream_head = None; + // branch commits do not conflict with new target, so lets merge them + let branch_head_merge_tree_oid = branch_head_merge_index + .write_tree_to(project_repository.repo()) + .context(format!( + "failed to write head merge index for {}", + branch.id + ))?; - let non_commited_files = diff::trees( - project_repository.repo(), - &branch_head_tree, - &branch_tree, - )?; - if non_commited_files.is_empty() { - // if there are no commited files, then the branch is fully merged - // and we can delete it. - vb_state.mark_as_not_in_workspace(branch.id)?; - project_repository.delete_branch_reference(&branch)?; - Ok(None) - } else { - vb_state.set_branch(branch.clone())?; - Ok(Some(branch)) - } - }; + let ok_with_force_push = branch.allow_rebasing; - if branch_head_tree.id() == new_target_tree.id() { - return result_integrated_detected(branch); - } + let result_merge = |mut branch: Branch| -> Result> { + // branch was pushed to upstream, and user doesn't like force pushing. + // create a merge commit to avoid the need of force pushing then. + let branch_head_merge_tree = repo + .find_tree(branch_head_merge_tree_oid) + .context("failed to find tree")?; - // try to merge branch head with new target - let mut branch_tree_merge_index = repo - .merge_trees(&old_target_tree, &branch_tree, &new_target_tree, None) - .context(format!("failed to merge trees for branch {}", branch.id))?; + let new_target_head = project_repository + .commit( + format!( + "Merged {}/{} into {}", + target.branch.remote(), + target.branch.branch(), + branch.name, + ) + .as_str(), + &branch_head_merge_tree, + &[&branch_head_commit, &new_target_commit], + None, + ) + .context("failed to commit merge")?; - 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())?; + branch.head = new_target_head; + branch.tree = branch_merge_index_tree_oid; + vb_state.set_branch(branch.clone())?; + Ok(Some(branch)) + }; - unapplied_branch_names.push(unapplied_real_branch); + if branch.upstream.is_some() && !ok_with_force_push { + return result_merge(branch); + } - return Ok(None); - } + // branch was not pushed to upstream yet. attempt a rebase, + let rebased_head_oid = cherry_rebase( + project_repository, + new_target_commit.id(), + new_target_commit.id(), + branch.head, + ); - let branch_merge_index_tree_oid = - branch_tree_merge_index.write_tree_to(project_repository.repo())?; + // rebase failed, just do the merge + if rebased_head_oid.is_err() { + return result_merge(branch); + } - if branch_merge_index_tree_oid == new_target_tree.id() { - return result_integrated_detected(branch); - } + if let Some(rebased_head_oid) = rebased_head_oid? { + // rebase worked out, rewrite the branch head + branch.head = rebased_head_oid; + branch.tree = branch_merge_index_tree_oid; + vb_state.set_branch(branch.clone())?; + return Ok(Some(branch)); + } - if branch.head == target.sha { - // there are no commits on the branch, so we can just update the head to the new target and calculate the new tree - branch.head = new_target_commit.id(); - branch.tree = branch_merge_index_tree_oid; - vb_state.set_branch(branch.clone())?; - return Ok(Some(branch)); - } - - let mut branch_head_merge_index = repo - .merge_trees(&old_target_tree, &branch_head_tree, &new_target_tree, None) - .context(format!( - "failed to merge head tree for branch {}", - branch.id - ))?; - - if branch_head_merge_index.has_conflicts() { - // 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())?; - unapplied_branch_names.push(unapplied_real_branch); - - return Ok(None); - } - - // branch commits do not conflict with new target, so lets merge them - let branch_head_merge_tree_oid = branch_head_merge_index - .write_tree_to(project_repository.repo()) - .context(format!( - "failed to write head merge index for {}", - branch.id - ))?; - - let ok_with_force_push = branch.allow_rebasing; - - let result_merge = - |mut branch: branch::Branch| -> Result> { - // branch was pushed to upstream, and user doesn't like force pushing. - // create a merge commit to avoid the need of force pushing then. - let branch_head_merge_tree = repo - .find_tree(branch_head_merge_tree_oid) - .context("failed to find tree")?; - - let new_target_head = project_repository - .commit( - format!( - "Merged {}/{} into {}", - target.branch.remote(), - target.branch.branch(), - branch.name, - ) - .as_str(), - &branch_head_merge_tree, - &[&branch_head_commit, &new_target_commit], - None, - ) - .context("failed to commit merge")?; - - branch.head = new_target_head; - branch.tree = branch_merge_index_tree_oid; - vb_state.set_branch(branch.clone())?; - Ok(Some(branch)) - }; - - if branch.upstream.is_some() && !ok_with_force_push { - return result_merge(branch); - } - - // branch was not pushed to upstream yet. attempt a rebase, - let rebased_head_oid = cherry_rebase( - project_repository, - new_target_commit.id(), - new_target_commit.id(), - branch.head, - ); - - // rebase failed, just do the merge - if rebased_head_oid.is_err() { - return result_merge(branch); - } - - if let Some(rebased_head_oid) = rebased_head_oid? { - // rebase worked out, rewrite the branch head - branch.head = rebased_head_oid; - branch.tree = branch_merge_index_tree_oid; - vb_state.set_branch(branch.clone())?; - return Ok(Some(branch)); - } - - result_merge(branch) - }, - ) + result_merge(branch) + }) .collect::>>()? .into_iter() .flatten() diff --git a/crates/gitbutler-branch-actions/src/branch_manager/branch_creation.rs b/crates/gitbutler-branch-actions/src/branch_manager/branch_creation.rs index 6a393e40e..6567bcdeb 100644 --- a/crates/gitbutler-branch-actions/src/branch_manager/branch_creation.rs +++ b/crates/gitbutler-branch-actions/src/branch_manager/branch_creation.rs @@ -7,10 +7,7 @@ use crate::{ }; use anyhow::{anyhow, bail, Context, Result}; use gitbutler_branch::{ - branch::{self, BranchCreateRequest, BranchId}, - dedup::dedup, - diff, - ownership::BranchOwnershipClaims, + dedup, diff, Branch, BranchOwnershipClaims, {self, BranchCreateRequest, BranchId}, }; use gitbutler_commit::commit_headers::HasCommitHeaders; use gitbutler_error::error::Marker; @@ -20,7 +17,7 @@ use gitbutler_repo::{rebase::cherry_rebase, RepoActions, RepositoryExt}; use gitbutler_time::time::now_since_unix_epoch_ms; impl BranchManager<'_> { - pub fn create_virtual_branch(&self, create: &BranchCreateRequest) -> Result { + pub fn create_virtual_branch(&self, create: &BranchCreateRequest) -> Result { let vb_state = self.project_repository.project().virtual_branches(); let default_target = vb_state.get_default_target()?; @@ -91,7 +88,7 @@ impl BranchManager<'_> { let now = gitbutler_time::time::now_ms(); - let mut branch = branch::Branch { + let mut branch = Branch { id: BranchId::generate(), name: name.clone(), notes: String::new(), @@ -170,7 +167,7 @@ impl BranchManager<'_> { .list_branches_in_workspace() .context("failed to read virtual branches")? .into_iter() - .collect::>(); + .collect::>(); let order = vb_state.next_order_index()?; @@ -228,7 +225,7 @@ impl BranchManager<'_> { branch } else { - branch::Branch { + Branch { id: BranchId::generate(), name: branch_name.clone(), notes: String::new(), diff --git a/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs b/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs index dacedbc3b..fc3a49cc7 100644 --- a/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs +++ b/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs @@ -6,10 +6,7 @@ use crate::{ }; use anyhow::{anyhow, Context, Result}; use git2::build::TreeUpdateBuilder; -use gitbutler_branch::{ - branch::{self, BranchId}, - branch_ext::BranchExt, -}; +use gitbutler_branch::{Branch, BranchExt, BranchId}; use gitbutler_commit::commit_headers::CommitHeadersV2; use gitbutler_oplog::snapshot::Snapshot; use gitbutler_reference::ReferenceName; @@ -124,7 +121,7 @@ impl BranchManager<'_> { impl BranchManager<'_> { fn build_real_branch( &self, - vbranch: &mut branch::Branch, + vbranch: &mut Branch, name_conflict_resolution: NameConflictResolution, ) -> Result> { let repo = self.project_repository.repo(); @@ -179,7 +176,7 @@ impl BranchManager<'_> { fn build_metadata_commit( &self, - vbranch: &mut branch::Branch, + vbranch: &mut Branch, branch: &git2::Branch<'_>, ) -> Result { let repo = self.project_repository.repo(); diff --git a/crates/gitbutler-branch-actions/src/integration.rs b/crates/gitbutler-branch-actions/src/integration.rs index 93ada3ff9..e19dcc7af 100644 --- a/crates/gitbutler-branch-actions/src/integration.rs +++ b/crates/gitbutler-branch-actions/src/integration.rs @@ -3,8 +3,8 @@ use std::{path::PathBuf, vec}; use anyhow::{anyhow, bail, Context, Result}; use bstr::ByteSlice; -use gitbutler_branch::branch::{self, BranchCreateRequest}; -use gitbutler_branch::VirtualBranchesHandle; +use gitbutler_branch::{self, BranchCreateRequest}; +use gitbutler_branch::{Branch, VirtualBranchesHandle}; use gitbutler_branch::{ GITBUTLER_INTEGRATION_COMMIT_AUTHOR_EMAIL, GITBUTLER_INTEGRATION_COMMIT_AUTHOR_NAME, GITBUTLER_INTEGRATION_REFERENCE, @@ -40,7 +40,7 @@ pub(crate) fn get_workspace_head( let repo: &git2::Repository = project_repo.repo(); let vb_state = project_repo.project().virtual_branches(); - let virtual_branches: Vec = vb_state.list_branches_in_workspace()?; + let virtual_branches: Vec = vb_state.list_branches_in_workspace()?; let target_commit = repo.find_commit(target.sha)?; let mut workspace_tree = target_commit.tree()?; @@ -166,7 +166,7 @@ pub fn update_gitbutler_integration( let vb_state = project_repository.project().virtual_branches(); // get all virtual branches, we need to try to update them all - let virtual_branches: Vec = vb_state + let virtual_branches: Vec = vb_state .list_branches_in_workspace() .context("failed to list virtual branches")?; diff --git a/crates/gitbutler-branch-actions/src/remote.rs b/crates/gitbutler-branch-actions/src/remote.rs index 2120460bc..3a889ab11 100644 --- a/crates/gitbutler-branch-actions/src/remote.rs +++ b/crates/gitbutler-branch-actions/src/remote.rs @@ -2,15 +2,13 @@ use std::path::Path; use anyhow::{Context, Result}; use bstr::BString; -use gitbutler_branch::VirtualBranchesHandle; +use gitbutler_branch::{Target, VirtualBranchesHandle}; use gitbutler_command_context::ProjectRepository; use gitbutler_commit::commit_ext::CommitExt; use gitbutler_reference::{Refname, RemoteRefname}; use gitbutler_repo::{LogUntil, RepoActions, RepositoryExt}; use serde::Serialize; -use gitbutler_branch::target; - use crate::author::Author; // this struct is a mapping to the view `RemoteBranch` type in Typescript @@ -198,6 +196,6 @@ pub(crate) fn commit_to_remote_commit(commit: &git2::Commit) -> RemoteCommit { } } -fn default_target(base_path: &Path) -> Result { +fn default_target(base_path: &Path) -> Result { VirtualBranchesHandle::new(base_path).get_default_target() } diff --git a/crates/gitbutler-branch-actions/src/virtual.rs b/crates/gitbutler-branch-actions/src/virtual.rs index 1e786b1d0..ffda8c056 100644 --- a/crates/gitbutler-branch-actions/src/virtual.rs +++ b/crates/gitbutler-branch-actions/src/virtual.rs @@ -1,10 +1,9 @@ -use gitbutler_branch::branch::{self, Branch, BranchCreateRequest, BranchId}; -use gitbutler_branch::dedup::{dedup, dedup_fmt}; use gitbutler_branch::diff::{self, diff_files_into_hunks, trees, FileDiff, GitHunk}; -use gitbutler_branch::file_ownership::OwnershipClaim; -use gitbutler_branch::hunk::{Hunk, HunkHash}; -use gitbutler_branch::ownership::{reconcile_claims, BranchOwnershipClaims}; -use gitbutler_branch::VirtualBranchesHandle; +use gitbutler_branch::{dedup, BranchUpdateRequest, VirtualBranchesHandle}; +use gitbutler_branch::{dedup_fmt, Branch, BranchCreateRequest, BranchId}; +use gitbutler_branch::{reconcile_claims, BranchOwnershipClaims}; +use gitbutler_branch::{Hunk, HunkHash}; +use gitbutler_branch::{OwnershipClaim, Target}; use gitbutler_command_context::ProjectRepository; use gitbutler_commit::commit_ext::CommitExt; use gitbutler_commit::commit_headers::HasCommitHeaders; @@ -34,13 +33,12 @@ use crate::conflicts::{self, RepoConflictsExt}; use crate::integration::get_workspace_head; use crate::remote::{branch_to_remote_branch, RemoteBranch}; use crate::VirtualBranchesExt; -use gitbutler_branch::target; use gitbutler_error::error::Code; use gitbutler_error::error::Marker; use gitbutler_repo::rebase::{cherry_rebase, cherry_rebase_group}; use gitbutler_time::time::now_since_unix_epoch_ms; -type AppliedStatuses = Vec<(branch::Branch, BranchStatus)>; +type AppliedStatuses = Vec<(Branch, BranchStatus)>; // this struct is a mapping to the view `Branch` type in Typescript // found in src-tauri/src/routes/repo/[project_id]/types.ts @@ -543,10 +541,7 @@ fn joined(start_a: u32, end_a: u32, start_b: u32, end_b: u32) -> bool { || ((start_b >= start_a && start_b <= end_a) || (end_b >= start_a && end_b <= end_a)) } -fn is_requires_force( - project_repository: &ProjectRepository, - branch: &branch::Branch, -) -> Result { +fn is_requires_force(project_repository: &ProjectRepository, branch: &Branch) -> Result { let upstream = if let Some(upstream) = &branch.upstream { upstream } else { @@ -594,7 +589,7 @@ fn list_virtual_commit_files( fn commit_to_vbranch_commit( repository: &ProjectRepository, - branch: &branch::Branch, + branch: &Branch, commit: &git2::Commit, is_integrated: bool, is_remote: bool, @@ -853,8 +848,8 @@ pub(crate) fn integrate_with_merge( pub fn update_branch( project_repository: &ProjectRepository, - branch_update: &branch::BranchUpdateRequest, -) -> Result { + branch_update: &BranchUpdateRequest, +) -> Result { let vb_state = project_repository.project().virtual_branches(); let mut branch = vb_state.get_branch_in_workspace(branch_update.id)?; @@ -957,8 +952,8 @@ pub(crate) fn ensure_selected_for_changes(vb_state: &VirtualBranchesHandle) -> R pub(crate) fn set_ownership( vb_state: &VirtualBranchesHandle, - target_branch: &mut branch::Branch, - ownership: &gitbutler_branch::ownership::BranchOwnershipClaims, + target_branch: &mut Branch, + ownership: &BranchOwnershipClaims, ) -> Result<()> { if target_branch.ownership.eq(ownership) { // nothing to update @@ -1071,7 +1066,7 @@ pub fn get_status_by_branch( fn new_compute_locks( repository: &git2::Repository, unstaged_hunks_by_path: &HashMap>, - virtual_branches: &[branch::Branch], + virtual_branches: &[Branch], ) -> Result>> { // If we cant find the integration commit and subsequently the target commit, we can't find any locks let target_tree = repository.target_commit()?.tree()?; @@ -1096,8 +1091,7 @@ fn new_compute_locks( }) .collect::>(); - let mut integration_hunks_by_path = - HashMap::>::new(); + let mut integration_hunks_by_path = HashMap::>::new(); for (branch, hunks_by_filepath) in branch_path_diffs { for (path, hunks) in hunks_by_filepath { @@ -1146,7 +1140,7 @@ fn new_compute_locks( pub(crate) fn get_applied_status( project_repository: &ProjectRepository, integration_commit: &git2::Oid, - mut virtual_branches: Vec, + mut virtual_branches: Vec, ) -> Result<(AppliedStatuses, Vec)> { let base_file_diffs = diff::workdir(project_repository.repo(), &integration_commit.to_owned()) .context("failed to diff workdir")?; @@ -1601,7 +1595,7 @@ pub fn commit( project_repository: &ProjectRepository, branch_id: BranchId, message: &str, - ownership: Option<&gitbutler_branch::ownership::BranchOwnershipClaims>, + ownership: Option<&BranchOwnershipClaims>, run_hooks: bool, ) -> Result { let mut message_buffer = message.to_owned(); @@ -1787,7 +1781,7 @@ pub(crate) fn push( fn is_commit_integrated( project_repository: &ProjectRepository, - target: &target::Target, + target: &Target, commit: &git2::Commit, ) -> Result { let remote_branch = project_repository diff --git a/crates/gitbutler-branch-actions/tests/extra/mod.rs b/crates/gitbutler-branch-actions/tests/extra/mod.rs index 9285116f4..bfb694adb 100644 --- a/crates/gitbutler-branch-actions/tests/extra/mod.rs +++ b/crates/gitbutler-branch-actions/tests/extra/mod.rs @@ -14,9 +14,7 @@ use anyhow::{Context, Result}; use git2::TreeEntry; use gitbutler_branch::VirtualBranchesHandle; use gitbutler_branch::{ - branch::{BranchCreateRequest, BranchUpdateRequest}, - ownership::BranchOwnershipClaims, - target::Target, + BranchOwnershipClaims, Target, {BranchCreateRequest, BranchUpdateRequest}, }; use gitbutler_branch_actions::BranchManagerExt; use gitbutler_branch_actions::{ diff --git a/crates/gitbutler-branch-actions/tests/virtual_branches/amend.rs b/crates/gitbutler-branch-actions/tests/virtual_branches/amend.rs index c43613770..7db358b00 100644 --- a/crates/gitbutler-branch-actions/tests/virtual_branches/amend.rs +++ b/crates/gitbutler-branch-actions/tests/virtual_branches/amend.rs @@ -1,4 +1,5 @@ -use gitbutler_branch::ownership::BranchOwnershipClaims; +use gitbutler_branch::BranchOwnershipClaims; +use gitbutler_branch::{BranchCreateRequest, BranchUpdateRequest}; use super::*; @@ -35,7 +36,7 @@ async fn forcepush_allowed() { .unwrap(); let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -90,14 +91,14 @@ async fn forcepush_forbidden() { .unwrap(); let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); controller .update_virtual_branch( project, - branch::BranchUpdateRequest { + BranchUpdateRequest { id: branch_id, allow_rebasing: Some(false), ..Default::default() @@ -147,7 +148,7 @@ async fn non_locked_hunk() { .unwrap(); let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -208,7 +209,7 @@ async fn locked_hunk() { .unwrap(); let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -278,7 +279,7 @@ async fn non_existing_ownership() { .unwrap(); let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); diff --git a/crates/gitbutler-branch-actions/tests/virtual_branches/apply_virtual_branch.rs b/crates/gitbutler-branch-actions/tests/virtual_branches/apply_virtual_branch.rs index a1a09935e..50dff5c3d 100644 --- a/crates/gitbutler-branch-actions/tests/virtual_branches/apply_virtual_branch.rs +++ b/crates/gitbutler-branch-actions/tests/virtual_branches/apply_virtual_branch.rs @@ -1,3 +1,4 @@ +use gitbutler_branch::BranchCreateRequest; use gitbutler_reference::Refname; use super::*; @@ -30,7 +31,7 @@ async fn rebase_commit() { let mut branch1_id = { // create a branch with some commited work let branch1_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); fs::write(repository.path().join("another_file.txt"), "virtual").unwrap(); @@ -145,7 +146,7 @@ async fn rebase_work() { let mut branch1_id = { // make a branch with some work let branch1_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); fs::write(repository.path().join("another_file.txt"), "").unwrap(); diff --git a/crates/gitbutler-branch-actions/tests/virtual_branches/convert_to_real_branch.rs b/crates/gitbutler-branch-actions/tests/virtual_branches/convert_to_real_branch.rs index 474e22022..1b7988a92 100644 --- a/crates/gitbutler-branch-actions/tests/virtual_branches/convert_to_real_branch.rs +++ b/crates/gitbutler-branch-actions/tests/virtual_branches/convert_to_real_branch.rs @@ -1,3 +1,4 @@ +use gitbutler_branch::BranchCreateRequest; use gitbutler_reference::Refname; use super::*; @@ -142,7 +143,7 @@ async fn delete_if_empty() { .unwrap(); controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); diff --git a/crates/gitbutler-branch-actions/tests/virtual_branches/create_commit.rs b/crates/gitbutler-branch-actions/tests/virtual_branches/create_commit.rs index e801660b9..b38ba2e30 100644 --- a/crates/gitbutler-branch-actions/tests/virtual_branches/create_commit.rs +++ b/crates/gitbutler-branch-actions/tests/virtual_branches/create_commit.rs @@ -1,4 +1,4 @@ -use gitbutler_branch::branch::Branch; +use gitbutler_branch::{Branch, BranchCreateRequest, BranchUpdateRequest}; use gitbutler_branch_actions::VirtualBranch; use gitbutler_id::id::Id; @@ -19,7 +19,7 @@ async fn should_lock_updated_hunks() { .unwrap(); let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -76,14 +76,14 @@ async fn should_reset_into_same_branch() { .unwrap(); controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); let branch_2_id = controller .create_virtual_branch( project, - &branch::BranchCreateRequest { + &BranchCreateRequest { selected_for_changes: Some(true), ..Default::default() }, @@ -108,7 +108,7 @@ async fn should_reset_into_same_branch() { controller .update_virtual_branch( project, - branch::BranchUpdateRequest { + BranchUpdateRequest { id: branch_2_id, selected_for_changes: Some(true), ..Default::default() diff --git a/crates/gitbutler-branch-actions/tests/virtual_branches/create_virtual_branch_from_branch.rs b/crates/gitbutler-branch-actions/tests/virtual_branches/create_virtual_branch_from_branch.rs index 4cc9ae2e2..ac2fb9f4c 100644 --- a/crates/gitbutler-branch-actions/tests/virtual_branches/create_virtual_branch_from_branch.rs +++ b/crates/gitbutler-branch-actions/tests/virtual_branches/create_virtual_branch_from_branch.rs @@ -1,3 +1,4 @@ +use gitbutler_branch::BranchCreateRequest; use gitbutler_reference::LocalRefname; use super::*; @@ -20,7 +21,7 @@ async fn integration() { // make a remote branch let branch_id = controller - .create_virtual_branch(project, &super::branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); diff --git a/crates/gitbutler-branch-actions/tests/virtual_branches/delete_virtual_branch.rs b/crates/gitbutler-branch-actions/tests/virtual_branches/delete_virtual_branch.rs index 32ea6c06f..54d68adce 100644 --- a/crates/gitbutler-branch-actions/tests/virtual_branches/delete_virtual_branch.rs +++ b/crates/gitbutler-branch-actions/tests/virtual_branches/delete_virtual_branch.rs @@ -1,4 +1,5 @@ use super::*; +use gitbutler_branch::BranchCreateRequest; #[tokio::test] async fn should_unapply_diff() { @@ -53,7 +54,7 @@ async fn should_remove_reference() { let id = controller .create_virtual_branch( project, - &branch::BranchCreateRequest { + &BranchCreateRequest { name: Some("name".to_string()), ..Default::default() }, diff --git a/crates/gitbutler-branch-actions/tests/virtual_branches/insert_blank_commit.rs b/crates/gitbutler-branch-actions/tests/virtual_branches/insert_blank_commit.rs index 21236a6e2..6460155d3 100644 --- a/crates/gitbutler-branch-actions/tests/virtual_branches/insert_blank_commit.rs +++ b/crates/gitbutler-branch-actions/tests/virtual_branches/insert_blank_commit.rs @@ -1,4 +1,5 @@ use super::*; +use gitbutler_branch::BranchCreateRequest; #[tokio::test] async fn insert_blank_commit_down() { @@ -15,7 +16,7 @@ async fn insert_blank_commit_down() { .unwrap(); let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -87,7 +88,7 @@ async fn insert_blank_commit_up() { .unwrap(); let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); diff --git a/crates/gitbutler-branch-actions/tests/virtual_branches/mod.rs b/crates/gitbutler-branch-actions/tests/virtual_branches/mod.rs index aafb73e47..e624f2886 100644 --- a/crates/gitbutler-branch-actions/tests/virtual_branches/mod.rs +++ b/crates/gitbutler-branch-actions/tests/virtual_branches/mod.rs @@ -1,14 +1,13 @@ use std::path::PathBuf; use std::{fs, path, str::FromStr}; -use gitbutler_branch::branch; +use gitbutler_branch::BranchCreateRequest; use gitbutler_branch_actions::VirtualBranchActions; use gitbutler_error::error::Marker; use gitbutler_project::{self as projects, Project, ProjectId}; use gitbutler_reference::Refname; -use tempfile::TempDir; - use gitbutler_testsupport::{paths, TestProject, VAR_NO_CLEANUP}; +use tempfile::TempDir; struct Test { repository: TestProject, @@ -111,7 +110,7 @@ async fn resolve_conflict_flow() { { // make a branch that conflicts with the remote branch, but doesn't know about it yet let branch1_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); fs::write(repository.path().join("file.txt"), "conflict").unwrap(); diff --git a/crates/gitbutler-branch-actions/tests/virtual_branches/move_commit_file.rs b/crates/gitbutler-branch-actions/tests/virtual_branches/move_commit_file.rs index f8f16814c..513abfeff 100644 --- a/crates/gitbutler-branch-actions/tests/virtual_branches/move_commit_file.rs +++ b/crates/gitbutler-branch-actions/tests/virtual_branches/move_commit_file.rs @@ -1,4 +1,5 @@ -use gitbutler_branch::ownership::BranchOwnershipClaims; +use gitbutler_branch::BranchCreateRequest; +use gitbutler_branch::BranchOwnershipClaims; use gitbutler_commit::commit_ext::CommitExt; use super::*; @@ -18,7 +19,7 @@ async fn move_file_down() { .unwrap(); let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -82,7 +83,7 @@ async fn move_file_up() { .unwrap(); let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -141,7 +142,7 @@ async fn move_file_up_overlapping_hunks() { .unwrap(); let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -180,7 +181,7 @@ async fn move_file_up_overlapping_hunks() { .unwrap(); // move one line from middle commit two up to middle commit one - let to_amend: branch::BranchOwnershipClaims = "file2.txt:1-6".parse().unwrap(); + let to_amend: BranchOwnershipClaims = "file2.txt:1-6".parse().unwrap(); controller .move_commit_file(project, branch_id, commit2_id, commit3_id, &to_amend) .await diff --git a/crates/gitbutler-branch-actions/tests/virtual_branches/move_commit_to_vbranch.rs b/crates/gitbutler-branch-actions/tests/virtual_branches/move_commit_to_vbranch.rs index 1a44f3cc6..c6b3c8176 100644 --- a/crates/gitbutler-branch-actions/tests/virtual_branches/move_commit_to_vbranch.rs +++ b/crates/gitbutler-branch-actions/tests/virtual_branches/move_commit_to_vbranch.rs @@ -1,4 +1,4 @@ -use gitbutler_branch::branch::{BranchCreateRequest, BranchId}; +use gitbutler_branch::{BranchCreateRequest, BranchId}; use super::Test; diff --git a/crates/gitbutler-branch-actions/tests/virtual_branches/oplog.rs b/crates/gitbutler-branch-actions/tests/virtual_branches/oplog.rs index 47b291ea8..0de2e5a47 100644 --- a/crates/gitbutler-branch-actions/tests/virtual_branches/oplog.rs +++ b/crates/gitbutler-branch-actions/tests/virtual_branches/oplog.rs @@ -1,5 +1,5 @@ use super::*; -use gitbutler_branch::VirtualBranchesHandle; +use gitbutler_branch::{BranchCreateRequest, VirtualBranchesHandle}; use gitbutler_oplog::oplog::Oplog; use itertools::Itertools; use std::io::Write; @@ -31,7 +31,7 @@ async fn workdir_vbranch_restore() -> anyhow::Result<()> { let branch_id = controller .create_virtual_branch( project, - &branch::BranchCreateRequest { + &BranchCreateRequest { name: Some(round.to_string()), ..Default::default() }, @@ -113,7 +113,7 @@ async fn basic_oplog() -> anyhow::Result<()> { .await?; let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await?; // create commit @@ -146,7 +146,7 @@ async fn basic_oplog() -> anyhow::Result<()> { // create state with conflict state let _empty_branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await?; std::fs::remove_file(&base_merge_parent_path)?; @@ -268,7 +268,7 @@ async fn restores_gitbutler_integration() -> anyhow::Result<()> { 0 ); let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await?; assert_eq!( VirtualBranchesHandle::new(project.gb_dir()) diff --git a/crates/gitbutler-branch-actions/tests/virtual_branches/references.rs b/crates/gitbutler-branch-actions/tests/virtual_branches/references.rs index 21cc20d00..7e17fd468 100644 --- a/crates/gitbutler-branch-actions/tests/virtual_branches/references.rs +++ b/crates/gitbutler-branch-actions/tests/virtual_branches/references.rs @@ -1,9 +1,8 @@ use super::*; mod create_virtual_branch { - use branch::BranchCreateRequest; - use super::*; + use gitbutler_branch::BranchCreateRequest; #[tokio::test] async fn simple() { @@ -20,7 +19,7 @@ mod create_virtual_branch { .unwrap(); let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -92,6 +91,7 @@ mod create_virtual_branch { mod update_virtual_branch { use super::*; + use gitbutler_branch::{BranchCreateRequest, BranchUpdateRequest}; #[tokio::test] async fn simple() { @@ -110,7 +110,7 @@ mod update_virtual_branch { let branch_id = controller .create_virtual_branch( project, - &branch::BranchCreateRequest { + &BranchCreateRequest { name: Some("name".to_string()), ..Default::default() }, @@ -121,7 +121,7 @@ mod update_virtual_branch { controller .update_virtual_branch( project, - branch::BranchUpdateRequest { + BranchUpdateRequest { id: branch_id, name: Some("new name".to_string()), ..Default::default() @@ -161,7 +161,7 @@ mod update_virtual_branch { let branch1_id = controller .create_virtual_branch( project, - &branch::BranchCreateRequest { + &BranchCreateRequest { name: Some("name".to_string()), ..Default::default() }, @@ -172,7 +172,7 @@ mod update_virtual_branch { let branch2_id = controller .create_virtual_branch( project, - &branch::BranchCreateRequest { + &BranchCreateRequest { ..Default::default() }, ) @@ -182,7 +182,7 @@ mod update_virtual_branch { controller .update_virtual_branch( project, - branch::BranchUpdateRequest { + BranchUpdateRequest { id: branch2_id, name: Some("name".to_string()), ..Default::default() @@ -209,8 +209,8 @@ mod update_virtual_branch { } mod push_virtual_branch { - use super::*; + use gitbutler_branch::{BranchCreateRequest, BranchUpdateRequest}; #[tokio::test] async fn simple() { @@ -229,7 +229,7 @@ mod push_virtual_branch { let branch1_id = controller .create_virtual_branch( project, - &branch::BranchCreateRequest { + &BranchCreateRequest { name: Some("name".to_string()), ..Default::default() }, @@ -284,7 +284,7 @@ mod push_virtual_branch { let branch1_id = controller .create_virtual_branch( project, - &branch::BranchCreateRequest { + &BranchCreateRequest { name: Some("name".to_string()), ..Default::default() }, @@ -307,7 +307,7 @@ mod push_virtual_branch { controller .update_virtual_branch( project, - branch::BranchUpdateRequest { + BranchUpdateRequest { id: branch1_id, name: Some("updated name".to_string()), ..Default::default() @@ -321,7 +321,7 @@ mod push_virtual_branch { let branch2_id = controller .create_virtual_branch( project, - &branch::BranchCreateRequest { + &BranchCreateRequest { name: Some("name".to_string()), ..Default::default() }, diff --git a/crates/gitbutler-branch-actions/tests/virtual_branches/reorder_commit.rs b/crates/gitbutler-branch-actions/tests/virtual_branches/reorder_commit.rs index 7a5121e01..b214413ab 100644 --- a/crates/gitbutler-branch-actions/tests/virtual_branches/reorder_commit.rs +++ b/crates/gitbutler-branch-actions/tests/virtual_branches/reorder_commit.rs @@ -1,4 +1,5 @@ use super::*; +use gitbutler_branch::BranchCreateRequest; #[tokio::test] async fn reorder_commit_down() { @@ -15,7 +16,7 @@ async fn reorder_commit_down() { .unwrap(); let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -76,7 +77,7 @@ async fn reorder_commit_up() { .unwrap(); let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); diff --git a/crates/gitbutler-branch-actions/tests/virtual_branches/reset_virtual_branch.rs b/crates/gitbutler-branch-actions/tests/virtual_branches/reset_virtual_branch.rs index 1d0699447..e893fd7d7 100644 --- a/crates/gitbutler-branch-actions/tests/virtual_branches/reset_virtual_branch.rs +++ b/crates/gitbutler-branch-actions/tests/virtual_branches/reset_virtual_branch.rs @@ -1,6 +1,6 @@ use std::fs; -use gitbutler_branch::branch::BranchCreateRequest; +use gitbutler_branch::BranchCreateRequest; use super::Test; diff --git a/crates/gitbutler-branch-actions/tests/virtual_branches/selected_for_changes.rs b/crates/gitbutler-branch-actions/tests/virtual_branches/selected_for_changes.rs index cb1f8ab76..15e7d9391 100644 --- a/crates/gitbutler-branch-actions/tests/virtual_branches/selected_for_changes.rs +++ b/crates/gitbutler-branch-actions/tests/virtual_branches/selected_for_changes.rs @@ -1,4 +1,5 @@ use super::*; +use gitbutler_branch::{BranchCreateRequest, BranchUpdateRequest}; #[tokio::test] async fn unapplying_selected_branch_selects_anther() { @@ -18,13 +19,13 @@ async fn unapplying_selected_branch_selects_anther() { // first branch should be created as default let b_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); // if default branch exists, new branch should not be created as default let b2_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -65,13 +66,13 @@ async fn deleting_selected_branch_selects_anther() { // first branch should be created as default let b_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); // if default branch exists, new branch should not be created as default let b2_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -111,7 +112,7 @@ async fn create_virtual_branch_should_set_selected_for_changes() { // first branch should be created as default let b_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); let branch = controller @@ -126,7 +127,7 @@ async fn create_virtual_branch_should_set_selected_for_changes() { // if default branch exists, new branch should not be created as default let b_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); let branch = controller @@ -143,7 +144,7 @@ async fn create_virtual_branch_should_set_selected_for_changes() { let b_id = controller .create_virtual_branch( project, - &branch::BranchCreateRequest { + &BranchCreateRequest { selected_for_changes: Some(false), ..Default::default() }, @@ -164,7 +165,7 @@ async fn create_virtual_branch_should_set_selected_for_changes() { let b_id = controller .create_virtual_branch( project, - &branch::BranchCreateRequest { + &BranchCreateRequest { selected_for_changes: Some(true), ..Default::default() }, @@ -196,7 +197,7 @@ async fn update_virtual_branch_should_reset_selected_for_changes() { .unwrap(); let b1_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); let b1 = controller @@ -210,7 +211,7 @@ async fn update_virtual_branch_should_reset_selected_for_changes() { assert!(b1.selected_for_changes); let b2_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); let b2 = controller @@ -226,7 +227,7 @@ async fn update_virtual_branch_should_reset_selected_for_changes() { controller .update_virtual_branch( project, - branch::BranchUpdateRequest { + BranchUpdateRequest { id: b2_id, selected_for_changes: Some(true), ..Default::default() @@ -271,7 +272,7 @@ async fn unapply_virtual_branch_should_reset_selected_for_changes() { .unwrap(); let b1_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); std::fs::write(repository.path().join("file.txt"), "content").unwrap(); @@ -287,7 +288,7 @@ async fn unapply_virtual_branch_should_reset_selected_for_changes() { assert!(b1.selected_for_changes); let b2_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -337,7 +338,7 @@ async fn hunks_distribution() { controller .create_virtual_branch( project, - &branch::BranchCreateRequest { + &BranchCreateRequest { selected_for_changes: Some(true), ..Default::default() }, @@ -423,7 +424,7 @@ async fn new_locked_hunk_without_modifying_existing() { controller .create_virtual_branch( project, - &branch::BranchCreateRequest { + &BranchCreateRequest { selected_for_changes: Some(true), ..Default::default() }, diff --git a/crates/gitbutler-branch-actions/tests/virtual_branches/set_base_branch.rs b/crates/gitbutler-branch-actions/tests/virtual_branches/set_base_branch.rs index a741cc4bf..009294467 100644 --- a/crates/gitbutler-branch-actions/tests/virtual_branches/set_base_branch.rs +++ b/crates/gitbutler-branch-actions/tests/virtual_branches/set_base_branch.rs @@ -42,9 +42,9 @@ mod error { } mod go_back_to_integration { - use pretty_assertions::assert_eq; - use super::*; + use gitbutler_branch::BranchCreateRequest; + use pretty_assertions::assert_eq; #[tokio::test] async fn should_preserve_applied_vbranches() { @@ -67,7 +67,7 @@ mod go_back_to_integration { .unwrap(); let vbranch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); diff --git a/crates/gitbutler-branch-actions/tests/virtual_branches/squash.rs b/crates/gitbutler-branch-actions/tests/virtual_branches/squash.rs index 7d0914245..d3fb231dd 100644 --- a/crates/gitbutler-branch-actions/tests/virtual_branches/squash.rs +++ b/crates/gitbutler-branch-actions/tests/virtual_branches/squash.rs @@ -1,4 +1,5 @@ use super::*; +use gitbutler_branch::{BranchCreateRequest, BranchUpdateRequest}; #[tokio::test] async fn head() { @@ -15,7 +16,7 @@ async fn head() { .unwrap(); let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -91,7 +92,7 @@ async fn middle() { .unwrap(); let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -177,7 +178,7 @@ async fn forcepush_allowed() { .unwrap(); let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -259,14 +260,14 @@ async fn forcepush_forbidden() { .unwrap(); let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); controller .update_virtual_branch( project, - branch::BranchUpdateRequest { + BranchUpdateRequest { id: branch_id, allow_rebasing: Some(false), ..Default::default() @@ -337,7 +338,7 @@ async fn root_forbidden() { .unwrap(); let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); diff --git a/crates/gitbutler-branch-actions/tests/virtual_branches/unapply_ownership.rs b/crates/gitbutler-branch-actions/tests/virtual_branches/unapply_ownership.rs index 8b105d299..4ded013af 100644 --- a/crates/gitbutler-branch-actions/tests/virtual_branches/unapply_ownership.rs +++ b/crates/gitbutler-branch-actions/tests/virtual_branches/unapply_ownership.rs @@ -1,6 +1,6 @@ use std::fs; -use gitbutler_branch::{branch::BranchCreateRequest, ownership::BranchOwnershipClaims}; +use gitbutler_branch::{BranchCreateRequest, BranchOwnershipClaims}; use super::Test; diff --git a/crates/gitbutler-branch-actions/tests/virtual_branches/undo_commit.rs b/crates/gitbutler-branch-actions/tests/virtual_branches/undo_commit.rs index d7d4691ea..dc2315c18 100644 --- a/crates/gitbutler-branch-actions/tests/virtual_branches/undo_commit.rs +++ b/crates/gitbutler-branch-actions/tests/virtual_branches/undo_commit.rs @@ -1,4 +1,5 @@ use super::*; +use gitbutler_branch::BranchCreateRequest; #[tokio::test] async fn undo_commit_simple() { @@ -15,7 +16,7 @@ async fn undo_commit_simple() { .unwrap(); let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); diff --git a/crates/gitbutler-branch-actions/tests/virtual_branches/update_base_branch.rs b/crates/gitbutler-branch-actions/tests/virtual_branches/update_base_branch.rs index 4bfc4c998..3e77620e2 100644 --- a/crates/gitbutler-branch-actions/tests/virtual_branches/update_base_branch.rs +++ b/crates/gitbutler-branch-actions/tests/virtual_branches/update_base_branch.rs @@ -1,8 +1,8 @@ use super::*; mod applied_branch { - use super::*; + use gitbutler_branch::BranchCreateRequest; #[tokio::test] async fn conflicts_with_uncommitted_work() { @@ -31,7 +31,7 @@ mod applied_branch { { // make a branch that conflicts with the remote branch, but doesn't know about it yet controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -97,7 +97,7 @@ mod applied_branch { // make a branch with a commit that conflicts with upstream, and work that fixes // that conflict let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -168,7 +168,7 @@ mod applied_branch { // make a branch with a commit that conflicts with upstream, and work that fixes // that conflict let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -244,7 +244,7 @@ mod applied_branch { // make a branch with a commit that conflicts with upstream, and work that fixes // that conflict let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -317,7 +317,7 @@ mod applied_branch { // make a branch with a commit that conflicts with upstream, and work that fixes // that conflict let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -364,6 +364,7 @@ mod applied_branch { mod no_conflicts_pushed { use super::*; + use gitbutler_branch::BranchUpdateRequest; #[tokio::test] async fn force_push_ok() { @@ -401,7 +402,7 @@ mod applied_branch { let branch_id = { let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -467,7 +468,7 @@ mod applied_branch { let branch_id = { let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -490,7 +491,7 @@ mod applied_branch { controller .update_virtual_branch( project, - branch::BranchUpdateRequest { + BranchUpdateRequest { id: branch_id, allow_rebasing: Some(false), ..Default::default() @@ -547,7 +548,7 @@ mod applied_branch { let branch_id = { let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -611,7 +612,7 @@ mod applied_branch { let branch_id = { // make a branch that conflicts with the remote branch, but doesn't know about it yet let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -706,7 +707,7 @@ mod applied_branch { // branch has no conflict let branch_id = { let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -810,7 +811,7 @@ mod applied_branch { let branch_id = { let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -872,7 +873,7 @@ mod applied_branch { let branch_id = { // make a branch that conflicts with the remote branch, but doesn't know about it yet let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -944,7 +945,7 @@ mod applied_branch { { // make a branch that conflicts with the remote branch, but doesn't know about it yet let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -992,7 +993,7 @@ mod applied_branch { .unwrap(); let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -1050,7 +1051,7 @@ mod applied_branch { .unwrap(); let branch_1_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -1063,7 +1064,7 @@ mod applied_branch { let branch_2_id = controller .create_virtual_branch( project, - &branch::BranchCreateRequest { + &BranchCreateRequest { selected_for_changes: Some(true), ..Default::default() }, diff --git a/crates/gitbutler-branch-actions/tests/virtual_branches/update_commit_message.rs b/crates/gitbutler-branch-actions/tests/virtual_branches/update_commit_message.rs index 3beb8701d..4a0b39ff5 100644 --- a/crates/gitbutler-branch-actions/tests/virtual_branches/update_commit_message.rs +++ b/crates/gitbutler-branch-actions/tests/virtual_branches/update_commit_message.rs @@ -1,3 +1,4 @@ +use gitbutler_branch::{BranchCreateRequest, BranchUpdateRequest}; use gitbutler_commit::commit_ext::CommitExt; use super::*; @@ -17,7 +18,7 @@ async fn head() { .unwrap(); let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -95,7 +96,7 @@ async fn middle() { .unwrap(); let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -173,7 +174,7 @@ async fn forcepush_allowed() { .unwrap(); let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -228,14 +229,14 @@ async fn forcepush_forbidden() { .unwrap(); let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); controller .update_virtual_branch( project, - branch::BranchUpdateRequest { + BranchUpdateRequest { id: branch_id, allow_rebasing: Some(false), ..Default::default() @@ -282,7 +283,7 @@ async fn root() { .unwrap(); let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -350,7 +351,7 @@ async fn empty() { .unwrap(); let branch_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); diff --git a/crates/gitbutler-branch-actions/tests/virtual_branches/upstream.rs b/crates/gitbutler-branch-actions/tests/virtual_branches/upstream.rs index a6fcbbac2..5df80e655 100644 --- a/crates/gitbutler-branch-actions/tests/virtual_branches/upstream.rs +++ b/crates/gitbutler-branch-actions/tests/virtual_branches/upstream.rs @@ -1,4 +1,5 @@ use super::*; +use gitbutler_branch::BranchCreateRequest; #[tokio::test] async fn detect_upstream_commits() { @@ -15,7 +16,7 @@ async fn detect_upstream_commits() { .unwrap(); let branch1_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); @@ -82,7 +83,7 @@ async fn detect_integrated_commits() { .unwrap(); let branch1_id = controller - .create_virtual_branch(project, &branch::BranchCreateRequest::default()) + .create_virtual_branch(project, &BranchCreateRequest::default()) .await .unwrap(); diff --git a/crates/gitbutler-oplog/src/oplog.rs b/crates/gitbutler-oplog/src/oplog.rs index 700710d8a..72e6458df 100644 --- a/crates/gitbutler-oplog/src/oplog.rs +++ b/crates/gitbutler-oplog/src/oplog.rs @@ -1,8 +1,7 @@ use anyhow::{anyhow, bail, Context}; use git2::{DiffOptions, FileMode}; -use gitbutler_branch::branch::Branch; use gitbutler_branch::diff::{hunks_by_filepath, FileDiff}; -use gitbutler_branch::{VirtualBranchesHandle, VirtualBranchesState}; +use gitbutler_branch::{Branch, VirtualBranchesHandle, VirtualBranchesState}; use gitbutler_project::Project; use gitbutler_repo::RepositoryExt; use std::collections::HashMap; diff --git a/crates/gitbutler-oplog/src/snapshot.rs b/crates/gitbutler-oplog/src/snapshot.rs index 5308c28c0..f0ea1ef31 100644 --- a/crates/gitbutler-oplog/src/snapshot.rs +++ b/crates/gitbutler-oplog/src/snapshot.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use gitbutler_branch::branch::{Branch, BranchUpdateRequest}; +use gitbutler_branch::{Branch, BranchUpdateRequest}; use gitbutler_project::Project; use gitbutler_reference::ReferenceName; use std::vec; diff --git a/crates/gitbutler-repo/src/askpass.rs b/crates/gitbutler-repo/src/askpass.rs index 2e705d2a5..792058684 100644 --- a/crates/gitbutler-repo/src/askpass.rs +++ b/crates/gitbutler-repo/src/askpass.rs @@ -1,11 +1,10 @@ use std::{collections::HashMap, path::Path, sync::Arc}; -use gitbutler_branch::branch::BranchId; +use gitbutler_branch::BranchId; +use gitbutler_id::id::Id; use serde::Serialize; use tokio::sync::{oneshot, Mutex}; -use gitbutler_id::id::Id; - static mut GLOBAL_ASKPASS_BROKER: Option = None; /// Initialize the global askpass broker. diff --git a/crates/gitbutler-repo/src/repository.rs b/crates/gitbutler-repo/src/repository.rs index 2aa5e7a61..177b54e29 100644 --- a/crates/gitbutler-repo/src/repository.rs +++ b/crates/gitbutler-repo/src/repository.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use anyhow::{anyhow, Context, Result}; -use gitbutler_branch::branch::{Branch, BranchId}; +use gitbutler_branch::{Branch, BranchId}; use gitbutler_command_context::ProjectRepository; use gitbutler_commit::commit_headers::CommitHeadersV2; use gitbutler_error::error::Code; diff --git a/crates/gitbutler-sync/src/cloud.rs b/crates/gitbutler-sync/src/cloud.rs index a2f7fe0b0..f697aba12 100644 --- a/crates/gitbutler-sync/src/cloud.rs +++ b/crates/gitbutler-sync/src/cloud.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use std::time; use anyhow::{anyhow, Context, Result}; -use gitbutler_branch::target::Target; +use gitbutler_branch::Target; use gitbutler_branch::VirtualBranchesHandle; use gitbutler_command_context::ProjectRepository; use gitbutler_error::error::Code; diff --git a/crates/gitbutler-tauri/src/app.rs b/crates/gitbutler-tauri/src/app.rs index a2db44933..ceb679dc6 100644 --- a/crates/gitbutler-tauri/src/app.rs +++ b/crates/gitbutler-tauri/src/app.rs @@ -1,5 +1,5 @@ use anyhow::{Context, Result}; -use gitbutler_branch::branch::BranchId; +use gitbutler_branch::BranchId; use gitbutler_branch_actions::conflicts; use gitbutler_command_context::ProjectRepository; use gitbutler_project as projects; diff --git a/crates/gitbutler-tauri/src/virtual_branches.rs b/crates/gitbutler-tauri/src/virtual_branches.rs index f030221f9..1e3bd524d 100644 --- a/crates/gitbutler-tauri/src/virtual_branches.rs +++ b/crates/gitbutler-tauri/src/virtual_branches.rs @@ -1,8 +1,8 @@ pub mod commands { use crate::error::Error; use anyhow::{anyhow, Context}; - use gitbutler_branch::branch::{BranchCreateRequest, BranchId, BranchUpdateRequest}; - use gitbutler_branch::ownership::BranchOwnershipClaims; + use gitbutler_branch::BranchOwnershipClaims; + use gitbutler_branch::{BranchCreateRequest, BranchId, BranchUpdateRequest}; use gitbutler_branch_actions::BaseBranch; use gitbutler_branch_actions::RemoteBranchFile; use gitbutler_branch_actions::{NameConflictResolution, VirtualBranchActions, VirtualBranches}; diff --git a/crates/gitbutler-testsupport/src/lib.rs b/crates/gitbutler-testsupport/src/lib.rs index 9abaa5c02..cc0d27d9e 100644 --- a/crates/gitbutler-testsupport/src/lib.rs +++ b/crates/gitbutler-testsupport/src/lib.rs @@ -18,7 +18,7 @@ pub mod paths { } pub mod virtual_branches { - use gitbutler_branch::target::Target; + use gitbutler_branch::Target; use gitbutler_branch::VirtualBranchesHandle; use gitbutler_command_context::ProjectRepository; From 4a7b63a56ef80b96d2ce50f610d622c1305c27e3 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 13 Jul 2024 11:08:24 +0200 Subject: [PATCH 08/13] various refactors in main functions called during project load * use `ctx` as name instead of `project_repository` to make lines shorter and more readable. This could be done everywhere once the type-name changes as well. * Where possible, avoid using `&self` for `VirtualBranchActions` as there is no state. For now I avoided to remove its usage as field in the filesystem monitor. * Use a more modern way to use state in `tauri` commands. * Add the `Ext` suffix to what clearly is extension traits. --- .../gitbutler-branch-actions/src/actions.rs | 15 ++--- crates/gitbutler-branch-actions/src/base.rs | 2 +- .../src/branch_manager/branch_creation.rs | 4 +- .../src/branch_manager/branch_removal.rs | 4 +- .../src/integration.rs | 4 +- crates/gitbutler-branch-actions/src/remote.rs | 58 ++++++++----------- .../gitbutler-branch-actions/src/virtual.rs | 49 ++++++---------- .../tests/virtual_branches/oplog.rs | 2 +- crates/gitbutler-branch/src/state.rs | 4 +- crates/gitbutler-cli/src/main.rs | 2 +- crates/gitbutler-oplog/src/oplog.rs | 4 +- crates/gitbutler-oplog/src/snapshot.rs | 6 +- crates/gitbutler-repo/src/lib.rs | 2 +- crates/gitbutler-repo/src/rebase.rs | 2 +- crates/gitbutler-repo/src/repository.rs | 4 +- crates/gitbutler-sync/src/cloud.rs | 2 +- crates/gitbutler-tauri/src/app.rs | 2 +- crates/gitbutler-tauri/src/projects.rs | 36 ++++++------ crates/gitbutler-tauri/src/undo.rs | 2 +- .../gitbutler-tauri/src/virtual_branches.rs | 46 +++++++-------- crates/gitbutler-watcher/src/handler.rs | 2 +- 21 files changed, 115 insertions(+), 137 deletions(-) diff --git a/crates/gitbutler-branch-actions/src/actions.rs b/crates/gitbutler-branch-actions/src/actions.rs index f58595198..9f48253de 100644 --- a/crates/gitbutler-branch-actions/src/actions.rs +++ b/crates/gitbutler-branch-actions/src/actions.rs @@ -5,13 +5,14 @@ use gitbutler_branch::{ use gitbutler_command_context::ProjectRepository; use gitbutler_oplog::{ entry::{OperationKind, SnapshotDetails}, - oplog::Oplog, - snapshot::Snapshot, + oplog::OplogExt, + snapshot::SnapshotExt, }; use gitbutler_project::{FetchResult, Project}; use gitbutler_reference::ReferenceName; use gitbutler_reference::{Refname, RemoteRefname}; -use gitbutler_repo::{credentials::Helper, RepoActions, RepositoryExt}; +use gitbutler_repo::{credentials::Helper, RepoActionsExt, RepositoryExt}; +use tracing::instrument; use crate::{ base::{ @@ -73,8 +74,7 @@ impl VirtualBranchActions { &self, project: &Project, ) -> Result<(Vec, Vec)> { - let project_repository = open_with_verify(project)?; - branch::list_virtual_branches(&project_repository).map_err(Into::into) + branch::list_virtual_branches(&open_with_verify(project)?).map_err(Into::into) } pub async fn create_virtual_branch( @@ -88,7 +88,8 @@ impl VirtualBranchActions { Ok(branch_id) } - pub async fn get_base_branch_data(&self, project: &Project) -> Result { + #[instrument(skip(project), err(Debug))] + pub async fn get_base_branch_data(project: &Project) -> Result { let project_repository = ProjectRepository::open(project)?; get_base_branch_data(&project_repository) } @@ -324,7 +325,7 @@ impl VirtualBranchActions { branch::push(&project_repository, branch_id, with_force, &helper, askpass) } - pub async fn list_remote_branches(&self, project: Project) -> Result> { + pub async fn list_remote_branches(project: Project) -> Result> { let project_repository = ProjectRepository::open(&project)?; list_remote_branches(&project_repository) } diff --git a/crates/gitbutler-branch-actions/src/base.rs b/crates/gitbutler-branch-actions/src/base.rs index aecd5da1e..19f117500 100644 --- a/crates/gitbutler-branch-actions/src/base.rs +++ b/crates/gitbutler-branch-actions/src/base.rs @@ -11,7 +11,7 @@ use gitbutler_command_context::ProjectRepository; use gitbutler_project::FetchResult; use gitbutler_reference::ReferenceName; use gitbutler_reference::{Refname, RemoteRefname}; -use gitbutler_repo::{LogUntil, RepoActions, RepositoryExt}; +use gitbutler_repo::{LogUntil, RepoActionsExt, RepositoryExt}; use serde::Serialize; use super::r#virtual as vb; diff --git a/crates/gitbutler-branch-actions/src/branch_manager/branch_creation.rs b/crates/gitbutler-branch-actions/src/branch_manager/branch_creation.rs index 6567bcdeb..7ebfb2a88 100644 --- a/crates/gitbutler-branch-actions/src/branch_manager/branch_creation.rs +++ b/crates/gitbutler-branch-actions/src/branch_manager/branch_creation.rs @@ -11,9 +11,9 @@ use gitbutler_branch::{ }; use gitbutler_commit::commit_headers::HasCommitHeaders; use gitbutler_error::error::Marker; -use gitbutler_oplog::snapshot::Snapshot; +use gitbutler_oplog::snapshot::SnapshotExt; use gitbutler_reference::Refname; -use gitbutler_repo::{rebase::cherry_rebase, RepoActions, RepositoryExt}; +use gitbutler_repo::{rebase::cherry_rebase, RepoActionsExt, RepositoryExt}; use gitbutler_time::time::now_since_unix_epoch_ms; impl BranchManager<'_> { diff --git a/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs b/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs index fc3a49cc7..e0b55cd03 100644 --- a/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs +++ b/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs @@ -8,10 +8,10 @@ use anyhow::{anyhow, Context, Result}; use git2::build::TreeUpdateBuilder; use gitbutler_branch::{Branch, BranchExt, BranchId}; use gitbutler_commit::commit_headers::CommitHeadersV2; -use gitbutler_oplog::snapshot::Snapshot; +use gitbutler_oplog::snapshot::SnapshotExt; use gitbutler_reference::ReferenceName; use gitbutler_reference::{normalize_branch_name, Refname}; -use gitbutler_repo::{RepoActions, RepositoryExt}; +use gitbutler_repo::{RepoActionsExt, RepositoryExt}; use super::BranchManager; diff --git a/crates/gitbutler-branch-actions/src/integration.rs b/crates/gitbutler-branch-actions/src/integration.rs index e19dcc7af..b69c7b3de 100644 --- a/crates/gitbutler-branch-actions/src/integration.rs +++ b/crates/gitbutler-branch-actions/src/integration.rs @@ -12,7 +12,7 @@ use gitbutler_branch::{ use gitbutler_command_context::ProjectRepository; use gitbutler_commit::commit_ext::CommitExt; use gitbutler_error::error::Marker; -use gitbutler_repo::{LogUntil, RepoActions, RepositoryExt}; +use gitbutler_repo::{LogUntil, RepoActionsExt, RepositoryExt}; use crate::branch_manager::BranchManagerExt; use crate::{conflicts, VirtualBranchesExt}; @@ -92,7 +92,7 @@ pub(crate) fn get_workspace_head( } // TODO: Why does commit only accept a slice of commits? Feels like we - // could make use of AsRef with the right traits. + // could make use of AsRef with the right traits. let head_refs: Vec<&git2::Commit<'_>> = heads.iter().collect(); let workspace_head_id = repo.commit( diff --git a/crates/gitbutler-branch-actions/src/remote.rs b/crates/gitbutler-branch-actions/src/remote.rs index 3a889ab11..174cfa8c9 100644 --- a/crates/gitbutler-branch-actions/src/remote.rs +++ b/crates/gitbutler-branch-actions/src/remote.rs @@ -6,7 +6,7 @@ use gitbutler_branch::{Target, VirtualBranchesHandle}; use gitbutler_command_context::ProjectRepository; use gitbutler_commit::commit_ext::CommitExt; use gitbutler_reference::{Refname, RemoteRefname}; -use gitbutler_repo::{LogUntil, RepoActions, RepositoryExt}; +use gitbutler_repo::{LogUntil, RepoActionsExt, RepositoryExt}; use serde::Serialize; use crate::author::Author; @@ -69,7 +69,7 @@ pub fn list_remote_branches(project_repository: &ProjectRepository) -> Result Result> { +pub(crate) fn branch_to_remote_branch(branch: &git2::Branch) -> Option { let commit = match branch.get().peel_to_commit() { Ok(c) => c, Err(err) => { @@ -110,38 +110,28 @@ pub(crate) fn branch_to_remote_branch(branch: &git2::Branch) -> Result branch - .get() - .target() - .map(|sha| { - Ok(RemoteBranch { - sha, - upstream: if let Refname::Local(local_name) = &name { - local_name.remote().cloned() - } else { - None - }, - name, - last_commit_timestamp_ms: commit - .time() - .seconds() - .try_into() - .map(|t: u128| t * 1000) - .ok(), - last_commit_author: commit - .author() - .name() - .map(std::string::ToString::to_string), - }) - }) - .transpose(), - Err(_) => Ok(None), - } + let name = Refname::try_from(branch) + .context("could not get branch name") + .ok()?; + branch.get().target().map(|sha| RemoteBranch { + sha, + upstream: if let Refname::Local(local_name) = &name { + local_name.remote().cloned() + } else { + None + }, + name, + last_commit_timestamp_ms: commit + .time() + .seconds() + .try_into() + .map(|t: u128| t * 1000) + .ok(), + last_commit_author: commit.author().name().map(std::string::ToString::to_string), + }) } pub(crate) fn branch_to_remote_branch_data( @@ -185,7 +175,7 @@ pub(crate) fn branch_to_remote_branch_data( } pub(crate) fn commit_to_remote_commit(commit: &git2::Commit) -> RemoteCommit { - let parent_ids: Vec = commit.parents().map(|c| c.id()).collect::>(); + let parent_ids = commit.parents().map(|c| c.id()).collect(); RemoteCommit { id: commit.id().to_string(), description: commit.message_bstr().to_owned(), diff --git a/crates/gitbutler-branch-actions/src/virtual.rs b/crates/gitbutler-branch-actions/src/virtual.rs index ffda8c056..e4c99169e 100644 --- a/crates/gitbutler-branch-actions/src/virtual.rs +++ b/crates/gitbutler-branch-actions/src/virtual.rs @@ -9,7 +9,7 @@ use gitbutler_commit::commit_ext::CommitExt; use gitbutler_commit::commit_headers::HasCommitHeaders; use gitbutler_reference::{normalize_branch_name, Refname, RemoteRefname}; use gitbutler_repo::credentials::Helper; -use gitbutler_repo::{LogUntil, RepoActions, RepositoryExt}; +use gitbutler_repo::{LogUntil, RepoActionsExt, RepositoryExt}; use std::borrow::Borrow; #[cfg(target_family = "unix")] use std::os::unix::prelude::PermissionsExt; @@ -364,27 +364,22 @@ fn resolve_old_applied_state( } pub fn list_virtual_branches( - project_repository: &ProjectRepository, + ctx: &ProjectRepository, ) -> Result<(Vec, Vec)> { let mut branches: Vec = Vec::new(); - let vb_state = project_repository.project().virtual_branches(); + let vb_state = ctx.project().virtual_branches(); - resolve_old_applied_state(project_repository, &vb_state)?; + resolve_old_applied_state(ctx, &vb_state)?; let default_target = vb_state .get_default_target() .context("failed to get default target")?; - let integration_commit_id = - crate::integration::get_workspace_head(&vb_state, project_repository)?; - let integration_commit = project_repository - .repo() - .find_commit(integration_commit_id) - .unwrap(); + let integration_commit_id = get_workspace_head(&vb_state, ctx)?; + let integration_commit = ctx.repo().find_commit(integration_commit_id).unwrap(); - let (statuses, skipped_files) = - get_status_by_branch(project_repository, Some(&integration_commit.id()))?; + let (statuses, skipped_files) = get_status_by_branch(ctx, Some(&integration_commit.id()))?; let max_selected_for_changes = statuses .iter() .filter_map(|(branch, _)| branch.selected_for_changes) @@ -392,8 +387,8 @@ pub fn list_virtual_branches( .unwrap_or(-1); for (branch, files) in statuses { - let repo = project_repository.repo(); - update_conflict_markers(project_repository, &files)?; + let repo = ctx.repo(); + update_conflict_markers(ctx, &files)?; let upstream_branch = match branch.clone().upstream { Some(upstream) => repo.find_branch_by_refname(&Refname::from(upstream))?, @@ -419,7 +414,7 @@ pub fn list_virtual_branches( upstream.id(), default_target.sha ))?; - for oid in project_repository.l(upstream.id(), LogUntil::Commit(merge_base))? { + for oid in ctx.l(upstream.id(), LogUntil::Commit(merge_base))? { pushed_commits.insert(oid, true); } } @@ -428,7 +423,7 @@ pub fn list_virtual_branches( let mut is_remote = false; // find all commits on head that are not on target.sha - let commits = project_repository.log(branch.head, LogUntil::Commit(default_target.sha))?; + let commits = ctx.log(branch.head, LogUntil::Commit(default_target.sha))?; let vbranch_commits = commits .iter() .map(|commit| { @@ -442,16 +437,10 @@ pub fn list_virtual_branches( is_integrated = if is_integrated { is_integrated } else { - is_commit_integrated(project_repository, &default_target, commit)? + is_commit_integrated(ctx, &default_target, commit)? }; - commit_to_vbranch_commit( - project_repository, - &branch, - commit, - is_integrated, - is_remote, - ) + commit_to_vbranch_commit(ctx, &branch, commit, is_integrated, is_remote) }) .collect::>>()?; @@ -460,12 +449,10 @@ pub fn list_virtual_branches( .context("failed to find merge base")?; let base_current = true; - let upstream = upstream_branch - .map(|upstream_branch| branch_to_remote_branch(&upstream_branch)) - .transpose()? - .flatten(); + let upstream = + upstream_branch.and_then(|upstream_branch| branch_to_remote_branch(&upstream_branch)); - let mut files = diffs_into_virtual_files(project_repository, files); + let mut files = diffs_into_virtual_files(ctx, files); let path_claim_positions: HashMap<&PathBuf, usize> = branch .ownership @@ -482,7 +469,7 @@ pub fn list_virtual_branches( .cmp(path_claim_positions.get(&b.path).unwrap_or(&usize::MAX)) }); - let requires_force = is_requires_force(project_repository, &branch)?; + let requires_force = is_requires_force(ctx, &branch)?; let fork_point = commits .last() @@ -502,7 +489,7 @@ pub fn list_virtual_branches( upstream_name: branch .upstream .and_then(|r| Refname::from(r).branch().map(Into::into)), - conflicted: conflicts::is_resolving(project_repository), + conflicted: conflicts::is_resolving(ctx), base_current, ownership: branch.ownership, updated_at: branch.updated_timestamp_ms, diff --git a/crates/gitbutler-branch-actions/tests/virtual_branches/oplog.rs b/crates/gitbutler-branch-actions/tests/virtual_branches/oplog.rs index 0de2e5a47..e1f042499 100644 --- a/crates/gitbutler-branch-actions/tests/virtual_branches/oplog.rs +++ b/crates/gitbutler-branch-actions/tests/virtual_branches/oplog.rs @@ -1,6 +1,6 @@ use super::*; use gitbutler_branch::{BranchCreateRequest, VirtualBranchesHandle}; -use gitbutler_oplog::oplog::Oplog; +use gitbutler_oplog::oplog::OplogExt; use itertools::Itertools; use std::io::Write; use std::path::Path; diff --git a/crates/gitbutler-branch/src/state.rs b/crates/gitbutler-branch/src/state.rs index a890e3a85..0bea973bd 100644 --- a/crates/gitbutler-branch/src/state.rs +++ b/crates/gitbutler-branch/src/state.rs @@ -88,8 +88,8 @@ impl VirtualBranchesHandle { /// /// Errors if the file cannot be read or written. pub fn get_default_target(&self) -> Result { - let virtual_branches = self.read_file(); - virtual_branches? + let virtual_branches = self.read_file()?; + virtual_branches .default_target .ok_or(anyhow!("there is no default target").context(Code::DefaultTargetNotFound)) } diff --git a/crates/gitbutler-cli/src/main.rs b/crates/gitbutler-cli/src/main.rs index 7d8dd543a..c90e1e6f6 100644 --- a/crates/gitbutler-cli/src/main.rs +++ b/crates/gitbutler-cli/src/main.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use gitbutler_oplog::oplog::Oplog; +use gitbutler_oplog::oplog::OplogExt; use clap::{arg, Command}; use gitbutler_project::Project; diff --git a/crates/gitbutler-oplog/src/oplog.rs b/crates/gitbutler-oplog/src/oplog.rs index 72e6458df..2332db266 100644 --- a/crates/gitbutler-oplog/src/oplog.rs +++ b/crates/gitbutler-oplog/src/oplog.rs @@ -43,7 +43,7 @@ const SNAPSHOT_FILE_LIMIT_BYTES: u64 = 32 * 1024 * 1024; /// │ └── tree (subtree) /// └── virtual_branches.toml /// ``` -pub trait Oplog { +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. @@ -128,7 +128,7 @@ pub trait Oplog { fn oplog_head(&self) -> Result>; } -impl Oplog for Project { +impl OplogExt for Project { fn prepare_snapshot(&self) -> Result { let worktree_dir = self.path.as_path(); let repo = git2::Repository::open(worktree_dir)?; diff --git a/crates/gitbutler-oplog/src/snapshot.rs b/crates/gitbutler-oplog/src/snapshot.rs index f0ea1ef31..7298ff718 100644 --- a/crates/gitbutler-oplog/src/snapshot.rs +++ b/crates/gitbutler-oplog/src/snapshot.rs @@ -6,12 +6,12 @@ use std::vec; use crate::{ entry::{OperationKind, SnapshotDetails}, - oplog::Oplog, + oplog::OplogExt, }; use super::entry::Trailer; -pub trait Snapshot { +pub trait SnapshotExt { fn snapshot_branch_unapplied( &self, snapshot_tree: git2::Oid, @@ -45,7 +45,7 @@ pub trait Snapshot { } /// Snapshot functionality -impl Snapshot for Project { +impl SnapshotExt for Project { fn snapshot_branch_unapplied( &self, snapshot_tree: git2::Oid, diff --git a/crates/gitbutler-repo/src/lib.rs b/crates/gitbutler-repo/src/lib.rs index 0998e655c..8948076d5 100644 --- a/crates/gitbutler-repo/src/lib.rs +++ b/crates/gitbutler-repo/src/lib.rs @@ -1,7 +1,7 @@ pub mod rebase; mod repository; -pub use repository::{LogUntil, RepoActions}; +pub use repository::{LogUntil, RepoActionsExt}; mod commands; pub use commands::RepoCommands; diff --git a/crates/gitbutler-repo/src/rebase.rs b/crates/gitbutler-repo/src/rebase.rs index affcc0517..4015eb66a 100644 --- a/crates/gitbutler-repo/src/rebase.rs +++ b/crates/gitbutler-repo/src/rebase.rs @@ -4,7 +4,7 @@ use gitbutler_command_context::ProjectRepository; use gitbutler_commit::{commit_ext::CommitExt, commit_headers::HasCommitHeaders}; use gitbutler_error::error::Marker; -use crate::{LogUntil, RepoActions, RepositoryExt}; +use crate::{LogUntil, RepoActionsExt, RepositoryExt}; /// cherry-pick based rebase, which handles empty commits /// this function takes a commit range and generates a Vector of commit oids diff --git a/crates/gitbutler-repo/src/repository.rs b/crates/gitbutler-repo/src/repository.rs index 177b54e29..27b0de77d 100644 --- a/crates/gitbutler-repo/src/repository.rs +++ b/crates/gitbutler-repo/src/repository.rs @@ -12,7 +12,7 @@ use crate::{askpass, ssh, Config}; use gitbutler_project::AuthKey; use crate::{credentials::Helper, RepositoryExt}; -pub trait RepoActions { +pub trait RepoActionsExt { fn fetch(&self, remote_name: &str, credentials: &Helper, askpass: Option) -> Result<()>; fn push( @@ -47,7 +47,7 @@ pub trait RepoActions { ) -> Result<()>; } -impl RepoActions for ProjectRepository { +impl RepoActionsExt for ProjectRepository { fn git_test_push( &self, credentials: &Helper, diff --git a/crates/gitbutler-sync/src/cloud.rs b/crates/gitbutler-sync/src/cloud.rs index f697aba12..51dce7e42 100644 --- a/crates/gitbutler-sync/src/cloud.rs +++ b/crates/gitbutler-sync/src/cloud.rs @@ -8,7 +8,7 @@ use gitbutler_branch::VirtualBranchesHandle; use gitbutler_command_context::ProjectRepository; use gitbutler_error::error::Code; use gitbutler_id::id::Id; -use gitbutler_oplog::oplog::Oplog; +use gitbutler_oplog::oplog::OplogExt; use gitbutler_project as projects; use gitbutler_project::{CodePushState, Project}; use gitbutler_reference::Refname; diff --git a/crates/gitbutler-tauri/src/app.rs b/crates/gitbutler-tauri/src/app.rs index ceb679dc6..94e7c0b5b 100644 --- a/crates/gitbutler-tauri/src/app.rs +++ b/crates/gitbutler-tauri/src/app.rs @@ -5,7 +5,7 @@ use gitbutler_command_context::ProjectRepository; use gitbutler_project as projects; use gitbutler_project::ProjectId; use gitbutler_reference::RemoteRefname; -use gitbutler_repo::{credentials::Helper, RepoActions, RepositoryExt}; +use gitbutler_repo::{credentials::Helper, RepoActionsExt, RepositoryExt}; #[derive(Clone)] pub struct App { diff --git a/crates/gitbutler-tauri/src/projects.rs b/crates/gitbutler-tauri/src/projects.rs index 1378ae1ab..1ec013e56 100644 --- a/crates/gitbutler-tauri/src/projects.rs +++ b/crates/gitbutler-tauri/src/projects.rs @@ -14,40 +14,40 @@ pub mod commands { use crate::{window, WindowState}; #[tauri::command(async)] - #[instrument(skip(controller), err(Debug))] + #[instrument(skip(projects), err(Debug))] pub async fn update_project( - controller: State<'_, Controller>, + projects: State<'_, Controller>, project: projects::UpdateRequest, ) -> Result { - Ok(controller.update(&project).await?) + Ok(projects.update(&project).await?) } #[tauri::command(async)] - #[instrument(skip(controller), err(Debug))] + #[instrument(skip(projects), err(Debug))] pub async fn add_project( - controller: State<'_, Controller>, + projects: State<'_, Controller>, path: &path::Path, ) -> Result { - Ok(controller.add(path)?) + Ok(projects.add(path)?) } #[tauri::command(async)] - #[instrument(skip(controller), err(Debug))] + #[instrument(skip(projects), err(Debug))] pub async fn get_project( - controller: State<'_, Controller>, + projects: State<'_, Controller>, id: ProjectId, ) -> Result { - Ok(controller.get(id)?) + Ok(projects.get(id)?) } #[tauri::command(async)] - #[instrument(skip(controller, window_state), err(Debug))] + #[instrument(skip(projects, window_state), err(Debug))] pub async fn list_projects( window_state: State<'_, WindowState>, - controller: State<'_, Controller>, + projects: State<'_, Controller>, ) -> Result, Error> { let open_projects = window_state.open_projects(); - controller.list().map_err(Into::into).map(|projects| { + projects.list().map_err(Into::into).map(|projects| { projects .into_iter() .map(|project| ProjectForFrontend { @@ -62,14 +62,14 @@ pub mod commands { /// /// We use it to start watching for filesystem events. #[tauri::command(async)] - #[instrument(skip(controller, window_state, window), err(Debug))] + #[instrument(skip(projects, window_state, window), err(Debug))] pub async fn set_project_active( - controller: State<'_, Controller>, + projects: State<'_, Controller>, window_state: State<'_, WindowState>, window: Window, id: ProjectId, ) -> Result<(), Error> { - let project = controller.get(id).context("project not found")?; + let project = projects.get(id).context("project not found")?; Ok(window_state.set_project_to_window(window.label(), &project)?) } @@ -93,12 +93,12 @@ pub mod commands { } #[tauri::command(async)] - #[instrument(skip(controller), err(Debug))] + #[instrument(skip(projects), err(Debug))] pub async fn delete_project( - controller: State<'_, Controller>, + projects: State<'_, Controller>, id: ProjectId, ) -> Result<(), Error> { - controller.delete(id).await.map_err(Into::into) + projects.delete(id).await.map_err(Into::into) } } diff --git a/crates/gitbutler-tauri/src/undo.rs b/crates/gitbutler-tauri/src/undo.rs index 8e1f96c07..e325aa838 100644 --- a/crates/gitbutler-tauri/src/undo.rs +++ b/crates/gitbutler-tauri/src/undo.rs @@ -2,7 +2,7 @@ use crate::error::Error; use anyhow::Context; use gitbutler_branch::diff::FileDiff; use gitbutler_oplog::entry::Snapshot; -use gitbutler_oplog::oplog::Oplog; +use gitbutler_oplog::oplog::OplogExt; use gitbutler_project as projects; use gitbutler_project::ProjectId; use std::collections::HashMap; diff --git a/crates/gitbutler-tauri/src/virtual_branches.rs b/crates/gitbutler-tauri/src/virtual_branches.rs index 1e3bd524d..6ea005254 100644 --- a/crates/gitbutler-tauri/src/virtual_branches.rs +++ b/crates/gitbutler-tauri/src/virtual_branches.rs @@ -12,7 +12,7 @@ pub mod commands { use gitbutler_project::ProjectId; use gitbutler_reference::ReferenceName; use gitbutler_reference::{Refname, RemoteRefname}; - use tauri::{AppHandle, Manager}; + use tauri::{AppHandle, Manager, State}; use tracing::instrument; use crate::WindowState; @@ -36,19 +36,20 @@ pub mod commands { } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects), err(Debug))] pub async fn list_virtual_branches( - handle: AppHandle, + projects: State<'_, projects::Controller>, project_id: ProjectId, ) -> Result { - let project = handle.state::().get(project_id)?; - let (branches, skipped_files) = - VirtualBranchActions.list_virtual_branches(&project).await?; - - Ok(VirtualBranches { - branches, - skipped_files, - }) + let project = projects.get(project_id)?; + VirtualBranchActions + .list_virtual_branches(&project) + .await + .map_err(Into::into) + .map(|(branches, skipped_files)| VirtualBranches { + branches, + skipped_files, + }) } #[tauri::command(async)] @@ -97,13 +98,13 @@ pub mod commands { } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects), err(Debug))] pub async fn get_base_branch_data( - handle: AppHandle, + projects: State<'_, projects::Controller>, project_id: ProjectId, ) -> Result, Error> { - let project = handle.state::().get(project_id)?; - if let Ok(base_branch) = VirtualBranchActions.get_base_branch_data(&project).await { + let project = projects.get(project_id)?; + if let Ok(base_branch) = VirtualBranchActions::get_base_branch_data(&project).await { Ok(Some(base_branch)) } else { Ok(None) @@ -388,13 +389,13 @@ pub mod commands { } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects), err(Debug))] pub async fn list_remote_branches( - handle: tauri::AppHandle, + projects: State<'_, projects::Controller>, project_id: ProjectId, ) -> Result, Error> { - let project = handle.state::().get(project_id)?; - let branches = VirtualBranchActions.list_remote_branches(project).await?; + let project = projects.get(project_id)?; + let branches = VirtualBranchActions::list_remote_branches(project).await?; Ok(branches) } @@ -430,13 +431,12 @@ pub mod commands { } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects), err(Debug))] pub async fn fetch_from_remotes( - handle: tauri::AppHandle, + projects: State<'_, projects::Controller>, project_id: ProjectId, action: Option, ) -> Result { - let projects = handle.state::(); let project = projects.get(project_id)?; let project_data_last_fetched = VirtualBranchActions @@ -458,7 +458,7 @@ pub mod commands { .await .context("failed to update project with last fetched timestamp")?; - let base_branch = VirtualBranchActions.get_base_branch_data(&project).await?; + let base_branch = VirtualBranchActions::get_base_branch_data(&project).await?; Ok(base_branch) } diff --git a/crates/gitbutler-watcher/src/handler.rs b/crates/gitbutler-watcher/src/handler.rs index fccf04961..f92c37508 100644 --- a/crates/gitbutler-watcher/src/handler.rs +++ b/crates/gitbutler-watcher/src/handler.rs @@ -7,7 +7,7 @@ use gitbutler_command_context::ProjectRepository; use gitbutler_error::error::Marker; use gitbutler_oplog::{ entry::{OperationKind, SnapshotDetails}, - oplog::Oplog, + oplog::OplogExt, }; use gitbutler_project as projects; use gitbutler_project::ProjectId; From b3b87b34a5d761798688fc9d613cc52df5ba5731 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 14 Jul 2024 16:12:25 +0200 Subject: [PATCH 09/13] reduce `tauri` state This is in preparation for making operations that access the filesystems less stateful, which in turn makes it less akward to identify writing methods with `&mut` lateron. This includes accepting that all we really need is a single directory to generate everything else we need on the fly. This simplifies commands. For good measure, this also simplifies imports of smaller crates that are involved. --- crates/gitbutler-feedback/Cargo.toml | 4 - crates/gitbutler-feedback/src/controller.rs | 66 ++---- crates/gitbutler-feedback/src/lib.rs | 5 +- .../src/{zipper.rs => zipper/mod.rs} | 3 + .../{tests/mod.rs => src/zipper/tests.rs} | 4 +- crates/gitbutler-project/src/controller.rs | 17 +- crates/gitbutler-project/src/lib.rs | 6 +- crates/gitbutler-project/src/storage.rs | 14 +- crates/gitbutler-storage/src/lib.rs | 3 +- crates/gitbutler-tauri/src/app.rs | 41 ++-- crates/gitbutler-tauri/src/commands.rs | 80 +++---- crates/gitbutler-tauri/src/config.rs | 19 +- crates/gitbutler-tauri/src/lib.rs | 4 +- crates/gitbutler-tauri/src/main.rs | 96 ++++---- crates/gitbutler-tauri/src/remotes.rs | 14 +- crates/gitbutler-tauri/src/repo.rs | 20 +- crates/gitbutler-tauri/src/undo.rs | 28 +-- crates/gitbutler-tauri/src/users.rs | 31 +-- .../gitbutler-tauri/src/virtual_branches.rs | 214 ++++++++++-------- crates/gitbutler-tauri/src/zip.rs | 38 ++-- crates/gitbutler-testsupport/src/suite.rs | 4 +- crates/gitbutler-user/src/controller.rs | 8 +- crates/gitbutler-user/src/lib.rs | 9 +- crates/gitbutler-user/src/storage.rs | 14 +- 24 files changed, 343 insertions(+), 399 deletions(-) rename crates/gitbutler-feedback/src/{zipper.rs => zipper/mod.rs} (99%) rename crates/gitbutler-feedback/{tests/mod.rs => src/zipper/tests.rs} (97%) diff --git a/crates/gitbutler-feedback/Cargo.toml b/crates/gitbutler-feedback/Cargo.toml index 01f5bf921..145667186 100644 --- a/crates/gitbutler-feedback/Cargo.toml +++ b/crates/gitbutler-feedback/Cargo.toml @@ -12,9 +12,5 @@ walkdir = "2.5.0" sha2 = "0.10.8" gitbutler-project.workspace = true -[[test]] -name="feedback" -path = "tests/mod.rs" - [dev-dependencies] tempfile = "3.10" diff --git a/crates/gitbutler-feedback/src/controller.rs b/crates/gitbutler-feedback/src/controller.rs index 48979800f..5dbfbd147 100644 --- a/crates/gitbutler-feedback/src/controller.rs +++ b/crates/gitbutler-feedback/src/controller.rs @@ -1,52 +1,34 @@ use anyhow::Result; -use std::path; - use gitbutler_project as projects; use gitbutler_project::ProjectId; +use std::path::PathBuf; use crate::zipper::Zipper; -#[derive(Clone)] -pub struct Controller { - local_data_dir: path::PathBuf, - logs_dir: path::PathBuf, - zipper: Zipper, - #[allow(clippy::struct_field_names)] - projects_controller: projects::Controller, +pub struct Archival { + pub cache_dir: PathBuf, + pub logs_dir: PathBuf, + pub projects_controller: projects::Controller, } -impl Controller { - pub fn new( - local_data_dir: path::PathBuf, - logs_dir: path::PathBuf, - zipper: Zipper, - projects_controller: projects::Controller, - ) -> Self { - Self { - local_data_dir, - logs_dir, - zipper, - projects_controller, - } - } - - pub fn archive(&self, project_id: ProjectId) -> Result { - let project = self.projects_controller.get(project_id)?; - self.zipper.zip(project.path).map_err(Into::into) - } - - pub fn data_archive(&self, project_id: ProjectId) -> Result { - let project = self.projects_controller.get(project_id)?; - self.zipper - .zip( - self.local_data_dir - .join("projects") - .join(project.id.to_string()), - ) - .map_err(Into::into) - } - - pub fn logs_archive(&self) -> Result { - self.zipper.zip(&self.logs_dir).map_err(Into::into) +impl Archival { + fn zipper(&self) -> Zipper { + Zipper::new(self.cache_dir.clone()) + } +} + +impl Archival { + pub fn archive(&self, project_id: ProjectId) -> Result { + let project = self.projects_controller.get(project_id)?; + self.zipper().zip(project.path).map_err(Into::into) + } + + pub fn data_archive(&self, project_id: ProjectId) -> Result { + let dir_to_archive = self.projects_controller.project_metadata_dir(project_id); + self.zipper().zip(dir_to_archive).map_err(Into::into) + } + + pub fn logs_archive(&self) -> Result { + self.zipper().zip(&self.logs_dir).map_err(Into::into) } } diff --git a/crates/gitbutler-feedback/src/lib.rs b/crates/gitbutler-feedback/src/lib.rs index 97bdf5203..7fd5e7e50 100644 --- a/crates/gitbutler-feedback/src/lib.rs +++ b/crates/gitbutler-feedback/src/lib.rs @@ -1,2 +1,3 @@ -pub mod controller; -pub mod zipper; +mod controller; +pub use controller::Archival; +mod zipper; diff --git a/crates/gitbutler-feedback/src/zipper.rs b/crates/gitbutler-feedback/src/zipper/mod.rs similarity index 99% rename from crates/gitbutler-feedback/src/zipper.rs rename to crates/gitbutler-feedback/src/zipper/mod.rs index d78796d83..87930d761 100644 --- a/crates/gitbutler-feedback/src/zipper.rs +++ b/crates/gitbutler-feedback/src/zipper/mod.rs @@ -159,3 +159,6 @@ fn file_hash>(digest: &mut Sha256, path: P) -> Result<()> { ); Ok(()) } + +#[cfg(test)] +mod tests; diff --git a/crates/gitbutler-feedback/tests/mod.rs b/crates/gitbutler-feedback/src/zipper/tests.rs similarity index 97% rename from crates/gitbutler-feedback/tests/mod.rs rename to crates/gitbutler-feedback/src/zipper/tests.rs index e6b111670..d5b393c0d 100644 --- a/crates/gitbutler-feedback/tests/mod.rs +++ b/crates/gitbutler-feedback/src/zipper/tests.rs @@ -1,6 +1,6 @@ -use std::{fs::File, io::Write}; +use super::*; -use gitbutler_feedback::zipper::Zipper; +use std::{fs::File, io::Write}; use tempfile::tempdir; use walkdir::WalkDir; diff --git a/crates/gitbutler-project/src/controller.rs b/crates/gitbutler-project/src/controller.rs index a803908a3..948727a0f 100644 --- a/crates/gitbutler-project/src/controller.rs +++ b/crates/gitbutler-project/src/controller.rs @@ -13,13 +13,6 @@ pub struct Controller { } impl Controller { - pub fn new(local_data_dir: PathBuf, projects_storage: storage::Storage) -> Self { - Self { - local_data_dir, - projects_storage, - } - } - pub fn from_path(path: impl Into) -> Self { let path = path.into(); Self { @@ -164,11 +157,7 @@ impl Controller { .purge(project.id) .map_err(anyhow::Error::from)?; - if let Err(error) = std::fs::remove_dir_all( - self.local_data_dir - .join("projects") - .join(project.id.to_string()), - ) { + if let Err(error) = std::fs::remove_dir_all(self.project_metadata_dir(project.id)) { tracing::error!(project_id = %id, ?error, "failed to remove project data",); } @@ -184,4 +173,8 @@ impl Controller { Ok(()) } + + pub fn project_metadata_dir(&self, id: ProjectId) -> PathBuf { + self.local_data_dir.join("projects").join(id.to_string()) + } } diff --git a/crates/gitbutler-project/src/lib.rs b/crates/gitbutler-project/src/lib.rs index 1bc7142cb..91aff10de 100644 --- a/crates/gitbutler-project/src/lib.rs +++ b/crates/gitbutler-project/src/lib.rs @@ -1,8 +1,8 @@ -pub mod controller; +mod controller; mod default_true; mod project; -pub mod storage; +mod storage; -pub use controller::*; +pub use controller::Controller; pub use project::{ApiProject, AuthKey, CodePushState, FetchResult, Project, ProjectId}; pub use storage::UpdateRequest; diff --git a/crates/gitbutler-project/src/storage.rs b/crates/gitbutler-project/src/storage.rs index 344ee2ff4..aa643820f 100644 --- a/crates/gitbutler-project/src/storage.rs +++ b/crates/gitbutler-project/src/storage.rs @@ -2,15 +2,13 @@ use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; -use gitbutler_storage::storage; - use crate::{ApiProject, AuthKey, CodePushState, FetchResult, Project, ProjectId}; const PROJECTS_FILE: &str = "projects.json"; #[derive(Debug, Clone)] -pub struct Storage { - inner: storage::Storage, +pub(crate) struct Storage { + inner: gitbutler_storage::Storage, } #[derive(Debug, Serialize, Deserialize, Default, Clone)] @@ -31,12 +29,10 @@ pub struct UpdateRequest { } impl Storage { - pub fn new(storage: storage::Storage) -> Self { - Self { inner: storage } - } - pub fn from_path(path: impl Into) -> Self { - Self::new(storage::Storage::new(path)) + Storage { + inner: gitbutler_storage::Storage::new(path), + } } pub fn list(&self) -> Result> { diff --git a/crates/gitbutler-storage/src/lib.rs b/crates/gitbutler-storage/src/lib.rs index 30f61eb69..caf1003a3 100644 --- a/crates/gitbutler-storage/src/lib.rs +++ b/crates/gitbutler-storage/src/lib.rs @@ -1 +1,2 @@ -pub mod storage; +mod storage; +pub use storage::Storage; diff --git a/crates/gitbutler-tauri/src/app.rs b/crates/gitbutler-tauri/src/app.rs index 94e7c0b5b..c5aa29739 100644 --- a/crates/gitbutler-tauri/src/app.rs +++ b/crates/gitbutler-tauri/src/app.rs @@ -5,20 +5,28 @@ use gitbutler_command_context::ProjectRepository; use gitbutler_project as projects; use gitbutler_project::ProjectId; use gitbutler_reference::RemoteRefname; -use gitbutler_repo::{credentials::Helper, RepoActionsExt, RepositoryExt}; +use gitbutler_repo::{credentials, RepoActionsExt, RepositoryExt}; +use std::path::PathBuf; #[derive(Clone)] pub struct App { - projects: projects::Controller, + pub app_data_dir: PathBuf, +} + +/// Access to primary categories of data. +impl App { + pub fn projects(&self) -> projects::Controller { + projects::Controller::from_path(self.app_data_dir.clone()) + } + + pub fn users(&self) -> gitbutler_user::Controller { + gitbutler_user::Controller::from_path(&self.app_data_dir) + } } impl App { - pub fn new(projects: projects::Controller) -> Self { - Self { projects } - } - pub fn mark_resolved(&self, project_id: ProjectId, path: &str) -> Result<()> { - let project = self.projects.get(project_id)?; + let project = self.projects().get(project_id)?; let project_repository = ProjectRepository::open(&project)?; // mark file as resolved conflicts::resolve(&project_repository, path)?; @@ -26,7 +34,7 @@ impl App { } pub fn git_remote_branches(&self, project_id: ProjectId) -> Result> { - let project = self.projects.get(project_id)?; + let project = self.projects().get(project_id)?; let project_repository = ProjectRepository::open(&project)?; project_repository.repo().remote_branches() } @@ -36,10 +44,10 @@ impl App { project_id: ProjectId, remote_name: &str, branch_name: &str, - credentials: &Helper, + credentials: &credentials::Helper, askpass: Option>, ) -> Result<()> { - let project = self.projects.get(project_id)?; + let project = self.projects().get(project_id)?; let project_repository = ProjectRepository::open(&project)?; project_repository.git_test_push(credentials, remote_name, branch_name, askpass) } @@ -48,16 +56,16 @@ impl App { &self, project_id: ProjectId, remote_name: &str, - credentials: &Helper, + credentials: &credentials::Helper, askpass: Option, ) -> Result<()> { - let project = self.projects.get(project_id)?; + let project = self.projects().get(project_id)?; let project_repository = ProjectRepository::open(&project)?; project_repository.fetch(remote_name, credentials, askpass) } pub fn git_index_size(&self, project_id: ProjectId) -> Result { - let project = self.projects.get(project_id)?; + let project = self.projects().get(project_id)?; let project_repository = ProjectRepository::open(&project)?; let size = project_repository .repo() @@ -68,7 +76,7 @@ impl App { } pub fn git_head(&self, project_id: ProjectId) -> Result { - let project = self.projects.get(project_id)?; + let project = self.projects().get(project_id)?; let project_repository = ProjectRepository::open(&project)?; let head = project_repository .repo() @@ -104,8 +112,9 @@ impl App { } pub async fn delete_all_data(&self) -> Result<()> { - for project in self.projects.list().context("failed to list projects")? { - self.projects + let controller = self.projects(); + for project in controller.list().context("failed to list projects")? { + controller .delete(project.id) .await .map_err(|err| err.context("failed to delete project"))?; diff --git a/crates/gitbutler-tauri/src/commands.rs b/crates/gitbutler-tauri/src/commands.rs index 2d7a0b284..a3f4b9bf3 100644 --- a/crates/gitbutler-tauri/src/commands.rs +++ b/crates/gitbutler-tauri/src/commands.rs @@ -1,33 +1,29 @@ +use crate::error::Error; +use crate::App; use gitbutler_project::ProjectId; use gitbutler_reference::RemoteRefname; -use gitbutler_repo::credentials::Helper; -use tauri::Manager; +use gitbutler_repo::credentials; +use tauri::State; use tracing::instrument; -use crate::app; -use crate::error::Error; - #[tauri::command(async)] -#[instrument(skip(handle), err(Debug))] +#[instrument(skip(app), err(Debug))] pub async fn git_remote_branches( - handle: tauri::AppHandle, + app: State<'_, App>, project_id: ProjectId, ) -> Result, Error> { - let app = handle.state::(); - let branches = app.git_remote_branches(project_id)?; - Ok(branches) + Ok(app.git_remote_branches(project_id)?) } #[tauri::command(async)] -#[instrument(skip(handle), err(Debug))] +#[instrument(skip(app, helper), err(Debug))] pub async fn git_test_push( - handle: tauri::AppHandle, + app: State<'_, App>, + helper: State<'_, credentials::Helper>, project_id: ProjectId, remote_name: &str, branch_name: &str, ) -> Result<(), Error> { - let app = handle.state::(); - let helper = handle.state::(); Ok(app.git_test_push( project_id, remote_name, @@ -39,15 +35,14 @@ pub async fn git_test_push( } #[tauri::command(async)] -#[instrument(skip(handle), err(Debug))] +#[instrument(skip(app, helper), err(Debug))] pub async fn git_test_fetch( - handle: tauri::AppHandle, + app: State<'_, App>, + helper: State<'_, credentials::Helper>, project_id: ProjectId, remote_name: &str, action: Option, ) -> Result<(), Error> { - let app = handle.state::(); - let helper = handle.state::(); Ok(app.git_test_fetch( project_id, remote_name, @@ -57,66 +52,49 @@ pub async fn git_test_fetch( } #[tauri::command(async)] -#[instrument(skip(handle), err(Debug))] -pub async fn git_index_size( - handle: tauri::AppHandle, - project_id: ProjectId, -) -> Result { - let app = handle.state::(); +#[instrument(skip(app), err(Debug))] +pub async fn git_index_size(app: State<'_, App>, project_id: ProjectId) -> Result { Ok(app.git_index_size(project_id).expect("git index size")) } #[tauri::command(async)] -#[instrument(skip(handle), err(Debug))] -pub async fn git_head(handle: tauri::AppHandle, project_id: ProjectId) -> Result { - let app = handle.state::(); - let head = app.git_head(project_id)?; - Ok(head) +#[instrument(skip(app), err(Debug))] +pub async fn git_head(app: State<'_, App>, project_id: ProjectId) -> Result { + Ok(app.git_head(project_id)?) } #[tauri::command(async)] -#[instrument(skip(handle), err(Debug))] -pub async fn delete_all_data(handle: tauri::AppHandle) -> Result<(), Error> { - let app = handle.state::(); +#[instrument(skip(app), err(Debug))] +pub async fn delete_all_data(app: State<'_, App>) -> Result<(), Error> { app.delete_all_data().await?; Ok(()) } #[tauri::command(async)] -#[instrument(skip(handle), err(Debug))] +#[instrument(skip(app), err(Debug))] pub async fn mark_resolved( - handle: tauri::AppHandle, + app: State<'_, App>, project_id: ProjectId, path: &str, ) -> Result<(), Error> { - let app = handle.state::(); app.mark_resolved(project_id, path)?; Ok(()) } #[tauri::command(async)] -#[instrument(skip(_handle), err(Debug))] -pub async fn git_set_global_config( - _handle: tauri::AppHandle, - key: &str, - value: &str, -) -> Result { - let result = app::App::git_set_global_config(key, value)?; - Ok(result) +#[instrument(err(Debug))] +pub async fn git_set_global_config(key: &str, value: &str) -> Result { + Ok(App::git_set_global_config(key, value)?) } #[tauri::command(async)] #[instrument(err(Debug))] pub async fn git_remove_global_config(key: &str) -> Result<(), Error> { - Ok(app::App::git_remove_global_config(key)?) + Ok(App::git_remove_global_config(key)?) } #[tauri::command(async)] -#[instrument(skip(_handle), err(Debug))] -pub async fn git_get_global_config( - _handle: tauri::AppHandle, - key: &str, -) -> Result, Error> { - let result = app::App::git_get_global_config(key)?; - Ok(result) +#[instrument(err(Debug))] +pub async fn git_get_global_config(key: &str) -> Result, Error> { + Ok(App::git_get_global_config(key)?) } diff --git a/crates/gitbutler-tauri/src/config.rs b/crates/gitbutler-tauri/src/config.rs index 546d8e571..db381b836 100644 --- a/crates/gitbutler-tauri/src/config.rs +++ b/crates/gitbutler-tauri/src/config.rs @@ -2,31 +2,26 @@ use crate::error::Error; use gitbutler_config::{api::ProjectCommands, git::GbConfig}; use gitbutler_project as projects; use gitbutler_project::ProjectId; -use tauri::Manager; +use tauri::State; use tracing::instrument; #[tauri::command(async)] -#[instrument(skip(handle), err(Debug))] +#[instrument(skip(projects), err(Debug))] pub async fn get_gb_config( - handle: tauri::AppHandle, + projects: State<'_, projects::Controller>, project_id: ProjectId, ) -> Result { - handle - .state::() - .get(project_id)? - .gb_config() - .map_err(Into::into) + projects.get(project_id)?.gb_config().map_err(Into::into) } #[tauri::command(async)] -#[instrument(skip(handle), err(Debug))] +#[instrument(skip(projects), err(Debug))] pub async fn set_gb_config( - handle: tauri::AppHandle, + projects: State<'_, projects::Controller>, project_id: ProjectId, config: GbConfig, ) -> Result<(), Error> { - handle - .state::() + projects .get(project_id)? .set_gb_config(config) .map_err(Into::into) diff --git a/crates/gitbutler-tauri/src/lib.rs b/crates/gitbutler-tauri/src/lib.rs index 43f79ea82..da77be939 100644 --- a/crates/gitbutler-tauri/src/lib.rs +++ b/crates/gitbutler-tauri/src/lib.rs @@ -13,7 +13,9 @@ clippy::too_many_lines )] -pub mod app; +mod app; +pub use app::App; + pub mod commands; pub mod logs; diff --git a/crates/gitbutler-tauri/src/main.rs b/crates/gitbutler-tauri/src/main.rs index 7c88c3819..d3af37eb4 100644 --- a/crates/gitbutler-tauri/src/main.rs +++ b/crates/gitbutler-tauri/src/main.rs @@ -13,11 +13,10 @@ clippy::too_many_lines )] -use gitbutler_repo::credentials::Helper; -use gitbutler_storage::storage; +use gitbutler_repo::credentials; use gitbutler_tauri::{ - app, askpass, commands, config, github, logs, menu, projects, remotes, repo, secret, undo, - users, virtual_branches, zip, WindowState, + askpass, commands, config, github, logs, menu, projects, remotes, repo, secret, undo, users, + virtual_branches, zip, App, WindowState, }; use tauri::{generate_context, Manager}; use tauri_plugin_log::LogTarget; @@ -42,13 +41,18 @@ fn main() { tauri::Builder::default() .setup(move |tauri_app| { - let window = - gitbutler_tauri::window::create(&tauri_app.handle(), "main", "index.html".into()).expect("Failed to create window"); + let window = gitbutler_tauri::window::create( + &tauri_app.handle(), + "main", + "index.html".into(), + ) + .expect("Failed to create window"); #[cfg(debug_assertions)] window.open_devtools(); tokio::task::spawn(async move { - let mut six_hours = tokio::time::interval(tokio::time::Duration::new(6 * 60 * 60, 0)); + let mut six_hours = + tokio::time::interval(tokio::time::Duration::new(6 * 60 * 60, 0)); loop { six_hours.tick().await; _ = window.emit_and_trigger("tauri://update", ()); @@ -73,52 +77,41 @@ fn main() { gitbutler_repo::askpass::init({ let handle = app_handle.clone(); move |event| { - handle.emit_all("git_prompt", event).expect("tauri event emission doesn't fail in practice") + handle + .emit_all("git_prompt", event) + .expect("tauri event emission doesn't fail in practice") } }); } - let app_data_dir = app_handle.path_resolver().app_data_dir().expect("missing app data dir"); - let app_cache_dir = app_handle.path_resolver().app_cache_dir().expect("missing app cache dir"); - let app_log_dir = app_handle.path_resolver().app_log_dir().expect("missing app log dir"); - + let (app_data_dir, app_cache_dir, app_log_dir) = { + let paths = app_handle.path_resolver(); + ( + paths.app_data_dir().expect("missing app data dir"), + paths.app_cache_dir().expect("missing app cache dir"), + paths.app_log_dir().expect("missing app log dir"), + ) + }; std::fs::create_dir_all(&app_data_dir).expect("failed to create app data dir"); std::fs::create_dir_all(&app_cache_dir).expect("failed to create cache dir"); - tracing::info!(version = %app_handle.package_info().version, name = %app_handle.package_info().name, "starting app"); - - let storage_controller = storage::Storage::new(&app_data_dir); - app_handle.manage(storage_controller.clone()); + tracing::info!(version = %app_handle.package_info().version, + name = %app_handle.package_info().name, "starting app"); app_handle.manage(WindowState::new(app_handle.clone())); - let projects_storage_controller = gitbutler_project::storage::Storage::new(storage_controller.clone()); - app_handle.manage(projects_storage_controller.clone()); - - let users_storage_controller = gitbutler_user::storage::Storage::new(storage_controller.clone()); - app_handle.manage(users_storage_controller.clone()); - - let users_controller = gitbutler_user::Controller::new(users_storage_controller.clone()); - app_handle.manage(users_controller.clone()); - - let projects_controller = gitbutler_project::Controller::new( - app_data_dir.clone(), - projects_storage_controller.clone() - ); - app_handle.manage(projects_controller.clone()); - - let zipper = gitbutler_feedback::zipper::Zipper::new(&app_cache_dir); - app_handle.manage(zipper.clone()); - - app_handle.manage(gitbutler_feedback::controller::Controller::new(app_data_dir.clone(), app_log_dir.clone(), zipper.clone(), projects_controller.clone())); - - let git_credentials_controller = Helper::default(); - app_handle.manage(git_credentials_controller.clone()); - - let app = app::App::new( - projects_controller, - ); + let app = App { + app_data_dir: app_data_dir.clone(), + }; + app_handle.manage(app.users()); + app_handle.manage(app.projects()); + app_handle.manage(gitbutler_feedback::Archival { + cache_dir: app_cache_dir, + logs_dir: app_log_dir, + projects_controller: app.projects(), + }); + app_handle.manage(credentials::Helper::default()); app_handle.manage(app); Ok(()) @@ -199,28 +192,33 @@ fn main() { remotes::add_remote ]) .menu(menu::build(tauri_context.package_info())) - .on_menu_event(|event|menu::handle_event(&event)) + .on_menu_event(|event| menu::handle_event(&event)) .on_window_event(|event| { let window = event.window(); match event.event() { #[cfg(target_os = "macos")] tauri::WindowEvent::CloseRequested { api, .. } => { if window.app_handle().windows().len() == 1 { - tracing::debug!("Hiding all application windows and preventing exit"); + tracing::debug!( + "Hiding all application windows and preventing exit" + ); window.app_handle().hide().ok(); api.prevent_close(); } } - tauri::WindowEvent::Destroyed => { - window.app_handle() + tauri::WindowEvent::Destroyed => { + window + .app_handle() .state::() .remove(window.label()); } tauri::WindowEvent::Focused(focused) if *focused => { - window.app_handle() + window + .app_handle() .state::() - .flush(window.label()).ok(); - }, + .flush(window.label()) + .ok(); + } _ => {} } }) diff --git a/crates/gitbutler-tauri/src/remotes.rs b/crates/gitbutler-tauri/src/remotes.rs index 27cbd4150..21f0a55a5 100644 --- a/crates/gitbutler-tauri/src/remotes.rs +++ b/crates/gitbutler-tauri/src/remotes.rs @@ -2,27 +2,27 @@ use crate::error::Error; use gitbutler_project as projects; use gitbutler_project::ProjectId; use gitbutler_repo::RepoCommands; -use tauri::Manager; +use tauri::State; use tracing::instrument; #[tauri::command(async)] -#[instrument(skip(handle), err(Debug))] +#[instrument(skip(projects), err(Debug))] pub async fn list_remotes( - handle: tauri::AppHandle, + projects: State<'_, projects::Controller>, project_id: ProjectId, ) -> Result, Error> { - let project = handle.state::().get(project_id)?; + let project = projects.get(project_id)?; project.remotes().map_err(Into::into) } #[tauri::command(async)] -#[instrument(skip(handle), err(Debug))] +#[instrument(skip(projects), err(Debug))] pub async fn add_remote( - handle: tauri::AppHandle, + projects: State<'_, projects::Controller>, project_id: ProjectId, name: &str, url: &str, ) -> Result<(), Error> { - let project = handle.state::().get(project_id)?; + let project = projects.get(project_id)?; project.add_remote(name, url).map_err(Into::into) } diff --git a/crates/gitbutler-tauri/src/repo.rs b/crates/gitbutler-tauri/src/repo.rs index 6e8a6d9e5..7f1a15ef2 100644 --- a/crates/gitbutler-tauri/src/repo.rs +++ b/crates/gitbutler-tauri/src/repo.rs @@ -3,39 +3,39 @@ pub mod commands { use gitbutler_project as projects; use gitbutler_project::ProjectId; use gitbutler_repo::RepoCommands; - use tauri::Manager; + use tauri::State; use tracing::instrument; #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects), err(Debug))] pub async fn git_get_local_config( - handle: tauri::AppHandle, + projects: State<'_, projects::Controller>, id: ProjectId, key: &str, ) -> Result, Error> { - let project = handle.state::().get(id)?; + let project = projects.get(id)?; Ok(project.get_local_config(key)?) } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects), err(Debug))] pub async fn git_set_local_config( - handle: tauri::AppHandle, + projects: State<'_, projects::Controller>, id: ProjectId, key: &str, value: &str, ) -> Result<(), Error> { - let project = handle.state::().get(id)?; + let project = projects.get(id)?; project.set_local_config(key, value).map_err(Into::into) } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects), err(Debug))] pub async fn check_signing_settings( - handle: tauri::AppHandle, + projects: State<'_, projects::Controller>, id: ProjectId, ) -> Result { - let project = handle.state::().get(id)?; + let project = projects.get(id)?; project.check_signing_settings().map_err(Into::into) } } diff --git a/crates/gitbutler-tauri/src/undo.rs b/crates/gitbutler-tauri/src/undo.rs index e325aa838..5d6330056 100644 --- a/crates/gitbutler-tauri/src/undo.rs +++ b/crates/gitbutler-tauri/src/undo.rs @@ -7,21 +7,18 @@ use gitbutler_project as projects; use gitbutler_project::ProjectId; use std::collections::HashMap; use std::path::PathBuf; -use tauri::Manager; +use tauri::State; use tracing::instrument; #[tauri::command(async)] -#[instrument(skip(handle), err(Debug))] +#[instrument(skip(projects), err(Debug))] pub async fn list_snapshots( - handle: tauri::AppHandle, + projects: State<'_, projects::Controller>, project_id: ProjectId, limit: usize, sha: Option, ) -> Result, Error> { - let project = handle - .state::() - .get(project_id) - .context("failed to get project")?; + let project = projects.get(project_id).context("failed to get project")?; let snapshots = project.list_snapshots( limit, sha.map(|hex| hex.parse().map_err(anyhow::Error::from)) @@ -31,31 +28,26 @@ pub async fn list_snapshots( } #[tauri::command(async)] -#[instrument(skip(handle), err(Debug))] +#[instrument(skip(projects), err(Debug))] pub async fn restore_snapshot( + projects: State<'_, projects::Controller>, handle: tauri::AppHandle, project_id: ProjectId, sha: String, ) -> Result<(), Error> { - let project = handle - .state::() - .get(project_id) - .context("failed to get project")?; + let project = projects.get(project_id).context("failed to get project")?; project.restore_snapshot(sha.parse().map_err(anyhow::Error::from)?)?; Ok(()) } #[tauri::command(async)] -#[instrument(skip(handle), err(Debug))] +#[instrument(skip(projects), err(Debug))] pub async fn snapshot_diff( - handle: tauri::AppHandle, + projects: State<'_, projects::Controller>, project_id: ProjectId, sha: String, ) -> Result, Error> { - let project = handle - .state::() - .get(project_id) - .context("failed to get project")?; + let project = projects.get(project_id).context("failed to get project")?; let diff = project.snapshot_diff(sha.parse().map_err(anyhow::Error::from)?)?; Ok(diff) } diff --git a/crates/gitbutler-tauri/src/users.rs b/crates/gitbutler-tauri/src/users.rs index 48e310525..3463bbe5a 100644 --- a/crates/gitbutler-tauri/src/users.rs +++ b/crates/gitbutler-tauri/src/users.rs @@ -1,20 +1,18 @@ pub mod commands { - use gitbutler_user::{controller::Controller, User}; + use gitbutler_user::{Controller, User}; use serde::{Deserialize, Serialize}; - use tauri::{AppHandle, Manager}; + use tauri::State; use tracing::instrument; use crate::error::Error; #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] - pub async fn get_user(handle: AppHandle) -> Result, Error> { - let app = handle.state::(); - - match app.get_user()? { + #[instrument(skip(login), err(Debug))] + pub async fn get_user(login: State<'_, Controller>) -> Result, Error> { + match login.get_user()? { Some(user) => { if let Err(err) = user.access_token() { - app.delete_user()?; + login.delete_user()?; return Err(err.context("Please login to GitButler again").into()); } Ok(Some(user.try_into()?)) @@ -24,21 +22,16 @@ pub mod commands { } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] - pub async fn set_user(handle: AppHandle, user: User) -> Result { - let app = handle.state::(); - - app.set_user(&user)?; + #[instrument(skip(login), err(Debug))] + pub async fn set_user(login: State<'_, Controller>, user: User) -> Result { + login.set_user(&user)?; Ok(user) } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] - pub async fn delete_user(handle: AppHandle) -> Result<(), Error> { - let app = handle.state::(); - - app.delete_user()?; - + #[instrument(skip(login), err(Debug))] + pub async fn delete_user(login: State<'_, Controller>) -> Result<(), Error> { + login.delete_user()?; Ok(()) } diff --git a/crates/gitbutler-tauri/src/virtual_branches.rs b/crates/gitbutler-tauri/src/virtual_branches.rs index 6ea005254..12665255a 100644 --- a/crates/gitbutler-tauri/src/virtual_branches.rs +++ b/crates/gitbutler-tauri/src/virtual_branches.rs @@ -12,26 +12,27 @@ pub mod commands { use gitbutler_project::ProjectId; use gitbutler_reference::ReferenceName; use gitbutler_reference::{Refname, RemoteRefname}; - use tauri::{AppHandle, Manager, State}; + use tauri::State; use tracing::instrument; use crate::WindowState; #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects, windows), err(Debug))] pub async fn commit_virtual_branch( - handle: AppHandle, + windows: State<'_, WindowState>, + projects: State<'_, projects::Controller>, project_id: ProjectId, branch: BranchId, message: &str, ownership: Option, run_hooks: bool, ) -> Result { - let project = handle.state::().get(project_id)?; + let project = projects.get(project_id)?; let oid = VirtualBranchActions .create_commit(&project, branch, message, ownership.as_ref(), run_hooks) .await?; - emit_vbranches(&handle, project_id).await; + emit_vbranches(&windows, project_id).await; Ok(oid.to_string()) } @@ -53,47 +54,50 @@ pub mod commands { } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects, windows), err(Debug))] pub async fn create_virtual_branch( - handle: AppHandle, + windows: State<'_, WindowState>, + projects: State<'_, projects::Controller>, project_id: ProjectId, branch: BranchCreateRequest, ) -> Result { - let project = handle.state::().get(project_id)?; + let project = projects.get(project_id)?; let branch_id = VirtualBranchActions .create_virtual_branch(&project, &branch) .await?; - emit_vbranches(&handle, project_id).await; + emit_vbranches(&windows, project_id).await; Ok(branch_id) } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects, windows), err(Debug))] pub async fn create_virtual_branch_from_branch( - handle: AppHandle, + windows: State<'_, WindowState>, + projects: State<'_, projects::Controller>, project_id: ProjectId, branch: Refname, ) -> Result { - let project = handle.state::().get(project_id)?; + let project = projects.get(project_id)?; let branch_id = VirtualBranchActions .create_virtual_branch_from_branch(&project, &branch) .await?; - emit_vbranches(&handle, project_id).await; + emit_vbranches(&windows, project_id).await; Ok(branch_id) } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects, windows), err(Debug))] pub async fn integrate_upstream_commits( - handle: AppHandle, + windows: State<'_, WindowState>, + projects: State<'_, projects::Controller>, project_id: ProjectId, branch: BranchId, ) -> Result<(), Error> { - let project = handle.state::().get(project_id)?; + let project = projects.get(project_id)?; VirtualBranchActions .integrate_upstream_commits(&project, branch) .await?; - emit_vbranches(&handle, project_id).await; + emit_vbranches(&windows, project_id).await; Ok(()) } @@ -112,14 +116,15 @@ pub mod commands { } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects, windows), err(Debug))] pub async fn set_base_branch( - handle: AppHandle, + windows: State<'_, WindowState>, + projects: State<'_, projects::Controller>, project_id: ProjectId, branch: &str, push_remote: Option<&str>, // optional different name of a remote to push to (defaults to same as the branch) ) -> Result { - let project = handle.state::().get(project_id)?; + let project = projects.get(project_id)?; let branch_name = format!("refs/remotes/{}", branch) .parse() .context("Invalid branch name")?; @@ -133,140 +138,147 @@ pub mod commands { .set_target_push_remote(&project, push_remote) .await?; } - emit_vbranches(&handle, project_id).await; + emit_vbranches(&windows, project_id).await; Ok(base_branch) } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects, windows), err(Debug))] pub async fn update_base_branch( - handle: AppHandle, + windows: State<'_, WindowState>, + projects: State<'_, projects::Controller>, project_id: ProjectId, ) -> Result, Error> { - let project = handle.state::().get(project_id)?; + let project = projects.get(project_id)?; let unapplied_branches = VirtualBranchActions.update_base_branch(&project).await?; - emit_vbranches(&handle, project_id).await; + emit_vbranches(&windows, project_id).await; Ok(unapplied_branches) } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects, windows), err(Debug))] pub async fn update_virtual_branch( - handle: AppHandle, + windows: State<'_, WindowState>, + projects: State<'_, projects::Controller>, project_id: ProjectId, branch: BranchUpdateRequest, ) -> Result<(), Error> { - let project = handle.state::().get(project_id)?; + let project = projects.get(project_id)?; VirtualBranchActions .update_virtual_branch(&project, branch) .await?; - emit_vbranches(&handle, project_id).await; + emit_vbranches(&windows, project_id).await; Ok(()) } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects, windows), err(Debug))] pub async fn delete_virtual_branch( - handle: AppHandle, + windows: State<'_, WindowState>, + projects: State<'_, projects::Controller>, project_id: ProjectId, branch_id: BranchId, ) -> Result<(), Error> { - let project = handle.state::().get(project_id)?; + let project = projects.get(project_id)?; VirtualBranchActions .delete_virtual_branch(&project, branch_id) .await?; - emit_vbranches(&handle, project_id).await; + emit_vbranches(&windows, project_id).await; Ok(()) } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects, windows), err(Debug))] pub async fn convert_to_real_branch( - handle: AppHandle, + windows: State<'_, WindowState>, + projects: State<'_, projects::Controller>, project_id: ProjectId, branch: BranchId, name_conflict_resolution: NameConflictResolution, ) -> Result<(), Error> { - let project = handle.state::().get(project_id)?; + let project = projects.get(project_id)?; VirtualBranchActions .convert_to_real_branch(&project, branch, name_conflict_resolution) .await?; - emit_vbranches(&handle, project_id).await; + emit_vbranches(&windows, project_id).await; Ok(()) } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects, windows), err(Debug))] pub async fn unapply_ownership( - handle: AppHandle, + windows: State<'_, WindowState>, + projects: State<'_, projects::Controller>, project_id: ProjectId, ownership: BranchOwnershipClaims, ) -> Result<(), Error> { - let project = handle.state::().get(project_id)?; + let project = projects.get(project_id)?; VirtualBranchActions .unapply_ownership(&project, &ownership) .await?; - emit_vbranches(&handle, project_id).await; + emit_vbranches(&windows, project_id).await; Ok(()) } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects, windows), err(Debug))] pub async fn reset_files( - handle: AppHandle, + windows: State<'_, WindowState>, + projects: State<'_, projects::Controller>, project_id: ProjectId, files: &str, ) -> Result<(), Error> { - let project = handle.state::().get(project_id)?; + let project = projects.get(project_id)?; // convert files to Vec let files = files .split('\n') .map(std::string::ToString::to_string) .collect::>(); VirtualBranchActions.reset_files(&project, &files).await?; - emit_vbranches(&handle, project_id).await; + emit_vbranches(&windows, project_id).await; Ok(()) } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects, windows), err(Debug))] pub async fn push_virtual_branch( - handle: AppHandle, + windows: State<'_, WindowState>, + projects: State<'_, projects::Controller>, project_id: ProjectId, branch_id: BranchId, with_force: bool, ) -> Result<(), Error> { - let project = handle.state::().get(project_id)?; + let project = projects.get(project_id)?; VirtualBranchActions .push_virtual_branch(&project, branch_id, with_force, Some(Some(branch_id))) .await .map_err(|err| err.context(Code::Unknown))?; - emit_vbranches(&handle, project_id).await; + emit_vbranches(&windows, project_id).await; Ok(()) } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects), err(Debug))] pub async fn can_apply_remote_branch( - handle: AppHandle, + projects: State<'_, projects::Controller>, project_id: ProjectId, branch: RemoteRefname, ) -> Result { - let project = handle.state::().get(project_id)?; + let project = projects.get(project_id)?; Ok(VirtualBranchActions .can_apply_remote_branch(&project, &branch) .await?) } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects), err(Debug))] pub async fn list_remote_commit_files( - handle: AppHandle, + projects: State<'_, projects::Controller>, project_id: ProjectId, commit_oid: String, ) -> Result, Error> { - let project = handle.state::().get(project_id)?; + let project = projects.get(project_id)?; let commit_oid = git2::Oid::from_str(&commit_oid).map_err(|e| anyhow!(e))?; VirtualBranchActions .list_remote_commit_files(&project, commit_oid) @@ -275,51 +287,54 @@ pub mod commands { } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects, windows), err(Debug))] pub async fn reset_virtual_branch( - handle: AppHandle, + windows: State<'_, WindowState>, + projects: State<'_, projects::Controller>, project_id: ProjectId, branch_id: BranchId, target_commit_oid: String, ) -> Result<(), Error> { - let project = handle.state::().get(project_id)?; + let project = projects.get(project_id)?; let target_commit_oid = git2::Oid::from_str(&target_commit_oid).map_err(|e| anyhow!(e))?; VirtualBranchActions .reset_virtual_branch(&project, branch_id, target_commit_oid) .await?; - emit_vbranches(&handle, project_id).await; + emit_vbranches(&windows, project_id).await; Ok(()) } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects, windows), err(Debug))] pub async fn amend_virtual_branch( - handle: AppHandle, + windows: State<'_, WindowState>, + projects: State<'_, projects::Controller>, project_id: ProjectId, branch_id: BranchId, commit_oid: String, ownership: BranchOwnershipClaims, ) -> Result { - let project = handle.state::().get(project_id)?; + let project = projects.get(project_id)?; let commit_oid = git2::Oid::from_str(&commit_oid).map_err(|e| anyhow!(e))?; let oid = VirtualBranchActions .amend(&project, branch_id, commit_oid, &ownership) .await?; - emit_vbranches(&handle, project_id).await; + emit_vbranches(&windows, project_id).await; Ok(oid.to_string()) } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects, windows), err(Debug))] pub async fn move_commit_file( - handle: AppHandle, + windows: State<'_, WindowState>, + projects: State<'_, projects::Controller>, project_id: ProjectId, branch_id: BranchId, from_commit_oid: String, to_commit_oid: String, ownership: BranchOwnershipClaims, ) -> Result { - let project = handle.state::().get(project_id)?; + let project = projects.get(project_id)?; let from_commit_oid = git2::Oid::from_str(&from_commit_oid).map_err(|e| anyhow!(e))?; let to_commit_oid = git2::Oid::from_str(&to_commit_oid).map_err(|e| anyhow!(e))?; let oid = VirtualBranchActions @@ -331,60 +346,63 @@ pub mod commands { &ownership, ) .await?; - emit_vbranches(&handle, project_id).await; + emit_vbranches(&windows, project_id).await; Ok(oid.to_string()) } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects, windows), err(Debug))] pub async fn undo_commit( - handle: AppHandle, + windows: State<'_, WindowState>, + projects: State<'_, projects::Controller>, project_id: ProjectId, branch_id: BranchId, commit_oid: String, ) -> Result<(), Error> { - let project = handle.state::().get(project_id)?; + let project = projects.get(project_id)?; let commit_oid = git2::Oid::from_str(&commit_oid).map_err(|e| anyhow!(e))?; VirtualBranchActions .undo_commit(&project, branch_id, commit_oid) .await?; - emit_vbranches(&handle, project_id).await; + emit_vbranches(&windows, project_id).await; Ok(()) } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects, windows), err(Debug))] pub async fn insert_blank_commit( - handle: AppHandle, + windows: State<'_, WindowState>, + projects: State<'_, projects::Controller>, project_id: ProjectId, branch_id: BranchId, commit_oid: String, offset: i32, ) -> Result<(), Error> { - let project = handle.state::().get(project_id)?; + let project = projects.get(project_id)?; let commit_oid = git2::Oid::from_str(&commit_oid).map_err(|e| anyhow!(e))?; VirtualBranchActions .insert_blank_commit(&project, branch_id, commit_oid, offset) .await?; - emit_vbranches(&handle, project_id).await; + emit_vbranches(&windows, project_id).await; Ok(()) } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects, windows), err(Debug))] pub async fn reorder_commit( - handle: AppHandle, + windows: State<'_, WindowState>, + projects: State<'_, projects::Controller>, project_id: ProjectId, branch_id: BranchId, commit_oid: String, offset: i32, ) -> Result<(), Error> { - let project = handle.state::().get(project_id)?; + let project = projects.get(project_id)?; let commit_oid = git2::Oid::from_str(&commit_oid).map_err(|e| anyhow!(e))?; VirtualBranchActions .reorder_commit(&project, branch_id, commit_oid, offset) .await?; - emit_vbranches(&handle, project_id).await; + emit_vbranches(&windows, project_id).await; Ok(()) } @@ -400,13 +418,13 @@ pub mod commands { } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects), err(Debug))] pub async fn get_remote_branch_data( - handle: tauri::AppHandle, + projects: State<'_, projects::Controller>, project_id: ProjectId, refname: Refname, ) -> Result { - let project = handle.state::().get(project_id)?; + let project = projects.get(project_id)?; let branch_data = VirtualBranchActions .get_remote_branch_data(&project, &refname) .await?; @@ -414,19 +432,20 @@ pub mod commands { } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects, windows), err(Debug))] pub async fn squash_branch_commit( - handle: tauri::AppHandle, + windows: State<'_, WindowState>, + projects: State<'_, projects::Controller>, project_id: ProjectId, branch_id: BranchId, target_commit_oid: String, ) -> Result<(), Error> { - let project = handle.state::().get(project_id)?; + let project = projects.get(project_id)?; let target_commit_oid = git2::Oid::from_str(&target_commit_oid).map_err(|e| anyhow!(e))?; VirtualBranchActions .squash(&project, branch_id, target_commit_oid) .await?; - emit_vbranches(&handle, project_id).await; + emit_vbranches(&windows, project_id).await; Ok(()) } @@ -463,43 +482,44 @@ pub mod commands { } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects, windows), err(Debug))] pub async fn move_commit( - handle: tauri::AppHandle, + windows: State<'_, WindowState>, + projects: State<'_, projects::Controller>, project_id: ProjectId, commit_oid: String, target_branch_id: BranchId, ) -> Result<(), Error> { - let project = handle.state::().get(project_id)?; + let project = projects.get(project_id)?; let commit_oid = git2::Oid::from_str(&commit_oid).map_err(|e| anyhow!(e))?; VirtualBranchActions .move_commit(&project, target_branch_id, commit_oid) .await?; - emit_vbranches(&handle, project_id).await; + emit_vbranches(&windows, project_id).await; Ok(()) } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(projects, windows), err(Debug))] pub async fn update_commit_message( - handle: tauri::AppHandle, + windows: State<'_, WindowState>, + projects: State<'_, projects::Controller>, project_id: ProjectId, branch_id: BranchId, commit_oid: String, message: &str, ) -> Result<(), Error> { - let project = handle.state::().get(project_id)?; + let project = projects.get(project_id)?; let commit_oid = git2::Oid::from_str(&commit_oid).map_err(|e| anyhow!(e))?; VirtualBranchActions .update_commit_message(&project, branch_id, commit_oid, message) .await?; - emit_vbranches(&handle, project_id).await; + emit_vbranches(&windows, project_id).await; Ok(()) } - async fn emit_vbranches(handle: &AppHandle, project_id: projects::ProjectId) { - if let Err(error) = handle - .state::() + async fn emit_vbranches(windows: &WindowState, project_id: projects::ProjectId) { + if let Err(error) = windows .post(gitbutler_watcher::Action::CalculateVirtualBranches( project_id, )) diff --git a/crates/gitbutler-tauri/src/zip.rs b/crates/gitbutler-tauri/src/zip.rs index b7d067703..e915851d7 100644 --- a/crates/gitbutler-tauri/src/zip.rs +++ b/crates/gitbutler-tauri/src/zip.rs @@ -1,54 +1,44 @@ pub mod commands { #![allow(clippy::used_underscore_binding)] use anyhow::Context; - use gitbutler_feedback::controller; - use std::path; - use gitbutler_error::error; use gitbutler_error::error::Code; - use tauri::{AppHandle, Manager}; + use gitbutler_feedback::Archival; + use std::path::PathBuf; + use tauri::State; use tracing::instrument; use crate::error::Error; #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(archival), err(Debug))] pub async fn get_project_archive_path( - handle: AppHandle, + archival: State<'_, Archival>, project_id: &str, - ) -> Result { + ) -> Result { let project_id = project_id.parse().context(error::Context::new_static( Code::Validation, "Malformed project id", ))?; - handle - .state::() - .archive(project_id) - .map_err(Into::into) + archival.archive(project_id).map_err(Into::into) } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] + #[instrument(skip(archival), err(Debug))] pub async fn get_project_data_archive_path( - handle: AppHandle, + archival: State<'_, Archival>, project_id: &str, - ) -> Result { + ) -> Result { let project_id = project_id.parse().context(error::Context::new_static( Code::Validation, "Malformed project id", ))?; - handle - .state::() - .data_archive(project_id) - .map_err(Into::into) + archival.data_archive(project_id).map_err(Into::into) } #[tauri::command(async)] - #[instrument(skip(handle), err(Debug))] - pub async fn get_logs_archive_path(handle: AppHandle) -> Result { - handle - .state::() - .logs_archive() - .map_err(Into::into) + #[instrument(skip(archival), err(Debug))] + pub async fn get_logs_archive_path(archival: State<'_, Archival>) -> Result { + archival.logs_archive().map_err(Into::into) } } diff --git a/crates/gitbutler-testsupport/src/suite.rs b/crates/gitbutler-testsupport/src/suite.rs index b7ac5513a..41ea679c2 100644 --- a/crates/gitbutler-testsupport/src/suite.rs +++ b/crates/gitbutler-testsupport/src/suite.rs @@ -12,7 +12,7 @@ use crate::{init_opts, init_opts_bare, VAR_NO_CLEANUP}; pub struct Suite { pub local_app_data: Option, - pub storage: gitbutler_storage::storage::Storage, + pub storage: gitbutler_storage::Storage, pub users: gitbutler_user::Controller, pub projects: gitbutler_project::Controller, } @@ -28,7 +28,7 @@ impl Drop for Suite { impl Default for Suite { fn default() -> Self { let local_app_data = temp_dir(); - let storage = gitbutler_storage::storage::Storage::new(local_app_data.path()); + let storage = gitbutler_storage::Storage::new(local_app_data.path()); let users = gitbutler_user::Controller::from_path(local_app_data.path()); let projects = gitbutler_project::Controller::from_path(local_app_data.path()); Self { diff --git a/crates/gitbutler-user/src/controller.rs b/crates/gitbutler-user/src/controller.rs index f3c32ebf9..f1423d008 100644 --- a/crates/gitbutler-user/src/controller.rs +++ b/crates/gitbutler-user/src/controller.rs @@ -17,12 +17,10 @@ pub struct Controller { } impl Controller { - pub fn new(storage: Storage) -> Controller { - Controller { storage } - } - pub fn from_path(path: impl Into) -> Controller { - Controller::new(Storage::from_path(path)) + Controller { + storage: Storage::from_path(path), + } } /// Return the current login, or `None` if there is none yet. diff --git a/crates/gitbutler-user/src/lib.rs b/crates/gitbutler-user/src/lib.rs index 7c26c7204..f463ee540 100644 --- a/crates/gitbutler-user/src/lib.rs +++ b/crates/gitbutler-user/src/lib.rs @@ -1,6 +1,7 @@ -pub mod controller; -pub mod storage; -mod user; +mod controller; -pub use controller::*; +mod storage; +pub use controller::Controller; + +mod user; pub use user::User; diff --git a/crates/gitbutler-user/src/storage.rs b/crates/gitbutler-user/src/storage.rs index 01ebd9afa..b88398698 100644 --- a/crates/gitbutler-user/src/storage.rs +++ b/crates/gitbutler-user/src/storage.rs @@ -1,24 +1,20 @@ use anyhow::Result; use std::path::PathBuf; -use gitbutler_storage::storage as core_storage; - use crate::User; const USER_FILE: &str = "user.json"; #[derive(Debug, Clone)] -pub struct Storage { - inner: core_storage::Storage, +pub(crate) struct Storage { + inner: gitbutler_storage::Storage, } impl Storage { - pub fn new(storage: core_storage::Storage) -> Storage { - Storage { inner: storage } - } - pub fn from_path(path: impl Into) -> Storage { - Storage::new(core_storage::Storage::new(path)) + Storage { + inner: gitbutler_storage::Storage::new(path), + } } pub fn get(&self) -> Result> { From 3e79238e7fa76ef5631878ec83f51e59476566e2 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 15 Jul 2024 08:08:38 +0200 Subject: [PATCH 10/13] assure `conflicts::mark()` writes its file atomically That way it can't be observed half-written, or remain in a half-written state in case of crash. --- Cargo.lock | 1 + crates/gitbutler-branch-actions/Cargo.toml | 1 + .../gitbutler-branch-actions/src/conflicts.rs | 37 +++--- crates/gitbutler-branch/src/state.rs | 4 +- crates/gitbutler-fs/src/fs.rs | 115 ----------------- crates/gitbutler-fs/src/lib.rs | 116 +++++++++++++++++- crates/gitbutler-oplog/src/reflog.rs | 2 +- crates/gitbutler-oplog/src/state.rs | 4 +- crates/gitbutler-storage/src/storage.rs | 2 +- 9 files changed, 141 insertions(+), 141 deletions(-) delete mode 100644 crates/gitbutler-fs/src/fs.rs diff --git a/Cargo.lock b/Cargo.lock index 9962849ce..d2c08028f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2005,6 +2005,7 @@ dependencies = [ "gitbutler-command-context", "gitbutler-commit", "gitbutler-error", + "gitbutler-fs", "gitbutler-git", "gitbutler-id", "gitbutler-oplog", diff --git a/crates/gitbutler-branch-actions/Cargo.toml b/crates/gitbutler-branch-actions/Cargo.toml index 9e53ace65..af2189755 100644 --- a/crates/gitbutler-branch-actions/Cargo.toml +++ b/crates/gitbutler-branch-actions/Cargo.toml @@ -21,6 +21,7 @@ gitbutler-id.workspace = true gitbutler-time.workspace = true gitbutler-commit.workspace = true gitbutler-url.workspace = true +gitbutler-fs.workspace = true serde = { workspace = true, features = ["std"] } bstr = "1.9.1" diffy = "0.3.0" diff --git a/crates/gitbutler-branch-actions/src/conflicts.rs b/crates/gitbutler-branch-actions/src/conflicts.rs index 40fc5a334..a7b037730 100644 --- a/crates/gitbutler-branch-actions/src/conflicts.rs +++ b/crates/gitbutler-branch-actions/src/conflicts.rs @@ -1,10 +1,9 @@ -// stuff to manage merge conflict state -// this is the dumbest possible way to do this, but it is a placeholder -// conflicts are stored one path per line in .git/conflicts -// merge parent is stored in .git/base_merge_parent -// conflicts are removed as they are resolved, the conflicts file is removed when there are no more conflicts -// the merge parent file is removed when the merge is complete - +/// stuff to manage merge conflict state. +/// This is the dumbest possible way to do this, but it is a placeholder. +/// Conflicts are stored one path per line in .git/conflicts. +/// Merge parent is stored in .git/base_merge_parent. +/// Conflicts are removed as they are resolved, the conflicts file is removed when there are no more conflicts +/// or when the merge is complete. use std::{ io::{BufRead, Write}, path::{Path, PathBuf}, @@ -25,19 +24,20 @@ pub(crate) fn mark, A: AsRef<[P]>>( if paths.is_empty() { return Ok(()); } - let conflicts_path = repository.repo().path().join("conflicts"); // write all the file paths to a file on disk - let mut file = std::fs::File::create(conflicts_path)?; + let mut buf = Vec::::with_capacity(512); for path in paths { - file.write_all(path.as_ref().as_os_str().as_encoded_bytes())?; - file.write_all(b"\n")?; + buf.write_all(path.as_ref().as_os_str().as_encoded_bytes())?; + buf.write_all(b"\n")?; } + gitbutler_fs::write(repository.repo().path().join("conflicts"), buf)?; if let Some(parent) = parent { - let merge_path = repository.repo().path().join("base_merge_parent"); // write all the file paths to a file on disk - let mut file = std::fs::File::create(merge_path)?; - file.write_all(parent.to_string().as_bytes())?; + gitbutler_fs::write( + repository.repo().path().join("base_merge_parent"), + parent.to_string().as_bytes(), + )?; } Ok(()) @@ -74,11 +74,10 @@ pub fn resolve>(repository: &ProjectRepository, path: P) -> Resul } } - // remove file - std::fs::remove_file(conflicts_path)?; - - // re-write file if needed - if !remaining.is_empty() { + // re-write file if needed, otherwise remove file entirely + if remaining.is_empty() { + std::fs::remove_file(conflicts_path)?; + } else { mark(repository, &remaining, None)?; } Ok(()) diff --git a/crates/gitbutler-branch/src/state.rs b/crates/gitbutler-branch/src/state.rs index 0bea973bd..07221c49c 100644 --- a/crates/gitbutler-branch/src/state.rs +++ b/crates/gitbutler-branch/src/state.rs @@ -10,7 +10,7 @@ use crate::{ target::Target, }; use gitbutler_error::error::Code; -use gitbutler_fs::fs::read_toml_file_or_default; +use gitbutler_fs::read_toml_file_or_default; // use gitbutler_project::Project; use gitbutler_reference::Refname; use itertools::Itertools; @@ -247,5 +247,5 @@ impl VirtualBranchesHandle { } fn write>(file_path: P, virtual_branches: &VirtualBranches) -> Result<()> { - gitbutler_fs::fs::write(file_path, toml::to_string(&virtual_branches)?) + gitbutler_fs::write(file_path, toml::to_string(&virtual_branches)?) } diff --git a/crates/gitbutler-fs/src/fs.rs b/crates/gitbutler-fs/src/fs.rs deleted file mode 100644 index 6e2d8b76d..000000000 --- a/crates/gitbutler-fs/src/fs.rs +++ /dev/null @@ -1,115 +0,0 @@ -use std::fs::File; -use std::io::Read; -use std::{ - io::Write, - path::{Path, PathBuf}, -}; - -use anyhow::{Context, Result}; -use bstr::BString; -use gix::{ - dir::walk::EmissionMode, - tempfile::{create_dir::Retries, AutoRemove, ContainingDirectory}, -}; -use serde::de::DeserializeOwned; -use walkdir::WalkDir; - -// Returns an ordered list of relative paths for files inside a directory recursively. -pub fn list_files>(dir_path: P, ignore_prefixes: &[P]) -> Result> { - let mut files = vec![]; - let dir_path = dir_path.as_ref(); - if !dir_path.exists() { - return Ok(files); - } - for entry in WalkDir::new(dir_path) { - let entry = entry?; - if !entry.file_type().is_dir() { - let path = entry.path(); - let path = path.strip_prefix(dir_path)?; - let path = path.to_path_buf(); - if ignore_prefixes - .iter() - .any(|prefix| path.starts_with(prefix.as_ref())) - { - continue; - } - files.push(path); - } - } - files.sort(); - Ok(files) -} - -// Return an iterator of worktree-relative slash-separated paths for files inside the `worktree_dir`, recursively. -// Fails if the `worktree_dir` isn't a valid git repository. -pub fn iter_worktree_files( - worktree_dir: impl AsRef, -) -> Result> { - let repo = gix::open(worktree_dir.as_ref())?; - let index = repo.index_or_empty()?; - let disabled_interrupt_handling = Default::default(); - let options = repo - .dirwalk_options()? - .emit_tracked(true) - .emit_untracked(EmissionMode::Matching); - Ok(repo - .dirwalk_iter(index, None::<&str>, disabled_interrupt_handling, options)? - .filter_map(Result::ok) - .map(|e| e.entry.rela_path)) -} - -/// Write a single file so that the write either fully succeeds, or fully fails, -/// assuming the containing directory already exists. -pub fn write>(file_path: P, contents: impl AsRef<[u8]>) -> anyhow::Result<()> { - let mut temp_file = gix::tempfile::new( - file_path.as_ref().parent().unwrap(), - ContainingDirectory::Exists, - AutoRemove::Tempfile, - )?; - temp_file.write_all(contents.as_ref())?; - Ok(persist_tempfile(temp_file, file_path)?) -} - -/// Write a single file so that the write either fully succeeds, or fully fails, -/// and create all leading directories. -pub fn create_dirs_then_write>( - file_path: P, - contents: impl AsRef<[u8]>, -) -> std::io::Result<()> { - let mut temp_file = gix::tempfile::new( - file_path.as_ref().parent().unwrap(), - ContainingDirectory::CreateAllRaceProof(Retries::default()), - AutoRemove::Tempfile, - )?; - temp_file.write_all(contents.as_ref())?; - persist_tempfile(temp_file, file_path) -} - -fn persist_tempfile( - tempfile: gix::tempfile::Handle, - to_path: impl AsRef, -) -> std::io::Result<()> { - match tempfile.persist(to_path) { - Ok(Some(_opened_file)) => Ok(()), - Ok(None) => unreachable!( - "BUG: a signal has caused the tempfile to be removed, but we didn't install a handler" - ), - Err(err) => Err(err.error), - } -} - -/// Reads and parses the state file. -/// -/// If the file does not exist, it will be created. -pub fn read_toml_file_or_default(path: &Path) -> Result { - let mut file = match File::open(path) { - Ok(f) => f, - Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(T::default()), - Err(err) => return Err(err.into()), - }; - let mut contents = String::new(); - file.read_to_string(&mut contents)?; - let value: T = - toml::from_str(&contents).with_context(|| format!("Failed to parse {}", path.display()))?; - Ok(value) -} diff --git a/crates/gitbutler-fs/src/lib.rs b/crates/gitbutler-fs/src/lib.rs index d521fbd77..6e2d8b76d 100644 --- a/crates/gitbutler-fs/src/lib.rs +++ b/crates/gitbutler-fs/src/lib.rs @@ -1 +1,115 @@ -pub mod fs; +use std::fs::File; +use std::io::Read; +use std::{ + io::Write, + path::{Path, PathBuf}, +}; + +use anyhow::{Context, Result}; +use bstr::BString; +use gix::{ + dir::walk::EmissionMode, + tempfile::{create_dir::Retries, AutoRemove, ContainingDirectory}, +}; +use serde::de::DeserializeOwned; +use walkdir::WalkDir; + +// Returns an ordered list of relative paths for files inside a directory recursively. +pub fn list_files>(dir_path: P, ignore_prefixes: &[P]) -> Result> { + let mut files = vec![]; + let dir_path = dir_path.as_ref(); + if !dir_path.exists() { + return Ok(files); + } + for entry in WalkDir::new(dir_path) { + let entry = entry?; + if !entry.file_type().is_dir() { + let path = entry.path(); + let path = path.strip_prefix(dir_path)?; + let path = path.to_path_buf(); + if ignore_prefixes + .iter() + .any(|prefix| path.starts_with(prefix.as_ref())) + { + continue; + } + files.push(path); + } + } + files.sort(); + Ok(files) +} + +// Return an iterator of worktree-relative slash-separated paths for files inside the `worktree_dir`, recursively. +// Fails if the `worktree_dir` isn't a valid git repository. +pub fn iter_worktree_files( + worktree_dir: impl AsRef, +) -> Result> { + let repo = gix::open(worktree_dir.as_ref())?; + let index = repo.index_or_empty()?; + let disabled_interrupt_handling = Default::default(); + let options = repo + .dirwalk_options()? + .emit_tracked(true) + .emit_untracked(EmissionMode::Matching); + Ok(repo + .dirwalk_iter(index, None::<&str>, disabled_interrupt_handling, options)? + .filter_map(Result::ok) + .map(|e| e.entry.rela_path)) +} + +/// Write a single file so that the write either fully succeeds, or fully fails, +/// assuming the containing directory already exists. +pub fn write>(file_path: P, contents: impl AsRef<[u8]>) -> anyhow::Result<()> { + let mut temp_file = gix::tempfile::new( + file_path.as_ref().parent().unwrap(), + ContainingDirectory::Exists, + AutoRemove::Tempfile, + )?; + temp_file.write_all(contents.as_ref())?; + Ok(persist_tempfile(temp_file, file_path)?) +} + +/// Write a single file so that the write either fully succeeds, or fully fails, +/// and create all leading directories. +pub fn create_dirs_then_write>( + file_path: P, + contents: impl AsRef<[u8]>, +) -> std::io::Result<()> { + let mut temp_file = gix::tempfile::new( + file_path.as_ref().parent().unwrap(), + ContainingDirectory::CreateAllRaceProof(Retries::default()), + AutoRemove::Tempfile, + )?; + temp_file.write_all(contents.as_ref())?; + persist_tempfile(temp_file, file_path) +} + +fn persist_tempfile( + tempfile: gix::tempfile::Handle, + to_path: impl AsRef, +) -> std::io::Result<()> { + match tempfile.persist(to_path) { + Ok(Some(_opened_file)) => Ok(()), + Ok(None) => unreachable!( + "BUG: a signal has caused the tempfile to be removed, but we didn't install a handler" + ), + Err(err) => Err(err.error), + } +} + +/// Reads and parses the state file. +/// +/// If the file does not exist, it will be created. +pub fn read_toml_file_or_default(path: &Path) -> Result { + let mut file = match File::open(path) { + Ok(f) => f, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(T::default()), + Err(err) => return Err(err.into()), + }; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + let value: T = + toml::from_str(&contents).with_context(|| format!("Failed to parse {}", path.display()))?; + Ok(value) +} diff --git a/crates/gitbutler-oplog/src/reflog.rs b/crates/gitbutler-oplog/src/reflog.rs index 8ccd96eb5..c457c35b6 100644 --- a/crates/gitbutler-oplog/src/reflog.rs +++ b/crates/gitbutler-oplog/src/reflog.rs @@ -2,7 +2,7 @@ use anyhow::{Context, Result}; use gitbutler_branch::{ GITBUTLER_INTEGRATION_COMMIT_AUTHOR_EMAIL, GITBUTLER_INTEGRATION_COMMIT_AUTHOR_NAME, }; -use gitbutler_fs::fs::write; +use gitbutler_fs::write; use gix::config::tree::Key; use std::path::Path; diff --git a/crates/gitbutler-oplog/src/state.rs b/crates/gitbutler-oplog/src/state.rs index 95a1ff2d7..1a412f67f 100644 --- a/crates/gitbutler-oplog/src/state.rs +++ b/crates/gitbutler-oplog/src/state.rs @@ -4,7 +4,7 @@ use std::{ time::SystemTime, }; -use gitbutler_fs::fs::read_toml_file_or_default; +use gitbutler_fs::read_toml_file_or_default; use serde::{Deserialize, Deserializer, Serialize}; use super::OPLOG_FILE_NAME; @@ -92,6 +92,6 @@ impl OplogHandle { fn write_file(&self, mut oplog: Oplog) -> Result<()> { oplog.modified_at = SystemTime::now(); - gitbutler_fs::fs::write(&self.file_path, toml::to_string(&oplog)?) + gitbutler_fs::write(&self.file_path, toml::to_string(&oplog)?) } } diff --git a/crates/gitbutler-storage/src/storage.rs b/crates/gitbutler-storage/src/storage.rs index 422983466..71f65e1e1 100644 --- a/crates/gitbutler-storage/src/storage.rs +++ b/crates/gitbutler-storage/src/storage.rs @@ -43,7 +43,7 @@ impl Storage { /// Generally, the filesystem is used for synchronization, not in-memory primitives. pub fn write(&self, rela_path: impl AsRef, content: &str) -> std::io::Result<()> { let file_path = self.local_data_dir.join(rela_path); - gitbutler_fs::fs::create_dirs_then_write(file_path, content) + gitbutler_fs::create_dirs_then_write(file_path, content) } /// Delete the file or directory at `rela_path`. From 09ca2d0284276f3bd664687b6e34bf31f6dad176 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 14 Jul 2024 21:51:28 +0200 Subject: [PATCH 11/13] enforce in-process-synchronization during worktree updates and prolonged reads in `oplog` That way it's assured that reads and writes don't intersect, but assure we only hold such lock for the shortest amount of time for reads and and for the full duration of writes. --- Cargo.lock | 2 + Cargo.toml | 2 + .../gitbutler-branch-actions/src/actions.rs | 3 +- .../src/branch_manager/branch_creation.rs | 2 +- .../src/branch_manager/branch_removal.rs | 2 +- .../tests/virtual_branches/oplog.rs | 2 +- crates/gitbutler-cli/src/main.rs | 3 +- crates/gitbutler-oplog/src/lib.rs | 6 +- crates/gitbutler-oplog/src/oplog.rs | 627 +++++++++--------- crates/gitbutler-project/Cargo.toml | 4 + crates/gitbutler-project/src/access.rs | 134 ++++ crates/gitbutler-project/src/lib.rs | 1 + crates/gitbutler-project/src/project.rs | 3 +- crates/gitbutler-sync/src/cloud.rs | 2 +- crates/gitbutler-tauri/Cargo.toml | 2 +- crates/gitbutler-tauri/src/undo.rs | 2 +- crates/gitbutler-tauri/src/window.rs | 20 +- crates/gitbutler-watcher/src/handler.rs | 2 +- .../vendor/debouncer/Cargo.toml | 2 +- 19 files changed, 490 insertions(+), 331 deletions(-) create mode 100644 crates/gitbutler-project/src/access.rs diff --git a/Cargo.lock b/Cargo.lock index d2c08028f..a7dd14241 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2176,6 +2176,7 @@ name = "gitbutler-project" version = "0.0.0" dependencies = [ "anyhow", + "fslock", "git2", "gitbutler-error", "gitbutler-id", @@ -2183,6 +2184,7 @@ dependencies = [ "gitbutler-storage", "gitbutler-testsupport", "gix", + "parking_lot 0.12.3", "resolve-path", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index f5852185b..85b302a3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,8 @@ thiserror = "1.0.61" tokio = { version = "1.38.0", default-features = false } keyring = "2.3.3" anyhow = "1.0.86" +fslock = "0.2.1" +parking_lot = "0.12.3" gitbutler-id = { path = "crates/gitbutler-id" } gitbutler-git = { path = "crates/gitbutler-git" } diff --git a/crates/gitbutler-branch-actions/src/actions.rs b/crates/gitbutler-branch-actions/src/actions.rs index 9f48253de..f170b922d 100644 --- a/crates/gitbutler-branch-actions/src/actions.rs +++ b/crates/gitbutler-branch-actions/src/actions.rs @@ -5,8 +5,7 @@ use gitbutler_branch::{ use gitbutler_command_context::ProjectRepository; use gitbutler_oplog::{ entry::{OperationKind, SnapshotDetails}, - oplog::OplogExt, - snapshot::SnapshotExt, + OplogExt, SnapshotExt, }; use gitbutler_project::{FetchResult, Project}; use gitbutler_reference::ReferenceName; diff --git a/crates/gitbutler-branch-actions/src/branch_manager/branch_creation.rs b/crates/gitbutler-branch-actions/src/branch_manager/branch_creation.rs index 7ebfb2a88..6e1212950 100644 --- a/crates/gitbutler-branch-actions/src/branch_manager/branch_creation.rs +++ b/crates/gitbutler-branch-actions/src/branch_manager/branch_creation.rs @@ -11,7 +11,7 @@ use gitbutler_branch::{ }; use gitbutler_commit::commit_headers::HasCommitHeaders; use gitbutler_error::error::Marker; -use gitbutler_oplog::snapshot::SnapshotExt; +use gitbutler_oplog::SnapshotExt; use gitbutler_reference::Refname; use gitbutler_repo::{rebase::cherry_rebase, RepoActionsExt, RepositoryExt}; use gitbutler_time::time::now_since_unix_epoch_ms; diff --git a/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs b/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs index e0b55cd03..41b5a8746 100644 --- a/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs +++ b/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs @@ -8,7 +8,7 @@ use anyhow::{anyhow, Context, Result}; use git2::build::TreeUpdateBuilder; use gitbutler_branch::{Branch, BranchExt, BranchId}; use gitbutler_commit::commit_headers::CommitHeadersV2; -use gitbutler_oplog::snapshot::SnapshotExt; +use gitbutler_oplog::SnapshotExt; use gitbutler_reference::ReferenceName; use gitbutler_reference::{normalize_branch_name, Refname}; use gitbutler_repo::{RepoActionsExt, RepositoryExt}; diff --git a/crates/gitbutler-branch-actions/tests/virtual_branches/oplog.rs b/crates/gitbutler-branch-actions/tests/virtual_branches/oplog.rs index e1f042499..d486d9202 100644 --- a/crates/gitbutler-branch-actions/tests/virtual_branches/oplog.rs +++ b/crates/gitbutler-branch-actions/tests/virtual_branches/oplog.rs @@ -1,6 +1,6 @@ use super::*; use gitbutler_branch::{BranchCreateRequest, VirtualBranchesHandle}; -use gitbutler_oplog::oplog::OplogExt; +use gitbutler_oplog::OplogExt; use itertools::Itertools; use std::io::Write; use std::path::Path; diff --git a/crates/gitbutler-cli/src/main.rs b/crates/gitbutler-cli/src/main.rs index c90e1e6f6..e589739f5 100644 --- a/crates/gitbutler-cli/src/main.rs +++ b/crates/gitbutler-cli/src/main.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use gitbutler_oplog::oplog::OplogExt; +use gitbutler_oplog::OplogExt; use clap::{arg, Command}; use gitbutler_project::Project; @@ -63,6 +63,7 @@ fn list_snapshots(repo_dir: &str) -> Result<()> { fn restore_snapshot(repo_dir: &str, snapshot_id: &str) -> Result<()> { let project = project_from_path(repo_dir); + let _guard = project.try_exclusive_access()?; project.restore_snapshot(snapshot_id.parse()?)?; Ok(()) } diff --git a/crates/gitbutler-oplog/src/lib.rs b/crates/gitbutler-oplog/src/lib.rs index 6d98b72ad..0322b0265 100644 --- a/crates/gitbutler-oplog/src/lib.rs +++ b/crates/gitbutler-oplog/src/lib.rs @@ -1,7 +1,9 @@ pub mod entry; -pub mod oplog; +mod oplog; +pub use oplog::OplogExt; mod reflog; -pub mod snapshot; +mod snapshot; +pub use snapshot::SnapshotExt; mod state; /// The name of the file holding our state, useful for watching for changes. diff --git a/crates/gitbutler-oplog/src/oplog.rs b/crates/gitbutler-oplog/src/oplog.rs index 2332db266..b0a034cb6 100644 --- a/crates/gitbutler-oplog/src/oplog.rs +++ b/crates/gitbutler-oplog/src/oplog.rs @@ -13,15 +13,15 @@ use std::{fs, path::PathBuf}; use anyhow::Result; use tracing::instrument; -use gitbutler_branch::{ - GITBUTLER_INTEGRATION_COMMIT_AUTHOR_EMAIL, GITBUTLER_INTEGRATION_COMMIT_AUTHOR_NAME, -}; - use super::{ entry::{OperationKind, Snapshot, SnapshotDetails, Trailer}, reflog::set_reference_to_oplog, state::OplogHandle, }; +use gitbutler_branch::{ + GITBUTLER_INTEGRATION_COMMIT_AUTHOR_EMAIL, GITBUTLER_INTEGRATION_COMMIT_AUTHOR_NAME, +}; +use gitbutler_project::access::{WorktreeReadPermission, WorktreeWritePermission}; const SNAPSHOT_FILE_LIMIT_BYTES: u64 = 32 * 1024 * 1024; @@ -130,118 +130,8 @@ pub trait OplogExt { impl OplogExt for Project { fn prepare_snapshot(&self) -> Result { - let worktree_dir = self.path.as_path(); - let repo = git2::Repository::open(worktree_dir)?; - - let vb_state = VirtualBranchesHandle::new(self.gb_dir()); - - // grab the target commit - let default_target_commit = repo.find_commit(vb_state.get_default_target()?.sha)?; - let target_tree_id = default_target_commit.tree_id(); - - // Create a blob out of `.git/gitbutler/virtual_branches.toml` - let vb_path = repo.path().join("gitbutler").join("virtual_branches.toml"); - let vb_content = fs::read(vb_path)?; - let vb_blob_id = repo.blob(&vb_content)?; - - // Create a tree out of the conflicts state if present - let conflicts_tree_id = write_conflicts_tree(worktree_dir, &repo)?; - - // write out the index as a tree to store - let mut index = repo.index()?; - let index_tree_oid = index.write_tree()?; - - // start building our snapshot tree - let mut tree_builder = repo.treebuilder(None)?; - tree_builder.insert("index", index_tree_oid, FileMode::Tree.into())?; - tree_builder.insert("target_tree", target_tree_id, FileMode::Tree.into())?; - tree_builder.insert("conflicts", conflicts_tree_id, FileMode::Tree.into())?; - tree_builder.insert("virtual_branches.toml", vb_blob_id, FileMode::Blob.into())?; - - // go through all virtual branches and create a subtree for each with the tree and any commits encoded - let mut branches_tree_builder = repo.treebuilder(None)?; - let mut head_tree_ids = Vec::new(); - - for branch in vb_state.list_branches_in_workspace()? { - head_tree_ids.push(branch.tree); - - // commits in virtual branches (tree and commit data) - // calculate all the commits between branch.head and the target and codify them - let mut branch_tree_builder = repo.treebuilder(None)?; - branch_tree_builder.insert("tree", branch.tree, FileMode::Tree.into())?; - - // let's get all the commits between the branch head and the target - let mut revwalk = repo.revwalk()?; - revwalk.push(branch.head)?; - revwalk.hide(default_target_commit.id())?; - - let mut commits_tree_builder = repo.treebuilder(None)?; - for commit_id in revwalk { - let commit_id = commit_id?; - let commit = repo.find_commit(commit_id)?; - let commit_tree = commit.tree()?; - - let mut commit_tree_builder = repo.treebuilder(None)?; - let commit_data_blob_id = repo.blob(&serialize_commit(&commit))?; - commit_tree_builder.insert("commit", commit_data_blob_id, FileMode::Blob.into())?; - commit_tree_builder.insert("tree", commit_tree.id(), FileMode::Tree.into())?; - let commit_tree_id = commit_tree_builder.write()?; - - commits_tree_builder.insert( - commit_id.to_string(), - commit_tree_id, - FileMode::Tree.into(), - )?; - } - - let commits_tree_id = commits_tree_builder.write()?; - branch_tree_builder.insert("commits", commits_tree_id, FileMode::Tree.into())?; - - let branch_tree_id = branch_tree_builder.write()?; - branches_tree_builder.insert( - branch.id.to_string(), - branch_tree_id, - FileMode::Tree.into(), - )?; - } - - // also add the gitbutler/integration commit to the branches tree - let head = repo.head()?; - if head.name() == Some("refs/heads/gitbutler/integration") { - let head_commit = head.peel_to_commit()?; - let head_tree = head_commit.tree()?; - - let mut head_commit_tree_builder = repo.treebuilder(None)?; - - // convert that data into a blob - let commit_data_blob = repo.blob(&serialize_commit(&head_commit))?; - head_commit_tree_builder.insert("commit", commit_data_blob, FileMode::Blob.into())?; - head_commit_tree_builder.insert("tree", head_tree.id(), FileMode::Tree.into())?; - - let head_commit_tree_id = head_commit_tree_builder.write()?; - - // have to make a subtree to match - let mut commits_tree_builder = repo.treebuilder(None)?; - commits_tree_builder.insert( - head_commit.id().to_string(), - head_commit_tree_id, - FileMode::Tree.into(), - )?; - let commits_tree_id = commits_tree_builder.write()?; - - let mut branch_tree_builder = repo.treebuilder(None)?; - branch_tree_builder.insert("tree", head_tree.id(), FileMode::Tree.into())?; - branch_tree_builder.insert("commits", commits_tree_id, FileMode::Tree.into())?; - let branch_tree_id = branch_tree_builder.write()?; - - branches_tree_builder.insert("integration", branch_tree_id, FileMode::Tree.into())?; - } - - let branch_tree_id = branches_tree_builder.write()?; - tree_builder.insert("virtual_branches", branch_tree_id, FileMode::Tree.into())?; - - let tree_id = tree_builder.write()?; - Ok(tree_id) + let guard = self.shared_worktree_access(); + prepare_snapshot(self, guard.read_permission()) } fn commit_snapshot( @@ -249,46 +139,15 @@ impl OplogExt for Project { snapshot_tree_id: git2::Oid, details: SnapshotDetails, ) -> Result> { - let repo = git2::Repository::open(self.path.as_path())?; - let snapshot_tree = repo.find_tree(snapshot_tree_id)?; - - let oplog_state = OplogHandle::new(&self.gb_dir()); - let oplog_head_commit = oplog_state - .oplog_head()? - .and_then(|head_id| repo.find_commit(head_id).ok()); - - // Construct a new commit - let signature = git2::Signature::now( - GITBUTLER_INTEGRATION_COMMIT_AUTHOR_NAME, - GITBUTLER_INTEGRATION_COMMIT_AUTHOR_EMAIL, - ) - .unwrap(); - let parents = oplog_head_commit - .as_ref() - .map(|head| vec![head]) - .unwrap_or_default(); - let snapshot_commit_id = repo.commit( - None, - &signature, - &signature, - &details.to_string(), - &snapshot_tree, - parents.as_slice(), - )?; - - oplog_state.set_oplog_head(snapshot_commit_id)?; - - let vb_state = VirtualBranchesHandle::new(self.gb_dir()); - let target_commit_id = vb_state.get_default_target()?.sha; - set_reference_to_oplog(&self.path, target_commit_id, snapshot_commit_id)?; - - Ok(Some(snapshot_commit_id)) + let mut guard = self.exclusive_worktree_access(); + commit_snapshot(self, snapshot_tree_id, details, guard.write_permission()) } #[instrument(skip(details), err(Debug))] fn create_snapshot(&self, details: SnapshotDetails) -> Result> { - let tree_id = self.prepare_snapshot()?; - self.commit_snapshot(tree_id, details) + 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()) } fn list_snapshots( @@ -396,153 +255,8 @@ impl OplogExt for Project { } fn restore_snapshot(&self, snapshot_commit_id: git2::Oid) -> Result> { - let worktree_dir = self.path.as_path(); - let repo = git2::Repository::open(worktree_dir)?; - - let before_restore_snapshot_result = self.prepare_snapshot(); - let snapshot_commit = repo.find_commit(snapshot_commit_id)?; - - let snapshot_tree = snapshot_commit.tree()?; - let vb_toml_entry = snapshot_tree - .get_name("virtual_branches.toml") - .context("failed to get virtual_branches.toml blob")?; - // virtual_branches.toml blob - let vb_toml_blob = repo - .find_blob(vb_toml_entry.id()) - .context("failed to convert virtual_branches tree entry to blob")?; - - if let Err(err) = restore_conflicts_tree(&snapshot_tree, &repo) { - tracing::warn!("failed to restore conflicts tree - ignoring: {err}") - } - - // make sure we reconstitute any commits that were in the snapshot that are not here for some reason - // for every entry in the virtual_branches subtree, reconsitute the commits - let vb_tree_entry = snapshot_tree - .get_name("virtual_branches") - .context("failed to get virtual_branches tree entry")?; - let vb_tree = repo - .find_tree(vb_tree_entry.id()) - .context("failed to convert virtual_branches tree entry to tree")?; - - // walk through all the entries (branches by id) - let walker = vb_tree.iter(); - for branch_entry in walker { - let branch_tree = repo - .find_tree(branch_entry.id()) - .context("failed to convert virtual_branches tree entry to tree")?; - let branch_name = branch_entry.name(); - - let commits_tree_entry = branch_tree - .get_name("commits") - .context("failed to get commits tree entry")?; - let commits_tree = repo - .find_tree(commits_tree_entry.id()) - .context("failed to convert commits tree entry to tree")?; - - // walk through all the commits in the branch - for commit_entry in commits_tree.iter() { - // for each commit, recreate the commit from the commit data if it doesn't exist - if let Some(commit_id) = commit_entry.name() { - // check for the oid in the repo - let commit_oid = git2::Oid::from_str(commit_id)?; - if repo.find_commit(commit_oid).is_err() { - // commit is not in the repo, let's build it from our data - let new_commit_oid = deserialize_commit(&repo, &commit_entry)?; - if new_commit_oid != commit_oid { - bail!("commit id mismatch: failed to recreate a commit from its parts"); - } - } - - // if branch_name is 'integration', we need to create or update the gitbutler/integration branch - if branch_name == Some("integration") { - // TODO(ST): with `gitoxide`, just update the branch without this dance, - // similar to `git update-ref`. - // Then a missing integration branch also doesn't have to be - // fatal, but we wouldn't want to `set_head()` if we are - // not already on the integration branch. - let mut integration_ref = repo.integration_ref_from_head()?; - - // reset the branch if it's there, otherwise bail as we don't meddle with other branches - // need to detach the head for just a moment. - repo.set_head_detached(commit_oid)?; - integration_ref.delete()?; - - // ok, now we set the branch to what it was and update HEAD - let integration_commit = repo.find_commit(commit_oid)?; - repo.branch("gitbutler/integration", &integration_commit, true)?; - // make sure head is gitbutler/integration - repo.set_head("refs/heads/gitbutler/integration")?; - } - } - } - } - - repo.integration_ref_from_head().context( - "We will not change a worktree which for some reason isn't on the integration branch", - )?; - - let workdir_tree_id = tree_from_applied_vbranches(&repo, snapshot_commit_id)?; - let workdir_tree = repo.find_tree(workdir_tree_id)?; - - // Exclude files that are larger than the limit (eg. database.sql which may never be intended to be committed) - let files_to_exclude = - worktree_files_larger_than_limit_as_git2_ignore_rule(&repo, worktree_dir)?; - // In-memory, libgit2 internal ignore rule - repo.add_ignore_rule(&files_to_exclude)?; - - // Define the checkout builder - let mut checkout_builder = git2::build::CheckoutBuilder::new(); - checkout_builder.remove_untracked(true); - checkout_builder.force(); - // Checkout the tree - repo.checkout_tree(workdir_tree.as_object(), Some(&mut checkout_builder))?; - - // Update virtual_branches.toml with the state from the snapshot - fs::write( - repo.path().join("gitbutler").join("virtual_branches.toml"), - vb_toml_blob.content(), - )?; - - // reset the repo index to our index tree - let index_tree_entry = snapshot_tree - .get_name("index") - .context("failed to get virtual_branches.toml blob")?; - let index_tree = repo - .find_tree(index_tree_entry.id()) - .context("failed to convert index tree entry to tree")?; - let mut index = repo.index()?; - index.read_tree(&index_tree)?; - - let restored_operation = snapshot_commit - .message() - .and_then(|msg| SnapshotDetails::from_str(msg).ok()) - .map(|d| d.operation.to_string()) - .unwrap_or_default(); - - // create new snapshot - let before_restore_snapshot_tree_id = before_restore_snapshot_result?; - let restored_date_ms = snapshot_commit.time().seconds() * 1000; - let details = SnapshotDetails { - version: Default::default(), - operation: OperationKind::RestoreFromSnapshot, - title: "Restored from snapshot".to_string(), - body: None, - trailers: vec![ - Trailer { - key: "restored_from".to_string(), - value: snapshot_commit_id.to_string(), - }, - Trailer { - key: "restored_operation".to_string(), - value: restored_operation, - }, - Trailer { - key: "restored_date".to_string(), - value: restored_date_ms.to_string(), - }, - ], - }; - self.commit_snapshot(before_restore_snapshot_tree_id, details) + let mut guard = self.exclusive_worktree_access(); + restore_snapshot(self, snapshot_commit_id, guard.write_permission()) } fn should_auto_snapshot(&self, check_if_last_snapshot_older_than: Duration) -> Result { @@ -596,6 +310,321 @@ impl OplogExt for Project { oplog_state.oplog_head() } } +fn prepare_snapshot(ctx: &Project, _shared_access: &WorktreeReadPermission) -> Result { + let worktree_dir = ctx.path.as_path(); + let repo = git2::Repository::open(worktree_dir)?; + + let vb_state = VirtualBranchesHandle::new(ctx.gb_dir()); + + // grab the target commit + let default_target_commit = repo.find_commit(vb_state.get_default_target()?.sha)?; + let target_tree_id = default_target_commit.tree_id(); + + // Create a blob out of `.git/gitbutler/virtual_branches.toml` + let vb_path = repo.path().join("gitbutler").join("virtual_branches.toml"); + let vb_content = fs::read(vb_path)?; + let vb_blob_id = repo.blob(&vb_content)?; + + // Create a tree out of the conflicts state if present + let conflicts_tree_id = write_conflicts_tree(worktree_dir, &repo)?; + + // write out the index as a tree to store + let mut index = repo.index()?; + let index_tree_oid = index.write_tree()?; + + // start building our snapshot tree + let mut tree_builder = repo.treebuilder(None)?; + tree_builder.insert("index", index_tree_oid, FileMode::Tree.into())?; + tree_builder.insert("target_tree", target_tree_id, FileMode::Tree.into())?; + tree_builder.insert("conflicts", conflicts_tree_id, FileMode::Tree.into())?; + tree_builder.insert("virtual_branches.toml", vb_blob_id, FileMode::Blob.into())?; + + // go through all virtual branches and create a subtree for each with the tree and any commits encoded + let mut branches_tree_builder = repo.treebuilder(None)?; + let mut head_tree_ids = Vec::new(); + + for branch in vb_state.list_branches_in_workspace()? { + head_tree_ids.push(branch.tree); + + // commits in virtual branches (tree and commit data) + // calculate all the commits between branch.head and the target and codify them + let mut branch_tree_builder = repo.treebuilder(None)?; + branch_tree_builder.insert("tree", branch.tree, FileMode::Tree.into())?; + + // let's get all the commits between the branch head and the target + let mut revwalk = repo.revwalk()?; + revwalk.push(branch.head)?; + revwalk.hide(default_target_commit.id())?; + + let mut commits_tree_builder = repo.treebuilder(None)?; + for commit_id in revwalk { + let commit_id = commit_id?; + let commit = repo.find_commit(commit_id)?; + let commit_tree = commit.tree()?; + + let mut commit_tree_builder = repo.treebuilder(None)?; + let commit_data_blob_id = repo.blob(&serialize_commit(&commit))?; + commit_tree_builder.insert("commit", commit_data_blob_id, FileMode::Blob.into())?; + commit_tree_builder.insert("tree", commit_tree.id(), FileMode::Tree.into())?; + let commit_tree_id = commit_tree_builder.write()?; + + commits_tree_builder.insert( + commit_id.to_string(), + commit_tree_id, + FileMode::Tree.into(), + )?; + } + + let commits_tree_id = commits_tree_builder.write()?; + branch_tree_builder.insert("commits", commits_tree_id, FileMode::Tree.into())?; + + let branch_tree_id = branch_tree_builder.write()?; + branches_tree_builder.insert( + branch.id.to_string(), + branch_tree_id, + FileMode::Tree.into(), + )?; + } + + // also add the gitbutler/integration commit to the branches tree + let head = repo.head()?; + if head.name() == Some("refs/heads/gitbutler/integration") { + let head_commit = head.peel_to_commit()?; + let head_tree = head_commit.tree()?; + + let mut head_commit_tree_builder = repo.treebuilder(None)?; + + // convert that data into a blob + let commit_data_blob = repo.blob(&serialize_commit(&head_commit))?; + head_commit_tree_builder.insert("commit", commit_data_blob, FileMode::Blob.into())?; + head_commit_tree_builder.insert("tree", head_tree.id(), FileMode::Tree.into())?; + + let head_commit_tree_id = head_commit_tree_builder.write()?; + + // have to make a subtree to match + let mut commits_tree_builder = repo.treebuilder(None)?; + commits_tree_builder.insert( + head_commit.id().to_string(), + head_commit_tree_id, + FileMode::Tree.into(), + )?; + let commits_tree_id = commits_tree_builder.write()?; + + let mut branch_tree_builder = repo.treebuilder(None)?; + branch_tree_builder.insert("tree", head_tree.id(), FileMode::Tree.into())?; + branch_tree_builder.insert("commits", commits_tree_id, FileMode::Tree.into())?; + let branch_tree_id = branch_tree_builder.write()?; + + branches_tree_builder.insert("integration", branch_tree_id, FileMode::Tree.into())?; + } + + let branch_tree_id = branches_tree_builder.write()?; + tree_builder.insert("virtual_branches", branch_tree_id, FileMode::Tree.into())?; + + let tree_id = tree_builder.write()?; + Ok(tree_id) +} + +fn commit_snapshot( + ctx: &Project, + snapshot_tree_id: git2::Oid, + details: SnapshotDetails, + _exclusive_access: &mut WorktreeWritePermission, +) -> Result> { + let repo = git2::Repository::open(ctx.path.as_path())?; + let snapshot_tree = repo.find_tree(snapshot_tree_id)?; + + let oplog_state = OplogHandle::new(&ctx.gb_dir()); + let oplog_head_commit = oplog_state + .oplog_head()? + .and_then(|head_id| repo.find_commit(head_id).ok()); + + // Construct a new commit + let signature = git2::Signature::now( + GITBUTLER_INTEGRATION_COMMIT_AUTHOR_NAME, + GITBUTLER_INTEGRATION_COMMIT_AUTHOR_EMAIL, + ) + .unwrap(); + let parents = oplog_head_commit + .as_ref() + .map(|head| vec![head]) + .unwrap_or_default(); + let snapshot_commit_id = repo.commit( + None, + &signature, + &signature, + &details.to_string(), + &snapshot_tree, + parents.as_slice(), + )?; + + oplog_state.set_oplog_head(snapshot_commit_id)?; + + let vb_state = VirtualBranchesHandle::new(ctx.gb_dir()); + let target_commit_id = vb_state.get_default_target()?.sha; + set_reference_to_oplog(&ctx.path, target_commit_id, snapshot_commit_id)?; + + Ok(Some(snapshot_commit_id)) +} + +fn restore_snapshot( + ctx: &Project, + snapshot_commit_id: git2::Oid, + exclusive_access: &mut WorktreeWritePermission, +) -> Result> { + let worktree_dir = ctx.path.as_path(); + let repo = git2::Repository::open(worktree_dir)?; + + let before_restore_snapshot_result = prepare_snapshot(ctx, exclusive_access.read_permission()); + let snapshot_commit = repo.find_commit(snapshot_commit_id)?; + + let snapshot_tree = snapshot_commit.tree()?; + let vb_toml_entry = snapshot_tree + .get_name("virtual_branches.toml") + .context("failed to get virtual_branches.toml blob")?; + // virtual_branches.toml blob + let vb_toml_blob = repo + .find_blob(vb_toml_entry.id()) + .context("failed to convert virtual_branches tree entry to blob")?; + + if let Err(err) = restore_conflicts_tree(&snapshot_tree, &repo) { + tracing::warn!("failed to restore conflicts tree - ignoring: {err}") + } + + // make sure we reconstitute any commits that were in the snapshot that are not here for some reason + // for every entry in the virtual_branches subtree, reconsitute the commits + let vb_tree_entry = snapshot_tree + .get_name("virtual_branches") + .context("failed to get virtual_branches tree entry")?; + let vb_tree = repo + .find_tree(vb_tree_entry.id()) + .context("failed to convert virtual_branches tree entry to tree")?; + + // walk through all the entries (branches by id) + let walker = vb_tree.iter(); + for branch_entry in walker { + let branch_tree = repo + .find_tree(branch_entry.id()) + .context("failed to convert virtual_branches tree entry to tree")?; + let branch_name = branch_entry.name(); + + let commits_tree_entry = branch_tree + .get_name("commits") + .context("failed to get commits tree entry")?; + let commits_tree = repo + .find_tree(commits_tree_entry.id()) + .context("failed to convert commits tree entry to tree")?; + + // walk through all the commits in the branch + for commit_entry in commits_tree.iter() { + // for each commit, recreate the commit from the commit data if it doesn't exist + if let Some(commit_id) = commit_entry.name() { + // check for the oid in the repo + let commit_oid = git2::Oid::from_str(commit_id)?; + if repo.find_commit(commit_oid).is_err() { + // commit is not in the repo, let's build it from our data + let new_commit_oid = deserialize_commit(&repo, &commit_entry)?; + if new_commit_oid != commit_oid { + bail!("commit id mismatch: failed to recreate a commit from its parts"); + } + } + + // if branch_name is 'integration', we need to create or update the gitbutler/integration branch + if branch_name == Some("integration") { + // TODO(ST): with `gitoxide`, just update the branch without this dance, + // similar to `git update-ref`. + // Then a missing integration branch also doesn't have to be + // fatal, but we wouldn't want to `set_head()` if we are + // not already on the integration branch. + let mut integration_ref = repo.integration_ref_from_head()?; + + // reset the branch if it's there, otherwise bail as we don't meddle with other branches + // need to detach the head for just a moment. + repo.set_head_detached(commit_oid)?; + integration_ref.delete()?; + + // ok, now we set the branch to what it was and update HEAD + let integration_commit = repo.find_commit(commit_oid)?; + repo.branch("gitbutler/integration", &integration_commit, true)?; + // make sure head is gitbutler/integration + repo.set_head("refs/heads/gitbutler/integration")?; + } + } + } + } + + repo.integration_ref_from_head().context( + "We will not change a worktree which for some reason isn't on the integration branch", + )?; + + let workdir_tree_id = tree_from_applied_vbranches(&repo, snapshot_commit_id)?; + let workdir_tree = repo.find_tree(workdir_tree_id)?; + + // Exclude files that are larger than the limit (eg. database.sql which may never be intended to be committed) + let files_to_exclude = + worktree_files_larger_than_limit_as_git2_ignore_rule(&repo, worktree_dir)?; + // In-memory, libgit2 internal ignore rule + repo.add_ignore_rule(&files_to_exclude)?; + + // Define the checkout builder + let mut checkout_builder = git2::build::CheckoutBuilder::new(); + checkout_builder.remove_untracked(true); + checkout_builder.force(); + // Checkout the tree + repo.checkout_tree(workdir_tree.as_object(), Some(&mut checkout_builder))?; + + // Update virtual_branches.toml with the state from the snapshot + fs::write( + repo.path().join("gitbutler").join("virtual_branches.toml"), + vb_toml_blob.content(), + )?; + + // reset the repo index to our index tree + let index_tree_entry = snapshot_tree + .get_name("index") + .context("failed to get virtual_branches.toml blob")?; + let index_tree = repo + .find_tree(index_tree_entry.id()) + .context("failed to convert index tree entry to tree")?; + let mut index = repo.index()?; + index.read_tree(&index_tree)?; + + let restored_operation = snapshot_commit + .message() + .and_then(|msg| SnapshotDetails::from_str(msg).ok()) + .map(|d| d.operation.to_string()) + .unwrap_or_default(); + + // create new snapshot + let before_restore_snapshot_tree_id = before_restore_snapshot_result?; + let restored_date_ms = snapshot_commit.time().seconds() * 1000; + let details = SnapshotDetails { + version: Default::default(), + operation: OperationKind::RestoreFromSnapshot, + title: "Restored from snapshot".to_string(), + body: None, + trailers: vec![ + Trailer { + key: "restored_from".to_string(), + value: snapshot_commit_id.to_string(), + }, + Trailer { + key: "restored_operation".to_string(), + value: restored_operation, + }, + Trailer { + key: "restored_date".to_string(), + value: restored_date_ms.to_string(), + }, + ], + }; + commit_snapshot( + ctx, + before_restore_snapshot_tree_id, + details, + exclusive_access, + ) +} /// Restore the state of .git/base_merge_parent and .git/conflicts from the snapshot /// Will remove those files if they are not present in the snapshot diff --git a/crates/gitbutler-project/Cargo.toml b/crates/gitbutler-project/Cargo.toml index eba8ec409..a27d2a992 100644 --- a/crates/gitbutler-project/Cargo.toml +++ b/crates/gitbutler-project/Cargo.toml @@ -7,6 +7,7 @@ publish = false [dependencies] anyhow = "1.0.86" +parking_lot = { workspace = true, features = ["arc_lock"] } serde = { workspace = true, features = ["std"]} serde_json = { version = "1.0", features = [ "std", "arbitrary_precision" ] } gitbutler-error.workspace = true @@ -19,6 +20,9 @@ uuid.workspace = true tracing = "0.1.40" resolve-path = "0.1.0" +# for locking +fslock.workspace = true + [[test]] name="project" path = "tests/mod.rs" diff --git a/crates/gitbutler-project/src/access.rs b/crates/gitbutler-project/src/access.rs new file mode 100644 index 000000000..9b5252a03 --- /dev/null +++ b/crates/gitbutler-project/src/access.rs @@ -0,0 +1,134 @@ +use crate::{Project, ProjectId}; +use anyhow::{bail, Context}; +use parking_lot::RawRwLock; +use std::collections::BTreeMap; +use std::sync::Arc; + +/// Access Control +impl Project { + /// Try to obtain the exclusive inter-process lock on the entire project, preventing other GitButler + /// instances to operate on it entirely. + /// This lock should be obtained and held for as long as a user interface is observing the project. + /// + /// Note that the lock is automatically released on `Drop`, or when the process quits for any reason, + /// so it can't go stale. + pub fn try_exclusive_access(&self) -> anyhow::Result { + // MIGRATION: bluntly remove old lock files, which are now more generally named to also fit + // the CLI. + std::fs::remove_file(self.gb_dir().join("window.lock").as_os_str()).ok(); + + let mut lock = fslock::LockFile::open(self.gb_dir().join("project.lock").as_os_str())?; + let got_lock = lock + .try_lock() + .context("Failed to check if lock is taken")?; + if !got_lock { + bail!( + "Project '{}' is already opened in another window", + self.title + ); + } + Ok(lock) + } + + /// Return a guard for exclusive (read+write) worktree access, blocking while waiting for someone else, + /// in the same process only, to release it, or for all readers to disappear. + /// Locking is fair. + /// + /// Note that this in-process locking works only under the assumption that no two instances of + /// GitButler are able to read or write the same repository. + pub fn exclusive_worktree_access(&self) -> WriteWorkspaceGuard { + let mut map = WORKTREE_LOCKS.lock(); + WriteWorkspaceGuard { + _inner: map.entry(self.id).or_default().write_arc(), + perm: WorktreeWritePermission(()), + } + } + + /// 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. + pub fn shared_worktree_access(&self) -> WorkspaceReadGuard { + let mut map = WORKTREE_LOCKS.lock(); + WorkspaceReadGuard(map.entry(self.id).or_default().read_arc()) + } +} + +pub struct WriteWorkspaceGuard { + _inner: parking_lot::ArcRwLockWriteGuard, + perm: WorktreeWritePermission, +} + +impl WriteWorkspaceGuard { + /// Signal that a write-permission is available - useful as API-marker to assure these + /// can only be called when the respective protection/permission is present. + pub fn write_permission(&mut self) -> &mut WorktreeWritePermission { + &mut self.perm + } + + /// 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 { + self.perm.read_permission() + } +} + +pub struct UpgradableWorkspaceReadGuard(parking_lot::ArcRwLockUpgradableReadGuard); + +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); + +impl WorkspaceReadGuard { + /// 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 + } +} + +/// A token to indicate read-only access was granted to the worktree, assuring there are no writers +/// *within this process*. +pub struct WorktreeReadPermission(()); + +/// A token to indicate exclusive access was granted to the worktree, assuring there are no readers or other writers +/// *within this process*. +pub struct WorktreeWritePermission(()); + +impl 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 + } +} + +static WORKTREE_LOCKS: parking_lot::Mutex>>> = + parking_lot::Mutex::new(BTreeMap::new()); diff --git a/crates/gitbutler-project/src/lib.rs b/crates/gitbutler-project/src/lib.rs index 91aff10de..200f90172 100644 --- a/crates/gitbutler-project/src/lib.rs +++ b/crates/gitbutler-project/src/lib.rs @@ -1,3 +1,4 @@ +pub mod access; mod controller; mod default_true; mod project; diff --git a/crates/gitbutler-project/src/project.rs b/crates/gitbutler-project/src/project.rs index 52ad164d4..ccd96cab3 100644 --- a/crates/gitbutler-project/src/project.rs +++ b/crates/gitbutler-project/src/project.rs @@ -1,10 +1,9 @@ +use serde::{Deserialize, Serialize}; use std::{ path::{self, PathBuf}, time, }; -use serde::{Deserialize, Serialize}; - use crate::default_true::DefaultTrue; use gitbutler_id::id::Id; diff --git a/crates/gitbutler-sync/src/cloud.rs b/crates/gitbutler-sync/src/cloud.rs index 51dce7e42..1c130d54b 100644 --- a/crates/gitbutler-sync/src/cloud.rs +++ b/crates/gitbutler-sync/src/cloud.rs @@ -8,7 +8,7 @@ use gitbutler_branch::VirtualBranchesHandle; use gitbutler_command_context::ProjectRepository; use gitbutler_error::error::Code; use gitbutler_id::id::Id; -use gitbutler_oplog::oplog::OplogExt; +use gitbutler_oplog::OplogExt; use gitbutler_project as projects; use gitbutler_project::{CodePushState, Project}; use gitbutler_reference::Refname; diff --git a/crates/gitbutler-tauri/Cargo.toml b/crates/gitbutler-tauri/Cargo.toml index cef99214d..b11a10f3a 100644 --- a/crates/gitbutler-tauri/Cargo.toml +++ b/crates/gitbutler-tauri/Cargo.toml @@ -26,7 +26,7 @@ anyhow = "1.0.86" backtrace = { version = "0.3.72", optional = true } console-subscriber = "0.2.0" dirs = "5.0.1" -fslock = "0.2.1" +fslock.workspace = true futures = "0.3" git2.workspace = true once_cell = "1.19" diff --git a/crates/gitbutler-tauri/src/undo.rs b/crates/gitbutler-tauri/src/undo.rs index 5d6330056..a5fd852a5 100644 --- a/crates/gitbutler-tauri/src/undo.rs +++ b/crates/gitbutler-tauri/src/undo.rs @@ -2,7 +2,7 @@ use crate::error::Error; use anyhow::Context; use gitbutler_branch::diff::FileDiff; use gitbutler_oplog::entry::Snapshot; -use gitbutler_oplog::oplog::OplogExt; +use gitbutler_oplog::OplogExt; use gitbutler_project as projects; use gitbutler_project::ProjectId; use std::collections::HashMap; diff --git a/crates/gitbutler-tauri/src/window.rs b/crates/gitbutler-tauri/src/window.rs index 13401d7dd..920719fc1 100644 --- a/crates/gitbutler-tauri/src/window.rs +++ b/crates/gitbutler-tauri/src/window.rs @@ -2,7 +2,7 @@ pub(super) mod state { use std::collections::BTreeMap; use std::sync::Arc; - use anyhow::{bail, Context, Result}; + use anyhow::{Context, Result}; use futures::executor::block_on; use gitbutler_project as projects; use gitbutler_project::ProjectId; @@ -66,9 +66,6 @@ pub(super) mod state { } use event::ChangeForFrontend; - /// The name of the lock file to signal exclusive access to other windows. - const WINDOW_LOCK_FILE: &str = "window.lock"; - struct State { /// The id of the project displayed by the window. project_id: ProjectId, @@ -141,18 +138,7 @@ pub(super) mod state { return Ok(()); } } - let mut lock_file = - fslock::LockFile::open(project.gb_dir().join(WINDOW_LOCK_FILE).as_os_str())?; - let got_lock = lock_file - .try_lock() - .context("Failed to check if lock is taken")?; - if !got_lock { - bail!( - "Project '{}' is already opened in another window", - project.title - ); - } - + let exclusive_access = project.try_exclusive_access()?; let handler = handler_from_app(&self.app_handle)?; let worktree_dir = project.path.clone(); let project_id = project.id; @@ -163,7 +149,7 @@ pub(super) mod state { State { project_id, watcher, - exclusive_access: lock_file, + exclusive_access, }, ); tracing::debug!("Maintaining {} Windows", state_by_label.len()); diff --git a/crates/gitbutler-watcher/src/handler.rs b/crates/gitbutler-watcher/src/handler.rs index f92c37508..33a353ecf 100644 --- a/crates/gitbutler-watcher/src/handler.rs +++ b/crates/gitbutler-watcher/src/handler.rs @@ -7,7 +7,7 @@ use gitbutler_command_context::ProjectRepository; use gitbutler_error::error::Marker; use gitbutler_oplog::{ entry::{OperationKind, SnapshotDetails}, - oplog::OplogExt, + OplogExt, }; use gitbutler_project as projects; use gitbutler_project::ProjectId; diff --git a/crates/gitbutler-watcher/vendor/debouncer/Cargo.toml b/crates/gitbutler-watcher/vendor/debouncer/Cargo.toml index f47190268..cbda1792d 100644 --- a/crates/gitbutler-watcher/vendor/debouncer/Cargo.toml +++ b/crates/gitbutler-watcher/vendor/debouncer/Cargo.toml @@ -14,7 +14,7 @@ mock_instant = ["dep:mock_instant"] tracing = "0.1.40" notify = { version = "6.0.1" } -parking_lot = "0.12.3" +parking_lot.workspace = true file-id = "0.2.1" walkdir = "2.2.2" crossbeam-channel = "0.5.13" From 822fd92b9d651eed798a8a67489ee37214003eb3 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 15 Jul 2024 14:22:27 +0200 Subject: [PATCH 12/13] 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. --- .../gitbutler-branch-actions/src/actions.rs | 182 ++++++---- crates/gitbutler-branch-actions/src/base.rs | 16 +- .../src/branch_manager/branch_creation.rs | 28 +- .../src/branch_manager/branch_removal.rs | 13 +- .../src/integration.rs | 31 +- .../gitbutler-branch-actions/src/virtual.rs | 56 +++- .../tests/extra/mod.rs | 312 ++++++++++++------ crates/gitbutler-oplog/src/oplog.rs | 31 +- crates/gitbutler-oplog/src/snapshot.rs | 56 +++- crates/gitbutler-project/src/access.rs | 30 -- crates/gitbutler-watcher/src/handler.rs | 13 +- 11 files changed, 505 insertions(+), 263 deletions(-) diff --git a/crates/gitbutler-branch-actions/src/actions.rs b/crates/gitbutler-branch-actions/src/actions.rs index f170b922d..2507065c9 100644 --- a/crates/gitbutler-branch-actions/src/actions.rs +++ b/crates/gitbutler-branch-actions/src/actions.rs @@ -40,7 +40,10 @@ impl VirtualBranchActions { run_hooks: bool, ) -> 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 = 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, Vec)> { - 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 { 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 { 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> { 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) -> 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 { 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 { 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 { 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 { 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 { 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) } diff --git a/crates/gitbutler-branch-actions/src/base.rs b/crates/gitbutler-branch-actions/src/base.rs index 19f117500..c7d7fcdc9 100644 --- a/crates/gitbutler-branch-actions/src/base.rs +++ b/crates/gitbutler-branch-actions/src/base.rs @@ -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> { 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); diff --git a/crates/gitbutler-branch-actions/src/branch_manager/branch_creation.rs b/crates/gitbutler-branch-actions/src/branch_manager/branch_creation.rs index 6e1212950..66bc2e871 100644 --- a/crates/gitbutler-branch-actions/src/branch_manager/branch_creation.rs +++ b/crates/gitbutler-branch-actions/src/branch_manager/branch_creation.rs @@ -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 { + pub fn create_virtual_branch( + &self, + create: &BranchCreateRequest, + perm: &mut WorktreeWritePermission, + ) -> Result { 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 { + pub fn create_virtual_branch_from_branch( + &self, + upstream: &Refname, + perm: &mut WorktreeWritePermission, + ) -> Result { // 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 { + fn apply_branch( + &self, + branch_id: BranchId, + perm: &mut WorktreeWritePermission, + ) -> Result { 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 diff --git a/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs b/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs index 41b5a8746..589804c44 100644 --- a/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs +++ b/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs @@ -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 { 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")?; diff --git a/crates/gitbutler-branch-actions/src/integration.rs b/crates/gitbutler-branch-actions/src/integration.rs index b69c7b3de..52cf5bbbe 100644 --- a/crates/gitbutler-branch-actions/src/integration.rs +++ b/crates/gitbutler-branch-actions/src/integration.rs @@ -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 { - name: extra_commits - .last() - .map(|commit| commit.message_bstr().to_string()), - ..Default::default() - }) + .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 { diff --git a/crates/gitbutler-branch-actions/src/virtual.rs b/crates/gitbutler-branch-actions/src/virtual.rs index e4c99169e..3e9a3119a 100644 --- a/crates/gitbutler-branch-actions/src/virtual.rs +++ b/crates/gitbutler-branch-actions/src/virtual.rs @@ -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,9 +213,13 @@ 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) - .context("failed to get status by branch")?; + 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 .iter() @@ -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, Vec)> { let mut branches: Vec = 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, + perm: Option<&mut WorktreeWritePermission>, ) -> Result<(AppliedStatuses, Vec)> { 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() { - virtual_branches = vec![branch_manager - .create_virtual_branch(&BranchCreateRequest::default()) - .context("failed to create default branch")?]; + if let Some(perm) = perm { + virtual_branches = vec![branch_manager + .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 = virtual_branches @@ -2115,6 +2131,7 @@ pub(crate) fn amend( branch_id: BranchId, commit_oid: git2::Oid, target_ownership: &BranchOwnershipClaims, + perm: &mut WorktreeWritePermission, ) -> Result { 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() diff --git a/crates/gitbutler-branch-actions/tests/extra/mod.rs b/crates/gitbutler-branch-actions/tests/extra/mod.rs index bfb694adb..3b07a4eee 100644 --- a/crates/gitbutler-branch-actions/tests/extra/mod.rs +++ b/crates/gitbutler-branch-actions/tests/extra/mod.rs @@ -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 { - ownership: Some(branch0.ownership), - ..Default::default() - }) + .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 { - order: Some(1), - ..Default::default() - }) + .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; diff --git a/crates/gitbutler-oplog/src/oplog.rs b/crates/gitbutler-oplog/src/oplog.rs index b0a034cb6..fb6b19378 100644 --- a/crates/gitbutler-oplog/src/oplog.rs +++ b/crates/gitbutler-oplog/src/oplog.rs @@ -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; + fn prepare_snapshot(&self, perm: &WorktreeReadPermission) -> Result; /// 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>; /// 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>; + fn create_snapshot( + &self, + details: SnapshotDetails, + perm: &mut WorktreeWritePermission, + ) -> Result>; /// 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 { - let guard = self.shared_worktree_access(); - prepare_snapshot(self, guard.read_permission()) + fn prepare_snapshot(&self, perm: &WorktreeReadPermission) -> Result { + prepare_snapshot(self, perm) } fn commit_snapshot( &self, snapshot_tree_id: git2::Oid, details: SnapshotDetails, + perm: &mut WorktreeWritePermission, ) -> Result> { - 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> { - 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> { + let tree_id = prepare_snapshot(self, perm.read_permission())?; + commit_snapshot(self, tree_id, details, perm) } fn list_snapshots( diff --git a/crates/gitbutler-oplog/src/snapshot.rs b/crates/gitbutler-oplog/src/snapshot.rs index 7298ff718..24bedd129 100644 --- a/crates/gitbutler-oplog/src/snapshot.rs +++ b/crates/gitbutler-oplog/src/snapshot.rs @@ -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, + 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, + 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(()) } } diff --git a/crates/gitbutler-project/src/access.rs b/crates/gitbutler-project/src/access.rs index 9b5252a03..0effca915 100644 --- a/crates/gitbutler-project/src/access.rs +++ b/crates/gitbutler-project/src/access.rs @@ -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); - -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); impl WorkspaceReadGuard { diff --git a/crates/gitbutler-watcher/src/handler.rs b/crates/gitbutler-watcher/src/handler.rs index 33a353ecf..c6304309a 100644 --- a/crates/gitbutler-watcher/src/handler.rs +++ b/crates/gitbutler-watcher/src/handler.rs @@ -122,13 +122,8 @@ impl Handler { paths: Vec, 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(()) } From 613a773c77b7321863ed44970102d86dad301ed6 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 15 Jul 2024 22:22:55 +0200 Subject: [PATCH 13/13] fix remaining tests that depend on the auto-creation of vbranches --- .../gitbutler-branch-actions/src/actions.rs | 20 +- crates/gitbutler-branch-actions/src/base.rs | 2 +- .../src/branch_manager/branch_removal.rs | 2 +- .../gitbutler-branch-actions/src/virtual.rs | 30 +-- .../tests/extra/mod.rs | 184 ++++++++++-------- 5 files changed, 133 insertions(+), 105 deletions(-) diff --git a/crates/gitbutler-branch-actions/src/actions.rs b/crates/gitbutler-branch-actions/src/actions.rs index 2507065c9..82dbebe04 100644 --- a/crates/gitbutler-branch-actions/src/actions.rs +++ b/crates/gitbutler-branch-actions/src/actions.rs @@ -1,3 +1,12 @@ +use crate::{ + base::{ + get_base_branch_data, set_base_branch, set_target_push_remote, update_base_branch, + BaseBranch, + }, + branch_manager::BranchManagerExt, + remote::{get_branch_data, list_remote_branches, RemoteBranch, RemoteBranchData}, + VirtualBranchesExt, +}; use anyhow::Result; use gitbutler_branch::{ diff, BranchOwnershipClaims, {BranchCreateRequest, BranchId, BranchUpdateRequest}, @@ -13,16 +22,6 @@ use gitbutler_reference::{Refname, RemoteRefname}; use gitbutler_repo::{credentials::Helper, RepoActionsExt, RepositoryExt}; use tracing::instrument; -use crate::{ - base::{ - get_base_branch_data, set_base_branch, set_target_push_remote, update_base_branch, - BaseBranch, - }, - branch_manager::BranchManagerExt, - remote::{get_branch_data, list_remote_branches, RemoteBranch, RemoteBranchData}, - VirtualBranchesExt, -}; - use super::r#virtual as branch; use crate::files::RemoteBranchFile; @@ -50,6 +49,7 @@ impl VirtualBranchActions { message, ownership, run_hooks, + guard.write_permission(), ) .map_err(Into::into); let _ = snapshot_tree.and_then(|snapshot_tree| { diff --git a/crates/gitbutler-branch-actions/src/base.rs b/crates/gitbutler-branch-actions/src/base.rs index c7d7fcdc9..b21740fc2 100644 --- a/crates/gitbutler-branch-actions/src/base.rs +++ b/crates/gitbutler-branch-actions/src/base.rs @@ -367,7 +367,7 @@ pub(crate) fn update_base_branch( // try to update every branch let updated_vbranches = - vb::get_status_by_branch(project_repository, Some(&integration_commit))? + vb::get_status_by_branch(project_repository, Some(&integration_commit), perm)? .0 .into_iter() .map(|(branch, _)| branch) diff --git a/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs b/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs index 589804c44..21de5f588 100644 --- a/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs +++ b/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs @@ -82,7 +82,7 @@ impl BranchManager<'_> { self.project_repository, &integration_commit.id(), virtual_branches, - Some(perm), + perm, ) .context("failed to get status by branch")?; diff --git a/crates/gitbutler-branch-actions/src/virtual.rs b/crates/gitbutler-branch-actions/src/virtual.rs index 3e9a3119a..fccdcc485 100644 --- a/crates/gitbutler-branch-actions/src/virtual.rs +++ b/crates/gitbutler-branch-actions/src/virtual.rs @@ -217,7 +217,7 @@ pub fn unapply_ownership( project_repository, &integration_commit_id, virtual_branches, - Some(perm), + perm, ) .context("failed to get status by branch")?; @@ -389,7 +389,8 @@ pub fn list_virtual_branches( let integration_commit_id = get_workspace_head(&vb_state, ctx)?; let integration_commit = ctx.repo().find_commit(integration_commit_id).unwrap(); - let (statuses, skipped_files) = get_status_by_branch(ctx, Some(&integration_commit.id()))?; + let (statuses, skipped_files) = + get_status_by_branch(ctx, Some(&integration_commit.id()), perm)?; let max_selected_for_changes = statuses .iter() .filter_map(|(branch, _)| branch.selected_for_changes) @@ -1041,6 +1042,7 @@ pub type VirtualBranchHunksByPathMap = HashMap>; pub fn get_status_by_branch( project_repository: &ProjectRepository, integration_commit: Option<&git2::Oid>, + perm: &mut WorktreeWritePermission, ) -> Result<(AppliedStatuses, Vec)> { let vb_state = project_repository.project().virtual_branches(); @@ -1055,7 +1057,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, + perm, )?; Ok((applied_status, skipped_files)) @@ -1139,7 +1141,7 @@ pub(crate) fn get_applied_status( project_repository: &ProjectRepository, integration_commit: &git2::Oid, mut virtual_branches: Vec, - perm: Option<&mut WorktreeWritePermission>, + perm: &mut WorktreeWritePermission, ) -> Result<(AppliedStatuses, Vec)> { let base_file_diffs = diff::workdir(project_repository.repo(), &integration_commit.to_owned()) .context("failed to diff workdir")?; @@ -1158,13 +1160,9 @@ 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(), perm) - .context("failed to create default branch")?]; - } else { - bail!("Would have wanted to create a virtual branch but wasn't allowed to make changes") - } + virtual_branches = vec![branch_manager + .create_virtual_branch(&BranchCreateRequest::default(), perm) + .context("failed to create default branch")?]; } let mut diffs_by_branch: HashMap = virtual_branches @@ -1600,6 +1598,7 @@ pub fn commit( message: &str, ownership: Option<&BranchOwnershipClaims>, run_hooks: bool, + perm: &mut WorktreeWritePermission, ) -> Result { let mut message_buffer = message.to_owned(); let vb_state = project_repository.project().virtual_branches(); @@ -1629,8 +1628,9 @@ pub fn commit( let integration_commit_id = get_workspace_head(&vb_state, project_repository)?; // get the files to commit - let (statuses, _) = get_status_by_branch(project_repository, Some(&integration_commit_id)) - .context("failed to get status by branch")?; + let (statuses, _) = + get_status_by_branch(project_repository, Some(&integration_commit_id), perm) + .context("failed to get status by branch")?; let (ref mut branch, files) = statuses .into_iter() @@ -2152,7 +2152,7 @@ pub(crate) fn amend( project_repository, &integration_commit_id, virtual_branches, - Some(perm), + perm, )?; let (ref mut target_branch, target_status) = applied_statuses @@ -2640,7 +2640,7 @@ pub(crate) fn move_commit( project_repository, &integration_commit_id, applied_branches, - Some(perm), + perm, )?; let (ref mut source_branch, source_status) = applied_statuses diff --git a/crates/gitbutler-branch-actions/tests/extra/mod.rs b/crates/gitbutler-branch-actions/tests/extra/mod.rs index 3b07a4eee..b7608d271 100644 --- a/crates/gitbutler-branch-actions/tests/extra/mod.rs +++ b/crates/gitbutler-branch-actions/tests/extra/mod.rs @@ -61,7 +61,14 @@ fn commit_on_branch_then_change_file_then_get_status() -> Result<()> { assert_eq!(branch.commits.len(), 0); // commit - commit(project_repository, branch1_id, "test commit", None, false)?; + commit( + project_repository, + branch1_id, + "test commit", + None, + false, + guard.write_permission(), + )?; // status (no files) let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?; @@ -155,7 +162,14 @@ fn track_binary_files() -> Result<()> { ); // commit - commit(project_repository, branch1_id, "test commit", None, false)?; + commit( + project_repository, + branch1_id, + "test commit", + None, + false, + guard.write_permission(), + )?; // status (no files) let (branches, _) = @@ -182,7 +196,14 @@ fn track_binary_files() -> Result<()> { file.write_all(&image_data)?; // commit - commit(project_repository, branch1_id, "test commit", None, false)?; + commit( + project_repository, + branch1_id, + "test commit", + None, + false, + guard.write_permission(), + )?; let (branches, _) = list_virtual_branches(project_repository, guard.write_permission()).unwrap(); @@ -215,14 +236,13 @@ fn create_branch_with_ownership() -> Result<()> { std::fs::write(Path::new(&project.path).join(file_path), "line1\nline2\n").unwrap(); let branch_manager = project_repository.branch_manager(); + let mut guard = project.exclusive_worktree_access(); let branch0 = branch_manager - .create_virtual_branch( - &BranchCreateRequest::default(), - project.exclusive_worktree_access().write_permission(), - ) + .create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission()) .expect("failed to create virtual branch"); - get_status_by_branch(project_repository, None).expect("failed to get status"); + get_status_by_branch(project_repository, None, guard.write_permission()) + .expect("failed to get status"); let vb_state = VirtualBranchesHandle::new(project_repository.project().gb_dir()); let branch0 = vb_state.get_branch_in_workspace(branch0.id).unwrap(); @@ -233,11 +253,11 @@ fn create_branch_with_ownership() -> Result<()> { ownership: Some(branch0.ownership), ..Default::default() }, - project.exclusive_worktree_access().write_permission(), + guard.write_permission(), ) .expect("failed to create virtual branch"); - let statuses = get_status_by_branch(project_repository, None) + let statuses = get_status_by_branch(project_repository, None, guard.write_permission()) .expect("failed to get status") .0; @@ -346,22 +366,17 @@ fn hunk_expantion() -> Result<()> { std::fs::write(Path::new(&project.path).join(file_path), "line1\nline2\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(), - project.exclusive_worktree_access().write_permission(), - ) + .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(), - project.exclusive_worktree_access().write_permission(), - ) + .create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission()) .expect("failed to create virtual branch") .id; - let statuses = get_status_by_branch(project_repository, None) + let statuses = get_status_by_branch(project_repository, None, guard.write_permission()) .expect("failed to get status") .0; @@ -398,7 +413,7 @@ fn hunk_expantion() -> Result<()> { "line1\nline2\nline3\n", )?; - let statuses = get_status_by_branch(project_repository, None) + let statuses = get_status_by_branch(project_repository, None, guard.write_permission()) .expect("failed to get status") .0; let files_by_branch_id = statuses @@ -417,14 +432,20 @@ fn hunk_expantion() -> Result<()> { fn get_status_files_by_branch_no_hunks_no_branches() -> Result<()> { let suite = Suite::default(); let Case { - project_repository, .. + project_repository, + project, + .. } = &suite.new_case(); set_test_target(project_repository)?; - let statuses = get_status_by_branch(project_repository, None) - .expect("failed to get status") - .0; + let statuses = get_status_by_branch( + project_repository, + None, + project.exclusive_worktree_access().write_permission(), + ) + .expect("failed to get status") + .0; assert_eq!(statuses.len(), 0); @@ -446,22 +467,17 @@ fn get_status_files_by_branch() -> Result<()> { std::fs::write(Path::new(&project.path).join(file_path), "line1\nline2\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(), - project.exclusive_worktree_access().write_permission(), - ) + .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(), - project.exclusive_worktree_access().write_permission(), - ) + .create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission()) .expect("failed to create virtual branch") .id; - let statuses = get_status_by_branch(project_repository, None) + let statuses = get_status_by_branch(project_repository, None, guard.write_permission()) .expect("failed to get status") .0; let files_by_branch_id = statuses @@ -491,25 +507,17 @@ fn move_hunks_multiple_sources() -> 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(), - project.exclusive_worktree_access().write_permission(), - ) + .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(), - project.exclusive_worktree_access().write_permission(), - ) + .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(), - project.exclusive_worktree_access().write_permission(), - ) + .create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission()) .expect("failed to create virtual branch") .id; @@ -530,7 +538,7 @@ fn move_hunks_multiple_sources() -> Result<()> { }; vb_state.set_branch(branch1.clone())?; - let statuses = get_status_by_branch(project_repository, None) + let statuses = get_status_by_branch(project_repository, None, guard.write_permission()) .expect("failed to get status") .0; @@ -555,7 +563,7 @@ fn move_hunks_multiple_sources() -> Result<()> { }, )?; - let statuses = get_status_by_branch(project_repository, None) + let statuses = get_status_by_branch(project_repository, None, guard.write_permission()) .expect("failed to get status") .0; @@ -603,23 +611,18 @@ fn move_hunks_partial_explicitly() -> Result<()> { )?; let branch_manager = project_repository.branch_manager(); + let mut guard = project.exclusive_worktree_access(); let branch1_id = branch_manager - .create_virtual_branch( - &BranchCreateRequest::default(), - project.exclusive_worktree_access().write_permission(), - ) + .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(), - project.exclusive_worktree_access().write_permission(), - ) + .create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission()) .expect("failed to create virtual branch") .id; - let statuses = get_status_by_branch(project_repository, None) + let statuses = get_status_by_branch(project_repository, None, guard.write_permission()) .expect("failed to get status") .0; let files_by_branch_id = statuses @@ -641,7 +644,7 @@ fn move_hunks_partial_explicitly() -> Result<()> { }, )?; - let statuses = get_status_by_branch(project_repository, None) + let statuses = get_status_by_branch(project_repository, None, guard.write_permission()) .expect("failed to get status") .0; @@ -694,14 +697,12 @@ fn add_new_hunk_to_the_end() -> Result<()> { )?; let branch_manager = project_repository.branch_manager(); + let mut guard = project.exclusive_worktree_access(); branch_manager - .create_virtual_branch( - &BranchCreateRequest::default(), - project.exclusive_worktree_access().write_permission(), - ) + .create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission()) .expect("failed to create virtual branch"); - let statuses = get_status_by_branch(project_repository, None) + let statuses = get_status_by_branch(project_repository, None, guard.write_permission()) .expect("failed to get status") .0; assert_eq!( @@ -714,7 +715,7 @@ fn add_new_hunk_to_the_end() -> Result<()> { "line0\nline1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nline9\nline10\nline11\nline12\nline13\nline14\nline15\n", )?; - let statuses = get_status_by_branch(project_repository, None) + let statuses = get_status_by_branch(project_repository, None, guard.write_permission()) .expect("failed to get status") .0; @@ -1027,6 +1028,7 @@ async fn merge_vbranch_upstream_conflict() -> Result<()> { "fix merge conflict", None, false, + guard.write_permission(), )?; let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?; @@ -1561,6 +1563,7 @@ fn upstream_integrated_vbranch() -> Result<()> { "integrated commit", None, false, + guard.write_permission(), )?; commit( project_repository, @@ -1568,6 +1571,7 @@ fn upstream_integrated_vbranch() -> Result<()> { "non-integrated commit", None, false, + guard.write_permission(), )?; let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?; @@ -1630,6 +1634,7 @@ fn commit_same_hunk_twice() -> Result<()> { "first commit to test.txt", None, false, + guard.write_permission(), )?; let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?; @@ -1664,6 +1669,7 @@ fn commit_same_hunk_twice() -> Result<()> { "second commit to test.txt", None, false, + guard.write_permission(), )?; let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?; @@ -1724,6 +1730,7 @@ fn commit_same_file_twice() -> Result<()> { "first commit to test.txt", None, false, + guard.write_permission(), )?; let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?; @@ -1758,6 +1765,7 @@ fn commit_same_file_twice() -> Result<()> { "second commit to test.txt", None, false, + guard.write_permission(), )?; let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?; @@ -1818,6 +1826,7 @@ fn commit_partial_by_hunk() -> Result<()> { "first commit to test.txt", Some(&"test.txt:1-6".parse::().unwrap()), false, + guard.write_permission(), )?; let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?; @@ -1835,6 +1844,7 @@ fn commit_partial_by_hunk() -> Result<()> { "second commit to test.txt", Some(&"test.txt:16-22".parse::().unwrap()), false, + guard.write_permission(), )?; let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?; @@ -1887,6 +1897,7 @@ fn commit_partial_by_file() -> Result<()> { "branch1 commit", None, false, + guard.write_permission(), )?; let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?; @@ -1948,6 +1959,7 @@ fn commit_add_and_delete_files() -> Result<()> { "branch1 commit", None, false, + guard.write_permission(), )?; let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?; @@ -2015,6 +2027,7 @@ fn commit_executable_and_symlinks() -> Result<()> { "branch1 commit", None, false, + guard.write_permission(), )?; let (branches, _) = list_virtual_branches(project_repository, guard.write_permission())?; @@ -2169,11 +2182,9 @@ fn pre_commit_hook_rejection() -> 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(), - project.exclusive_worktree_access().write_permission(), - ) + .create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission()) .expect("failed to create virtual branch") .id; @@ -2189,7 +2200,14 @@ fn pre_commit_hook_rejection() -> Result<()> { git2_hooks::create_hook(project_repository.repo(), git2_hooks::HOOK_PRE_COMMIT, hook); - let res = commit(project_repository, branch1_id, "test commit", None, true); + let res = commit( + project_repository, + branch1_id, + "test commit", + None, + true, + guard.write_permission(), + ); let err = res.unwrap_err(); assert_eq!(err.to_string(), "commit hook rejected: rejected"); @@ -2212,11 +2230,9 @@ fn post_commit_hook() -> 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(), - project.exclusive_worktree_access().write_permission(), - ) + .create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission()) .expect("failed to create virtual branch") .id; @@ -2244,7 +2260,14 @@ fn post_commit_hook() -> Result<()> { assert!(!hook_ran_proof.exists()); - commit(project_repository, branch1_id, "test commit", None, true)?; + commit( + project_repository, + branch1_id, + "test commit", + None, + true, + guard.write_permission(), + )?; assert!(hook_ran_proof.exists()); @@ -2266,11 +2289,9 @@ fn commit_msg_hook_rejection() -> 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(), - project.exclusive_worktree_access().write_permission(), - ) + .create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission()) .expect("failed to create virtual branch") .id; @@ -2286,7 +2307,14 @@ fn commit_msg_hook_rejection() -> Result<()> { git2_hooks::create_hook(project_repository.repo(), git2_hooks::HOOK_COMMIT_MSG, hook); - let res = commit(project_repository, branch1_id, "test commit", None, true); + let res = commit( + project_repository, + branch1_id, + "test commit", + None, + true, + guard.write_permission(), + ); let err = res.unwrap_err(); assert_eq!(err.to_string(), "commit-msg hook rejected: rejected");