98286406 Andy McCall 1 month ago ↶
1
2
3
use super :: * ;
957fee30 Andy McCall 1 month ago ↶
4
use crate :: objects:: AtomRef;
98286406 Andy McCall 1 month ago ↶
5
6
# [ derive ( Clone, Copy, Debug, PartialEq, Eq ) ]
7
pub enum ChangeStatus {
8
Added,
9
Modified,
10
Removed,
6e723b81 Andy McCall 1 month ago ↶
11
12
Renamed,
98286406 Andy McCall 1 month ago ↶
13
ModeChanged,
14
15
ConflictEdited,
16
17
Resolved,
18
}
19
20
21
pub fn has_conflict_markers ( content : & [u8 ]) -> bool {
22
split_lines ( content) .iter ( ) .any ( | l | {
23
l.starts_with ( b " <<<<<<< oxy conflict" ) | | l.starts_with ( b " ======= oxy conflict" ) | | l.starts_with ( b " >>>>>>> oxy conflict" )
24
} )
25
}
26
27
# [ derive ( Clone, Debug, PartialEq, Eq ) ]
28
pub struct PathStatus {
29
pub path : String,
30
pub status : ChangeStatus,
31
}
32
614541c3 Andy McCall 1 month ago ↶
33
34
pub ( crate ) struct BuiltChange {
35
pub tree_ops : Vec< TreeOp> ,
36
pub edits : Vec< Edit> ,
37
pub contributors : Vec< ChangeHash> ,
38
pub resolves : bool ,
39
pub statuses : Vec< PathStatus> ,
b7f102fc Andy McCall 1 month ago ↶
40
41
pub blobs : HashMap< String, ObjectRef> ,
614541c3 Andy McCall 1 month ago ↶
42
}
43
98286406 Andy McCall 1 month ago ↶
44
45
pub struct SnapshotResult {
46
pub wc_hash : ChangeHash,
47
pub wc : Change,
48
49
pub base : State,
50
pub base_snapshot : Snapshot,
51
pub statuses : Vec< PathStatus> ,
52
53
pub changed : bool ,
54
55
pub resolves : bool ,
56
57
pub contributors : Vec< ChangeHash> ,
58
}
59
60
pub fn is_binary ( content : & [u8 ]) -> bool {
61
content[ .. content.len ( ) .min ( BINARY_SNIFF ) ] .contains ( & 0 )
62
}
63
64
impl Repo {
614541c3 Andy McCall 1 month ago ↶
65
66
67
68
pub ( crate ) fn build_against ( & self , base : & State, base_snapshot : & Snapshot, reuse : HashMap< String, ( NodeId, NodeKind) > ) -> Result< BuiltChange> {
4dee277c Andy McCall 1 month ago ↶
69
let fs_entries = self .walk_working_copy_with ( None , & Self :: tracked_kinds( base_snapshot) ) ? ;
c3069537 Andy McCall 1 month ago ↶
70
self .build_from_entries ( base, base_snapshot, reuse, & fs_entries)
71
}
72
73
74
pub ( crate ) fn build_from_entries ( & self , base : & State, base_snapshot : & Snapshot, reuse : HashMap< String, ( NodeId, NodeKind) > , fs_entries : & BTreeMap< String, FsEntry> ) -> Result< BuiltChange> {
cdb5beba Andy McCall 1 month ago ↶
75
76
77
let attrs = crate :: attributes:: Attributes:: from_files(
78
fs_entries.iter ( ) .filter ( | ( p , _) | p.as_str ( ) = = " .oxyattributes" | | p.ends_with ( " /.oxyattributes" ) ) .map ( | ( p , e ) | ( p.as_str ( ) , e.content.as_slice ( ) ) ) ,
79
) ;
80
let mut b = ChangeBuilder:: new( base, base_snapshot, reuse) .with_attributes ( attrs) ;
6e723b81 Andy McCall 1 month ago ↶
81
82
83
84
let renames = detect_renames ( base_snapshot, fs_entries, | p | self .in_scope ( p) ) ;
85
let renamed_from: HashMap< & str , & str > = renames.iter ( ) .map ( | ( old , new ) | ( new.as_str ( ) , old.as_str ( ) ) ) .collect ( ) ;
86
let renamed_to: HashMap< & str , & str > = renames.iter ( ) .map ( | ( old , new ) | ( old.as_str ( ) , new.as_str ( ) ) ) .collect ( ) ;
c3069537 Andy McCall 1 month ago ↶
87
for ( path, fs_entry) in fs_entries {
98286406 Andy McCall 1 month ago ↶
88
match base_snapshot.files.get ( path) {
6e723b81 Andy McCall 1 month ago ↶
89
None => match renamed_from.get ( path.as_str ( ) ) {
90
Some ( old) => b.move_path ( self , old, path, fs_entry, & base_snapshot.files[ * old] ) ? ,
91
None => b.add_path ( self , path, fs_entry) ? ,
92
} ,
98286406 Andy McCall 1 month ago ↶
93
Some ( expected) => b.update_path ( self , path, fs_entry, expected) ? ,
94
}
95
}
96
for ( path, expected) in & base_snapshot.files {
6e723b81 Andy McCall 1 month ago ↶
97
if fs_entries.contains_key ( path) | | ! self .in_scope ( path) | | renamed_to.contains_key ( path.as_str ( ) ) {
98286406 Andy McCall 1 month ago ↶
98
continue ;
99
}
7421309f Andy McCall 1 month ago ↶
100
101
let still_needed = expected.kind = = NodeKind:: Dir
102
& & ( fs_entries.keys ( ) .any ( | p | p.starts_with ( & format! ( " {path} /" ) ) )
103
| | base_snapshot.files.keys ( ) .any ( | p | p.starts_with ( & format! ( " {path} /" ) ) & & ! self .in_scope ( p) ) ) ;
98286406 Andy McCall 1 month ago ↶
104
if ! still_needed {
105
b.remove_path ( path, expected) ;
106
}
107
}
614541c3 Andy McCall 1 month ago ↶
108
let mut statuses = b.statuses;
109
statuses.sort_by ( | a , b | a.path.cmp ( & b.path) ) ;
b7f102fc Andy McCall 1 month ago ↶
110
Ok ( BuiltChange { tree_ops: b.tree_ops, edits: b.edits, contributors: b.contributors.into_iter ( ) .collect ( ) , resolves: b.resolves, statuses, blobs: b.blobs } )
614541c3 Andy McCall 1 month ago ↶
111
}
98286406 Andy McCall 1 month ago ↶
112
c07e0e7d Andy McCall 1 month ago ↶
113
114
115
614541c3 Andy McCall 1 month ago ↶
116
pub ( crate ) fn store_signed ( & self , change : & Change) -> Result< ChangeHash> {
c07e0e7d Andy McCall 1 month ago ↶
117
let ours = change.author = = self .author ( ) ;
118
if self .config.sign_changes & & ours & & self .secret_key ( ) ? .is_none ( ) {
614541c3 Andy McCall 1 month ago ↶
119
return fail ( " core.sign is \" change\" but no secret key is available for author.key" ) ;
120
}
121
let hash = self .store.put_change ( change) ? ;
c07e0e7d Andy McCall 1 month ago ↶
122
if self .config.sign_changes & & ours {
614541c3 Andy McCall 1 month ago ↶
123
self .sign_change ( & hash) ? ;
124
}
125
Ok ( hash)
126
}
127
128
129
pub fn snapshot ( & self , command : & str ) -> Result< SnapshotResult> {
4e88e774 Andy McCall 1 month ago ↶
130
let _lock = self .lock_snapshot ( ) ? ;
8f10a07a Andy McCall 1 month ago ↶
131
let batch = self .store.begin_batch ( ) ? ;
132
let result = self .snapshot_locked ( command) ;
133
batch.finish ( ) ? ;
134
result
135
}
136
137
fn snapshot_locked ( & self , command : & str ) -> Result< SnapshotResult> {
614541c3 Andy McCall 1 month ago ↶
138
let ( op_ref, op) = self .head_op ( ) ? ;
139
let ( old_hash, old_wc) = self .working_copy ( ) ? ;
140
let base = self .load_state ( & old_wc.deps) ? ;
141
let base_snapshot = base.materialize ( ) ;
142
143
144
145
let reuse = added_node_paths ( & old_wc, & base_snapshot) ;
b7f102fc Andy McCall 1 month ago ↶
146
147
let manifest = self .read_manifest ( ) ? .map ( | ( _, _, m ) | m ) ;
4dee277c Andy McCall 1 month ago ↶
148
let fs_entries = self .walk_working_copy_with ( manifest.as_ref ( ) , & Self :: tracked_kinds( & base_snapshot) ) ? ;
b7f102fc Andy McCall 1 month ago ↶
149
let built = self .build_from_entries ( & base, & base_snapshot, reuse, & fs_entries) ? ;
614541c3 Andy McCall 1 month ago ↶
150
98286406 Andy McCall 1 month ago ↶
151
let mut wc = Change {
152
change_id: old_wc.change_id,
153
deps: vec! [ ] ,
154
author: self .author ( ) ,
155
hlc: old_wc.hlc,
156
message: old_wc.message.clone ( ) ,
614541c3 Andy McCall 1 month ago ↶
157
tree_ops: built.tree_ops,
158
edits: built.edits,
98286406 Andy McCall 1 month ago ↶
159
kind: ChangeKind:: WorkingCopy,
160
} ;
161
let base_heads = base.maximal ( & old_wc.deps) ;
162
let mut deps: BTreeSet< ChangeHash> = base_heads.into_iter ( ) .collect ( ) ;
163
deps.extend ( referenced_changes ( & wc, & base) ) ;
614541c3 Andy McCall 1 month ago ↶
164
deps.extend ( built.contributors.iter ( ) .copied ( ) ) ;
98286406 Andy McCall 1 month ago ↶
165
wc.deps = deps.into_iter ( ) .collect ( ) ;
166
wc.normalize ( ) ;
614541c3 Andy McCall 1 month ago ↶
167
let resolves = built.resolves;
168
let contributors = built.contributors;
169
let statuses = built.statuses;
98286406 Andy McCall 1 month ago ↶
170
171
let new_hash = wc.hash ( ) ;
172
let changed = new_hash ! = old_hash;
173
if changed {
174
self .store.put_change ( & wc) ? ;
175
let mut view = op.view.clone ( ) ;
176
view.workspaces.insert ( self .workspace, new_hash) ;
177
self .record_op ( & [ op_ref] , & format! ( " {command} {SNAPSHOT_SUFFIX} " ) , view) ? ;
178
}
135212de Andy McCall 1 month ago ↶
179
b7f102fc Andy McCall 1 month ago ↶
180
self .write_manifest_entries ( base_snapshot.is_conflicted ( ) , ! wc.tree_ops.is_empty ( ) | | ! wc.edits.is_empty ( ) , & fs_entries, & built.blobs) ? ;
98286406 Andy McCall 1 month ago ↶
181
Ok ( SnapshotResult { wc_hash: new_hash, wc, base, base_snapshot, statuses, changed, resolves, contributors } )
182
}
183
50a046e4 Andy McCall 1 month ago ↶
184
185
186
187
pub fn status_json ( & self , snap : & SnapshotResult) -> Result< String> {
188
let heads = self .bookmark_heads ( ) ? ;
189
let base_snapshot = & snap.base_snapshot;
190
let by_node = base_snapshot.tree.by_node ( ) ;
191
let path_of = | n : & NodeId| by_node.get ( n) .map ( | e | e.path.clone ( ) ) ;
192
let id_of = | h : & ChangeHash| snap.base.get ( h) .map ( | m | m.change_id.to_hex ( ) ) .unwrap_or_else ( | | h.to_hex ( ) ) ;
193
let paths = json_array ( snap.statuses.iter ( ) .map ( | s | format! ( " {{ \" path\" :{} ,\" status\" :{} }} " , json_str ( & s.path) , json_str ( & format! ( " {:?} " , s.status) ) ) ) ) ;
194
let mut conflicts: Vec< String> = Vec :: new( ) ;
195
for ( p, f) in & base_snapshot.files {
196
if ! f.is_conflicted ( ) {
197
continue ;
198
}
199
let ( kind, changes) = match & f.content {
200
FileContent:: Text { .. } => ( " text" , snap.base.conflict_regions ( & f.node) .contributors) ,
201
FileContent:: Opaque { blobs } => ( " replace" , snap.base.blob_contributors ( & f.node, blobs) ) ,
202
FileContent:: Dir => continue ,
203
} ;
204
let resolved = match snap.statuses.iter ( ) .find ( | s | s.path = = * p ) .map ( | s | s.status ) {
205
Some ( ChangeStatus:: Resolved) => " true" ,
206
Some ( ChangeStatus:: ConflictEdited) => " \" markers\" " ,
207
_ => " false" ,
208
} ;
209
conflicts.push ( format! (
210
" {{ \" kind\" :{} ,\" path\" :{} ,\" node\" :{} ,\" changes\" :{} ,\" resolved\" :{resolved} }} " ,
211
json_str ( kind) ,
212
json_str ( p) ,
213
json_str ( & f.node.to_hex ( ) ) ,
214
json_array ( changes.iter ( ) .map ( | h | json_str ( & id_of ( h) ) ) )
215
) ) ;
216
}
217
for c in & base_snapshot.tree.conflicts {
218
let ( kind, nodes) : ( & str , Vec< NodeId> ) = match c {
219
crate :: tree:: TreeConflict:: Name { nodes, .. } => ( " name" , nodes.clone ( ) ) ,
220
crate :: tree:: TreeConflict:: MultiPlacement { node, .. } => ( " multi-placement" , vec! [ * node] ) ,
221
crate :: tree:: TreeConflict:: Orphan { node, .. } => ( " orphan" , vec! [ * node] ) ,
222
crate :: tree:: TreeConflict:: Cycle { node } => ( " cycle" , vec! [ * node] ) ,
223
crate :: tree:: TreeConflict:: Mode { node, .. } => ( " mode" , vec! [ * node] ) ,
224
} ;
225
let detail = match c {
226
crate :: tree:: TreeConflict:: Name { name, parent, .. } => {
227
let dir = if * parent = = NodeId:: ROOT { String :: new( ) } else { path_of ( parent) .unwrap_or_default ( ) } ;
228
format! ( " ,\" path\" :{} " , json_str ( & if dir.is_empty ( ) { name.clone ( ) } else { format! ( " {dir} /{name} " ) } ) )
229
}
230
_ => nodes.first ( ) .and_then ( path_of) .map ( | p | format! ( " ,\" path\" :{} " , json_str ( & p) ) ) .unwrap_or_default ( ) ,
231
} ;
232
conflicts.push ( format! (
233
" {{ \" kind\" :{} ,\" nodes\" :{} {detail} }} " ,
234
json_str ( kind) ,
235
json_array ( nodes.iter ( ) .map ( | n | json_str ( & n.to_hex ( ) ) ) )
236
) ) ;
237
}
238
Ok ( format! (
239
" {{ \" bookmark\" :{} ,\" working_copy\" :{} ,\" heads\" :{} ,\" clean\" :{} ,\" conflicted\" :{} ,\" paths\" :{paths} ,\" conflicts\" :{} }} " ,
240
json_str ( & self .config.bookmark) ,
241
json_str ( & snap.wc.change_id.to_hex ( ) ) ,
242
heads.len ( ) ,
243
snap.statuses.is_empty ( ) ,
244
base_snapshot.is_conflicted ( ) ,
245
json_array ( conflicts)
246
) )
247
}
248
98286406 Andy McCall 1 month ago ↶
249
250
pub fn commit ( & self , message : & str , extra_deps : & [ChangeHash]) -> Result< ChangeHash> {
957fee30 Andy McCall 1 month ago ↶
251
self .commit_selection ( message, extra_deps, & Selection:: All)
252
}
253
254
255
256
pub fn hunks ( & self ) -> Result< ( SnapshotResult, Vec< Hunk> ) > {
257
let snap = self .snapshot ( " oxy commit" ) ? ;
258
let hunks = render_hunks ( & snap.wc, & snap.base, & snap.base_snapshot) ;
259
Ok ( ( snap, hunks) )
260
}
261
262
263
264
265
266
pub fn commit_selection ( & self , message : & str , extra_deps : & [ChangeHash], selection : & Selection) -> Result< ChangeHash> {
98286406 Andy McCall 1 month ago ↶
267
let snap = self .snapshot ( " oxy commit" ) ? ;
268
if snap.wc.tree_ops.is_empty ( ) & & snap.wc.edits.is_empty ( ) {
269
return fail ( " nothing to commit: the working copy matches its base" ) ;
270
}
271
if let Some ( s) = snap.statuses.iter ( ) .find ( | s | s.status = = ChangeStatus:: ConflictEdited ) {
272
return fail ( format! ( " {} still contains conflict markers" , s.path) ) ;
273
}
274
if message.trim ( ) .is_empty ( ) {
275
return fail ( " a commit message is required" ) ;
276
}
957fee30 Andy McCall 1 month ago ↶
277
let ( tree_ops, edits, partial) = match selection {
278
Selection:: All => ( snap.wc.tree_ops.clone ( ) , snap.wc.edits.clone ( ) , false ) ,
279
Selection:: Paths( paths) => {
280
let ( o, e) = select_paths ( & snap.wc, & snap.base_snapshot, paths) ;
281
let partial = o.len ( ) ! = snap.wc.tree_ops.len ( ) | | e.len ( ) ! = snap.wc.edits.len ( ) ;
282
( o, e, partial)
283
}
284
Selection:: Hunks( indices) => {
285
let ( o, e) = select_hunks ( & snap.wc, indices) ;
286
let partial = o.len ( ) ! = snap.wc.tree_ops.len ( ) | | e.len ( ) ! = snap.wc.edits.len ( ) ;
287
( o, e, partial)
288
}
289
} ;
290
if tree_ops.is_empty ( ) & & edits.is_empty ( ) {
291
return fail ( " nothing selected to commit" ) ;
292
}
293
if partial & & snap.resolves {
294
return fail ( " a conflict resolution must be committed whole" ) ;
295
}
6d3eefbc Andy McCall 1 month ago ↶
296
297
self .run_hook (
298
" pre-commit" ,
299
& format! (
300
" {{ \" event\" :\" pre-commit\" ,\" message\" :{} ,\" paths\" :{} }} " ,
301
json_str ( message) ,
302
json_array ( snap.statuses.iter ( ) .map ( | s | format! ( " {{ \" path\" :{} ,\" status\" :{} }} " , json_str ( & s.path) , json_str ( & format! ( " {:?} " , s.status) ) ) ) )
303
) ,
304
) ? ;
98286406 Andy McCall 1 month ago ↶
305
let ( op_ref, op) = self .head_op ( ) ? ;
306
let mut deps: BTreeSet< ChangeHash> = referenced_changes ( & snap.wc, & snap.base) ;
6d3eefbc Andy McCall 1 month ago ↶
307
308
if self .config.semantic_deps {
309
let touched: Vec< String> = snap.statuses.iter ( ) .map ( | s | s.path.clone ( ) ) .collect ( ) ;
310
let by_node = snap.base_snapshot.tree.by_node ( ) ;
311
let mut candidates: Vec< ( ChangeId, ChangeHash, Vec< String> ) > = Vec :: new( ) ;
312
let mut seen: BTreeSet< ChangeHash> = BTreeSet:: new( ) ;
313
for h in deps.iter ( ) .chain ( snap.base.maximal ( & snap.wc.deps) .iter ( ) ) {
314
if ! seen.insert ( * h) {
315
continue ;
316
}
317
let c = self .store.get_change ( h) ? ;
318
let mut paths: BTreeSet< String> = BTreeSet:: new( ) ;
319
for n in c.tree_ops.iter ( ) .map ( | o | o.node ( ) ) .chain ( c.edits.iter ( ) .map ( | e | e.node ( ) ) ) {
320
if let Some ( e) = by_node.get ( & n) {
321
paths.insert ( e.path.clone ( ) ) ;
322
}
323
}
324
candidates.push ( ( c.change_id, * h, paths.into_iter ( ) .collect ( ) ) ) ;
325
}
326
for d in self .semantic_deps ( & snap.wc.change_id, & touched, & candidates) ? {
327
if snap.base.contains ( & d) {
328
deps.insert ( d) ;
329
}
330
}
331
}
98286406 Andy McCall 1 month ago ↶
332
333
334
335
deps.extend ( snap.contributors.iter ( ) .copied ( ) ) ;
336
for d in extra_deps {
337
if ! snap.base.contains ( d) {
338
return fail ( format! ( " --depends-on {d} : not in the working copy's base state" ) ) ;
339
}
340
deps.insert ( * d) ;
341
}
342
let mut change = Change {
957fee30 Andy McCall 1 month ago ↶
343
change_id: if partial { ChangeId:: random( ) } else { snap.wc.change_id } ,
344
deps: vec! [ ] ,
98286406 Andy McCall 1 month ago ↶
345
author: self .author ( ) ,
346
hlc: Hlc:: now( ) ,
347
message: message.trim_end ( ) .to_string ( ) ,
957fee30 Andy McCall 1 month ago ↶
348
tree_ops,
349
edits,
98286406 Andy McCall 1 month ago ↶
350
kind: if snap.resolves { ChangeKind:: Resolution } else { ChangeKind:: Normal } ,
351
} ;
957fee30 Andy McCall 1 month ago ↶
352
if partial {
353
354
355
deps = referenced_changes ( & change, & snap.base) ;
356
for d in extra_deps {
357
deps.insert ( * d) ;
358
}
359
}
360
change.deps = deps.into_iter ( ) .collect ( ) ;
98286406 Andy McCall 1 month ago ↶
361
change.normalize ( ) ;
614541c3 Andy McCall 1 month ago ↶
362
let hash = self .store_signed ( & change) ? ;
98286406 Andy McCall 1 month ago ↶
363
364
365
366
367
368
let old_heads = self .heads_of ( & op.view, & self .config.bookmark) ? ;
369
let old_base = snap.base.maximal ( & snap.wc.deps) ;
370
let mut all: Vec< ChangeHash> = old_heads.clone ( ) ;
371
all.extend ( old_base.iter ( ) .copied ( ) ) ;
372
all.push ( hash) ;
373
let mut st = self .load_state ( & all) ? ;
374
st.add ( change) ? ;
375
let mut with_new = old_heads.clone ( ) ;
376
with_new.push ( hash) ;
377
let new_heads = st.maximal ( & with_new) ;
252e8cb2 Andy McCall 1 month ago ↶
378
379
let since = self .latest_checkpoint ( & self .config.bookmark) ? .map ( | ( _, cp ) | cp.count ) .unwrap_or ( 0 ) ;
380
if self .config.checkpoint_interval > 0 & & ( st.len ( ) as u64 ) .saturating_sub ( since) > = self .config.checkpoint_interval {
381
let cp_state = self .load_state_full ( & new_heads) ? ;
382
self .write_checkpoint ( & self .config.bookmark, & cp_state, & new_heads) ? ;
383
}
98286406 Andy McCall 1 month ago ↶
384
let mut wc_base = old_base.clone ( ) ;
385
wc_base.push ( hash) ;
386
let wc_base = st.maximal ( & wc_base) ;
387
388
let wc = self .new_working_copy ( wc_base, " " ) ;
389
let wc_hash = self .store.put_change ( & wc) ? ;
390
let mut view = op.view.clone ( ) ;
391
view.bookmarks.insert ( self .config.bookmark.clone ( ) , self .store.put_state ( & StateObject:: new( new_heads) ) ? ) ;
392
view.workspaces.insert ( self .workspace, wc_hash) ;
393
self .record_op ( & [ op_ref] , & format! ( " oxy commit -m {message:?} " ) , view) ? ;
957fee30 Andy McCall 1 month ago ↶
394
if partial {
395
396
self .drop_manifest ( ) ;
397
} else {
398
self .rewrite_manifest_flags ( snap.base_snapshot.is_conflicted ( ) & & ! snap.resolves, false ) ? ;
399
}
d02e545d Andy McCall 1 month ago ↶
400
self .maybe_autopack ( ) ? ;
6d3eefbc Andy McCall 1 month ago ↶
401
let _ = self .run_hook ( " post-commit" , & format! ( " {{ \" event\" :\" post-commit\" ,\" change_id\" :{} ,\" hash\" :{} ,\" message\" :{} }} " , json_str ( & snap.wc.change_id.to_hex ( ) ) , json_str ( & hash.to_hex ( ) ) , json_str ( message) ) ) ;
98286406 Andy McCall 1 month ago ↶
402
Ok ( hash)
403
}
404
}
405
957fee30 Andy McCall 1 month ago ↶
406
407
# [ derive ( Clone, Debug ) ]
408
pub enum Selection {
409
All,
410
411
Paths( Vec< String> ) ,
412
413
Hunks( Vec< usize > ) ,
414
}
415
416
417
# [ derive ( Clone, Debug ) ]
418
pub struct Hunk {
419
pub index : usize ,
420
pub path : String,
421
422
pub text : String,
423
}
424
425
426
427
fn select_paths ( wc : & Change, base_snapshot : & Snapshot, paths : & [String]) -> ( Vec< TreeOp> , Vec< Edit> ) {
428
let selected = | p : & str | paths.iter ( ) .any ( | x | p = = x | | p.starts_with ( & format! ( " {} /" , x.trim_end_matches ( ' /' ) ) ) ) ;
429
let by_node = base_snapshot.tree.by_node ( ) ;
430
let mut new_names: HashMap< NodeId, ( NodeId, String, NodeKind) > = HashMap:: new( ) ;
431
for op in & wc.tree_ops {
432
if let TreeOp:: AddNode { node, parent, name, kind, .. } = op {
433
new_names.insert ( * node, ( * parent, name.clone ( ) , * kind) ) ;
434
}
435
}
436
let mut nodes: BTreeSet< NodeId> = BTreeSet:: new( ) ;
437
for n in wc.tree_ops.iter ( ) .map ( | o | o.node ( ) ) .chain ( wc.edits.iter ( ) .map ( | e | e.node ( ) ) ) {
438
if path_of_node ( & n, & by_node, & new_names) .is_some_and ( | p | selected ( & p) ) {
439
nodes.insert ( n) ;
440
}
441
}
442
443
let mut changed = true ;
444
while changed {
445
changed = false ;
446
for ( n, ( parent, _ , _ ) ) in & new_names {
447
if nodes.contains ( n) & & new_names.contains_key ( parent) & & nodes.insert ( * parent) {
448
changed = true ;
449
}
450
}
451
}
452
let ops = wc.tree_ops.iter ( ) .filter ( | o | nodes.contains ( & o.node ( ) ) ) .cloned ( ) .collect ( ) ;
453
let edits = wc.edits.iter ( ) .filter ( | e | nodes.contains ( & e.node ( ) ) ) .cloned ( ) .collect ( ) ;
454
( ops, edits)
455
}
456
457
458
459
460
fn select_hunks ( wc : & Change, indices : & [usize ]) -> ( Vec< TreeOp> , Vec< Edit> ) {
461
let n_ops = wc.tree_ops.len ( ) ;
462
let mut ops_sel = vec! [ false ; n_ops] ;
463
let mut edits_sel = vec! [ false ; wc.edits.len ( ) ] ;
464
for & i in indices {
465
if i < n_ops {
466
ops_sel[ i] = true ;
467
} else if i - n_ops < wc.edits.len ( ) {
468
edits_sel[ i - n_ops] = true ;
469
}
470
}
471
472
let mut per_node: HashMap< NodeId, u32 > = HashMap:: new( ) ;
473
let mut insert_ranges: Vec< Option< ( NodeId, u32 , u32 ) > > = Vec :: new( ) ;
474
for e in & wc.edits {
475
insert_ranges.push ( match e {
476
Edit:: Insert { node, lines, .. } => {
477
let start = per_node.entry ( * node) .or_default ( ) ;
478
let r = ( * node, * start, * start + lines.len ( ) as u32 ) ;
479
* start + = lines.len ( ) as u32 ;
480
Some ( r)
481
}
482
_ => None ,
483
} ) ;
484
}
485
let add_index: HashMap< NodeId, usize > = wc.tree_ops.iter ( ) .enumerate ( ) .filter_map ( | ( i , o ) | if let TreeOp:: AddNode { node, .. } = o { Some ( ( * node, i) ) } else { None } ) .collect ( ) ;
486
let insert_for = | node : & NodeId, index : u32 | -> Option< usize > { insert_ranges.iter ( ) .position ( | r | matches! ( r, Some ( ( n, a, b) ) if n = = node & & * a < = index & & index < * b) ) } ;
487
488
let mut changed = true ;
489
while changed {
490
changed = false ;
491
for i in 0 .. n_ops {
492
if ! ops_sel[ i] {
493
continue ;
494
}
d8c086ca Andy McCall 1 month ago ↶
495
if let TreeOp:: AddNode { node, parent, .. } = & wc.tree_ops[ i] {
957fee30 Andy McCall 1 month ago ↶
496
if let Some ( & p) = add_index.get ( parent) {
497
if ! ops_sel[ p] {
498
ops_sel[ p] = true ;
499
changed = true ;
500
}
501
}
d8c086ca Andy McCall 1 month ago ↶
502
503
504
for ( j, e) in wc.edits.iter ( ) .enumerate ( ) {
505
if e.node ( ) = = * node & & ! edits_sel[ j] {
506
edits_sel[ j] = true ;
507
changed = true ;
508
}
509
}
957fee30 Andy McCall 1 month ago ↶
510
}
511
}
512
for j in 0 .. wc.edits.len ( ) {
513
if ! edits_sel[ j] {
514
continue ;
515
}
516
let e = & wc.edits[ j] ;
517
if let Some ( & a) = add_index.get ( & e.node ( ) ) {
518
if ! ops_sel[ a] {
519
ops_sel[ a] = true ;
520
changed = true ;
521
}
522
}
523
if let Edit:: Insert { node, after, before, .. } = e {
524
for r in [ after, before] {
525
if let AtomRef:: Atom { change, index } = r {
526
if change.is_zero ( ) {
527
if let Some ( k) = insert_for ( node, * index) {
528
if ! edits_sel[ k] {
529
edits_sel[ k] = true ;
530
changed = true ;
531
}
532
}
533
}
534
}
535
}
536
}
537
}
538
}
539
540
let mut remap: HashMap< ( NodeId, u32 ) , u32 > = HashMap:: new( ) ;
541
let mut new_counts: HashMap< NodeId, u32 > = HashMap:: new( ) ;
542
for ( j, r) in insert_ranges.iter ( ) .enumerate ( ) {
543
if let ( true , Some ( ( node, a, b) ) ) = ( edits_sel[ j] , r) {
544
let next = new_counts.entry ( * node) .or_default ( ) ;
545
for old in * a.. * b {
546
remap.insert ( ( * node, old) , * next) ;
547
* next + = 1 ;
548
}
549
}
550
}
551
let map_ref = | r : AtomRef, node : & NodeId| -> AtomRef {
552
match r {
553
AtomRef:: Atom { change, index } if change.is_zero ( ) => AtomRef:: Atom { change, index: remap.get ( & ( * node, index) ) .copied ( ) .unwrap_or ( index) } ,
554
other => other,
555
}
556
} ;
557
let ops = wc.tree_ops.iter ( ) .enumerate ( ) .filter ( | ( i , _) | ops_sel[ * i] ) .map ( | ( _, o ) | o.clone ( ) ) .collect ( ) ;
558
let edits = wc
559
.edits
560
.iter ( )
561
.enumerate ( )
562
.filter ( | ( j , _) | edits_sel[ * j] )
563
.map ( | ( _, e ) | match e {
564
Edit:: Insert { node, after, before, lines } => Edit:: Insert { node: * node, after: map_ref ( * after, node) , before: map_ref ( * before, node) , lines: lines.clone ( ) } ,
565
other => other.clone ( ) ,
566
} )
567
.collect ( ) ;
568
( ops, edits)
569
}
570
571
572
pub fn render_hunks ( change : & Change, base : & State, base_snapshot : & Snapshot) -> Vec< Hunk> {
573
let by_node = base_snapshot.tree.by_node ( ) ;
574
let mut new_names: HashMap< NodeId, ( NodeId, String, NodeKind) > = HashMap:: new( ) ;
575
for op in & change.tree_ops {
576
if let TreeOp:: AddNode { node, parent, name, kind, .. } = op {
577
new_names.insert ( * node, ( * parent, name.clone ( ) , * kind) ) ;
578
}
579
}
580
let mut out = Vec :: new( ) ;
581
for ( i, op) in change.tree_ops.iter ( ) .enumerate ( ) {
582
let one = Change { tree_ops: vec! [ op.clone ( ) ] , edits: vec! [ ] , .. change.clone ( ) } ;
583
let path = path_of_node ( & op.node ( ) , & by_node, & new_names) .unwrap_or_default ( ) ;
584
out.push ( Hunk { index: i, path, text: render_change ( & one, base, base_snapshot) } ) ;
585
}
586
for ( j, e) in change.edits.iter ( ) .enumerate ( ) {
587
let one = Change { tree_ops: vec! [ ] , edits: vec! [ e.clone ( ) ] , .. change.clone ( ) } ;
588
let path = path_of_node ( & e.node ( ) , & by_node, & new_names) .unwrap_or_default ( ) ;
589
out.push ( Hunk { index: change.tree_ops.len ( ) + j, path, text: render_change ( & one, base, base_snapshot) } ) ;
590
}
591
out
592
}
593
98286406 Andy McCall 1 month ago ↶
594
595
struct ChangeBuilder < 'a > {
596
base : & 'a State,
597
base_snapshot : & 'a Snapshot,
598
tree_ops : Vec< TreeOp> ,
599
edits : Vec< Edit> ,
600
statuses : Vec< PathStatus> ,
601
dir_nodes : HashMap< String, NodeId> ,
602
reuse : HashMap< String, ( NodeId, NodeKind) > ,
603
604
contributors : BTreeSet< ChangeHash> ,
605
resolves : bool ,
cdb5beba Andy McCall 1 month ago ↶
606
attrs : crate:: attributes:: Attributes,
b7f102fc Andy McCall 1 month ago ↶
607
blobs : HashMap< String, ObjectRef> ,
98286406 Andy McCall 1 month ago ↶
608
}
609
610
impl < 'a > ChangeBuilder < 'a > {
611
fn new ( base : & 'a State, base_snapshot : & 'a Snapshot, reuse : HashMap< String, ( NodeId, NodeKind) > ) -> Self {
612
let mut dir_nodes: HashMap< String, NodeId> = HashMap:: new( ) ;
613
dir_nodes.insert ( String :: new( ) , NodeId:: ROOT ) ;
614
for ( p, f) in & base_snapshot.files {
615
if f.kind = = NodeKind:: Dir {
616
dir_nodes.insert ( p.clone ( ) , f.node) ;
617
}
618
}
b7f102fc Andy McCall 1 month ago ↶
619
ChangeBuilder { base, base_snapshot, tree_ops: vec! [ ] , edits: vec! [ ] , statuses: vec! [ ] , dir_nodes, reuse, contributors: BTreeSet:: new( ) , resolves: false , attrs: crate :: attributes:: Attributes:: default( ) , blobs: HashMap:: new( ) }
98286406 Andy McCall 1 month ago ↶
620
}
621
cdb5beba Andy McCall 1 month ago ↶
622
fn with_attributes ( mut self , attrs : crate:: attributes:: Attributes) -> Self {
623
self .attrs = attrs;
624
self
625
}
626
627
628
b7f102fc Andy McCall 1 month ago ↶
629
630
fn blob_of ( & mut self , repo : & Repo, path : & str , e : & FsEntry, nocompress : bool ) -> Result< ObjectRef> {
631
let b = match e.blob {
632
Some ( b) => b,
633
None => repo.store_blob_for ( & e.content, nocompress) ? ,
634
} ;
635
self .blobs.insert ( path.to_string ( ) , b) ;
636
Ok ( b)
637
}
638
cdb5beba Andy McCall 1 month ago ↶
639
fn opaque_for ( & self , path : & str , e : & FsEntry) -> bool {
b7f102fc Andy McCall 1 month ago ↶
640
if e.blob.is_some ( ) {
641
return true ;
642
}
4dee277c Andy McCall 1 month ago ↶
643
if e.kind = = NodeKind:: Symlink | | e.kind = = NodeKind:: Subrepo {
cdb5beba Andy McCall 1 month ago ↶
644
return true ;
645
}
646
match self .attrs.lookup ( path) .binary {
647
Some ( true ) => true ,
648
Some ( false ) => is_binary ( & e.content) ,
649
None => is_binary ( & e.content) ,
650
}
651
}
652
653
654
fn stored_text ( & self , path : & str , content : & [u8 ]) -> Vec< u8 > {
655
if self .attrs.lookup ( path) .eol.is_some ( ) { crate :: attributes:: normalize_eol( content) } else { content.to_vec ( ) }
656
}
657
98286406 Andy McCall 1 month ago ↶
658
fn reused_node ( & self , path : & str , kind : NodeKind) -> Option< NodeId> {
659
self .reuse.get ( path) .filter ( | ( _, k ) | * k = = kind) .map ( | ( n , _) | * n)
660
}
661
662
fn ensure_dir ( & mut self , path : & str ) -> NodeId {
663
if let Some ( n) = self .dir_nodes.get ( path) {
664
return * n;
665
}
666
let mut parent = NodeId:: ROOT ;
667
let mut prefix = String :: new( ) ;
668
for comp in path.split ( ' /' ) {
669
if ! prefix.is_empty ( ) {
670
prefix.push ( ' /' ) ;
671
}
672
prefix.push_str ( comp) ;
673
parent = match self .dir_nodes.get ( & prefix) {
674
Some ( n) => * n,
675
None => {
676
let node = self .reused_node ( & prefix, NodeKind:: Dir) .unwrap_or_else ( NodeId:: random) ;
677
self .tree_ops.push ( TreeOp:: AddNode { node, kind: NodeKind:: Dir, mode: Mode:: default( ) , parent, name: comp.to_string ( ) } ) ;
678
self .dir_nodes.insert ( prefix.clone ( ) , node) ;
679
node
680
}
681
} ;
682
}
683
parent
684
}
685
686
fn add_path ( & mut self , repo : & Repo, path : & str , e : & FsEntry) -> Result< ( ) > {
687
let ( dir, name) = split_path ( path) ;
688
let parent = self .ensure_dir ( dir) ;
689
let node = self .reused_node ( path, e.kind) .unwrap_or_else ( NodeId:: random) ;
cdb5beba Andy McCall 1 month ago ↶
690
let opaque = self .opaque_for ( path, e) ;
691
let nocompress = self .attrs.lookup ( path) .nocompress;
98286406 Andy McCall 1 month ago ↶
692
self .tree_ops.push ( TreeOp:: AddNode { node, kind: e.kind, mode: Mode { exec: e.exec, opaque } , parent, name: name.to_string ( ) } ) ;
693
if opaque {
b7f102fc Andy McCall 1 month ago ↶
694
let blob = self .blob_of ( repo, path, e, nocompress) ? ;
98286406 Andy McCall 1 month ago ↶
695
self .edits.push ( Edit:: Replace { node, old: None , new: Some ( blob) } ) ;
696
} else if ! e.content.is_empty ( ) {
cdb5beba Andy McCall 1 month ago ↶
697
let content = self .stored_text ( path, & e.content) ;
98286406 Andy McCall 1 month ago ↶
698
self .edits.push ( Edit:: Insert {
699
node,
700
after: crate :: objects:: AtomRef:: Start,
701
before: crate :: objects:: AtomRef:: End,
cdb5beba Andy McCall 1 month ago ↶
702
lines: split_lines ( & content) .iter ( ) .map ( | l | l.to_vec ( ) ) .collect ( ) ,
98286406 Andy McCall 1 month ago ↶
703
} ) ;
704
}
705
self .statuses.push ( PathStatus { path: path.to_string ( ) , status: ChangeStatus:: Added } ) ;
706
Ok ( ( ) )
707
}
708
6e723b81 Andy McCall 1 month ago ↶
709
710
fn move_path ( & mut self , repo : & Repo, old : & str , new : & str , e : & FsEntry, expected : & crate:: state:: FileState) -> Result< ( ) > {
711
let entry = & self .base_snapshot.tree.entries[ old] ;
712
let ( dir, name) = split_path ( new) ;
713
let parent = self .ensure_dir ( dir) ;
714
self .tree_ops.push ( TreeOp:: Move { node: expected.node, old: op_ref ( entry.placement) , parent, name: name.to_string ( ) } ) ;
715
self .update_path ( repo, old, e, expected) ? ;
716
717
self .statuses.retain ( | s | s.path ! = old ) ;
718
self .statuses.push ( PathStatus { path: new.to_string ( ) , status: ChangeStatus:: Renamed } ) ;
719
Ok ( ( ) )
720
}
721
98286406 Andy McCall 1 month ago ↶
722
fn remove_path ( & mut self , path : & str , expected : & crate:: state:: FileState) {
723
let entry = & self .base_snapshot.tree.entries[ path] ;
724
self .tree_ops.push ( TreeOp:: RemoveNode { node: expected.node, placement: op_ref ( entry.placement) } ) ;
725
if path.contains ( " .oxy-conflict-" ) | | expected.is_conflicted ( ) {
726
self .resolves = true ;
727
}
728
self .statuses.push ( PathStatus { path: path.to_string ( ) , status: ChangeStatus:: Removed } ) ;
729
}
730
731
fn update_path ( & mut self , repo : & Repo, path : & str , e : & FsEntry, expected : & crate:: state:: FileState) -> Result< ( ) > {
732
if expected.kind ! = e.kind {
733
self .remove_path ( path, expected) ;
734
self .statuses.pop ( ) ;
735
self .add_path ( repo, path, e) ? ;
736
self .statuses.pop ( ) ;
737
self .statuses.push ( PathStatus { path: path.to_string ( ) , status: ChangeStatus:: Modified } ) ;
738
return Ok ( ( ) ) ;
739
}
740
let entry = & self .base_snapshot.tree.entries[ path] ;
741
let node = expected.node;
742
let mode_atom = entry.mode_atom.map ( op_ref) ;
743
let mut status: Option< ChangeStatus> = None ;
cdb5beba Andy McCall 1 month ago ↶
744
let wants_opaque = self .opaque_for ( path, e) ;
745
let nocompress = self .attrs.lookup ( path) .nocompress;
746
let disk_text = self .stored_text ( path, & e.content) ;
98286406 Andy McCall 1 month ago ↶
747
match & expected.content {
748
FileContent:: Dir => { }
749
FileContent:: Text { bytes, materialized } => {
750
if materialized.is_conflicted ( ) {
cdb5beba Andy McCall 1 month ago ↶
751
if bytes = = & disk_text {
98286406 Andy McCall 1 month ago ↶
752
return Ok ( ( ) ) ;
753
}
754
if has_conflict_markers ( & e.content) {
755
self .statuses.push ( PathStatus { path: path.to_string ( ) , status: ChangeStatus:: ConflictEdited } ) ;
756
return Ok ( ( ) ) ;
757
}
758
759
760
761
762
let regions = self .base.conflict_regions ( & node) ;
cdb5beba Andy McCall 1 month ago ↶
763
let mut edits = text_edits ( node, & regions.plain, & disk_text) ;
98286406 Andy McCall 1 month ago ↶
764
if ! regions.region_atoms.is_empty ( ) {
765
edits.insert ( 0 , Edit:: Delete { node, atoms: ranges ( & regions.region_atoms) } ) ;
766
}
767
self .edits.extend ( edits) ;
768
self .contributors.extend ( regions.contributors) ;
769
self .resolves = true ;
770
if e.exec ! = expected.mode.exec {
771
if let Some ( old) = mode_atom {
772
self .tree_ops.push ( TreeOp:: SetMode { node, old, mode: Mode { exec: e.exec, opaque: false } } ) ;
773
}
774
}
775
self .statuses.push ( PathStatus { path: path.to_string ( ) , status: ChangeStatus:: Resolved } ) ;
776
return Ok ( ( ) ) ;
777
}
cdb5beba Andy McCall 1 month ago ↶
778
if wants_opaque {
779
780
if let Some ( old) = mode_atom {
781
self .tree_ops.push ( TreeOp:: SetMode { node, old, mode: Mode { exec: e.exec, opaque: true } } ) ;
782
}
783
let live: Vec< _ > = repo_live_keys ( self .base, & node) ;
784
if ! live.is_empty ( ) {
785
self .edits.push ( Edit:: Delete { node, atoms: ranges ( & live) } ) ;
786
}
b7f102fc Andy McCall 1 month ago ↶
787
let blob = self .blob_of ( repo, path, e, nocompress) ? ;
cdb5beba Andy McCall 1 month ago ↶
788
self .edits.push ( Edit:: Replace { node, old: None , new: Some ( blob) } ) ;
789
status = Some ( ChangeStatus:: Modified) ;
790
} else if bytes ! = & disk_text {
791
let old = self .base.live_lines ( & node) ;
792
let edits = text_edits ( node, & old, & disk_text) ;
793
if ! edits.is_empty ( ) {
794
self .edits.extend ( edits) ;
98286406 Andy McCall 1 month ago ↶
795
status = Some ( ChangeStatus:: Modified) ;
796
}
797
}
cdb5beba Andy McCall 1 month ago ↶
798
let switched_to_opaque = wants_opaque;
98286406 Andy McCall 1 month ago ↶
799
if ! switched_to_opaque & & e.exec ! = expected.mode.exec {
800
if let Some ( old) = mode_atom {
801
self .tree_ops.push ( TreeOp:: SetMode { node, old, mode: Mode { exec: e.exec, opaque: false } } ) ;
802
status.get_or_insert ( ChangeStatus:: ModeChanged) ;
803
}
804
}
805
}
806
FileContent:: Opaque { blobs } => {
b7f102fc Andy McCall 1 month ago ↶
807
let new_ref = e.blob.unwrap_or_else ( | | Repo:: blob_ref( & e.content) ) ;
808
if e.kind = = NodeKind:: File & & wants_opaque {
809
810
self .blobs.insert ( path.to_string ( ) , new_ref) ;
811
}
98286406 Andy McCall 1 month ago ↶
812
if blobs.len ( ) > 1 {
813
if ! blobs.contains ( & new_ref) {
814
self .statuses.push ( PathStatus { path: path.to_string ( ) , status: ChangeStatus:: ConflictEdited } ) ;
815
}
816
return Ok ( ( ) ) ;
817
}
818
let current = blobs.first ( ) .copied ( ) ;
819
if current ! = Some ( new_ref) {
cdb5beba Andy McCall 1 month ago ↶
820
if e.kind = = NodeKind:: File & & ! wants_opaque {
98286406 Andy McCall 1 month ago ↶
821
822
if let Some ( old) = mode_atom {
823
self .tree_ops.push ( TreeOp:: SetMode { node, old, mode: Mode { exec: e.exec, opaque: false } } ) ;
824
}
825
self .edits.push ( Edit:: Replace { node, old: current, new: None } ) ;
826
if ! e.content.is_empty ( ) {
827
self .edits.push ( Edit:: Insert {
828
node,
829
after: crate :: objects:: AtomRef:: Start,
830
before: crate :: objects:: AtomRef:: End,
831
lines: split_lines ( & e.content) .iter ( ) .map ( | l | l.to_vec ( ) ) .collect ( ) ,
832
} ) ;
833
}
834
} else {
b7f102fc Andy McCall 1 month ago ↶
835
let blob = self .blob_of ( repo, path, e, nocompress) ? ;
98286406 Andy McCall 1 month ago ↶
836
self .edits.push ( Edit:: Replace { node, old: current, new: Some ( blob) } ) ;
837
}
838
status = Some ( ChangeStatus:: Modified) ;
839
} else if e.kind = = NodeKind:: File & & e.exec ! = expected.mode.exec {
840
if let Some ( old) = mode_atom {
841
self .tree_ops.push ( TreeOp:: SetMode { node, old, mode: Mode { exec: e.exec, opaque: true } } ) ;
842
status = Some ( ChangeStatus:: ModeChanged) ;
843
}
844
}
845
}
846
}
847
if let Some ( s) = status {
848
self .statuses.push ( PathStatus { path: path.to_string ( ) , status: s } ) ;
849
}
850
Ok ( ( ) )
851
}
852
}
853
854
fn repo_live_keys ( base : & State, node : & NodeId) -> Vec< crate:: graph:: AtomKey> {
855
base.live_lines ( node) .into_iter ( ) .map ( | ( k , _) | k ) .collect ( )
856
}
857
858
859
860
pub fn referenced_changes ( change : & Change, base : & State) -> BTreeSet< ChangeHash> {
861
let mut out = BTreeSet:: new( ) ;
862
let opref = | r : & OpRef, out : & mut BTreeSet< ChangeHash> | {
863
if ! r.change.is_zero ( ) {
864
out.insert ( r.change) ;
865
}
866
} ;
867
let mut nodes: Vec< NodeId> = Vec :: new( ) ;
868
for op in & change.tree_ops {
869
match op {
870
TreeOp:: AddNode { parent, .. } => nodes.push ( * parent) ,
871
TreeOp:: RemoveNode { node, placement } => {
872
nodes.push ( * node) ;
873
opref ( placement, & mut out) ;
874
}
875
TreeOp:: Move { node, old, parent, .. } => {
876
nodes.push ( * node) ;
877
nodes.push ( * parent) ;
878
opref ( old, & mut out) ;
879
}
880
TreeOp:: SetMode { node, old, .. } => {
881
nodes.push ( * node) ;
882
opref ( old, & mut out) ;
883
}
884
}
885
}
886
for e in & change.edits {
887
nodes.push ( e.node ( ) ) ;
888
match e {
889
Edit:: Insert { after, before, .. } => {
890
for r in [ after, before] {
891
if let crate :: objects:: AtomRef:: Atom { change, .. } = r {
892
if ! change.is_zero ( ) {
893
out.insert ( * change) ;
894
}
895
}
896
}
897
}
898
Edit:: Delete { atoms, .. } => {
899
for r in atoms {
900
if ! r.change.is_zero ( ) {
901
out.insert ( r.change) ;
902
}
903
}
904
}
905
Edit:: Replace { .. } => { }
906
}
907
}
908
for n in nodes {
909
if let Some ( h) = base.tree ( ) .introducer ( & n) {
910
out.insert ( h) ;
911
}
912
}
913
out
914
}
915
916
917
pub fn added_node_paths ( change : & Change, base_snapshot : & Snapshot) -> HashMap< String, ( NodeId, NodeKind) > {
918
let by_node = base_snapshot.tree.by_node ( ) ;
919
let mut new_names: HashMap< NodeId, ( NodeId, String, NodeKind) > = HashMap:: new( ) ;
920
for op in & change.tree_ops {
921
if let TreeOp:: AddNode { node, parent, name, kind, .. } = op {
922
new_names.insert ( * node, ( * parent, name.clone ( ) , * kind) ) ;
923
}
924
}
925
let mut out = HashMap:: new( ) ;
926
for ( node, ( _ , _ , kind) ) in & new_names {
927
if let Some ( path) = path_of_node ( node, & by_node, & new_names) {
928
out.insert ( path, ( * node, * kind) ) ;
929
}
930
}
931
out
932
}
933
934
fn path_of_node (
935
n : & NodeId,
936
by_node : & HashMap< NodeId, & crate:: tree:: TreeEntry> ,
937
new_names : & HashMap< NodeId, ( NodeId, String, NodeKind) > ,
938
) -> Option< String> {
939
if let Some ( e) = by_node.get ( n) {
940
return Some ( e.path.clone ( ) ) ;
941
}
942
let mut parts = Vec :: new( ) ;
943
let mut cur = * n;
944
loop {
945
if cur.is_root ( ) {
946
break ;
947
}
948
if let Some ( e) = by_node.get ( & cur) {
949
if ! e.path.is_empty ( ) {
950
parts.push ( e.path.clone ( ) ) ;
951
}
952
break ;
953
}
954
let ( parent, name, _ ) = new_names.get ( & cur) ? ;
955
parts.push ( name.clone ( ) ) ;
956
cur = * parent;
957
}
958
parts.reverse ( ) ;
959
Some ( parts.join ( " /" ) )
960
}
961
962
963
pub fn render_change ( change : & Change, base : & State, base_snapshot : & Snapshot) -> String {
964
let by_node = base_snapshot.tree.by_node ( ) ;
965
let mut new_names: HashMap< NodeId, ( NodeId, String, NodeKind) > = HashMap:: new( ) ;
966
for op in & change.tree_ops {
967
if let TreeOp:: AddNode { node, parent, name, kind, .. } = op {
968
new_names.insert ( * node, ( * parent, name.clone ( ) , * kind) ) ;
969
}
970
}
971
let path_of = | n : & NodeId| -> String { path_of_node ( n, & by_node, & new_names) .unwrap_or_else ( | | format! ( " <node {} >" , n.short ( 8 ) ) ) } ;
972
973
let mut out = String :: new( ) ;
974
for op in & change.tree_ops {
975
match op {
976
TreeOp:: AddNode { node, kind, mode, .. } => {
977
out.push_str ( & format! ( " A {} ({:?} {} {} )\n " , path_of ( node) , kind, if mode.exec { " , exec" } else { " " } , if mode.opaque { " , opaque" } else { " " } ) ) ;
978
}
979
TreeOp:: RemoveNode { node, .. } => out.push_str ( & format! ( " D {} \n " , path_of ( node) ) ) ,
980
TreeOp:: Move { node, name, .. } => out.push_str ( & format! ( " R {} -> {} \n " , path_of ( node) , name) ) ,
981
TreeOp:: SetMode { node, mode, .. } => {
982
out.push_str ( & format! ( " M {} mode: exec={} opaque={} \n " , path_of ( node) , mode.exec, mode.opaque) ) ;
983
}
984
}
985
}
986
let mut last_node: Option< NodeId> = None ;
987
for e in & change.edits {
988
let n = e.node ( ) ;
989
if last_node ! = Some ( n) {
990
out.push_str ( & format! ( " --- {} \n " , path_of ( & n) ) ) ;
991
last_node = Some ( n) ;
992
}
993
match e {
994
Edit:: Insert { lines, .. } => {
995
for l in lines {
996
out.push ( ' +' ) ;
997
out.push_str ( & String :: from_utf8_lossy( l) ) ;
998
if ! l.ends_with ( b " \n " ) {
999
out.push ( ' \n ' ) ;
1000
}
1001
}
1002
}
1003
Edit:: Delete { atoms, .. } => {
1004
for r in atoms {
1005
for i in r.from.. r.to {
1006
let content = base.file ( & n) .and_then ( | g | g.content ( & ( r.change, i) ) ) .unwrap_or ( b " <unknown>\n " ) ;
1007
out.push ( ' -' ) ;
1008
out.push_str ( & String :: from_utf8_lossy( content) ) ;
1009
if ! content.ends_with ( b " \n " ) {
1010
out.push ( ' \n ' ) ;
1011
}
1012
}
1013
}
1014
}
1015
Edit:: Replace { old, new, .. } => {
1016
out.push_str ( & format! (
1017
" opaque content {} -> {} \n " ,
1018
old.map ( | o | o.short ( 12 ) ) .unwrap_or_else ( | | " none" .into ( ) ) ,
1019
new.map ( | o | o.short ( 12 ) ) .unwrap_or_else ( | | " none" .into ( ) )
1020
) ) ;
1021
}
1022
}
1023
}
1024
out
1025
}
6e723b81 Andy McCall 1 month ago ↶
1026
1027
1028
1029
1030
const RENAME_THRESHOLD : u32 = 50 ;
1031
const RENAME_LIMIT : usize = 10_000 ;
1032
1033
fn detect_renames ( base_snapshot : & Snapshot, fs_entries : & BTreeMap< String, FsEntry> , in_scope : impl Fn( &str ) -> bool ) -> Vec< ( String, String) > {
1034
let removed: Vec< ( & String, & crate:: state:: FileState) > = base_snapshot
1035
.files
1036
.iter ( )
1037
.filter ( | ( p , f ) | f.kind ! = NodeKind:: Dir & & ! fs_entries.contains_key ( * p) & & in_scope ( p) & & ! f.is_conflicted ( ) )
1038
.collect ( ) ;
1039
let added: Vec< ( & String, & FsEntry) > = fs_entries.iter ( ) .filter ( | ( p , e ) | e.kind ! = NodeKind:: Dir & & ! base_snapshot.files.contains_key ( * p) ) .collect ( ) ;
1040
if removed.is_empty ( ) | | added.is_empty ( ) {
1041
return vec! [ ] ;
1042
}
1043
let exact_only = removed.len ( ) * added.len ( ) > RENAME_LIMIT ;
1044
let old_content = | f : & crate:: state:: FileState| -> Option< Vec< u8 > > {
1045
match & f.content {
1046
FileContent:: Text { bytes, .. } => Some ( bytes.clone ( ) ) ,
1047
_ => None ,
1048
}
1049
} ;
1050
let mut scored: Vec< ( u32 , usize , usize ) > = Vec :: new( ) ;
1051
for ( ri, ( _ , f) ) in removed.iter ( ) .enumerate ( ) {
1052
let old = old_content ( f) ;
1053
let old_blob = match & f.content {
1054
FileContent:: Opaque { blobs } if blobs.len ( ) = = 1 => Some ( blobs[ 0 ] ) ,
1055
_ => None ,
1056
} ;
1057
for ( ai, ( _ , e) ) in added.iter ( ) .enumerate ( ) {
1058
if e.kind ! = f.kind {
1059
continue ;
1060
}
1061
let score = match ( & old, old_blob) {
1062
( Some ( o) , _ ) => {
1063
if exact_only {
1064
if * o = = e.content { 100 } else { 0 }
1065
} else {
1066
similarity ( o, & e.content)
1067
}
1068
}
b7f102fc Andy McCall 1 month ago ↶
1069
( None , Some ( b) ) if e.blob.unwrap_or_else ( | | Repo:: blob_ref( & e.content) ) = = b => 100 ,
6e723b81 Andy McCall 1 month ago ↶
1070
_ => 0 ,
1071
} ;
1072
if score > = RENAME_THRESHOLD {
1073
scored.push ( ( score, ri, ai) ) ;
1074
}
1075
}
1076
}
1077
scored.sort_by ( | a , b | b.0. cmp ( & a.0 ) .then ( removed[ a.1 ] .0. cmp ( removed[ b.1 ] .0 ) ) .then ( added[ a.2 ] .0. cmp ( added[ b.2 ] .0 ) ) ) ;
1078
let mut used_r = vec! [ false ; removed.len ( ) ] ;
1079
let mut used_a = vec! [ false ; added.len ( ) ] ;
1080
let mut out = Vec :: new( ) ;
1081
for ( _ , ri, ai) in scored {
1082
if used_r[ ri] | | used_a[ ai] {
1083
continue ;
1084
}
1085
used_r[ ri] = true ;
1086
used_a[ ai] = true ;
1087
out.push ( ( removed[ ri] .0. clone ( ) , added[ ai] .0. clone ( ) ) ) ;
1088
}
1089
out
1090
}
1091
1092
1093
1094
pub fn similarity ( a : & [u8 ], b : & [u8 ]) -> u32 {
1095
if a = = b {
1096
return 100 ;
1097
}
1098
if a.is_empty ( ) | | b.is_empty ( ) {
1099
return 0 ;
1100
}
1101
let mut counts: HashMap< & [ u8 ] , usize > = HashMap:: new( ) ;
1102
for l in split_lines ( a) {
1103
* counts.entry ( l) .or_default ( ) + = 1 ;
1104
}
1105
let mut shared = 0 usize ;
1106
for l in split_lines ( b) {
1107
if let Some ( n) = counts.get_mut ( l) {
1108
if * n > 0 {
1109
* n - = 1 ;
1110
shared + = l.len ( ) ;
1111
}
1112
}
1113
}
1114
( shared * 100 / a.len ( ) .max ( b.len ( ) ) ) as u32
1115
}
1116