Skip to content

Commit fae9e59

Browse files
authored
Merge pull request #13132 from gitbutlerapp/multi-process-safety
Multi-process safety
2 parents a0eeafc + bfb44ea commit fae9e59

43 files changed

Lines changed: 1547 additions & 393 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

crates/but-api/src/branch.rs

Lines changed: 114 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
use std::collections::BTreeMap;
22

33
use but_api_macros::but_api;
4-
use but_core::{RefMetadata, ui::TreeChanges, worktree::checkout::UncommitedWorktreeChanges};
4+
use but_core::{
5+
RefMetadata, sync::RepoExclusive, ui::TreeChanges,
6+
worktree::checkout::UncommitedWorktreeChanges,
7+
};
58
use but_ctx::Context;
69
use but_oplog::legacy::{OperationKind, SnapshotDetails, Trailer};
710
use but_rebase::graph_rebase::Editor;
@@ -86,13 +89,33 @@ pub mod json {
8689
}
8790
}
8891

89-
/// Apply `existing_branch` to the workspace in the repository that `ctx` refers to, or create the workspace with default name.
92+
/// Applies a branch using the behavior described by [`apply_only_with_perm()`].
93+
///
94+
/// This acquires exclusive worktree access from `ctx` before applying
95+
/// `existing_branch`.
9096
pub fn apply_only(
9197
ctx: &mut but_ctx::Context,
9298
existing_branch: &gix::refs::FullNameRef,
99+
) -> anyhow::Result<but_workspace::branch::apply::Outcome<'static>> {
100+
let mut guard = ctx.exclusive_worktree_access();
101+
apply_only_with_perm(ctx, existing_branch, guard.write_permission())
102+
}
103+
104+
/// Applies `existing_branch` to the current workspace under caller-held
105+
/// exclusive repository access.
106+
///
107+
/// It applies the branch with the default workspace-apply options, updates the
108+
/// in-memory workspace stored in `ctx` to the returned workspace state, and
109+
/// returns the apply outcome. This variant does not create an oplog
110+
/// entry. For lower-level implementation details, see
111+
/// [`but_workspace::branch::apply()`].
112+
pub fn apply_only_with_perm(
113+
ctx: &mut but_ctx::Context,
114+
existing_branch: &gix::refs::FullNameRef,
115+
perm: &mut RepoExclusive,
93116
) -> anyhow::Result<but_workspace::branch::apply::Outcome<'static>> {
94117
let mut meta = ctx.meta()?;
95-
let (_guard, repo, mut ws, _) = ctx.workspace_mut_and_db()?;
118+
let (repo, mut ws, _) = ctx.workspace_mut_and_db_with_perm(perm)?;
96119
let out = but_workspace::branch::apply(
97120
existing_branch,
98121
&ws,
@@ -115,74 +138,116 @@ pub fn apply_only(
115138
Ok(out)
116139
}
117140

118-
/// Just like [apply_only()], but will create an oplog entry as well on success.
141+
/// Applies `existing_branch` using the behavior described by
142+
/// [`apply_with_perm()`].
143+
///
144+
/// This acquires exclusive worktree access from `ctx`, applies
145+
/// `existing_branch`, and records an oplog snapshot on success.
119146
#[but_api(napi, json::ApplyOutcome)]
120147
#[instrument(err(Debug))]
121148
pub fn apply(
122149
ctx: &mut but_ctx::Context,
123150
existing_branch: &gix::refs::FullNameRef,
151+
) -> anyhow::Result<but_workspace::branch::apply::Outcome<'static>> {
152+
let mut guard = ctx.exclusive_worktree_access();
153+
apply_with_perm(ctx, existing_branch, guard.write_permission())
154+
}
155+
156+
/// Apply `existing_branch` to the workspace under caller-held exclusive
157+
/// repository access and record an oplog snapshot on success.
158+
///
159+
/// It behaves like [`apply_only_with_perm()`], but first prepares a best-effort
160+
/// oplog snapshot for a create-branch operation, annotated with the branch
161+
/// name, and commits that snapshot only if the apply succeeds. For lower-level
162+
/// implementation details, see [`but_workspace::branch::apply()`].
163+
pub fn apply_with_perm(
164+
ctx: &mut but_ctx::Context,
165+
existing_branch: &gix::refs::FullNameRef,
166+
perm: &mut RepoExclusive,
124167
) -> anyhow::Result<but_workspace::branch::apply::Outcome<'static>> {
125168
// NOTE: since this is optional by nature, the same would be true if snapshotting/undo would be disabled via `ctx` app settings, for instance.
126-
let maybe_oplog_entry = but_oplog::UnmaterializedOplogSnapshot::from_details(
169+
let maybe_oplog_entry = but_oplog::UnmaterializedOplogSnapshot::from_details_with_perm(
127170
ctx,
128171
SnapshotDetails::new(OperationKind::CreateBranch).with_trailers(vec![Trailer {
129172
key: "name".into(),
130173
value: existing_branch.to_string(),
131174
}]),
175+
perm.read_permission(),
132176
)
133177
.ok();
134178

135-
let res = apply_only(ctx, existing_branch);
179+
let res = apply_only_with_perm(ctx, existing_branch, perm);
136180
if let Some(snapshot) = maybe_oplog_entry.filter(|_| res.is_ok()) {
137-
let mut guard = ctx.exclusive_worktree_access();
138-
snapshot.commit(ctx, guard.write_permission()).ok();
181+
snapshot.commit(ctx, perm).ok();
139182
}
140183
res
141184
}
142185

