@@ -5,7 +5,7 @@ use std::collections::BTreeSet;
5
5
/// A way to determine what should be included in the snapshot when calling [create_tree()](function::create_tree).
6
6
#[ derive( Debug , Clone ) ]
7
7
pub struct State {
8
- /// The result of a previous worktree changes call.
8
+ /// The result of a previous worktree changes call, but [the one **without** renames](but_core::diff::worktree_changes_no_renames()) .
9
9
///
10
10
/// It contains detailed information about the complete set of possible changes to become part of the worktree.
11
11
pub changes : but_core:: WorktreeChanges ,
@@ -59,9 +59,13 @@ pub fn no_workspace_and_meta() -> Option<(
59
59
pub ( super ) mod function {
60
60
use super :: { Outcome , State } ;
61
61
use crate :: { DiffSpec , commit_engine} ;
62
- use anyhow:: bail;
63
- use but_core:: RefMetadata ;
62
+ use anyhow:: { Context , bail} ;
63
+ use bstr:: { BString , ByteSlice } ;
64
+ use but_core:: { ChangeState , RefMetadata } ;
65
+ use gix:: diff:: index:: Change ;
64
66
use gix:: object:: tree:: EntryKind ;
67
+ use gix:: status:: plumbing:: index_as_worktree:: EntryStatus ;
68
+ use std:: collections:: BTreeSet ;
65
69
use tracing:: instrument;
66
70
67
71
/// Create a tree that represents the snapshot for the given `selection`, whereas the basis for these changes
@@ -94,7 +98,10 @@ pub(super) mod function {
94
98
/// even though it might be that the respective objects aren't written to disk yet.
95
99
/// - Note that this tree may contain files with conflict markers as it will pick up the conflicting state visible on disk.
96
100
/// * `index`
97
- /// - A representation of the non-conflicting portions of the index, without its meta-data.
101
+ /// - A representation of the non-conflicting and changed portions of the index, without its meta-data.
102
+ /// - may be empty if only conflicts exist.
103
+ /// * `index-conflicts`
104
+ /// - `<entry-path>/[1,2,3]` - the blobs at their respective stages.
98
105
#[ instrument( skip( changes, _workspace_and_meta) , err( Debug ) ) ]
99
106
pub fn create_tree (
100
107
head_tree_id : gix:: Id < ' _ > ,
@@ -105,13 +112,47 @@ pub(super) mod function {
105
112
} : State ,
106
113
_workspace_and_meta : Option < ( & but_graph:: projection:: Workspace , & impl RefMetadata ) > ,
107
114
) -> anyhow:: Result < Outcome > {
115
+ // Assure this is a tree early.
116
+ let head_tree = head_tree_id. object ( ) ?. into_tree ( ) ;
108
117
let repo = head_tree_id. repo ;
109
118
let mut changes_to_apply: Vec < _ > = changes
110
119
. changes
111
120
. iter ( )
112
121
. filter ( |c| selection. contains ( & c. path ) )
113
122
. map ( |c| Ok ( DiffSpec :: from ( c) ) )
114
123
. collect ( ) ;
124
+ changes_to_apply. extend (
125
+ changes
126
+ . ignored_changes
127
+ . iter ( )
128
+ . filter_map ( |c| match & c. status_item {
129
+ Some ( gix:: status:: Item :: IndexWorktree (
130
+ gix:: status:: index_worktree:: Item :: Modification {
131
+ status : EntryStatus :: Conflict { .. } ,
132
+ rela_path,
133
+ ..
134
+ } ,
135
+ ) ) => Some ( rela_path) ,
136
+ _ => None ,
137
+ } )
138
+ . filter ( |rela_path| selection. contains ( rela_path. as_bstr ( ) ) )
139
+ . map ( |rela_path| {
140
+ // Create a pretend-addition to pick up conflicted paths as well.
141
+ Ok ( DiffSpec :: from ( but_core:: TreeChange {
142
+ path : rela_path. to_owned ( ) ,
143
+ status : but_core:: TreeStatus :: Addition {
144
+ state : ChangeState {
145
+ id : repo. object_hash ( ) . null ( ) ,
146
+ // This field isn't relevant when entries are read from disk.
147
+ kind : EntryKind :: Tree ,
148
+ } ,
149
+ is_untracked : true ,
150
+ } ,
151
+ status_item : None ,
152
+ } ) )
153
+ } ) ,
154
+ ) ;
155
+
115
156
let ( new_tree, base_tree) = commit_engine:: tree:: apply_worktree_changes (
116
157
head_tree_id. into ( ) ,
117
158
repo,
@@ -138,19 +179,141 @@ pub(super) mod function {
138
179
needs_head = true ;
139
180
}
140
181
182
+ let ( index, index_conflicts) = snapshot_index ( & mut edit, head_tree, changes, selection) ?
183
+ . inspect ( |( index, index_conflicts) | {
184
+ needs_head |= index_conflicts. is_some ( ) && index. is_none ( ) ;
185
+ } )
186
+ . unwrap_or_default ( ) ;
187
+
141
188
if needs_head {
142
189
edit. upsert ( "HEAD" , EntryKind :: Tree , head_tree_id) ?;
143
190
}
144
191
145
192
Ok ( Outcome {
146
193
snapshot_tree : edit. write ( ) ?. into ( ) ,
147
- head_tree : head_tree_id. into ( ) ,
194
+ head_tree : head_tree_id. detach ( ) ,
148
195
worktree,
149
- index : None ,
150
- index_conflicts : None ,
196
+ index,
197
+ index_conflicts,
151
198
workspace_references : None ,
152
199
head_references : None ,
153
200
metadata : None ,
154
201
} )
155
202
}
203
+
204
+ /// `snapshot_tree` is the tree into which our `index` and `index-conflicts` trees are written. These will also be returned
205
+ /// if they were written.
206
+ ///
207
+ /// `base_tree_id` is the tree from which a clean index can be created, and which we will edit to incorporate the
208
+ /// non-conflicting index changes.
209
+ fn snapshot_index (
210
+ snapshot_tree : & mut gix:: object:: tree:: Editor ,
211
+ base_tree : gix:: Tree ,
212
+ changes : but_core:: WorktreeChanges ,
213
+ selection : BTreeSet < BString > ,
214
+ ) -> anyhow:: Result < Option < ( Option < gix:: ObjectId > , Option < gix:: ObjectId > ) > > {
215
+ let mut conflicts = Vec :: new ( ) ;
216
+ let changes: Vec < _ > = changes
217
+ . changes
218
+ . into_iter ( )
219
+ . filter_map ( |c| c. status_item )
220
+ . chain (
221
+ changes
222
+ . ignored_changes
223
+ . into_iter ( )
224
+ . filter_map ( |c| c. status_item ) ,
225
+ )
226
+ . filter_map ( |item| match item {
227
+ gix:: status:: Item :: IndexWorktree (
228
+ gix:: status:: index_worktree:: Item :: Modification {
229
+ status : EntryStatus :: Conflict { entries, .. } ,
230
+ rela_path,
231
+ ..
232
+ } ,
233
+ ) => {
234
+ conflicts. push ( ( rela_path, entries) ) ;
235
+ None
236
+ }
237
+ gix:: status:: Item :: TreeIndex ( c) => Some ( c) ,
238
+ _ => None ,
239
+ } )
240
+ . filter ( |c| selection. iter ( ) . any ( |path| path == c. location ( ) ) )
241
+ . collect ( ) ;
242
+
243
+ if changes. is_empty ( ) && conflicts. is_empty ( ) {
244
+ return Ok ( None ) ;
245
+ }
246
+
247
+ let mut base_tree_edit = base_tree. edit ( ) ?;
248
+ for change in changes {
249
+ match change {
250
+ Change :: Deletion { location, .. } => {
251
+ base_tree_edit. remove ( location. as_bstr ( ) ) ?;
252
+ }
253
+ Change :: Addition {
254
+ location,
255
+ entry_mode,
256
+ id,
257
+ ..
258
+ }
259
+ | Change :: Modification {
260
+ location,
261
+ entry_mode,
262
+ id,
263
+ ..
264
+ } => {
265
+ base_tree_edit. upsert (
266
+ location. as_bstr ( ) ,
267
+ entry_mode
268
+ . to_tree_entry_mode ( )
269
+ . with_context ( || format ! ( "Could not convert the index entry {entry_mode:?} at '{location}' into a tree entry kind" ) ) ?
270
+ . kind ( ) ,
271
+ id. into_owned ( ) ,
272
+ ) ?;
273
+ }
274
+ Change :: Rewrite { .. } => {
275
+ unreachable ! ( "BUG: this must have been deactivated" )
276
+ }
277
+ }
278
+ }
279
+
280
+ let index = base_tree_edit. write ( ) ?;
281
+ let index = ( index != base_tree. id ) . then_some ( index. detach ( ) ) ;
282
+ if let Some ( index) = index {
283
+ snapshot_tree. upsert ( "index" , EntryKind :: Tree , index) ?;
284
+ }
285
+
286
+ let index_conflicts = if conflicts. is_empty ( ) {
287
+ None
288
+ } else {
289
+ let mut root = snapshot_tree. cursor_at ( "index-conflicts" ) ?;
290
+ for ( rela_path, conflict_entries) in conflicts {
291
+ for ( stage, entry) in conflict_entries
292
+ . into_iter ( )
293
+ . enumerate ( )
294
+ . filter_map ( |( idx, e) | e. map ( |e| ( idx + 1 , e) ) )
295
+ {
296
+ root. upsert (
297
+ format ! ( "{rela_path}/{stage}" ) ,
298
+ entry
299
+ . mode
300
+ . to_tree_entry_mode ( )
301
+ . with_context ( || {
302
+ format ! (
303
+ "Could not convert the index entry {entry_mode:?} \
304
+ at '{location}' into a tree entry kind",
305
+ entry_mode = entry. mode,
306
+ location = rela_path
307
+ )
308
+ } ) ?
309
+ . kind ( ) ,
310
+ entry. id ,
311
+ ) ?;
312
+ }
313
+ }
314
+ root. write ( ) ?. detach ( ) . into ( )
315
+ } ;
316
+
317
+ Ok ( Some ( ( index, index_conflicts) ) )
318
+ }
156
319
}
0 commit comments