Introduce operating modes

This commit is contained in:
Caleb Owens 2024-07-29 13:19:38 +02:00
parent 4ae1a9be62
commit 2d61cae43a
8 changed files with 123 additions and 9 deletions

11
Cargo.lock generated
View File

@ -2024,6 +2024,7 @@ dependencies = [
"gitbutler-fs", "gitbutler-fs",
"gitbutler-git", "gitbutler-git",
"gitbutler-id", "gitbutler-id",
"gitbutler-operating-modes",
"gitbutler-oplog", "gitbutler-oplog",
"gitbutler-project", "gitbutler-project",
"gitbutler-reference", "gitbutler-reference",
@ -2188,6 +2189,16 @@ dependencies = [
"walkdir", "walkdir",
] ]
[[package]]
name = "gitbutler-operating-modes"
version = "0.0.0"
dependencies = [
"anyhow",
"git2",
"gitbutler-command-context",
"serde",
]
[[package]] [[package]]
name = "gitbutler-oplog" name = "gitbutler-oplog"
version = "0.0.0" version = "0.0.0"

View File

@ -26,15 +26,15 @@ members = [
"crates/gitbutler-time", "crates/gitbutler-time",
"crates/gitbutler-commit", "crates/gitbutler-commit",
"crates/gitbutler-tagged-string", "crates/gitbutler-tagged-string",
"crates/gitbutler-url", "crates/gitbutler-url",
"crates/gitbutler-diff", "crates/gitbutler-diff",
"crates/gitbutler-operating-modes",
] ]
resolver = "2" resolver = "2"
[workspace.dependencies] [workspace.dependencies]
# Add the `tracing` or `tracing-detail` features to see more of gitoxide in the logs. Useful to see which programs it invokes. # 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 = [ git2 = { version = "0.18.3", features = [
"vendored-openssl", "vendored-openssl",
"vendored-libgit2", "vendored-libgit2",
@ -75,6 +75,7 @@ gitbutler-commit = { path = "crates/gitbutler-commit" }
gitbutler-tagged-string = { path = "crates/gitbutler-tagged-string" } gitbutler-tagged-string = { path = "crates/gitbutler-tagged-string" }
gitbutler-url = { path = "crates/gitbutler-url" } gitbutler-url = { path = "crates/gitbutler-url" }
gitbutler-diff = { path = "crates/gitbutler-diff" } gitbutler-diff = { path = "crates/gitbutler-diff" }
gitbutler-operating-modes = { path = "crates/gitbutler-operating-modes" }
[profile.release] [profile.release]
codegen-units = 1 # Compile crates one after another so the compiler can optimize better codegen-units = 1 # Compile crates one after another so the compiler can optimize better

View File

@ -24,6 +24,7 @@ gitbutler-commit.workspace = true
gitbutler-url.workspace = true gitbutler-url.workspace = true
gitbutler-fs.workspace = true gitbutler-fs.workspace = true
gitbutler-diff.workspace = true gitbutler-diff.workspace = true
gitbutler-operating-modes.workspace = true
serde = { workspace = true, features = ["std"] } serde = { workspace = true, features = ["std"] }
bstr = "1.9.1" bstr = "1.9.1"
diffy = "0.4.0" diffy = "0.4.0"

View File

