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
}
495
if let TreeOp:: AddNode { parent, .. } = & wc.tree_ops[ i] {
496
if let Some ( & p) = add_index.get ( parent) {
497
if ! ops_sel[ p] {
498
ops_sel[ p] = true ;
499
changed = true ;
500
}
501
}
502
}
503
}
504
for j in 0 .. wc.edits.len ( ) {
505
if ! edits_sel[ j] {
506
continue ;
507
}
508
let e = & wc.edits[ j] ;
509
if let Some ( & a) = add_index.get ( & e.node ( ) ) {
510
if ! ops_sel[ a] {
511
ops_sel[ a] = true ;
512
changed = true ;
513
}
514
}
515
if let Edit:: Insert { node, after, before, .. } = e {
516
for r in [ after, before] {
517
if let AtomRef:: Atom { change, index } = r {
518
if change.is_zero ( ) {
519
if let Some ( k) = insert_for ( node, * index) {
520
if ! edits_sel[ k] {
521
edits_sel[ k] = true ;
522
changed = true ;
523
}
524
}
525
}
526
}
527
}
528
}
529
}
530
}
531
532
let mut remap: HashMap< ( NodeId, u32 ) , u32 > = HashMap:: new( ) ;
533
let mut new_counts: HashMap< NodeId, u32 > = HashMap:: new( ) ;
534
for ( j, r) in insert_ranges.iter ( ) .enumerate ( ) {
535
if let ( true , Some ( ( node, a, b) ) ) = ( edits_sel[ j] , r) {
536
let next = new_counts.entry ( * node) .or_default ( ) ;
537
for old in * a.. * b {
538
remap.insert ( ( * node, old) , * next) ;
539
* next + = 1 ;
540
}
541
}
542
}
543
let map_ref = | r : AtomRef, node : & NodeId| -> AtomRef {
544
match r {
545
AtomRef:: Atom { change, index } if change.is_zero ( ) => AtomRef:: Atom { change, index: remap.get ( & ( * node, index) ) .copied ( ) .unwrap_or ( index) } ,
546
other => other,
547
}
548
} ;
549
let ops = wc.tree_ops.iter ( ) .enumerate ( ) .filter ( | ( i , _) | ops_sel[ * i] ) .map ( | ( _, o ) | o.clone ( ) ) .collect ( ) ;
550
let edits = wc
551
.edits
552
.iter ( )
553
.enumerate ( )
554
.filter ( | ( j , _) | edits_sel[ * j] )
555
.map ( | ( _, e ) | match e {
556
Edit:: Insert { node, after, before, lines } => Edit:: Insert { node: * node, after: map_ref ( * after, node) , before: map_ref ( * before, node) , lines: lines.clone ( ) } ,
557
other => other.clone ( ) ,
558
} )
559
.collect ( ) ;
560
( ops, edits)
561
}
562
563
564
pub fn render_hunks ( change : & Change, base : & State, base_snapshot : & Snapshot) -> Vec< Hunk> {
565
let by_node = base_snapshot.tree.by_node ( ) ;
566
let mut new_names: HashMap< NodeId, ( NodeId, String, NodeKind) > = HashMap:: new( ) ;
567
for op in & change.tree_ops {
568
if let TreeOp:: AddNode { node, parent, name, kind, .. } = op {
569
new_names.insert ( * node, ( * parent, name.clone ( ) , * kind) ) ;
570
}
571
}
572
let mut out = Vec :: new( ) ;
573
for ( i, op) in change.tree_ops.iter ( ) .enumerate ( ) {
574
let one = Change { tree_ops: vec! [ op.clone ( ) ] , edits: vec! [ ] , .. change.clone ( ) } ;
575
let path = path_of_node ( & op.node ( ) , & by_node, & new_names) .unwrap_or_default ( ) ;
576
out.push ( Hunk { index: i, path, text: render_change ( & one, base, base_snapshot) } ) ;
577
}
578
for ( j, e) in change.edits.iter ( ) .enumerate ( ) {
579
let one = Change { tree_ops: vec! [ ] , edits: vec! [ e.clone ( ) ] , .. change.clone ( ) } ;
580
let path = path_of_node ( & e.node ( ) , & by_node, & new_names) .unwrap_or_default ( ) ;
581
out.push ( Hunk { index: change.tree_ops.len ( ) + j, path, text: render_change ( & one, base, base_snapshot) } ) ;
582
}
583
out
584
}
585
98286406 Andy McCall 1 month ago ↶
586
587
struct ChangeBuilder < 'a > {
588
base : & 'a State,
589
base_snapshot : & 'a Snapshot,
590
tree_ops : Vec< TreeOp> ,
591
edits : Vec< Edit> ,
592
statuses : Vec< PathStatus> ,
593
dir_nodes : HashMap< String, NodeId> ,
594
reuse : HashMap< String, ( NodeId, NodeKind) > ,
595
596
contributors : BTreeSet< ChangeHash> ,
597
resolves : bool ,
cdb5beba Andy McCall 1 month ago ↶
598
attrs : crate:: attributes:: Attributes,
b7f102fc Andy McCall 1 month ago ↶
599
blobs : HashMap< String, ObjectRef> ,
98286406 Andy McCall 1 month ago ↶
600
}
601
602
impl < 'a > ChangeBuilder < 'a > {
603
fn new ( base : & 'a State, base_snapshot : & 'a Snapshot, reuse : HashMap< String, ( NodeId, NodeKind) > ) -> Self {
604
let mut dir_nodes: HashMap< String, NodeId> = HashMap:: new( ) ;
605
dir_nodes.insert ( String :: new( ) , NodeId:: ROOT ) ;
606
for ( p, f) in & base_snapshot.files {
607
if f.kind = = NodeKind:: Dir {
608
dir_nodes.insert ( p.clone ( ) , f.node) ;
609
}
610
}
b7f102fc Andy McCall 1 month ago ↶
611
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 ↶
612
}
613
cdb5beba Andy McCall 1 month ago ↶
614
fn with_attributes ( mut self , attrs : crate:: attributes:: Attributes) -> Self {
615
self .attrs = attrs;
616
self
617
}
618
619
620
b7f102fc Andy McCall 1 month ago ↶
621
622
fn blob_of ( & mut self , repo : & Repo, path : & str , e : & FsEntry, nocompress : bool ) -> Result< ObjectRef> {
623
let b = match e.blob {
624
Some ( b) => b,
625
None => repo.store_blob_for ( & e.content, nocompress) ? ,
626
} ;
627
self .blobs.insert ( path.to_string ( ) , b) ;
628
Ok ( b)
629
}
630
cdb5beba Andy McCall 1 month ago ↶
631
fn opaque_for ( & self , path : & str , e : & FsEntry) -> bool {
b7f102fc Andy McCall 1 month ago ↶
632
if e.blob.is_some ( ) {
633
return true ;
634
}
4dee277c Andy McCall 1 month ago ↶
635
if e.kind = = NodeKind:: Symlink | | e.kind = = NodeKind:: Subrepo {
cdb5beba Andy McCall 1 month ago ↶
636
return true ;
637
}
638
match self .attrs.lookup ( path) .binary {
639
Some ( true ) => true ,
640
Some ( false ) => is_binary ( & e.content) ,
641
None => is_binary ( & e.content) ,
642
}
643
}
644
645
646
fn stored_text ( & self , path : & str , content : & [u8 ]) -> Vec< u8 > {
647
if self .attrs.lookup ( path) .eol.is_some ( ) { crate :: attributes:: normalize_eol( content) } else { content.to_vec ( ) }
648
}
649
98286406 Andy McCall 1 month ago ↶
650
fn reused_node ( & self , path : & str , kind : NodeKind) -> Option< NodeId> {
651
self .reuse.get ( path) .filter ( | ( _, k ) | * k = = kind) .map ( | ( n , _) | * n)
652
}
653
654
fn ensure_dir ( & mut self , path : & str ) -> NodeId {
655
if let Some ( n) = self .dir_nodes.get ( path) {
656
return * n;
657
}
658
let mut parent = NodeId:: ROOT ;
659
let mut prefix = String :: new( ) ;
660
for comp in path.split ( ' /' ) {
661
if ! prefix.is_empty ( ) {
662
prefix.push ( ' /' ) ;
663
}
664
prefix.push_str ( comp) ;
665
parent = match self .dir_nodes.get ( & prefix) {
666
Some ( n) => * n,
667
None => {
668
let node = self .reused_node ( & prefix, NodeKind:: Dir) .unwrap_or_else ( NodeId:: random) ;
669
self .tree_ops.push ( TreeOp:: AddNode { node, kind: NodeKind:: Dir, mode: Mode:: default( ) , parent, name: comp.to_string ( ) } ) ;
670
self .dir_nodes.insert ( prefix.clone ( ) , node) ;
671
node
672
}
673
} ;
674
}
675
parent
676
}
677
678
fn add_path ( & mut self , repo : & Repo, path : & str , e : & FsEntry) -> Result< ( ) > {
679
let ( dir, name) = split_path ( path) ;
680
let parent = self .ensure_dir ( dir) ;
681
let node = self .reused_node ( path, e.kind) .unwrap_or_else ( NodeId:: random) ;
cdb5beba Andy McCall 1 month ago ↶
682
let opaque = self .opaque_for ( path, e) ;
683
let nocompress = self .attrs.lookup ( path) .nocompress;
98286406 Andy McCall 1 month ago ↶
684
self .tree_ops.push ( TreeOp:: AddNode { node, kind: e.kind, mode: Mode { exec: e.exec, opaque } , parent, name: name.to_string ( ) } ) ;
685
if opaque {
b7f102fc Andy McCall 1 month ago ↶
686
let blob = self .blob_of ( repo, path, e, nocompress) ? ;
98286406 Andy McCall 1 month ago ↶
687
self .edits.push ( Edit:: Replace { node, old: None , new: Some ( blob) } ) ;
688
} else if ! e.content.is_empty ( ) {
cdb5beba Andy McCall 1 month ago ↶
689
let content = self .stored_text ( path, & e.content) ;
98286406 Andy McCall 1 month ago ↶
690
self .edits.push ( Edit:: Insert {
691
node,
692
after: crate :: objects:: AtomRef:: Start,
693
before: crate :: objects:: AtomRef:: End,
cdb5beba Andy McCall 1 month ago ↶
694
lines: split_lines ( & content) .iter ( ) .map ( | l | l.to_vec ( ) ) .collect ( ) ,
98286406 Andy McCall 1 month ago ↶
695
} ) ;
696
}
697
self .statuses.push ( PathStatus { path: path.to_string ( ) , status: ChangeStatus:: Added } ) ;
698
Ok ( ( ) )
699
}
700
6e723b81 Andy McCall 1 month ago ↶
701
702
fn move_path ( & mut self , repo : & Repo, old : & str , new : & str , e : & FsEntry, expected : & crate:: state:: FileState) -> Result< ( ) > {
703
let entry = & self .base_snapshot.tree.entries[ old] ;
704
let ( dir, name) = split_path ( new) ;
705
let parent = self .ensure_dir ( dir) ;
706
self .tree_ops.push ( TreeOp:: Move { node: expected.node, old: op_ref ( entry.placement) , parent, name: name.to_string ( ) } ) ;
707
self .update_path ( repo, old, e, expected) ? ;
708
709
self .statuses.retain ( | s | s.path ! = old ) ;
710
self .statuses.push ( PathStatus { path: new.to_string ( ) , status: ChangeStatus:: Renamed } ) ;
711
Ok ( ( ) )
712
}
713
98286406 Andy McCall 1 month ago ↶
714
fn remove_path ( & mut self , path : & str , expected : & crate:: state:: FileState) {
715
let entry = & self .base_snapshot.tree.entries[ path] ;
716
self .tree_ops.push ( TreeOp:: RemoveNode { node: expected.node, placement: op_ref ( entry.placement) } ) ;
717
if path.contains ( " .oxy-conflict-" ) | | expected.is_conflicted ( ) {
718
self .resolves = true ;
719
}
720
self .statuses.push ( PathStatus { path: path.to_string ( ) , status: ChangeStatus:: Removed } ) ;
721
}
722
723
fn update_path ( & mut self , repo : & Repo, path : & str , e : & FsEntry, expected : & crate:: state:: FileState) -> Result< ( ) > {
724
if expected.kind ! = e.kind {
725
self .remove_path ( path, expected) ;
726
self .statuses.pop ( ) ;
727
self .add_path ( repo, path, e) ? ;
728
self .statuses.pop ( ) ;
729
self .statuses.push ( PathStatus { path: path.to_string ( ) , status: ChangeStatus:: Modified } ) ;
730
return Ok ( ( ) ) ;
731
}
732
let entry = & self .base_snapshot.tree.entries[ path] ;
733
let node = expected.node;
734
let mode_atom = entry.mode_atom.map ( op_ref) ;
735
let mut status: Option< ChangeStatus> = None ;
cdb5beba Andy McCall 1 month ago ↶
736
let wants_opaque = self .opaque_for ( path, e) ;
737
let nocompress = self .attrs.lookup ( path) .nocompress;
738
let disk_text = self .stored_text ( path, & e.content) ;
98286406 Andy McCall 1 month ago ↶
739
match & expected.content {
740
FileContent:: Dir => { }
741
FileContent:: Text { bytes, materialized } => {
742
if materialized.is_conflicted ( ) {
cdb5beba Andy McCall 1 month ago ↶
743
if bytes = = & disk_text {
98286406 Andy McCall 1 month ago ↶
744
return Ok ( ( ) ) ;
745
}
746
if has_conflict_markers ( & e.content) {
747
self .statuses.push ( PathStatus { path: path.to_string ( ) , status: ChangeStatus:: ConflictEdited } ) ;
748
return Ok ( ( ) ) ;
749
}
750
751
752
753
754
let regions = self .base.conflict_regions ( & node) ;
cdb5beba Andy McCall 1 month ago ↶
755
let mut edits = text_edits ( node, & regions.plain, & disk_text) ;
98286406 Andy McCall 1 month ago ↶
756
if ! regions.region_atoms.is_empty ( ) {
757
edits.insert ( 0 , Edit:: Delete { node, atoms: ranges ( & regions.region_atoms) } ) ;
758
}
759
self .edits.extend ( edits) ;
760
self .contributors.extend ( regions.contributors) ;
761
self .resolves = true ;
762
if e.exec ! = expected.mode.exec {
763
if let Some ( old) = mode_atom {
764
self .tree_ops.push ( TreeOp:: SetMode { node, old, mode: Mode { exec: e.exec, opaque: false } } ) ;
765
}
766
}
767
self .statuses.push ( PathStatus { path: path.to_string ( ) , status: ChangeStatus:: Resolved } ) ;
768
return Ok ( ( ) ) ;
769
}
cdb5beba Andy McCall 1 month ago ↶
770
if wants_opaque {
771
772
if let Some ( old) = mode_atom {
773
self .tree_ops.push ( TreeOp:: SetMode { node, old, mode: Mode { exec: e.exec, opaque: true } } ) ;
774
}
775
let live: Vec< _ > = repo_live_keys ( self .base, & node) ;
776
if ! live.is_empty ( ) {
777
self .edits.push ( Edit:: Delete { node, atoms: ranges ( & live) } ) ;
778
}
b7f102fc Andy McCall 1 month ago ↶
779
let blob = self .blob_of ( repo, path, e, nocompress) ? ;
cdb5beba Andy McCall 1 month ago ↶
780
self .edits.push ( Edit:: Replace { node, old: None , new: Some ( blob) } ) ;
781
status = Some ( ChangeStatus:: Modified) ;
782
} else if bytes ! = & disk_text {
783
let old = self .base.live_lines ( & node) ;
784
let edits = text_edits ( node, & old, & disk_text) ;
785
if ! edits.is_empty ( ) {
786
self .edits.extend ( edits) ;
98286406 Andy McCall 1 month ago ↶
787
status = Some ( ChangeStatus:: Modified) ;
788
}
789
}
cdb5beba Andy McCall 1 month ago ↶
790
let switched_to_opaque = wants_opaque;
98286406 Andy McCall 1 month ago ↶
791
if ! switched_to_opaque & & e.exec ! = expected.mode.exec {
792
if let Some ( old) = mode_atom {
793
self .tree_ops.push ( TreeOp:: SetMode { node, old, mode: Mode { exec: e.exec, opaque: false } } ) ;
794
status.get_or_insert ( ChangeStatus:: ModeChanged) ;
795
}
796
}
797
}
798
FileContent:: Opaque { blobs } => {
b7f102fc Andy McCall 1 month ago ↶
799
let new_ref = e.blob.unwrap_or_else ( | | Repo:: blob_ref( & e.content) ) ;
800
if e.kind = = NodeKind:: File & & wants_opaque {
801
802
self .blobs.insert ( path.to_string ( ) , new_ref) ;
803
}
98286406 Andy McCall 1 month ago ↶
804
if blobs.len ( ) > 1 {
805
if ! blobs.contains ( & new_ref) {
806
self .statuses.push ( PathStatus { path: path.to_string ( ) , status: ChangeStatus:: ConflictEdited } ) ;
807
}
808
return Ok ( ( ) ) ;
809
}
810
let current = blobs.first ( ) .copied ( ) ;
811
if current ! = Some ( new_ref) {
cdb5beba Andy McCall 1 month ago ↶
812
if e.kind = = NodeKind:: File & & ! wants_opaque {
98286406 Andy McCall 1 month ago ↶
813
814
if let Some ( old) = mode_atom {
815
self .tree_ops.push ( TreeOp:: SetMode { node, old, mode: Mode { exec: e.exec, opaque: false } } ) ;
816
}
817
self .edits.push ( Edit:: Replace { node, old: current, new: None } ) ;
818
if ! e.content.is_empty ( ) {
819
self .edits.push ( Edit:: Insert {
820
node,
821
after: crate :: objects:: AtomRef:: Start,
822
before: crate :: objects:: AtomRef:: End,
823
lines: split_lines ( & e.content) .iter ( ) .map ( | l | l.to_vec ( ) ) .collect ( ) ,
824
} ) ;
825
}
826
} else {
b7f102fc Andy McCall 1 month ago ↶
827
let blob = self .blob_of ( repo, path, e, nocompress) ? ;
98286406 Andy McCall 1 month ago ↶
828
self .edits.push ( Edit:: Replace { node, old: current, new: Some ( blob) } ) ;
829
}
830
status = Some ( ChangeStatus:: Modified) ;
831
} else if e.kind = = NodeKind:: File & & e.exec ! = expected.mode.exec {
832
if let Some ( old) = mode_atom {
833
self .tree_ops.push ( TreeOp:: SetMode { node, old, mode: Mode { exec: e.exec, opaque: true } } ) ;
834
status = Some ( ChangeStatus:: ModeChanged) ;
835
}
836
}
837
}
838
}
839
if let Some ( s) = status {
840
self .statuses.push ( PathStatus { path: path.to_string ( ) , status: s } ) ;
841
}
842
Ok ( ( ) )
843
}
844
}
845
846
fn repo_live_keys ( base : & State, node : & NodeId) -> Vec< crate:: graph:: AtomKey> {
847
base.live_lines ( node) .into_iter ( ) .map ( | ( k , _) | k ) .collect ( )
848
}
849
850
851
852
pub fn referenced_changes ( change : & Change, base : & State) -> BTreeSet< ChangeHash> {
853
let mut out = BTreeSet:: new( ) ;
854
let opref = | r : & OpRef, out : & mut BTreeSet< ChangeHash> | {
855
if ! r.change.is_zero ( ) {
856
out.insert ( r.change) ;
857
}
858
} ;
859
let mut nodes: Vec< NodeId> = Vec :: new( ) ;
860
for op in & change.tree_ops {
861
match op {
862
TreeOp:: AddNode { parent, .. } => nodes.push ( * parent) ,
863
TreeOp:: RemoveNode { node, placement } => {
864
nodes.push ( * node) ;
865
opref ( placement, & mut out) ;
866
}
867
TreeOp:: Move { node, old, parent, .. } => {
868
nodes.push ( * node) ;
869
nodes.push ( * parent) ;
870
opref ( old, & mut out) ;
871
}
872
TreeOp:: SetMode { node, old, .. } => {
873
nodes.push ( * node) ;
874
opref ( old, & mut out) ;
875
}
876
}
877
}
878
for e in & change.edits {
879
nodes.push ( e.node ( ) ) ;
880
match e {
881
Edit:: Insert { after, before, .. } => {
882
for r in [ after, before] {
883
if let crate :: objects:: AtomRef:: Atom { change, .. } = r {
884
if ! change.is_zero ( ) {
885
out.insert ( * change) ;
886
}
887
}
888
}
889
}
890
Edit:: Delete { atoms, .. } => {
891
for r in atoms {
892
if ! r.change.is_zero ( ) {
893
out.insert ( r.change) ;
894
}
895
}
896
}
897
Edit:: Replace { .. } => { }
898
}
899
}
900
for n in nodes {
901
if let Some ( h) = base.tree ( ) .introducer ( & n) {
902
out.insert ( h) ;
903
}
904
}
905
out
906
}
907
908
909
pub fn added_node_paths ( change : & Change, base_snapshot : & Snapshot) -> HashMap< String, ( NodeId, NodeKind) > {
910
let by_node = base_snapshot.tree.by_node ( ) ;
911
let mut new_names: HashMap< NodeId, ( NodeId, String, NodeKind) > = HashMap:: new( ) ;
912
for op in & change.tree_ops {
913
if let TreeOp:: AddNode { node, parent, name, kind, .. } = op {
914
new_names.insert ( * node, ( * parent, name.clone ( ) , * kind) ) ;
915
}
916
}
917
let mut out = HashMap:: new( ) ;
918
for ( node, ( _ , _ , kind) ) in & new_names {
919
if let Some ( path) = path_of_node ( node, & by_node, & new_names) {
920
out.insert ( path, ( * node, * kind) ) ;
921
}
922
}
923
out
924
}
925
926
fn path_of_node (
927
n : & NodeId,
928
by_node : & HashMap< NodeId, & crate:: tree:: TreeEntry> ,
929
new_names : & HashMap< NodeId, ( NodeId, String, NodeKind) > ,
930
) -> Option< String> {
931
if let Some ( e) = by_node.get ( n) {
932
return Some ( e.path.clone ( ) ) ;
933
}
934
let mut parts = Vec :: new( ) ;
935
let mut cur = * n;
936
loop {
937
if cur.is_root ( ) {
938
break ;
939
}
940
if let Some ( e) = by_node.get ( & cur) {
941
if ! e.path.is_empty ( ) {
942
parts.push ( e.path.clone ( ) ) ;
943
}
944
break ;
945
}
946
let ( parent, name, _ ) = new_names.get ( & cur) ? ;
947
parts.push ( name.clone ( ) ) ;
948
cur = * parent;
949
}
950
parts.reverse ( ) ;
951
Some ( parts.join ( " /" ) )
952
}
953
954
955
pub fn render_change ( change : & Change, base : & State, base_snapshot : & Snapshot) -> String {
956
let by_node = base_snapshot.tree.by_node ( ) ;
957
let mut new_names: HashMap< NodeId, ( NodeId, String, NodeKind) > = HashMap:: new( ) ;
958
for op in & change.tree_ops {
959
if let TreeOp:: AddNode { node, parent, name, kind, .. } = op {
960
new_names.insert ( * node, ( * parent, name.clone ( ) , * kind) ) ;
961
}
962
}
963
let path_of = | n : & NodeId| -> String { path_of_node ( n, & by_node, & new_names) .unwrap_or_else ( | | format! ( " <node {} >" , n.short ( 8 ) ) ) } ;
964
965
let mut out = String :: new( ) ;
966
for op in & change.tree_ops {
967
match op {
968
TreeOp:: AddNode { node, kind, mode, .. } => {
969
out.push_str ( & format! ( " A {} ({:?} {} {} )\n " , path_of ( node) , kind, if mode.exec { " , exec" } else { " " } , if mode.opaque { " , opaque" } else { " " } ) ) ;
970
}
971
TreeOp:: RemoveNode { node, .. } => out.push_str ( & format! ( " D {} \n " , path_of ( node) ) ) ,
972
TreeOp:: Move { node, name, .. } => out.push_str ( & format! ( " R {} -> {} \n " , path_of ( node) , name) ) ,
973
TreeOp:: SetMode { node, mode, .. } => {
974
out.push_str ( & format! ( " M {} mode: exec={} opaque={} \n " , path_of ( node) , mode.exec, mode.opaque) ) ;
975
}
976
}
977
}
978
let mut last_node: Option< NodeId> = None ;
979
for e in & change.edits {
980
let n = e.node ( ) ;
981
if last_node ! = Some ( n) {
982
out.push_str ( & format! ( " --- {} \n " , path_of ( & n) ) ) ;
983
last_node = Some ( n) ;
984
}
985
match e {
986
Edit:: Insert { lines, .. } => {
987
for l in lines {
988
out.push ( ' +' ) ;
989
out.push_str ( & String :: from_utf8_lossy( l) ) ;
990
if ! l.ends_with ( b " \n " ) {
991
out.push ( ' \n ' ) ;
992
}
993
}
994
}
995
Edit:: Delete { atoms, .. } => {
996
for r in atoms {
997
for i in r.from.. r.to {
998
let content = base.file ( & n) .and_then ( | g | g.content ( & ( r.change, i) ) ) .unwrap_or ( b " <unknown>\n " ) ;
999
out.push ( ' -' ) ;
1000
out.push_str ( & String :: from_utf8_lossy( content) ) ;
1001
if ! content.ends_with ( b " \n " ) {
1002
out.push ( ' \n ' ) ;
1003
}
1004
}
1005
}
1006
}
1007
Edit:: Replace { old, new, .. } => {
1008
out.push_str ( & format! (
1009
" opaque content {} -> {} \n " ,
1010
old.map ( | o | o.short ( 12 ) ) .unwrap_or_else ( | | " none" .into ( ) ) ,
1011
new.map ( | o | o.short ( 12 ) ) .unwrap_or_else ( | | " none" .into ( ) )
1012
) ) ;
1013
}
1014
}
1015
}
1016
out
1017
}
6e723b81 Andy McCall 1 month ago ↶
1018
1019
1020
1021
1022
const RENAME_THRESHOLD : u32 = 50 ;
1023
const RENAME_LIMIT : usize = 10_000 ;
1024
1025
fn detect_renames ( base_snapshot : & Snapshot, fs_entries : & BTreeMap< String, FsEntry> , in_scope : impl Fn( &str ) -> bool ) -> Vec< ( String, String) > {
1026
let removed: Vec< ( & String, & crate:: state:: FileState) > = base_snapshot
1027
.files
1028
.iter ( )
1029
.filter ( | ( p , f ) | f.kind ! = NodeKind:: Dir & & ! fs_entries.contains_key ( * p) & & in_scope ( p) & & ! f.is_conflicted ( ) )
1030
.collect ( ) ;
1031
let added: Vec< ( & String, & FsEntry) > = fs_entries.iter ( ) .filter ( | ( p , e ) | e.kind ! = NodeKind:: Dir & & ! base_snapshot.files.contains_key ( * p) ) .collect ( ) ;
1032
if removed.is_empty ( ) | | added.is_empty ( ) {
1033
return vec! [ ] ;
1034
}
1035
let exact_only = removed.len ( ) * added.len ( ) > RENAME_LIMIT ;
1036
let old_content = | f : & crate:: state:: FileState| -> Option< Vec< u8 > > {
1037
match & f.content {
1038
FileContent:: Text { bytes, .. } => Some ( bytes.clone ( ) ) ,
1039
_ => None ,
1040
}
1041
} ;
1042
let mut scored: Vec< ( u32 , usize , usize ) > = Vec :: new( ) ;
1043
for ( ri, ( _ , f) ) in removed.iter ( ) .enumerate ( ) {
1044
let old = old_content ( f) ;
1045
let old_blob = match & f.content {
1046
FileContent:: Opaque { blobs } if blobs.len ( ) = = 1 => Some ( blobs[ 0 ] ) ,
1047
_ => None ,
1048
} ;
1049
for ( ai, ( _ , e) ) in added.iter ( ) .enumerate ( ) {
1050
if e.kind ! = f.kind {
1051
continue ;
1052
}
1053
let score = match ( & old, old_blob) {
1054
( Some ( o) , _ ) => {
1055
if exact_only {
1056
if * o = = e.content { 100 } else { 0 }
1057
} else {
1058
similarity ( o, & e.content)
1059
}
1060
}
b7f102fc Andy McCall 1 month ago ↶
1061
( None , Some ( b) ) if e.blob.unwrap_or_else ( | | Repo:: blob_ref( & e.content) ) = = b => 100 ,
6e723b81 Andy McCall 1 month ago ↶
1062
_ => 0 ,
1063
} ;
1064
if score > = RENAME_THRESHOLD {
1065
scored.push ( ( score, ri, ai) ) ;
1066
}
1067
}
1068
}
1069
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 ) ) ) ;
1070
let mut used_r = vec! [ false ; removed.len ( ) ] ;
1071
let mut used_a = vec! [ false ; added.len ( ) ] ;
1072
let mut out = Vec :: new( ) ;
1073
for ( _ , ri, ai) in scored {
1074
if used_r[ ri] | | used_a[ ai] {
1075
continue ;
1076
}
1077
used_r[ ri] = true ;
1078
used_a[ ai] = true ;
1079
out.push ( ( removed[ ri] .0. clone ( ) , added[ ai] .0. clone ( ) ) ) ;
1080
}
1081
out
1082
}
1083
1084
1085
1086
pub fn similarity ( a : & [u8 ], b : & [u8 ]) -> u32 {
1087
if a = = b {
1088
return 100 ;
1089
}
1090
if a.is_empty ( ) | | b.is_empty ( ) {
1091
return 0 ;
1092
}
1093
let mut counts: HashMap< & [ u8 ] , usize > = HashMap:: new( ) ;
1094
for l in split_lines ( a) {
1095
* counts.entry ( l) .or_default ( ) + = 1 ;
1096
}
1097
let mut shared = 0 usize ;
1098
for l in split_lines ( b) {
1099
if let Some ( n) = counts.get_mut ( l) {
1100
if * n > 0 {
1101
* n - = 1 ;
1102
shared + = l.len ( ) ;
1103
}
1104
}
1105
}
1106
( shared * 100 / a.len ( ) .max ( b.len ( ) ) ) as u32
1107
}
1108