Merge pull request #4528 from gitbutlerapp/more-accuratly-define-operating-modes

Introduce operating modes
This commit is contained in:
Caleb Owens 2024-07-29 13:47:21 +02:00 committed by GitHub
commit 16400aa4f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 151 additions and 17 deletions

12
Cargo.lock generated
View File

@ -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"
@ -2442,6 +2453,7 @@ dependencies = [
"gitbutler-command-context",
"gitbutler-error",
"gitbutler-notify-debouncer",
"gitbutler-operating-modes",
"gitbutler-oplog",
"gitbutler-project",
"gitbutler-reference",

View File

@ -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

View File

@ -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"

View File

@ -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<git2::Oid> {
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<bool> {
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<branch::VirtualBranch>, Vec<gitbutler_diff::FileDiff>)> {
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<BranchId> {
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<Vec<ReferenceName>> {
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<BranchUpdateRequest>,
) -> 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<String>) -> 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<git2::Oid> {
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<git2::Oid> {
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<ReferenceName> {
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<RemoteRefname>,
) -> Result<BranchId> {
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

View File

@ -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<VirtualBranchesStatus> {
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()

View File

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

View File

@ -24,6 +24,7 @@ gitbutler-project.workspace = true
gitbutler-user.workspace = true
gitbutler-reference.workspace = true
gitbutler-error.workspace = true
gitbutler-operating-modes.workspace = true
backoff = "0.4.0"
notify = { version = "6.0.1" }

View File

@ -4,6 +4,7 @@ use anyhow::{Context, Result};
use gitbutler_branch_actions::{VirtualBranchActions, VirtualBranches};
use gitbutler_command_context::CommandContext;
use gitbutler_error::error::Marker;
use gitbutler_operating_modes::in_open_workspace_mode;
use gitbutler_oplog::{
entry::{OperationKind, SnapshotDetails},
OplogExt,
@ -79,8 +80,22 @@ impl Handler {
(self.send_event)(event).context("failed to send event")
}
fn open_command_context(&self, project_id: ProjectId) -> Result<CommandContext> {
let project = self
.projects
.get(project_id)
.context("failed to get project")?;
CommandContext::open(&project).context("Failed to create a command context")
}
#[instrument(skip(self, project_id))]
fn calculate_virtual_branches(&self, project_id: ProjectId) -> Result<()> {
let ctx = self.open_command_context(project_id)?;
// Skip if we're not on the open workspace mode
if !in_open_workspace_mode(&ctx)? {
return Ok(());
}
let project = self
.projects
.get(project_id)
@ -107,6 +122,12 @@ impl Handler {
#[instrument(skip(self, paths, project_id), fields(paths = paths.len()))]
fn recalculate_everything(&self, paths: Vec<PathBuf>, project_id: ProjectId) -> Result<()> {
let ctx = self.open_command_context(project_id)?;
// Skip if we're not on the open workspace mode
if !in_open_workspace_mode(&ctx)? {
return Ok(());
}
self.maybe_create_snapshot(project_id).ok();
self.calculate_virtual_branches(project_id)?;
Ok(())
@ -135,10 +156,6 @@ impl Handler {
.projects
.get(project_id)
.context("failed to get project")?;
let open_projects_repository = || {
CommandContext::open(&project.clone())
.context("failed to open project repository for project")
};
for path in paths {
let Some(file_name) = path.to_str() else {
@ -152,16 +169,17 @@ impl Handler {
self.emit_app_event(Change::GitActivity(project.id))?;
}
"HEAD" => {
let ctx = open_projects_repository()?;
let head_ref = ctx.repository().head().context("failed to get head")?;
let head_ref_name = head_ref.name().context("failed to get head name")?;
if head_ref_name != "refs/heads/gitbutler/integration" {
let ctx = CommandContext::open(&project)
.context("Failed to create a command context")?;
if in_open_workspace_mode(&ctx)? {
let mut integration_reference = ctx.repository().find_reference(
&Refname::from(LocalRefname::new("gitbutler/integration", None))
.to_string(),
)?;
integration_reference.delete()?;
}
let head_ref = ctx.repository().head().context("failed to get head")?;
if let Some(head) = head_ref.name() {
self.emit_app_event(Change::GitHead {
project_id,