From 2d61cae43ade04d60bbac8d16f910eef0fe3dae5 Mon Sep 17 00:00:00 2001 From: Caleb Owens Date: Mon, 29 Jul 2024 13:19:38 +0200 Subject: [PATCH] Introduce operating modes --- Cargo.lock | 11 ++++ Cargo.toml | 7 ++- crates/gitbutler-branch-actions/Cargo.toml | 1 + .../gitbutler-branch-actions/src/actions.rs | 57 +++++++++++++++++-- crates/gitbutler-branch-actions/src/status.rs | 3 + .../gitbutler-branch-actions/src/virtual.rs | 3 + crates/gitbutler-operating-modes/Cargo.toml | 12 ++++ crates/gitbutler-operating-modes/src/lib.rs | 38 +++++++++++++ 8 files changed, 123 insertions(+), 9 deletions(-) create mode 100644 crates/gitbutler-operating-modes/Cargo.toml create mode 100644 crates/gitbutler-operating-modes/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 2bb7f37ab..819a0c27c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2024,6 +2024,7 @@ dependencies = [ "gitbutler-fs", "gitbutler-git", "gitbutler-id", + "gitbutler-operating-modes", "gitbutler-oplog", "gitbutler-project", "gitbutler-reference", @@ -2188,6 +2189,16 @@ dependencies = [ "walkdir", ] +[[package]] +name = "gitbutler-operating-modes" +version = "0.0.0" +dependencies = [ + "anyhow", + "git2", + "gitbutler-command-context", + "serde", +] + [[package]] name = "gitbutler-oplog" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index 189870b7d..74524e821 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,15 +26,15 @@ members = [ "crates/gitbutler-time", "crates/gitbutler-commit", "crates/gitbutler-tagged-string", - "crates/gitbutler-url", + "crates/gitbutler-url", "crates/gitbutler-diff", + "crates/gitbutler-operating-modes", ] resolver = "2" [workspace.dependencies] # Add the `tracing` or `tracing-detail` features to see more of gitoxide in the logs. Useful to see which programs it invokes. -gix = { version = "0.64", default-features = false, features = [ -] } +gix = { version = "0.64", default-features = false, features = [] } git2 = { version = "0.18.3", features = [ "vendored-openssl", "vendored-libgit2", @@ -75,6 +75,7 @@ gitbutler-commit = { path = "crates/gitbutler-commit" } gitbutler-tagged-string = { path = "crates/gitbutler-tagged-string" } gitbutler-url = { path = "crates/gitbutler-url" } gitbutler-diff = { path = "crates/gitbutler-diff" } +gitbutler-operating-modes = { path = "crates/gitbutler-operating-modes" } [profile.release] codegen-units = 1 # Compile crates one after another so the compiler can optimize better diff --git a/crates/gitbutler-branch-actions/Cargo.toml b/crates/gitbutler-branch-actions/Cargo.toml index 09ba81ea5..d9f1ef722 100644 --- a/crates/gitbutler-branch-actions/Cargo.toml +++ b/crates/gitbutler-branch-actions/Cargo.toml @@ -24,6 +24,7 @@ gitbutler-commit.workspace = true gitbutler-url.workspace = true gitbutler-fs.workspace = true gitbutler-diff.workspace = true +gitbutler-operating-modes.workspace = true serde = { workspace = true, features = ["std"] } bstr = "1.9.1" diffy = "0.4.0" diff --git a/crates/gitbutler-branch-actions/src/actions.rs b/crates/gitbutler-branch-actions/src/actions.rs index 331f26872..071275a3e 100644 --- a/crates/gitbutler-branch-actions/src/actions.rs +++ b/crates/gitbutler-branch-actions/src/actions.rs @@ -1,6 +1,7 @@ -use anyhow::Result; +use anyhow::{Context, Result}; use gitbutler_branch::{BranchCreateRequest, BranchId, BranchOwnershipClaims, BranchUpdateRequest}; use gitbutler_command_context::CommandContext; +use gitbutler_operating_modes::assure_open_workspace_mode; use gitbutler_oplog::{ entry::{OperationKind, SnapshotDetails}, OplogExt, SnapshotExt, @@ -35,6 +36,8 @@ impl VirtualBranchActions { run_hooks: bool, ) -> Result { let ctx = open_with_verify(project)?; + assure_open_workspace_mode(&ctx) + .context("Creating a commit requires open workspace mode")?; let mut guard = project.exclusive_worktree_access(); let snapshot_tree = ctx.project().prepare_snapshot(guard.read_permission()); let result = @@ -57,6 +60,8 @@ impl VirtualBranchActions { branch_name: &RemoteRefname, ) -> Result { let ctx = CommandContext::open(project)?; + assure_open_workspace_mode(&ctx) + .context("Testing branch mergability requires open workspace mode")?; branch::is_remote_branch_mergeable(&ctx, branch_name).map_err(Into::into) } @@ -64,11 +69,13 @@ impl VirtualBranchActions { &self, project: &Project, ) -> Result<(Vec, Vec)> { - branch::list_virtual_branches( - &open_with_verify(project)?, - project.exclusive_worktree_access().write_permission(), - ) - .map_err(Into::into) + let ctx = open_with_verify(project)?; + + assure_open_workspace_mode(&ctx) + .context("Listing virtual branches requires open workspace mode")?; + + branch::list_virtual_branches(&ctx, project.exclusive_worktree_access().write_permission()) + .map_err(Into::into) } pub fn create_virtual_branch( @@ -77,6 +84,8 @@ impl VirtualBranchActions { create: &BranchCreateRequest, ) -> Result { let ctx = open_with_verify(project)?; + assure_open_workspace_mode(&ctx) + .context("Creating a branch requires open workspace mode")?; let mut guard = project.exclusive_worktree_access(); let branch_manager = ctx.branch_manager(); let branch_id = branch_manager @@ -121,6 +130,8 @@ impl VirtualBranchActions { pub fn integrate_upstream_commits(&self, project: &Project, branch_id: BranchId) -> Result<()> { let ctx = open_with_verify(project)?; + assure_open_workspace_mode(&ctx) + .context("Integrating upstream commits requires open workspace mode")?; let mut guard = project.exclusive_worktree_access(); let _ = ctx.project().create_snapshot( SnapshotDetails::new(OperationKind::MergeUpstream), @@ -131,6 +142,8 @@ impl VirtualBranchActions { pub fn update_base_branch(&self, project: &Project) -> Result> { let ctx = open_with_verify(project)?; + assure_open_workspace_mode(&ctx) + .context("Updating base branch requires open workspace mode")?; let mut guard = project.exclusive_worktree_access(); let _ = ctx.project().create_snapshot( SnapshotDetails::new(OperationKind::UpdateWorkspaceBase), @@ -145,6 +158,8 @@ impl VirtualBranchActions { branch_update: BranchUpdateRequest, ) -> Result<()> { let ctx = open_with_verify(project)?; + assure_open_workspace_mode(&ctx) + .context("Updating a branch requires open workspace mode")?; let mut guard = project.exclusive_worktree_access(); let snapshot_tree = ctx.project().prepare_snapshot(guard.read_permission()); let old_branch = ctx @@ -171,6 +186,8 @@ impl VirtualBranchActions { branch_updates: Vec, ) -> Result<()> { let ctx = open_with_verify(project)?; + assure_open_workspace_mode(&ctx) + .context("Updating branch order requires open workspace mode")?; for branch_update in branch_updates { let branch = ctx .project() @@ -185,6 +202,8 @@ impl VirtualBranchActions { pub fn delete_virtual_branch(&self, project: &Project, branch_id: BranchId) -> Result<()> { let ctx = open_with_verify(project)?; + assure_open_workspace_mode(&ctx) + .context("Deleting a branch order requires open workspace mode")?; let branch_manager = ctx.branch_manager(); let mut guard = project.exclusive_worktree_access(); branch_manager.delete_branch(branch_id, guard.write_permission()) @@ -196,6 +215,7 @@ impl VirtualBranchActions { ownership: &BranchOwnershipClaims, ) -> Result<()> { let ctx = open_with_verify(project)?; + assure_open_workspace_mode(&ctx).context("Unapply a patch requires open workspace mode")?; let mut guard = project.exclusive_worktree_access(); let _ = ctx.project().create_snapshot( SnapshotDetails::new(OperationKind::DiscardHunk), @@ -206,6 +226,8 @@ impl VirtualBranchActions { pub fn reset_files(&self, project: &Project, files: &Vec) -> Result<()> { let ctx = open_with_verify(project)?; + assure_open_workspace_mode(&ctx) + .context("Resetting a file requires open workspace mode")?; let mut guard = project.exclusive_worktree_access(); let _ = ctx.project().create_snapshot( SnapshotDetails::new(OperationKind::DiscardFile), @@ -222,6 +244,8 @@ impl VirtualBranchActions { ownership: &BranchOwnershipClaims, ) -> Result { let ctx = open_with_verify(project)?; + assure_open_workspace_mode(&ctx) + .context("Amending a commit requires open workspace mode")?; let mut guard = project.exclusive_worktree_access(); let _ = ctx.project().create_snapshot( SnapshotDetails::new(OperationKind::AmendCommit), @@ -239,6 +263,8 @@ impl VirtualBranchActions { ownership: &BranchOwnershipClaims, ) -> Result { let ctx = open_with_verify(project)?; + assure_open_workspace_mode(&ctx) + .context("Amending a commit requires open workspace mode")?; let mut guard = project.exclusive_worktree_access(); let _ = ctx.project().create_snapshot( SnapshotDetails::new(OperationKind::MoveCommitFile), @@ -255,6 +281,8 @@ impl VirtualBranchActions { commit_oid: git2::Oid, ) -> Result<()> { let ctx = open_with_verify(project)?; + assure_open_workspace_mode(&ctx) + .context("Undoing a commit requires open workspace mode")?; let mut guard = project.exclusive_worktree_access(); let snapshot_tree = ctx.project().prepare_snapshot(guard.read_permission()); let result: Result<()> = @@ -278,6 +306,8 @@ impl VirtualBranchActions { offset: i32, ) -> Result<()> { let ctx = open_with_verify(project)?; + assure_open_workspace_mode(&ctx) + .context("Inserting a blank commit requires open workspace mode")?; let mut guard = project.exclusive_worktree_access(); let _ = ctx.project().create_snapshot( SnapshotDetails::new(OperationKind::InsertBlankCommit), @@ -294,6 +324,8 @@ impl VirtualBranchActions { offset: i32, ) -> Result<()> { let ctx = open_with_verify(project)?; + assure_open_workspace_mode(&ctx) + .context("Reordering a commit requires open workspace mode")?; let mut guard = project.exclusive_worktree_access(); let _ = ctx.project().create_snapshot( SnapshotDetails::new(OperationKind::ReorderCommit), @@ -309,6 +341,8 @@ impl VirtualBranchActions { target_commit_oid: git2::Oid, ) -> Result<()> { let ctx = open_with_verify(project)?; + assure_open_workspace_mode(&ctx) + .context("Resetting a branch requires open workspace mode")?; let mut guard = project.exclusive_worktree_access(); let _ = ctx.project().create_snapshot( SnapshotDetails::new(OperationKind::UndoCommit), @@ -323,6 +357,8 @@ impl VirtualBranchActions { branch_id: BranchId, ) -> Result { let ctx = open_with_verify(project)?; + assure_open_workspace_mode(&ctx) + .context("Converting branch to a real branch requires open workspace mode")?; let mut guard = project.exclusive_worktree_access(); let snapshot_tree = ctx.project().prepare_snapshot(guard.read_permission()); let branch_manager = ctx.branch_manager(); @@ -348,6 +384,8 @@ impl VirtualBranchActions { ) -> Result<()> { let helper = Helper::default(); let ctx = open_with_verify(project)?; + assure_open_workspace_mode(&ctx) + .context("Pushing a branch requires open workspace mode")?; branch::push(&ctx, branch_id, with_force, &helper, askpass) } @@ -372,6 +410,8 @@ impl VirtualBranchActions { commit_oid: git2::Oid, ) -> Result<()> { let ctx = open_with_verify(project)?; + assure_open_workspace_mode(&ctx) + .context("Squashing a commit requires open workspace mode")?; let mut guard = project.exclusive_worktree_access(); let _ = ctx.project().create_snapshot( SnapshotDetails::new(OperationKind::SquashCommit), @@ -388,6 +428,8 @@ impl VirtualBranchActions { message: &str, ) -> Result<()> { let ctx = open_with_verify(project)?; + assure_open_workspace_mode(&ctx) + .context("Updating a commit message requires open workspace mode")?; let mut guard = project.exclusive_worktree_access(); let _ = ctx.project().create_snapshot( SnapshotDetails::new(OperationKind::UpdateCommitMessage), @@ -434,6 +476,7 @@ impl VirtualBranchActions { commit_oid: git2::Oid, ) -> Result<()> { let ctx = open_with_verify(project)?; + assure_open_workspace_mode(&ctx).context("Moving a commit requires open workspace mode")?; let mut guard = project.exclusive_worktree_access(); let _ = ctx.project().create_snapshot( SnapshotDetails::new(OperationKind::MoveCommit), @@ -449,6 +492,8 @@ impl VirtualBranchActions { remote: Option, ) -> Result { let ctx = open_with_verify(project)?; + assure_open_workspace_mode(&ctx) + .context("Creating a virtual branch from a branch open workspace mode")?; let branch_manager = ctx.branch_manager(); let mut guard = project.exclusive_worktree_access(); branch_manager diff --git a/crates/gitbutler-branch-actions/src/status.rs b/crates/gitbutler-branch-actions/src/status.rs index 39cf12d4e..d9c575289 100644 --- a/crates/gitbutler-branch-actions/src/status.rs +++ b/crates/gitbutler-branch-actions/src/status.rs @@ -6,6 +6,7 @@ use gitbutler_branch::{ }; use gitbutler_command_context::CommandContext; use gitbutler_diff::{diff_files_into_hunks, GitHunk, Hunk, HunkHash}; +use gitbutler_operating_modes::assure_open_workspace_mode; use gitbutler_project::access::WorktreeWritePermission; use gitbutler_repo::RepositoryExt; @@ -32,6 +33,8 @@ pub fn get_applied_status( ctx: &CommandContext, perm: Option<&mut WorktreeWritePermission>, ) -> Result { + assure_open_workspace_mode(ctx) + .context("Getting applied status requires open workspace mode")?; let integration_commit = get_workspace_head(ctx)?; let mut virtual_branches = ctx .project() diff --git a/crates/gitbutler-branch-actions/src/virtual.rs b/crates/gitbutler-branch-actions/src/virtual.rs index 0e485a8af..2ef8fe872 100644 --- a/crates/gitbutler-branch-actions/src/virtual.rs +++ b/crates/gitbutler-branch-actions/src/virtual.rs @@ -16,6 +16,7 @@ use gitbutler_command_context::CommandContext; use gitbutler_commit::{commit_ext::CommitExt, commit_headers::HasCommitHeaders}; use gitbutler_diff::{trees, GitHunk, Hunk}; use gitbutler_error::error::{Code, Marker}; +use gitbutler_operating_modes::assure_open_workspace_mode; use gitbutler_project::access::WorktreeWritePermission; use gitbutler_reference::{normalize_branch_name, Refname, RemoteRefname}; use gitbutler_repo::{ @@ -258,6 +259,8 @@ pub fn list_virtual_branches( // that conditionally write things. perm: &mut WorktreeWritePermission, ) -> Result<(Vec, Vec)> { + assure_open_workspace_mode(ctx) + .context("Listing virtual branches requires open workspace mode")?; let mut branches: Vec = Vec::new(); let vb_state = ctx.project().virtual_branches(); diff --git a/crates/gitbutler-operating-modes/Cargo.toml b/crates/gitbutler-operating-modes/Cargo.toml new file mode 100644 index 000000000..64f1b9068 --- /dev/null +++ b/crates/gitbutler-operating-modes/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "gitbutler-operating-modes" +version = "0.0.0" +edition = "2021" +authors = ["GitButler "] +publish = false + +[dependencies] +serde = { workspace = true, features = ["std"] } +git2.workspace = true +anyhow.workspace = true +gitbutler-command-context.workspace = true diff --git a/crates/gitbutler-operating-modes/src/lib.rs b/crates/gitbutler-operating-modes/src/lib.rs new file mode 100644 index 000000000..c673dbe1b --- /dev/null +++ b/crates/gitbutler-operating-modes/src/lib.rs @@ -0,0 +1,38 @@ +use anyhow::{bail, Context, Result}; +use gitbutler_command_context::CommandContext; + +/// Operating Modes: +/// Gitbutler currently has two main operating modes: +/// - `in workspace mode`: When the app is on the gitbutler/integration branch. +/// This is when normal operations can be performed. +/// - `outside workspace mode`: When the user has left the gitbutler/integration +/// branch to perform regular git commands. + +const INTEGRATION_BRANCH_REF: &str = "refs/heads/gitbutler/integration"; + +pub fn in_open_workspace_mode(ctx: &CommandContext) -> Result { + let head_ref = ctx.repository().head().context("failed to get head")?; + let head_ref_name = head_ref.name().context("failed to get head name")?; + + Ok(head_ref_name == INTEGRATION_BRANCH_REF) +} + +pub fn assure_open_workspace_mode(ctx: &CommandContext) -> Result<()> { + if in_open_workspace_mode(ctx)? { + Ok(()) + } else { + bail!("Unexpected state: cannot perform operation on non-integration branch") + } +} + +pub fn in_outside_workspace_mode(ctx: &CommandContext) -> Result { + in_open_workspace_mode(ctx).map(|open_mode| !open_mode) +} + +pub fn assure_outside_workspace_mode(ctx: &CommandContext) -> Result<()> { + if in_open_workspace_mode(ctx)? { + Ok(()) + } else { + bail!("Unexpected state: cannot perform operation on non-integration branch") + } +}