@ -1,6 +1,7 @@
use anyhow::Result; use anyhow::{Context, Result};
use gitbutler_branch::{BranchCreateRequest, BranchId, BranchOwnershipClaims, BranchUpdateRequest}; use gitbutler_branch::{BranchCreateRequest, BranchId, BranchOwnershipClaims, BranchUpdateRequest};
use gitbutler_command_context::CommandContext; use gitbutler_command_context::CommandContext;
use gitbutler_operating_modes::assure_open_workspace_mode;
use gitbutler_oplog::{ use gitbutler_oplog::{
entry::{OperationKind, SnapshotDetails}, entry::{OperationKind, SnapshotDetails},
OplogExt, SnapshotExt, OplogExt, SnapshotExt,
@ -35,6 +36,8 @@ impl VirtualBranchActions {
run_hooks: bool, run_hooks: bool,
) -> Result<git2::Oid> { ) -> Result<git2::Oid> {
let ctx = open_with_verify(project)?; 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 mut guard = project.exclusive_worktree_access();
let snapshot_tree = ctx.project().prepare_snapshot(guard.read_permission()); let snapshot_tree = ctx.project().prepare_snapshot(guard.read_permission());
let result = let result =
@ -57,6 +60,8 @@ impl VirtualBranchActions {
branch_name: &RemoteRefname, branch_name: &RemoteRefname,
) -> Result<bool> { ) -> Result<bool> {
let ctx = CommandContext::open(project)?; 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) branch::is_remote_branch_mergeable(&ctx, branch_name).map_err(Into::into)
} }
@ -64,11 +69,13 @@ impl VirtualBranchActions {
&self, &self,
project: &Project, project: &Project,
) -> Result<(Vec<branch::VirtualBranch>, Vec<gitbutler_diff::FileDiff>)> { ) -> Result<(Vec<branch::VirtualBranch>, Vec<gitbutler_diff::FileDiff>)> {
branch::list_virtual_branches( let ctx = open_with_verify(project)?;
&open_with_verify(project)?,
project.exclusive_worktree_access().write_permission(), assure_open_workspace_mode(&ctx)
) .context("Listing virtual branches requires open workspace mode")?;
.map_err(Into::into)
branch::list_virtual_branches(&ctx, project.exclusive_worktree_access().write_permission())
.map_err(Into::into)
} }
pub fn create_virtual_branch( pub fn create_virtual_branch(
@ -77,6 +84,8 @@ impl VirtualBranchActions {
create: &BranchCreateRequest, create: &BranchCreateRequest,
) -> Result<BranchId> { ) -> Result<BranchId> {
let ctx = open_with_verify(project)?; 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 mut guard = project.exclusive_worktree_access();
let branch_manager = ctx.branch_manager(); let branch_manager = ctx.branch_manager();
let branch_id = 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<()> { pub fn integrate_upstream_commits(&self, project: &Project, branch_id: BranchId) -> Result<()> {
let ctx = open_with_verify(project)?; 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 mut guard = project.exclusive_worktree_access();
let _ = ctx.project().create_snapshot( let _ = ctx.project().create_snapshot(
SnapshotDetails::new(OperationKind::MergeUpstream), SnapshotDetails::new(OperationKind::MergeUpstream),
@ -131,6 +142,8 @@ impl VirtualBranchActions {
pub fn update_base_branch(&self, project: &Project) -> Result<Vec<ReferenceName>> { pub fn update_base_branch(&self, project: &Project) -> Result<Vec<ReferenceName>> {
let ctx = open_with_verify(project)?; 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 mut guard = project.exclusive_worktree_access();
let _ = ctx.project().create_snapshot( let _ = ctx.project().create_snapshot(
SnapshotDetails::new(OperationKind::UpdateWorkspaceBase), SnapshotDetails::new(OperationKind::UpdateWorkspaceBase),
@ -145,6 +158,8 @@ impl VirtualBranchActions {
branch_update: BranchUpdateRequest, branch_update: BranchUpdateRequest,
) -> Result<()> { ) -> Result<()> {
let ctx = open_with_verify(project)?; 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 mut guard = project.exclusive_worktree_access();
let snapshot_tree = ctx.project().prepare_snapshot(guard.read_permission()); let snapshot_tree = ctx.project().prepare_snapshot(guard.read_permission());
let old_branch = ctx let old_branch = ctx
@ -171,6 +186,8 @@ impl VirtualBranchActions {
branch_updates: Vec<BranchUpdateRequest>, branch_updates: Vec<BranchUpdateRequest>,
) -> Result<()> { ) -> Result<()> {
let ctx = open_with_verify(project)?; 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 { for branch_update in branch_updates {
let branch = ctx let branch = ctx
.project() .project()
@ -185,6 +202,8 @@ impl VirtualBranchActions {
pub fn delete_virtual_branch(&self, project: &Project, branch_id: BranchId) -> Result<()> { pub fn delete_virtual_branch(&self, project: &Project, branch_id: BranchId) -> Result<()> {
let ctx = open_with_verify(project)?; 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 branch_manager = ctx.branch_manager();
let mut guard = project.exclusive_worktree_access(); let mut guard = project.exclusive_worktree_access();
branch_manager.delete_branch(branch_id, guard.write_permission()) branch_manager.delete_branch(branch_id, guard.write_permission())
@ -196,6 +215,7 @@ impl VirtualBranchActions {
ownership: &BranchOwnershipClaims, ownership: &BranchOwnershipClaims,
) -> Result<()> { ) -> Result<()> {
let ctx = open_with_verify(project)?; 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 mut guard = project.exclusive_worktree_access();
let _ = ctx.project().create_snapshot( let _ = ctx.project().create_snapshot(
SnapshotDetails::new(OperationKind::DiscardHunk), SnapshotDetails::new(OperationKind::DiscardHunk),
@ -206,6 +226,8 @@ impl VirtualBranchActions {
pub fn reset_files(&self, project: &Project, files: &Vec<String>) -> Result<()> { pub fn reset_files(&self, project: &Project, files: &Vec<String>) -> Result<()> {
let ctx = open_with_verify(project)?; 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 mut guard = project.exclusive_worktree_access();
let _ = ctx.project().create_snapshot( let _ = ctx.project().create_snapshot(
SnapshotDetails::new(OperationKind::DiscardFile), SnapshotDetails::new(OperationKind::DiscardFile),
@ -222,6 +244,8 @@ impl VirtualBranchActions {
ownership: &BranchOwnershipClaims, ownership: &BranchOwnershipClaims,
) -> Result<git2::Oid> { ) -> Result<git2::Oid> {
let ctx = open_with_verify(project)?; 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 mut guard = project.exclusive_worktree_access();
let _ = ctx.project().create_snapshot( let _ = ctx.project().create_snapshot(
SnapshotDetails::new(OperationKind::AmendCommit), SnapshotDetails::new(OperationKind::AmendCommit),
@ -239,6 +263,8 @@ impl VirtualBranchActions {
ownership: &BranchOwnershipClaims, ownership: &BranchOwnershipClaims,
) -> Result<git2::Oid> { ) -> Result<git2::Oid> {
let ctx = open_with_verify(project)?; 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 mut guard = project.exclusive_worktree_access();
let _ = ctx.project().create_snapshot( let _ = ctx.project().create_snapshot(
SnapshotDetails::new(OperationKind::MoveCommitFile), SnapshotDetails::new(OperationKind::MoveCommitFile),
@ -255,6 +281,8 @@ impl VirtualBranchActions {
commit_oid: git2::Oid, commit_oid: git2::Oid,
) -> Result<()> { ) -> Result<()> {
let ctx = open_with_verify(project)?; 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 mut guard = project.exclusive_worktree_access();
let snapshot_tree = ctx.project().prepare_snapshot(guard.read_permission()); let snapshot_tree = ctx.project().prepare_snapshot(guard.read_permission());
let result: Result<()> = let result: Result<()> =
@ -278,6 +306,8 @@ impl VirtualBranchActions {
offset: i32, offset: i32,
) -> Result<()> { ) -> Result<()> {
let ctx = open_with_verify(project)?; 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 mut guard = project.exclusive_worktree_access();
let _ = ctx.project().create_snapshot( let _ = ctx.project().create_snapshot(
SnapshotDetails::new(OperationKind::InsertBlankCommit), SnapshotDetails::new(OperationKind::InsertBlankCommit),
@ -294,6 +324,8 @@ impl VirtualBranchActions {
offset: i32, offset: i32,
) -> Result<()> { ) -> Result<()> {
let ctx = open_with_verify(project)?; 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 mut guard = project.exclusive_worktree_access();
let _ = ctx.project().create_snapshot( let _ = ctx.project().create_snapshot(
SnapshotDetails::new(OperationKind::ReorderCommit), SnapshotDetails::new(OperationKind::ReorderCommit),
@ -309,6 +341,8 @@ impl VirtualBranchActions {
target_commit_oid: git2::Oid, target_commit_oid: git2::Oid,
) -> Result<()> { ) -> Result<()> {
let ctx = open_with_verify(project)?; 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 mut guard = project.exclusive_worktree_access();
let _ = ctx.project().create_snapshot( let _ = ctx.project().create_snapshot(
SnapshotDetails::new(OperationKind::UndoCommit), SnapshotDetails::new(OperationKind::UndoCommit),
@ -323,6 +357,8 @@ impl VirtualBranchActions {
branch_id: BranchId, branch_id: BranchId,
) -> Result<ReferenceName> { ) -> Result<ReferenceName> {
let ctx = open_with_verify(project)?; 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 mut guard = project.exclusive_worktree_access();
let snapshot_tree = ctx.project().prepare_snapshot(guard.read_permission()); let snapshot_tree = ctx.project().prepare_snapshot(guard.read_permission());
let branch_manager = ctx.branch_manager(); let branch_manager = ctx.branch_manager();
@ -348,6 +384,8 @@ impl VirtualBranchActions {
) -> Result<()> { ) -> Result<()> {
let helper = Helper::default(); let helper = Helper::default();
let ctx = open_with_verify(project)?; 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) branch::push(&ctx, branch_id, with_force, &helper, askpass)
} }
@ -372,6 +410,8 @@ impl VirtualBranchActions {
commit_oid: git2::Oid, commit_oid: git2::Oid,
) -> Result<()> { ) -> Result<()> {
let ctx = open_with_verify(project)?; 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 mut guard = project.exclusive_worktree_access();
let _ = ctx.project().create_snapshot( let _ = ctx.project().create_snapshot(
SnapshotDetails::new(OperationKind::SquashCommit), SnapshotDetails::new(OperationKind::SquashCommit),
@ -388,6 +428,8 @@ impl VirtualBranchActions {
message: &str, message: &str,
) -> Result<()> { ) -> Result<()> {
let ctx = open_with_verify(project)?; 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 mut guard = project.exclusive_worktree_access();
let _ = ctx.project().create_snapshot( let _ = ctx.project().create_snapshot(
SnapshotDetails::new(OperationKind::UpdateCommitMessage), SnapshotDetails::new(OperationKind::UpdateCommitMessage),
@ -434,6 +476,7 @@ impl VirtualBranchActions {
commit_oid: git2::Oid, commit_oid: git2::Oid,
) -> Result<()> { ) -> Result<()> {
let ctx = open_with_verify(project)?; 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 mut guard = project.exclusive_worktree_access();
let _ = ctx.project().create_snapshot( let _ = ctx.project().create_snapshot(
SnapshotDetails::new(OperationKind::MoveCommit), SnapshotDetails::new(OperationKind::MoveCommit),
@ -449,6 +492,8 @@ impl VirtualBranchActions {
remote: Option<RemoteRefname>, remote: Option<RemoteRefname>,
) -> Result<BranchId> { ) -> Result<BranchId> {
let ctx = open_with_verify(project)?; 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 branch_manager = ctx.branch_manager();
let mut guard = project.exclusive_worktree_access(); let mut guard = project.exclusive_worktree_access();
branch_manager branch_manager

View File

@ -6,6 +6,7 @@ use gitbutler_branch::{
}; };
use gitbutler_command_context::CommandContext; use gitbutler_command_context::CommandContext;
use gitbutler_diff::{diff_files_into_hunks, GitHunk, Hunk, HunkHash}; 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_project::access::WorktreeWritePermission;
use gitbutler_repo::RepositoryExt; use gitbutler_repo::RepositoryExt;
@ -32,6 +33,8 @@ pub fn get_applied_status(
ctx: &CommandContext, ctx: &CommandContext,
perm: Option<&mut WorktreeWritePermission>, perm: Option<&mut WorktreeWritePermission>,
) -> Result<VirtualBranchesStatus> { ) -> Result<VirtualBranchesStatus> {
assure_open_workspace_mode(ctx)
.context("Getting applied status requires open workspace mode")?;
let integration_commit = get_workspace_head(ctx)?; let integration_commit = get_workspace_head(ctx)?;
let mut virtual_branches = ctx let mut virtual_branches = ctx
.project() .project()

View File

@ -16,6 +16,7 @@ use gitbutler_command_context::CommandContext;
use gitbutler_commit::{commit_ext::CommitExt, commit_headers::HasCommitHeaders}; use gitbutler_commit::{commit_ext::CommitExt, commit_headers::HasCommitHeaders};
use gitbutler_diff::{trees, GitHunk, Hunk}; use gitbutler_diff::{trees, GitHunk, Hunk};
use gitbutler_error::error::{Code, Marker}; use gitbutler_error::error::{Code, Marker};
use gitbutler_operating_modes::assure_open_workspace_mode;
use gitbutler_project::access::WorktreeWritePermission; use gitbutler_project::access::WorktreeWritePermission;
use gitbutler_reference::{normalize_branch_name, Refname, RemoteRefname}; use gitbutler_reference::{normalize_branch_name, Refname, RemoteRefname};
use gitbutler_repo::{ use gitbutler_repo::{
@ -258,6 +259,8 @@ pub fn list_virtual_branches(
// that conditionally write things. // that conditionally write things.
perm: &mut WorktreeWritePermission, perm: &mut WorktreeWritePermission,
) -> Result<(Vec<VirtualBranch>, Vec<gitbutler_diff::FileDiff>)> { ) -> Result<(Vec<VirtualBranch>, Vec<gitbutler_diff::FileDiff>)> {
assure_open_workspace_mode(ctx)
.context("Listing virtual branches requires open workspace mode")?;
let mut branches: Vec<VirtualBranch> = Vec::new(); let mut branches: Vec<VirtualBranch> = Vec::new();
let vb_state = ctx.project().virtual_branches(); let vb_state = ctx.project().virtual_branches();

View File

@ -0,0 +1,12 @@
[package]
name = "gitbutler-operating-modes"
version = "0.0.0"
edition = "2021"
authors = ["GitButler <gitbutler@gitbutler.com>"]
publish = false
[dependencies]
serde = { workspace = true, features = ["std"] }
git2.workspace = true
anyhow.workspace = true
gitbutler-command-context.workspace = true

View File

@ -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<bool> {
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<bool> {
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")
}
}