11use std:: collections:: BTreeMap ;
22
33use 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+ } ;
58use but_ctx:: Context ;
69use but_oplog:: legacy:: { OperationKind , SnapshotDetails , Trailer } ;
710use 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`.
9096pub 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 ) ) ]
121148pub 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 ) ) ]
146193pub 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 ) ) ]
155205pub 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 ) ) ]
207273pub 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 ) ?;
0 commit comments