143-
/// Gets the changes for a given branch.
144-
#[but_api(napi, TreeChanges)]
186+
/// Computes the worktree-visible diff for `branch` in the current workspace.
187+
///
188+
/// `branch` is resolved by name in the repository referenced by `ctx`, and the
189+
/// diff is computed against the current workspace state. For lower-level
190+
/// implementation details, see [`but_workspace::ui::diff::changes_in_branch()`].
191+
#[but_api(napi)]
145192
#[instrument(err(Debug))]
146193
pub fn branch_diff(ctx: &Context, branch: String) -> anyhow::Result<TreeChanges> {
147194
let (_guard, repo, ws, _) = ctx.workspace_and_db()?;
148195
let branch = repo.find_reference(&branch)?;
149196
but_workspace::ui::diff::changes_in_branch(&repo, &ws, branch.name())
150197
}
151198

152-
/// Move a branch on top of another
199+
/// Moves a branch using the behavior described by [`move_branch_with_perm()`].
200+
///
201+
/// This acquires exclusive worktree access from `ctx`, moves `subject_branch`
202+
/// on top of `target_branch`, and records an oplog snapshot on success.
153203
#[but_api(napi, json::UIMoveBranchResult)]
154204
#[instrument(err(Debug))]
155205
pub fn move_branch(
156206
ctx: &mut but_ctx::Context,
157207
subject_branch: &gix::refs::FullNameRef,
158208
target_branch: &gix::refs::FullNameRef,
159209
) -> anyhow::Result<MoveBranchResult> {
160-
let maybe_oplog_entry = but_oplog::UnmaterializedOplogSnapshot::from_details(
210+
let mut guard = ctx.exclusive_worktree_access();
211+
move_branch_with_perm(ctx, subject_branch, target_branch, guard.write_permission())
212+
}
213+
214+
/// Move `subject_branch` on top of `target_branch` under caller-held
215+
/// exclusive repository access and record an oplog snapshot on success.
216+
///
217+
/// It prepares a best-effort move-branch oplog snapshot, rebases the subject
218+
/// branch onto the target branch, updates workspace metadata, and commits the
219+
/// snapshot only if the move succeeds. The returned [`MoveBranchResult`]
220+
/// contains the commit-id mapping produced by materializing the rebase. For
221+
/// lower-level implementation details, see
222+
/// [`but_workspace::branch::move_branch()`].
223+
pub fn move_branch_with_perm(
224+
ctx: &mut but_ctx::Context,
225+
subject_branch: &gix::refs::FullNameRef,
226+
target_branch: &gix::refs::FullNameRef,
227+
perm: &mut RepoExclusive,
228+
) -> anyhow::Result<MoveBranchResult> {
229+
let maybe_oplog_entry = but_oplog::UnmaterializedOplogSnapshot::from_details_with_perm(
161230
ctx,
162231
SnapshotDetails::new(OperationKind::MoveBranch),
232+
perm.read_permission(),
163233
)
164234
.ok();
165235

166-
let move_branch_result = move_branch_impl(ctx, subject_branch, target_branch);
236+
let move_branch_result = move_branch_impl_with_perm(ctx, subject_branch, target_branch, perm);
167237
if let Some(snapshot) = maybe_oplog_entry.filter(|_| move_branch_result.is_ok()) {
168-
let mut guard = ctx.exclusive_worktree_access();
169-
snapshot.commit(ctx, guard.write_permission()).ok();
238+
snapshot.commit(ctx, perm).ok();
170239
}
171240
move_branch_result
172241
}
173242

174-
/// Move the branch, updating the workspace and the metadata.
175-
///
176-
/// `subject_branch` - The branch to move.
177-
///
178-
/// `target_branch` - The branch to move `subject_branch` on top of.
179-
fn move_branch_impl(
243+
fn move_branch_impl_with_perm(
180244
ctx: &mut but_ctx::Context,
181245
subject_branch: &gix::refs::FullNameRef,
182246
target_branch: &gix::refs::FullNameRef,
247+
perm: &mut RepoExclusive,
183248
) -> anyhow::Result<MoveBranchResult> {
184249
let mut meta = ctx.meta()?;
185-
let (_guard, repo, mut ws, _, _cache) = ctx.workspace_mut_and_db_and_cache()?;
250+
let (repo, mut ws, _, _cache) = ctx.workspace_mut_and_db_and_cache_with_perm(perm)?;
186251
let editor = Editor::create(&mut ws, &mut meta, &repo)?;
187252
let but_workspace::branch::move_branch::Outcome { rebase, ws_meta } =
188253
but_workspace::branch::move_branch(editor, subject_branch, target_branch)?;
@@ -199,36 +264,55 @@ fn move_branch_impl(
199264
})
200265
}
201266

202-
/// Take a branch out of a stack
267+
/// Tears off a branch using the behavior described by [`tear_off_branch_with_perm()`].
203268
///
204-
/// `subject_branch` - The branch to take out of its stack, and create a new one out of.
269+
/// This acquires exclusive worktree access from `ctx`, tears `subject_branch`
270+
/// out of its current stack, and records an oplog snapshot on success.
205271
#[but_api(napi, json::UIMoveBranchResult)]
206272
#[instrument(err(Debug))]
207273
pub fn tear_off_branch(
208274
ctx: &mut but_ctx::Context,
209275
subject_branch: &gix::refs::FullNameRef,
210276
) -> anyhow::Result<MoveBranchResult> {
211-
let maybe_oplog_entry = but_oplog::UnmaterializedOplogSnapshot::from_details(
277+
let mut guard = ctx.exclusive_worktree_access();
278+
tear_off_branch_with_perm(ctx, subject_branch, guard.write_permission())
279+
}
280+
281+
/// Removes `subject_branch` from its current stack, creating a new stack for
282+
/// it, under caller-held exclusive repository access.
283+
///
284+
/// It prepares a best-effort tear-off oplog snapshot, performs the tear-off
285+
/// rebase and workspace metadata update under `perm`, and commits the snapshot
286+
/// only if the mutation succeeds. The returned [`MoveBranchResult`] contains
287+
/// the commit-id mapping produced by materializing the rebase. For lower-level
288+
/// implementation details, see [`but_workspace::branch::tear_off_branch()`].
289+
pub fn tear_off_branch_with_perm(
290+
ctx: &mut but_ctx::Context,
291+
subject_branch: &gix::refs::FullNameRef,
292+
perm: &mut RepoExclusive,
293+
) -> anyhow::Result<MoveBranchResult> {
294+
let maybe_oplog_entry = but_oplog::UnmaterializedOplogSnapshot::from_details_with_perm(
212295
ctx,
213296
SnapshotDetails::new(OperationKind::TearOffBranch),
297+
perm.read_permission(),
214298
)
215299
.ok();
216300

217-
let move_branch_result = tear_off_branch_impl(ctx, subject_branch);
301+
let move_branch_result = tear_off_branch_impl_with_perm(ctx, subject_branch, perm);
218302
if let Some(snapshot) = maybe_oplog_entry.filter(|_| move_branch_result.is_ok()) {
219-
let mut guard = ctx.exclusive_worktree_access();
220-
snapshot.commit(ctx, guard.write_permission()).ok();
303+
snapshot.commit(ctx, perm).ok();
221304
}
222305
move_branch_result
223306
}
224307

225308
/// Move the branch, updating the workspace and the metadata.
226-
fn tear_off_branch_impl(
309+
fn tear_off_branch_impl_with_perm(
227310
ctx: &mut but_ctx::Context,
228311
subject_branch: &gix::refs::FullNameRef,
312+
perm: &mut RepoExclusive,
229313
) -> anyhow::Result<MoveBranchResult> {
230314
let mut meta = ctx.meta()?;
231-
let (_guard, repo, mut ws, _, _cache) = ctx.workspace_mut_and_db_and_cache()?;
315+
let (repo, mut ws, _, _cache) = ctx.workspace_mut_and_db_and_cache_with_perm(perm)?;
232316
let editor = Editor::create(&mut ws, &mut meta, &repo)?;
233317
let but_workspace::branch::move_branch::Outcome { rebase, ws_meta } =
234318
but_workspace::branch::tear_off_branch(editor, subject_branch, None)?;

crates/but-api/src/commit/amend.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ use tracing::instrument;
88

99
use super::types::CommitCreateResult;
1010

11-
/// Amends an existing commit with selected changes.
11+
/// Amends the commit at `commit_id` with `changes`.
12+
///
13+
/// See [`but_workspace::commit::commit_amend()`] for lower-level implementation
14+
/// details.
1215
#[but_api(crate::commit::json::UICommitCreateResult)]
1316
#[instrument(err(Debug))]
1417
pub fn commit_amend_only(
@@ -27,7 +30,6 @@ pub fn commit_amend_only(
2730
)
2831
}
2932

30-
/// Amends an existing commit with selected changes.
3133
pub(crate) fn commit_amend_only_impl(
3234
ctx: &mut but_ctx::Context,
3335
commit_id: gix::ObjectId,
@@ -62,7 +64,12 @@ pub(crate) fn commit_amend_only_impl(
6264
})
6365
}
6466

65-
/// Amends an existing commit with selected changes, with oplog support.
67+
/// Amend the commit at `commit_id` with `changes` and record an oplog snapshot on success.
68+
///
69+
/// This performs the rewrite under exclusive worktree access and creates a
70+
/// best-effort `AmendCommit` oplog entry if the operation succeeds. For
71+
/// lower-level implementation details, see
72+
/// [`but_workspace::commit::commit_amend()`].
6673
#[but_api(napi, crate::commit::json::UICommitCreateResult)]
6774
#[instrument(err(Debug))]
6875
pub fn commit_amend(

crates/but-api/src/commit/create.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ use tracing::instrument;
1111

1212
use super::types::CommitCreateResult;
1313

14-
/// Creates and inserts a commit relative to either a commit or a reference.
14+
/// Creates a commit from `changes` with `message`, inserted on `side` of
15+
/// `relative_to`.
16+
///
17+
/// This acquires exclusive worktree access from `ctx` before creating the
18+
/// commit. For lower-level implementation details, see
19+
/// [`but_workspace::commit::commit_create()`].
1520
#[but_api(crate::commit::json::UICommitCreateResult)]
1621
#[instrument(err(Debug))]
1722
pub fn commit_create_only(
@@ -78,7 +83,14 @@ pub(crate) fn commit_create_only_impl(
7883
})
7984
}
8085

81-
/// Creates and inserts a commit relative to either a commit or a reference, with oplog support.
86+
/// Insert a new commit built from `changes` and record an oplog snapshot on
87+
/// success.
88+
///
89+
/// `relative_to` and `side` choose where the commit is inserted. `message` is
90+
/// the entire commit message text, not just the title. On success, this commits
91+
/// a best-effort `CreateCommit` oplog snapshot using the same lock. For
92+
/// lower-level implementation details, see
93+
/// [`but_workspace::commit::commit_create()`].
8294
#[but_api(napi, crate::commit::json::UICommitCreateResult)]
8395
#[instrument(skip_all, fields(relative_to, side, message), err(Debug))]
8496
pub fn commit_create(

0 commit comments

Comments
 (0)