@@ -103,11 +103,13 @@ impl<'a> ReferenceAnchor<'a> {
103
103
104
104
pub ( super ) mod function {
105
105
#![ allow( clippy:: indexing_slicing) ]
106
+
106
107
use crate :: branch:: { ReferenceAnchor , ReferencePosition } ;
107
108
use anyhow:: { Context , bail} ;
108
109
use but_core:: ref_metadata:: { StackId , WorkspaceStack , WorkspaceStackBranch } ;
109
110
use but_core:: { RefMetadata , ref_metadata} ;
110
111
use gix:: refs:: transaction:: PreviousValue ;
112
+ use std:: borrow:: Cow ;
111
113
use std:: ops:: DerefMut ;
112
114
113
115
/// Create a new reference named `ref_name` to point at a commit relative to `anchor`.
@@ -132,7 +134,17 @@ pub(super) mod function {
132
134
let anchor = anchor. into ( ) ;
133
135
134
136
let ws_base = workspace. lower_bound ;
135
- let ( check_if_commit_in_workspace, ref_target, insert) : ( _ , _ , Option < Instruction < ' _ > > ) = {
137
+ // Note that we will never create metadata for a workspace!
138
+ let mut existing_ws_meta = workspace
139
+ . ref_name ( )
140
+ . and_then ( |ws_ref| meta. workspace_opt ( ws_ref) . transpose ( ) )
141
+ . transpose ( ) ?;
142
+
143
+ let ( check_if_id_in_workspace, ref_target_id, instruction) : (
144
+ _ ,
145
+ _ ,
146
+ Option < Instruction < ' _ > > ,
147
+ ) = {
136
148
match anchor {
137
149
None => {
138
150
// The new ref exists already in the workspace, do nothing.
@@ -145,40 +157,47 @@ pub(super) mod function {
145
157
let base = ws_base. with_context ( || {
146
158
format ! (
147
159
"workspace at {} is missing a base" ,
148
- workspace. ref_name( ) . map_or_else(
149
- || "<anonymous>" . to_string( ) ,
150
- |rn| rn. as_bstr( ) . to_string( )
151
- )
160
+ workspace. ref_name_display( )
152
161
)
153
162
} ) ?;
154
163
(
155
164
// do not validate, as the base is expectedly outside of workspace
156
165
false ,
157
166
base,
158
- Some ( None ) ,
167
+ Some ( Instruction :: Independent ) ,
159
168
)
160
169
}
161
170
Some ( ReferenceAnchor :: AtCommit {
162
171
commit_id,
163
172
position,
164
173
} ) => {
165
174
let mut validate_id = true ;
166
- let id = position. resolve_commit (
167
- workspace
168
- . lookup_commit (
169
- workspace. try_find_owner_indexes_by_commit_id ( commit_id) ?,
170
- )
171
- . into ( ) ,
172
- ws_base,
173
- ) ?;
174
- if Some ( id) == ws_base {
175
+ let indexes = workspace. try_find_owner_indexes_by_commit_id ( commit_id) ?;
176
+ let ref_target_id = position
177
+ . resolve_commit ( workspace. lookup_commit ( indexes) . into ( ) , ws_base) ?;
178
+ let id_out_of_workspace = Some ( ref_target_id) == ws_base;
179
+ if id_out_of_workspace {
175
180
validate_id = false
176
181
}
177
- ( validate_id, id, None )
182
+
183
+ let instruction = existing_ws_meta
184
+ . as_ref ( )
185
+ . filter ( |_| !id_out_of_workspace)
186
+ . map ( |_| {
187
+ instruction_by_named_anchor_for_commit (
188
+ workspace,
189
+ commit_id,
190
+ ref_target_id,
191
+ position,
192
+ )
193
+ } )
194
+ . transpose ( ) ?;
195
+
196
+ ( validate_id, ref_target_id, instruction)
178
197
}
179
198
Some ( ReferenceAnchor :: AtSegment { ref_name, position } ) => {
180
199
let mut validate_id = true ;
181
- let id = if workspace. has_metadata ( ) {
200
+ let ref_target_id = if workspace. has_metadata ( ) {
182
201
let ( stack_idx, seg_idx) =
183
202
workspace. try_find_segment_owner_indexes_by_refname ( ref_name) ?;
184
203
let segment = & workspace. stacks [ stack_idx] . segments [ seg_idx] ;
@@ -207,29 +226,28 @@ pub(super) mod function {
207
226
"BUG: empty segments aren't possible without workspace metadata" ,
208
227
) ?. into ( ) , ws_base) ?
209
228
} ;
210
- ( validate_id, id, Some ( Some ( ( ref_name, position) ) ) )
229
+ (
230
+ validate_id,
231
+ ref_target_id,
232
+ Some ( Instruction :: Dependent {
233
+ ref_name : Cow :: Borrowed ( ref_name) ,
234
+ position,
235
+ } ) ,
236
+ )
211
237
}
212
238
}
213
239
} ;
214
240
215
- let mut existing_ws_meta = workspace
216
- . ref_name ( )
217
- . and_then ( |ws_ref| meta. workspace_opt ( ws_ref) . transpose ( ) )
241
+ let updated_ws_meta = existing_ws_meta
242
+ . take ( )
243
+ . zip ( instruction)
244
+ . map ( |( mut existing, instruction) | {
245
+ update_workspace_metadata ( & mut existing, ref_name, instruction) . map ( |( ) | existing)
246
+ } )
218
247
. transpose ( ) ?;
219
- let updated_ws_meta = if let Some ( instruction) = insert {
220
- if let Some ( mut existing) = existing_ws_meta. take ( ) {
221
- update_workspace_metadata ( & mut existing, ref_name, instruction) ?;
222
- Some ( existing)
223
- } else {
224
- None
225
- }
226
- } else {
227
- None
228
- } ;
229
-
230
248
// Assure this commit is in the workspace as well.
231
- if check_if_commit_in_workspace {
232
- workspace. try_find_owner_indexes_by_commit_id ( ref_target ) ?;
249
+ if check_if_id_in_workspace {
250
+ workspace. try_find_owner_indexes_by_commit_id ( ref_target_id ) ?;
233
251
}
234
252
235
253
let graph_with_new_ref = {
@@ -243,7 +261,7 @@ pub(super) mod function {
243
261
but_graph:: init:: Overlay :: default ( )
244
262
. with_references_if_new ( Some ( gix:: refs:: Reference {
245
263
name : ref_name. into ( ) ,
246
- target : gix:: refs:: Target :: Object ( ref_target ) ,
264
+ target : gix:: refs:: Target :: Object ( ref_target_id ) ,
247
265
peeled : None ,
248
266
} ) )
249
267
. with_branch_metadata_override ( Some ( (
@@ -265,24 +283,23 @@ pub(super) mod function {
265
283
if !has_new_ref_as_standalone_segment {
266
284
// TODO: this should probably be easier to understand for the UI, with error codes maybe?
267
285
bail ! (
268
- "Reference '{}' cannot be created as segment at {ref_target }" ,
286
+ "Reference '{}' cannot be created as segment at {ref_target_id }" ,
269
287
ref_name. shorten( )
270
288
)
271
289
}
272
290
273
291
// Actually apply the changes
274
292
repo. reference (
275
293
ref_name,
276
- ref_target ,
277
- PreviousValue :: ExistingMustMatch ( gix:: refs:: Target :: Object ( ref_target ) ) ,
294
+ ref_target_id ,
295
+ PreviousValue :: ExistingMustMatch ( gix:: refs:: Target :: Object ( ref_target_id ) ) ,
278
296
"Dependent branch by GitButler" ,
279
297
) ?;
280
298
// Important to first update the workspace so we have the correct stack setup.
281
299
if let Some ( ws_meta) = updated_ws_meta {
282
- // TODO: overwrite stored information with reality in new graph.
283
300
meta. set_workspace ( & ws_meta) ?;
284
- } else if let Some ( mut existing) = existing_ws_meta {
285
- updated_workspace . update_metadata ( & mut * existing ) ;
301
+ } else if let Some ( existing) = existing_ws_meta {
302
+ // TODO: overwrite stored information with reality in new graph.
286
303
meta. set_workspace ( & existing) ?;
287
304
}
288
305
@@ -315,23 +332,25 @@ pub(super) mod function {
315
332
new_ref : & gix:: refs:: FullNameRef ,
316
333
instruction : Instruction < ' _ > ,
317
334
) -> anyhow:: Result < ( ) > {
335
+ if ws_meta. find_branch ( new_ref) . is_some ( ) {
336
+ return Ok ( ( ) ) ;
337
+ }
318
338
match instruction {
319
339
// create new
320
- None => {
321
- if ws_meta. find_branch ( new_ref) . is_none ( ) {
322
- ws_meta. stacks . push ( WorkspaceStack {
323
- id : StackId :: generate ( ) ,
324
- branches : vec ! [ WorkspaceStackBranch {
325
- ref_name: new_ref. to_owned( ) ,
326
- archived: false ,
327
- } ] ,
328
- } )
329
- }
330
- }
340
+ Instruction :: Independent => ws_meta. stacks . push ( WorkspaceStack {
341
+ id : StackId :: generate ( ) ,
342
+ branches : vec ! [ WorkspaceStackBranch {
343
+ ref_name: new_ref. to_owned( ) ,
344
+ archived: false ,
345
+ } ] ,
346
+ } ) ,
331
347
// insert dependent branch at anchor
332
- Some ( ( anchor_ref, position) ) => {
348
+ Instruction :: Dependent {
349
+ ref_name : anchor_ref,
350
+ position,
351
+ } => {
333
352
let ( stack_idx, branch_idx) = ws_meta
334
- . find_owner_indexes_by_name ( anchor_ref)
353
+ . find_owner_indexes_by_name ( anchor_ref. as_ref ( ) )
335
354
. with_context ( || {
336
355
format ! (
337
356
"Couldn't find anchor '{}' in workspace metadata" ,
@@ -354,5 +373,82 @@ pub(super) mod function {
354
373
Ok ( ( ) )
355
374
}
356
375
357
- type Instruction < ' a > = Option < ( & ' a gix:: refs:: FullNameRef , ReferencePosition ) > ;
376
+ /// Create the instruction that would be needed to insert the new ref-name into workspace data
377
+ /// so that it represents the `position` of `anchor_id` and `target_id` (being (`anchor_id` offset by `position`).
378
+ /// `position` indicates where, in relation to `anchor_id`, the ref name should be inserted.
379
+ /// The first name that is also in `ws_meta` will be used.
380
+ fn instruction_by_named_anchor_for_commit (
381
+ ws : & but_graph:: projection:: Workspace < ' _ > ,
382
+ anchor_id : gix:: ObjectId ,
383
+ target_id : gix:: ObjectId ,
384
+ position : ReferencePosition ,
385
+ ) -> anyhow:: Result < Instruction < ' static > > {
386
+ use ReferencePosition :: * ;
387
+ let ( anchor_stack_idx, anchor_seg_idx, _anchor_commit_idx) = ws
388
+ . find_owner_indexes_by_commit_id ( anchor_id)
389
+ . with_context ( || {
390
+ format ! (
391
+ "No segment in workspace at '{}' that holds {anchor_id}" ,
392
+ ws. ref_name_display( )
393
+ )
394
+ } ) ?;
395
+ let ( target_stack_idx, target_seg_idx, _target_commit_idx) = ws
396
+ . find_owner_indexes_by_commit_id ( target_id)
397
+ . with_context ( || {
398
+ format ! (
399
+ "No segment in workspace at '{}' that holds {target_id}" ,
400
+ ws. ref_name_display( )
401
+ )
402
+ } ) ?;
403
+ if anchor_stack_idx != target_stack_idx {
404
+ bail ! (
405
+ "BUG: Anchor id {anchor_id} and {target_id} ended up in different stacks - how can that be?"
406
+ ) ;
407
+ }
408
+
409
+ // TODO: this is missing a lot of tests for the empty-segment complexity, but also the
410
+ let stack = & ws. stacks [ anchor_stack_idx] ;
411
+ if anchor_seg_idx == target_seg_idx {
412
+ // Find first non-empty segment in this stack upward and downward.
413
+ ( 0 ..anchor_seg_idx + 1 ) . rev ( )
414
+ . find_map ( |seg_idx| {
415
+ stack. segments [ seg_idx]
416
+ . ref_name
417
+ . as_ref ( )
418
+ . map ( |rn| ( rn. as_ref ( ) , Below ) )
419
+ } )
420
+ . or_else ( || {
421
+ ( anchor_seg_idx + 1 ..stack. segments . len ( ) ) . find_map ( |seg_idx| {
422
+ stack. segments [ seg_idx]
423
+ . ref_name
424
+ . as_ref ( )
425
+ . map ( |rn| ( rn. as_ref ( ) , Above ) )
426
+ } )
427
+ } )
428
+ . map ( |( anchor_ref, position) | Instruction :: Dependent {
429
+ ref_name : Cow :: Owned ( anchor_ref. to_owned ( ) ) ,
430
+ position,
431
+ } )
432
+ } else {
433
+ if anchor_seg_idx + 1 != target_seg_idx {
434
+ bail ! (
435
+ "BUG: The anchor commit {anchor_id} must be above {target_id}, but the segments where {anchor_seg_idx} and {target_seg_idx}"
436
+ ) ;
437
+ }
438
+ todo ! ( "sort out start position and positioning" )
439
+ } . with_context ( || {
440
+ format ! (
441
+ "Didn't find a named segment in workspace at '{}' to indicate where to insert {anchor_id} {position:?}" ,
442
+ ws. ref_name_display( )
443
+ )
444
+ } )
445
+ }
446
+
447
+ enum Instruction < ' a > {
448
+ Independent ,
449
+ Dependent {
450
+ ref_name : Cow < ' a , gix:: refs:: FullNameRef > ,
451
+ position : ReferencePosition ,
452
+ } ,
453
+ }
358
454
}
0 commit comments