98286406 Andy McCall 1 month ago ↶
1
2
3
use super :: * ;
75167672 Andy McCall 1 month ago
4
use crate :: protocol:: SignedPolicy;
957fee30 Andy McCall 1 month ago ↶
5
use crate :: repo:: { BisectStep, Selection} ;
98286406 Andy McCall 1 month ago ↶
6
7
8
9
fn hermetic ( ) {
10
static ONCE : std:: sync:: Once = std:: sync:: Once:: new( ) ;
11
static DIR : std:: sync:: OnceLock< tempfile:: TempDir> = std:: sync:: OnceLock:: new( ) ;
12
ONCE .call_once ( | | {
13
let d = tempfile:: tempdir( ) .unwrap ( ) ;
14
std:: env:: set_var( " OXY_HOME" , d.path ( ) ) ;
15
DIR .set ( d) .ok ( ) ;
16
} ) ;
17
}
18
19
fn write ( root : & Path, rel : & str , content : & [u8 ]) {
20
let p = root.join ( rel) ;
21
fs:: create_dir_all( p.parent ( ) .unwrap ( ) ) .unwrap ( ) ;
22
fs:: write( p, content) .unwrap ( ) ;
23
}
24
25
fn statuses ( s : & SnapshotResult) -> Vec< ( String, ChangeStatus) > {
75167672 Andy McCall 1 month ago
26
s.statuses
27
.iter ( )
28
.map ( | p | ( p.path.clone ( ) , p.status) )
29
.collect ( )
98286406 Andy McCall 1 month ago ↶
30
}
31
32
# [ test ]
33
fn init_snapshot_commit_log_undo ( ) {
34
hermetic ( ) ;
35
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
36
let root = dir.path ( ) ;
37
let repo = Repo:: init( root) .unwrap ( ) ;
38
assert! ( Repo:: init( root) .is_err ( ) ) ;
39
let ops = repo.op_log ( ) .unwrap ( ) ;
40
assert_eq! ( ops.len ( ) , 1 ) ;
41
assert_eq! ( ops[ 0 ] .1. command, " oxy init" ) ;
42
43
44
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
45
assert! ( ! s.changed) ;
46
assert_eq! ( repo.op_log ( ) .unwrap ( ) .len ( ) , 1 ) ;
47
48
write ( root, " src/main.rs" , b " fn main() {}\n " ) ;
49
write ( root, " README.md" , b " # Hello\n " ) ;
50
write ( root, " .oxyignore" , b " /target\n " ) ;
51
write ( root, " target/junk.o" , b " \0 \0 \0 " ) ;
52
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
53
assert! ( s.changed) ;
54
assert_eq! (
55
statuses ( & s) ,
56
vec! [
57
( " .oxyignore" .into ( ) , ChangeStatus:: Added) ,
58
( " README.md" .into ( ) , ChangeStatus:: Added) ,
59
( " src/main.rs" .into ( ) , ChangeStatus:: Added) ,
60
]
61
) ;
62
assert_eq! ( repo.op_log ( ) .unwrap ( ) .len ( ) , 2 ) ;
63
64
let s2 = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
65
assert! ( ! s2.changed) ;
66
assert_eq! ( s2.wc_hash, s.wc_hash) ;
67
assert_eq! ( repo.op_log ( ) .unwrap ( ) .len ( ) , 2 ) ;
68
69
let h1 = repo.commit ( " Initial import" , & [ ] ) .unwrap ( ) ;
70
let c1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) ;
71
assert_eq! ( c1.kind, ChangeKind:: Normal) ;
72
assert! ( c1.deps.is_empty ( ) , " first change has nothing to depend on" ) ;
73
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
74
let ( wc_hash, wc) = repo.working_copy ( ) .unwrap ( ) ;
75
assert_eq! ( wc.deps, vec! [ h1] ) ;
76
assert! ( wc.edits.is_empty ( ) ) ;
77
assert! ( repo.commit ( " nothing" , & [ ] ) .is_err ( ) ) ;
78
79
75167672 Andy McCall 1 month ago
80
write (
81
root,
82
" src/main.rs" ,
83
b " fn main() {\n println!(\" hi\" );\n }\n " ,
84
) ;
98286406 Andy McCall 1 month ago ↶
85
fs:: remove_file( root.join ( " README.md" ) ) .unwrap ( ) ;
86
write ( root, " logo.png" , b " \x89 PNG\0 \0 \x01 " ) ;
87
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
88
assert_eq! (
89
statuses ( & s) ,
90
vec! [
91
( " README.md" .into ( ) , ChangeStatus:: Removed) ,
92
( " logo.png" .into ( ) , ChangeStatus:: Added) ,
93
( " src/main.rs" .into ( ) , ChangeStatus:: Modified) ,
94
]
95
) ;
96
assert_ne! ( s.wc_hash, wc_hash) ;
97
98
assert_eq! ( s.wc.change_id, wc.change_id) ;
99
assert! ( s.wc.deps.contains ( & h1) ) ;
100
101
let h2 = repo.commit ( " Second" , & [ ] ) .unwrap ( ) ;
102
let c2 = repo.store ( ) .get_change ( & h2) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
103
assert_eq! (
104
c2.deps,
105
vec! [ h1] ,
106
" everything it touches was introduced by h1"
107
) ;
98286406 Andy McCall 1 month ago ↶
108
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
109
110
111
let st = repo.load_state ( & [ h2] ) .unwrap ( ) ;
112
let snap = st.materialize ( ) ;
113
assert! ( ! snap.is_conflicted ( ) ) ;
75167672 Andy McCall 1 month ago
114
assert_eq! (
115
snap.files.keys ( ) .cloned ( ) .collect:: < Vec< _ > > ( ) ,
116
[ " .oxyignore" , " logo.png" , " src" , " src/main.rs" ]
117
) ;
118
assert! (
119
matches! ( & snap.files[ " src/main.rs" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " fn main() {\n println!(\" hi\" );\n }\n " )
120
) ;
98286406 Andy McCall 1 month ago ↶
121
assert! ( snap.files[ " logo.png" ] .mode.opaque) ;
122
if let FileContent:: Opaque { blobs } = & snap.files[ " logo.png" ] .content {
123
assert_eq! ( repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) , b " \x89 PNG\0 \0 \x01 " ) ;
124
} else {
125
panic! ( " logo should be opaque" ) ;
126
}
127
128
129
let log = repo.log ( ) .unwrap ( ) ;
130
assert_eq! ( log.len ( ) , 3 ) ;
131
assert! ( log[ 0 ] .is_working_copy) ;
132
assert_eq! ( log[ 1 ] .hash, h2) ;
133
assert! ( log[ 1 ] .is_head) ;
134
assert_eq! ( log[ 2 ] .hash, h1) ;
135
let d = repo.describe_change ( & h2) .unwrap ( ) ;
136
assert! ( d.contains ( " D README.md" ) , " {d}" ) ;
0912a033 Andy McCall 1 month ago ↶
137
assert! (
138
d.lines ( )
139
.any ( | l | l.starts_with ( " M " ) & & l.ends_with ( " main.rs" ) ) ,
140
" a modified file is in the summary too: {d}"
141
) ;
98286406 Andy McCall 1 month ago ↶
142
assert! ( d.contains ( " + println!(\" hi\" );" ) , " {d}" ) ;
143
assert! ( d.contains ( " -fn main() {}" ) , " {d}" ) ;
144
145
146
assert_eq! ( repo.resolve_change ( & c2.change_id.short ( 6 ) ) .unwrap ( ) , h2) ;
75167672 Andy McCall 1 month ago
147
assert_eq! (
148
repo.resolve_change ( & format! ( " #{} " , h1.short ( 8 ) ) ) .unwrap ( ) ,
149
h1
150
) ;
98286406 Andy McCall 1 month ago ↶
151
assert! ( repo.resolve_change ( " zzzz" ) .is_err ( ) ) ;
152
153
154
repo.undo ( ) .unwrap ( ) ;
155
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
156
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
157
assert! (
158
! s.changed,
159
" files on disk still match the pre-commit working copy"
160
) ;
98286406 Andy McCall 1 month ago ↶
161
assert_eq! ( s.statuses.len ( ) , 3 ) ;
162
}
163
164
165
fn conflicted_repo ( ) -> ( tempfile:: TempDir, Repo, ChangeHash, ChangeHash, ChangeHash) {
166
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
167
let root = dir.path ( ) ;
168
let repo = Repo:: init( root) .unwrap ( ) ;
169
write ( root, " f.txt" , b " a\n b\n c\n " ) ;
170
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
171
write ( root, " f.txt" , b " a\n B\n c\n " ) ;
172
let h2 = repo.commit ( " upper" , & [ ] ) .unwrap ( ) ;
173
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
174
write ( root, " f.txt" , b " a\n X\n c\n " ) ;
175
let h3 = repo.commit ( " ex" , & [ ] ) .unwrap ( ) ;
176
( dir, repo, h1, h2, h3)
177
}
178
3652104a Andy McCall 17 days ago ↶
179
180
181
98286406 Andy McCall 1 month ago ↶
182
# [ test ]
3652104a Andy McCall 17 days ago ↶
183
fn marker_like_text_does_not_block_a_resolution ( ) {
184
hermetic ( ) ;
185
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
186
let root = dir.path ( ) ;
187
let repo = Repo:: init( root) .unwrap ( ) ;
188
let example = " ```\n <<<<<<< oxy conflict 1 of 2, side 1: change k7m2p9 \" Add retry loop\" \n ... lines ...\n ======= oxy conflict 1 of 2, side 2: change q3x8w1 \" Refactor client\" \n >>>>>>> oxy conflict 1 of 2 end\n ```\n " ;
189
write (
190
root,
191
" doc.md" ,
192
format! ( " intro\n {example} b\n end\n " ) .as_bytes ( ) ,
193
) ;
194
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
195
write (
196
root,
197
" doc.md" ,
198
format! ( " intro\n {example} B\n end\n " ) .as_bytes ( ) ,
199
) ;
200
repo.commit ( " upper" , & [ ] ) .unwrap ( ) ;
201
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
202
write (
203
root,
204
" doc.md" ,
205
format! ( " intro\n {example} X\n end\n " ) .as_bytes ( ) ,
206
) ;
207
repo.commit ( " ex" , & [ ] ) .unwrap ( ) ;
208
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
209
.unwrap ( ) ;
210
let marked = fs:: read_to_string( root.join ( " doc.md" ) ) .unwrap ( ) ;
211
assert! ( marked.contains ( " oxy conflict 1 of 1" ) , " {marked}" ) ;
212
213
write ( root, " doc.md" , marked.replace ( " B\n " , " BB\n " ) .as_bytes ( ) ) ;
214
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
215
assert_eq! (
216
statuses ( & s) ,
217
vec! [ ( " doc.md" .into ( ) , ChangeStatus:: ConflictEdited) ]
218
) ;
219
220
write (
221
root,
222
" doc.md" ,
223
format! ( " intro\n {example} X\n end\n " ) .as_bytes ( ) ,
224
) ;
225
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
226
assert_eq! (
227
statuses ( & s) ,
228
vec! [ ( " doc.md" .into ( ) , ChangeStatus:: Resolved) ]
229
) ;
230
let h = repo.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
231
assert_eq! (
232
repo.store ( ) .get_change ( & h) .unwrap ( ) .kind,
233
ChangeKind:: Resolution
234
) ;
235
assert! ( ! repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) .is_conflicted ( ) ) ;
236
}
237
238
# [ test ]
98286406 Andy McCall 1 month ago ↶
239
fn concurrent_edits_conflict_and_can_be_resolved ( ) {
240
hermetic ( ) ;
241
let ( dir, repo, h1, h2, h3) = conflicted_repo ( ) ;
242
let root = dir.path ( ) ;
243
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
244
assert_eq! ( heads, [ h2, h3] .into_iter ( ) .collect ( ) ) ;
245
let st = repo.load_state ( & [ h2, h3] ) .unwrap ( ) ;
246
assert! ( st.materialize ( ) .is_conflicted ( ) ) ;
247
248
75167672 Andy McCall 1 month ago
249
let r = repo
250
.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
251
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
252
assert! ( r.conflicted) ;
253
let marked = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
254
assert! (
255
marked.starts_with ( " a\n <<<<<<< oxy conflict 1 of 1" ) ,
256
" {marked}"
257
) ;
258
assert! (
259
marked.contains ( " B\n " ) & & marked.contains ( " X\n " ) & & marked.ends_with ( " end\n c\n " ) ,
260
" {marked}"
261
) ;
98286406 Andy McCall 1 month ago ↶
262
assert! ( has_conflict_markers ( marked.as_bytes ( ) ) ) ;
263
264
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
265
assert! ( s.statuses.is_empty ( ) ) ;
266
assert! ( ! s.resolves) ;
267
268
269
write ( root, " f.txt" , marked.replace ( " B\n " , " BB\n " ) .as_bytes ( ) ) ;
270
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
271
assert_eq! (
272
statuses ( & s) ,
273
vec! [ ( " f.txt" .into ( ) , ChangeStatus:: ConflictEdited) ]
274
) ;
98286406 Andy McCall 1 month ago ↶
275
assert! ( repo.commit ( " no" , & [ ] ) .is_err ( ) ) ;
276
277
278
write ( root, " f.txt" , b " a\n X\n c\n d\n " ) ;
279
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
280
assert_eq! ( statuses ( & s) , vec! [ ( " f.txt" .into ( ) , ChangeStatus:: Resolved) ] ) ;
281
assert! ( s.resolves) ;
282
let h4 = repo.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
283
let c4 = repo.store ( ) .get_change ( & h4) .unwrap ( ) ;
284
assert_eq! ( c4.kind, ChangeKind:: Resolution) ;
285
let mut want = vec! [ h1, h2, h3] ;
286
want.sort ( ) ;
75167672 Andy McCall 1 month ago
287
assert_eq! (
288
c4.deps, want,
289
" a resolution depends on exactly the contributors and what it references"
290
) ;
98286406 Andy McCall 1 month ago ↶
291
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h4] ) ;
292
let snap = repo.load_state ( & [ h4] ) .unwrap ( ) .materialize ( ) ;
293
assert! ( ! snap.is_conflicted ( ) ) ;
75167672 Andy McCall 1 month ago
294
assert! (
295
matches! ( & snap.files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n X\n c\n d\n " )
296
) ;
98286406 Andy McCall 1 month ago ↶
297
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
298
299
300
let mut st = State:: new( ) ;
301
for h in [ h4, h3, h1, h2] {
302
st.add_unchecked ( repo.store ( ) .get_change ( & h) .unwrap ( ) ) ;
303
}
304
st.validate ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
305
assert! (
306
matches! ( & st.materialize ( ) .files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n X\n c\n d\n " )
307
) ;
98286406 Andy McCall 1 month ago ↶
308
}
309
69953b50 Andy McCall 17 days ago ↶
310
311
312
98286406 Andy McCall 1 month ago ↶
313
# [ test ]
69953b50 Andy McCall 17 days ago ↶
314
fn resolutions_and_amends_take_paths ( ) {
315
hermetic ( ) ;
316
let ( dir, repo, _h1, _h2, _h3) = conflicted_repo ( ) ;
317
let root = dir.path ( ) ;
318
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
319
.unwrap ( ) ;
320
321
write ( root, " f.txt" , b " a\n X\n c\n " ) ;
322
write ( root, " notes.txt" , b " pending\n " ) ;
323
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
324
assert! ( s.resolves) ;
325
let err = repo
326
.commit_selection_partial ( " resolve" , & [ ] , & Selection:: Paths( vec! [ " notes.txt" .into ( ) ] ) )
327
.unwrap_err ( )
328
.to_string ( ) ;
329
assert! (
330
err.contains ( " takes every file it resolves" ) & & err.contains ( " f.txt" ) ,
331
" {err}"
332
) ;
333
let ( h4, partial) = repo
334
.commit_selection_partial ( " resolve" , & [ ] , & Selection:: Paths( vec! [ " f.txt" .into ( ) ] ) )
335
.unwrap ( ) ;
336
assert! ( partial) ;
337
assert_eq! (
338
repo.store ( ) .get_change ( & h4) .unwrap ( ) .kind,
339
ChangeKind:: Resolution
340
) ;
341
assert! ( ! repo
342
.load_state ( & [ h4] )
343
.unwrap ( )
344
.materialize ( )
345
.is_conflicted ( ) ) ;
346
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
347
assert_eq! (
348
statuses ( & s) ,
349
vec! [ ( " notes.txt" .into ( ) , ChangeStatus:: Added) ] ,
350
" the other work waits"
351
) ;
352
353
354
355
let h5 = repo.commit ( " notes" , & [ h4] ) .unwrap ( ) ;
356
let id5 = repo.store ( ) .get_change ( & h5) .unwrap ( ) .change_id;
357
write ( root, " notes.txt" , b " pending\n more\n " ) ;
358
write ( root, " other.txt" , b " later\n " ) ;
359
let h6 = repo
360
.amend_paths ( Some ( " notes, amended" ) , & [ " notes.txt" .into ( ) ] )
361
.unwrap ( ) ;
362
let c6 = repo.store ( ) .get_change ( & h6) .unwrap ( ) ;
363
assert_eq! ( ( c6.change_id, c6.message.as_str ( ) ) , ( id5, " notes, amended" ) ) ;
364
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h6] ) ;
365
let snap6 = repo.load_state ( & [ h6] ) .unwrap ( ) .materialize ( ) ;
366
assert! (
367
matches! ( & snap6.files[ " notes.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " pending\n more\n " )
368
) ;
369
assert! (
370
! snap6.files.contains_key ( " other.txt" ) ,
371
" other.txt stays out"
372
) ;
373
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
374
assert_eq! (
375
statuses ( & s) ,
376
vec! [ ( " other.txt" .into ( ) , ChangeStatus:: Added) ]
377
) ;
378
}
379
380
# [ test ]
98286406 Andy McCall 1 month ago ↶
381
fn resolution_may_keep_both_sides_or_neither ( ) {
382
hermetic ( ) ;
383
let ( dir, repo, _h1, _h2, _h3) = conflicted_repo ( ) ;
384
let root = dir.path ( ) ;
75167672 Andy McCall 1 month ago
385
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
386
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
387
388
write ( root, " f.txt" , b " a\n X\n B\n c\n " ) ;
389
let h = repo.commit ( " both" , & [ ] ) .unwrap ( ) ;
390
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
391
assert! ( ! snap.is_conflicted ( ) ) ;
75167672 Andy McCall 1 month ago
392
assert! (
393
matches! ( & snap.files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n X\n B\n c\n " )
394
) ;
98286406 Andy McCall 1 month ago ↶
395
396
repo.undo ( ) .unwrap ( ) ;
397
write ( root, " f.txt" , b " a\n c\n " ) ;
398
let h = repo.commit ( " neither" , & [ ] ) .unwrap ( ) ;
399
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
400
assert! ( ! snap.is_conflicted ( ) ) ;
75167672 Andy McCall 1 month ago
401
assert! (
402
matches! ( & snap.files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n c\n " )
403
) ;
98286406 Andy McCall 1 month ago ↶
404
}
405
406
# [ test ]
407
fn name_conflict_resolved_by_removing_a_candidate ( ) {
408
hermetic ( ) ;
409
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
410
let root = dir.path ( ) ;
411
let repo = Repo:: init( root) .unwrap ( ) ;
412
write ( root, " keep.txt" , b " k\n " ) ;
413
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
414
write ( root, " new.txt" , b " one\n " ) ;
415
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
416
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
417
write ( root, " new.txt" , b " two\n " ) ;
418
repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
419
let r = repo
420
.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
421
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
422
assert! ( r.conflicted) ;
75167672 Andy McCall 1 month ago
423
assert! (
424
root.join ( " new.txt.oxy-conflict-1" ) .exists ( )
425
& & root.join ( " new.txt.oxy-conflict-2" ) .exists ( )
426
) ;
98286406 Andy McCall 1 month ago ↶
427
assert! ( ! root.join ( " new.txt" ) .exists ( ) ) ;
428
429
75167672 Andy McCall 1 month ago
430
let ( keep, drop) = if fs:: read( root.join ( " new.txt.oxy-conflict-1" ) ) .unwrap ( ) = = b " two\n " {
431
( 1 , 2 )
432
} else {
433
( 2 , 1 )
434
} ;
98286406 Andy McCall 1 month ago ↶
435
fs:: remove_file( root.join ( format! ( " new.txt.oxy-conflict-{drop} " ) ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
436
fs:: rename(
437
root.join ( format! ( " new.txt.oxy-conflict-{keep} " ) ) ,
438
root.join ( " new.txt" ) ,
439
)
440
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
441
let h = repo.commit ( " pick two" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
442
assert_eq! (
443
repo.store ( ) .get_change ( & h) .unwrap ( ) .kind,
444
ChangeKind:: Resolution
445
) ;
98286406 Andy McCall 1 month ago ↶
446
447
448
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
449
assert_eq! ( heads, [ h1, h] .into_iter ( ) .collect ( ) ) ;
75167672 Andy McCall 1 month ago
450
let snap = repo
451
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
452
.unwrap ( )
453
.materialize ( ) ;
98286406 Andy McCall 1 month ago ↶
454
assert! ( ! snap.is_conflicted ( ) , " {:?}" , snap.tree.conflicts) ;
75167672 Andy McCall 1 month ago
455
assert_eq! (
456
snap.files.keys ( ) .cloned ( ) .collect:: < Vec< _ > > ( ) ,
457
[ " keep.txt" , " new.txt" ]
458
) ;
459
assert! (
460
matches! ( & snap.files[ " new.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " two\n " )
461
) ;
98286406 Andy McCall 1 month ago ↶
462
}
463
c2e1d090 Andy McCall 17 days ago
464
465
466
467
fn merged_additions (
468
side : impl Fn( &Path , bool ) ,
469
) -> ( tempfile:: TempDir, Repo, Vec< ChangeHash> , [ChangeHash; 3]) {
470
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
471
let root = dir.path ( ) .to_path_buf ( ) ;
472
let mut repo = Repo:: init( & root) .unwrap ( ) ;
473
write ( & root, " keep.txt" , b " k\n " ) ;
474
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
475
repo.bookmark_create ( " side" , None ) .unwrap ( ) ;
476
side ( & root, false ) ;
477
let ha = repo.commit ( " add on main" , & [ ] ) .unwrap ( ) ;
478
repo.bookmark_switch ( " side" , false ) .unwrap ( ) ;
479
side ( & root, true ) ;
480
let hb = repo.commit ( " add on side" , & [ ] ) .unwrap ( ) ;
481
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
482
let m = repo.merge ( " side" ) .unwrap ( ) ;
483
assert! ( m.changed & & m.checked_out) ;
484
( dir, repo, m.heads, [ h1, ha, hb] )
485
}
486
487
fn conflict_copies ( root : & Path) -> Vec< String> {
488
let mut out: Vec< String> = fs:: read_dir( root)
489
.unwrap ( )
490
.map ( | e | e.unwrap ( ) .file_name ( ) .to_string_lossy ( ) .into_owned ( ) )
491
.filter ( | n | n.contains ( " .oxy-conflict-" ) )
492
.collect ( ) ;
493
out.sort ( ) ;
494
out
495
}
496
497
498
499
500
# [ test ]
501
fn identical_additions_unify_on_merge ( ) {
502
hermetic ( ) ;
503
let ( dir, repo, heads, [ h1, ha, hb] ) =
504
merged_additions ( | root , _| write ( root, " x.txt" , b " same\n lines\n " ) ) ;
505
let root = dir.path ( ) ;
506
507
let st = repo.load_state ( & heads) .unwrap ( ) ;
508
assert! ( st
509
.tree ( )
510
.view ( )
511
.conflicts
512
.iter ( )
513
.any ( | c | matches! ( c, crate :: tree:: TreeConflict:: Name { .. } ) ) ) ;
514
let snap = st.materialize ( ) ;
515
assert! ( ! snap.is_conflicted ( ) , " {:?}" , snap.tree.conflicts) ;
516
assert_eq! (
517
snap.files.keys ( ) .cloned ( ) .collect:: < Vec< _ > > ( ) ,
518
[ " keep.txt" , " x.txt" ]
519
) ;
520
assert! (
521
matches! ( & snap.files[ " x.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " same\n lines\n " )
522
) ;
523
assert_eq! ( snap.tree.unified.len ( ) , 1 ) ;
524
525
assert_eq! ( fs:: read( root.join ( " x.txt" ) ) .unwrap ( ) , b " same\n lines\n " ) ;
526
assert! (
527
conflict_copies ( root) .is_empty ( ) ,
528
" {:?}" ,
529
conflict_copies ( root)
530
) ;
531
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
532
assert! ( s.statuses.is_empty ( ) , " {:?}" , statuses ( & s) ) ;
533
assert! ( ! s.changed & & ! s.resolves) ;
534
assert! ( ! s.base_snapshot.is_conflicted ( ) ) ;
535
assert! ( s.wc.tree_ops.is_empty ( ) & & s.wc.edits.is_empty ( ) ) ;
536
let json = repo.status_json ( & s) .unwrap ( ) ;
537
assert! ( json.contains ( " \" conflicted\" :false" ) , " {json}" ) ;
538
assert! ( repo.commit ( " nothing" , & [ ] ) .is_err ( ) ) ;
539
assert_eq! ( repo.file_at ( " x.txt" , None ) .unwrap ( ) , b " same\n lines\n " ) ;
540
541
let mut peer = State:: new( ) ;
542
for h in [ hb, ha, h1] {
543
peer.add_unchecked ( repo.store ( ) .get_change ( & h) .unwrap ( ) ) ;
544
}
545
peer.validate ( ) .unwrap ( ) ;
546
let ps = peer.materialize ( ) ;
547
assert! ( ! ps.is_conflicted ( ) ) ;
548
assert_eq! ( ps.tree.entries[ " x.txt" ] , snap.tree.entries[ " x.txt" ] ) ;
549
assert_eq! ( ps.tree.unified, snap.tree.unified) ;
550
551
let log: Vec< ChangeHash> = repo
552
.log_for_path ( " x.txt" )
553
.unwrap ( )
554
.iter ( )
555
.map ( | e | e.hash )
556
.collect ( ) ;
557
assert! ( log.contains ( & ha) & & log.contains ( & hb) , " {log:?}" ) ;
558
559
560
write ( root, " x.txt" , b " same\n edited\n " ) ;
561
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
562
assert_eq! ( statuses ( & s) , vec! [ ( " x.txt" .into ( ) , ChangeStatus:: Modified) ] ) ;
563
let he = repo.commit ( " edit x" , & [ ] ) .unwrap ( ) ;
564
let c = repo.store ( ) .get_change ( & he) .unwrap ( ) ;
565
assert_eq! ( c.kind, ChangeKind:: Normal) ;
566
let ( dup, _ ) = snap.tree.unified.iter ( ) .next ( ) .unwrap ( ) ;
567
assert! (
568
c.tree_ops
569
.iter ( )
570
.any ( | o | matches! ( o, TreeOp:: RemoveNode { node, .. } if node = = dup) ) ,
571
" {:?}" ,
572
c.tree_ops
573
) ;
574
let d = repo.describe_change ( & he) .unwrap ( ) ;
575
assert! ( ! d.contains ( " D x.txt" ) , " {d}" ) ;
576
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
577
assert! (
578
st.tree ( ) .view ( ) .conflicts.is_empty ( ) ,
579
" nothing left to unify"
580
) ;
581
let snap = st.materialize ( ) ;
582
assert! ( ! snap.is_conflicted ( ) ) ;
583
assert! (
584
matches! ( & snap.files[ " x.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " same\n edited\n " )
585
) ;
586
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
587
588
589
fs:: remove_file( root.join ( " x.txt" ) ) .unwrap ( ) ;
590
repo.commit ( " drop x" , & [ ] ) .unwrap ( ) ;
591
let snap = repo
592
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
593
.unwrap ( )
594
.materialize ( ) ;
595
assert! ( ! snap.is_conflicted ( ) , " {:?}" , snap.tree.conflicts) ;
596
assert_eq! ( snap.files.keys ( ) .cloned ( ) .collect:: < Vec< _ > > ( ) , [ " keep.txt" ] ) ;
597
assert! ( ! root.join ( " x.txt" ) .exists ( ) ) ;
598
}
599
600
601
602
# [ test ]
603
fn unified_additions_stay_one_when_removed_renamed_or_committed_alone ( ) {
604
hermetic ( ) ;
605
let add = | root : & Path, _: bool | write ( root, " x.txt" , b " same\n " ) ;
606
for how in [ " remove" , " rename" , " path" , " hunk" ] {
607
let ( dir, repo, _ , _ ) = merged_additions ( add) ;
608
let root = dir.path ( ) ;
609
match how {
610
" remove" => fs:: remove_file( root.join ( " x.txt" ) ) .unwrap ( ) ,
611
" rename" => fs:: rename( root.join ( " x.txt" ) , root.join ( " y.txt" ) ) .unwrap ( ) ,
612
_ => {
613
write ( root, " x.txt" , b " same\n more\n " ) ;
614
write ( root, " other.txt" , b " o\n " ) ;
615
}
616
}
617
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
618
let h = match how {
619
" path" => repo
620
.commit_selection ( " x alone" , & [ ] , & Selection:: Paths( vec! [ " x.txt" .into ( ) ] ) )
621
.unwrap ( ) ,
622
" hunk" => {
623
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
624
625
assert! ( hunks.iter ( ) .all ( | h | ! h.text.starts_with ( " D " ) ) , " {hunks:?}" ) ;
626
let x: Vec< usize > = hunks
627
.iter ( )
628
.filter ( | h | h.path = = " x.txt" )
629
.map ( | h | h.index )
630
.collect ( ) ;
631
assert! ( ! x.is_empty ( ) ) ;
632
repo.commit_selection ( " x hunks" , & [ ] , & Selection:: Hunks( x) )
633
.unwrap ( )
634
}
635
_ => repo.commit ( how, & [ ] ) .unwrap ( ) ,
636
} ;
637
assert! ( ! s.base_snapshot.is_conflicted ( ) ) ;
638
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
639
assert! ( heads.contains ( & h) ) ;
640
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
641
assert! ( ! snap.is_conflicted ( ) , " {how}: {:?}" , snap.tree.conflicts) ;
642
let want: & [ & str ] = match how {
643
" remove" => & [ " keep.txt" ] ,
644
" rename" => & [ " keep.txt" , " y.txt" ] ,
645
_ => & [ " keep.txt" , " x.txt" ] ,
646
} ;
647
assert_eq! (
648
snap.files.keys ( ) .cloned ( ) .collect:: < Vec< _ > > ( ) ,
649
want,
650
" {how}"
651
) ;
652
assert! ( conflict_copies ( root) .is_empty ( ) , " {how}" ) ;
653
}
654
}
655
656
657
658
# [ test ]
659
fn additions_that_differ_still_conflict ( ) {
660
hermetic ( ) ;
661
# [ cfg ( unix ) ]
662
use std:: os:: unix:: fs:: PermissionsExt;
663
let content =
664
| root : & Path, side : bool | write ( root, " x.txt" , if side { b " two\n " } else { b " one\n " } ) ;
665
let mode = | root : & Path, side : bool | {
666
write ( root, " x.txt" , b " same\n " ) ;
667
# [ cfg ( unix ) ]
668
if side {
669
fs:: set_permissions( root.join ( " x.txt" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
670
}
671
} ;
672
let check = | what : & str , side : & dyn Fn( & Path, bool ) | {
673
let ( dir, repo, heads, _ ) = merged_additions ( side) ;
674
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
675
assert! ( snap.tree.unified.is_empty ( ) , " {what}" ) ;
676
assert! (
677
snap.tree.conflicts.iter ( ) .any (
678
| c | matches! ( c, crate :: tree:: TreeConflict:: Name { name, .. } if name = = " x.txt" )
679
) ,
680
" {what}: {:?}" ,
681
snap.tree.conflicts
682
) ;
683
assert_eq! (
684
conflict_copies ( dir.path ( ) ) ,
685
[ " x.txt.oxy-conflict-1" , " x.txt.oxy-conflict-2" ] ,
686
" {what}"
687
) ;
688
assert! ( ! dir.path ( ) .join ( " x.txt" ) .exists ( ) , " {what}" ) ;
689
} ;
690
check ( " content" , & content) ;
691
# [ cfg ( unix ) ]
692
check ( " mode" , & mode) ;
693
# [ cfg ( not ( unix ) ) ]
694
let _ = mode;
695
}
696
697
698
699
700
# [ test ]
701
fn the_same_subrepo_added_on_two_bookmarks_unifies ( ) {
702
hermetic ( ) ;
703
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
704
let root = dir.path ( ) ;
705
let lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
706
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
707
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
708
.unwrap ( )
709
.with_home ( root.join ( " home" ) ) ;
710
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
711
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
712
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
713
let _ = lib_origin;
714
715
let app_dir = root.join ( " app" ) ;
716
let mut app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
717
write ( & app_dir, " main.txt" , b " app\n " ) ;
718
app.commit ( " base" , & [ ] ) .unwrap ( ) ;
719
app.bookmark_create ( " side" , None ) .unwrap ( ) ;
720
app.submodule_add ( & lib_url, " rstar-api" , None ) .unwrap ( ) ;
721
let modules = fs:: read( app_dir.join ( " .oxymodules" ) ) .unwrap ( ) ;
722
app.commit ( " rstar-api on main" , & [ ] ) .unwrap ( ) ;
723
724
725
app.bookmark_switch ( " side" , false ) .unwrap ( ) ;
726
assert! ( app_dir.join ( " rstar-api/.oxy" ) .is_dir ( ) ) ;
727
write ( & app_dir, " .oxymodules" , & modules) ;
728
let s = app.snapshot ( " status" ) .unwrap ( ) ;
729
assert_eq! (
730
statuses ( & s) ,
731
vec! [
732
( " .oxymodules" .into ( ) , ChangeStatus:: Added) ,
733
( " rstar-api" .into ( ) , ChangeStatus:: Added)
734
]
735
) ;
736
app.commit ( " rstar-api on side" , & [ ] ) .unwrap ( ) ;
737
app.bookmark_switch ( " main" , false ) .unwrap ( ) ;
738
739
let m = app.merge ( " side" ) .unwrap ( ) ;
740
assert! ( m.changed & & m.checked_out) ;
741
let snap = app.load_state ( & m.heads) .unwrap ( ) .materialize ( ) ;
742
assert! ( ! snap.is_conflicted ( ) , " {:?}" , snap.tree.conflicts) ;
743
assert_eq! ( snap.tree.unified.len ( ) , 2 ) ;
744
assert_eq! ( snap.files[ " rstar-api" ] .kind, NodeKind:: Subrepo) ;
745
assert! ( conflict_copies ( & app_dir) .is_empty ( ) ) ;
746
let s = app.snapshot ( " status" ) .unwrap ( ) ;
747
assert! ( s.statuses.is_empty ( ) , " {:?}" , statuses ( & s) ) ;
748
assert! ( ! s.base_snapshot.is_conflicted ( ) ) ;
749
assert_eq! (
750
fs:: read_to_string( app_dir.join ( " rstar-api/lib.txt" ) ) .unwrap ( ) ,
751
" v1\n "
752
) ;
753
}
754
98286406 Andy McCall 1 month ago ↶
755
# [ test ]
756
fn identities_and_signed_commits ( ) {
757
hermetic ( ) ;
758
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
759
let root = dir.path ( ) ;
760
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
761
write ( root, " a.txt" , b " a\n " ) ;
762
let h0 = repo.commit ( " unsigned" , & [ ] ) .unwrap ( ) ;
763
assert_eq! ( repo.verify_change ( & h0) .unwrap ( ) , SignatureStatus:: Unsigned) ;
75167672 Andy McCall 1 month ago
764
assert_eq! (
765
repo.store ( ) .get_change ( & h0) .unwrap ( ) .author,
766
ObjectRef:: ZERO
767
) ;
98286406 Andy McCall 1 month ago ↶
768
75167672 Andy McCall 1 month ago
769
let ( key, id) = repo
770
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
771
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
772
assert_eq! ( id.hash ( ) , key) ;
773
assert_eq! ( repo.config.author_key, Some ( key) ) ;
774
775
let reopened = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
776
assert_eq! ( reopened.config.author_key, Some ( key) ) ;
777
assert_eq! ( reopened.secret_key ( ) .unwrap ( ) .unwrap ( ) .0 , key) ;
778
779
780
write ( root, " a.txt" , b " a\n b\n " ) ;
781
let h1 = repo.commit ( " authored" , & [ ] ) .unwrap ( ) ;
782
assert_eq! ( repo.store ( ) .get_change ( & h1) .unwrap ( ) .author, key) ;
783
assert_eq! ( repo.verify_change ( & h1) .unwrap ( ) , SignatureStatus:: Unsigned) ;
784
785
repo.sign_change ( & h1) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
786
assert! (
787
matches! ( repo.verify_change ( & h1) .unwrap ( ) , SignatureStatus:: Valid { key: k, .. } if k = = key)
788
) ;
98286406 Andy McCall 1 month ago ↶
789
790
repo.config.set ( " core.sign" , " change" ) .unwrap ( ) ;
791
repo.save_config ( ) .unwrap ( ) ;
792
write ( root, " a.txt" , b " a\n b\n c\n " ) ;
793
let h2 = repo.commit ( " signed" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
794
assert_eq! (
795
repo.verify_change ( & h2) .unwrap ( ) ,
796
SignatureStatus:: Valid {
797
key,
798
name: " Ada" .into ( ) ,
799
email: " [email protected] " .into ( ) ,
800
sealed: false ,
801
revoked: false
802
}
803
) ;
98286406 Andy McCall 1 month ago ↶
804
05e71ed6 Andy McCall 1 month ago ↶
805
806
807
98286406 Andy McCall 1 month ago ↶
808
let other = SecretKey:: generate( ) ;
809
let other_id = other.identity ( " Mallory" , " [email protected] " , 0 ) ;
810
let other_key = repo.store ( ) .put_identity ( & other_id) .unwrap ( ) ;
05e71ed6 Andy McCall 1 month ago ↶
811
write ( root, " a.txt" , b " a\n b\n c\n d\n " ) ;
812
repo.config.set ( " core.sign" , " seal" ) .unwrap ( ) ;
813
let h3 = repo.commit ( " ada's, unsigned" , & [ ] ) .unwrap ( ) ;
814
let forged = other.sign ( Domain:: Change, & other_key, & h3) ;
815
repo.store ( ) .put_signature ( & h3, & forged) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
816
assert! (
05e71ed6 Andy McCall 1 month ago ↶
817
matches! ( repo.verify_change ( & h3) .unwrap ( ) , SignatureStatus:: Invalid( m) if m.contains ( " neither the author" ) )
75167672 Andy McCall 1 month ago
818
) ;
05e71ed6 Andy McCall 1 month ago ↶
819
let adopted = other.sign ( Domain:: Change, & other_key, & h0) ;
820
repo.store ( ) .put_signature ( & h0, & adopted) .unwrap ( ) ;
821
assert! (
822
matches! ( repo.verify_change ( & h0) .unwrap ( ) , SignatureStatus:: Valid { name, .. } if name = = " Mallory" )
823
) ;
98286406 Andy McCall 1 month ago ↶
824
825
826
let mut sig = repo.store ( ) .signatures ( & h2) .unwrap ( ) .remove ( 0 ) ;
827
sig.ed[ 0 ] ^ = 1 ;
828
repo.store ( ) .put_signature ( & h2, & sig) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
829
assert! ( matches! (
830
repo.verify_change ( & h2) .unwrap ( ) ,
831
SignatureStatus:: Invalid( _ )
832
) ) ;
98286406 Andy McCall 1 month ago ↶
833
834
835
let no_keys = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " nowhere" ) ) ;
836
write ( root, " a.txt" , b " z\n " ) ;
837
assert! ( no_keys.commit ( " cannot sign" , & [ ] ) .is_err ( ) ) ;
838
}
839
840
# [ test ]
841
fn checkout_moves_the_working_copy ( ) {
842
hermetic ( ) ;
843
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
844
let root = dir.path ( ) ;
845
let repo = Repo:: init( root) .unwrap ( ) ;
846
write ( root, " a.txt" , b " one\n " ) ;
847
write ( root, " sub/deep/b.txt" , b " b\n " ) ;
848
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
849
write ( root, " a.txt" , b " two\n " ) ;
850
fs:: remove_file( root.join ( " sub/deep/b.txt" ) ) .unwrap ( ) ;
851
write ( root, " bin.dat" , & [ 0 , 1 , 2 , 3 ] ) ;
852
let h2 = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
853
854
let r = repo.checkout ( & [ h1] , false , " oxy checkout" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
855
assert_eq! (
856
r,
857
CheckoutReport {
858
written: 2 ,
859
removed: 1 ,
9b723f69 Andy McCall 24 days ago ↶
860
removed_paths: vec! [ " bin.dat" .into ( ) ] ,
75167672 Andy McCall 1 month ago
861
conflicted: false ,
d3cd7ef9 Andy McCall 1 month ago ↶
862
notes: vec! [ ] ,
863
lacks: 1 ,
75167672 Andy McCall 1 month ago
864
}
865
) ;
98286406 Andy McCall 1 month ago ↶
866
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n " ) ;
867
assert_eq! ( fs:: read( root.join ( " sub/deep/b.txt" ) ) .unwrap ( ) , b " b\n " ) ;
868
assert! ( ! root.join ( " bin.dat" ) .exists ( ) ) ;
869
let ( _ , wc) = repo.working_copy ( ) .unwrap ( ) ;
870
assert_eq! ( wc.deps, vec! [ h1] ) ;
75167672 Andy McCall 1 month ago
871
assert! (
872
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
873
" checkout leaves a clean working copy"
874
) ;
98286406 Andy McCall 1 month ago ↶
875
876
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
877
878
879
write ( root, " a.txt" , b " dirty\n " ) ;
880
assert! ( repo.checkout ( & [ h2] , false , " oxy checkout" ) .is_err ( ) ) ;
75167672 Andy McCall 1 month ago
881
let r = repo
882
.checkout ( & [ h2] , true , " oxy checkout --discard" )
883
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
884
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " two\n " ) ;
885
assert_eq! ( fs:: read( root.join ( " bin.dat" ) ) .unwrap ( ) , [ 0 , 1 , 2 , 3 ] ) ;
886
assert! ( ! root.join ( " sub" ) .exists ( ) , " empty directories are pruned" ) ;
887
assert_eq! ( r.removed, 1 ) ;
888
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
889
890
891
# [ cfg ( unix ) ]
892
{
893
fs:: remove_dir_all( root.join ( " sub" ) ) .ok ( ) ;
894
std:: os:: unix:: fs:: symlink( " /tmp" , root.join ( " sub" ) ) .unwrap ( ) ;
895
assert! ( repo.checkout ( & [ h1] , true , " oxy checkout" ) .is_err ( ) ) ;
896
}
897
}
898
899
# [ test ]
900
fn large_files_are_chunked_and_read_back ( ) {
901
hermetic ( ) ;
902
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
903
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
904
let mut big = vec! [ 0 u8 ; 700 * 1024 ] ;
905
for ( i, b) in big.iter_mut ( ) .enumerate ( ) {
906
* b = ( i as u32 ) .wrapping_mul ( 2654435761 ) .to_le_bytes ( ) [ 0 ] ;
907
}
908
let r = repo.store_blob ( & big) .unwrap ( ) ;
909
let blob = Blob:: decode( & repo.store ( ) .get_typed ( & r, ObjectType:: Blob) .unwrap ( ) ) .unwrap ( ) ;
910
assert! ( blob.chunks.len ( ) > = 3 , " {}" , blob.chunks.len ( ) ) ;
911
assert_eq! ( repo.read_blob ( & r) .unwrap ( ) , big) ;
912
assert_eq! ( Repo:: blob_ref( & big) , r) ;
913
}
914
915
# [ test ]
916
fn clone_push_pull_with_seals_and_attestations ( ) {
917
hermetic ( ) ;
918
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
919
let root = dir.path ( ) ;
920
let origin_path = root.join ( " origin" ) ;
921
let origin = Repo:: init( & origin_path) .unwrap ( ) ;
922
let origin_url = origin_path.to_string_lossy ( ) .to_string ( ) ;
923
924
75167672 Andy McCall 1 month ago
925
let mut a = Repo:: clone( & origin_url, & root.join ( " a" ) )
926
.unwrap ( )
927
.with_home ( root.join ( " home-a" ) ) ;
98286406 Andy McCall 1 month ago ↶
928
assert_eq! ( a.id ( ) .unwrap ( ) , origin.id ( ) .unwrap ( ) ) ;
75167672 Andy McCall 1 month ago
929
let ( key_a, _ ) = a
930
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
931
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
932
write ( & root.join ( " a" ) , " f.txt" , b " a\n " ) ;
933
let h1 = a.commit ( " first" , & [ ] ) .unwrap ( ) ;
934
let results = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
935
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
936
assert! ( results[ 0 ] .ok, " {}" , results[ 0 ] .reason) ;
937
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
938
75167672 Andy McCall 1 month ago
939
assert! (
940
matches! ( origin.authenticate_change ( & h1) .unwrap ( ) , SignatureStatus:: Valid { key, sealed: true , .. } if key = = key_a)
941
) ;
942
assert_eq! (
943
origin.verify_change ( & h1) .unwrap ( ) ,
944
SignatureStatus:: Unsigned
945
) ;
f8c69576 Andy McCall 1 month ago ↶
946
let serial_of = | att : & SignedAttest| match att.attest.body {
947
AttestBody:: Ref { serial, .. } => serial,
948
_ => unreachable! ( ) ,
949
} ;
98286406 Andy McCall 1 month ago ↶
950
let ( _ , att) = origin.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
951
952
953
let first = serial_of ( & att) ;
954
assert! ( first > 1_000_000_000_000 , " {first}" ) ;
98286406 Andy McCall 1 month ago ↶
955
let again = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
956
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
957
assert! ( again[ 0 ] .ok & & again[ 0 ] .reason.contains ( " up to date" ) ) ;
958
let ( _ , att) = origin.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
959
assert_eq! ( serial_of ( & att) , first, " a no-op push does not attest again" ) ;
98286406 Andy McCall 1 month ago ↶
960
961
962
let b = Repo:: clone( & origin_url, & root.join ( " b" ) ) .unwrap ( ) ;
963
assert_eq! ( fs:: read( root.join ( " b/f.txt" ) ) .unwrap ( ) , b " a\n " ) ;
964
assert_eq! ( b.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
75167672 Andy McCall 1 month ago
965
assert! ( matches! (
966
b.authenticate_change ( & h1) .unwrap ( ) ,
967
SignatureStatus:: Valid { sealed: true , .. }
968
) ) ;
98286406 Andy McCall 1 month ago ↶
969
970
971
write ( & root.join ( " b" ) , " f.txt" , b " a\n b\n " ) ;
972
let h2 = b.commit ( " second" , & [ ] ) .unwrap ( ) ;
973
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
974
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
975
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
976
977
978
write ( & root.join ( " a" ) , " g.txt" , b " g\n " ) ;
979
let h3 = a.commit ( " third" , & [ ] ) .unwrap ( ) ;
980
assert! ( a.push ( " origin" , false ) .is_err ( ) ) ;
981
let pr = a.pull ( " origin" ) .unwrap ( ) ;
982
assert! ( pr.changed & & pr.checked_out) ;
983
let mut want = vec! [ h2, h3] ;
984
want.sort ( ) ;
985
assert_eq! ( pr.heads, want) ;
986
assert_eq! ( fs:: read( root.join ( " a/f.txt" ) ) .unwrap ( ) , b " a\n b\n " ) ;
987
assert_eq! ( fs:: read( root.join ( " a/g.txt" ) ) .unwrap ( ) , b " g\n " ) ;
988
let results = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
989
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
990
assert! ( results[ 0 ] .ok, " {}" , results[ 0 ] .reason) ;
991
let mut origin_heads = origin.bookmark_heads ( ) .unwrap ( ) ;
992
origin_heads.sort ( ) ;
993
assert_eq! ( origin_heads, want) ;
994
let ( _ , att) = origin.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
995
assert_eq! ( serial_of ( & att) , first + 1 ) ;
98286406 Andy McCall 1 month ago ↶
996
997
998
let pr = b.pull ( " origin" ) .unwrap ( ) ;
999
assert_eq! ( pr.heads, want) ;
1000
assert_eq! ( fs:: read( root.join ( " b/g.txt" ) ) .unwrap ( ) , b " g\n " ) ;
1001
assert! ( ! b.snapshot ( " status" ) .unwrap ( ) .changed) ;
1002
1003
1004
write ( & root.join ( " a" ) , " f.txt" , b " a\n A\n " ) ;
1005
a.commit ( " a-edit" , & [ ] ) .unwrap ( ) ;
1006
a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
1007
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1008
write ( & root.join ( " b" ) , " f.txt" , b " a\n B\n " ) ;
1009
b.commit ( " b-edit" , & [ ] ) .unwrap ( ) ;
1010
let pr = b.pull ( " origin" ) .unwrap ( ) ;
1011
assert! ( pr.changed & & pr.checked_out) ;
1012
let text = fs:: read_to_string( root.join ( " b/f.txt" ) ) .unwrap ( ) ;
1013
assert! ( text.contains ( " <<<<<<< oxy conflict" ) , " {text}" ) ;
1014
write ( & root.join ( " b" ) , " f.txt" , b " a\n AB\n " ) ;
1015
let hr = b.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1016
assert_eq! (
1017
b.store ( ) .get_change ( & hr) .unwrap ( ) .kind,
1018
ChangeKind:: Resolution
1019
) ;
98286406 Andy McCall 1 month ago ↶
1020
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
1021
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1022
a.pull ( " origin" ) .unwrap ( ) ;
1023
assert_eq! ( fs:: read( root.join ( " a/f.txt" ) ) .unwrap ( ) , b " a\n AB\n " ) ;
1024
1025
1026
let mut want = vec! [ hr, h3] ;
1027
want.sort ( ) ;
1028
assert_eq! ( a.bookmark_heads ( ) .unwrap ( ) , want) ;
1029
assert! ( ! a.load_state ( & want) .unwrap ( ) .materialize ( ) .is_conflicted ( ) ) ;
1030
}
1031
1032
# [ test ]
1033
fn receive_push_rejects_bad_updates ( ) {
1034
hermetic ( ) ;
1035
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1036
let root = dir.path ( ) ;
1037
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1038
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
1039
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1040
.unwrap ( )
1041
.with_home ( root.join ( " home" ) ) ;
1042
let ( key, _ ) = a
1043
.create_identity ( " Ada" , " a@b" , IdentityScope:: Repo)
1044
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1045
write ( & root.join ( " a" ) , " f.txt" , b " x\n " ) ;
1046
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
1047
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
1048
let empty = origin.store ( ) .put_state ( & StateObject:: new( vec! [ ] ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1049
let s1 = origin
1050
.store ( )
1051
.put_state ( & StateObject:: new( vec! [ h1] ) )
1052
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1053
1054
75167672 Andy McCall 1 month ago
1055
let ghost = origin
1056
.store ( )
1057
.put_state ( & StateObject:: new( vec! [ ObjectRef( [ 9 ; 32 ] ) ] ) )
1058
.unwrap ( ) ;
1059
let r = origin
1060
.receive_push (
1061
Bundle:: default( ) ,
1062
vec! [ RefUpdate {
1063
name: " main" .into ( ) ,
1064
old_state: s1,
1065
new_state: ghost,
1066
force: false ,
1067
attestation: None ,
1068
} ] ,
1069
None ,
1070
vec! [ ] ,
1071
)
1072
.unwrap ( ) ;
1073
assert! (
1074
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " missing" ) ,
1075
" {}" ,
1076
r[ 0 ] .reason
1077
) ;
98286406 Andy McCall 1 month ago ↶
1078
1079
75167672 Andy McCall 1 month ago
1080
let r = origin
1081
.receive_push (
1082
Bundle:: default( ) ,
1083
vec! [ RefUpdate {
1084
name: " main" .into ( ) ,
1085
old_state: empty,
1086
new_state: s1,
1087
force: false ,
1088
attestation: None ,
1089
} ] ,
1090
None ,
1091
vec! [ ] ,
1092
)
1093
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1094
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " stale" ) , " {}" , r[ 0 ] .reason) ;
1095
1096
75167672 Andy McCall 1 month ago
1097
let r = origin
1098
.receive_push (
1099
Bundle:: default( ) ,
1100
vec! [ RefUpdate {
1101
name: " main" .into ( ) ,
1102
old_state: s1,
1103
new_state: empty,
1104
force: false ,
1105
attestation: None ,
1106
} ] ,
1107
None ,
1108
vec! [ ] ,
1109
)
1110
.unwrap ( ) ;
1111
assert! (
1112
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " fast-forward" ) ,
1113
" {}" ,
1114
r[ 0 ] .reason
1115
) ;
1116
let r = origin
1117
.receive_push (
1118
Bundle:: default( ) ,
1119
vec! [ RefUpdate {
1120
name: " main" .into ( ) ,
1121
old_state: s1,
1122
new_state: empty,
1123
force: true ,
1124
attestation: None ,
1125
} ] ,
1126
None ,
1127
vec! [ ] ,
1128
)
1129
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1130
assert! ( r[ 0 ] .ok) ;
1131
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ ] ) ;
1132
1133
75167672 Andy McCall 1 month ago
1134
let sa = a
1135
.sign_attest ( & Attest {
1136
repo: a.id ( ) .unwrap ( ) ,
1137
hlc: Hlc:: now( ) ,
1138
body: AttestBody:: Ref {
1139
name: " main" .into ( ) ,
1140
old_state: empty,
1141
new_state: s1,
1142
serial: 1 ,
1143
} ,
1144
} )
1145
.unwrap ( ) ;
1146
let r = origin
1147
.receive_push (
1148
Bundle:: default( ) ,
1149
vec! [ RefUpdate {
1150
name: " main" .into ( ) ,
1151
old_state: empty,
1152
new_state: s1,
1153
force: false ,
1154
attestation: Some ( sa.clone ( ) ) ,
1155
} ] ,
1156
None ,
1157
vec! [ ] ,
1158
)
1159
.unwrap ( ) ;
1160
assert! (
1161
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " serial" ) ,
1162
" {}" ,
1163
r[ 0 ] .reason
1164
) ;
98286406 Andy McCall 1 month ago ↶
1165
75167672 Andy McCall 1 month ago
1166
let sa2 = a
1167
.sign_attest ( & Attest {
1168
repo: a.id ( ) .unwrap ( ) ,
1169
hlc: Hlc:: now( ) ,
1170
body: AttestBody:: Ref {
1171
name: " main" .into ( ) ,
1172
old_state: s1,
1173
new_state: empty,
1174
serial: 5 ,
1175
} ,
1176
} )
1177
.unwrap ( ) ;
1178
let r = origin
1179
.receive_push (
1180
Bundle:: default( ) ,
1181
vec! [ RefUpdate {
1182
name: " main" .into ( ) ,
1183
old_state: empty,
1184
new_state: s1,
1185
force: false ,
1186
attestation: Some ( sa2) ,
1187
} ] ,
1188
None ,
1189
vec! [ ] ,
1190
)
1191
.unwrap ( ) ;
1192
assert! (
1193
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " describe" ) ,
1194
" {}" ,
1195
r[ 0 ] .reason
1196
) ;
98286406 Andy McCall 1 month ago ↶
1197
let _ = key;
1198
}
1199
1200
# [ test ]
1201
fn user_config_supplies_defaults_and_clones_sign ( ) {
1202
hermetic ( ) ;
1203
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1204
let root = dir.path ( ) ;
1205
let home = root.join ( " home" ) ;
1206
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1207
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1208
1209
1210
let mut a = Repo:: init( & root.join ( " a" ) ) .unwrap ( ) .with_home ( & home) ;
75167672 Andy McCall 1 month ago
1211
let ( key, _ ) = a
1212
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1213
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1214
assert! ( ! a.config.repo_layer ( ) .contains_key ( " author.key" ) ) ;
1215
assert_eq! ( a.config.author_key, Some ( key) ) ;
1216
assert! ( home.join ( " config.toml" ) .exists ( ) ) ;
1217
a.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
1218
assert! ( a.config.sign_changes) ;
1219
1220
1221
a.config.set ( " core.sign" , " seal" ) .unwrap ( ) ;
1222
a.save_config ( ) .unwrap ( ) ;
1223
let again = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) .with_home ( & home) ;
1224
assert! ( ! again.config.sign_changes) ;
1225
assert_eq! ( again.config.author_key, Some ( key) ) ;
75167672 Andy McCall 1 month ago
1226
assert_eq! (
1227
read_config_file ( & root.join ( " a/.oxy/config" ) )
1228
.unwrap ( )
1229
.get ( " core.sign" )
1230
.map ( String :: as_str) ,
1231
Some ( " seal" )
1232
) ;
98286406 Andy McCall 1 month ago ↶
1233
1234
1235
1236
let b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) .with_home ( & home) ;
1237
assert! ( b.config.sign_changes) ;
1238
write ( & root.join ( " b" ) , " f.txt" , b " f\n " ) ;
1239
let h = b.commit ( " signed from a clone" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1240
assert! (
1241
matches! ( b.verify_change ( & h) .unwrap ( ) , SignatureStatus:: Valid { key: k, .. } if k = = key)
1242
) ;
98286406 Andy McCall 1 month ago ↶
1243
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
75167672 Andy McCall 1 month ago
1244
assert! ( matches! (
1245
origin.verify_change ( & h) .unwrap ( ) ,
1246
SignatureStatus:: Valid { .. }
1247
) ) ;
98286406 Andy McCall 1 month ago ↶
1248
}
1249
1250
# [ test ]
1251
fn format1_repositories_are_migrated_on_open ( ) {
1252
hermetic ( ) ;
1253
use crate :: oplog:: { Format1Op, Format1View} ;
1254
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1255
let root = dir.path ( ) ;
1256
let repo = Repo:: init( root) .unwrap ( ) ;
1257
write ( root, " a.txt" , b " a\n " ) ;
1258
let h1 = repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
1259
let ( wc_hash, _ ) = repo.working_copy ( ) .unwrap ( ) ;
1260
1261
let mut v1 = Format1View:: default( ) ;
1262
v1.bookmarks.insert ( " main" .into ( ) , vec! [ ] ) ;
1263
v1.workspaces.insert ( repo.workspace ( ) , wc_hash) ;
75167672 Andy McCall 1 month ago
1264
let init = Format1Op {
1265
parents: vec! [ ] ,
1266
hlc: Hlc:: default( ) ,
1267
command: " oxy init" .into ( ) ,
1268
view: v1.clone ( ) ,
1269
} ;
98286406 Andy McCall 1 month ago ↶
1270
let init_ref = repo.store ( ) .put ( ObjectType:: Op, & init.encode ( ) ) .unwrap ( ) ;
1271
v1.bookmarks.insert ( " main" .into ( ) , vec! [ h1] ) ;
75167672 Andy McCall 1 month ago
1272
let commit = Format1Op {
1273
parents: vec! [ init_ref] ,
1274
hlc: Hlc:: default( ) ,
1275
command: " oxy commit" .into ( ) ,
1276
view: v1,
1277
} ;
98286406 Andy McCall 1 month ago ↶
1278
let commit_ref = repo.store ( ) .put ( ObjectType:: Op, & commit.encode ( ) ) .unwrap ( ) ;
1279
write_atomic ( & root.join ( " .oxy/ops/head" ) , commit_ref.to_hex ( ) .as_bytes ( ) ) .unwrap ( ) ;
1280
write_atomic ( & root.join ( " .oxy/format" ) , FORMAT1_LINE .as_bytes ( ) ) .unwrap ( ) ;
1281
drop ( repo) ;
1282
1283
let repo = Repo:: open( root) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1284
assert_eq! (
1285
fs:: read_to_string( root.join ( " .oxy/format" ) ) .unwrap ( ) ,
1286
FORMAT_LINE
1287
) ;
98286406 Andy McCall 1 month ago ↶
1288
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
1289
let ops = repo.op_log ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1290
assert_eq! (
1291
ops.iter ( )
1292
.map ( | ( _, o ) | o.command.as_str ( ) )
1293
.collect:: < Vec< _ > > ( ) ,
1294
[ " oxy commit" , " oxy init" ]
1295
) ;
98286406 Andy McCall 1 month ago ↶
1296
assert_eq! ( repo.heads_of ( & ops[ 1 ] .1. view, " main" ) .unwrap ( ) , vec! [ ] ) ;
1297
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
1298
1299
assert_eq! ( Repo:: open( root) .unwrap ( ) .op_log ( ) .unwrap ( ) .len ( ) , 2 ) ;
1300
}
1301
1302
# [ test ]
1303
fn undo_skips_snapshot_operations ( ) {
1304
hermetic ( ) ;
1305
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1306
let root = dir.path ( ) ;
1307
let repo = Repo:: init( root) .unwrap ( ) ;
1308
write ( root, " a.txt" , b " a\n " ) ;
1309
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
1310
1311
write ( root, " b.txt" , b " b\n " ) ;
1312
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
1313
assert! ( s.changed) ;
1314
assert! ( repo.head_op ( ) .unwrap ( ) .1. is_snapshot ( ) ) ;
1315
assert_eq! ( repo.last_command ( ) .unwrap ( ) .command, " oxy commit -m \" a\" " ) ;
1316
1317
repo.undo ( ) .unwrap ( ) ;
1318
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ ] ) ;
1319
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
1320
let paths: Vec< _ > = s.statuses.iter ( ) .map ( | p | p.path.as_str ( ) ) .collect ( ) ;
75167672 Andy McCall 1 month ago
1321
assert_eq! (
1322
paths,
1323
[ " a.txt" , " b.txt" ] ,
1324
" both files are back in the working copy"
1325
) ;
98286406 Andy McCall 1 month ago ↶
1326
assert! ( repo.store ( ) .get_change ( & h1) .unwrap ( ) .deps.is_empty ( ) ) ;
1327
1328
assert_eq! ( repo.last_command ( ) .unwrap ( ) .command, " oxy init" ) ;
1329
assert! ( repo.undo ( ) .is_err ( ) ) ;
1330
}
1331
1332
# [ test ]
1333
fn independent_changes_commute_and_make_multiple_heads ( ) {
1334
hermetic ( ) ;
1335
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1336
let root = dir.path ( ) ;
1337
let repo = Repo:: init( root) .unwrap ( ) ;
1338
write ( root, " a.txt" , b " a\n " ) ;
1339
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
1340
write ( root, " b.txt" , b " b\n " ) ;
1341
let h2 = repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
1342
let c2 = repo.store ( ) .get_change ( & h2) .unwrap ( ) ;
1343
assert! ( c2.deps.is_empty ( ) , " b.txt touches nothing from h1" ) ;
1344
let mut heads = repo.bookmark_heads ( ) .unwrap ( ) ;
1345
heads.sort ( ) ;
1346
let mut want = vec! [ h1, h2] ;
1347
want.sort ( ) ;
1348
assert_eq! ( heads, want) ;
1349
1350
write ( root, " a.txt" , b " a\n a2\n " ) ;
1351
let h3 = repo.commit ( " a2" , & [ ] ) .unwrap ( ) ;
1352
assert_eq! ( repo.store ( ) .get_change ( & h3) .unwrap ( ) .deps, vec! [ h1] ) ;
1353
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
1354
assert_eq! ( heads, [ h2, h3] .into_iter ( ) .collect ( ) ) ;
1355
1356
write ( root, " c.txt" , b " c\n " ) ;
1357
let h4 = repo.commit ( " c" , & [ h2] ) .unwrap ( ) ;
1358
assert_eq! ( repo.store ( ) .get_change ( & h4) .unwrap ( ) .deps, vec! [ h2] ) ;
1359
}
1360
1361
# [ test ]
1362
fn mode_change_and_text_binary_switch ( ) {
1363
hermetic ( ) ;
1364
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1365
let root = dir.path ( ) ;
1366
let repo = Repo:: init( root) .unwrap ( ) ;
1367
write ( root, " run.sh" , b " #!/bin/sh\n " ) ;
1368
repo.commit ( " add script" , & [ ] ) .unwrap ( ) ;
1369
# [ cfg ( unix ) ]
1370
{
1371
use std:: os:: unix:: fs:: PermissionsExt;
1372
fs:: set_permissions( root.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
1373
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1374
assert_eq! (
1375
statuses ( & s) ,
1376
vec! [ ( " run.sh" .into ( ) , ChangeStatus:: ModeChanged) ]
1377
) ;
98286406 Andy McCall 1 month ago ↶
1378
let h = repo.commit ( " exec" , & [ ] ) .unwrap ( ) ;
1379
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1380
assert! ( snap.files[ " run.sh" ] .mode.exec) ;
1381
}
1382
1383
write ( root, " run.sh" , b " \0 binary" ) ;
1384
let h = repo.commit ( " binary" , & [ ] ) .unwrap ( ) ;
1385
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1386
assert! ( snap.files[ " run.sh" ] .mode.opaque) ;
1387
write ( root, " run.sh" , b " text again\n " ) ;
1388
let h = repo.commit ( " text" , & [ ] ) .unwrap ( ) ;
1389
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1390
assert! ( ! snap.files[ " run.sh" ] .mode.opaque) ;
75167672 Andy McCall 1 month ago
1391
assert! (
1392
matches! ( & snap.files[ " run.sh" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " text again\n " )
1393
) ;
98286406 Andy McCall 1 month ago ↶
1394
assert! ( ! snap.is_conflicted ( ) ) ;
1395
}
83e0677b Andy McCall 1 month ago ↶
1396
6eae06b6 Andy McCall 27 days ago ↶
1397
1398
1399
# [ cfg ( unix ) ]
1400
# [ test ]
1401
fn a_binary_file_changed_and_made_executable_at_once_keeps_both ( ) {
1402
use std:: os:: unix:: fs:: PermissionsExt;
1403
hermetic ( ) ;
1404
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1405
let root = dir.path ( ) ;
1406
let repo = Repo:: init( root) .unwrap ( ) ;
1407
write ( root, " tool.exe" , b " MZ\0 one" ) ;
1408
repo.commit ( " add" , & [ ] ) .unwrap ( ) ;
1409
write ( root, " tool.exe" , b " MZ\0 two" ) ;
1410
fs:: set_permissions( root.join ( " tool.exe" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
1411
let h = repo.commit ( " update and +x" , & [ ] ) .unwrap ( ) ;
1412
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1413
let f = & snap.files[ " tool.exe" ] ;
1414
assert! ( f.mode.opaque & & f.mode.exec, " {:?}" , f.mode) ;
1415
assert! (
1416
repo.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ,
1417
" nothing left to commit"
1418
) ;
1419
}
1420
f0a517d2 Andy McCall 18 days ago ↶
1421
1422
1423
1424
83e0677b Andy McCall 1 month ago ↶
1425
# [ test ]
f0a517d2 Andy McCall 18 days ago ↶
1426
fn a_certified_key_pushes_under_a_subject_grant ( ) {
1427
use crate :: cert:: { Certificate, SignedCertificate} ;
1428
use crate :: identity:: Scope;
1429
hermetic ( ) ;
1430
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1431
let root = dir.path ( ) ;
1432
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1433
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1434
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1435
.unwrap ( )
1436
.with_home ( root.join ( " home-a" ) ) ;
1437
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1438
.unwrap ( ) ;
1439
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1440
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1441
a.policy_init ( ) .unwrap ( ) ;
1442
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1443
let hub_sk = SecretKey:: generate( ) ;
1444
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1445
a.policy_grant_subject (
1446
" agent:claude@ada" ,
1447
hub.clone ( ) ,
1448
Scope:: CHANGES | Scope:: REFS ,
1449
None ,
1450
)
1451
.unwrap ( ) ;
1452
assert! ( a
1453
.push ( " origin" , false )
1454
.unwrap ( )
1455
.iter ( )
1456
.any ( | r | r.name = = " policy" & & r.ok ) ) ;
1457
1458
let now = Hlc:: now( ) .wall_ms;
1459
1460
1461
let agent = | name : & str , subject : & str , from : u64 , to : u64 | {
1462
let mut r = Repo:: clone( & url, & root.join ( name) )
1463
.unwrap ( )
1464
.with_home ( root.join ( format! ( " home-{name} " ) ) ) ;
1465
let ( _ , id) = r
1466
.create_identity ( name, " [email protected] " , IdentityScope:: User)
1467
.unwrap ( ) ;
1468
let cert = SignedCertificate:: sign(
1469
Certificate {
1470
key: id,
1471
issuer: hub.hash ( ) ,
1472
subject: subject.into ( ) ,
1473
claims: vec! [ ] ,
1474
scope: Scope:: CHANGES | Scope:: REFS ,
1475
not_before: from,
1476
expires: to,
a2387d54 Andy McCall 18 days ago ↶
1477
grant: None ,
f0a517d2 Andy McCall 18 days ago ↶
1478
} ,
1479
& hub,
1480
& hub_sk,
1481
) ;
1482
r.store ( ) .put_identity ( & hub) .unwrap ( ) ;
1483
r.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1484
write ( & root.join ( name) , & format! ( " {name} .txt" ) , b " agent work\n " ) ;
1485
r.commit ( " work" , & [ ] ) .unwrap ( ) ;
1486
r
1487
} ;
1488
1489
let claude = agent ( " claude" , " agent:claude@ada" , now - 60_000 , now + 600_000 ) ;
1490
let r = claude.push ( " origin" , false ) .unwrap ( ) ;
1491
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
1492
1493
a.pull ( " origin" ) .unwrap ( ) ;
1494
assert! ( a.bookmark_heads ( ) .unwrap ( ) .iter ( ) .any ( | h | a
1495
.store ( )
1496
.get_change ( h)
1497
.map ( | c | c.message = = " work" )
1498
.unwrap_or ( false ) ) ) ;
1499
f53cbe7a Andy McCall 18 days ago ↶
1500
1501
1502
1503
1504
let host = Repo:: open( & root.join ( " origin" ) )
1505
.unwrap ( )
1506
.with_acceptor ( hub.clone ( ) , hub_sk.clone ( ) ) ;
1507
let ( attest_ref, sa) = host.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
1508
host.record_acceptance ( & attest_ref) .unwrap ( ) ;
1509
let served = host.remote_refs ( ) .unwrap ( ) ;
1510
let acc = served
1511
.refs
1512
.iter ( )
1513
.find ( | r | r.name = = " main" )
1514
.unwrap ( )
1515
.accepted
1516
.clone ( )
1517
.expect ( " served with the refs" ) ;
1518
let signer = sa.sigs[ 0 ] .key;
1519
assert_eq! (
1520
a.certified_time ( & signer, & sa, Some ( & acc) ) ,
1521
acc.attest.hlc.wall_ms
1522
) ;
1523
assert_eq! ( a.certified_time ( & signer, & sa, None ) , sa.attest.hlc.wall_ms) ;
1524
let stranger_sk = SecretKey:: generate( ) ;
1525
let stranger = stranger_sk.identity ( " stranger" , " s@x" , 1 ) ;
1526
let mut forged = acc.clone ( ) ;
1527
forged.sigs = vec! [ stranger_sk.sign ( Domain:: Attest, & stranger.hash ( ) , & acc.attest.hash ( ) ) ] ;
1528
assert_eq! (
1529
a.certified_time ( & signer, & sa, Some ( & forged) ) ,
1530
sa.attest.hlc.wall_ms,
1531
" not the issuer's"
1532
) ;
1533
let mut elsewhere = acc.clone ( ) ;
1534
elsewhere.attest.body = AttestBody:: Accepted {
1535
attest: ObjectRef( [ 7 ; 32 ] ) ,
1536
} ;
1537
elsewhere.sigs = vec! [ hub_sk.sign ( Domain:: Attest, & hub.hash ( ) , & elsewhere.attest.hash ( ) ) ] ;
1538
assert_eq! (
1539
a.certified_time ( & signer, & sa, Some ( & elsewhere) ) ,
1540
sa.attest.hlc.wall_ms,
1541
" another attestation's"
1542
) ;
1543
a.pull ( " origin" ) .unwrap ( ) ;
1544
1545
host.gc ( true ) .unwrap ( ) ;
1546
assert! ( host.acceptance_of ( & attest_ref) .unwrap ( ) .is_some ( ) ) ;
1547
f0a517d2 Andy McCall 18 days ago ↶
1548
let late = agent ( " late" , " agent:claude@ada" , now - 1_200_000 , now - 600_000 ) ;
1549
let r = late.push ( " origin" , false ) .unwrap ( ) ;
1550
assert! (
1551
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " may not update" ) ,
1552
" an expired certificate: {r:?}"
1553
) ;
1554
let other = agent ( " other" , " agent:mallory@ada" , now - 60_000 , now + 600_000 ) ;
1555
let r = other.push ( " origin" , false ) .unwrap ( ) ;
1556
assert! (
1557
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " may not update" ) ,
1558
" a subject not granted: {r:?}"
1559
) ;
1560
}
1561
d36ba2f7 Andy McCall 17 days ago ↶
1562
1563
1564
1565
1566
1567
# [ test ]
1568
fn a_bookmark_moved_by_a_certified_key_alone_is_fetched ( ) {
1569
use crate :: cert:: { Certificate, SignedCertificate} ;
1570
use crate :: identity:: Scope;
1571
hermetic ( ) ;
1572
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1573
let root = dir.path ( ) ;
1574
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1575
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1576
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1577
.unwrap ( )
1578
.with_home ( root.join ( " home-a" ) ) ;
1579
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1580
.unwrap ( ) ;
1581
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1582
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1583
a.policy_init ( ) .unwrap ( ) ;
1584
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1585
let hub_sk = SecretKey:: generate( ) ;
1586
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1587
a.policy_grant_subject (
1588
" agent:claude@ada" ,
1589
hub.clone ( ) ,
1590
Scope:: CHANGES | Scope:: REFS ,
1591
None ,
1592
)
1593
.unwrap ( ) ;
1594
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1595
1596
let now = Hlc:: now( ) .wall_ms;
1597
let mut agent = Repo:: clone( & url, & root.join ( " agent" ) )
1598
.unwrap ( )
1599
.with_home ( root.join ( " home-agent" ) ) ;
1600
let ( key, id) = agent
1601
.create_identity ( " claude" , " [email protected] " , IdentityScope:: User)
1602
.unwrap ( ) ;
1603
let cert = SignedCertificate:: sign(
1604
Certificate {
1605
key: id,
1606
issuer: hub.hash ( ) ,
1607
subject: " agent:claude@ada" .into ( ) ,
1608
claims: vec! [ ] ,
1609
scope: Scope:: CHANGES | Scope:: REFS ,
1610
not_before: now - 60_000 ,
1611
expires: now + 600_000 ,
1612
grant: None ,
1613
} ,
1614
& hub,
1615
& hub_sk,
1616
) ;
1617
agent.store ( ) .put_identity ( & hub) .unwrap ( ) ;
1618
agent.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1619
let heads = agent.bookmark_heads ( ) .unwrap ( ) ;
1620
agent.bookmark_create ( " agent-work" , Some ( & heads) ) .unwrap ( ) ;
1621
let r = agent.push_bookmark ( " origin" , " agent-work" , false ) .unwrap ( ) ;
1622
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
1623
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
1624
let ( _ , sa) = host.latest_ref_attest ( " agent-work" ) .unwrap ( ) .unwrap ( ) ;
1625
assert_eq! ( sa.sigs[ 0 ] .key, key, " the certified key alone moved it" ) ;
1626
1627
1628
let bundle = host.bundle_raw_for ( & [ ] , None ) .unwrap ( ) ;
1629
assert! ( bundle.changes.is_empty ( ) & & bundle.raw_changes.is_empty ( ) ) ;
1630
assert_eq! ( bundle.certificates.len ( ) , 1 ) ;
1631
for k in [ key, hub.hash ( ) ] {
1632
assert! ( bundle
1633
.objects
1634
.iter ( )
1635
.any ( | ( t , b ) | * t = = ObjectType:: Identity & & hash_object ( * t, b) = = k) ) ;
1636
}
1637
1638
let c = Repo:: clone( & url, & root.join ( " c" ) ) .unwrap ( ) ;
1639
let tracked = c.view ( ) .unwrap ( ) .remotes[ " origin" ] .clone ( ) ;
1640
assert_eq! (
1641
tracked.get ( " agent-work" ) ,
1642
host.view ( ) .unwrap ( ) .bookmarks.get ( " agent-work" )
1643
) ;
1644
assert! ( c.store ( ) .certificate_of ( & key) .unwrap ( ) .is_some ( ) ) ;
1645
}
1646
028545df Andy McCall 17 days ago ↶
1647
1648
1649
1650
1651
1652
1653
# [ test ]
1654
fn a_fetched_attestation_is_judged_at_its_integrated_time ( ) {
1655
use super :: transparency:: LogCheck;
1656
use crate :: cert:: { Certificate, SignedCertificate} ;
1657
use crate :: identity:: Scope;
1658
use crate :: protocol:: { Integration, Logged, RefsResponse, RemoteRef} ;
1659
use crate :: tlog:: { self , Checkpoint, Entry, Hash, Integrated, Note} ;
1660
hermetic ( ) ;
1661
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1662
let root = dir.path ( ) ;
1663
let mut a = Repo:: init( & root.join ( " a" ) )
1664
.unwrap ( )
1665
.with_home ( root.join ( " home-a" ) ) ;
1666
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1667
.unwrap ( ) ;
1668
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1669
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1670
a.policy_init ( ) .unwrap ( ) ;
1671
let issuer_sk = SecretKey:: generate( ) ;
1672
let issuer = issuer_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1673
a.policy_grant_subject (
1674
" agent:claude@ada" ,
1675
issuer.clone ( ) ,
1676
Scope:: CHANGES | Scope:: REFS ,
1677
None ,
1678
)
1679
.unwrap ( ) ;
1680
let p = a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy;
1681
1682
1683
let now = Hlc:: now( ) .wall_ms;
1684
let t0 = now - 2 * 3_600_000 ;
1685
let agent_sk = SecretKey:: generate( ) ;
1686
let agent = agent_sk.identity ( " claude" , " [email protected] " , t0) ;
1687
let cert = SignedCertificate:: sign(
1688
Certificate {
1689
key: agent.clone ( ) ,
1690
issuer: issuer.hash ( ) ,
1691
subject: " agent:claude@ada" .into ( ) ,
1692
claims: vec! [ ] ,
1693
scope: Scope:: CHANGES | Scope:: REFS ,
1694
not_before: t0,
1695
expires: t0 + 600_000 ,
1696
grant: None ,
1697
} ,
1698
& issuer,
1699
& issuer_sk,
1700
) ;
1701
a.store ( ) .put_identity ( & agent) .unwrap ( ) ;
1702
a.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1703
let signer = agent.hash ( ) ;
1704
let attested = | claimed : u64 | {
1705
let attest = Attest {
1706
repo: a.id ( ) .unwrap ( ) ,
1707
hlc: Hlc {
1708
wall_ms: claimed,
1709
logical: 0 ,
1710
} ,
1711
body: AttestBody:: Ref {
1712
name: " main" .into ( ) ,
1713
old_state: ObjectRef( [ 1 ; 32 ] ) ,
1714
new_state: ObjectRef( [ 2 ; 32 ] ) ,
1715
serial: 1 ,
1716
} ,
1717
} ;
1718
let sig = agent_sk.sign ( Domain:: Attest, & signer, & attest.hash ( ) ) ;
1719
SignedAttest {
1720
attest,
1721
sigs: vec! [ sig] ,
1722
}
1723
} ;
1724
1725
1726
1727
let log_sk = SecretKey:: generate( ) ;
1728
let log_key = log_sk.identity ( " hub.example" , " [email protected] " , 1 ) ;
1729
let origin = " hub.example/log" ;
1730
let hub = " https://hub.example" ;
1731
let url = " https://hub.example/ada/a" ;
1732
let w_sk = SecretKey:: generate( ) ;
1733
let w = w_sk.identity ( " w" , " [email protected] " , 1 ) ;
1734
let stranger_sk = SecretKey:: generate( ) ;
1735
let stranger = stranger_sk.identity ( " s" , " [email protected] " , 1 ) ;
1736
let sa = attested ( t0 + 300_000 ) ;
1737
let entry_of = | i : u64 | {
1738
let ( kind, object) = if i = = 3 {
1739
( " ref" , sa.attest.hash ( ) )
1740
} else {
1741
( " seal" , ObjectRef( [ i as u8 ; 32 ] ) )
1742
} ;
1743
Entry {
1744
kind: kind.into ( ) ,
1745
repo: sa.attest.repo,
1746
object,
1747
signers: vec! [ signer] ,
1748
received: t0 + 300_000 ,
1749
}
1750
.encode ( )
1751
} ;
1752
let mut stored: Vec< Hash> = Vec :: new( ) ;
1753
for i in 0 .. 8 {
1754
let add = tlog:: stored_hashes_for_leaf( i, tlog:: leaf_hash( & entry_of ( i) ) , & stored) .unwrap ( ) ;
1755
stored.extend ( add) ;
1756
}
1757
let note_of = | n : u64 , cosigners : & [( & str , & Identity, & SecretKey, u64 ) ]| {
1758
let mut note = Checkpoint {
1759
origin: origin.into ( ) ,
1760
size: n,
1761
root: tlog:: tree_hash( n, & stored) .unwrap ( ) ,
1762
extensions: vec! [ ] ,
1763
}
1764
.sign ( & log_key, & log_sk)
1765
.unwrap ( ) ;
1766
for ( name, id, sk, t) in cosigners {
1767
note.sigs
1768
.extend ( tlog:: cosign( & note.text, name, id, sk, * t) .unwrap ( ) ) ;
1769
}
1770
note.encode ( )
1771
} ;
1772
let latest = note_of ( 8 , & [ ] ) ;
1773
let witnessed_at = ( t0 + 360_000 ) / 1000 ;
1774
let cosigned = note_of (
1775
4 ,
1776
& [
1777
( " w.example" , & w, & w_sk, witnessed_at) ,
1778
( " s.example" , & stranger, & stranger_sk, 1 ) ,
1779
] ,
1780
) ;
1781
let answer = | integration : Vec< Integration> , witnesses : Vec< ( String, Vec< u8 > ) > | RefsResponse {
1782
refs: vec! [ RemoteRef {
1783
name: " main" .into ( ) ,
1784
state: ObjectRef( [ 2 ; 32 ] ) ,
1785
heads: vec! [ ] ,
1786
attestation: Some ( sa.clone ( ) ) ,
1787
last_serial: None ,
1788
accepted: None ,
1789
logged: Some ( Logged {
1790
index: 3 ,
1791
entry: entry_of ( 3 ) ,
1792
proof: tlog:: prove_inclusion( 8 , 3 , & stored) .unwrap ( ) ,
1793
} ) ,
1794
integration,
1795
} ] ,
1796
log: Some ( latest.clone ( ) ) ,
1797
witnesses,
1798
.. RefsResponse:: default( )
1799
} ;
1800
let proof4 = tlog:: prove_inclusion( 4 , 3 , & stored) .unwrap ( ) ;
1801
let served = vec! [ Integration {
1802
note: cosigned.clone ( ) ,
1803
proof: proof4.clone ( ) ,
1804
} ] ;
1805
let listed = vec! [ ( " w.example" .to_string ( ) , w.encode ( ) ) ] ;
1806
1807
a.remember_log ( hub, & log_key, & Note:: parse( & latest) .unwrap ( ) )
1808
.unwrap ( ) ;
1809
1810
1811
1812
let resp = answer ( served.clone ( ) , listed.clone ( ) ) ;
1813
let lc = a.check_log ( url, None , & resp) .unwrap ( ) ;
1814
assert! ( lc.notes.is_empty ( ) , " {:?}" , lc.notes) ;
1815
let t = lc.time_of ( " main" ) .unwrap ( ) ;
1816
assert_eq! ( ( t.time, t.witness.as_str ( ) ) , ( witnessed_at, " w.example" ) ) ;
1817
1818
1819
assert! ( cert.cert.expires < now) ;
1820
let mut notes = Vec :: new( ) ;
1821
a.authorize_in_time ( & p, " origin" , & resp.refs[ 0 ] , & sa, & signer, & lc, & mut notes)
1822
.unwrap ( ) ;
1823
assert! ( notes.is_empty ( ) , " {notes:?}" ) ;
1824
1825
1826
1827
let lonely = vec! [ Integration {
1828
note: note_of ( 4 , & [ ( " s.example" , & stranger, & stranger_sk, 1 ) ] ) ,
1829
proof: proof4.clone ( ) ,
1830
} ] ;
1831
let resp = answer ( lonely, vec! [ ] ) ;
1832
let lc = a.check_log ( url, None , & resp) .unwrap ( ) ;
1833
assert! ( lc
1834
.time_of ( " main" )
1835
.unwrap_err ( )
1836
.contains ( " no witness this user trusts" ) ) ;
1837
let mut notes = Vec :: new( ) ;
1838
a.authorize_in_time ( & p, " origin" , & resp.refs[ 0 ] , & sa, & signer, & lc, & mut notes)
1839
.unwrap ( ) ;
1840
assert! (
1841
notes.len ( ) = = 1 & & notes[ 0 ] .contains ( " not at a witnessed time" ) ,
1842
" {notes:?}"
1843
) ;
1844
1845
1846
1847
let resp = answer (
1848
served.clone ( ) ,
1849
vec! [ ( " w.example" .into ( ) , stranger.encode ( ) ) ] ,
1850
) ;
1851
let lc = a.check_log ( url, None , & resp) .unwrap ( ) ;
1852
assert! (
1853
lc.notes.iter ( ) .any ( | n | n.contains ( " another key" ) ) ,
1854
" {:?}" ,
1855
lc.notes
1856
) ;
1857
assert! (
1858
lc.time_of ( " main" ) .is_err ( ) ,
1859
" w's cosignature is by its old key"
1860
) ;
1861
a.check_log ( url, None , & answer ( served.clone ( ) , listed.clone ( ) ) )
1862
.unwrap ( ) ;
1863
1864
1865
1866
let mut old = answer ( vec! [ ] , vec! [ ] ) ;
1867
old.log = None ;
1868
old.refs[ 0 ] .logged = None ;
1869
let lc = a.check_log ( url, None , & old) .unwrap ( ) ;
1870
assert! ( ! lc.kept & & lc.notes.is_empty ( ) ) ;
1871
let mut notes = Vec :: new( ) ;
1872
a.authorize_in_time ( & p, " origin" , & old.refs[ 0 ] , & sa, & signer, & lc, & mut notes)
1873
.unwrap ( ) ;
1874
assert! ( notes.is_empty ( ) , " {notes:?}" ) ;
1875
1876
1877
let at = | secs_after_t0 : u64 | {
1878
let mut times = std:: collections:: HashMap:: new( ) ;
1879
times.insert (
1880
" main" .to_string ( ) ,
1881
Ok ( Integrated {
1882
time: ( t0 + secs_after_t0 * 1000 ) / 1000 ,
1883
witness: " w.example" .into ( ) ,
1884
size: 4 ,
1885
} ) ,
1886
) ;
1887
LogCheck {
1888
kept: true ,
1889
times,
1890
.. LogCheck:: default( )
1891
}
1892
} ;
1893
let judged = | sa : & SignedAttest, lc : & LogCheck| {
1894
let mut notes = Vec :: new( ) ;
1895
a.authorize_in_time ( & p, " origin" , & old.refs[ 0 ] , sa, & signer, lc, & mut notes)
1896
.map ( | _| notes )
1897
} ;
1898
1899
1900
1901
let notes = judged ( & sa, & at ( 3600 ) ) .unwrap ( ) ;
1902
assert! (
1903
notes
1904
.iter ( )
1905
.any ( | n | n.contains ( " had lapsed when witness w.example" ) )
1906
& & notes
1907
.iter ( )
1908
.any ( | n | n.contains ( " 55 minutes before witness" ) ) ,
1909
" {notes:?}"
1910
) ;
1911
1912
1913
1914
let fast = attested ( now) ;
1915
let notes = judged ( & fast, & at ( 400 ) ) .unwrap ( ) ;
1916
assert! (
1917
notes.len ( ) = = 1 & & notes[ 0 ] .contains ( " after witness w.example saw it" ) ,
1918
" {notes:?}"
1919
) ;
1920
1921
assert! ( judged ( & fast, & at ( 3600 ) ) .is_err ( ) ) ;
1922
1923
assert! ( judged ( & sa, & at ( 500 ) ) .unwrap ( ) .is_empty ( ) ) ;
1924
}
1925
a2387d54 Andy McCall 18 days ago ↶
1926
1927
1928
1929
f0a517d2 Andy McCall 18 days ago ↶
1930
# [ test ]
a2387d54 Andy McCall 18 days ago ↶
1931
fn a_certified_key_acts_for_its_author_under_a_grant ( ) {
1932
use crate :: cert:: { Certificate, Grant, SignedCertificate, SignedGrant} ;
1933
use crate :: identity:: { Identity, Scope} ;
1934
hermetic ( ) ;
1935
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1936
let root = dir.path ( ) ;
1937
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1938
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1939
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1940
.unwrap ( )
1941
.with_home ( root.join ( " home-a" ) ) ;
1942
let ( ada, _ ) = a
1943
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1944
.unwrap ( ) ;
1945
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1946
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1947
a.policy_init ( ) .unwrap ( ) ;
1948
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1949
let hub_sk = SecretKey:: generate( ) ;
1950
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1951
a.policy_grant_subject (
1952
" agent:claude@ada" ,
1953
hub.clone ( ) ,
1954
Scope:: CHANGES | Scope:: REFS ,
1955
None ,
1956
)
1957
.unwrap ( ) ;
1958
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1959
let ( _ , ada_sk) = a.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
1960
let ada_id = a.identity ( & ada) .unwrap ( ) ;
1961
1962
let now = Hlc:: now( ) .wall_ms;
1963
let grant = | by : & Identity, sk : & SecretKey, from : u64 , to : u64 | {
1964
SignedGrant:: sign(
1965
Grant {
1966
grantor: by.hash ( ) ,
1967
subject: " agent:claude@ada" .into ( ) ,
1968
issuer: hub.hash ( ) ,
1969
scope: Scope:: CHANGES | Scope:: SEAL ,
1970
not_before: from,
1971
expires: to,
1972
} ,
1973
by,
1974
sk,
1975
)
1976
} ;
1977
1978
1979
let agent = | name : & str , g : Option< SignedGrant> | {
1980
let mut r = Repo:: clone( & url, & root.join ( name) )
1981
.unwrap ( )
1982
.with_home ( root.join ( format! ( " home-{name} " ) ) ) ;
1983
let ( key, id) = r
1984
.create_identity ( name, " [email protected] " , IdentityScope:: User)
1985
.unwrap ( ) ;
1986
r.set_user_config ( " author.key" , & ada.to_hex ( ) ) .unwrap ( ) ;
1987
r.set_user_config ( " author.signer" , & key.to_hex ( ) ) .unwrap ( ) ;
1988
let cert = SignedCertificate:: sign(
1989
Certificate {
1990
key: id,
1991
issuer: hub.hash ( ) ,
1992
subject: " agent:claude@ada" .into ( ) ,
1993
claims: vec! [ ] ,
1994
scope: Scope:: CHANGES | Scope:: REFS | Scope:: SEAL ,
1995
not_before: now - 60_000 ,
1996
expires: now + 600_000 ,
1997
grant: g,
1998
} ,
1999
& hub,
2000
& hub_sk,
2001
) ;
2002
r.store ( ) .put_identity ( & hub) .unwrap ( ) ;
2003
r.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
2004
write ( & root.join ( name) , & format! ( " {name} .txt" ) , b " agent work\n " ) ;
2005
let h = r.commit ( " work" , & [ ] ) .unwrap ( ) ;
2006
assert_eq! ( r.store ( ) .get_change_meta ( & h) .unwrap ( ) .author, ada) ;
2007
( r, key, h)
2008
} ;
2009
2010
let ( claude, key, h) = agent (
2011
" claude" ,
2012
Some ( grant ( & ada_id, & ada_sk, now - 60_000 , now + 86_400_000 ) ) ,
2013
) ;
2014
let r = claude.push ( " origin" , false ) .unwrap ( ) ;
2015
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
2016
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
68dcf21f Andy McCall 18 days ago ↶
2017
assert! (
2018
matches! (
2019
host.authenticate_change ( & h) .unwrap ( ) ,
2020
SignatureStatus:: Valid { key: k, sealed: true , name, .. } if k = = key & & name = = " agent:claude@ada"
2021
) ,
2022
" named by its certificate's subject"
2023
) ;
a2387d54 Andy McCall 18 days ago ↶
2024
a.pull ( " origin" ) .unwrap ( ) ;
2025
assert! ( matches! (
2026
a.authenticate_change ( & h) .unwrap ( ) ,
2027
SignatureStatus:: Valid { key: k, .. } if k = = key
2028
) ) ;
2029
2030
let ( none, _ , _ ) = agent ( " none" , None ) ;
2031
let r = none.push ( " origin" , false ) .unwrap ( ) ;
2032
assert! ( r.iter ( ) .all ( | x | ! x.ok) , " no grant: {r:?}" ) ;
2033
let mallory_sk = SecretKey:: generate( ) ;
2034
let mallory = mallory_sk.identity ( " Mallory" , " [email protected] " , 1 ) ;
2035
let ( stranger, _ , _ ) = agent (
2036
" stranger" ,
2037
Some ( grant ( & mallory, & mallory_sk, now - 60_000 , now + 600_000 ) ) ,
2038
) ;
2039
let r = stranger.push ( " origin" , false ) .unwrap ( ) ;
2040
assert! ( r.iter ( ) .all ( | x | ! x.ok) , " a stranger's grant: {r:?}" ) ;
2041
let ( ended, _ , eh) = agent (
2042
" ended" ,
2043
Some ( grant ( & ada_id, & ada_sk, now - 7_200_000 , now - 3_600_000 ) ) ,
2044
) ;
2045
let r = ended.push ( " origin" , false ) .unwrap ( ) ;
2046
assert! ( r.iter ( ) .all ( | x | ! x.ok) , " an ended grant: {r:?}" ) ;
2047
assert! ( matches! (
2048
ended.authenticate_change ( & eh) .unwrap ( ) ,
2049
SignatureStatus:: Invalid( e) if e.contains ( " grant did not cover" )
2050
) ) ;
2051
}
2052
2053
# [ test ]
83e0677b Andy McCall 1 month ago ↶
2054
fn trust_policy_governs_pushes_and_fetches ( ) {
2055
hermetic ( ) ;
2056
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2057
let root = dir.path ( ) ;
2058
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
2059
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
2060
2061
75167672 Andy McCall 1 month ago
2062
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
2063
.unwrap ( )
2064
.with_home ( root.join ( " home-a" ) ) ;
2065
let ( key_a, _ ) = a
2066
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
2067
.unwrap ( ) ;
2068
let mut b = Repo:: clone( & url, & root.join ( " b" ) )
2069
.unwrap ( )
2070
.with_home ( root.join ( " home-b" ) ) ;
83e0677b Andy McCall 1 month ago ↶
2071
write ( & root.join ( " b" ) , " b.txt" , b " b\n " ) ;
2072
b.commit ( " unsigned before policy" , & [ ] ) .unwrap ( ) ;
2073
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
2074
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2075
2076
2077
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
2078
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
2079
let sp = a.policy_init ( ) .unwrap ( ) ;
2080
assert_eq! ( sp.policy.serial, 1 ) ;
2081
a.pull ( " origin" ) .unwrap ( ) ;
2082
let results = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
2083
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2084
assert! ( results.iter ( ) .all ( | r | r.ok ) , " {results:?}" ) ;
2085
assert_eq! ( origin.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 1 ) ;
2086
2087
2088
b.pull ( " origin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2089
assert! (
2090
b.current_policy ( ) .unwrap ( ) .is_some ( ) ,
2091
" the policy arrived with the fetch"
2092
) ;
83e0677b Andy McCall 1 month ago ↶
2093
assert! ( b.push ( " origin" , false ) .is_err ( ) ) ;
2094
75167672 Andy McCall 1 month ago
2095
let ( key_b, id_b) = b
2096
.create_identity ( " Bob" , " [email protected] " , IdentityScope:: User)
2097
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2098
write ( & root.join ( " b" ) , " b.txt" , b " b2\n " ) ;
2099
let h_b2 = b.commit ( " b2 as Bob" , & [ ] ) .unwrap ( ) ;
2100
assert_eq! ( b.store ( ) .get_change ( & h_b2) .unwrap ( ) .author, key_b) ;
2101
let r = b.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
2102
origin.refresh ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2103
assert! (
2104
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " may not update" ) ,
2105
" {:?}" ,
2106
r
2107
) ;
83e0677b Andy McCall 1 month ago ↶
2108
2109
2110
let sp = a.policy_grant ( id_b.clone ( ) , None ) .unwrap ( ) ;
2111
assert_eq! ( sp.policy.serial, 2 ) ;
2112
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
2113
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2114
assert! ( r.iter ( ) .any ( | x | x.name = = " policy" & & x.ok ) , " {r:?}" ) ;
2115
75167672 Andy McCall 1 month ago
2116
assert! ( b
2117
.push ( " origin" , false )
2118
.unwrap_or_else ( | e | vec! [ RefResult {
2119
name: " main" .into ( ) ,
2120
ok: false ,
2121
reason: e.to_string ( )
2122
} ] )
2123
.iter ( )
2124
.all ( | x | ! x.ok) ) ;
83e0677b Andy McCall 1 month ago ↶
2125
b.pull ( " origin" ) .unwrap ( ) ;
2126
assert_eq! ( b.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 2 ) ;
2127
let r = b.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
2128
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2129
assert! ( r[ 0 ] .ok, " {:?}" , r) ;
2130
2131
let heads = origin.bookmark_heads ( ) .unwrap ( ) ;
2132
for h in & heads {
2133
let c = origin.store ( ) .get_change ( h) .unwrap ( ) ;
2134
if c.author = = key_b {
75167672 Andy McCall 1 month ago
2135
origin
2136
.change_allowed (
2137
h,
2138
& origin.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy,
2139
" main" ,
2140
)
2141
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2142
}
2143
}
2144
2145
2146
let mut forged = b.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.clone ( ) ;
2147
forged.serial = 3 ;
2148
forged.roots = vec! [ key_b] ;
2149
let fh = forged.hash ( ) ;
2150
let ( _ , sk_b) = b.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2151
let forged = SignedPolicy {
2152
policy: forged,
2153
sigs: vec! [ sk_b.sign ( Domain:: Policy, & key_b, & fh) ] ,
2154
} ;
2155
let r = origin
2156
.receive_push ( Bundle:: default( ) , vec! [ ] , Some ( forged) , vec! [ ] )
2157
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2158
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " root" ) , " {:?}" , r) ;
2159
let stale = a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1 ;
2160
let mut old = stale.clone ( ) ;
2161
old.policy.serial = 1 ;
75167672 Andy McCall 1 month ago
2162
let r = origin
2163
.receive_push ( Bundle:: default( ) , vec! [ ] , Some ( old) , vec! [ ] )
2164
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2165
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " serial" ) , " {:?}" , r) ;
2166
2167
2168
a.pull ( " origin" ) .unwrap ( ) ;
2169
a.policy_revoke ( & key_b) .unwrap ( ) ;
2170
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
2171
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2172
assert! ( r.iter ( ) .any ( | x | x.name = = " policy" & & x.ok ) , " {r:?}" ) ;
2173
b.pull ( " origin" ) .unwrap ( ) ;
2174
write ( & root.join ( " b" ) , " b.txt" , b " b3\n " ) ;
2175
b.commit ( " b3" , & [ ] ) .unwrap ( ) ;
2176
let r = b.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
2177
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2178
assert! ( ! r[ 0 ] .ok, " {:?}" , r) ;
2179
2180
75167672 Andy McCall 1 month ago
2181
let c = Repo:: clone( & url, & root.join ( " c" ) )
2182
.unwrap ( )
2183
.with_home ( root.join ( " home-c" ) ) ;
83e0677b Andy McCall 1 month ago ↶
2184
assert_eq! ( c.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 3 ) ;
75167672 Andy McCall 1 month ago
2185
assert! ( c
2186
.current_policy ( )
2187
.unwrap ( )
2188
.unwrap ( )
2189
.1
2190
.policy
2191
.roots
2192
.contains ( & key_a) ) ;
83e0677b Andy McCall 1 month ago ↶
2193
assert! ( ! c.snapshot ( " status" ) .unwrap ( ) .changed) ;
2194
}
d02e545d Andy McCall 1 month ago ↶
2195
9ae4d15a Andy McCall 1 month ago ↶
2196
d02e545d Andy McCall 1 month ago ↶
2197
# [ test ]
9ae4d15a Andy McCall 1 month ago ↶
2198
fn gc_reports_its_phases ( ) {
2199
hermetic ( ) ;
2200
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2201
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
2202
for i in 0 .. 3 {
2203
write (
2204
dir.path ( ) ,
2205
& format! ( " f{i} .txt" ) ,
2206
format! ( " {i} \n " ) .as_bytes ( ) ,
2207
) ;
2208
repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ;
2209
}
2210
let mut phases = Vec :: new( ) ;
2211
repo.gc_with ( true , & mut | p| phases.push ( p.to_string ( ) ) )
2212
.unwrap ( ) ;
2213
assert_eq! ( phases[ 0 ] , " finding what is reachable" , " {phases:?}" ) ;
2214
assert! (
2215
phases.iter ( ) .any ( | p | p.starts_with ( " pruning" ) ) ,
2216
" {phases:?}"
2217
) ;
2218
assert! (
2219
phases.iter ( ) .any ( | p | p.starts_with ( " packing 0 of " ) ) ,
2220
" {phases:?}"
2221
) ;
2222
assert! (
2223
phases
2224
.iter ( )
2225
.any ( | p | p.starts_with ( " writing the index of " ) ) ,
2226
" {phases:?}"
2227
) ;
2228
}
2229
2230
# [ test ]
d02e545d Andy McCall 1 month ago ↶
2231
fn gc_packs_objects_and_history_still_works ( ) {
2232
hermetic ( ) ;
2233
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2234
let root = dir.path ( ) ;
2235
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
2236
repo.create_identity ( " Ada" , " a@b" , IdentityScope:: User)
2237
.unwrap ( ) ;
d02e545d Andy McCall 1 month ago ↶
2238
repo.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
2239
write ( root, " a.txt" , b " one\n " ) ;
2240
let h1 = repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
2241
write ( root, " a.txt" , b " two\n " ) ;
2242
write ( root, " big.bin" , & vec! [ 0 u8 ; 100_000 ] ) ;
2243
let h2 = repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
ba160f60 Andy McCall 1 month ago ↶
2244
2245
for i in 0 .. 5 {
2246
write ( root, " a.txt" , format! ( " two\n {i} \n " ) .as_bytes ( ) ) ;
2247
repo.snapshot ( " status" ) .unwrap ( ) ;
2248
}
2249
write ( root, " a.txt" , b " two\n " ) ;
2250
repo.snapshot ( " status" ) .unwrap ( ) ;
d02e545d Andy McCall 1 month ago ↶
2251
let before = repo.store ( ) .list ( ) .unwrap ( ) ;
2252
assert! ( repo.store ( ) .loose_count ( ) .unwrap ( ) > 0 ) ;
ba160f60 Andy McCall 1 month ago ↶
2253
2254
2255
let r = repo.gc ( true ) .unwrap ( ) ;
2256
assert_eq! ( r.packed.objects, before.len ( ) ) ;
2257
assert_eq! ( r.pruned.objects, 0 ) ;
d02e545d Andy McCall 1 month ago ↶
2258
assert_eq! ( repo.store ( ) .loose_count ( ) .unwrap ( ) , 0 ) ;
2259
assert_eq! ( repo.store ( ) .list ( ) .unwrap ( ) , before) ;
ba160f60 Andy McCall 1 month ago ↶
2260
2261
2262
2263
repo.config.set ( " core.op-retention" , " 0" ) .unwrap ( ) ;
2264
repo.save_config ( ) .unwrap ( ) ;
21b2aec2 Andy McCall 1 month ago ↶
2265
let mut repo = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
ba160f60 Andy McCall 1 month ago ↶
2266
let r = repo.gc ( true ) .unwrap ( ) ;
2267
assert! ( r.pruned.objects > = 5 , " {r:?}" ) ;
2268
let after = repo.store ( ) .list ( ) .unwrap ( ) ;
2269
assert! ( after.len ( ) < before.len ( ) ) ;
2270
assert! ( after.contains ( & h1) & & after.contains ( & h2) ) ;
2271
for ( _ , op) in repo.op_log ( ) .unwrap ( ) {
2272
assert! ( ! op.command.is_empty ( ) , " operation objects are all kept" ) ;
2273
}
2274
d02e545d Andy McCall 1 month ago ↶
2275
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
75167672 Andy McCall 1 month ago
2276
assert! ( matches! (
2277
repo.verify_change ( & h2) .unwrap ( ) ,
2278
SignatureStatus:: Valid { .. }
2279
) ) ;
d02e545d Andy McCall 1 month ago ↶
2280
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 3 ) ;
2281
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
2282
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n " ) ;
2283
repo.checkout ( & [ h2] , false , " checkout" ) .unwrap ( ) ;
2284
assert_eq! ( fs:: read( root.join ( " big.bin" ) ) .unwrap ( ) .len ( ) , 100_000 ) ;
2285
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
ba160f60 Andy McCall 1 month ago ↶
2286
d02e545d Andy McCall 1 month ago ↶
2287
write ( root, " a.txt" , b " three\n " ) ;
2288
repo.commit ( " three" , & [ ] ) .unwrap ( ) ;
ba160f60 Andy McCall 1 month ago ↶
2289
let r = repo.gc ( false ) .unwrap ( ) ;
2290
assert_eq! ( r.pruned.objects, 0 ) ;
2291
assert! ( r.pruned.deferred > 0 ) ;
2292
let r = repo.gc ( true ) .unwrap ( ) ;
2293
assert! ( r.pruned.objects > 0 ) ;
2294
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 ) ;
21b2aec2 Andy McCall 1 month ago ↶
2295
2296
2297
repo.config.set ( " core.op-retention" , " 90" ) .unwrap ( ) ;
2298
repo.save_config ( ) .unwrap ( ) ;
2299
let repo = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2300
let r = repo.gc ( false ) .unwrap ( ) ;
2301
assert_eq! ( r.pruned.objects, 0 ) ;
2302
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 ) ;
d02e545d Andy McCall 1 month ago ↶
2303
}
614541c3 Andy McCall 1 month ago ↶
2304
2305
# [ test ]
2306
fn describe_amend_abandon_heads ( ) {
2307
hermetic ( ) ;
2308
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2309
let root = dir.path ( ) ;
2310
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
2311
repo.create_identity ( " Ada" , " a@b" , IdentityScope:: User)
2312
.unwrap ( ) ;
614541c3 Andy McCall 1 month ago ↶
2313
repo.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
2314
write ( root, " a.txt" , b " one\n " ) ;
2315
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
2316
write ( root, " a.txt" , b " one\n two\n " ) ;
2317
let h2 = repo.commit ( " secnod" , & [ ] ) .unwrap ( ) ;
2318
let id2 = repo.store ( ) .get_change ( & h2) .unwrap ( ) .change_id;
2319
2320
2321
let h2b = repo.describe ( & h2, " second" ) .unwrap ( ) ;
2322
assert_ne! ( h2b, h2) ;
2323
let c = repo.store ( ) .get_change ( & h2b) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2324
assert_eq! (
2325
( c.change_id, c.message.as_str ( ) , c.deps.clone ( ) ) ,
2326
( id2, " second" , vec! [ h1] )
2327
) ;
614541c3 Andy McCall 1 month ago ↶
2328
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2b] ) ;
75167672 Andy McCall 1 month ago
2329
assert! ( matches! (
2330
repo.verify_change ( & h2b) .unwrap ( ) ,
2331
SignatureStatus:: Valid { .. }
2332
) ) ;
614541c3 Andy McCall 1 month ago ↶
2333
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
2334
assert! ( s.statuses.is_empty ( ) , " {:?}" , s.statuses) ;
75167672 Andy McCall 1 month ago
2335
assert_eq! (
2336
repo.resolve_change ( & id2.short ( 8 ) ) .unwrap ( ) ,
2337
h2b,
2338
" the old object is no longer reachable from the log"
2339
) ;
c07e0e7d Andy McCall 1 month ago ↶
2340
2341
let h1b = repo.describe ( & h1, " first, described" ) .unwrap ( ) ;
2342
assert_ne! ( h1b, h1) ;
2343
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
2344
assert_eq! ( heads.len ( ) , 1 ) ;
2345
let top = repo.store ( ) .get_change ( & heads[ 0 ] ) .unwrap ( ) ;
2346
assert_eq! ( ( top.change_id, top.deps.clone ( ) ) , ( id2, vec! [ h1b] ) ) ;
75167672 Andy McCall 1 month ago
2347
assert! (
2348
matches! (
2349
repo.verify_change ( & heads[ 0 ] ) .unwrap ( ) ,
2350
SignatureStatus:: Valid { .. }
2351
) ,
2352
" rewritten own change is re-signed"
2353
) ;
c07e0e7d Andy McCall 1 month ago ↶
2354
let ( _ , wc) = repo.working_copy ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2355
assert_eq! (
2356
wc.deps,
2357
vec! [ heads[ 0 ] ] ,
2358
" the working copy follows the rewrite"
2359
) ;
c07e0e7d Andy McCall 1 month ago ↶
2360
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
2361
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
2362
assert! (
2363
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n " )
2364
) ;
614541c3 Andy McCall 1 month ago ↶
2365
2366
2367
write ( root, " a.txt" , b " one\n two\n three\n " ) ;
2368
write ( root, " b.txt" , b " b\n " ) ;
2369
let h2c = repo.amend ( None ) .unwrap ( ) ;
2370
let c = repo.store ( ) .get_change ( & h2c) .unwrap ( ) ;
2371
assert_eq! ( c.change_id, id2) ;
2372
assert_eq! ( c.message, " second" ) ;
2373
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2c] ) ;
75167672 Andy McCall 1 month ago
2374
assert_eq! (
2375
repo.log ( ) .unwrap ( ) .len ( ) ,
2376
3 ,
2377
" working copy, amended head, first"
2378
) ;
614541c3 Andy McCall 1 month ago ↶
2379
let snap = repo.load_state ( & [ h2c] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
2380
assert! (
2381
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n three\n " )
2382
) ;
614541c3 Andy McCall 1 month ago ↶
2383
assert! ( snap.files.contains_key ( " b.txt" ) ) ;
2384
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
75167672 Andy McCall 1 month ago
2385
assert! (
2386
repo.amend ( None ) .is_err ( ) ,
2387
" clean working copy and no message"
2388
) ;
614541c3 Andy McCall 1 month ago ↶
2389
let h2d = repo.amend ( Some ( " second, amended" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2390
assert_eq! (
2391
repo.store ( ) .get_change ( & h2d) .unwrap ( ) .message,
2392
" second, amended"
2393
) ;
614541c3 Andy McCall 1 month ago ↶
2394
2395
2396
repo.abandon ( & h2d) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2397
assert_eq! (
2398
repo.bookmark_heads ( ) .unwrap ( ) ,
2399
vec! [ h1b] ,
2400
" the described first change is the head again"
2401
) ;
614541c3 Andy McCall 1 month ago ↶
2402
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2403
let paths: Vec< _ > = s
2404
.statuses
2405
.iter ( )
2406
.map ( | p | ( p.path.as_str ( ) , p.status) )
2407
.collect ( ) ;
2408
assert_eq! (
2409
paths,
2410
vec! [
2411
( " a.txt" , ChangeStatus:: Modified) ,
2412
( " b.txt" , ChangeStatus:: Added)
2413
]
2414
) ;
614541c3 Andy McCall 1 month ago ↶
2415
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n two\n three\n " ) ;
2416
let h3 = repo.commit ( " recommitted" , & [ ] ) .unwrap ( ) ;
2417
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h3] ) ;
2418
2419
repo.undo ( ) .unwrap ( ) ;
2420
repo.undo ( ) .unwrap ( ) ;
2421
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2d] ) ;
c07e0e7d Andy McCall 1 month ago ↶
2422
6706bdcf Andy McCall 1 month ago ↶
2423
2424
2425
c07e0e7d Andy McCall 1 month ago ↶
2426
let base = repo.bookmark_heads ( ) .unwrap ( ) [ 0 ] ;
2427
let base_change = repo.store ( ) .get_change ( & base) .unwrap ( ) ;
2428
let h1_now = base_change.deps[ 0 ] ;
6706bdcf Andy McCall 1 month ago ↶
2429
let notes = repo.abandon ( & h1_now) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2430
assert! (
2431
! notes.is_empty ( ) ,
2432
" the dependent's edit of a.txt was reified"
2433
) ;
6706bdcf Andy McCall 1 month ago ↶
2434
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
2435
let after = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
2436
assert! (
2437
! after.files.contains_key ( " a.txt" ) & & after.files.contains_key ( " b.txt" ) ,
2438
" {:?}" ,
2439
after.files.keys ( )
2440
) ;
6706bdcf Andy McCall 1 month ago ↶
2441
repo.undo ( ) .unwrap ( ) ;
2442
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ base] ) ;
c07e0e7d Andy McCall 1 month ago ↶
2443
write ( root, " c.txt" , b " c\n " ) ;
2444
let hc = repo.commit ( " c" , & [ ] ) .unwrap ( ) ;
2445
write ( root, " d.txt" , b " d\n " ) ;
2446
let hd = repo.commit ( " d" , & [ hc] ) .unwrap ( ) ;
2447
assert_eq! ( repo.store ( ) .get_change ( & hd) .unwrap ( ) .deps, vec! [ hc] ) ;
2448
repo.abandon ( & hc) .unwrap ( ) ;
2449
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
2450
assert! ( ! heads.contains ( & hc) ) ;
75167672 Andy McCall 1 month ago
2451
let d_now = heads
2452
.iter ( )
2453
.find ( | h | repo.store ( ) .get_change ( h) .unwrap ( ) .message = = " d" )
2454
.copied ( )
2455
.unwrap ( ) ;
2456
assert! (
2457
repo.store ( ) .get_change ( & d_now) .unwrap ( ) .deps.is_empty ( ) ,
2458
" d inherited c's (empty) dependencies"
2459
) ;
c07e0e7d Andy McCall 1 month ago ↶
2460
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2461
assert_eq! (
2462
s.statuses
2463
.iter ( )
2464
.map ( | p | p.path.as_str ( ) )
2465
.collect:: < Vec< _ > > ( ) ,
2466
vec! [ " c.txt" ] ,
2467
" c's content is now uncommitted work"
2468
) ;
614541c3 Andy McCall 1 month ago ↶
2469
}
79ab8528 Andy McCall 1 month ago ↶
2470
2471
# [ test ]
2472
fn git_export_mirrors_history ( ) {
2473
hermetic ( ) ;
75167672 Andy McCall 1 month ago
2474
if std:: process:: Command:: new( " git" )
2475
.arg ( " --version" )
2476
.output ( )
2477
.is_err ( )
2478
{
79ab8528 Andy McCall 1 month ago ↶
2479
eprintln! ( " git not installed; skipping" ) ;
2480
return ;
2481
}
2482
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2483
let root = dir.path ( ) ;
75167672 Andy McCall 1 month ago
2484
let mut repo = Repo:: init( & root.join ( " r" ) )
2485
.unwrap ( )
2486
.with_home ( root.join ( " home" ) ) ;
2487
repo.create_identity ( " Ada Lovelace" , " [email protected] " , IdentityScope:: User)
2488
.unwrap ( ) ;
79ab8528 Andy McCall 1 month ago ↶
2489
let r = root.join ( " r" ) ;
2490
write ( & r, " README.md" , b " hello\n " ) ;
2491
write ( & r, " src/main.rs" , b " fn main() {}\n " ) ;
2492
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
2493
write ( & r, " src/main.rs" , b " fn main() { run(); }\n " ) ;
2494
write ( & r, " logo.png" , b " \x89 PNG\0 \0 " ) ;
2495
let h2 = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
2496
2497
let git_dir = root.join ( " mirror.git" ) ;
2498
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
2499
assert_eq! ( ( rep.exported, rep.skipped, rep.heads) , ( 2 , 0 , 1 ) ) ;
2500
let git = | args : & [& str ]| -> String {
75167672 Andy McCall 1 month ago
2501
let out = std:: process:: Command:: new( " git" )
2502
.arg ( " -C" )
2503
.arg ( & git_dir)
2504
.args ( args)
2505
.output ( )
2506
.unwrap ( ) ;
2507
assert! (
2508
out.status.success ( ) ,
2509
" {}" ,
2510
String :: from_utf8_lossy( & out.stderr)
2511
) ;
79ab8528 Andy McCall 1 month ago ↶
2512
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
2513
} ;
2514
assert_eq! ( git ( & [ " log" , " --format=%s" , " main" ] ) , " second\n first" ) ;
2515
assert_eq! ( git ( & [ " show" , " main:src/main.rs" ] ) , " fn main() { run(); }" ) ;
2516
assert_eq! ( git ( & [ " show" , " main:README.md" ] ) , " hello" ) ;
2517
let body = git ( & [ " log" , " -1" , " --format=%B" , " main" ] ) ;
75167672 Andy McCall 1 month ago
2518
assert! (
2519
body.contains ( & format! ( " Oxy-Hash: {} " , h2.to_hex ( ) ) ) ,
2520
" {body}"
2521
) ;
79ab8528 Andy McCall 1 month ago ↶
2522
assert! ( body.contains ( " Change-Id: " ) , " {body}" ) ;
75167672 Andy McCall 1 month ago
2523
assert_eq! (
2524
git ( & [ " log" , " -1" , " --format=%an <%ae>" , " main" ] ) ,
2525
" Ada Lovelace <[email protected] >"
2526
) ;
2527
assert_eq! (
2528
git ( & [ " ls-tree" , " --name-only" , " main" ] ) ,
2529
" README.md\n logo.png\n src"
2530
) ;
79ab8528 Andy McCall 1 month ago ↶
2531
2532
2533
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
2534
assert_eq! ( ( rep.exported, rep.skipped) , ( 0 , 2 ) ) ;
2535
2536
2537
2538
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
2539
write ( & r, " other.txt" , b " o\n " ) ;
2540
let h3 = repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
2541
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
2542
assert_eq! ( ( rep.exported, rep.heads) , ( 1 , 2 ) ) ;
2543
assert_eq! ( git ( & [ " log" , " --format=%s" , " main" ] ) , " third\n second\n first" ) ;
2544
let parents = git ( & [ " rev-list" , " --parents" , " -1" , " main" ] ) ;
2545
assert_eq! ( parents.split ( ' ' ) .count ( ) , 2 , " one parent: {parents}" ) ;
2546
assert_eq! ( git ( & [ " show" , " main:other.txt" ] ) , " o" ) ;
2547
assert_eq! ( git ( & [ " show" , " main:src/main.rs" ] ) , " fn main() { run(); }" ) ;
75167672 Andy McCall 1 month ago
2548
assert_eq! (
2549
git ( & [ " ls-tree" , " --name-only" , " main" ] ) ,
2550
" README.md\n logo.png\n other.txt\n src"
2551
) ;
79ab8528 Andy McCall 1 month ago ↶
2552
let body = git ( & [ " log" , " -1" , " --format=%B" , " main" ] ) ;
75167672 Andy McCall 1 month ago
2553
assert! (
2554
body.contains ( & format! ( " Oxy-Hash: {} " , h3.to_hex ( ) ) ) & & body.contains ( " Oxy-State: " ) ,
2555
" {body}"
2556
) ;
79ab8528 Andy McCall 1 month ago ↶
2557
2558
repo.describe ( & h3, " third, described" ) .unwrap ( ) ;
2559
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
2560
assert_eq! ( rep.exported, 1 ) ;
75167672 Andy McCall 1 month ago
2561
assert_eq! (
2562
git ( & [ " log" , " -1" , " --format=%s" , " main" ] ) ,
2563
" third, described"
2564
) ;
79ab8528 Andy McCall 1 month ago ↶
2565
assert_eq! ( git ( & [ " log" , " --format=%s" , " main" ] ) .lines ( ) .count ( ) , 4 ) ;
2566
let _ = h2;
2567
}
c3069537 Andy McCall 1 month ago ↶
2568
2569
# [ test ]
2570
fn git_import_reproduces_trees ( ) {
c3d2ef61 Andy McCall 1 month ago ↶
2571
git_import_reproduces_trees_with ( None ) ;
2572
}
2573
2574
2575
2576
# [ test ]
2577
fn git_import_reproduces_trees_in_batches ( ) {
2578
git_import_reproduces_trees_with ( Some ( 1 ) ) ;
2579
}
2580
2581
fn git_import_reproduces_trees_with ( batch : Option< usize > ) {
2582
crate :: repo:: git:: set_merge_batch_for_tests( batch) ;
c3069537 Andy McCall 1 month ago ↶
2583
hermetic ( ) ;
75167672 Andy McCall 1 month ago
2584
if std:: process:: Command:: new( " git" )
2585
.arg ( " --version" )
2586
.output ( )
2587
.is_err ( )
2588
{
c3069537 Andy McCall 1 month ago ↶
2589
eprintln! ( " git not installed; skipping" ) ;
2590
return ;
2591
}
2592
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2593
let root = dir.path ( ) ;
2594
let g = root.join ( " src.git" ) ;
2595
fs:: create_dir_all( & g) .unwrap ( ) ;
2596
let git = | args : & [& str ]| -> String {
2597
let out = std:: process:: Command:: new( " git" )
2598
.arg ( " -C" )
2599
.arg ( & g)
75167672 Andy McCall 1 month ago
2600
.args ( [
2601
" -c" ,
2602
" user.name=Grace" ,
2603
" -c" ,
2604
" [email protected] " ,
2605
" -c" ,
2606
" commit.gpgsign=false" ,
2607
] )
c3069537 Andy McCall 1 month ago ↶
2608
.args ( args)
2609
.output ( )
2610
.unwrap ( ) ;
75167672 Andy McCall 1 month ago
2611
assert! (
2612
out.status.success ( ) ,
2613
" git {args:?}: {}" ,
2614
String :: from_utf8_lossy( & out.stderr)
2615
) ;
c3069537 Andy McCall 1 month ago ↶
2616
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
2617
} ;
2618
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
2619
write ( & g, " README.md" , b " # Hi\n " ) ;
2620
write ( & g, " src/lib.rs" , b " pub fn a() {}\n " ) ;
2621
git ( & [ " add" , " ." ] ) ;
2622
git ( & [ " commit" , " -q" , " -m" , " initial" ] ) ;
2623
write ( & g, " src/lib.rs" , b " pub fn a() {}\n pub fn b() {}\n " ) ;
2624
write ( & g, " run.sh" , b " #!/bin/sh\n " ) ;
2625
# [ cfg ( unix ) ]
2626
{
2627
use std:: os:: unix:: fs:: PermissionsExt;
2628
fs:: set_permissions( g.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
2629
}
2630
write ( & g, " logo.png" , b " \x89 PNG\0 \0 \x01 " ) ;
2631
git ( & [ " add" , " ." ] ) ;
75167672 Andy McCall 1 month ago
2632
git ( & [
2633
" commit" ,
2634
" -q" ,
2635
" -m" ,
2636
" second\n \n Change-Id: 0123456789abcdef0123456789abcdef" ,
2637
] ) ;
c3069537 Andy McCall 1 month ago ↶
2638
2639
git ( & [ " checkout" , " -q" , " -b" , " topic" , " HEAD~1" ] ) ;
2640
write ( & g, " README.md" , b " # Hi\n \n More.\n " ) ;
2641
git ( & [ " commit" , " -q" , " -am" , " readme" ] ) ;
2642
git ( & [ " checkout" , " -q" , " main" ] ) ;
2643
git ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge topic" , " topic" ] ) ;
2644
2645
2646
let initial = git ( & [ " rev-list" , " --max-parents=0" , " HEAD" ] ) ;
2647
git ( & [ " checkout" , " -q" , " -b" , " clash" , & initial] ) ;
2648
write ( & g, " src/lib.rs" , b " pub fn a() {}\n pub fn c() {}\n " ) ;
2649
git ( & [ " commit" , " -q" , " -am" , " clash" ] ) ;
2650
git ( & [ " checkout" , " -q" , " main" ] ) ;
7df9007b Andy McCall 1 month ago ↶
2651
2652
2653
75167672 Andy McCall 1 month ago
2654
let _ = std:: process:: Command:: new( " git" )
2655
.arg ( " -C" )
2656
.arg ( & g)
7df9007b Andy McCall 1 month ago ↶
2657
.args ( [
2658
" -c" ,
2659
" user.name=Grace" ,
2660
" -c" ,
2661
" [email protected] " ,
2662
" merge" ,
2663
" -q" ,
2664
" clash" ,
2665
] )
75167672 Andy McCall 1 month ago
2666
.output ( ) ;
7df9007b Andy McCall 1 month ago ↶
2667
assert! (
2668
g.join ( " .git/MERGE_HEAD" ) .exists ( ) ,
2669
" the clash merge stopped at its conflict"
2670
) ;
75167672 Andy McCall 1 month ago
2671
write (
2672
& g,
2673
" src/lib.rs" ,
2674
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n " ,
2675
) ;
c3069537 Andy McCall 1 month ago ↶
2676
git ( & [ " add" , " ." ] ) ;
2677
git ( & [ " commit" , " -q" , " -m" , " resolve clash" ] ) ;
2678
git ( & [ " rm" , " -q" , " logo.png" ] ) ;
2679
git ( & [ " commit" , " -q" , " -m" , " drop logo" ] ) ;
6e723b81 Andy McCall 1 month ago ↶
2680
2681
git ( & [ " mv" , " src/lib.rs" , " src/lib2.rs" ] ) ;
75167672 Andy McCall 1 month ago
2682
write (
2683
& g,
2684
" src/lib2.rs" ,
2685
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n pub fn d() {}\n " ,
2686
) ;
6e723b81 Andy McCall 1 month ago ↶
2687
git ( & [ " add" , " ." ] ) ;
2688
git ( & [ " commit" , " -q" , " -m" , " rename lib" ] ) ;
958fb27d Andy McCall 1 month ago ↶
2689
2690
2691
2692
git ( & [ " checkout" , " -q" , " -b" , " discarded" ] ) ;
2693
write ( & g, " run.sh" , b " #!/bin/sh\n echo discarded\n " ) ;
2694
git ( & [ " commit" , " -q" , " -am" , " discarded edit" ] ) ;
2695
git ( & [ " checkout" , " -q" , " main" ] ) ;
314db9b4 Andy McCall 1 month ago ↶
2696
git ( & [
2697
" merge" ,
2698
" -q" ,
2699
" -s" ,
2700
" ours" ,
2701
" -m" ,
2702
" merge discarded, keeping ours" ,
2703
" discarded" ,
2704
] ) ;
c3069537 Andy McCall 1 month ago ↶
2705
let expected_count: usize = git ( & [ " rev-list" , " --count" , " main" ] ) .parse ( ) .unwrap ( ) ;
2706
2707
let dest = root.join ( " oxy" ) ;
2708
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2709
let rep = repo.import_git ( & g, " main" ) .unwrap ( ) ;
2710
assert_eq! ( rep.commits, expected_count) ;
958fb27d Andy McCall 1 month ago ↶
2711
assert_eq! ( rep.merges, 3 ) ;
314db9b4 Andy McCall 1 month ago ↶
2712
assert_eq! (
2713
fs:: read( dest.join ( " run.sh" ) ) .unwrap ( ) ,
2714
b " #!/bin/sh\n " ,
2715
" the ours merge kept main's run.sh"
2716
) ;
958fb27d Andy McCall 1 month ago ↶
2717
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
2718
assert_eq! ( v.commits, expected_count, " every commit's tree reproduced" ) ;
2719
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
c3069537 Andy McCall 1 month ago ↶
2720
75167672 Andy McCall 1 month ago
2721
assert_eq! (
2722
fs:: read( dest.join ( " src/lib2.rs" ) ) .unwrap ( ) ,
2723
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n pub fn d() {}\n "
2724
) ;
6e723b81 Andy McCall 1 month ago ↶
2725
assert! ( ! dest.join ( " src/lib.rs" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
2726
assert_eq! (
2727
fs:: read( dest.join ( " README.md" ) ) .unwrap ( ) ,
2728
b " # Hi\n \n More.\n "
2729
) ;
c3069537 Andy McCall 1 month ago ↶
2730
assert! ( ! dest.join ( " logo.png" ) .exists ( ) ) ;
2731
# [ cfg ( unix ) ]
2732
{
2733
use std:: os:: unix:: fs:: PermissionsExt;
75167672 Andy McCall 1 month ago
2734
assert! (
2735
fs:: metadata( dest.join ( " run.sh" ) )
2736
.unwrap ( )
2737
.permissions ( )
2738
.mode ( )
2739
& 0o111
2740
! = 0
2741
) ;
c3069537 Andy McCall 1 month ago ↶
2742
}
75167672 Andy McCall 1 month ago
2743
assert! (
2744
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
2745
" clean after import"
2746
) ;
c3069537 Andy McCall 1 month ago ↶
2747
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
2748
let st = repo.load_state ( & heads) .unwrap ( ) ;
2749
assert! ( ! st.materialize ( ) .is_conflicted ( ) ) ;
2750
assert_eq! ( st.len ( ) , expected_count) ;
75167672 Andy McCall 1 month ago
2751
let tip = repo
2752
.log ( )
2753
.unwrap ( )
2754
.into_iter ( )
2755
.find ( | e | e.change.message.starts_with ( " rename lib" ) )
2756
.unwrap ( ) ;
6e723b81 Andy McCall 1 month ago ↶
2757
let c = repo.store ( ) .get_change ( & tip.hash) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2758
assert! (
2759
c.tree_ops
2760
.iter ( )
2761
.any ( | op | matches! ( op, TreeOp:: Move { name, .. } if name = = " lib2.rs" ) ) ,
2762
" {:?}" ,
2763
c.tree_ops
2764
) ;
2765
assert! ( ! c
2766
.tree_ops
2767
.iter ( )
2768
.any ( | op | matches! ( op, TreeOp:: AddNode { .. } ) ) ) ;
c3069537 Andy McCall 1 month ago ↶
2769
2770
let log = repo.log ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2771
let second = log
2772
.iter ( )
2773
.find ( | e | e.change.message.starts_with ( " second" ) )
2774
.unwrap ( ) ;
2775
assert_eq! (
2776
second.change.change_id,
2777
ChangeId:: from_hex( " 0123456789abcdef0123456789abcdef" ) .unwrap ( )
2778
) ;
2779
assert! ( second
2780
.change
2781
.message
2782
.contains ( " Git-Author: Grace <[email protected] >" ) ) ;
2783
let resolve = log
2784
.iter ( )
2785
.find ( | e | e.change.message.starts_with ( " resolve clash" ) )
2786
.unwrap ( ) ;
2787
assert_eq! (
2788
resolve.change.kind,
2789
ChangeKind:: Resolution,
2790
" a conflicting Git merge becomes a resolution"
2791
) ;
c3069537 Andy McCall 1 month ago ↶
2792
75167672 Andy McCall 1 month ago
2793
let repo2 = Repo:: init( & root.join ( " oxy2" ) )
2794
.unwrap ( )
2795
.with_home ( root.join ( " home" ) ) ;
c3069537 Andy McCall 1 month ago ↶
2796
repo2.import_git ( & g, " main" ) .unwrap ( ) ;
dfb23a3a Andy McCall 1 month ago ↶
2797
2798
2799
assert_eq! (
2800
repo2.bookmark_heads ( ) .unwrap ( ) ,
2801
repo.bookmark_heads ( ) .unwrap ( )
2802
) ;
75167672 Andy McCall 1 month ago
2803
let ids: BTreeSet< ChangeId> = repo
2804
.log ( )
2805
.unwrap ( )
2806
.iter ( )
2807
.filter ( | e | ! e.is_working_copy)
2808
.map ( | e | e.change.change_id )
2809
.collect ( ) ;
2810
let ids2: BTreeSet< ChangeId> = repo2
2811
.log ( )
2812
.unwrap ( )
2813
.iter ( )
2814
.filter ( | e | ! e.is_working_copy)
2815
.map ( | e | e.change.change_id )
2816
.collect ( ) ;
c3069537 Andy McCall 1 month ago ↶
2817
assert_eq! ( ids, ids2) ;
2818
2819
let mirror = root.join ( " mirror.git" ) ;
2820
repo.git_export ( & mirror) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2821
let out = std:: process:: Command:: new( " git" )
2822
.arg ( " -C" )
2823
.arg ( & mirror)
2824
.args ( [ " show" , " main:src/lib2.rs" ] )
2825
.output ( )
2826
.unwrap ( ) ;
2827
assert_eq! (
2828
String :: from_utf8_lossy( & out.stdout) ,
2829
" pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n pub fn d() {}\n "
2830
) ;
2831
let out = std:: process:: Command:: new( " git" )
2832
.arg ( " -C" )
2833
.arg ( & mirror)
2834
.args ( [ " show" , " main:src/lib.rs" ] )
2835
.output ( )
2836
.unwrap ( ) ;
2837
assert! (
2838
! out.status.success ( ) ,
2839
" the old path is gone from the export"
2840
) ;
c3069537 Andy McCall 1 month ago ↶
2841
}
252e8cb2 Andy McCall 1 month ago ↶
2842
2843
# [ test ]
2844
fn checkpoints_accelerate_loading_and_stay_correct ( ) {
2845
hermetic ( ) ;
2846
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2847
let root = dir.path ( ) ;
2848
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2849
repo.config.set ( " core.checkpoint-interval" , " 5" ) .unwrap ( ) ;
2850
repo.save_config ( ) .unwrap ( ) ;
2851
let repo = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2852
let mut hashes = Vec :: new( ) ;
2853
for i in 0 .. 12 {
75167672 Andy McCall 1 month ago
2854
write (
2855
root,
2856
" a.txt" ,
2857
format! (
2858
" {} \n " ,
2859
( 0 .. = i)
2860
.map ( | k | k.to_string ( ) )
2861
.collect:: < Vec< _ > > ( )
2862
.join ( " \n " )
2863
)
2864
.as_bytes ( ) ,
2865
) ;
252e8cb2 Andy McCall 1 month ago ↶
2866
write ( root, & format! ( " f{i} .txt" ) , b " x\n " ) ;
2867
hashes.push ( repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ) ;
2868
}
75167672 Andy McCall 1 month ago
2869
let ( _ , cp) = repo
2870
.latest_checkpoint ( " main" )
2871
.unwrap ( )
2872
.expect ( " automatic checkpoint" ) ;
252e8cb2 Andy McCall 1 month ago ↶
2873
assert! ( cp.count > = 5 & & cp.count < = 12 , " {}" , cp.count) ;
2874
2875
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
2876
let fast = repo.load_state ( & heads) .unwrap ( ) ;
2877
let full = repo.load_state_full ( & heads) .unwrap ( ) ;
2878
assert_eq! ( fast.len ( ) , full.len ( ) ) ;
2879
assert_eq! ( fast.heads ( ) , full.heads ( ) ) ;
2880
let ( a, b) = ( fast.materialize ( ) , full.materialize ( ) ) ;
75167672 Andy McCall 1 month ago
2881
assert_eq! (
2882
a.files.keys ( ) .collect:: < Vec< _ > > ( ) ,
2883
b.files.keys ( ) .collect:: < Vec< _ > > ( )
2884
) ;
252e8cb2 Andy McCall 1 month ago ↶
2885
for ( p, f) in & a.files {
2886
assert_eq! ( f.content, b.files[ p] .content, " {p}" ) ;
2887
}
2888
assert! ( repo.load_state_checkpointed ( & heads) .unwrap ( ) .is_some ( ) ) ;
2889
75167672 Andy McCall 1 month ago
2890
assert! ( repo
2891
.load_state_checkpointed ( & [ hashes[ 0 ] ] )
2892
.unwrap ( )
2893
.is_none ( ) ) ;
252e8cb2 Andy McCall 1 month ago ↶
2894
assert_eq! ( repo.load_state ( & [ hashes[ 0 ] ] ) .unwrap ( ) .len ( ) , 1 ) ;
2895
2896
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
2897
let h = repo.describe ( & hashes[ 3 ] , " c3 described" ) .unwrap ( ) ;
2898
assert_eq! ( repo.store ( ) .get_change ( & h) .unwrap ( ) .message, " c3 described" ) ;
2899
let ( r, n, bytes) = repo.checkpoint ( ) .unwrap ( ) ;
2900
assert! ( n > = 12 & & bytes > 0 ) ;
2901
assert_eq! ( repo.latest_checkpoint ( " main" ) .unwrap ( ) .unwrap ( ) .0 , r) ;
2902
write ( root, " a.txt" , b " after\n " ) ;
2903
let h_after = repo.commit ( " after" , & [ ] ) .unwrap ( ) ;
2904
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
2905
assert! ( st.contains ( & h_after) ) ;
75167672 Andy McCall 1 month ago
2906
assert! (
2907
matches! ( & st.materialize ( ) .files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " after\n " )
2908
) ;
252e8cb2 Andy McCall 1 month ago ↶
2909
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 14 ) ;
2910
2911
repo.gc ( true ) .unwrap ( ) ;
2912
assert! ( repo.latest_checkpoint ( " main" ) .unwrap ( ) .is_some ( ) ) ;
75167672 Andy McCall 1 month ago
2913
assert! ( repo
2914
.load_state_checkpointed ( & repo.bookmark_heads ( ) .unwrap ( ) )
2915
.unwrap ( )
2916
.is_some ( ) ) ;
252e8cb2 Andy McCall 1 month ago ↶
2917
}
135212de Andy McCall 1 month ago ↶
2918
2919
# [ test ]
2920
fn quick_status_for_prompts ( ) {
2921
hermetic ( ) ;
2922
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2923
let root = dir.path ( ) ;
2924
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
2925
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
2926
let a = Repo:: clone( & url, & root.join ( " a" ) )
2927
.unwrap ( )
2928
.with_home ( root.join ( " home" ) ) ;
135212de Andy McCall 1 month ago ↶
2929
let ops_before = a.op_log ( ) .unwrap ( ) .len ( ) ;
2930
let info = a.quick_status ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2931
assert_eq! (
2932
(
2933
info.bookmark.as_str ( ) ,
2934
info.heads,
2935
info.dirty,
2936
info.conflicted,
2937
info.ahead,
2938
info.behind
2939
) ,
2940
( " main" , 0 , false , false , Some ( 0 ) , Some ( 0 ) )
2941
) ;
135212de Andy McCall 1 month ago ↶
2942
write ( & root.join ( " a" ) , " f.txt" , b " f\n " ) ;
2943
assert! ( a.quick_status ( ) .unwrap ( ) .dirty, " a new file is dirty" ) ;
75167672 Andy McCall 1 month ago
2944
assert_eq! (
2945
a.op_log ( ) .unwrap ( ) .len ( ) ,
2946
ops_before,
2947
" quick_status records nothing"
2948
) ;
135212de Andy McCall 1 month ago ↶
2949
2950
a.snapshot ( " status" ) .unwrap ( ) ;
2951
assert! ( a.quick_status ( ) .unwrap ( ) .dirty) ;
2952
a.commit ( " f" , & [ ] ) .unwrap ( ) ;
2953
let info = a.quick_status ( ) .unwrap ( ) ;
2954
assert! ( ! info.dirty, " clean right after a commit" ) ;
2955
assert_eq! ( ( info.ahead, info.behind) , ( Some ( 1 ) , Some ( 0 ) ) ) ;
75167672 Andy McCall 1 month ago
2956
assert_eq! (
2957
info.format ( " %b %c%d%x%A%D" ) ,
2958
format! ( " main {} +1" , info.change_id)
2959
) ;
135212de Andy McCall 1 month ago ↶
2960
assert_eq! ( info.format ( " [%h heads] %a/%B %%" ) , " [1 heads] 1/0 %" ) ;
2961
2962
2963
let p = root.join ( " a/f.txt" ) ;
2964
let content = fs:: read( & p) .unwrap ( ) ;
2965
fs:: write( & p, & content) .unwrap ( ) ;
2966
assert! ( ! a.quick_status ( ) .unwrap ( ) .dirty) ;
2967
fs:: write( & p, b " g\n " ) .unwrap ( ) ;
2968
assert! ( a.quick_status ( ) .unwrap ( ) .dirty) ;
2969
fs:: write( & p, b " f\n " ) .unwrap ( ) ;
2970
2971
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
2972
assert_eq! ( a.quick_status ( ) .unwrap ( ) .ahead, Some ( 0 ) ) ;
75167672 Andy McCall 1 month ago
2973
let b = Repo:: clone( & url, & root.join ( " b" ) )
2974
.unwrap ( )
2975
.with_home ( root.join ( " home-b" ) ) ;
135212de Andy McCall 1 month ago ↶
2976
write ( & root.join ( " b" ) , " g.txt" , b " g\n " ) ;
2977
b.commit ( " g" , & [ ] ) .unwrap ( ) ;
2978
b.push ( " origin" , false ) .unwrap ( ) ;
2979
a.fetch ( " origin" ) .unwrap ( ) ;
2980
let info = a.quick_status ( ) .unwrap ( ) ;
2981
assert_eq! ( ( info.ahead, info.behind) , ( Some ( 0 ) , Some ( 1 ) ) ) ;
2982
assert_eq! ( info.format ( " %A%D" ) , " -1" ) ;
2983
let _ = origin;
2984
}
b3c5a59a Andy McCall 1 month ago ↶
2985
2986
# [ test ]
2987
fn lazy_blobs_are_fetched_on_first_read ( ) {
2988
hermetic ( ) ;
2989
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2990
let root = dir.path ( ) ;
2991
let origin_dir = root.join ( " origin" ) ;
75167672 Andy McCall 1 month ago
2992
let origin = Repo:: init( & origin_dir)
2993
.unwrap ( )
2994
.with_home ( root.join ( " home" ) ) ;
b3c5a59a Andy McCall 1 month ago ↶
2995
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
2996
let big: Vec< u8 > = ( 0 .. 200_000 u32 ) .map ( | i | ( i % 251 ) as u8 ) .collect ( ) ;
2997
write ( & origin_dir, " big.bin" , & big) ;
2998
write ( & origin_dir, " small.bin" , & [ 0 u8 , 1 , 2 ] ) ;
2999
write ( & origin_dir, " t.txt" , b " t\n " ) ;
3000
let h = origin.commit ( " blobs" , & [ ] ) .unwrap ( ) ;
3001
let big_ref = Repo:: blob_ref( & big) ;
3002
3003
75167672 Andy McCall 1 month ago
3004
let a = Repo:: init_with_id( & root.join ( " a" ) , origin.id ( ) .unwrap ( ) )
3005
.unwrap ( )
3006
.with_home ( root.join ( " home" ) ) ;
b3c5a59a Andy McCall 1 month ago ↶
3007
let a = {
3008
let mut a = a;
3009
a.config.set ( " remote.origin.url" , & url) .unwrap ( ) ;
3010
a.config.set ( " filter.max-blob" , " 100K" ) .unwrap ( ) ;
3011
a.save_config ( ) .unwrap ( ) ;
3012
a
3013
} ;
3014
assert_eq! ( a.config.max_blob, Some ( 100 * 1024 ) ) ;
3015
a.fetch ( " origin" ) .unwrap ( ) ;
3016
assert! ( a.store ( ) .has ( & h) ) ;
3017
assert! ( ! a.store ( ) .has ( & big_ref) , " withheld" ) ;
75167672 Andy McCall 1 month ago
3018
assert! (
3019
a.store ( ) .has ( & Repo:: blob_ref( & [ 0 u8 , 1 , 2 ] ) ) ,
3020
" small blob came along"
3021
) ;
b3c5a59a Andy McCall 1 month ago ↶
3022
assert_eq! ( a.lazy_remote ( & big_ref) .as_deref ( ) , Some ( " origin" ) ) ;
3023
3024
3025
let ( op_ref, op) = a.head_op ( ) .unwrap ( ) ;
3026
let mut view = op.view.clone ( ) ;
75167672 Andy McCall 1 month ago
3027
view.bookmarks.insert (
3028
" main" .into ( ) ,
3029
a.store ( ) .put_state ( & StateObject:: new( vec! [ h] ) ) .unwrap ( ) ,
3030
) ;
b3c5a59a Andy McCall 1 month ago ↶
3031
a.record_op ( & [ op_ref] , " adopt" , view) .unwrap ( ) ;
3032
3033
a.checkout ( & [ h] , true , " checkout" ) .unwrap ( ) ;
3034
assert! ( a.store ( ) .has ( & big_ref) , " fetched on first read" ) ;
3035
assert_eq! ( fs:: read( root.join ( " a/big.bin" ) ) .unwrap ( ) , big) ;
3036
assert! ( a.lazy_remote ( & big_ref) .is_none ( ) , " no longer registered" ) ;
3037
write ( & root.join ( " a" ) , " t.txt" , b " t2\n " ) ;
3038
a.commit ( " text edit" , & [ ] ) .unwrap ( ) ;
3039
3040
7421309f Andy McCall 1 month ago ↶
3041
let b = Repo:: clone_with( & url, & root.join ( " b" ) , Some ( 1024 ) , & [ ] ) .unwrap ( ) ;
b3c5a59a Andy McCall 1 month ago ↶
3042
assert_eq! ( b.config.max_blob, Some ( 1024 ) ) ;
3043
assert_eq! ( fs:: read( root.join ( " b/big.bin" ) ) .unwrap ( ) , big) ;
3044
assert_eq! ( parse_size ( " 2M" ) .unwrap ( ) , 2 < < 20 ) ;
3045
assert! ( parse_size ( " lots" ) .is_err ( ) ) ;
3046
}
7421309f Andy McCall 1 month ago ↶
3047
3048
# [ test ]
3049
fn sparse_checkout_materializes_only_chosen_paths ( ) {
3050
hermetic ( ) ;
3051
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3052
let root = dir.path ( ) ;
3053
let origin_dir = root.join ( " origin" ) ;
75167672 Andy McCall 1 month ago
3054
let origin = Repo:: init( & origin_dir)
3055
.unwrap ( )
3056
.with_home ( root.join ( " home" ) ) ;
7421309f Andy McCall 1 month ago ↶
3057
write ( & origin_dir, " src/main.rs" , b " fn main() {}\n " ) ;
3058
write ( & origin_dir, " src/lib.rs" , b " pub fn a() {}\n " ) ;
3059
write ( & origin_dir, " docs/guide.md" , b " # guide\n " ) ;
3060
let logo: Vec< u8 > = ( 0 .. 50_000 u32 ) .map ( | i | ( i % 7 ) as u8 ) .collect ( ) ;
3061
write ( & origin_dir, " docs/logo.png" , & logo) ;
3062
write ( & origin_dir, " README.md" , b " # top\n " ) ;
3063
let h1 = origin.commit ( " tree" , & [ ] ) .unwrap ( ) ;
3064
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
3065
75167672 Andy McCall 1 month ago
3066
let a = Repo:: clone_with( & url, & root.join ( " a" ) , None , & [ " src" .into ( ) ] )
3067
.unwrap ( )
3068
.with_home ( root.join ( " home" ) ) ;
7421309f Andy McCall 1 month ago ↶
3069
assert_eq! ( a.config.sparse_paths, vec! [ " src" ] ) ;
3070
assert_eq! ( a.config.max_blob, Some ( 0 ) , " sparse implies lazy blobs" ) ;
3071
assert! ( root.join ( " a/src/main.rs" ) .exists ( ) ) ;
3072
assert! ( ! root.join ( " a/docs" ) .exists ( ) & & ! root.join ( " a/README.md" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
3073
assert! (
3074
! a.store ( ) .has ( & Repo:: blob_ref( & logo) ) ,
3075
" out-of-scope blob never fetched"
3076
) ;
3077
assert! (
3078
! a.snapshot ( " status" ) .unwrap ( ) .changed,
3079
" out-of-scope files are not removals"
3080
) ;
05e71ed6 Andy McCall 1 month ago ↶
3081
assert! ( ! a.quick_status ( ) .unwrap ( ) .dirty) ;
7421309f Andy McCall 1 month ago ↶
3082
3083
75167672 Andy McCall 1 month ago
3084
write (
3085
& root.join ( " a" ) ,
3086
" src/lib.rs" ,
3087
b " pub fn a() {}\n pub fn b() {}\n " ,
3088
) ;
7421309f Andy McCall 1 month ago ↶
3089
let h2 = a.commit ( " edit lib" , & [ ] ) .unwrap ( ) ;
3090
let st = a.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
3091
assert! (
3092
st.files.contains_key ( " docs/guide.md" ) ,
3093
" the state still has everything"
3094
) ;
7421309f Andy McCall 1 month ago ↶
3095
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
3096
origin.refresh ( ) .unwrap ( ) ;
7421309f Andy McCall 1 month ago ↶
3097
3098
write ( & root.join ( " a" ) , " docs/new.md" , b " ignored\n " ) ;
3099
assert! ( a.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
3100
3101
3102
let mut a = a;
3103
a.config.set ( " filter.paths" , " src,docs" ) .unwrap ( ) ;
3104
a.save_config ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3105
let r = a
3106
.checkout ( & a.bookmark_heads ( ) .unwrap ( ) , true , " widen" )
3107
.unwrap ( ) ;
7421309f Andy McCall 1 month ago ↶
3108
assert! ( r.written > = 2 ) ;
75167672 Andy McCall 1 month ago
3109
assert_eq! (
3110
fs:: read( root.join ( " a/docs/logo.png" ) ) .unwrap ( ) ,
3111
logo,
3112
" now fetched lazily"
3113
) ;
7421309f Andy McCall 1 month ago ↶
3114
assert! ( ! root.join ( " a/README.md" ) .exists ( ) ) ;
3115
3116
origin.pull ( " origin" ) .err ( ) ;
75167672 Andy McCall 1 month ago
3117
let full = origin
3118
.load_state ( & origin.bookmark_heads ( ) .unwrap ( ) )
3119
.unwrap ( )
3120
.materialize ( ) ;
3121
assert! (
3122
matches! ( & full.files[ " src/lib.rs" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " pub fn a() {}\n pub fn b() {}\n " )
3123
) ;
7421309f Andy McCall 1 month ago ↶
3124
assert! ( full.files.contains_key ( " README.md" ) ) ;
3125
let _ = h1;
3126
}
2863456a Andy McCall 1 month ago ↶
3127
3128
# [ test ]
3129
fn shallow_clone_and_deepen ( ) {
3130
hermetic ( ) ;
3131
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3132
let root = dir.path ( ) ;
3133
let origin_dir = root.join ( " origin" ) ;
75167672 Andy McCall 1 month ago
3134
let origin = Repo:: init( & origin_dir)
3135
.unwrap ( )
3136
.with_home ( root.join ( " home" ) ) ;
2863456a Andy McCall 1 month ago ↶
3137
let mut hashes = Vec :: new( ) ;
3138
for i in 0 .. 8 {
75167672 Andy McCall 1 month ago
3139
write (
3140
& origin_dir,
3141
" a.txt" ,
3142
format! (
3143
" {} \n " ,
3144
( 0 .. = i)
3145
.map ( | k | k.to_string ( ) )
3146
.collect:: < Vec< _ > > ( )
3147
.join ( " \n " )
3148
)
3149
.as_bytes ( ) ,
3150
) ;
2863456a Andy McCall 1 month ago ↶
3151
hashes.push ( origin.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ) ;
3152
}
3153
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
3154
let a = Repo:: clone_shallow( & url, & root.join ( " a" ) , 3 , None , & [ ] )
3155
.unwrap ( )
3156
.with_home ( root.join ( " home" ) ) ;
2863456a Andy McCall 1 month ago ↶
3157
assert! ( a.is_shallow ( ) ) ;
75167672 Andy McCall 1 month ago
3158
assert_eq! (
3159
fs:: read_to_string( root.join ( " a/a.txt" ) ) .unwrap ( ) ,
3160
" 0\n 1\n 2\n 3\n 4\n 5\n 6\n 7\n "
3161
) ;
3162
assert! (
3163
a.store ( ) .has ( & hashes[ 7 ] ) & & a.store ( ) .has ( & hashes[ 5 ] ) ,
3164
" three levels present"
3165
) ;
2863456a Andy McCall 1 month ago ↶
3166
assert! ( ! a.store ( ) .has ( & hashes[ 4 ] ) , " beyond the depth is absent" ) ;
3167
assert_eq! ( a.boundary ( ) , [ hashes[ 4 ] ] .into_iter ( ) .collect ( ) ) ;
75167672 Andy McCall 1 month ago
3168
assert_eq! (
3169
a.log ( ) .unwrap ( ) .len ( ) ,
3170
9 ,
3171
" the log shows every change from the checkpoint's metadata"
3172
) ;
2863456a Andy McCall 1 month ago ↶
3173
assert! ( ! a.snapshot ( " status" ) .unwrap ( ) .changed) ;
3174
assert_eq! ( a.quick_status ( ) .unwrap ( ) .heads, 1 ) ;
3175
3176
3177
write ( & root.join ( " a" ) , " a.txt" , b " 0\n 1\n 2\n 3\n 4\n 5\n 6\n 7\n 8\n " ) ;
3178
let h_new = a.commit ( " c8" , & [ ] ) .unwrap ( ) ;
3179
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
3180
origin.refresh ( ) .unwrap ( ) ;
2863456a Andy McCall 1 month ago ↶
3181
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ h_new] ) ;
3182
3183
write ( & origin_dir, " b.txt" , b " b\n " ) ;
3184
origin.pull ( " origin" ) .err ( ) ;
3185
let ( _ , wc) = origin.working_copy ( ) .unwrap ( ) ;
3186
let _ = wc;
75167672 Andy McCall 1 month ago
3187
origin
3188
.checkout ( & origin.bookmark_heads ( ) .unwrap ( ) , true , " co" )
3189
.unwrap ( ) ;
2863456a Andy McCall 1 month ago ↶
3190
write ( & origin_dir, " b.txt" , b " b\n " ) ;
3191
let h_b = origin.commit ( " b" , & [ ] ) .unwrap ( ) ;
3192
let pr = a.pull ( " origin" ) .unwrap ( ) ;
3193
assert! ( pr.changed) ;
3194
assert! ( a.store ( ) .has ( & h_b) ) ;
3195
assert! ( root.join ( " a/b.txt" ) .exists ( ) ) ;
3196
3197
3198
3199
3200
assert! ( a.deepen ( " origin" , 2 ) .unwrap ( ) > = 2 ) ;
3201
assert! ( a.store ( ) .has ( & hashes[ 3 ] ) ) ;
3202
assert! ( a.is_shallow ( ) ) ;
3203
a.deepen ( " origin" , 100 ) .unwrap ( ) ;
3204
assert! ( ! a.is_shallow ( ) , " boundary reached the root" ) ;
3205
assert! ( a.store ( ) .has ( & hashes[ 0 ] ) ) ;
75167672 Andy McCall 1 month ago
3206
assert_eq! (
3207
a.load_state_full ( & a.bookmark_heads ( ) .unwrap ( ) )
3208
.unwrap ( )
3209
.len ( ) ,
3210
10
3211
) ;
2863456a Andy McCall 1 month ago ↶
3212
}
132b884b Andy McCall 1 month ago ↶
3213
3214
# [ test ]
3215
fn tags_are_immutable_named_states_and_travel ( ) {
3216
hermetic ( ) ;
3217
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3218
let root = dir.path ( ) ;
3219
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
3220
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
3221
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
3222
.unwrap ( )
3223
.with_home ( root.join ( " home-a" ) ) ;
3224
let ( key_a, _ ) = a
3225
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
3226
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3227
write ( & root.join ( " a" ) , " f.txt" , b " 1\n " ) ;
3228
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
3229
write ( & root.join ( " a" ) , " f.txt" , b " 1\n 2\n " ) ;
3230
let h2 = a.commit ( " two" , & [ ] ) .unwrap ( ) ;
3231
3232
let t = a.tag_create ( " v1" , & [ h1] , " first release" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3233
assert_eq! (
3234
( t.heads.clone ( ) , t.signer.as_deref ( ) ) ,
3235
( vec! [ h1] , Some ( " Ada" ) )
3236
) ;
132b884b Andy McCall 1 month ago ↶
3237
assert! ( a.tag_create ( " v1" , & [ h2] , " " ) .is_err ( ) , " immutable" ) ;
3238
assert! ( a.tag_create ( " bad/name" , & [ h2] , " " ) .is_err ( ) ) ;
3239
assert_eq! ( a.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
3240
assert_eq! ( a.resolve_heads ( " v1" ) .unwrap ( ) , vec! [ h1] ) ;
3241
assert_eq! ( a.resolve_heads ( " main" ) .unwrap ( ) , vec! [ h2] ) ;
3242
let ( _ , sa) = a.tag_attest ( " v1" ) .unwrap ( ) .unwrap ( ) ;
3243
assert_eq! ( sa.sigs[ 0 ] .key, key_a) ;
3244
75167672 Andy McCall 1 month ago
3245
a.checkout ( & a.resolve_heads ( " v1" ) .unwrap ( ) , false , " co" )
3246
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3247
assert_eq! ( fs:: read( root.join ( " a/f.txt" ) ) .unwrap ( ) , b " 1\n " ) ;
75167672 Andy McCall 1 month ago
3248
a.checkout ( & a.bookmark_heads ( ) .unwrap ( ) , false , " co" )
3249
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3250
3251
3252
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
3253
origin.refresh ( ) .unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3254
assert! ( r.iter ( ) .any ( | x | x.name = = " tags/v1" & & x.ok ) , " {r:?}" ) ;
3255
assert_eq! ( origin.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
75167672 Andy McCall 1 month ago
3256
let mut forged = a
3257
.tag_refs ( )
3258
.unwrap ( )
3259
.into_iter ( )
3260
.find ( | t | t.name = = " v1" )
3261
.unwrap ( ) ;
3262
forged.state = origin
3263
.store ( )
3264
.put_state ( & StateObject:: new( vec! [ h2] ) )
3265
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3266
forged.heads = vec! [ h2] ;
3267
forged.attestation = None ;
75167672 Andy McCall 1 month ago
3268
let r = origin
3269
.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ forged] )
3270
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3271
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " immutable" ) , " {r:?}" ) ;
3272
3273
75167672 Andy McCall 1 month ago
3274
let b = Repo:: clone( & url, & root.join ( " b" ) )
3275
.unwrap ( )
3276
.with_home ( root.join ( " home-b" ) ) ;
132b884b Andy McCall 1 month ago ↶
3277
assert_eq! ( b.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
3278
assert! ( b.tags ( ) .unwrap ( ) [ 0 ] .signer.as_deref ( ) = = Some ( " Ada" ) ) ;
3279
a.tag_create ( " v2" , & [ h2] , " second" ) .unwrap ( ) ;
3280
a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
3281
origin.refresh ( ) .unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3282
let rep = b.fetch ( " origin" ) .unwrap ( ) ;
3283
assert_eq! ( rep.new_tags, 1 ) ;
3284
assert_eq! ( b.tag_heads ( " v2" ) .unwrap ( ) , Some ( vec! [ h2] ) ) ;
3285
3286
3287
a.policy_init ( ) .unwrap ( ) ;
3288
a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
3289
origin.refresh ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3290
let unattested = crate :: protocol:: TagRef {
3291
name: " v3" .into ( ) ,
3292
state: origin
3293
.store ( )
3294
.put_state ( & StateObject:: new( vec! [ h2] ) )
3295
.unwrap ( ) ,
3296
heads: vec! [ h2] ,
3297
attestation: None ,
3298
} ;
3299
let r = origin
3300
.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ unattested] )
3301
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3302
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " attestation" ) , " {r:?}" ) ;
3303
a.tag_create ( " v3" , & [ h2] , " " ) .unwrap ( ) ;
3304
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
3305
origin.refresh ( ) .unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3306
assert! ( r.iter ( ) .any ( | x | x.name = = " tags/v3" & & x.ok ) , " {r:?}" ) ;
3307
assert! ( a.gc ( true ) .is_ok ( ) ) ;
3308
assert_eq! ( a.tags ( ) .unwrap ( ) .len ( ) , 3 , " tags survive gc" ) ;
e5d9b051 Andy McCall 19 days ago ↶
3309
3310
3311
3312
3313
let rot = a.rotate_identity ( ) .unwrap ( ) ;
3314
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
3315
origin.refresh ( ) .unwrap ( ) ;
e5d9b051 Andy McCall 19 days ago ↶
3316
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
3317
let c = Repo:: clone( & url, & root.join ( " c" ) )
3318
.unwrap ( )
3319
.with_home ( root.join ( " home-c" ) ) ;
3320
assert_eq! ( c.tag_heads ( " v3" ) .unwrap ( ) , Some ( vec! [ h2] ) ) ;
3321
let ( _ , sp) = c.current_policy ( ) .unwrap ( ) .unwrap ( ) ;
3322
assert! ( ! sp.policy.authorizes ( & key_a, " tags/v9" ) ) ;
3323
let made = c.identity ( & rot.new) .unwrap ( ) .created;
3324
assert! ( c.predecessor_authorized ( & sp.policy, & key_a, " tags/v9" , made - 1 ) ) ;
3325
assert! ( ! c.predecessor_authorized ( & sp.policy, & key_a, " tags/v9" , made) ) ;
3326
assert! ( ! c.predecessor_authorized ( & sp.policy, & rot.new, " tags/v9" , 0 ) ) ;
132b884b Andy McCall 1 month ago ↶
3327
}
4e88e774 Andy McCall 1 month ago ↶
3328
3329
# [ test ]
3330
fn multiple_workspaces_share_a_repository ( ) {
3331
hermetic ( ) ;
3332
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3333
let root = dir.path ( ) ;
3334
let main_dir = root.join ( " main" ) ;
3335
let repo = Repo:: init( & main_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
3336
write ( & main_dir, " a.txt" , b " a\n " ) ;
3337
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
3338
assert_eq! ( repo.workspaces ( ) .unwrap ( ) .len ( ) , 1 ) ;
3339
3340
3341
let ws_dir = root.join ( " ws2" ) ;
3342
let ws = repo.workspace_add ( & ws_dir, None ) .unwrap ( ) ;
3343
assert! ( ws_dir.join ( " .oxy" ) .is_file ( ) ) ;
3344
assert_eq! ( fs:: read( ws_dir.join ( " a.txt" ) ) .unwrap ( ) , b " a\n " ) ;
3345
assert_ne! ( ws.workspace ( ) , repo.workspace ( ) ) ;
3346
let list = repo.workspaces ( ) .unwrap ( ) ;
3347
assert_eq! ( list.len ( ) , 2 ) ;
75167672 Andy McCall 1 month ago
3348
assert! ( list
3349
.iter ( )
3350
.any ( | w | w.id = = ws.workspace ( ) & & w.path = = ws_dir.canonicalize ( ) .unwrap ( ) & & ! w.current ) ) ;
4e88e774 Andy McCall 1 month ago ↶
3351
assert! ( ! ws.snapshot ( " status" ) .unwrap ( ) .changed) ;
3352
3353
3354
write ( & ws_dir, " b.txt" , b " b\n " ) ;
3355
let s = ws.snapshot ( " status" ) .unwrap ( ) ;
3356
assert_eq! ( s.statuses.len ( ) , 1 ) ;
75167672 Andy McCall 1 month ago
3357
assert! (
3358
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
3359
" the first workspace is untouched"
3360
) ;
4e88e774 Andy McCall 1 month ago ↶
3361
let h2 = ws.commit ( " b from ws2" , & [ ] ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
3362
repo.refresh ( ) .unwrap ( ) ;
4e88e774 Andy McCall 1 month ago ↶
3363
let mut heads = repo.bookmark_heads ( ) .unwrap ( ) ;
3364
heads.sort ( ) ;
3365
let mut want = vec! [ h1, h2] ;
3366
want.sort ( ) ;
3367
assert_eq! ( heads, want, " both workspaces see the bookmark move" ) ;
3368
assert! ( ! main_dir.join ( " b.txt" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
3369
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " co" )
3370
.unwrap ( ) ;
4e88e774 Andy McCall 1 month ago ↶
3371
assert! ( main_dir.join ( " b.txt" ) .exists ( ) ) ;
3372
3373
3374
let reopened = Repo:: open( & ws_dir) .unwrap ( ) ;
3375
assert_eq! ( reopened.workspace ( ) , ws.workspace ( ) ) ;
3376
assert_eq! ( reopened.id ( ) .unwrap ( ) , repo.id ( ) .unwrap ( ) ) ;
3377
assert_eq! ( reopened.quick_status ( ) .unwrap ( ) .heads, 2 ) ;
3378
3379
3380
assert! ( repo.workspace_add ( & main_dir.join ( " inner" ) , None ) .is_err ( ) ) ;
3381
assert! ( repo.workspace_forget ( & repo.workspace ( ) ) .is_err ( ) ) ;
3382
repo.workspace_forget ( & ws.workspace ( ) ) .unwrap ( ) ;
3383
assert_eq! ( repo.workspaces ( ) .unwrap ( ) .len ( ) , 1 ) ;
3384
assert! ( ws_dir.join ( " b.txt" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
3385
assert! (
3386
ws.snapshot ( " status" ) .is_err ( ) ,
3387
" a forgotten workspace is no longer in the view"
3388
) ;
4e88e774 Andy McCall 1 month ago ↶
3389
6caacfff Andy McCall 1 month ago ↶
3390
3391
4e88e774 Andy McCall 1 month ago ↶
3392
let lock = repo.lock_snapshot ( ) .unwrap ( ) ;
6caacfff Andy McCall 1 month ago ↶
3393
let again = repo.lock_working_copy ( ) .unwrap ( ) ;
3394
drop ( again) ;
4e88e774 Andy McCall 1 month ago ↶
3395
drop ( lock) ;
75167672 Andy McCall 1 month ago
3396
let lock_path = main_dir
3397
.join ( " .oxy/workspaces" )
3398
.join ( repo.workspace ( ) .to_hex ( ) )
3399
.join ( " lock" ) ;
4e88e774 Andy McCall 1 month ago ↶
3400
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 120 ) ;
6caacfff Andy McCall 1 month ago ↶
3401
let age = | p : & Path| fs:: File:: open( p) .unwrap ( ) .set_modified ( old) .unwrap ( ) ;
3402
fs:: write( & lock_path, std:: process:: id( ) .to_string ( ) ) .unwrap ( ) ;
3403
age ( & lock_path) ;
3404
assert! (
3405
! super :: workspace:: lock_is_stale( & lock_path) ,
3406
" a live process keeps its lock"
3407
) ;
3408
fs:: write( & lock_path, " 4294967295" ) .unwrap ( ) ;
3409
age ( & lock_path) ;
4e88e774 Andy McCall 1 month ago ↶
3410
assert! ( repo.lock_snapshot ( ) .is_ok ( ) , " stale lock taken over" ) ;
3411
}
cdb5beba Andy McCall 1 month ago ↶
3412
3413
# [ test ]
3414
fn attributes_binary_eol_nocompress_lockable ( ) {
3415
hermetic ( ) ;
3416
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3417
let root = dir.path ( ) ;
3418
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
3419
write (
3420
root,
3421
" .oxyattributes" ,
3422
b " *.dat binary nocompress\n *.txt eol=crlf\n assets/** lockable\n " ,
3423
) ;
cdb5beba Andy McCall 1 month ago ↶
3424
write ( root, " plain.dat" , b " no nul bytes here\n " ) ;
3425
write ( root, " notes.txt" , b " one\r \n two\r \n " ) ;
3426
write ( root, " assets/model.bin" , b " \0 model" ) ;
3427
let h = repo.commit ( " attrs" , & [ ] ) .unwrap ( ) ;
3428
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
3429
assert! (
3430
snap.files[ " plain.dat" ] .mode.opaque,
3431
" binary attribute forces opaque without NUL bytes"
3432
) ;
3433
assert! (
3434
matches! ( & snap.files[ " notes.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n " ) ,
3435
" stored with LF"
3436
) ;
3437
assert! (
3438
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
3439
" CRLF on disk equals LF in the state"
3440
) ;
cdb5beba Andy McCall 1 month ago ↶
3441
3442
let r = repo.gc ( true ) .unwrap ( ) ;
3443
assert! ( r.packed.objects > 0 ) ;
3444
3445
fs:: remove_file( root.join ( " notes.txt" ) ) .unwrap ( ) ;
3446
repo.checkout ( & [ h] , true , " co" ) .unwrap ( ) ;
3447
assert_eq! ( fs:: read( root.join ( " notes.txt" ) ) .unwrap ( ) , b " one\r \n two\r \n " ) ;
75167672 Andy McCall 1 month ago
3448
assert! ( fs:: metadata( root.join ( " assets/model.bin" ) )
3449
.unwrap ( )
3450
.permissions ( )
3451
.readonly ( ) ) ;
cdb5beba Andy McCall 1 month ago ↶
3452
assert! ( repo.lock_path ( " plain.dat" ) .is_err ( ) , " not lockable" ) ;
3453
repo.lock_path ( " assets/model.bin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3454
assert! ( ! fs:: metadata( root.join ( " assets/model.bin" ) )
3455
.unwrap ( )
3456
.permissions ( )
3457
.readonly ( ) ) ;
cdb5beba Andy McCall 1 month ago ↶
3458
assert! ( repo.is_locked ( " assets/model.bin" ) ) ;
3459
write ( root, " assets/model.bin" , b " \0 model2" ) ;
3460
repo.commit ( " edit model" , & [ ] ) .unwrap ( ) ;
3461
repo.unlock_path ( " assets/model.bin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3462
assert! ( fs:: metadata( root.join ( " assets/model.bin" ) )
3463
.unwrap ( )
3464
.permissions ( )
3465
.readonly ( ) ) ;
cdb5beba Andy McCall 1 month ago ↶
3466
3467
repo.checkout ( & [ h] , true , " co" ) .unwrap ( ) ;
3468
assert_eq! ( fs:: read( root.join ( " assets/model.bin" ) ) .unwrap ( ) , b " \0 model" ) ;
3469
}
6d3eefbc Andy McCall 1 month ago ↶
3470
3471
# [ cfg ( unix ) ]
3472
# [ test ]
3473
fn hooks_can_veto_observe_and_add_dependencies ( ) {
3474
use std:: os:: unix:: fs:: PermissionsExt;
3475
hermetic ( ) ;
3476
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3477
let root = dir.path ( ) ;
3478
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
3479
let hooks = root.join ( " .oxy/hooks" ) ;
3480
fs:: create_dir_all( & hooks) .unwrap ( ) ;
3481
let hook = | name : & str , body : & str | {
3482
let p = hooks.join ( name) ;
3483
fs:: write( & p, format! ( " #!/bin/sh\n {body} \n " ) ) .unwrap ( ) ;
3484
fs:: set_permissions( & p, fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
3485
} ;
3486
hook ( " pre-commit" , " cat > \" $OXY_ROOT/.last-pre-commit\" ; grep -q WIP \" $OXY_ROOT/.last-pre-commit\" && { echo 'no WIP commits' >&2; exit 1; }; exit 0" ) ;
3487
hook ( " post-commit" , " cat > \" $OXY_ROOT/.last-post-commit\" " ) ;
3488
hook ( " post-checkout" , " cat > \" $OXY_ROOT/.last-post-checkout\" " ) ;
3489
hook ( " pre-push" , " echo pushing blocked >&2; exit 3" ) ;
3490
write ( root, " .oxyignore" , b " .last-*\n " ) ;
3491
write ( root, " a.txt" , b " a\n " ) ;
3492
let err = repo.commit ( " WIP: nope" , & [ ] ) .unwrap_err ( ) .to_string ( ) ;
3493
assert! ( err.contains ( " pre-commit refused: no WIP commits" ) , " {err}" ) ;
3494
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
3495
let pre = fs:: read_to_string( root.join ( " .last-pre-commit" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3496
assert! (
3497
pre.contains ( " \" event\" :\" pre-commit\" " )
3498
& & pre.contains ( " \" path\" :\" a.txt\" " )
3499
& & pre.contains ( " \" message\" :\" first\" " ) ,
3500
" {pre}"
3501
) ;
6d3eefbc Andy McCall 1 month ago ↶
3502
let post = fs:: read_to_string( root.join ( " .last-post-commit" ) ) .unwrap ( ) ;
3503
assert! ( post.contains ( & h1.to_hex ( ) ) , " {post}" ) ;
3504
repo.checkout ( & [ h1] , false , " co" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3505
assert! ( fs:: read_to_string( root.join ( " .last-post-checkout" ) )
3506
.unwrap ( )
3507
.contains ( " post-checkout" ) ) ;
6d3eefbc Andy McCall 1 month ago ↶
3508
repo.config.set ( " remote.origin.url" , " /nowhere" ) .unwrap ( ) ;
3509
repo.save_config ( ) .unwrap ( ) ;
3510
let err = repo.push ( " origin" , false ) .unwrap_err ( ) .to_string ( ) ;
3511
assert! ( err.contains ( " pre-push refused: pushing blocked" ) , " {err}" ) ;
3512
3513
3514
write ( root, " b.txt" , b " b\n " ) ;
75167672 Andy McCall 1 month ago
3515
hook (
3516
" semantic-deps" ,
3517
& format! (
3518
" cat > \" $OXY_ROOT/.last-semantic\" ; echo '{{ \" deps\" : [\" {} \" ]}} '" ,
3519
h1.to_hex ( )
3520
) ,
3521
) ;
6d3eefbc Andy McCall 1 month ago ↶
3522
let h2 = repo.commit ( " b without hook" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3523
assert! (
3524
repo.store ( ) .get_change ( & h2) .unwrap ( ) .deps.is_empty ( ) ,
3525
" hook disabled by default"
3526
) ;
6d3eefbc Andy McCall 1 month ago ↶
3527
repo.config.set ( " hooks.semantic-deps" , " true" ) .unwrap ( ) ;
3528
repo.save_config ( ) .unwrap ( ) ;
3529
write ( root, " c.txt" , b " c\n " ) ;
3530
let h3 = repo.commit ( " c with hook" , & [ ] ) .unwrap ( ) ;
3531
assert_eq! ( repo.store ( ) .get_change ( & h3) .unwrap ( ) .deps, vec! [ h1] ) ;
3532
let sem = fs:: read_to_string( root.join ( " .last-semantic" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3533
assert! (
3534
sem.contains ( " \" touched_paths\" :[\" c.txt\" ]" ) & & sem.contains ( " candidate_deps" ) ,
3535
" {sem}"
3536
) ;
6d3eefbc Andy McCall 1 month ago ↶
3537
assert_eq! ( json_str ( " a\" b\\ c\n " ) , " \" a\\ \" b\\ \\ c\\ n\" " ) ;
3538
}
23b217d7 Andy McCall 1 month ago ↶
3539
3540
# [ test ]
3541
fn amend_into_older_change_and_split ( ) {
3542
hermetic ( ) ;
3543
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3544
let root = dir.path ( ) ;
3545
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
3546
repo.create_identity ( " Ada" , " a@b" , IdentityScope:: User)
3547
.unwrap ( ) ;
23b217d7 Andy McCall 1 month ago ↶
3548
repo.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
3549
write ( root, " a.txt" , b " one\n two\n " ) ;
3550
let h0 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
3551
write ( root, " a.txt" , b " one\n TWO\n " ) ;
3552
let h1 = repo.commit ( " edit two" , & [ ] ) .unwrap ( ) ;
3553
write ( root, " a.txt" , b " one\n TWO\n three\n " ) ;
3554
let h2 = repo.commit ( " add three" , & [ ] ) .unwrap ( ) ;
3555
let id1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) .change_id;
3556
3557
3558
write ( root, " a.txt" , b " one\n TWO!\n three\n " ) ;
3559
let h1b = repo.amend_into ( & h1, None ) .unwrap ( ) ;
3560
assert_ne! ( h1b, h1) ;
3561
let c1b = repo.store ( ) .get_change ( & h1b) .unwrap ( ) ;
3562
assert_eq! ( c1b.change_id, id1) ;
3563
assert_eq! ( c1b.deps, vec! [ h0] ) ;
75167672 Andy McCall 1 month ago
3564
assert! ( matches! (
3565
repo.verify_change ( & h1b) .unwrap ( ) ,
3566
SignatureStatus:: Valid { .. }
3567
) ) ;
23b217d7 Andy McCall 1 month ago ↶
3568
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
3569
assert_eq! ( heads.len ( ) , 1 ) ;
3570
let top = repo.store ( ) .get_change ( & heads[ 0 ] ) .unwrap ( ) ;
3571
assert_eq! ( top.message, " add three" ) ;
75167672 Andy McCall 1 month ago
3572
assert_eq! (
3573
top.deps.iter ( ) .copied ( ) .collect:: < BTreeSet< _ > > ( ) ,
3574
[ h0, h1b] .into_iter ( ) .collect ( ) ,
3575
" depends on the file's creator and the rewritten line"
3576
) ;
23b217d7 Andy McCall 1 month ago ↶
3577
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
3578
assert! (
3579
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n TWO!\n three\n " )
3580
) ;
3581
assert! (
3582
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
3583
" the working copy is clean after folding"
3584
) ;
23b217d7 Andy McCall 1 month ago ↶
3585
3586
let only = repo.load_state ( & [ h1b] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
3587
assert! (
3588
matches! ( & only.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n TWO!\n " )
3589
) ;
23b217d7 Andy McCall 1 month ago ↶
3590
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 ) ;
3591
3592
3593
write ( root, " a.txt" , b " one\n TWO!\n three!\n " ) ;
3594
let err = repo.amend_into ( & h1b, None ) .unwrap_err ( ) .to_string ( ) ;
3595
assert! ( err.contains ( " introduced after" ) , " {err}" ) ;
75167672 Andy McCall 1 month ago
3596
assert! (
3597
repo.snapshot ( " status" ) .unwrap ( ) .statuses.len ( ) = = 1 ,
3598
" nothing was changed by the refusal"
3599
) ;
23b217d7 Andy McCall 1 month ago ↶
3600
write ( root, " a.txt" , b " one\n TWO!\n three\n " ) ;
3601
3602
3603
write ( root, " b.txt" , b " b\n " ) ;
3604
write ( root, " c.txt" , b " c\n " ) ;
3605
let h3 = repo.commit ( " b and c" , & [ ] ) .unwrap ( ) ;
3606
let id3 = repo.store ( ) .get_change ( & h3) .unwrap ( ) .change_id;
3607
let ( p1, p2) = repo.split ( & h3, & [ " b.txt" .into ( ) ] ) .unwrap ( ) ;
3608
let c_1 = repo.store ( ) .get_change ( & p1) .unwrap ( ) ;
3609
let c_2 = repo.store ( ) .get_change ( & p2) .unwrap ( ) ;
3610
assert! ( c_1 .message.contains ( " part 1: b.txt" ) ) ;
3611
assert_eq! ( c_2 .change_id, id3) ;
3612
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
75167672 Andy McCall 1 month ago
3613
assert! (
3614
heads.contains ( & p1) & & heads.contains ( & p2) ,
3615
" independent parts are both heads"
3616
) ;
3617
let snap = repo
3618
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
3619
.unwrap ( )
3620
.materialize ( ) ;
23b217d7 Andy McCall 1 month ago ↶
3621
assert! ( snap.files.contains_key ( " b.txt" ) & & snap.files.contains_key ( " c.txt" ) ) ;
3622
let only1 = repo.load_state ( & [ p1] ) .unwrap ( ) .materialize ( ) ;
3623
assert! ( only1.files.contains_key ( " b.txt" ) & & ! only1.files.contains_key ( " c.txt" ) ) ;
3624
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
3625
assert! ( repo.split ( & p2, & [ " nope.txt" .into ( ) ] ) .is_err ( ) ) ;
3626
let _ = h2;
3627
}
28fc23d7 Andy McCall 1 month ago ↶
3628
3629
# [ test ]
3630
fn identity_lifecycle ( ) {
3631
hermetic ( ) ;
3632
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3633
let root = dir.path ( ) ;
3634
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
3635
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
3636
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
3637
.unwrap ( )
3638
.with_home ( root.join ( " home-a" ) ) ;
28fc23d7 Andy McCall 1 month ago ↶
3639
a.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3640
let ( key_a, _ ) = a
3641
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
3642
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3643
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
3644
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
3645
assert! ( a.policy_init ( ) .unwrap ( ) .applied) ;
3646
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
767ac6cd Andy McCall 18 days ago
3647
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3648
3649
75167672 Andy McCall 1 month ago
3650
let mut e = Repo:: init( & root.join ( " e" ) )
3651
.unwrap ( )
3652
.with_home ( root.join ( " home-e" ) ) ;
28fc23d7 Andy McCall 1 month ago ↶
3653
e.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3654
e.create_identity_with (
3655
" Eve" ,
3656
" [email protected] " ,
3657
IdentityScope:: User,
3658
Some ( Hlc:: now( ) .wall_ms - 1 ) ,
3659
)
3660
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3661
write ( & root.join ( " e" ) , " e.txt" , b " e\n " ) ;
3662
let he = e.commit ( " late" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3663
assert! (
3664
matches! ( e.verify_change ( & he) .unwrap ( ) , SignatureStatus:: Invalid( m) if m.contains ( " expired" ) )
3665
) ;
28fc23d7 Andy McCall 1 month ago ↶
3666
75167672 Andy McCall 1 month ago
3667
let r = e
3668
.renew_identity ( Some ( Hlc:: now( ) .wall_ms + 86_400_000 ) )
3669
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3670
assert_eq! ( e.identity ( & r.new) .unwrap ( ) .previous, Some ( r.old) ) ;
3671
write ( & root.join ( " e" ) , " e.txt" , b " e2\n " ) ;
3672
let he2 = e.commit ( " in time" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3673
assert! (
3674
matches! ( e.verify_change ( & he2) .unwrap ( ) , SignatureStatus:: Valid { key, .. } if key = = r.new)
3675
) ;
28fc23d7 Andy McCall 1 month ago ↶
3676
3677
3678
let r = a.rotate_identity ( ) .unwrap ( ) ;
3679
assert_eq! ( r.old, key_a) ;
3680
assert_eq! ( a.config.author_key, Some ( r.new) ) ;
3681
assert_eq! ( a.identity ( & r.new) .unwrap ( ) .previous, Some ( key_a) ) ;
3682
assert! ( r.policy.as_ref ( ) .unwrap ( ) .applied) ;
3683
let ( _ , sp) = a.current_policy ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3684
assert_eq! (
3685
( sp.policy.serial, sp.policy.roots.clone ( ) ) ,
3686
( 2 , vec! [ r.new] )
3687
) ;
3688
assert! (
3689
! sp.policy.revoked.contains ( & key_a) & & sp.policy.authorizes ( & r.new, " main" ) ,
3690
" superseded, not revoked"
3691
) ;
28fc23d7 Andy McCall 1 month ago ↶
3692
assert! ( sp.policy.succeeds ( & r.new, & key_a) & & ! sp.policy.succeeds ( & key_a, & r.new) ) ;
75167672 Andy McCall 1 month ago
3693
assert! (
3694
matches! ( a.verify_change ( & h1) .unwrap ( ) , SignatureStatus:: Valid { key, revoked: false , .. } if key = = key_a)
3695
) ;
28fc23d7 Andy McCall 1 month ago ↶
3696
write ( & root.join ( " a" ) , " a.txt" , b " a2\n " ) ;
3697
let h2 = a.commit ( " two" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3698
assert! (
3699
matches! ( a.verify_change ( & h2) .unwrap ( ) , SignatureStatus:: Valid { key, revoked: false , .. } if key = = r.new)
3700
) ;
28fc23d7 Andy McCall 1 month ago ↶
3701
3702
let res = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
3703
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3704
assert! ( res.iter ( ) .all ( | x | x.ok ) , " {res:?}" ) ;
3705
assert_eq! ( origin.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 2 ) ;
3706
let key_a = r.new;
3707
3708
3709
75167672 Andy McCall 1 month ago
3710
let mut b = Repo:: clone( & url, & root.join ( " b" ) )
3711
.unwrap ( )
3712
.with_home ( root.join ( " home-b" ) ) ;
3713
let ( key_b, id_b) = b
3714
.create_identity ( " Bob" , " [email protected] " , IdentityScope:: User)
3715
.unwrap ( ) ;
7ba155f2 Andy McCall 1 month ago ↶
3716
let r = a.delegate_add ( id_b.clone ( ) , Scope:: ALL , None ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3717
assert_eq! ( r.old, key_a) ;
3718
let key_a2 = r.new;
75167672 Andy McCall 1 month ago
3719
assert_eq! (
7ba155f2 Andy McCall 1 month ago ↶
3720
a.identity ( & key_a2) .unwrap ( ) .delegates,
3721
vec! [ Delegate:: full( key_b) ]
3722
) ;
3723
assert_eq! (
75167672 Andy McCall 1 month ago
3724
a.identity ( & key_a2) .unwrap ( ) .ed25519_pk,
3725
a.identity ( & key_a) .unwrap ( ) .ed25519_pk
7ba155f2 Andy McCall 1 month ago ↶
3726
) ;
3727
assert! (
3728
a.delegate_add ( id_b.clone ( ) , Scope:: ALL , None ) .is_err ( ) ,
3729
" already a delegate"
75167672 Andy McCall 1 month ago
3730
) ;
28fc23d7 Andy McCall 1 month ago ↶
3731
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | x | x.ok ) ) ;
767ac6cd Andy McCall 18 days ago
3732
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3733
write ( & root.join ( " a" ) , " a.txt" , b " a3\n " ) ;
3734
let h3 = a.commit ( " three" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3735
assert! (
3736
matches! ( a.verify_change ( & h3) .unwrap ( ) , SignatureStatus:: Valid { key, .. } if key = = key_a2) ,
3737
" the same secret signs under the new KeyId"
3738
) ;
28fc23d7 Andy McCall 1 month ago ↶
3739
3740
a.set_user_config ( " core.sign" , " seal" ) .unwrap ( ) ;
3741
write ( & root.join ( " a" ) , " a.txt" , b " a4\n " ) ;
3742
let h4 = a.commit ( " four" , & [ ] ) .unwrap ( ) ;
3743
assert_eq! ( a.verify_change ( & h4) .unwrap ( ) , SignatureStatus:: Unsigned) ;
3744
let ( _ , sk_b) = b.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3745
a.store ( )
3746
.put_signature ( & h4, & sk_b.sign ( Domain:: Change, & key_b, & h4) )
3747
.unwrap ( ) ;
3748
assert! (
3749
matches! ( a.verify_change ( & h4) .unwrap ( ) , SignatureStatus:: Valid { key, .. } if key = = key_b)
3750
) ;
28fc23d7 Andy McCall 1 month ago ↶
3751
a.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
3752
3753
let r = a.delegate_remove ( & key_b) .unwrap ( ) ;
3754
assert! ( a.identity ( & r.new) .unwrap ( ) .delegates.is_empty ( ) ) ;
3755
assert! ( a.delegate_remove ( & key_b) .is_err ( ) ) ;
3756
let key_a3 = r.new;
3757
let res = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
3758
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3759
assert! ( res.iter ( ) .all ( | x | x.ok ) , " {res:?}" ) ;
3760
3761
3762
3763
assert! ( a.policy_root_add ( id_b.clone ( ) ) .unwrap ( ) .applied) ;
3764
assert! ( a.policy_root_add ( id_b.clone ( ) ) .is_err ( ) ) ;
3765
assert! ( a.policy_set_threshold ( 3 ) .is_err ( ) ) ;
3766
assert! ( a.policy_set_threshold ( 2 ) .unwrap ( ) .applied) ;
3767
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | x | x.ok ) ) ;
767ac6cd Andy McCall 18 days ago
3768
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3769
b.pull ( " origin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3770
assert_eq! (
3771
b.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.root_threshold,
3772
2
3773
) ;
3774
let mut c = Repo:: init( & root.join ( " c" ) )
3775
.unwrap ( )
3776
.with_home ( root.join ( " home-c" ) ) ;
3777
let ( key_c, id_c) = c
3778
.create_identity ( " Cy" , " [email protected] " , IdentityScope:: User)
3779
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3780
let serial_before = a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial;
3781
let proposal = a.policy_grant ( id_c, None ) .unwrap ( ) ;
3782
assert! ( ! proposal.applied & & proposal.sigs.len ( ) = = 1 ) ;
3783
assert_eq! ( proposal.policy.serial, serial_before + 1 ) ;
75167672 Andy McCall 1 month ago
3784
assert_eq! (
3785
a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial,
3786
serial_before,
3787
" a proposal changes nothing yet"
3788
) ;
28fc23d7 Andy McCall 1 month ago ↶
3789
3790
let bytes = proposal.signed ( ) .encode ( ) ;
75167672 Andy McCall 1 month ago
3791
let signed = b
3792
.policy_sign ( SignedPolicy:: decode( & bytes) .unwrap ( ) )
3793
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3794
assert! ( signed.applied & & signed.sigs.len ( ) = = 2 ) ;
75167672 Andy McCall 1 month ago
3795
assert! ( b
3796
.current_policy ( )
3797
.unwrap ( )
3798
.unwrap ( )
3799
.1
3800
.policy
3801
.authorizes ( & key_c, " main" ) ) ;
3802
assert! (
3803
b.policy_sign ( signed.signed ( ) ) .is_err ( ) ,
3804
" a stale proposal is refused"
3805
) ;
28fc23d7 Andy McCall 1 month ago ↶
3806
75167672 Andy McCall 1 month ago
3807
assert! ( b
3808
.push ( " origin" , false )
3809
.unwrap ( )
3810
.iter ( )
3811
.any ( | x | x.name = = " policy" & & x.ok ) ) ;
767ac6cd Andy McCall 18 days ago
3812
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3813
a.pull ( " origin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3814
assert_eq! (
3815
a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial,
3816
serial_before + 1
3817
) ;
28fc23d7 Andy McCall 1 month ago ↶
3818
3819
assert! ( a.policy_revoke ( & key_a3) .is_err ( ) ) ;
75167672 Andy McCall 1 month ago
3820
let u = a
3821
.revoke_identity ( & key_c, " left the project" )
3822
.unwrap ( )
3823
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3824
assert! ( ! u.applied) ;
3825
assert! ( a.is_revoked ( & key_c) .unwrap ( ) ) ;
3826
assert! ( ! b.is_revoked ( & key_c) .unwrap ( ) ) ;
3827
let u = b.policy_sign ( u.signed ( ) ) .unwrap ( ) ;
3828
assert! ( u.applied & & u.policy.is_revoked ( & key_c) ) ;
3829
assert! ( b.is_revoked ( & key_c) .unwrap ( ) ) ;
3830
75167672 Andy McCall 1 month ago
3831
assert! (
3832
b.policy_revoke ( & key_a3) .is_err ( ) ,
3833
" would leave one root for a threshold of 2"
3834
) ;
28fc23d7 Andy McCall 1 month ago ↶
3835
3836
let key_c_own = c.config.author_key.unwrap ( ) ;
75167672 Andy McCall 1 month ago
3837
assert! ( c
3838
.revoke_identity ( & key_c_own, " lost laptop" )
3839
.unwrap ( )
3840
.is_none ( ) ) ;
28fc23d7 Andy McCall 1 month ago ↶
3841
assert! ( c.is_revoked ( & key_c_own) .unwrap ( ) ) ;
3842
assert_eq! ( key_c_own, key_c) ;
3843
}
3844
6e723b81 Andy McCall 1 month ago ↶
3845
# [ test ]
3846
fn renames_are_detected_and_tracked_as_moves ( ) {
3847
hermetic ( ) ;
3848
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3849
let root = dir.path ( ) ;
3850
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
3851
write (
3852
root,
3853
" src/old.rs" ,
3854
b " fn a() {}\n fn b() {}\n fn c() {}\n fn d() {}\n " ,
3855
) ;
6e723b81 Andy McCall 1 month ago ↶
3856
write ( root, " img.bin" , b " \x89 PNG\0 \0 \x01 \x02 " ) ;
3857
write ( root, " tiny.txt" , b " x\n " ) ;
3858
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
3859
let node_old = repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) .files[ " src/old.rs" ] .node;
3860
let node_img = repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) .files[ " img.bin" ] .node;
3861
3862
3863
3864
fs:: remove_file( root.join ( " src/old.rs" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3865
write (
3866
root,
3867
" lib/new.rs" ,
3868
b " fn a() {}\n fn b() {}\n fn c() {}\n fn e() {}\n " ,
3869
) ;
6e723b81 Andy McCall 1 month ago ↶
3870
fs:: remove_file( root.join ( " img.bin" ) ) .unwrap ( ) ;
3871
write ( root, " assets/img.bin" , b " \x89 PNG\0 \0 \x01 \x02 " ) ;
3872
fs:: remove_file( root.join ( " tiny.txt" ) ) .unwrap ( ) ;
3873
write ( root, " other.txt" , b " completely different\n " ) ;
3874
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3875
let statuses: Vec< ( & str , ChangeStatus) > = snap
3876
.statuses
3877
.iter ( )
3878
.map ( | s | ( s.path.as_str ( ) , s.status) )
3879
.collect ( ) ;
6e723b81 Andy McCall 1 month ago ↶
3880
assert_eq! (
3881
statuses,
75167672 Andy McCall 1 month ago
3882
vec! [
3883
( " assets/img.bin" , ChangeStatus:: Renamed) ,
3884
( " lib/new.rs" , ChangeStatus:: Renamed) ,
3885
( " other.txt" , ChangeStatus:: Added) ,
3886
( " src" , ChangeStatus:: Removed) ,
3887
( " tiny.txt" , ChangeStatus:: Removed)
3888
]
6e723b81 Andy McCall 1 month ago ↶
3889
) ;
3890
let h2 = repo.commit ( " move things" , & [ ] ) .unwrap ( ) ;
3891
let c = repo.store ( ) .get_change ( & h2) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3892
let moves: Vec< _ > = c
3893
.tree_ops
3894
.iter ( )
3895
.filter ( | op | matches! ( op, TreeOp:: Move { .. } ) )
3896
.collect ( ) ;
6e723b81 Andy McCall 1 month ago ↶
3897
assert_eq! ( moves.len ( ) , 2 , " {:?}" , c.tree_ops) ;
75167672 Andy McCall 1 month ago
3898
assert! ( c.tree_ops.iter ( ) .any (
3899
| op | matches! ( op, TreeOp:: Move { node, name, .. } if * node = = node_old & & name = = " new.rs" )
3900
) ) ;
3901
assert! ( c.tree_ops.iter ( ) .any (
3902
| op | matches! ( op, TreeOp:: Move { node, name, .. } if * node = = node_img & & name = = " img.bin" )
3903
) ) ;
6e723b81 Andy McCall 1 month ago ↶
3904
let snap = repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
3905
assert_eq! (
3906
snap.files[ " lib/new.rs" ] .node, node_old,
3907
" the node survives the rename"
3908
) ;
3909
assert! (
3910
matches! ( & snap.files[ " lib/new.rs" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " fn a() {}\n fn b() {}\n fn c() {}\n fn e() {}\n " )
3911
) ;
6e723b81 Andy McCall 1 month ago ↶
3912
assert! ( ! snap.files.contains_key ( " src/old.rs" ) & & ! snap.files.contains_key ( " src" ) ) ;
75167672 Andy McCall 1 month ago
3913
assert! ( render_change (
3914
& c,
3915
& repo.load_state ( & [ h1] ) .unwrap ( ) ,
3916
& repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( )
3917
)
0912a033 Andy McCall 1 month ago ↶
3918
.contains ( " R src/old.rs -> lib/new.rs" ) ) ;
6e723b81 Andy McCall 1 month ago ↶
3919
3920
let other = tempfile:: tempdir( ) .unwrap ( ) ;
3921
let clone = Repo:: clone( & root.to_string_lossy ( ) , other.path ( ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3922
assert_eq! (
3923
fs:: read( other.path ( ) .join ( " lib/new.rs" ) ) .unwrap ( ) ,
3924
b " fn a() {}\n fn b() {}\n fn c() {}\n fn e() {}\n "
3925
) ;
6e723b81 Andy McCall 1 month ago ↶
3926
assert! ( ! other.path ( ) .join ( " src" ) .exists ( ) ) ;
3927
assert! ( ! clone.snapshot ( " status" ) .unwrap ( ) .changed) ;
3928
3929
assert_eq! ( similarity ( b " a\n b\n " , b " a\n b\n " ) , 100 ) ;
3930
assert_eq! ( similarity ( b " a\n b\n c\n d\n " , b " a\n b\n x\n y\n " ) , 50 ) ;
3931
assert_eq! ( similarity ( b " hello\n " , b " world\n " ) , 0 ) ;
3932
assert_eq! ( similarity ( b " " , b " x\n " ) , 0 ) ;
3933
}
3934
30ea9c1e Andy McCall 1 month ago ↶
3935
# [ test ]
3936
fn git_fetch_and_push_round_trip ( ) {
3937
hermetic ( ) ;
75167672 Andy McCall 1 month ago
3938
if std:: process:: Command:: new( " git" )
3939
.arg ( " --version" )
3940
.output ( )
3941
.is_err ( )
3942
{
30ea9c1e Andy McCall 1 month ago ↶
3943
eprintln! ( " git not installed; skipping" ) ;
3944
return ;
3945
}
3946
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3947
let root = dir.path ( ) ;
3948
let git_in = | d : & Path, args : & [& str ]| -> String {
3949
let out = std:: process:: Command:: new( " git" )
3950
.arg ( " -C" )
3951
.arg ( d)
75167672 Andy McCall 1 month ago
3952
.args ( [
3953
" -c" ,
3954
" user.name=Grace" ,
3955
" -c" ,
3956
" [email protected] " ,
3957
" -c" ,
3958
" commit.gpgsign=false" ,
3959
] )
30ea9c1e Andy McCall 1 month ago ↶
3960
.args ( args)
3961
.output ( )
3962
.unwrap ( ) ;
75167672 Andy McCall 1 month ago
3963
assert! (
3964
out.status.success ( ) ,
3965
" git {args:?}: {}" ,
3966
String :: from_utf8_lossy( & out.stderr)
3967
) ;
30ea9c1e Andy McCall 1 month ago ↶
3968
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
3969
} ;
3970
3971
let remote = root.join ( " remote.git" ) ;
3972
fs:: create_dir_all( & remote) .unwrap ( ) ;
3973
git_in ( & remote, & [ " init" , " -q" , " --bare" , " -b" , " main" ] ) ;
3974
let work = root.join ( " work" ) ;
75167672 Andy McCall 1 month ago
3975
git_in (
3976
root,
3977
& [
3978
" clone" ,
3979
" -q" ,
3980
remote.to_str ( ) .unwrap ( ) ,
3981
work.to_str ( ) .unwrap ( ) ,
3982
] ,
3983
) ;
30ea9c1e Andy McCall 1 month ago ↶
3984
git_in ( & work, & [ " checkout" , " -q" , " -b" , " main" ] ) ;
3985
write ( & work, " README.md" , b " # Project\n " ) ;
3986
write ( & work, " src/lib.rs" , b " pub fn a() {}\n " ) ;
3987
git_in ( & work, & [ " add" , " ." ] ) ;
3988
git_in ( & work, & [ " commit" , " -q" , " -m" , " initial" ] ) ;
3989
write ( & work, " src/lib.rs" , b " pub fn a() {}\n pub fn b() {}\n " ) ;
3990
git_in ( & work, & [ " commit" , " -q" , " -am" , " second" ] ) ;
3991
git_in ( & work, & [ " push" , " -q" , " origin" , " main" ] ) ;
3992
let tip1 = git_in ( & work, & [ " rev-parse" , " HEAD" ] ) ;
3993
3994
3995
let o = root.join ( " oxy" ) ;
3996
let mut repo = Repo:: init( & o) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
3997
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
3998
.unwrap ( ) ;
30ea9c1e Andy McCall 1 month ago ↶
3999
let url = remote.to_string_lossy ( ) .to_string ( ) ;
4000
let r = repo.git_fetch ( & url, " main" ) .unwrap ( ) ;
4001
assert_eq! ( ( r.commits, r.heads) , ( 2 , 1 ) ) ;
75167672 Andy McCall 1 month ago
4002
assert_eq! (
4003
fs:: read( o.join ( " src/lib.rs" ) ) .unwrap ( ) ,
4004
b " pub fn a() {}\n pub fn b() {}\n "
4005
) ;
30ea9c1e Andy McCall 1 month ago ↶
4006
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
75167672 Andy McCall 1 month ago
4007
assert_eq! (
4008
repo.git_fetch ( & url, " main" ) .unwrap ( ) .commits,
4009
0 ,
4010
" nothing new"
4011
) ;
30ea9c1e Andy McCall 1 month ago ↶
4012
4013
75167672 Andy McCall 1 month ago
4014
write (
4015
& o,
4016
" src/lib.rs" ,
4017
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n " ,
4018
) ;
30ea9c1e Andy McCall 1 month ago ↶
4019
write ( & o, " NOTES.md" , b " from oxy\n " ) ;
4020
let h_oxy = repo.commit ( " add c and notes" , & [ ] ) .unwrap ( ) ;
4021
let r = repo.git_push ( & url, " main" ) .unwrap ( ) ;
4022
assert_eq! ( r.exported, 1 , " {r:?}" ) ;
4023
git_in ( & work, & [ " pull" , " -q" , " --ff-only" , " origin" , " main" ] ) ;
75167672 Andy McCall 1 month ago
4024
assert_eq! (
4025
git_in ( & work, & [ " log" , " --format=%s" , " main" ] ) ,
4026
" add c and notes\n second\n initial"
4027
) ;
30ea9c1e Andy McCall 1 month ago ↶
4028
assert_eq! ( git_in ( & work, & [ " rev-parse" , " HEAD~1" ] ) , tip1) ;
4029
assert_eq! ( fs:: read( work.join ( " NOTES.md" ) ) .unwrap ( ) , b " from oxy\n " ) ;
4030
assert_eq! ( git_in ( & work, & [ " log" , " -1" , " --format=%an" , " main" ] ) , " Ada" ) ;
75167672 Andy McCall 1 month ago
4031
assert! ( git_in ( & work, & [ " log" , " -1" , " --format=%B" , " main" ] )
4032
.contains ( & format! ( " Oxy-Hash: {} " , h_oxy.to_hex ( ) ) ) ) ;
4033
assert_eq! (
4034
repo.git_push ( & url, " main" ) .unwrap ( ) .exported,
4035
0 ,
4036
" already pushed"
4037
) ;
30ea9c1e Andy McCall 1 month ago ↶
4038
4039
4040
4041
write ( & work, " README.md" , b " # Project\n \n More.\n " ) ;
4042
git_in ( & work, & [ " commit" , " -q" , " -am" , " readme from git" ] ) ;
4043
git_in ( & work, & [ " push" , " -q" , " origin" , " main" ] ) ;
4044
let tip2 = git_in ( & work, & [ " rev-parse" , " HEAD" ] ) ;
75167672 Andy McCall 1 month ago
4045
assert! ( repo
4046
.git_push ( & url, " main" )
4047
.unwrap_err ( )
4048
.to_string ( )
4049
.contains ( " fetch" ) ) ;
30ea9c1e Andy McCall 1 month ago ↶
4050
let r = repo.git_fetch ( & url, " main" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4051
assert_eq! (
4052
( r.commits, r.heads) ,
4053
( 1 , 2 ) ,
4054
" the Git change and the oxy change commute"
4055
) ;
4056
assert_eq! (
4057
fs:: read( o.join ( " README.md" ) ) .unwrap ( ) ,
4058
b " # Project\n \n More.\n "
4059
) ;
30ea9c1e Andy McCall 1 month ago ↶
4060
assert_eq! ( fs:: read( o.join ( " NOTES.md" ) ) .unwrap ( ) , b " from oxy\n " ) ;
4061
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4062
4063
write ( & o, " NOTES.md" , b " from oxy\n again\n " ) ;
4064
repo.commit ( " notes again" , & [ ] ) .unwrap ( ) ;
4065
assert_eq! ( repo.git_push ( & url, " main" ) .unwrap ( ) .exported, 1 ) ;
4066
git_in ( & work, & [ " pull" , " -q" , " --ff-only" , " origin" , " main" ] ) ;
4067
assert_eq! ( git_in ( & work, & [ " rev-parse" , " HEAD~1" ] ) , tip2) ;
75167672 Andy McCall 1 month ago
4068
assert_eq! (
4069
fs:: read( work.join ( " NOTES.md" ) ) .unwrap ( ) ,
4070
b " from oxy\n again\n "
4071
) ;
4072
assert_eq! (
4073
git_in ( & work, & [ " log" , " --format=%s" , " main" ] )
4074
.lines ( )
4075
.count ( ) ,
4076
5
4077
) ;
30ea9c1e Andy McCall 1 month ago ↶
4078
}
4079
2b872d07 Andy McCall 1 month ago ↶
4080
4081
4082
# [ test ]
4083
# [ ignore ]
4084
fn bench_one_gibibyte_file ( ) {
4085
hermetic ( ) ;
4086
4087
let dir = match std:: env:: var_os( " OXY_BENCH_DIR" ) {
4088
Some ( d) => tempfile:: tempdir_in( d) .unwrap ( ) ,
4089
None => tempfile:: tempdir( ) .unwrap ( ) ,
4090
} ;
4091
let root = dir.path ( ) ;
75167672 Andy McCall 1 month ago
4092
let repo = Repo:: init( & root.join ( " r" ) )
4093
.unwrap ( )
4094
.with_home ( root.join ( " home" ) ) ;
2b872d07 Andy McCall 1 month ago ↶
4095
let size = 1 usize < < 30 ;
4096
let mut data = vec! [ 0 u8 ; size] ;
4097
let mut x = 0x9e3779b97f4a7c15 u64 ;
4098
for chunk in data.chunks_mut ( 8 ) {
4099
x ^ = x < < 13 ;
4100
x ^ = x > > 7 ;
4101
x ^ = x < < 17 ;
4102
let b = x.to_le_bytes ( ) ;
4103
chunk.copy_from_slice ( & b[ .. chunk.len ( ) ] ) ;
4104
}
4105
let t = std:: time:: Instant:: now( ) ;
4106
write ( & root.join ( " r" ) , " big.bin" , & data) ;
4107
eprintln! ( " write 1 GiB to disk: {:.2?}" , t.elapsed ( ) ) ;
4108
let t = std:: time:: Instant:: now( ) ;
4109
let h = repo.commit ( " big" , & [ ] ) .unwrap ( ) ;
4110
eprintln! ( " commit (chunk + hash + store): {:.2?}" , t.elapsed ( ) ) ;
4111
let t = std:: time:: Instant:: now( ) ;
4112
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4113
eprintln! ( " status on a clean 1 GiB working copy: {:.2?}" , t.elapsed ( ) ) ;
4114
let t = std:: time:: Instant:: now( ) ;
4115
let objects = repo.store ( ) .list ( ) .unwrap ( ) .len ( ) ;
4116
eprintln! ( " objects stored: {objects} ({:.2?} to list)" , t.elapsed ( ) ) ;
4117
let t = std:: time:: Instant:: now( ) ;
4118
let g = repo.gc ( true ) .unwrap ( ) ;
4119
eprintln! ( " gc (pack): {:.2?} {g:?}" , t.elapsed ( ) ) ;
4120
let t = std:: time:: Instant:: now( ) ;
4121
let clone = Repo:: clone( & root.join ( " r" ) .to_string_lossy ( ) , & root.join ( " c" ) ) .unwrap ( ) ;
4122
eprintln! ( " clone (fetch + checkout): {:.2?}" , t.elapsed ( ) ) ;
4123
let t = std:: time:: Instant:: now( ) ;
4124
let back = fs:: read( root.join ( " c/big.bin" ) ) .unwrap ( ) ;
4125
assert_eq! ( blake3:: hash( & back) , blake3:: hash( & data) ) ;
4126
eprintln! ( " read back and verify: {:.2?}" , t.elapsed ( ) ) ;
4127
assert_eq! ( clone.bookmark_heads ( ) .unwrap ( ) , vec! [ h] ) ;
4128
let store_bytes: u64 = walkdir_size ( & root.join ( " r/.oxy/objects" ) ) ;
75167672 Andy McCall 1 month ago
4129
eprintln! (
4130
" store size: {:.1} MiB for 1 GiB of data" ,
4131
store_bytes as f64 / ( 1024. 0 * 1024. 0 )
4132
) ;
2b872d07 Andy McCall 1 month ago ↶
4133
}
4134
4135
fn walkdir_size ( p : & Path) -> u64 {
4136
let mut total = 0 ;
4137
if let Ok ( rd) = fs:: read_dir( p) {
4138
for e in rd.flatten ( ) {
4139
let path = e.path ( ) ;
4140
if path.is_dir ( ) {
4141
total + = walkdir_size ( & path) ;
4142
} else if let Ok ( m) = fs:: metadata( & path) {
4143
total + = m.len ( ) ;
4144
}
4145
}
4146
}
4147
total
4148
}
4149
b7f102fc Andy McCall 1 month ago ↶
4150
4151
4152
# [ cfg ( unix ) ]
4153
# [ test ]
4154
fn stat_cache_skips_reading_unchanged_opaque_files ( ) {
4155
use std:: os:: unix:: fs:: PermissionsExt;
4156
hermetic ( ) ;
4157
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4158
let root = dir.path ( ) ;
4159
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4160
let mut big = vec! [ 0 u8 ; 300 * 1024 ] ;
4161
for ( i, b) in big.iter_mut ( ) .enumerate ( ) {
4162
* b = ( i as u32 ) .wrapping_mul ( 2654435761 ) .to_le_bytes ( ) [ 1 ] ;
4163
}
4164
big[ 0 ] = 0 ;
4165
write ( root, " big.bin" , & big) ;
4166
write ( root, " a.txt" , b " text\n " ) ;
4167
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4168
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4169
assert! (
4170
m.entries[ " big.bin" ] .blob.is_some ( ) ,
4171
" opaque files record their blob"
4172
) ;
b7f102fc Andy McCall 1 month ago ↶
4173
assert! ( m.entries[ " a.txt" ] .blob.is_none ( ) ) ;
4174
let blob = m.entries[ " big.bin" ] .blob.unwrap ( ) ;
4175
assert! ( repo.store ( ) .has ( & blob) ) ;
4176
4177
4178
4179
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
75167672 Andy McCall 1 month ago
4180
fs:: File:: options( )
4181
.write ( true )
4182
.open ( root.join ( " big.bin" ) )
4183
.unwrap ( )
4184
.set_modified ( old)
4185
.unwrap ( ) ;
b7f102fc Andy McCall 1 month ago ↶
4186
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4187
4188
fs:: set_permissions( root.join ( " big.bin" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
4189
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4190
assert! (
4191
! snap.changed & & snap.statuses.is_empty ( ) ,
4192
" {:?}" ,
4193
snap.statuses
4194
) ;
b7f102fc Andy McCall 1 month ago ↶
4195
fs:: set_permissions( root.join ( " big.bin" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
4196
4197
let mut big2 = big.clone ( ) ;
4198
big2[ 10 ] ^ = 0xff ;
4199
write ( root, " big.bin" , & big2) ;
4200
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4201
assert_eq! (
4202
snap.statuses
4203
.iter ( )
4204
.map ( | s | ( s.path.as_str ( ) , s.status) )
4205
.collect:: < Vec< _ > > ( ) ,
4206
vec! [ ( " big.bin" , ChangeStatus:: Modified) ]
4207
) ;
b7f102fc Andy McCall 1 month ago ↶
4208
let h2 = repo.commit ( " edit" , & [ ] ) .unwrap ( ) ;
4209
let files = repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
4210
assert_eq! (
4211
files[ " big.bin" ] .content,
4212
FileContent:: Opaque {
4213
blobs: vec! [ Repo:: blob_ref( & big2) ]
4214
}
4215
) ;
b7f102fc Andy McCall 1 month ago ↶
4216
4217
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
4218
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
4219
assert_eq! ( m.entries[ " big.bin" ] .blob, Some ( Repo:: blob_ref( & big) ) ) ;
4220
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4221
}
4222
50a046e4 Andy McCall 1 month ago ↶
4223
# [ test ]
4224
fn status_json_lists_conflicts_with_contributors ( ) {
4225
hermetic ( ) ;
4226
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4227
let root = dir.path ( ) ;
4228
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4229
write ( root, " a.txt" , b " one\n " ) ;
4230
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4231
write ( root, " a.txt" , b " one\n left\n " ) ;
4232
let ha = repo.commit ( " left" , & [ ] ) .unwrap ( ) ;
4233
repo.checkout ( & [ h1] , true , " checkout" ) .unwrap ( ) ;
4234
write ( root, " a.txt" , b " one\n right\n " ) ;
4235
let hb = repo.commit ( " right" , & [ ] ) .unwrap ( ) ;
4236
repo.checkout ( & [ ha, hb] , true , " checkout" ) .unwrap ( ) ;
4237
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4238
assert! ( snap.base_snapshot.is_conflicted ( ) ) ;
4239
let json = repo.status_json ( & snap) .unwrap ( ) ;
4240
let ida = repo.store ( ) .get_change ( & ha) .unwrap ( ) .change_id.to_hex ( ) ;
4241
let idb = repo.store ( ) .get_change ( & hb) .unwrap ( ) .change_id.to_hex ( ) ;
4242
assert! ( json.contains ( " \" conflicted\" :true" ) , " {json}" ) ;
75167672 Andy McCall 1 month ago
4243
assert! (
4244
json.contains ( " \" kind\" :\" text\" ,\" path\" :\" a.txt\" " ) ,
4245
" {json}"
4246
) ;
50a046e4 Andy McCall 1 month ago ↶
4247
assert! ( json.contains ( & ida) & & json.contains ( & idb) , " {json}" ) ;
4248
assert! ( json.contains ( " \" resolved\" :false" ) , " {json}" ) ;
4249
assert! ( json.starts_with ( ' {' ) & & json.ends_with ( ' }' ) ) ;
4250
4251
write ( root, " a.txt" , b " one\n left\n right\n " ) ;
4252
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4253
let json = repo.status_json ( & snap) .unwrap ( ) ;
4254
assert! ( json.contains ( " \" resolved\" :true" ) , " {json}" ) ;
4255
repo.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
4256
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4257
let json = repo.status_json ( & snap) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4258
assert! (
4259
json.contains ( " \" clean\" :true,\" conflicted\" :false,\" paths\" :[],\" conflicts\" :[]" ) ,
4260
" {json}"
4261
) ;
50a046e4 Andy McCall 1 month ago ↶
4262
}
4263
9ac1fbe2 Andy McCall 1 month ago ↶
4264
# [ test ]
4265
fn ignore_rules_do_not_untrack_files ( ) {
4266
hermetic ( ) ;
4267
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4268
let root = dir.path ( ) ;
4269
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4270
write ( root, " src.c" , b " int x;\n " ) ;
4271
write ( root, " build/out.o" , b " \0 obj" ) ;
4272
write ( root, " sub/keep.log" , b " keep\n " ) ;
4273
write ( root, " sub/junk.log" , b " junk\n " ) ;
4274
write ( root, " .oxyignore" , b " /build\n *.log\n !sub/keep.log\n " ) ;
4275
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4276
let added: Vec< & str > = snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect ( ) ;
75167672 Andy McCall 1 month ago
4277
assert_eq! (
4278
added,
4279
vec! [ " .oxyignore" , " src.c" , " sub/keep.log" ] ,
4280
" anchored, glob, and negated rules"
4281
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4282
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4283
4284
write ( root, " .oxyignore" , b " /build\n *.log\n !sub/keep.log\n src.c\n " ) ;
4285
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4286
assert_eq! (
4287
snap.statuses
4288
.iter ( )
4289
.map ( | s | s.path.as_str ( ) )
4290
.collect:: < Vec< _ > > ( ) ,
4291
vec! [ " .oxyignore" ]
4292
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4293
let h2 = repo.commit ( " ignore src.c" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4294
assert! ( repo
4295
.load_state ( & [ h2] )
4296
.unwrap ( )
4297
.materialize ( )
4298
.files
4299
.contains_key ( " src.c" ) ) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4300
4301
assert_eq! ( repo.quick_dirty ( ) .unwrap ( ) , Some ( false ) ) ;
4302
write ( root, " src.c" , b " int x, y;\n " ) ;
4303
assert_eq! ( repo.quick_dirty ( ) .unwrap ( ) , Some ( true ) ) ;
4304
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4305
assert_eq! (
4306
snap.statuses
4307
.iter ( )
4308
.map ( | s | ( s.path.as_str ( ) , s.status) )
4309
.collect:: < Vec< _ > > ( ) ,
4310
vec! [ ( " src.c" , ChangeStatus:: Modified) ]
4311
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4312
let h3 = repo.commit ( " edit src.c" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4313
assert! (
4314
matches! ( & repo.load_state ( & [ h3] ) .unwrap ( ) .materialize ( ) .files[ " src.c" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " int x, y;\n " )
4315
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4316
4317
fs:: remove_file( root.join ( " src.c" ) ) .unwrap ( ) ;
4318
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4319
assert_eq! (
4320
snap.statuses
4321
.iter ( )
4322
.map ( | s | ( s.path.as_str ( ) , s.status) )
4323
.collect:: < Vec< _ > > ( ) ,
4324
vec! [ ( " src.c" , ChangeStatus:: Removed) ]
4325
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4326
repo.checkout ( & [ h3] , true , " checkout" ) .unwrap ( ) ;
4327
assert_eq! ( fs:: read( root.join ( " src.c" ) ) .unwrap ( ) , b " int x, y;\n " ) ;
4328
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4329
let _ = h1;
4330
}
4331
b837d2b5 Andy McCall 1 month ago ↶
4332
# [ test ]
4333
fn split_works_on_non_heads_by_node ( ) {
4334
hermetic ( ) ;
4335
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4336
let root = dir.path ( ) ;
4337
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4338
write ( root, " a.txt" , b " one\n " ) ;
4339
write ( root, " junk/x.lock" , b " lock\n " ) ;
4340
let h1 = repo.commit ( " work plus junk" , & [ ] ) .unwrap ( ) ;
4341
write ( root, " a.txt" , b " one\n two\n " ) ;
4342
let h2 = repo.commit ( " more work" , & [ ] ) .unwrap ( ) ;
4343
assert_eq! ( repo.store ( ) .get_change ( & h2) .unwrap ( ) .deps, vec! [ h1] ) ;
4344
let id1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) .change_id;
4345
4346
let ( hj, h1b) = repo.split ( & h1, & [ " junk" .into ( ) ] ) .unwrap ( ) ;
4347
assert_ne! ( h1b, h1) ;
75167672 Andy McCall 1 month ago
4348
assert_eq! (
4349
repo.store ( ) .get_change ( & h1b) .unwrap ( ) .change_id,
4350
id1,
4351
" the rest keeps the ChangeId"
4352
) ;
b837d2b5 Andy McCall 1 month ago ↶
4353
let junk = repo.store ( ) .get_change ( & hj) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4354
assert! ( junk.tree_ops.iter ( ) .all (
4355
| op | matches! ( op, TreeOp:: AddNode { name, .. } if name = = " junk" | | name = = " x.lock" )
4356
) ) ;
b837d2b5 Andy McCall 1 month ago ↶
4357
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4358
assert! (
4359
heads.contains ( & hj) ,
4360
" the split-off part is a head of its own"
4361
) ;
b837d2b5 Andy McCall 1 month ago ↶
4362
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
4363
assert! (
4364
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n " ) ,
4365
" the dependent was rewritten onto the new hash"
4366
) ;
b837d2b5 Andy McCall 1 month ago ↶
4367
assert! ( snap.files.contains_key ( " junk/x.lock" ) ) ;
75167672 Andy McCall 1 month ago
4368
let h2b = heads
4369
.iter ( )
4370
.find ( | h | repo.store ( ) .get_change ( h) .unwrap ( ) .message = = " more work" )
4371
.copied ( )
4372
.unwrap ( ) ;
b837d2b5 Andy McCall 1 month ago ↶
4373
assert_eq! ( repo.store ( ) .get_change ( & h2b) .unwrap ( ) .deps, vec! [ h1b] ) ;
4374
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4375
4376
repo.abandon ( & hj) .unwrap ( ) ;
4377
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
4378
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
4379
assert! ( ! snap.files.contains_key ( " junk/x.lock" ) & & ! snap.files.contains_key ( " junk" ) ) ;
4380
assert! ( snap.files.contains_key ( " a.txt" ) ) ;
4381
4382
write ( root, " junk/y.lock" , b " y\n " ) ;
4383
let hb = repo.commit ( " junk base" , & [ ] ) .unwrap ( ) ;
4384
write ( root, " junk/y.lock" , b " yy\n " ) ;
4385
write ( root, " b.txt" , b " b\n " ) ;
4386
repo.commit ( " touches junk" , & [ ] ) .unwrap ( ) ;
4387
let err = repo.split ( & hb, & [ " junk" .into ( ) ] ) .unwrap_err ( ) .to_string ( ) ;
4388
assert! ( err.contains ( " depends on the split-off paths" ) , " {err}" ) ;
4389
}
4390
7b9809e6 Andy McCall 1 month ago ↶
4391
# [ test ]
4392
fn bookmarks_switch_merge_and_push ( ) {
4393
hermetic ( ) ;
4394
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4395
let root = dir.path ( ) ;
4396
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
4397
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
4398
let mut repo = Repo:: clone( & url, & root.join ( " r" ) )
4399
.unwrap ( )
4400
.with_home ( root.join ( " home" ) ) ;
7b9809e6 Andy McCall 1 month ago ↶
4401
let r = root.join ( " r" ) ;
4402
write ( & r, " a.txt" , b " a\n " ) ;
4403
let h_a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
4404
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
4405
origin.refresh ( ) .unwrap ( ) ;
7b9809e6 Andy McCall 1 month ago ↶
4406
4407
4408
assert! ( repo.bookmark_create ( " bad name.." , None ) .is_err ( ) ) ;
4409
assert! ( repo.bookmark_create ( " -x" , None ) .is_err ( ) ) ;
4410
assert_eq! ( repo.bookmark_create ( " feature" , None ) .unwrap ( ) , vec! [ h_a] ) ;
4411
assert! ( repo.bookmark_create ( " feature" , None ) .is_err ( ) , " exists" ) ;
4412
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
4413
assert_eq! ( repo.config.bookmark, " feature" ) ;
4414
write ( & r, " a.txt" , b " a\n b\n " ) ;
4415
write ( & r, " b.txt" , b " b\n " ) ;
4416
let h_b = repo.commit ( " b on feature" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4417
assert_eq! (
4418
repo.bookmark_heads ( ) .unwrap ( ) ,
4419
vec! [ h_b] ,
4420
" b edits a.txt, so it depends on a"
4421
) ;
4422
assert_eq! (
4423
repo.resolve_heads ( " main" ) .unwrap ( ) ,
4424
vec! [ h_a] ,
4425
" main is untouched"
4426
) ;
4427
assert_eq! (
4428
repo.resolve_change ( " feature" ) .unwrap ( ) ,
4429
h_b,
4430
" a one-head bookmark names its change"
4431
) ;
7b9809e6 Andy McCall 1 month ago ↶
4432
4433
let reopened = Repo:: open( & r) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4434
assert_eq! ( reopened.config.bookmark, " feature" ) ;
4435
4436
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
4437
assert! ( ! r.join ( " b.txt" ) .exists ( ) ) ;
4438
assert_eq! ( fs:: read( r.join ( " a.txt" ) ) .unwrap ( ) , b " a\n " ) ;
4439
write ( & r, " c.txt" , b " c\n " ) ;
4440
let h_c = repo.commit ( " c on main" , & [ ] ) .unwrap ( ) ;
4441
let list = repo.bookmarks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4442
assert_eq! (
4443
list.iter ( )
4444
.map ( | b | ( b.name.as_str ( ) , b.current) )
4445
.collect:: < Vec< _ > > ( ) ,
4446
vec! [ ( " feature" , false ) , ( " main" , true ) ]
4447
) ;
4448
assert_eq! (
4449
list.iter ( ) .find ( | b | b.name = = " main" ) .unwrap ( ) .ahead,
4450
Some ( 1 ) ,
4451
" c is not on origin yet"
4452
) ;
7b9809e6 Andy McCall 1 month ago ↶
4453
4454
let m = repo.merge ( " feature" ) .unwrap ( ) ;
4455
assert! ( m.changed & & m.checked_out) ;
4456
let mut want = vec! [ h_b, h_c] ;
4457
want.sort ( ) ;
4458
assert_eq! ( m.heads, want) ;
4459
assert_eq! ( fs:: read( r.join ( " b.txt" ) ) .unwrap ( ) , b " b\n " ) ;
4460
assert! ( ! repo.merge ( " feature" ) .unwrap ( ) .changed, " already merged" ) ;
4461
assert! ( repo.merge ( " main" ) .is_err ( ) ) ;
4462
4463
assert! ( repo.push_bookmark ( " origin" , " feature" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
4464
origin.refresh ( ) .unwrap ( ) ;
7b9809e6 Andy McCall 1 month ago ↶
4465
assert_eq! ( origin.resolve_heads ( " feature" ) .unwrap ( ) , vec! [ h_b] ) ;
4466
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
4467
origin.refresh ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4468
assert_eq! (
4469
repo.bookmarks ( )
4470
.unwrap ( )
4471
.iter ( )
4472
.find ( | b | b.name = = " main" )
4473
.unwrap ( )
4474
.ahead,
4475
Some ( 0 )
4476
) ;
7b9809e6 Andy McCall 1 month ago ↶
4477
4478
let ws = repo.workspace_add ( & root.join ( " ws" ) , None ) .unwrap ( ) ;
4479
assert_eq! ( ws.config.bookmark, " main" ) ;
4480
let mut ws = ws;
4481
ws.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4482
assert_eq! (
4483
Repo:: open( & r) .unwrap ( ) .config.bookmark,
4484
" main" ,
4485
" the first workspace is unaffected"
4486
) ;
4487
assert! (
4488
repo.bookmark_delete ( " feature" ) .is_err ( ) ,
4489
" current bookmark of another workspace"
4490
) ;
7b9809e6 Andy McCall 1 month ago ↶
4491
ws.bookmark_switch ( " main" , false ) .unwrap ( ) ;
4492
4493
assert_eq! ( repo.bookmark_set ( " feature" , & [ h_a] ) .unwrap ( ) , vec! [ h_a] ) ;
4494
assert! ( repo.bookmark_delete ( " main" ) .is_err ( ) , " current here" ) ;
4495
repo.bookmark_delete ( " feature" ) .unwrap ( ) ;
4496
assert! ( repo.resolve_heads ( " feature" ) .is_err ( ) ) ;
4497
assert! ( repo.bookmark_set ( " feature" , & [ h_a] ) .is_err ( ) ) ;
75167672 Andy McCall 1 month ago
4498
assert! (
4499
repo.checkout ( & [ h_a] , false , " checkout" ) .is_ok ( ) ,
4500
" changes of a deleted bookmark remain"
4501
) ;
7b9809e6 Andy McCall 1 month ago ↶
4502
}
4503
4dee277c Andy McCall 1 month ago ↶
4504
# [ test ]
4505
fn oxygen_subrepos_pin_follow_and_update ( ) {
4506
hermetic ( ) ;
4507
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4508
let root = dir.path ( ) ;
4509
4510
let lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
4511
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
4512
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
4513
.unwrap ( )
4514
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4515
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
4516
let l1 = lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
4517
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
4518
let _ = lib_origin;
4519
4520
4521
let app_origin = Repo:: init( & root.join ( " app-origin" ) ) .unwrap ( ) ;
4522
let app_url = root.join ( " app-origin" ) .to_string_lossy ( ) .to_string ( ) ;
4523
let _ = app_origin;
4524
let app_dir = root.join ( " app" ) ;
75167672 Andy McCall 1 month ago
4525
let app = Repo:: clone( & app_url, & app_dir)
4526
.unwrap ( )
4527
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4528
write ( & app_dir, " main.txt" , b " app\n " ) ;
4529
let pin = app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
4530
assert! ( matches! ( & pin, crate :: subrepo:: Pin:: Oxy { heads, .. } if heads = = & vec! [ l1] ) ) ;
4531
assert! ( app_dir.join ( " .oxymodules" ) .exists ( ) & & app_dir.join ( " libs/lib/.oxy" ) .is_dir ( ) ) ;
4532
let snap = app.snapshot ( " status" ) .unwrap ( ) ;
4533
let paths: Vec< & str > = snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect ( ) ;
75167672 Andy McCall 1 month ago
4534
assert_eq! (
4535
paths,
4536
vec! [ " .oxymodules" , " libs/lib" , " main.txt" ] ,
4537
" the nested files are not walked"
4538
) ;
4dee277c Andy McCall 1 month ago ↶
4539
let a1 = app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
4540
let files = app.load_state ( & [ a1] ) .unwrap ( ) .materialize ( ) .files;
4541
assert_eq! ( files[ " libs/lib" ] .kind, NodeKind:: Subrepo) ;
4542
assert! ( ! files.contains_key ( " libs/lib/lib.txt" ) ) ;
4543
assert! ( ! app.snapshot ( " status" ) .unwrap ( ) .changed) ;
4544
let subs = app.subrepos ( ) .unwrap ( ) ;
4545
assert_eq! ( subs.len ( ) , 1 ) ;
4546
assert_eq! ( subs[ 0 ] .actual.as_ref ( ) , Some ( & subs[ 0 ] .pin) ) ;
4547
4548
75167672 Andy McCall 1 month ago
4549
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) )
4550
.unwrap ( )
4551
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4552
write ( & app_dir.join ( " libs/lib" ) , " lib.txt" , b " v2\n " ) ;
4553
let l2 = nested.commit ( " lib v2" , & [ ] ) .unwrap ( ) ;
4554
assert! ( nested.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
4555
let snap = app.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4556
assert_eq! (
4557
snap.statuses
4558
.iter ( )
4559
.map ( | s | ( s.path.as_str ( ) , s.status) )
4560
.collect:: < Vec< _ > > ( ) ,
4561
vec! [ ( " libs/lib" , ChangeStatus:: Modified) ]
4562
) ;
4dee277c Andy McCall 1 month ago ↶
4563
let a2 = app.commit ( " bump lib" , & [ ] ) .unwrap ( ) ;
1a3b6203 Andy McCall 1 month ago ↶
4564
4565
4dee277c Andy McCall 1 month ago ↶
4566
let r = app.checkout ( & [ a1] , false , " checkout" ) .unwrap ( ) ;
1a3b6203 Andy McCall 1 month ago ↶
4567
assert! (
4568
r.notes.len ( ) = = 1 & & r.notes[ 0 ] .contains ( " keeps 1 newer change" ) ,
4569
" {:?}" ,
4570
r.notes
4571
) ;
4dee277c Andy McCall 1 month ago ↶
4572
assert_eq! ( fs:: read( app_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v1\n " ) ;
1a3b6203 Andy McCall 1 month ago ↶
4573
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) ) .unwrap ( ) ;
4574
assert_eq! ( nested.bookmark_heads ( ) .unwrap ( ) , vec! [ l2] ) ;
4575
assert_eq! ( nested.working_copy ( ) .unwrap ( ) .1. deps, vec! [ l1] ) ;
4dee277c Andy McCall 1 month ago ↶
4576
assert! ( ! app.snapshot ( " status" ) .unwrap ( ) .changed) ;
4577
4578
write ( & app_dir.join ( " libs/lib" ) , " scratch.txt" , b " wip\n " ) ;
4579
let r = app.checkout ( & [ a2] , false , " checkout" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4580
assert! (
4581
r.notes.iter ( ) .any ( | n | n.contains ( " uncommitted" ) ) ,
4582
" {:?}" ,
4583
r.notes
4584
) ;
4dee277c Andy McCall 1 month ago ↶
4585
assert_eq! ( fs:: read( app_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v1\n " ) ;
4586
fs:: remove_file( app_dir.join ( " libs/lib/scratch.txt" ) ) .unwrap ( ) ;
4587
let r = app.submodule_update ( false , & [ ] ) .unwrap ( ) ;
4588
assert_eq! ( r, vec! [ ( " libs/lib" .to_string ( ) , SubrepoSync:: Updated) ] ) ;
4589
assert_eq! ( fs:: read( app_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v2\n " ) ;
4590
let _ = l2;
4591
4592
4593
assert! ( app.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
4594
let clone_dir = root.join ( " app-clone" ) ;
75167672 Andy McCall 1 month ago
4595
let clone = Repo:: clone( & app_url, & clone_dir)
4596
.unwrap ( )
4597
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4598
assert! ( ! clone_dir.join ( " libs/lib/.oxy" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
4599
assert_eq! (
4600
clone.submodule_update ( false , & [ ] ) .unwrap ( ) ,
4601
vec! [ ( " libs/lib" .to_string ( ) , SubrepoSync:: Missing) ]
4602
) ;
4603
assert_eq! (
4604
clone.submodule_update ( true , & [ ] ) .unwrap ( ) ,
4605
vec! [ ( " libs/lib" .to_string ( ) , SubrepoSync:: Updated) ]
4606
) ;
4607
assert_eq! (
4608
fs:: read( clone_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) ,
4609
b " v2\n "
4610
) ;
4dee277c Andy McCall 1 month ago ↶
4611
assert! ( ! clone.snapshot ( " status" ) .unwrap ( ) .changed) ;
4612
4613
clone.submodule_remove ( " libs/lib" ) .unwrap ( ) ;
4614
let snap = clone.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4615
assert! (
4616
snap.statuses
4617
.iter ( )
4618
.any ( | s | s.path = = " libs/lib" & & s.status = = ChangeStatus:: Removed ) ,
4619
" {:?}" ,
4620
snap.statuses
4621
) ;
4dee277c Andy McCall 1 month ago ↶
4622
let c = clone.commit ( " drop lib" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4623
assert! ( ! clone
4624
.load_state ( & [ c] )
4625
.unwrap ( )
4626
.materialize ( )
4627
.files
4628
.contains_key ( " libs/lib" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4629
assert! ( clone_dir.join ( " libs/lib/lib.txt" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
4630
assert! (
4631
! clone.snapshot ( " status" ) .unwrap ( ) .changed,
4632
" an unregistered nested repository is ignored"
4633
) ;
4dee277c Andy McCall 1 month ago ↶
4634
}
4635
4636
# [ test ]
4637
fn git_submodules_import_and_export_as_gitlinks ( ) {
4638
hermetic ( ) ;
75167672 Andy McCall 1 month ago
4639
if std:: process:: Command:: new( " git" )
4640
.arg ( " --version" )
4641
.output ( )
4642
.is_err ( )
4643
{
4dee277c Andy McCall 1 month ago ↶
4644
eprintln! ( " git not installed; skipping" ) ;
4645
return ;
4646
}
4647
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4648
let root = dir.path ( ) ;
4649
let git_in = | d : & Path, args : & [& str ]| -> String {
4650
let out = std:: process:: Command:: new( " git" )
4651
.arg ( " -C" )
4652
.arg ( d)
75167672 Andy McCall 1 month ago
4653
.args ( [
4654
" -c" ,
4655
" user.name=Grace" ,
4656
" -c" ,
4657
" [email protected] " ,
4658
" -c" ,
4659
" commit.gpgsign=false" ,
4660
" -c" ,
4661
" protocol.file.allow=always" ,
4662
] )
4dee277c Andy McCall 1 month ago ↶
4663
.args ( args)
4664
.output ( )
4665
.unwrap ( ) ;
75167672 Andy McCall 1 month ago
4666
assert! (
4667
out.status.success ( ) ,
4668
" git {args:?}: {}" ,
4669
String :: from_utf8_lossy( & out.stderr)
4670
) ;
4dee277c Andy McCall 1 month ago ↶
4671
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
4672
} ;
4673
let sub = root.join ( " sub" ) ;
4674
fs:: create_dir_all( & sub) .unwrap ( ) ;
4675
git_in ( & sub, & [ " init" , " -q" , " -b" , " main" ] ) ;
4676
write ( & sub, " s.txt" , b " sub\n " ) ;
4677
git_in ( & sub, & [ " add" , " ." ] ) ;
4678
git_in ( & sub, & [ " commit" , " -q" , " -m" , " sub initial" ] ) ;
4679
let sub_sha = git_in ( & sub, & [ " rev-parse" , " HEAD" ] ) ;
4680
let g = root.join ( " main.git" ) ;
4681
fs:: create_dir_all( & g) .unwrap ( ) ;
4682
git_in ( & g, & [ " init" , " -q" , " -b" , " main" ] ) ;
4683
write ( & g, " README.md" , b " # app\n " ) ;
4684
git_in ( & g, & [ " add" , " ." ] ) ;
4685
git_in ( & g, & [ " commit" , " -q" , " -m" , " initial" ] ) ;
75167672 Andy McCall 1 month ago
4686
git_in (
4687
& g,
4688
& [
4689
" submodule" ,
4690
" add" ,
4691
" -q" ,
4692
sub.to_str ( ) .unwrap ( ) ,
4693
" vendor/sub" ,
4694
] ,
4695
) ;
4dee277c Andy McCall 1 month ago ↶
4696
git_in ( & g, & [ " commit" , " -q" , " -m" , " add submodule" ] ) ;
4697
4698
let dest = root.join ( " oxy" ) ;
4699
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4700
let rep = repo.import_git ( & g, " main" ) .unwrap ( ) ;
4701
assert_eq! ( ( rep.commits, rep.submodules) , ( 2 , 1 ) ) ;
75167672 Andy McCall 1 month ago
4702
let files = repo
4703
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
4704
.unwrap ( )
4705
.materialize ( )
4706
.files;
4dee277c Andy McCall 1 month ago ↶
4707
assert_eq! ( files[ " vendor/sub" ] .kind, NodeKind:: Subrepo) ;
75167672 Andy McCall 1 month ago
4708
let FileContent:: Opaque { blobs } = & files[ " vendor/sub" ] .content else {
4709
panic! ( )
4710
} ;
4711
assert_eq! (
4712
crate :: subrepo:: Pin:: decode( & repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) ) .unwrap ( ) ,
4713
crate :: subrepo:: Pin:: Git {
4714
sha: sub_sha.clone ( ) ,
4715
branch: " HEAD" .into ( )
4716
}
4717
) ;
4718
let modules =
4719
crate :: subrepo:: Modules:: parse( & fs:: read_to_string( dest.join ( " .oxymodules" ) ) .unwrap ( ) ) ;
4dee277c Andy McCall 1 month ago ↶
4720
assert_eq! ( modules.entries[ " vendor/sub" ] .url, sub.to_string_lossy ( ) ) ;
4721
assert! ( modules.entries[ " vendor/sub" ] .git) ;
75167672 Andy McCall 1 month ago
4722
assert! (
4723
files.contains_key ( " .gitmodules" ) ,
4724
" the Git file is kept for round trips"
4725
) ;
4726
assert! (
4727
! dest.join ( " vendor/sub/.git" ) .exists ( ) ,
4728
" not cloned until update --init"
4729
) ;
4dee277c Andy McCall 1 month ago ↶
4730
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4731
75167672 Andy McCall 1 month ago
4732
assert_eq! (
4733
repo.submodule_update ( true , & [ ] ) .unwrap ( ) ,
4734
vec! [ ( " vendor/sub" .to_string ( ) , SubrepoSync:: Updated) ]
4735
) ;
4736
assert_eq! (
4737
git_in ( & dest.join ( " vendor/sub" ) , & [ " rev-parse" , " HEAD" ] ) ,
4738
sub_sha
4739
) ;
4dee277c Andy McCall 1 month ago ↶
4740
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4741
4742
let mirror = root.join ( " mirror.git" ) ;
4743
let rep = repo.git_export ( & mirror) .unwrap ( ) ;
4744
assert_eq! ( rep.skipped_subrepos, 0 ) ;
4745
let ls = git_in ( & mirror, & [ " ls-tree" , " main" , " vendor/sub" ] ) ;
75167672 Andy McCall 1 month ago
4746
assert! (
4747
ls.starts_with ( " 160000 commit " ) & & ls.contains ( & sub_sha) ,
4748
" {ls}"
4749
) ;
4750
assert_eq! (
4751
git_in ( & mirror, & [ " ls-tree" , " --name-only" , " main" ] ) ,
4752
" .gitmodules\n README.md\n vendor"
4753
) ;
4dee277c Andy McCall 1 month ago ↶
4754
let orig_tree = git_in ( & g, & [ " rev-parse" , " main^{tree}" ] ) ;
75167672 Andy McCall 1 month ago
4755
assert_eq! (
4756
git_in ( & mirror, & [ " rev-parse" , " main^{tree}" ] ) ,
4757
orig_tree,
4758
" tree round trip"
4759
) ;
4dee277c Andy McCall 1 month ago ↶
4760
}
4761
957fee30 Andy McCall 1 month ago ↶
4762
# [ test ]
4763
fn partial_commits_by_path_and_by_hunk ( ) {
4764
hermetic ( ) ;
4765
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4766
let root = dir.path ( ) ;
4767
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4768
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
4769
write ( root, " b.txt" , b " b\n " ) ;
4770
let h0 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4771
write ( root, " a.txt" , b " 0\n 1\n 2\n 3\n 4\n " ) ;
4772
write ( root, " b.txt" , b " b\n bb\n " ) ;
4773
write ( root, " c.txt" , b " c\n " ) ;
4774
75167672 Andy McCall 1 month ago
4775
let h1 = repo
4776
.commit_selection ( " a only" , & [ ] , & Selection:: Paths( vec! [ " a.txt" .into ( ) ] ) )
4777
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
4778
let c1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4779
assert! (
4780
c1.tree_ops.is_empty ( ) & & c1.edits.iter ( ) .all ( | e | e.node ( ) = = c1.edits[ 0 ] .node ( ) ) ,
4781
" {:?}" ,
4782
c1.tree_ops
4783
) ;
957fee30 Andy McCall 1 month ago ↶
4784
assert_eq! ( c1.deps, vec! [ h0] ) ;
4785
let files = repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
4786
assert! (
4787
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 0\n 1\n 2\n 3\n 4\n " )
4788
) ;
957fee30 Andy McCall 1 month ago ↶
4789
assert! ( matches! ( & files[ " b.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " b\n " ) ) ;
4790
assert! ( ! files.contains_key ( " c.txt" ) ) ;
4791
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4792
assert_eq! (
4793
snap.statuses
4794
.iter ( )
4795
.map ( | s | ( s.path.as_str ( ) , s.status) )
4796
.collect:: < Vec< _ > > ( ) ,
4797
vec! [
4798
( " b.txt" , ChangeStatus:: Modified) ,
4799
( " c.txt" , ChangeStatus:: Added)
4800
]
4801
) ;
4802
assert_eq! (
4803
fs:: read( root.join ( " b.txt" ) ) .unwrap ( ) ,
4804
b " b\n bb\n " ,
4805
" disk untouched"
4806
) ;
957fee30 Andy McCall 1 month ago ↶
4807
4808
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4809
let b_hunk = hunks
4810
.iter ( )
4811
.find ( | h | h.path = = " b.txt" & & h.text.contains ( " +bb" ) )
4812
.unwrap ( )
4813
.index;
4814
let h2 = repo
4815
.commit_selection ( " b only" , & [ ] , & Selection:: Hunks( vec! [ b_hunk] ) )
4816
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
4817
let files = repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
4818
assert! (
4819
matches! ( & files[ " b.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " b\n bb\n " )
4820
) ;
957fee30 Andy McCall 1 month ago ↶
4821
assert! ( ! files.contains_key ( " c.txt" ) ) ;
4822
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4823
assert_eq! (
4824
snap.statuses
4825
.iter ( )
4826
.map ( | s | s.path.as_str ( ) )
4827
.collect:: < Vec< _ > > ( ) ,
4828
vec! [ " c.txt" ]
4829
) ;
957fee30 Andy McCall 1 month ago ↶
4830
4831
4832
write ( root, " d/e.txt" , b " e\n " ) ;
4833
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4834
let e_insert = hunks
4835
.iter ( )
4836
.find ( | h | h.path = = " d/e.txt" & & h.text.contains ( " +e" ) )
4837
.unwrap ( )
4838
.index;
4839
let h3 = repo
4840
.commit_selection ( " e only" , & [ ] , & Selection:: Hunks( vec! [ e_insert] ) )
4841
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
4842
let c3 = repo.store ( ) .get_change ( & h3) .unwrap ( ) ;
4843
assert_eq! ( c3.tree_ops.len ( ) , 2 , " {:?}" , c3.tree_ops) ;
4844
let files = repo.load_state ( & [ h3] ) .unwrap ( ) .materialize ( ) .files;
4845
assert! ( files.contains_key ( " d/e.txt" ) & & ! files.contains_key ( " c.txt" ) ) ;
d8c086ca Andy McCall 1 month ago ↶
4846
4847
write ( root, " f/g.txt" , b " g\n " ) ;
4848
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4849
let g_add = hunks
4850
.iter ( )
4851
.find ( | h | h.path = = " f/g.txt" & & h.text.starts_with ( " A " ) )
4852
.unwrap ( )
4853
.index;
4854
let h4 = repo
4855
.commit_selection ( " g by addition" , & [ ] , & Selection:: Hunks( vec! [ g_add] ) )
4856
.unwrap ( ) ;
d8c086ca Andy McCall 1 month ago ↶
4857
let files = repo.load_state ( & [ h4] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
4858
assert! (
4859
matches! ( & files[ " f/g.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " g\n " )
4860
) ;
4861
assert! ( ! repo
4862
.snapshot ( " status" )
4863
.unwrap ( )
4864
.statuses
4865
.iter ( )
4866
.any ( | s | s.path = = " f/g.txt" ) ) ;
957fee30 Andy McCall 1 month ago ↶
4867
4868
repo.commit ( " the rest" , & [ ] ) .unwrap ( ) ;
4869
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
75167672 Andy McCall 1 month ago
4870
let files = repo
4871
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
4872
.unwrap ( )
4873
.materialize ( )
4874
.files;
957fee30 Andy McCall 1 month ago ↶
4875
assert! ( files.contains_key ( " c.txt" ) ) ;
4876
}
4877
4878
# [ test ]
4879
fn blame_attributes_lines_to_introducing_changes_across_renames ( ) {
4880
hermetic ( ) ;
4881
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4882
let root = dir.path ( ) ;
4883
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
4884
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
4885
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
4886
write ( root, " a.txt" , b " one\n " ) ;
4887
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
4888
write ( root, " a.txt" , b " one\n two\n " ) ;
4889
let h2 = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
4890
let b = repo.blame ( " a.txt" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4891
assert_eq! (
4892
b.iter ( )
4893
.map ( | l | ( l.line, l.hash, l.text.as_slice ( ) ) )
4894
.collect:: < Vec< _ > > ( ) ,
4895
vec! [ ( 1 , h1, b " one\n " .as_slice ( ) ) , ( 2 , h2, b " two\n " .as_slice ( ) ) ]
4896
) ;
957fee30 Andy McCall 1 month ago ↶
4897
assert! ( b.iter ( ) .all ( | l | l.author = = " Ada" ) ) ;
75167672 Andy McCall 1 month ago
4898
assert_eq! (
4899
b[ 0 ] .change_id,
4900
repo.store ( ) .get_change ( & h1) .unwrap ( ) .change_id
4901
) ;
957fee30 Andy McCall 1 month ago ↶
4902
fs:: rename( root.join ( " a.txt" ) , root.join ( " b.txt" ) ) .unwrap ( ) ;
4903
repo.commit ( " rename" , & [ ] ) .unwrap ( ) ;
4904
let b = repo.blame ( " b.txt" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4905
assert_eq! (
4906
b.iter ( ) .map ( | l | l.hash ) .collect:: < Vec< _ > > ( ) ,
4907
vec! [ h1, h2] ,
4908
" the node survives the rename"
4909
) ;
957fee30 Andy McCall 1 month ago ↶
4910
assert! ( repo.blame ( " a.txt" ) .is_err ( ) ) ;
4911
}
4912
4913
# [ test ]
4914
fn bisect_finds_the_first_bad_change ( ) {
4915
hermetic ( ) ;
4916
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4917
let root = dir.path ( ) ;
4918
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4919
let mut content = String :: new( ) ;
4920
let mut hashes = Vec :: new( ) ;
4921
for i in 1 .. = 8 {
4922
content.push_str ( & format! ( " line {i} {} \n " , if i = = 5 { " bug" } else { " " } ) ) ;
4923
write ( root, " f.txt" , content.as_bytes ( ) ) ;
4924
hashes.push ( repo.commit ( & format! ( " step {i} " ) , & [ ] ) .unwrap ( ) ) ;
4925
}
4926
4927
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ hashes[ 7 ] ] ) ;
4928
repo.bisect_start ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4929
assert! ( matches! (
4930
repo.bisect_mark ( Some ( true ) , & [ hashes[ 0 ] ] ) .unwrap ( ) ,
4931
BisectStep:: NeedMarks { good: 1 , bad: 0 }
4932
) ) ;
957fee30 Andy McCall 1 month ago ↶
4933
let step = repo.bisect_mark ( Some ( false ) , & [ hashes[ 7 ] ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4934
assert! (
4935
matches! ( step, BisectStep:: Testing { remaining: 7 , .. } ) ,
4936
" {step:?}"
4937
) ;
957fee30 Andy McCall 1 month ago ↶
4938
75167672 Andy McCall 1 month ago
4939
let step = repo
4940
.bisect_run ( & [ " sh" .into ( ) , " -c" .into ( ) , " ! grep -q bug f.txt" .into ( ) ] )
4941
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
4942
assert_eq! ( step, BisectStep:: Found { culprit: hashes[ 4 ] } ) ;
75167672 Andy McCall 1 month ago
4943
assert_eq! (
4944
repo.bisect_status ( ) .unwrap ( ) ,
4945
Some ( BisectStep:: Found { culprit: hashes[ 4 ] } )
4946
) ;
957fee30 Andy McCall 1 month ago ↶
4947
repo.bisect_reset ( ) .unwrap ( ) ;
4948
assert_eq! ( fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) , content) ;
4949
assert! ( repo.bisect_status ( ) .unwrap ( ) .is_none ( ) ) ;
4950
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4951
4952
repo.bisect_start ( ) .unwrap ( ) ;
4953
repo.bisect_mark ( Some ( true ) , & [ hashes[ 0 ] ] ) .unwrap ( ) ;
4954
let mut step = repo.bisect_mark ( Some ( false ) , & [ hashes[ 7 ] ] ) .unwrap ( ) ;
4955
let mut skipped_once = false ;
4956
let mut guard = 0 ;
4957
while let BisectStep:: Testing { .. } = step {
4958
guard + = 1 ;
4959
assert! ( guard < 20 ) ;
4960
let text = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
4961
if ! skipped_once & & text.contains ( " line 6" ) {
4962
4963
skipped_once = true ;
4964
step = repo.bisect_mark ( None , & [ ] ) .unwrap ( ) ;
4965
continue ;
4966
}
4967
step = repo.bisect_mark ( Some ( ! text.contains ( " bug" ) ) , & [ ] ) .unwrap ( ) ;
4968
}
4969
assert! ( skipped_once) ;
4970
assert_eq! ( step, BisectStep:: Found { culprit: hashes[ 4 ] } ) ;
4971
repo.bisect_reset ( ) .unwrap ( ) ;
4972
4973
repo.bisect_start ( ) .unwrap ( ) ;
4974
repo.bisect_mark ( Some ( true ) , & [ hashes[ 0 ] ] ) .unwrap ( ) ;
4975
let mut step = repo.bisect_mark ( Some ( false ) , & [ hashes[ 7 ] ] ) .unwrap ( ) ;
4976
let mut guard = 0 ;
4977
while let BisectStep:: Testing { heads, .. } = & step {
4978
guard + = 1 ;
4979
assert! ( guard < 20 ) ;
4980
let text = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4981
let is_culprit_state =
4982
heads.contains ( & hashes[ 4 ] ) & & ! heads.iter ( ) .any ( | h | hashes[ 5 .. ] .contains ( h) ) ;
4983
step = if is_culprit_state {
4984
repo.bisect_mark ( None , & [ ] ) .unwrap ( )
4985
} else {
4986
repo.bisect_mark ( Some ( ! text.contains ( " bug" ) ) , & [ ] ) .unwrap ( )
4987
} ;
957fee30 Andy McCall 1 month ago ↶
4988
}
4989
match step {
75167672 Andy McCall 1 month ago
4990
BisectStep:: Inconclusive { candidates } => assert! (
4991
candidates.contains ( & hashes[ 4 ] ) & & candidates.len ( ) < = 2 ,
4992
" {candidates:?}"
4993
) ,
957fee30 Andy McCall 1 month ago ↶
4994
other => panic! ( " {other:?}" ) ,
4995
}
4996
repo.bisect_reset ( ) .unwrap ( ) ;
4997
}
4998
728da0f9 Andy McCall 1 month ago ↶
4999
# [ test ]
5000
fn squash_folds_a_change_into_its_dependency ( ) {
5001
hermetic ( ) ;
5002
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5003
let root = dir.path ( ) ;
5004
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5005
write ( root, " a.txt" , b " 1\n " ) ;
5006
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
5007
write ( root, " a.txt" , b " 1\n 2\n " ) ;
5008
let c = repo.commit ( " C" , & [ ] ) .unwrap ( ) ;
5009
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
5010
let e = repo.commit ( " E" , & [ ] ) .unwrap ( ) ;
5011
write ( root, " b.txt" , b " b\n " ) ;
5012
let b = repo.commit ( " B" , & [ ] ) .unwrap ( ) ;
5013
let id_a = repo.store ( ) .get_change ( & a) .unwrap ( ) .change_id;
5014
let id_e = repo.store ( ) .get_change ( & e) .unwrap ( ) .change_id;
5015
5016
let err = repo.squash ( & e, & a) .unwrap_err ( ) .to_string ( ) ;
5017
assert! ( err.contains ( & c.short ( 12 ) ) , " {err}" ) ;
5018
5019
let a2 = repo.squash ( & c, & a) .unwrap ( ) ;
5020
let a2c = repo.store ( ) .get_change ( & a2) .unwrap ( ) ;
5021
assert_eq! ( a2c.change_id, id_a) ;
5022
assert! ( a2c.deps.is_empty ( ) ) ;
5023
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5024
let st = repo.load_state ( & heads) .unwrap ( ) ;
5025
assert! ( ! st.contains ( & c) & & ! st.contains ( & a) & & ! st.contains ( & e) ) ;
75167672 Andy McCall 1 month ago
5026
let e2 = st
5027
.changes ( )
5028
.find ( | ( _, m ) | m.change_id = = id_e )
5029
.map ( | ( h , _) | * h)
5030
.unwrap ( ) ;
5031
assert_eq! (
5032
repo.store ( ) .get_change ( & e2) .unwrap ( ) .deps,
5033
vec! [ a2] ,
5034
" E now depends on A' alone"
5035
) ;
728da0f9 Andy McCall 1 month ago ↶
5036
let files = st.materialize ( ) .files;
75167672 Andy McCall 1 month ago
5037
assert! (
5038
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 1\n 2\n 3\n " )
5039
) ;
728da0f9 Andy McCall 1 month ago ↶
5040
assert! ( files.contains_key ( " b.txt" ) ) ;
5041
assert! ( st.contains ( & b) ) ;
5042
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
5043
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 , " working copy, B, E', A'" ) ;
5044
5045
repo.undo ( ) .unwrap ( ) ;
5046
repo.undo ( ) .unwrap ( ) ;
5047
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
5048
assert! ( st.contains ( & c) & & st.contains ( & a) & & st.contains ( & e) ) ;
5049
}
5050
5051
# [ test ]
5052
fn rebase_re_expresses_a_change_against_another_base ( ) {
5053
hermetic ( ) ;
5054
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5055
let root = dir.path ( ) ;
5056
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5057
write ( root, " a.txt" , b " 1\n 2\n " ) ;
5058
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
5059
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
5060
let c = repo.commit ( " C: append 3" , & [ ] ) .unwrap ( ) ;
5061
write ( root, " a.txt" , b " 1\n 2\n 3\n 4\n " ) ;
5062
let e = repo.commit ( " E: append 4" , & [ ] ) .unwrap ( ) ;
5063
5064
repo.checkout ( & [ a] , true , " checkout" ) .unwrap ( ) ;
5065
write ( root, " a.txt" , b " 0\n 1\n 2\n " ) ;
5066
let d = repo.commit ( " D: prepend 0" , & [ ] ) .unwrap ( ) ;
5067
let id_c = repo.store ( ) .get_change ( & c) .unwrap ( ) .change_id;
5068
let id_e = repo.store ( ) .get_change ( & e) .unwrap ( ) .change_id;
5069
5070
let c2 = repo.rebase ( & c, & [ d] ) .unwrap ( ) ;
5071
let c2c = repo.store ( ) .get_change ( & c2) .unwrap ( ) ;
5072
assert_eq! ( c2c.change_id, id_c) ;
5073
assert! ( c2c.deps.contains ( & d) , " {:?}" , c2c.deps) ;
5074
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5075
let st = repo.load_state ( & heads) .unwrap ( ) ;
5076
assert! ( ! st.contains ( & c) & & st.contains ( & d) ) ;
75167672 Andy McCall 1 month ago
5077
let e2 = st
5078
.changes ( )
5079
.find ( | ( _, m ) | m.change_id = = id_e )
5080
.map ( | ( h , _) | * h)
5081
.unwrap ( ) ;
728da0f9 Andy McCall 1 month ago ↶
5082
let e2deps = repo.store ( ) .get_change ( & e2) .unwrap ( ) .deps;
75167672 Andy McCall 1 month ago
5083
assert! (
5084
e2deps.contains ( & c2) & & ! e2deps.contains ( & c) & & e2deps.contains ( & a) ,
5085
" E follows C by content and still depends on A, which introduced the file: {e2deps:?}"
5086
) ;
728da0f9 Andy McCall 1 month ago ↶
5087
let files = st.materialize ( ) .files;
75167672 Andy McCall 1 month ago
5088
assert! (
5089
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 0\n 1\n 2\n 3\n 4\n " ) ,
5090
" {:?}" ,
5091
files[ " a.txt" ] .content
5092
) ;
728da0f9 Andy McCall 1 month ago ↶
5093
assert! ( ! files[ " a.txt" ] .is_conflicted ( ) ) ;
5094
5095
assert! ( repo.rebase ( & c2, & [ c2] ) .is_err ( ) ) ;
5096
let err = repo.rebase ( & e2, & [ d] ) .unwrap_err ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
5097
assert! (
5098
err.contains ( " lacks dependencies" ) ,
5099
" E needs C, which D's state does not have: {err}"
5100
) ;
728da0f9 Andy McCall 1 month ago ↶
5101
}
5102
c792033a Andy McCall 1 month ago ↶
5103
# [ test ]
5104
fn unsigned_tags_keep_their_message ( ) {
5105
hermetic ( ) ;
5106
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5107
let root = dir.path ( ) ;
5108
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5109
write ( root, " a.txt" , b " a\n " ) ;
5110
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
5111
assert! ( repo.secret_key ( ) .unwrap ( ) .is_none ( ) ) ;
75167672 Andy McCall 1 month ago
5112
repo.tag_create ( " v1" , & repo.bookmark_heads ( ) .unwrap ( ) , " first release" )
5113
.unwrap ( ) ;
c792033a Andy McCall 1 month ago ↶
5114
let tags = repo.tags ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5115
assert_eq! (
5116
(
5117
tags[ 0 ] .name.as_str ( ) ,
5118
tags[ 0 ] .message.as_str ( ) ,
5119
tags[ 0 ] .signer.as_deref ( )
5120
) ,
5121
( " v1" , " first release" , None )
5122
) ;
5123
assert! (
5124
repo.tag_refs ( ) .unwrap ( ) [ 0 ] .attestation.is_none ( ) ,
5125
" an unsigned attestation is not advertised"
5126
) ;
c792033a Andy McCall 1 month ago ↶
5127
}
387335ff Andy McCall 1 month ago ↶
5128
5129
# [ test ]
5130
fn fsck_passes_a_sound_repository_and_names_each_fault ( ) {
64818938 Andy McCall 1 month ago ↶
5131
5132
5133
if std:: env:: var_os( " OXY_STORE_BACKEND" ) .is_some ( ) {
5134
return ;
5135
}
387335ff Andy McCall 1 month ago ↶
5136
hermetic ( ) ;
5137
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5138
let root = dir.path ( ) ;
5139
let origin_path = root.join ( " origin" ) ;
5140
let origin = Repo:: init( & origin_path) .unwrap ( ) ;
5141
let mut a = Repo:: clone( & origin_path.to_string_lossy ( ) , & root.join ( " a" ) )
5142
.unwrap ( )
5143
.with_home ( root.join ( " home-a" ) ) ;
5144
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
5145
.unwrap ( ) ;
5146
write ( & root.join ( " a" ) , " f.txt" , b " one\n " ) ;
5147
write ( & root.join ( " a" ) , " bin.dat" , b " \0 \x01 \x02 binary" ) ;
5148
let h1 = a.commit ( " first" , & [ ] ) .unwrap ( ) ;
5149
write ( & root.join ( " a" ) , " f.txt" , b " one\n two\n " ) ;
5150
let h2 = a.commit ( " second" , & [ ] ) .unwrap ( ) ;
5151
a.tag_create ( " v1" , & [ h2] , " first release" ) .unwrap ( ) ;
5152
a.checkpoint ( ) .unwrap ( ) ;
5153
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
5154
origin.refresh ( ) .unwrap ( ) ;
387335ff Andy McCall 1 month ago ↶
5155
5156
5157
for repo in [ & a, & origin] {
5158
let rep = repo.fsck ( ) .unwrap ( ) ;
5159
assert_eq! ( rep.problems, vec! [ ] , " {rep:?}" ) ;
5160
5161
assert! ( rep.changes > = 2 & & rep.signed > = 2 , " {rep:?}" ) ;
5162
assert! ( rep.ops > 1 & & rep.objects > 5 ) ;
5163
}
5164
assert! ( origin.fsck ( ) .unwrap ( ) .attestations > = 1 ) ;
5165
5166
5167
let c1 = a.store ( ) .path_for ( & h1) ;
5168
let pristine = fs:: read( & c1) .unwrap ( ) ;
5169
let mut bytes = pristine.clone ( ) ;
5170
* bytes.last_mut ( ) .unwrap ( ) ^ = 1 ;
5171
fs:: write( & c1, & bytes) .unwrap ( ) ;
5172
let rep = a.fsck ( ) .unwrap ( ) ;
5173
assert! ( rep.errors ( ) > = 1 ) ;
5174
assert! (
5175
rep.problems
5176
.iter ( )
5177
.any ( | p | p.object = = Some ( h1) & & p.what.contains ( " unreadable" ) ) ,
5178
" {rep:?}"
5179
) ;
5180
fs:: write( & c1, & pristine) .unwrap ( ) ;
5181
assert_eq! ( a.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
5182
5183
5184
fs:: remove_file( & c1) .unwrap ( ) ;
5185
let rep = a.fsck ( ) .unwrap ( ) ;
5186
assert! (
5187
rep.problems.iter ( ) .any ( | p | p.severity = = Severity:: Error
5188
& & p.object = = Some ( h1)
5189
& & p.what.contains ( " change missing, needed by change" ) ) ,
5190
" {rep:?}"
5191
) ;
5192
fs:: write( & c1, & pristine) .unwrap ( ) ;
5193
5194
5195
let c2 = a.store ( ) .get_change ( & h1) .unwrap ( ) ;
5196
let blob = c2
5197
.edits
5198
.iter ( )
5199
.find_map ( | e | match e {
5200
Edit:: Replace { new: Some ( b) , .. } => Some ( * b) ,
5201
_ => None ,
5202
} )
5203
.expect ( " the binary file is a blob" ) ;
5204
let bp = a.store ( ) .path_for ( & blob) ;
5205
let blob_bytes = fs:: read( & bp) .unwrap ( ) ;
5206
fs:: remove_file( & bp) .unwrap ( ) ;
5207
let rep = a.fsck ( ) .unwrap ( ) ;
5208
assert! (
5209
rep.problems
5210
.iter ( )
5211
.any ( | p | p.object = = Some ( blob) & & p.what.contains ( " blob missing" ) ) ,
5212
" {rep:?}"
5213
) ;
5214
fs:: write( & bp, & blob_bytes) .unwrap ( ) ;
5215
5216
5217
let head = root.join ( " a/.oxy/ops/head" ) ;
5218
let saved = fs:: read( & head) .unwrap ( ) ;
5219
fs:: write( & head, b " not a hash" ) .unwrap ( ) ;
5220
let rep = a.fsck ( ) .unwrap ( ) ;
5221
assert! (
5222
rep.problems.iter ( ) .any ( | p | p.what.contains ( " ops/head" ) ) ,
5223
" {rep:?}"
5224
) ;
5225
fs:: write( & head, saved) .unwrap ( ) ;
5226
assert_eq! ( a.fsck ( ) .unwrap ( ) .problems, vec! [ ] ) ;
5227
}
4dcb8c09 Andy McCall 1 month ago ↶
5228
5229
5230
fn copy_tree ( from : & Path, to : & Path) {
5231
fs:: create_dir_all( to) .unwrap ( ) ;
5232
for e in fs:: read_dir( from) .unwrap ( ) {
5233
let e = e.unwrap ( ) ;
5234
let target = to.join ( e.file_name ( ) ) ;
5235
if e.file_type ( ) .unwrap ( ) .is_dir ( ) {
5236
copy_tree ( & e.path ( ) , & target) ;
5237
} else {
5238
fs:: copy( e.path ( ) , & target) .unwrap ( ) ;
5239
}
5240
}
5241
}
5242
5243
5244
5245
5246
# [ test ]
5247
fn crash_at_every_step_of_commit_and_push_leaves_sound_repositories ( ) {
64818938 Andy McCall 1 month ago ↶
5248
5249
5250
if std:: env:: var_os( " OXY_STORE_BACKEND" ) .is_some ( ) {
5251
return ;
5252
}
4dcb8c09 Andy McCall 1 month ago ↶
5253
use crate :: durable:: failpoint;
5254
hermetic ( ) ;
5255
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5256
let pristine = dir.path ( ) .join ( " pristine" ) ;
5257
let origin_path = pristine.join ( " origin" ) ;
5258
Repo:: init( & origin_path) .unwrap ( ) ;
5259
let mut a = Repo:: clone( & origin_path.to_string_lossy ( ) , & pristine.join ( " a" ) )
5260
.unwrap ( )
5261
.with_home ( dir.path ( ) .join ( " home" ) ) ;
5262
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
5263
.unwrap ( ) ;
5264
write ( & pristine.join ( " a" ) , " f.txt" , b " one\n " ) ;
5265
a.commit ( " first" , & [ ] ) .unwrap ( ) ;
5266
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
5267
drop ( a) ;
5268
5269
type Op = fn ( & Repo) -> Result< ( ) > ;
5270
let edit_and_commit: Op = | r | {
5271
write ( r.root ( ) , " f.txt" , b " one\n two\n " ) ;
5272
write ( r.root ( ) , " big.bin" , & vec! [ 7 u8 ; 300_000 ] ) ;
5273
r.commit ( " second" , & [ ] ) .map ( | _| ( ) )
5274
} ;
5275
let push: Op = | r | {
5276
let res = r.push ( " origin" , false ) ? ;
5277
if res.iter ( ) .all ( | x | x.ok ) {
5278
Ok ( ( ) )
5279
} else {
5280
Err ( RepoError:: Msg( format! ( " {res:?} " ) ) )
5281
}
5282
} ;
5283
5284
for ( name, prepare, op) in [
5285
( " commit" , None , edit_and_commit) ,
5286
( " push" , Some ( edit_and_commit) , push) ,
5287
] {
5288
let mut steps = 0 ;
5289
for n in 1 .. {
5290
let run = dir.path ( ) .join ( format! ( " {name} -{n} " ) ) ;
5291
copy_tree ( & pristine, & run) ;
5292
5293
let open = | p : & Path| {
5294
let mut r = Repo:: open( p) .unwrap ( ) .with_home ( dir.path ( ) .join ( " home" ) ) ;
5295
r.config
5296
.set ( " remote.origin.url" , & run.join ( " origin" ) .to_string_lossy ( ) )
5297
.unwrap ( ) ;
5298
r.save_config ( ) .unwrap ( ) ;
5299
r
5300
} ;
5301
let a = open ( & run.join ( " a" ) ) ;
5302
if let Some ( prep) = prepare {
5303
prep ( & a) .unwrap ( ) ;
5304
}
5305
failpoint:: crash_after( n) ;
5306
let outcome = op ( & a) ;
5307
let crashed = failpoint:: disarm( ) ;
5308
drop ( a) ;
5309
if ! crashed {
5310
outcome.unwrap_or_else ( | e | panic! ( " {name} failed without a crash: {e}" ) ) ;
5311
steps = n - 1 ;
5312
break ;
5313
}
5314
assert! (
5315
outcome.is_err ( ) ,
5316
" {name}: a crash at step {n} was swallowed"
5317
) ;
5318
for repo in [ " a" , " origin" ] {
5319
let r = Repo:: open( & run.join ( repo) ) .unwrap_or_else ( | e | {
5320
panic! ( " {name} crash at step {n}: {repo} no longer opens: {e}" )
5321
} ) ;
5322
let rep = r.fsck ( ) .unwrap ( ) ;
5323
assert_eq! (
5324
rep.errors ( ) ,
5325
0 ,
5326
" {name} crash at step {n}: {repo} fails fsck: {:?}" ,
5327
rep.problems
5328
) ;
5329
}
5330
5331
let a = open ( & run.join ( " a" ) ) ;
5332
5333
let landed = a
5334
.bookmark_heads ( )
5335
.unwrap ( )
5336
.iter ( )
5337
.any ( | h | a.store ( ) .get_change ( h) .unwrap ( ) .message = = " second" ) ;
5338
let again = match ( name, landed) {
5339
( " commit" , true ) => Ok ( ( ) ) ,
5340
( " commit" , false ) => a.commit ( " second" , & [ ] ) .map ( | _| ( ) ) ,
5341
_ => op ( & a) ,
5342
} ;
5343
again.unwrap_or_else ( | e | panic! ( " {name} crash at step {n}: retry failed: {e}" ) ) ;
5344
assert_eq! ( a.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
5345
fs:: remove_dir_all( & run) .unwrap ( ) ;
5346
}
5347
assert! ( steps > = 3 , " {name} has only {steps} durable steps?" ) ;
5348
}
5349
}
19555025 Andy McCall 1 month ago ↶
5350
5351
5352
5353
5354
# [ test ]
5355
fn concurrent_commits_in_two_workspaces_lose_nothing ( ) {
5356
hermetic ( ) ;
5357
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5358
let root = dir.path ( ) ;
5359
let origin = root.join ( " origin" ) ;
5360
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
5361
let main = root.join ( " main" ) ;
5362
let mut repo = Repo:: init( & main) .unwrap ( ) ;
5363
repo.config
5364
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
5365
.unwrap ( ) ;
5366
repo.save_config ( ) .unwrap ( ) ;
5367
write ( & main, " seed.txt" , b " seed\n " ) ;
5368
repo.commit ( " seed" , & [ ] ) .unwrap ( ) ;
5369
let second = root.join ( " second" ) ;
5370
repo.workspace_add ( & second, None ) .unwrap ( ) ;
5371
drop ( repo) ;
5372
5373
const EACH : usize = 15 ;
5374
std:: thread:: scope( | s | {
5375
for ( tag, dir, push) in [ ( " a" , main.clone ( ) , true ) , ( " b" , second.clone ( ) , false ) ] {
5376
s.spawn ( move | | {
5377
let r = Repo:: open( & dir) .unwrap ( ) ;
5378
for i in 0 .. EACH {
5379
write (
5380
& dir,
5381
& format! ( " {tag} -{i} .txt" ) ,
5382
format! ( " {tag} {i} \n " ) .as_bytes ( ) ,
5383
) ;
5384
r.commit ( & format! ( " {tag} {i} " ) , & [ ] ) .unwrap ( ) ;
5385
if push & & i % 5 = = 4 {
5386
5387
let _ = r.push ( " origin" , false ) ;
5388
}
5389
}
5390
} ) ;
5391
}
5392
} ) ;
5393
5394
let repo = Repo:: open( & main) .unwrap ( ) ;
5395
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5396
let state = repo.load_state ( & heads) .unwrap ( ) ;
5397
let messages: BTreeSet< String> = state.changes ( ) .map ( | ( _, m ) | m.message.clone ( ) ) .collect ( ) ;
5398
for tag in [ " a" , " b" ] {
5399
for i in 0 .. EACH {
5400
assert! (
5401
messages.contains ( & format! ( " {tag} {i} " ) ) ,
5402
" lost commit {tag} {i}; have {messages:?}"
5403
) ;
5404
}
5405
}
5406
let rep = repo.fsck ( ) .unwrap ( ) ;
5407
assert_eq! ( rep.errors ( ) , 0 , " {:?}" , rep.problems) ;
5408
assert! ( repo.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
5409
let hosted = Repo:: open( & origin) .unwrap ( ) ;
5410
let hosted_heads = hosted.bookmark_heads ( ) .unwrap ( ) ;
5411
assert_eq! (
5412
hosted.load_state ( & hosted_heads) .unwrap ( ) .len ( ) ,
5413
state.len ( ) ,
5414
" the remote has every commit"
5415
) ;
5416
}
5417
5418
5419
5420
5421
# [ test ]
5422
fn operations_from_the_same_parent_are_merged ( ) {
5423
hermetic ( ) ;
5424
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5425
let root = dir.path ( ) ;
5426
let repo = Repo:: init( root) .unwrap ( ) ;
5427
write ( root, " a.txt" , b " a\n " ) ;
5428
let a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
5429
write ( root, " b.txt" , b " b\n " ) ;
5430
let b = repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
5431
let ( x, op) = repo.head_op ( ) .unwrap ( ) ;
5432
let state = | heads : Vec< ChangeHash> | repo.store ( ) .put_state ( & StateObject:: new( heads) ) .unwrap ( ) ;
5433
5434
5435
let mut one = op.view.clone ( ) ;
5436
one.bookmarks.insert ( " main" .into ( ) , state ( vec! [ a] ) ) ;
5437
one.bookmarks.insert ( " topic" .into ( ) , state ( vec! [ a] ) ) ;
5438
let mut two = op.view.clone ( ) ;
5439
two.bookmarks.insert ( " main" .into ( ) , state ( vec! [ b] ) ) ;
5440
repo.record_op ( & [ x] , " one" , one) .unwrap ( ) ;
5441
let merged = repo.record_op ( & [ x] , " two" , two) .unwrap ( ) ;
5442
5443
let ( head, op) = repo.head_op ( ) .unwrap ( ) ;
5444
assert_eq! ( head, merged) ;
5445
assert_eq! ( op.parents.len ( ) , 2 , " a merge of the two operations" ) ;
5446
assert! ( op.command.contains ( " merged with a concurrent operation" ) ) ;
5447
let mut main = repo
5448
.store ( )
5449
.get_state ( & op.view.bookmarks[ " main" ] )
5450
.unwrap ( )
5451
.heads;
5452
main.sort ( ) ;
5453
let mut want = vec! [ a, b] ;
5454
want.sort ( ) ;
5455
assert_eq! ( main, want, " both sides' heads are kept" ) ;
5456
assert! (
5457
op.view.bookmarks.contains_key ( " topic" ) ,
5458
" a one-sided change survives"
5459
) ;
5460
assert_eq! ( repo.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
5461
}
3e5772cc Andy McCall 1 month ago ↶
5462
5463
# [ test ]
5464
fn failed_requests_come_with_a_next_step ( ) {
5465
use crate :: protocol:: hint_for;
5466
assert! ( hint_for ( " https://h/x/oxy/v1/push: HTTP 401: bad token" )
5467
.unwrap ( )
5468
.contains ( " token" ) ) ;
5469
assert! ( hint_for ( " …: HTTP 403: forbidden" )
5470
.unwrap ( )
5471
.contains ( " repo:write" ) ) ;
5472
assert! ( hint_for ( " …: HTTP 404: no such repository" )
5473
.unwrap ( )
5474
.contains ( " owner and repository name" ) ) ;
5475
assert! ( hint_for ( " invalid peer certificate: UnknownIssuer" )
5476
.unwrap ( )
5477
.contains ( " oxyhub-ca.pem" ) ) ;
5478
assert! ( hint_for ( " Connect error: Connection refused (os error 111)" )
5479
.unwrap ( )
5480
.contains ( " running" ) ) ;
5481
assert! ( hint_for ( " Dns Failed: failed to lookup address information" )
5482
.unwrap ( )
5483
.contains ( " resolve" ) ) ;
5484
assert_eq! ( hint_for ( " something else entirely" ) , None ) ;
5485
}
5486
5487
# [ test ]
5488
fn doctor_names_problems_and_what_to_do ( ) {
5489
hermetic ( ) ;
5490
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5491
let root = dir.path ( ) ;
5492
let origin = root.join ( " origin" ) ;
5493
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
5494
let mut repo = Repo:: init( & root.join ( " r" ) )
5495
.unwrap ( )
5496
.with_home ( root.join ( " home" ) ) ;
5497
let find = | r : & DoctorReport, area : & str , h : Health| {
5498
r.checks
5499
.iter ( )
5500
.find ( | c | c.area = = area & & c.health = = h )
5501
.cloned ( )
5502
} ;
5503
5504
let r = repo.doctor ( true ) ;
5505
assert! ( find ( & r, " identity" , Health:: Warn)
5506
.unwrap ( )
5507
.hint
5508
.unwrap ( )
5509
.contains ( " oxy identity create" ) ) ;
5510
assert! ( find ( & r, " repository" , Health:: Ok) .is_some ( ) ) ;
ad2496f7 Andy McCall 17 days ago ↶
5511
5512
assert! ( find ( & r, " trust" , Health:: Warn)
5513
.unwrap ( )
5514
.hint
5515
.unwrap ( )
5516
.contains ( " oxy policy init" ) ) ;
3e5772cc Andy McCall 1 month ago ↶
5517
5518
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
5519
.unwrap ( ) ;
ad2496f7 Andy McCall 17 days ago ↶
5520
repo.policy_init ( ) .unwrap ( ) ;
5521
let r = repo.doctor ( false ) ;
5522
let trust = find ( & r, " trust" , Health:: Ok) .expect ( " a fresh policy is fine" ) ;
5523
assert! (
5524
trust.message.contains ( " serial 1" ) & & trust.message.contains ( " 1 root" ) ,
5525
" {trust:?}"
5526
) ;
3e5772cc Andy McCall 1 month ago ↶
5527
repo.config.set ( " author.name" , " Ada" ) .unwrap ( ) ;
5528
repo.config.set ( " author.email" , " [email protected] " ) .unwrap ( ) ;
5529
repo.config
5530
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
5531
.unwrap ( ) ;
5532
repo.config
5533
.set ( " remote.down.url" , " http://127.0.0.1:9/a/b" )
5534
.unwrap ( ) ;
5535
repo.config.set ( " remote.down.token" , " t" ) .unwrap ( ) ;
5536
repo.config
5537
.set ( " remote.badca.url" , " https://example.invalid/a/b" )
5538
.unwrap ( ) ;
5539
repo.config
5540
.set (
5541
" remote.badca.ca" ,
5542
& root.join ( " missing.pem" ) .to_string_lossy ( ) ,
5543
)
5544
.unwrap ( ) ;
5545
repo.save_config ( ) .unwrap ( ) ;
5546
5547
let r = repo.doctor ( true ) ;
5548
assert! ( find ( & r, " identity" , Health:: Ok) .is_some ( ) , " {r:?}" ) ;
5549
assert! ( find ( & r, " author" , Health:: Ok) .is_some ( ) , " {r:?}" ) ;
5550
assert! ( find ( & r, " remote origin" , Health:: Ok)
5551
.unwrap ( )
5552
.message
5553
.contains ( " first push binds it" ) ) ;
5554
let down = find ( & r, " remote down" , Health:: Fail) .unwrap ( ) ;
5555
assert! (
5556
down.hint.as_deref ( ) .unwrap_or ( " " ) .contains ( " running" ) ,
5557
" {down:?}"
5558
) ;
5559
let badca = find ( & r, " remote badca" , Health:: Fail) .unwrap ( ) ;
5560
assert! (
5561
badca.hint.unwrap ( ) .contains ( " remote.badca.ca" ) ,
5562
" the remote's own name is in the hint"
5563
) ;
5564
assert_eq! ( r.worst ( ) , Health:: Fail) ;
5565
5566
5567
let r = repo.doctor ( false ) ;
5568
assert! ( find ( & r, " remote down" , Health:: Fail) .is_none ( ) ) ;
5569
assert! ( find ( & r, " remote down" , Health:: Ok)
5570
.unwrap ( )
5571
.message
5572
.contains ( " not contacted" ) ) ;
5573
}
62794ecc Andy McCall 1 month ago ↶
5574
5575
5576
5577
5578
# [ test ]
5579
fn deleting_a_line_after_an_insertion_below_it_is_not_a_conflict ( ) {
5580
hermetic ( ) ;
5581
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5582
let root = dir.path ( ) ;
5583
let repo = Repo:: init( root) .unwrap ( ) ;
5584
write ( root, " f" , b " a\n b\n c\n " ) ;
5585
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
5586
write ( root, " f" , b " a\n b\n x\n c\n " ) ;
5587
let insert = repo.commit ( " insert x after b" , & [ ] ) .unwrap ( ) ;
5588
write ( root, " f" , b " a\n x\n c\n " ) ;
5589
let delete = repo.commit ( " delete b" , & [ ] ) .unwrap ( ) ;
5590
assert! (
5591
repo.store ( )
5592
.get_change ( & delete)
5593
.unwrap ( )
5594
.deps
5595
.contains ( & insert) ,
5596
" the deletion depends on the insertion below the deleted line"
5597
) ;
5598
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5599
assert_eq! ( heads, vec! [ delete] ) ;
5600
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
5601
assert! ( ! snap.is_conflicted ( ) ) ;
5602
assert! (
5603
matches! ( & snap.files[ " f" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n x\n c\n " )
5604
) ;
5605
}
5606
5607
5608
5609
# [ test ]
5610
fn a_binary_file_reverted_to_an_earlier_blob_keeps_it ( ) {
5611
hermetic ( ) ;
5612
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5613
let root = dir.path ( ) ;
5614
let repo = Repo:: init( root) .unwrap ( ) ;
5615
write ( root, " bin" , b " \0 A" ) ;
5616
let v1 = repo.commit ( " v1" , & [ ] ) .unwrap ( ) ;
5617
write ( root, " bin" , b " \0 B" ) ;
5618
let v2 = repo.commit ( " v2" , & [ ] ) .unwrap ( ) ;
5619
assert! ( repo.store ( ) .get_change ( & v2) .unwrap ( ) .deps.contains ( & v1) ) ;
5620
write ( root, " bin" , b " \0 A" ) ;
5621
let v3 = repo.commit ( " back to A" , & [ ] ) .unwrap ( ) ;
5622
assert! ( repo.store ( ) .get_change ( & v3) .unwrap ( ) .deps.contains ( & v2) ) ;
5623
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ v3] ) ;
5624
let state = repo.load_state ( & [ v3] ) .unwrap ( ) ;
5625
let snap = state.materialize ( ) ;
5626
let FileContent:: Opaque { blobs } = & snap.files[ " bin" ] .content else {
5627
panic! ( " opaque" )
5628
} ;
5629
assert_eq! ( blobs.len ( ) , 1 , " one live blob, not none and not two" ) ;
5630
assert_eq! ( repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) , b " \0 A" ) ;
5631
}
5632
5633
5634
5635
5636
# [ test ]
5637
fn git_import_resolves_a_binary_merged_in_git_and_verifies ( ) {
5638
hermetic ( ) ;
5639
if std:: process:: Command:: new( " git" )
5640
.arg ( " --version" )
5641
.output ( )
5642
.is_err ( )
5643
{
5644
eprintln! ( " git not installed; skipping" ) ;
5645
return ;
5646
}
5647
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5648
let root = dir.path ( ) ;
5649
let g = root.join ( " src.git" ) ;
5650
fs:: create_dir_all( & g) .unwrap ( ) ;
5651
let git = | args : & [& str ]| -> String {
5652
let out = std:: process:: Command:: new( " git" )
5653
.arg ( " -C" )
5654
.arg ( & g)
5655
.args ( [
5656
" -c" ,
5657
" user.name=Grace" ,
5658
" -c" ,
5659
" [email protected] " ,
5660
" -c" ,
5661
" commit.gpgsign=false" ,
5662
] )
5663
.args ( args)
5664
.output ( )
5665
.unwrap ( ) ;
5666
assert! (
5667
out.status.success ( ) ,
5668
" git {args:?}: {}" ,
5669
String :: from_utf8_lossy( & out.stderr)
5670
) ;
5671
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
5672
} ;
5673
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
5674
write ( & g, " logo.bin" , b " \0 base" ) ;
5675
write ( & g, " notes.txt" , b " a\n b\n c\n " ) ;
5676
git ( & [ " add" , " ." ] ) ;
5677
git ( & [ " commit" , " -qm" , " base" ] ) ;
5678
git ( & [ " checkout" , " -qb" , " side" ] ) ;
5679
write ( & g, " logo.bin" , b " \0 side" ) ;
5680
write ( & g, " notes.txt" , b " a\n b\n x\n c\n " ) ;
5681
git ( & [ " commit" , " -qam" , " side" ] ) ;
5682
git ( & [ " checkout" , " -q" , " main" ] ) ;
5683
write ( & g, " logo.bin" , b " \0 main" ) ;
5684
git ( & [ " commit" , " -qam" , " main" ] ) ;
5685
5686
let _ = std:: process:: Command:: new( " git" )
5687
.arg ( " -C" )
5688
.arg ( & g)
5689
.args ( [
5690
" -c" ,
5691
" user.name=G" ,
5692
" -c" ,
5693
" user.email=g@e" ,
5694
" merge" ,
5695
" -q" ,
5696
" side" ,
5697
] )
5698
.output ( ) ;
5699
write ( & g, " logo.bin" , b " \0 main" ) ;
5700
git ( & [ " add" , " ." ] ) ;
5701
git ( & [ " commit" , " -qm" , " merge side" ] ) ;
5702
5703
write ( & g, " logo.bin" , b " \0 base" ) ;
5704
git ( & [ " commit" , " -qam" , " back to base" ] ) ;
5705
5706
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
5707
let rep = repo.import_git ( & g, " main" ) .unwrap ( ) ;
5708
assert_eq! ( rep.merges, 1 ) ;
5709
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
5710
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
5711
assert_eq! ( v.commits, 5 ) ;
5712
let snap = repo
5713
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
5714
.unwrap ( )
5715
.materialize ( ) ;
5716
assert! ( ! snap.is_conflicted ( ) ) ;
5717
let FileContent:: Opaque { blobs } = & snap.files[ " logo.bin" ] .content else {
5718
panic! ( " opaque" )
5719
} ;
5720
assert_eq! ( repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) , b " \0 base" ) ;
5721
}
c863c7a7 Andy McCall 1 month ago ↶
5722
5723
5724
5725
5726
# [ cfg ( unix ) ]
5727
# [ test ]
5728
fn windows_file_systems_keep_exec_bits_and_symlinks ( ) {
5729
use crate :: portable:: Platform;
5730
use std:: os:: unix:: fs:: PermissionsExt;
5731
hermetic ( ) ;
5732
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5733
let root = dir.path ( ) ;
5734
let repo = Repo:: init( root) .unwrap ( ) ;
5735
write ( root, " run.sh" , b " #!/bin/sh\n " ) ;
5736
fs:: set_permissions( root.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
5737
std:: os:: unix:: fs:: symlink( " run.sh" , root.join ( " link" ) ) .unwrap ( ) ;
5738
repo.commit ( " script and link" , & [ ] ) .unwrap ( ) ;
5739
drop ( repo) ;
5740
5741
5742
fs:: set_permissions( root.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
5743
fs:: remove_file( root.join ( " link" ) ) .unwrap ( ) ;
5744
write ( root, " link" , b " run.sh" ) ;
5745
let win = Repo:: open( root) .unwrap ( ) .with_platform ( Platform:: windows( ) ) ;
5746
let s = win.snapshot ( " oxy status" ) .unwrap ( ) ;
5747
assert! (
5748
s.statuses.is_empty ( ) ,
5749
" nothing changed on Windows: {:?}" ,
5750
statuses ( & s)
5751
) ;
5752
5753
write ( root, " run.sh" , b " #!/bin/sh\n echo hi\n " ) ;
5754
write ( root, " link" , b " other" ) ;
5755
let h = win.commit ( " edit on Windows" , & [ ] ) .unwrap ( ) ;
5756
let snap = win.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
5757
assert! ( snap.files[ " run.sh" ] .mode.exec, " the exec bit survives" ) ;
5758
assert_eq! ( snap.files[ " link" ] .kind, NodeKind:: Symlink) ;
5759
5760
let linux = Repo:: open( root) .unwrap ( ) ;
5761
assert! ( ! linux.snapshot ( " oxy status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
5762
}
5763
5764
5765
5766
# [ test ]
5767
fn paths_other_platforms_cannot_hold_are_reported ( ) {
5768
use crate :: portable:: Platform;
5769
hermetic ( ) ;
5770
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5771
let root = dir.path ( ) ;
5772
let repo = Repo:: init( root) .unwrap ( ) ;
5773
write ( root, " README.md" , b " a\n " ) ;
5774
write ( root, " readme.md" , b " b\n " ) ;
5775
write ( root, " src/aux.c" , b " int x;\n " ) ;
5776
let h = repo.commit ( " unportable" , & [ ] ) .unwrap ( ) ;
5777
let r = repo.doctor ( false ) ;
5778
let port = r.checks.iter ( ) .find ( | c | c.area = = " portability" ) .unwrap ( ) ;
5779
assert_eq! ( port.health, Health:: Warn) ;
5780
assert! (
5781
port.message.contains ( " differ only in case" ) & & port.message.contains ( " aux.c" ) ,
5782
" {}" ,
5783
port.message
5784
) ;
5785
let mac = Repo:: open( root) .unwrap ( ) .with_platform ( Platform:: macos( ) ) ;
5786
let rep = mac.checkout ( & [ h] , true , " oxy checkout" ) .unwrap ( ) ;
5787
assert! (
5788
rep.notes
5789
.iter ( )
5790
.any ( | n | n.contains ( " README.md and readme.md" ) ) ,
5791
" {:?}" ,
5792
rep.notes
5793
) ;
5794
assert! (
5795
! rep.notes.iter ( ) .any ( | n | n.contains ( " aux.c" ) ) ,
5796
" macOS allows aux.c"
5797
) ;
5798
}
bc489b6c Andy McCall 1 month ago ↶
5799
5800
# [ test ]
5801
fn history_and_blame_of_a_loaded_state_match_the_full_walk ( ) {
5802
hermetic ( ) ;
5803
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5804
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
5805
write ( dir.path ( ) , " a.txt" , b " one\n " ) ;
5806
write ( dir.path ( ) , " other.txt" , b " x\n " ) ;
5807
write ( dir.path ( ) , " bin.dat" , & [ 0 u8 , 1 , 2 , 0 , 255 ] ) ;
5808
repo.snapshot ( " t" ) .unwrap ( ) ;
5809
let c1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
5810
write ( dir.path ( ) , " other.txt" , b " y\n " ) ;
5811
repo.snapshot ( " t" ) .unwrap ( ) ;
5812
repo.commit ( " other only" , & [ ] ) .unwrap ( ) ;
5813
write ( dir.path ( ) , " a.txt" , b " one\n two\n " ) ;
5814
repo.snapshot ( " t" ) .unwrap ( ) ;
5815
let c3 = repo.commit ( " second line" , & [ ] ) .unwrap ( ) ;
5816
write ( dir.path ( ) , " other.txt" , b " z\n " ) ;
5817
repo.snapshot ( " t" ) .unwrap ( ) ;
5818
repo.commit ( " other again" , & [ ] ) .unwrap ( ) ;
5819
5820
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5821
let state = repo.load_state ( & heads) .unwrap ( ) ;
5822
let full: Vec< ChangeHash> = repo
5823
.log_for_path_in ( " a.txt" , & heads)
5824
.unwrap ( )
5825
.iter ( )
5826
.map ( | e | e.hash )
5827
.collect ( ) ;
5828
assert_eq! (
5829
full,
5830
vec! [ c3, c1] ,
5831
" newest first, only changes touching the path"
5832
) ;
5833
let newest: Vec< ChangeHash> = repo
5834
.log_for_path_of ( & state, " a.txt" , Some ( 1 ) )
5835
.unwrap ( )
5836
.iter ( )
5837
.map ( | e | e.hash )
5838
.collect ( ) ;
5839
assert_eq! ( newest, vec! [ c3] , " stops at the first" ) ;
5840
assert_eq! (
5841
repo.log_for_path_of ( & state, " a.txt" , None ) .unwrap ( ) .len ( ) ,
5842
2
5843
) ;
5844
assert! ( repo
5845
.log_for_path_of ( & state, " missing.txt" , Some ( 1 ) )
5846
.is_err ( ) ) ;
5847
assert_eq! (
5848
repo.log_state ( & heads) .unwrap ( ) .len ( ) ,
5849
repo.log_of ( & state) .len ( )
5850
) ;
5851
5852
let tree = state.tree ( ) .view ( ) ;
5853
let via_state = repo.blame_of ( & state, & tree, " a.txt" ) .unwrap ( ) ;
5854
let via_heads = repo.blame_in ( " a.txt" , & heads) .unwrap ( ) ;
5855
assert_eq! (
5856
via_state
5857
.iter ( )
5858
.map ( | l | ( l.hash, l.text.clone ( ) ) )
5859
.collect:: < Vec< _ > > ( ) ,
5860
via_heads
5861
.iter ( )
5862
.map ( | l | ( l.hash, l.text.clone ( ) ) )
5863
.collect:: < Vec< _ > > ( )
5864
) ;
5865
assert_eq! (
5866
via_state.iter ( ) .map ( | l | l.hash ) .collect:: < Vec< _ > > ( ) ,
5867
vec! [ c1, c3]
5868
) ;
5869
assert! ( repo.blame_of ( & state, & tree, " missing.txt" ) .is_err ( ) ) ;
5870
}
696bbf29 Andy McCall 1 month ago ↶
5871
5872
5873
5874
5875
# [ cfg ( unix ) ]
5876
# [ test ]
5877
fn stat_cache_takes_unchanged_text_from_the_base_and_misses_no_edit ( ) {
5878
use std:: os:: unix:: fs:: PermissionsExt;
5879
hermetic ( ) ;
5880
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5881
let root = dir.path ( ) ;
5882
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5883
write ( root, " a.txt" , b " one\n two\n " ) ;
5884
write ( root, " b.txt" , b " bee\n " ) ;
5885
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
5886
let age = | p : & str | {
5887
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
5888
fs:: File:: options( )
5889
.write ( true )
5890
.open ( root.join ( p) )
5891
.unwrap ( )
5892
.set_modified ( old)
5893
.unwrap ( ) ;
5894
} ;
5895
age ( " a.txt" ) ;
5896
age ( " b.txt" ) ;
5897
assert! (
5898
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
5899
" records the aged stat"
5900
) ;
5901
5902
5903
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
5904
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
5905
assert! (
5906
! snap.changed & & snap.statuses.is_empty ( ) ,
5907
" {:?}" ,
5908
snap.statuses
5909
) ;
5910
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
5911
5912
5913
5914
write ( root, " b.txt" , b " bee edited\n " ) ;
5915
age ( " b.txt" ) ;
5916
let first = repo.snapshot ( " status" ) .unwrap ( ) ;
5917
assert_eq! (
5918
statuses ( & first) ,
5919
vec! [ ( " b.txt" .to_string ( ) , ChangeStatus:: Modified) ]
5920
) ;
5921
let again = repo.snapshot ( " status" ) .unwrap ( ) ;
5922
assert_eq! (
5923
statuses ( & again) ,
5924
vec! [ ( " b.txt" .to_string ( ) , ChangeStatus:: Modified) ] ,
5925
" still seen once stat-clean"
5926
) ;
5927
assert! ( ! again.changed, " and the working copy hashes the same" ) ;
5928
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
5929
assert! (
5930
repo.snapshot ( " status" ) .is_err ( ) ,
5931
" an edited file must be read, never taken from the base"
5932
) ;
5933
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
5934
5935
5936
write ( root, " a.txt" , b " one\n TWO\n " ) ;
5937
assert_eq! (
5938
statuses ( & repo.snapshot ( " status" ) .unwrap ( ) ) ,
5939
vec! [
5940
( " a.txt" .to_string ( ) , ChangeStatus:: Modified) ,
5941
( " b.txt" .to_string ( ) , ChangeStatus:: Modified)
5942
]
5943
) ;
5944
5945
repo.commit ( " edits" , & [ ] ) .unwrap ( ) ;
5946
age ( " a.txt" ) ;
5947
age ( " b.txt" ) ;
5948
assert! ( repo.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
5949
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
5950
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
5951
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
5952
assert! ( ! snap.changed & & snap.statuses.is_empty ( ) ) ;
5953
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
5954
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
5955
}
afaba5f1 Andy McCall 1 month ago ↶
5956
5957
5958
5959
5960
# [ test ]
5961
fn checkpoints_keep_large_graphs_as_blobs_of_their_own ( ) {
5962
hermetic ( ) ;
5963
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5964
let root = dir.path ( ) ;
5965
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5966
let long: String = ( 0 .. 300 )
5967
.map ( | i | format! ( " line {i} of a long file\n " ) )
5968
.collect ( ) ;
5969
write ( root, " long.txt" , long.as_bytes ( ) ) ;
5970
write ( root, " short.txt" , b " short\n " ) ;
5971
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
5972
let ( r1, _ , _ ) = repo.checkpoint ( ) .unwrap ( ) ;
5973
let cp1 = crate :: objects:: Checkpoint:: decode(
5974
& repo.store ( ) .get_typed ( & r1, ObjectType:: Checkpoint) .unwrap ( ) ,
5975
)
5976
.unwrap ( ) ;
5977
let graphs1 = repo.checkpoint_graph_blobs ( & cp1) .unwrap ( ) ;
5978
assert_eq! ( graphs1.len ( ) , 1 , " only the long file's graph is a blob" ) ;
5979
5980
5981
repo.gc ( true ) .unwrap ( ) ;
5982
assert! ( repo.store ( ) .has ( & graphs1[ 0 ] ) ) ;
5983
let report = repo.fsck ( ) .unwrap ( ) ;
5984
assert_eq! ( report.errors ( ) , 0 , " {:?}" , report.problems) ;
5985
5986
5987
write ( root, " short.txt" , b " short, edited\n " ) ;
5988
repo.commit ( " edit the short file" , & [ ] ) .unwrap ( ) ;
5989
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5990
let fast = repo.load_state ( & heads) .unwrap ( ) ;
5991
let full = repo.load_state_full ( & heads) .unwrap ( ) ;
5992
assert_eq! ( fast.checkpoint_payload ( ) , full.checkpoint_payload ( ) ) ;
5993
5994
5995
let ( r2, _ , _ ) = repo.checkpoint ( ) .unwrap ( ) ;
5996
let cp2 = crate :: objects:: Checkpoint:: decode(
5997
& repo.store ( ) .get_typed ( & r2, ObjectType:: Checkpoint) .unwrap ( ) ,
5998
)
5999
.unwrap ( ) ;
6000
assert_eq! ( repo.checkpoint_graph_blobs ( & cp2) .unwrap ( ) , graphs1) ;
6001
6002
6003
let bundle = repo.checkpoint_bundle_for ( & heads) .unwrap ( ) ;
6004
assert! ( bundle.objects.iter ( ) .any ( | ( t , body ) | * t = = ObjectType:: Blob
6005
& & crate :: hash:: hash_object( ObjectType:: Blob, body) = = graphs1[ 0 ] ) ) ;
6006
6007
6008
let state = repo.load_state_full ( & heads) .unwrap ( ) ;
6009
let old = repo.store_blob ( & state.checkpoint_payload ( ) ) .unwrap ( ) ;
6010
let cp_old = crate :: objects:: Checkpoint {
6011
heads: cp2.heads.clone ( ) ,
6012
count: cp2.count,
6013
payload: old,
6014
} ;
6015
let r_old = repo
6016
.store ( )
6017
.put ( ObjectType:: Checkpoint, & cp_old.encode ( ) )
6018
.unwrap ( ) ;
6019
repo.install_checkpoint ( " main" , & r_old) .unwrap ( ) ;
6020
assert_eq! (
6021
repo.load_state ( & heads) .unwrap ( ) .checkpoint_payload ( ) ,
6022
full.checkpoint_payload ( )
6023
) ;
6024
assert_eq! ( repo.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
6025
6026
6027
6028
let dir2 = tempfile:: tempdir( ) .unwrap ( ) ;
6029
let root2 = dir2.path ( ) ;
6030
let repo2 = Repo:: init( root2) .unwrap ( ) .with_home ( root2.join ( " home" ) ) ;
6031
write ( root2, " long.txt" , long.as_bytes ( ) ) ;
6032
repo2.commit ( " base" , & [ ] ) .unwrap ( ) ;
6033
let ( r, _ , _ ) = repo2.checkpoint ( ) .unwrap ( ) ;
6034
let cp = crate :: objects:: Checkpoint:: decode(
6035
& repo2.store ( ) .get_typed ( & r, ObjectType:: Checkpoint) .unwrap ( ) ,
6036
)
6037
.unwrap ( ) ;
6038
let graph = repo2.checkpoint_graph_blobs ( & cp) .unwrap ( ) [ 0 ] ;
64818938 Andy McCall 1 month ago ↶
6039
repo2.store ( ) .remove_loose_for_test ( & graph) .unwrap ( ) ;
afaba5f1 Andy McCall 1 month ago ↶
6040
assert! ( repo2.load_state ( & repo2.bookmark_heads ( ) .unwrap ( ) ) .is_err ( ) ) ;
6041
assert! ( repo2.fsck ( ) .unwrap ( ) .errors ( ) > 0 , " fsck reports it" ) ;
6042
}
e3d071ce Andy McCall 1 month ago ↶
6043
6044
6045
6046
6047
# [ test ]
6048
fn identity_is_found_in_the_key_directory_for_a_new_repository ( ) {
6049
hermetic ( ) ;
6050
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6051
let home = dir.path ( ) .join ( " home" ) ;
6052
let mut first = Repo:: init( & dir.path ( ) .join ( " a" ) )
6053
.unwrap ( )
6054
.with_home ( home.clone ( ) ) ;
6055
let ( key, _ ) = first
6056
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
6057
.unwrap ( ) ;
6058
let fresh = Repo:: init( & dir.path ( ) .join ( " b" ) )
6059
.unwrap ( )
6060
.with_home ( home.clone ( ) ) ;
6061
assert! ( ! fresh.store ( ) .has ( & key) , " nothing stored yet" ) ;
6062
let id = fresh.identity ( & key) .unwrap ( ) ;
6063
assert_eq! ( ( id.name.as_str ( ) , id.hash ( ) ) , ( " Ada" , key) ) ;
6064
6065
let keys = fresh.keys_dir.clone ( ) ;
6066
let ( other, _ ) = first
6067
.create_identity ( " Bob" , " [email protected] " , IdentityScope:: User)
6068
.unwrap ( ) ;
6069
fs:: copy(
6070
keys.join ( format! ( " {} .identity" , other.to_hex ( ) ) ) ,
6071
keys.join ( format! ( " {} .identity" , key.to_hex ( ) ) ) ,
6072
)
6073
.unwrap ( ) ;
6074
assert! ( fresh
6075
.identity ( & key)
6076
.unwrap_err ( )
6077
.to_string ( )
6078
.contains ( " does not hold identity" ) ) ;
6079
}
cc7a0056 Andy McCall 1 month ago ↶
6080
6081
6082
6083
6084
6085
# [ cfg ( unix ) ]
6086
# [ test ]
6087
fn status_snapshots_skip_files_unchanged_since_the_manifest ( ) {
6088
use std:: os:: unix:: fs:: PermissionsExt;
6089
hermetic ( ) ;
6090
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6091
let root = dir.path ( ) ;
6092
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6093
for ( p, c) in [ ( " a.txt" , " one\n " ) , ( " b.txt" , " bee\n " ) , ( " d/c.txt" , " sea\n " ) ] {
6094
write ( root, p, c.as_bytes ( ) ) ;
6095
}
6096
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6097
let age = | p : & str | {
6098
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6099
fs:: File:: options( )
6100
.write ( true )
6101
.open ( root.join ( p) )
6102
.unwrap ( )
6103
.set_modified ( old)
6104
.unwrap ( ) ;
6105
} ;
6106
let lock = | p : & str , mode : u32 | {
6107
fs:: set_permissions( root.join ( p) , fs:: Permissions:: from_mode( mode) ) .unwrap ( )
6108
} ;
6109
for p in [ " a.txt" , " b.txt" , " d/c.txt" ] {
6110
age ( p) ;
6111
}
6112
assert! ( repo.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6113
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
6114
assert! (
6115
m.entries.values ( ) .all ( | e | e.graph.is_some ( ) ) ,
6116
" keys recorded"
6117
) ;
6118
6119
6120
6121
lock ( " a.txt" , 0o000 ) ;
6122
lock ( " b.txt" , 0o000 ) ;
6123
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6124
assert! ( ! snap.changed & & snap.statuses.is_empty ( ) ) ;
6125
assert_eq! (
6126
snap.base_snapshot
6127
.unrendered
6128
.iter ( )
6129
.cloned ( )
6130
.collect:: < Vec< _ > > ( ) ,
6131
vec! [ " a.txt" , " b.txt" , " d/c.txt" ]
6132
) ;
6133
assert! ( ! snap.base.is_full ( ) ) ;
6134
assert! ( ! snap.base_snapshot.is_conflicted ( ) ) ;
6135
6136
let full = repo.snapshot ( " status" ) .unwrap ( ) ;
6137
assert! ( full.statuses.is_empty ( ) & & full.base.is_full ( ) ) ;
6138
assert! ( full.base_snapshot.unrendered.is_empty ( ) ) ;
6139
assert! ( matches! ( & full.base_snapshot.files[ " a.txt" ] .content,
6140
FileContent:: Text { bytes, .. } if bytes = = b " one\n " ) ) ;
6141
6142
6143
write ( root, " d/c.txt" , b " sea edited\n " ) ;
6144
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6145
assert_eq! (
6146
statuses ( & snap) ,
6147
vec! [ ( " d/c.txt" .to_string ( ) , ChangeStatus:: Modified) ]
6148
) ;
6149
assert! ( ! snap.base_snapshot.unrendered.contains ( " d/c.txt" ) ) ;
6150
6151
lock ( " b.txt" , 0o100 ) ;
6152
assert_eq! (
6153
statuses ( & repo.snapshot_status ( " status" ) .unwrap ( ) ) ,
6154
vec! [
6155
( " b.txt" .to_string ( ) , ChangeStatus:: ModeChanged) ,
6156
( " d/c.txt" .to_string ( ) , ChangeStatus:: Modified)
6157
]
6158
) ;
6159
lock ( " b.txt" , 0o000 ) ;
6160
6161
6162
6163
repo.commit_selection ( " edit c" , & [ ] , & Selection:: Paths( vec! [ " d/c.txt" .into ( ) ] ) )
6164
.unwrap ( ) ;
6165
age ( " d/c.txt" ) ;
6166
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6167
assert! ( snap.statuses.is_empty ( ) , " {:?}" , snap.statuses) ;
6168
assert! ( snap.base_snapshot.unrendered.contains ( " a.txt" ) ) ;
6169
lock ( " d/c.txt" , 0o000 ) ;
6170
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6171
assert! ( snap.statuses.is_empty ( ) ) ;
6172
assert! ( snap.base_snapshot.unrendered.contains ( " d/c.txt" ) ) ;
6173
for p in [ " a.txt" , " b.txt" , " d/c.txt" ] {
6174
lock ( p, 0o644 ) ;
6175
}
6176
}
6177
6178
6179
6180
# [ cfg ( unix ) ]
6181
# [ test ]
6182
fn a_changed_attributes_file_disables_skipping_unchanged_files ( ) {
6183
hermetic ( ) ;
6184
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6185
let root = dir.path ( ) ;
6186
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6187
write ( root, " a.txt" , b " crlf\r \n " ) ;
6188
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6189
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6190
fs:: File:: options( )
6191
.write ( true )
6192
.open ( root.join ( " a.txt" ) )
6193
.unwrap ( )
6194
.set_modified ( old)
6195
.unwrap ( ) ;
6196
assert! ( repo.snapshot_status ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6197
assert! ( repo
6198
.snapshot_status ( " status" )
6199
.unwrap ( )
6200
.base_snapshot
6201
.unrendered
6202
.contains ( " a.txt" ) ) ;
6203
6204
write ( root, " .oxyattributes" , b " *.txt eol=lf\n " ) ;
6205
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6206
assert_eq! (
6207
statuses ( & snap) ,
6208
vec! [
6209
( " .oxyattributes" .to_string ( ) , ChangeStatus:: Added) ,
6210
( " a.txt" .to_string ( ) , ChangeStatus:: Modified)
6211
]
6212
) ;
6213
}
54cd08a1 Andy McCall 1 month ago ↶
6214
6215
6216
6217
6218
# [ test ]
6219
fn bookmarks_with_slashes_push_and_checkpoint ( ) {
6220
hermetic ( ) ;
6221
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6222
let root = dir.path ( ) ;
6223
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
6224
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
6225
let mut repo = Repo:: clone( & url, & root.join ( " r" ) )
6226
.unwrap ( )
6227
.with_home ( root.join ( " home" ) ) ;
6228
6229
6230
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
6231
.unwrap ( ) ;
6232
let r = root.join ( " r" ) ;
6233
write ( & r, " a.txt" , b " a\n " ) ;
6234
let h_a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
6235
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
6236
origin.refresh ( ) .unwrap ( ) ;
54cd08a1 Andy McCall 1 month ago ↶
6237
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
6238
repo.bookmark_create ( " feature/x" , None ) .unwrap ( ) ;
6239
repo.bookmark_switch ( " feature/x" , false ) .unwrap ( ) ;
6240
write ( & r, " b.txt" , b " b\n " ) ;
6241
let h_b = repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
6242
for name in [ " feature" , " feature/x" ] {
6243
let res = repo.push_bookmark ( " origin" , name, false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
6244
origin.refresh ( ) .unwrap ( ) ;
54cd08a1 Andy McCall 1 month ago ↶
6245
assert! ( res[ 0 ] .ok, " {name}: {res:?}" ) ;
6246
}
6247
assert_eq! ( origin.resolve_heads ( " feature" ) .unwrap ( ) , vec! [ h_a] ) ;
6248
assert_eq! (
6249
origin.resolve_heads ( " feature/x" ) .unwrap ( ) ,
6250
repo.resolve_heads ( " feature/x" ) .unwrap ( )
6251
) ;
6252
assert! ( origin.resolve_heads ( " feature/x" ) .unwrap ( ) .contains ( & h_b) ) ;
6253
assert! ( origin.latest_ref_attest ( " feature/x" ) .unwrap ( ) .is_some ( ) ) ;
6254
assert! ( origin.latest_ref_attest ( " feature" ) .unwrap ( ) .is_some ( ) ) ;
6255
6256
6257
let ( cp, _ , _ ) = repo.checkpoint ( ) .unwrap ( ) ;
6258
assert_eq! ( repo.latest_checkpoint ( " feature/x" ) .unwrap ( ) .unwrap ( ) .0 , cp) ;
6259
assert! ( repo.latest_checkpoint ( " feature" ) .unwrap ( ) .is_none ( ) ) ;
6260
let heads = repo.resolve_heads ( " feature/x" ) .unwrap ( ) ;
6261
assert! ( repo.load_state_checkpointed ( & heads) .unwrap ( ) .is_some ( ) ) ;
f8c69576 Andy McCall 1 month ago ↶
6262
}
6263
6264
6265
6266
6267
# [ test ]
6268
fn bookmarks_are_deleted_on_a_remote ( ) {
6269
hermetic ( ) ;
6270
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6271
let root = dir.path ( ) ;
6272
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
6273
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
6274
let mut repo = Repo:: clone( & url, & root.join ( " r" ) )
6275
.unwrap ( )
6276
.with_home ( root.join ( " home" ) ) ;
6277
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
6278
.unwrap ( ) ;
6279
let r = root.join ( " r" ) ;
6280
write ( & r, " a.txt" , b " a\n " ) ;
6281
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
6282
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
6283
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6284
repo.bookmark_create ( " feature/x" , None ) .unwrap ( ) ;
6285
assert! ( repo.push_bookmark ( " origin" , " feature/x" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
6286
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6287
assert! ( origin.resolve_heads ( " feature/x" ) .is_ok ( ) ) ;
6288
6289
let res = repo.push_delete ( " origin" , " feature/x" ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
6290
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6291
assert! ( res[ 0 ] .ok, " {res:?}" ) ;
6292
assert! ( ! origin.view ( ) .unwrap ( ) .bookmarks.contains_key ( " feature/x" ) ) ;
6293
let ( _ , att) = origin.latest_ref_attest ( " feature/x" ) .unwrap ( ) .unwrap ( ) ;
6294
assert! (
6295
matches! ( att.attest.body, AttestBody:: Ref { new_state, .. } if new_state.is_zero ( ) ) ,
6296
" the deletion is attested"
6297
) ;
6298
assert! (
6299
repo.view ( ) .unwrap ( ) .bookmarks.contains_key ( " feature/x" ) ,
6300
" the local bookmark stays"
6301
) ;
6302
assert! ( ! repo
6303
.view ( )
6304
.unwrap ( )
6305
.remotes
6306
.get ( " origin" )
6307
.is_some_and ( | m | m.contains_key ( " feature/x" ) ) ) ;
6308
6309
6310
let res = repo.push_bookmark ( " origin" , " feature/x" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
6311
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6312
assert! ( res[ 0 ] .ok, " {res:?}" ) ;
6313
assert! ( origin.view ( ) .unwrap ( ) .bookmarks.contains_key ( " feature/x" ) ) ;
6314
6315
6316
assert! ( repo.push_delete ( " origin" , " nope" ) .is_err ( ) ) ;
6317
let res = repo.push_delete ( " origin" , " main" ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
6318
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6319
assert! (
6320
! res[ 0 ] .ok & & res[ 0 ] .reason.contains ( " default bookmark" ) ,
6321
" {res:?}"
6322
) ;
6323
assert! ( origin.view ( ) .unwrap ( ) .bookmarks.contains_key ( " main" ) ) ;
54cd08a1 Andy McCall 1 month ago ↶
6324
}
61e3bce7 Andy McCall 1 month ago ↶
6325
6326
6327
6328
6329
# [ test ]
6330
fn switching_bookmarks_carries_uncommitted_work ( ) {
6331
hermetic ( ) ;
6332
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6333
let root = dir.path ( ) ;
6334
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6335
write ( root, " a.txt" , b " a\n " ) ;
6336
write ( root, " b.txt" , b " b\n " ) ;
6337
write ( root, " gone.txt" , b " gone\n " ) ;
6338
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6339
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
6340
6341
write ( root, " a.txt" , b " a\n more\n " ) ;
6342
write ( root, " new.txt" , b " new\n " ) ;
6343
fs:: remove_file( root.join ( " gone.txt" ) ) .unwrap ( ) ;
44f1ebe7 Andy McCall 17 days ago ↶
6344
let ( report, carried) = repo.bookmark_switch_carry ( " feature" ) .unwrap ( ) ;
61e3bce7 Andy McCall 1 month ago ↶
6345
assert_eq! ( carried, vec! [ " a.txt" , " gone.txt" , " new.txt" ] ) ;
44f1ebe7 Andy McCall 17 days ago ↶
6346
6347
assert_eq! (
6348
( report.removed, report.removed_paths.len ( ) ) ,
6349
( 0 , 0 ) ,
6350
" {report:?}"
6351
) ;
61e3bce7 Andy McCall 1 month ago ↶
6352
assert_eq! ( repo.config.bookmark, " feature" ) ;
6353
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " a\n more\n " ) ;
6354
assert_eq! ( fs:: read( root.join ( " new.txt" ) ) .unwrap ( ) , b " new\n " ) ;
6355
assert! ( ! root.join ( " gone.txt" ) .exists ( ) ) ;
6356
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
6357
assert_eq! (
6358
statuses ( & snap) ,
6359
vec! [
6360
( " a.txt" .to_string ( ) , ChangeStatus:: Modified) ,
6361
( " gone.txt" .to_string ( ) , ChangeStatus:: Removed) ,
6362
( " new.txt" .to_string ( ) , ChangeStatus:: Added)
6363
]
6364
) ;
6365
repo.commit ( " the work" , & [ ] ) .unwrap ( ) ;
6366
assert! ( repo.resolve_heads ( " main" ) .unwrap ( ) .len ( ) = = 1 ) ;
6367
6368
6369
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
6370
write ( root, " b.txt" , b " b on main\n " ) ;
6371
repo.commit ( " b on main" , & [ ] ) .unwrap ( ) ;
6372
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
6373
write ( root, " b.txt" , b " b edited\n " ) ;
6374
let err = repo.bookmark_switch_carry ( " main" ) .unwrap_err ( ) .to_string ( ) ;
6375
assert! ( err.contains ( " b.txt" ) & & err.contains ( " --discard" ) , " {err}" ) ;
6376
assert_eq! ( repo.config.bookmark, " feature" , " nothing happened" ) ;
6377
assert_eq! ( fs:: read( root.join ( " b.txt" ) ) .unwrap ( ) , b " b edited\n " ) ;
6378
}
1c2bd9ab Andy McCall 1 month ago ↶
6379
6380
6381
6382
6383
# [ test ]
6384
fn merges_move_clean_working_copies_and_checkouts_say_what_blocks_them ( ) {
6385
hermetic ( ) ;
6386
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6387
let root = dir.path ( ) ;
6388
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6389
write ( root, " a.txt" , b " a\n " ) ;
6390
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
6391
write ( root, " b.txt" , b " b\n " ) ;
6392
repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
6393
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
6394
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
6395
write ( root, " c.txt" , b " c\n " ) ;
6396
repo.commit ( " c on feature" , & [ ] ) .unwrap ( ) ;
6397
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
6398
6399
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
6400
assert! ( ! root.join ( " b.txt" ) .exists ( ) ) ;
6401
let m = repo.merge ( " feature" ) .unwrap ( ) ;
6402
assert! ( m.checked_out, " a clean working copy follows the merge" ) ;
6403
for f in [ " a.txt" , " b.txt" , " c.txt" ] {
6404
assert! ( root.join ( f) .exists ( ) , " {f}" ) ;
6405
}
6406
6407
6408
fs:: remove_file( root.join ( " b.txt" ) ) .unwrap ( ) ;
6409
write ( root, " a.txt" , b " a edited\n " ) ;
6410
let err = repo
6411
.checkout ( & [ h1] , false , " checkout" )
6412
.unwrap_err ( )
6413
.to_string ( ) ;
6414
assert! (
6415
err.contains ( " modified a.txt" ) & & err.contains ( " removed b.txt" ) & & err.contains ( " --carry" ) ,
6416
" {err}"
6417
) ;
fe9586d9 Andy McCall 24 days ago ↶
6418
6419
6420
1c2bd9ab Andy McCall 1 month ago ↶
6421
let ( _ , carried) = repo
6422
.checkout_carry ( & [ h1] , " the first change" , " checkout" )
6423
.unwrap ( ) ;
6424
assert_eq! ( carried, vec! [ " a.txt" ] ) ;
6425
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " a edited\n " ) ;
6426
assert! ( ! root.join ( " b.txt" ) .exists ( ) , " h1 has no b.txt" ) ;
6427
}
e7ac6c2e Andy McCall 1 month ago ↶
6428
6429
6430
6431
6432
# [ test ]
6433
fn a_new_repository_is_healthy_before_its_first_commit ( ) {
6434
hermetic ( ) ;
6435
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6436
let root = dir.path ( ) ;
6437
let home = root.join ( " home" ) ;
6438
let mut first = Repo:: init( & root.join ( " first" ) )
6439
.unwrap ( )
6440
.with_home ( home.clone ( ) ) ;
6441
let ( key, _ ) = first
6442
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
6443
.unwrap ( ) ;
6444
let repo = Repo:: init( & root.join ( " second" ) )
6445
.unwrap ( )
6446
.with_home ( home.clone ( ) ) ;
6447
6448
let keys = repo.keys_dir.clone ( ) ;
6449
let _ = fs:: remove_file( keys.join ( format! ( " {} .identity" , key.to_hex ( ) ) ) ) ;
6450
assert! ( keys.join ( format! ( " {} .secret" , key.to_hex ( ) ) ) .is_file ( ) ) ;
6451
let r = repo.doctor ( true ) ;
6452
let id = r.checks.iter ( ) .find ( | c | c.area = = " identity" ) .unwrap ( ) ;
6453
assert_eq! ( id.health, Health:: Ok, " {}" , id.message) ;
6454
assert! ( id.message.contains ( " first signed change" ) , " {}" , id.message) ;
6455
6456
6457
let lib = root.join ( " lib" ) ;
6458
let l = Repo:: init( & lib) .unwrap ( ) .with_home ( home.clone ( ) ) ;
6459
write ( & lib, " x.txt" , b " x\n " ) ;
6460
l.commit ( " x" , & [ ] ) .unwrap ( ) ;
6461
let second = root.join ( " second" ) ;
6462
write ( & second, " a.txt" , b " a\n " ) ;
6463
repo.submodule_add ( & lib.to_string_lossy ( ) , " deps/lib" , None )
6464
.unwrap ( ) ;
6465
let subs = repo.subrepos ( ) .unwrap ( ) ;
6466
assert_eq! ( subs.len ( ) , 1 ) ;
6467
assert_eq! (
6468
( subs[ 0 ] .path.as_str ( ) , subs[ 0 ] .committed) ,
6469
( " deps/lib" , false )
6470
) ;
6471
assert_eq! ( subs[ 0 ] .actual.as_ref ( ) , Some ( & subs[ 0 ] .pin) ) ;
6472
repo.commit ( " add lib" , & [ ] ) .unwrap ( ) ;
6473
let subs = repo.subrepos ( ) .unwrap ( ) ;
6474
assert! ( subs[ 0 ] .committed, " committed now" ) ;
6475
}
8ab265dc Andy McCall 1 month ago ↶
6476
6477
6478
6479
6480
# [ cfg ( unix ) ]
6481
# [ test ]
6482
fn commits_and_diffs_leave_unchanged_files_alone ( ) {
6483
use std:: os:: unix:: fs:: PermissionsExt;
6484
hermetic ( ) ;
6485
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6486
let root = dir.path ( ) ;
6487
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6488
write ( root, " big.txt" , b " lots\n of\n lines\n " ) ;
6489
write ( root, " a.txt" , b " one\n two\n " ) ;
6490
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6491
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6492
for f in [ " big.txt" , " a.txt" ] {
6493
fs:: File:: options( )
6494
.write ( true )
6495
.open ( root.join ( f) )
6496
.unwrap ( )
6497
.set_modified ( old)
6498
.unwrap ( ) ;
6499
}
6500
assert! ( repo.snapshot_status ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6501
6502
fs:: set_permissions( root.join ( " big.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6503
6504
write ( root, " a.txt" , b " one\n TWO\n three\n " ) ;
6505
let diff = repo.diff_files ( None ) .unwrap ( ) ;
6506
assert_eq! (
6507
diff.iter ( ) .map ( | f | f.path.as_str ( ) ) .collect:: < Vec< _ > > ( ) ,
6508
[ " a.txt" ]
6509
) ;
6510
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
6511
assert! ( ! hunks.is_empty ( ) & & hunks.iter ( ) .all ( | h | h.path = = " a.txt" ) ) ;
6512
let h = repo.commit ( " edit a" , & [ ] ) .unwrap ( ) ;
6513
6514
6515
let c = repo.store ( ) .get_change ( & h) .unwrap ( ) ;
6516
let full = repo.load_state ( & c.deps) .unwrap ( ) ;
6517
let want = render_change ( & c, & full, & full.materialize ( ) ) ;
6518
assert_eq! ( repo.describe_change ( & h) .unwrap ( ) , want) ;
6519
let ( base, snap) = repo.change_base ( & c) .unwrap ( ) ;
6520
assert! ( ! base.is_full ( ) & & snap.unrendered.contains ( " big.txt" ) ) ;
6521
6522
6523
write ( root, " a.txt" , b " scratch\n " ) ;
6524
assert_eq! ( repo.discard ( & [ " a.txt" .into ( ) ] ) .unwrap ( ) , 1 ) ;
6525
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n TWO\n three\n " ) ;
6526
fs:: set_permissions( root.join ( " big.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6527
}
f7510b63 Andy McCall 1 month ago ↶
6528
6529
6530
6531
6532
# [ test ]
6533
fn replace_conflicts_are_settled_when_asked ( ) {
6534
hermetic ( ) ;
6535
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6536
let root = dir.path ( ) ;
6537
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6538
let blob = | n : u8 | {
6539
let mut v = vec! [ 0 u8 ; 64 ] ;
6540
v[ 1 ] = n;
6541
v
6542
} ;
6543
write ( root, " img.bin" , & blob ( 1 ) ) ;
6544
repo.commit ( " add img" , & [ ] ) .unwrap ( ) ;
6545
repo.bookmark_create ( " other" , None ) .unwrap ( ) ;
6546
write ( root, " img.bin" , & blob ( 2 ) ) ;
6547
repo.commit ( " img two" , & [ ] ) .unwrap ( ) ;
6548
repo.bookmark_switch ( " other" , false ) .unwrap ( ) ;
6549
write ( root, " img.bin" , & blob ( 3 ) ) ;
6550
repo.commit ( " img three" , & [ ] ) .unwrap ( ) ;
6551
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
6552
repo.merge ( " other" ) .unwrap ( ) ;
6553
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6554
let merged = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
6555
assert! (
6556
matches! ( & merged.files[ " img.bin" ] .content, FileContent:: Opaque { blobs } if blobs.len ( ) = = 2 ) ,
6557
" a replace conflict"
6558
) ;
6559
6560
assert! ( repo
6561
.snapshot ( " status" )
6562
.unwrap ( )
6563
.statuses
6564
.iter ( )
6565
.all ( | s | s.path ! = " img.bin" ) ) ;
6566
6567
6568
let author = repo.author ( ) ;
6569
let edits = [ FileEdit {
6570
path: " img.bin" .into ( ) ,
6571
content: Some ( blob ( 3 ) ) ,
6572
} ] ;
6573
let c = repo
6574
.build_change ( & heads, & edits, " keep three" , author)
6575
.unwrap ( ) ;
6576
assert_eq! ( c.kind, ChangeKind:: Resolution) ;
6577
let mut st = repo.load_state ( & heads) .unwrap ( ) ;
6578
for d in & c.deps {
6579
assert! ( st.contains ( d) ) ;
6580
}
6581
assert! (
6582
heads.iter ( ) .all ( | h | c.deps.contains ( h) ) ,
6583
" depends on both contributors"
6584
) ;
6585
st.add ( c) .unwrap ( ) ;
6586
let after = st.materialize ( ) ;
6587
assert! ( ! after.is_conflicted ( ) ) ;
6588
assert_eq! (
6589
after.files[ " img.bin" ] .content,
6590
FileContent:: Opaque {
6591
blobs: vec! [ Repo:: blob_ref( & blob ( 3 ) ) ]
6592
}
6593
) ;
6594
6595
6596
let edits = [ FileEdit {
6597
path: " img.bin" .into ( ) ,
6598
content: Some ( blob ( 9 ) ) ,
6599
} ] ;
6600
let c = repo.build_change ( & heads, & edits, " new" , author) .unwrap ( ) ;
6601
let mut st = repo.load_state ( & heads) .unwrap ( ) ;
6602
st.add ( c) .unwrap ( ) ;
6603
assert_eq! (
6604
st.materialize ( ) .files[ " img.bin" ] .content,
6605
FileContent:: Opaque {
6606
blobs: vec! [ Repo:: blob_ref( & blob ( 9 ) ) ]
6607
}
6608
) ;
6609
}
83d61e80 Andy McCall 1 month ago ↶
6610
6611
6612
6613
6614
# [ test ]
6615
fn line_ranges_have_exact_histories ( ) {
6616
hermetic ( ) ;
6617
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6618
let root = dir.path ( ) ;
6619
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6620
write ( root, " f.rs" , b " use x;\n fn a() {\n one();\n two();\n }\n " ) ;
6621
let base = repo.commit ( " add f" , & [ ] ) .unwrap ( ) ;
6622
write ( root, " f.rs" , b " use y;\n fn a() {\n one();\n two();\n }\n " ) ;
6623
let outside = repo.commit ( " change the import" , & [ ] ) .unwrap ( ) ;
6624
write ( root, " f.rs" , b " use y;\n fn a() {\n ONE();\n two();\n }\n " ) ;
6625
let inside = repo.commit ( " edit a line of a" , & [ ] ) .unwrap ( ) ;
6626
write ( root, " f.rs" , b " use y;\n fn a() {\n ONE();\n }\n " ) ;
6627
let deleted = repo.commit ( " delete a line of a" , & [ ] ) .unwrap ( ) ;
6628
write ( root, " f.rs" , b " use y;\n fn a() {\n ONE();\n }\n fn b() {}\n " ) ;
6629
let after = repo.commit ( " add b after a" , & [ ] ) .unwrap ( ) ;
6630
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6631
6632
6633
let hist: Vec< ChangeHash> = repo
6634
.line_history ( & heads, " f.rs" , 2 , 4 )
6635
.unwrap ( )
6636
.iter ( )
6637
.map ( | e | e.hash )
6638
.collect ( ) ;
6639
for ( h, what) in [
6640
( base, " wrote it" ) ,
6641
( inside, " edited a line" ) ,
6642
( deleted, " deleted a line from inside" ) ,
6643
] {
6644
assert! ( hist.contains ( & h) , " {what}" ) ;
6645
}
6646
for ( h, what) in [ ( outside, " an edit above" ) , ( after, " an addition below" ) ] {
6647
assert! ( ! hist.contains ( & h) , " {what}" ) ;
6648
}
6649
assert_eq! ( hist[ 0 ] , deleted, " newest first" ) ;
6650
6651
let one: Vec< ChangeHash> = repo
6652
.line_history ( & heads, " f.rs" , 1 , 1 )
6653
.unwrap ( )
6654
.iter ( )
6655
.map ( | e | e.hash )
6656
.collect ( ) ;
6657
assert! ( one.contains ( & outside) & & ! one.contains ( & inside) ) ;
6658
assert! ( repo.line_history ( & heads, " f.rs" , 3 , 9 ) .is_err ( ) ) ;
6659
assert! ( repo.line_history ( & heads, " f.rs" , 0 , 1 ) .is_err ( ) ) ;
6660
assert! ( repo.line_history ( & heads, " nope.rs" , 1 , 1 ) .is_err ( ) ) ;
6661
}
fc1a466a Andy McCall 1 month ago ↶
6662
6663
# [ test ]
6664
fn receipts_are_kept_with_their_state_and_verified ( ) {
6665
use crate :: receipt:: { Receipt, SignedReceipt} ;
6666
hermetic ( ) ;
6667
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6668
let root = dir.path ( ) ;
6669
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6670
write ( root, " a.txt" , b " 1\n " ) ;
6671
repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
6672
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6673
let state = repo
6674
.store ( )
6675
.put_state ( & StateObject:: new( heads.clone ( ) ) )
6676
.unwrap ( ) ;
6677
let out = repo.store_blob ( b " built\n " ) .unwrap ( ) ;
6678
let receipt = Receipt {
6679
job_name: " build" .into ( ) ,
6680
pipeline_hash: ObjectRef( [ 1 ; 32 ] ) ,
6681
scope_tree_hash: ObjectRef( [ 2 ; 32 ] ) ,
6682
inputs: vec! [ ] ,
6683
environment: ObjectRef( [ 3 ; 32 ] ) ,
6684
outputs: vec! [ out] ,
6685
exit_code: 0 ,
6686
duration_ms: 42 ,
6687
} ;
6688
let sk = SecretKey:: generate( ) ;
6689
let runner = sk.identity ( " earth-1" , " runner@earth" , 1 ) ;
6690
let sr = SignedReceipt:: sign(
6691
receipt.clone ( ) ,
6692
repo.id ( ) .unwrap ( ) ,
6693
state,
6694
" demo#1" ,
6695
& runner,
6696
& sk,
6697
) ;
6698
6699
let r = repo.add_receipt ( & sr) .unwrap ( ) ;
6700
let found = repo.receipts ( & heads) .unwrap ( ) ;
6701
assert_eq! ( found.len ( ) , 1 ) ;
6702
assert_eq! ( found[ 0 ] .attest, r) ;
6703
assert_eq! ( found[ 0 ] .signed, sr) ;
6704
assert! ( found[ 0 ] .problem.is_none ( ) & & ! found[ 0 ] .revoked) ;
6705
assert! ( repo.receipts ( & [ ObjectRef( [ 5 ; 32 ] ) ] ) .unwrap ( ) .is_empty ( ) ) ;
6706
6707
6708
let elsewhere = SignedReceipt:: sign( receipt.clone ( ) , [ 0 ; 16 ] , state, " x" , & runner, & sk) ;
6709
assert! ( repo
6710
.add_receipt ( & elsewhere)
6711
.unwrap_err ( )
6712
.to_string ( )
6713
.contains ( " another repository" ) ) ;
6714
let mut forged = sr.clone ( ) ;
6715
forged.receipt.exit_code = 1 ;
6716
assert! ( repo
6717
.add_receipt ( & forged)
6718
.unwrap_err ( )
6719
.to_string ( )
6720
.contains ( " does not verify" ) ) ;
6721
c63fed2e Andy McCall 19 days ago ↶
6722
6723
6724
let hub_sk = SecretKey:: generate( ) ;
6725
let hub = hub_sk.identity ( " oxygenhub.io" , " [email protected] " , 1 ) ;
6726
let job_sk = SecretKey:: generate( ) ;
6727
let job = job_sk.identity ( " job 2 test" , " [email protected] " , 1 ) ;
6728
let mut test_receipt = receipt.clone ( ) ;
6729
test_receipt.job_name = " test" .into ( ) ;
6730
let plain = SignedReceipt:: sign(
6731
test_receipt,
6732
repo.id ( ) .unwrap ( ) ,
6733
state,
6734
" demo#2" ,
6735
& job,
6736
& job_sk,
6737
) ;
6738
let now = plain.attest.hlc.wall_ms;
6739
let cert = crate :: cert:: SignedCertificate:: sign(
6740
crate :: cert:: Certificate {
6741
key: job.clone ( ) ,
6742
issuer: hub.hash ( ) ,
6743
subject: " repo:andy/demo:bookmark:main" .into ( ) ,
6744
claims: vec! [ ] ,
6745
scope: crate :: identity:: Scope:: SEAL ,
6746
not_before: now - 1_000 ,
6747
expires: now + 600_000 ,
a2387d54 Andy McCall 18 days ago ↶
6748
grant: None ,
c63fed2e Andy McCall 19 days ago ↶
6749
} ,
6750
& hub,
6751
& hub_sk,
6752
) ;
6753
let certified = plain.with_certificate ( cert, hub.clone ( ) ) ;
6754
repo.add_receipt ( & certified) .unwrap ( ) ;
6755
let found = repo.receipts ( & heads) .unwrap ( ) ;
6756
let got = found
6757
.iter ( )
6758
.find ( | i | i.signed.receipt.job_name = = " test" )
6759
.unwrap ( ) ;
6760
assert_eq! ( got.signed, certified) ;
6761
assert! ( got.problem.is_none ( ) , " {:?}" , got.problem) ;
6762
6763
6764
fc1a466a Andy McCall 1 month ago ↶
6765
repo.gc ( true ) .unwrap ( ) ;
c63fed2e Andy McCall 19 days ago ↶
6766
let found = repo.receipts ( & heads) .unwrap ( ) ;
6767
assert_eq! ( found.len ( ) , 2 ) ;
6768
let got = found
6769
.iter ( )
6770
.find ( | i | i.signed.receipt.job_name = = " test" )
6771
.unwrap ( ) ;
6772
assert_eq! ( got.signed, certified, " the certificate survives gc" ) ;
6773
assert! ( got.problem.is_none ( ) ) ;
fc1a466a Andy McCall 1 month ago ↶
6774
assert_eq! ( repo.read_blob ( & out) .unwrap ( ) , b " built\n " ) ;
6775
assert! ( repo.fsck ( ) .unwrap ( ) .problems.is_empty ( ) ) ;
dfb23a3a Andy McCall 1 month ago ↶
6776
}
6777
6778
# [ test ]
3a4b3e01 Andy McCall 20 days ago ↶
6779
fn approvals_are_kept_with_their_state_and_survive_gc ( ) {
6780
hermetic ( ) ;
6781
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6782
let root = dir.path ( ) ;
6783
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6784
write ( root, " a.txt" , b " 1\n " ) ;
6785
repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
6786
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6787
let state = repo.store ( ) .put_state ( & StateObject:: new( heads) ) .unwrap ( ) ;
6788
let plan = repo.store_blob ( b " the reviewed plan\n " ) .unwrap ( ) ;
6789
let sk = SecretKey:: generate( ) ;
6790
let andy = sk.identity ( " Andy" , " [email protected] " , 1 ) ;
6791
let statement = crate :: approval:: Statement {
6792
repo: repo.id ( ) .unwrap ( ) ,
6793
run: " https://hub.example/andy/infra/flow/8" .into ( ) ,
6794
job: " apply" .into ( ) ,
6795
job_hash: ObjectRef( [ 1 ; 32 ] ) ,
6796
state,
6797
inputs: vec! [ plan] ,
6798
environment: " prod" .into ( ) ,
6799
approve: true ,
6800
} ;
6801
let sa = crate :: approval:: SignedApproval:: sign( & statement, & andy, & sk) ;
6802
let r = repo.add_approval ( & sa) .unwrap ( ) ;
6803
assert_eq! ( repo.store ( ) .approvals_for ( & state) .unwrap ( ) , vec! [ r] ) ;
6804
6805
let elsewhere = crate :: approval:: SignedApproval:: sign(
6806
& crate :: approval:: Statement {
6807
repo: [ 0 ; 16 ] ,
6808
.. statement.clone ( )
6809
} ,
6810
& andy,
6811
& sk,
6812
) ;
6813
assert! ( repo
6814
.add_approval ( & elsewhere)
6815
.unwrap_err ( )
6816
.to_string ( )
6817
.contains ( " another repository" ) ) ;
6818
let mut forged = sa.clone ( ) ;
6819
if let AttestBody:: Approval { environment, .. } = & mut forged.attest.body {
6820
* environment = " dev" .into ( ) ;
6821
}
6822
assert! ( repo
6823
.add_approval ( & forged)
6824
.unwrap_err ( )
6825
.to_string ( )
6826
.contains ( " does not verify" ) ) ;
6827
6828
repo.gc ( true ) .unwrap ( ) ;
6829
assert_eq! ( repo.store ( ) .approvals_for ( & state) .unwrap ( ) , vec! [ r] ) ;
6830
assert_eq! ( repo.store ( ) .get_attest ( & r) .unwrap ( ) , sa.attest) ;
6831
assert_eq! ( repo.store ( ) .signatures ( & r) .unwrap ( ) , sa.sigs) ;
6832
assert! ( repo.fsck ( ) .unwrap ( ) .problems.is_empty ( ) ) ;
6833
}
6834
6835
# [ test ]
dfb23a3a Andy McCall 1 month ago ↶
6836
fn status_leaves_an_unchanged_manifest_alone ( ) {
6837
hermetic ( ) ;
6838
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6839
let root = dir.path ( ) ;
6840
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6841
write ( root, " a.txt" , b " 1\n " ) ;
6842
write ( root, " b.txt" , b " 2\n " ) ;
6843
repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
6844
6845
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 60 ) ;
6846
for f in [ " a.txt" , " b.txt" ] {
6847
fs:: File:: options( )
6848
.write ( true )
6849
.open ( root.join ( f) )
6850
.unwrap ( )
6851
.set_modified ( old)
6852
.unwrap ( ) ;
6853
}
6854
repo.snapshot_status ( " status" ) .unwrap ( ) ;
6855
let path = repo.manifest_path ( ) ;
6856
let first = fs:: read( & path) .unwrap ( ) ;
6857
repo.snapshot_status ( " status" ) .unwrap ( ) ;
6858
assert_eq! (
6859
fs:: read( & path) .unwrap ( ) ,
6860
first,
6861
" an unchanged tree does not rewrite the manifest"
6862
) ;
6863
6864
write ( root, " a.txt" , b " 1\n 2\n " ) ;
6865
repo.snapshot_status ( " status" ) .unwrap ( ) ;
6866
assert_ne! ( fs:: read( & path) .unwrap ( ) , first) ;
fc1a466a Andy McCall 1 month ago ↶
6867
}
6f8aeb99 Andy McCall 1 month ago ↶
6868
6869
# [ test ]
6870
fn large_text_files_travel_and_survive_gc_as_text_blobs ( ) {
6871
hermetic ( ) ;
6872
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6873
let root = dir.path ( ) ;
6874
let origin_path = root.join ( " origin" ) ;
6875
Repo:: init( & origin_path) .unwrap ( ) ;
6876
let origin_url = origin_path.to_string_lossy ( ) .to_string ( ) ;
6877
let a = Repo:: clone( & origin_url, & root.join ( " a" ) ) .unwrap ( ) ;
6878
let big: String = ( 0 .. 5000 ) .map ( | i | format! ( " line {i} \n " ) ) .collect ( ) ;
6879
write ( & root.join ( " a" ) , " big.txt" , big.as_bytes ( ) ) ;
6880
write ( & root.join ( " a" ) , " small.txt" , b " small\n " ) ;
6881
let h1 = a.commit ( " large" , & [ ] ) .unwrap ( ) ;
6882
let ( _ , body) = a.store ( ) .get ( & h1) .unwrap ( ) ;
6883
assert! ( body.len ( ) < 2048 , " the change is {} bytes" , body.len ( ) ) ;
6884
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
6885
6886
6887
let b = Repo:: clone( & origin_url, & root.join ( " b" ) ) .unwrap ( ) ;
6888
assert_eq! ( fs:: read( root.join ( " b/big.txt" ) ) .unwrap ( ) , big.as_bytes ( ) ) ;
6889
6890
let edited = big.replace ( " line 2500\n " , " line 2500, edited\n " ) ;
6891
write ( & root.join ( " b" ) , " big.txt" , edited.as_bytes ( ) ) ;
6892
let h2 = b.commit ( " edit" , & [ ] ) .unwrap ( ) ;
6893
assert! ( b.store ( ) .get_change ( & h2) .unwrap ( ) .edits.len ( ) < = 2 ) ;
6894
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
6895
assert! ( a.pull ( " origin" ) .unwrap ( ) .checked_out) ;
6896
assert_eq! ( fs:: read( root.join ( " a/big.txt" ) ) .unwrap ( ) , edited.as_bytes ( ) ) ;
6897
assert_eq! (
6898
a.fsck ( ) .unwrap ( ) .errors ( ) ,
6899
0 ,
6900
" {:?}" ,
6901
a.fsck ( ) .unwrap ( ) .problems
6902
) ;
6903
6904
6905
let mut a = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) ;
6906
a.config.set ( " core.op-retention" , " 0" ) .unwrap ( ) ;
6907
a.save_config ( ) .unwrap ( ) ;
6908
let a = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) ;
6909
a.gc ( true ) .unwrap ( ) ;
6910
let a = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) ;
6911
assert_eq! ( a.store ( ) .get_change ( & h1) .unwrap ( ) .message, " large" ) ;
6912
assert_eq! (
6913
a.fsck ( ) .unwrap ( ) .errors ( ) ,
6914
0 ,
6915
" {:?}" ,
6916
a.fsck ( ) .unwrap ( ) .problems
6917
) ;
6918
6919
let c = Repo:: clone( & root.join ( " a" ) .to_string_lossy ( ) , & root.join ( " c" ) ) .unwrap ( ) ;
6920
assert_eq! ( fs:: read( root.join ( " c/big.txt" ) ) .unwrap ( ) , edited.as_bytes ( ) ) ;
6921
drop ( c) ;
6caacfff Andy McCall 1 month ago ↶
6922
}
6923
6924
# [ test ]
6925
fn operations_that_both_replace_a_working_copy_are_not_merged ( ) {
6926
hermetic ( ) ;
6927
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6928
let root = dir.path ( ) ;
6929
let repo = Repo:: init( root) .unwrap ( ) ;
6930
write ( root, " a.txt" , b " a\n " ) ;
6931
let a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
6932
write ( root, " b.txt" , b " b\n " ) ;
6933
repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
6934
let ( x, op) = repo.head_op ( ) .unwrap ( ) ;
6935
let ws = * op.view.workspaces.keys ( ) .next ( ) .unwrap ( ) ;
6936
let mut one = op.view.clone ( ) ;
6937
one.workspaces.insert ( ws, a) ;
6938
let mut two = op.view.clone ( ) ;
6939
two.workspaces.insert ( ws, ObjectRef( [ 7 ; 32 ] ) ) ;
6940
let first = repo.record_op ( & [ x] , " one" , one) .unwrap ( ) ;
6941
let e = repo.record_op ( & [ x] , " two" , two) .unwrap_err ( ) ;
6942
assert! (
6943
e.to_string ( )
6944
.contains ( " changed this working copy at the same time" ) ,
6945
" {e}"
6946
) ;
6947
assert_eq! (
6948
repo.head_op ( ) .unwrap ( ) .0 ,
6949
first,
6950
" the second is not recorded"
6951
) ;
6952
}
6953
6954
6955
6956
6957
# [ test ]
6958
fn a_snapshot_during_a_switch_never_records_the_switch_as_edits ( ) {
6959
hermetic ( ) ;
6960
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6961
let root = dir.path ( ) .to_path_buf ( ) ;
6962
let mut repo = Repo:: init( & root) .unwrap ( ) ;
6963
write ( & root, " keep.txt" , b " keep\n " ) ;
6964
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6965
repo.bookmark_create ( " small" , None ) .unwrap ( ) ;
6966
for i in 0 .. 300 {
6967
write (
6968
& root,
6969
& format! ( " many/f{i} .txt" ) ,
6970
format! ( " file {i} \n " ) .as_bytes ( ) ,
6971
) ;
6972
}
6973
repo.commit ( " many" , & [ ] ) .unwrap ( ) ;
6974
let stop = std:: sync:: Arc:: new( std:: sync:: atomic:: AtomicBool:: new( false ) ) ;
6975
let watcher = {
6976
let ( root, stop) = ( root.clone ( ) , stop.clone ( ) ) ;
6977
std:: thread:: spawn( move | | {
6978
let r = Repo:: open( & root) .unwrap ( ) ;
6979
let mut ran = 0 ;
6980
while ! stop.load ( std:: sync:: atomic:: Ordering:: Relaxed) {
6981
if r.snapshot_status ( " oxy status" ) .is_ok ( ) {
6982
ran + = 1 ;
6983
}
6984
}
6985
ran
6986
} )
6987
} ;
6988
for i in 0 .. 20 {
6989
let to = if i % 2 = = 0 { " small" } else { " main" } ;
6990
repo.bookmark_switch ( to, false ) .unwrap ( ) ;
6991
}
6992
stop.store ( true , std:: sync:: atomic:: Ordering:: Relaxed) ;
6993
assert! ( watcher.join ( ) .unwrap ( ) > 0 , " the watcher snapshotted" ) ;
6994
6995
let repo = Repo:: open( & root) .unwrap ( ) ;
6996
let s = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
6997
assert! ( s.statuses.is_empty ( ) , " {:?}" , statuses ( & s) ) ;
6998
assert! ( s.wc.edits.is_empty ( ) & & s.wc.tree_ops.is_empty ( ) ) ;
6999
assert_eq! ( s.wc.deps, repo.bookmark_heads ( ) .unwrap ( ) ) ;
7000
assert! ( root.join ( " many/f299.txt" ) .exists ( ) ) ;
1a3b6203 Andy McCall 1 month ago ↶
7001
}
7002
7003
7004
7005
7006
# [ test ]
7007
fn a_parent_switch_with_carry_leaves_a_subrepo_ahead_of_its_pin_alone ( ) {
7008
hermetic ( ) ;
7009
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7010
let root = dir.path ( ) ;
7011
Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
7012
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
7013
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) ) .unwrap ( ) ;
7014
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
7015
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
7016
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7017
7018
let app_dir = root.join ( " app" ) ;
7019
let mut app = Repo:: init( & app_dir) .unwrap ( ) ;
7020
write ( & app_dir, " main.txt" , b " app\n " ) ;
7021
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
7022
app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
7023
app.bookmark_create ( " feature" , None ) .unwrap ( ) ;
7024
7025
7026
let nested_dir = app_dir.join ( " libs/lib" ) ;
7027
let nested = Repo:: open( & nested_dir) .unwrap ( ) ;
7028
write ( & nested_dir, " new.txt" , b " new\n " ) ;
7029
let l2 = nested.commit ( " lib: new file" , & [ ] ) .unwrap ( ) ;
7030
drop ( nested) ;
7031
7032
let ( _ , carried) = app.bookmark_switch_carry ( " feature" ) .unwrap ( ) ;
7033
assert! ( carried.contains ( & " libs/lib" .to_string ( ) ) , " {carried:?}" ) ;
7034
let nested = Repo:: open( & nested_dir) .unwrap ( ) ;
7035
assert! (
7036
nested.bookmark_heads ( ) .unwrap ( ) .contains ( & l2) ,
7037
" its bookmark kept its work"
7038
) ;
7039
assert! ( nested_dir.join ( " new.txt" ) .exists ( ) , " and its files" ) ;
7040
let s = app.snapshot ( " status" ) .unwrap ( ) ;
7041
assert_eq! (
7042
statuses ( & s) ,
7043
vec! [ ( " libs/lib" .to_string ( ) , ChangeStatus:: Modified) ]
7044
) ;
6f8aeb99 Andy McCall 1 month ago ↶
7045
}
7d9dab13 Andy McCall 1 month ago ↶
7046
7047
7048
7049
7050
# [ test ]
7051
fn pull_takes_a_remote_move_to_changes_already_here ( ) {
7052
hermetic ( ) ;
7053
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7054
let root = dir.path ( ) ;
7055
let origin_path = root.join ( " origin" ) ;
7056
let origin = Repo:: init( & origin_path) .unwrap ( ) ;
7057
let url = origin_path.to_string_lossy ( ) .to_string ( ) ;
7058
let mut b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) ;
7059
write ( & root.join ( " b" ) , " base.txt" , b " base\n " ) ;
7060
b.commit ( " base" , & [ ] ) .unwrap ( ) ;
7061
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
7062
origin.refresh ( ) .unwrap ( ) ;
7d9dab13 Andy McCall 1 month ago ↶
7063
7064
7065
b.bookmark_create ( " feat" , None ) .unwrap ( ) ;
7066
b.bookmark_switch ( " feat" , false ) .unwrap ( ) ;
7067
write ( & root.join ( " b" ) , " x.txt" , b " x\n " ) ;
7068
let x = b.commit ( " x" , & [ ] ) .unwrap ( ) ;
7069
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
7070
origin.refresh ( ) .unwrap ( ) ;
7d9dab13 Andy McCall 1 month ago ↶
7071
b.bookmark_switch ( " main" , false ) .unwrap ( ) ;
7072
7073
7074
let feat = origin
7075
.store ( )
7076
.get_state ( & origin.view ( ) .unwrap ( ) .bookmarks[ " feat" ] )
7077
.unwrap ( )
7078
.heads;
7079
origin.bookmark_set ( " main" , & feat) .unwrap ( ) ;
7080
7081
let r = b.pull ( " origin" ) .unwrap ( ) ;
7082
assert! ( r.changed, " {r:?}" ) ;
7083
assert! ( r.heads.contains ( & x) | | b.load_state ( & r.heads) .unwrap ( ) .contains ( & x) ) ;
7084
let main = b
7085
.bookmarks ( )
7086
.unwrap ( )
7087
.into_iter ( )
7088
.find ( | m | m.name = = " main" )
7089
.unwrap ( ) ;
7090
assert_eq! ( main.behind, Some ( 0 ) , " {main:?}" ) ;
7091
}
86e1824f Andy McCall 1 month ago ↶
7092
14edbaaf Andy McCall 1 month ago ↶
7093
7094
7095
7096
7097
# [ test ]
7098
fn remote_bookmarks_are_named_switched_to_pulled_and_cloned ( ) {
7099
hermetic ( ) ;
7100
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7101
let root = dir.path ( ) ;
7102
let origin_path = root.join ( " origin" ) ;
7103
let mut origin = Repo:: init( & origin_path) .unwrap ( ) ;
7104
let url = origin_path.to_string_lossy ( ) .to_string ( ) ;
7105
let mut a = Repo:: clone( & url, & root.join ( " a" ) ) .unwrap ( ) ;
7106
write ( & root.join ( " a" ) , " base.txt" , b " base\n " ) ;
7107
a.commit ( " base" , & [ ] ) .unwrap ( ) ;
7108
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7109
a.bookmark_create ( " develop" , None ) .unwrap ( ) ;
7110
a.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
7111
write ( & root.join ( " a" ) , " base.txt" , b " base\n d1\n " ) ;
7112
let d1 = a.commit ( " d1" , & [ ] ) .unwrap ( ) ;
7113
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7114
7115
7116
let c_root = root.join ( " c" ) ;
7117
let mut c = Repo:: clone( & url, & c_root ) .unwrap ( ) ;
7118
assert_eq! ( c.config.bookmark, " main" ) ;
7119
assert! ( ! c.view ( ) .unwrap ( ) .bookmarks.contains_key ( " develop" ) ) ;
7120
assert_eq! ( c.resolve_heads ( " origin/develop" ) .unwrap ( ) , vec! [ d1] ) ;
7121
assert_eq! ( c.resolve_change ( " origin/develop" ) .unwrap ( ) , d1) ;
7122
assert_eq! ( c.remote_only_bookmarks ( ) .unwrap ( ) .len ( ) , 1 ) ;
7123
7124
7125
c.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
7126
assert_eq! ( c.config.bookmark, " develop" ) ;
7127
assert_eq! ( c.bookmark_heads ( ) .unwrap ( ) , vec! [ d1] ) ;
7128
assert_eq! ( fs:: read( c_root .join ( " base.txt" ) ) .unwrap ( ) , b " base\n d1\n " ) ;
7129
assert! ( c.remote_only_bookmarks ( ) .unwrap ( ) .is_empty ( ) ) ;
7130
assert! ( c.bookmark_switch ( " nowhere" , false ) .is_err ( ) ) ;
7131
7132
7133
write ( & root.join ( " a" ) , " base.txt" , b " base\n d1\n d2\n " ) ;
7134
let d2 = a.commit ( " d2" , & [ ] ) .unwrap ( ) ;
7135
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7136
a.bookmark_switch ( " main" , false ) .unwrap ( ) ;
7137
write ( & root.join ( " a" ) , " m.txt" , b " m\n " ) ;
7138
let m = a.commit ( " m" , & [ ] ) .unwrap ( ) ;
7139
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
733ea16a Andy McCall 17 days ago ↶
7140
7141
let f = Repo:: clone( & url, & root.join ( " f" ) ) .unwrap ( ) ;
7142
assert! ( ! f.view ( ) .unwrap ( ) .bookmarks.contains_key ( " develop" ) ) ;
7143
let merged = f.merge ( " origin/develop" ) .unwrap ( ) ;
7144
assert! ( merged.changed, " {merged:?}" ) ;
7145
assert! (
7146
f.load_state_indexed ( & f.bookmark_heads ( ) .unwrap ( ) )
7147
.unwrap ( )
7148
.contains ( & d1) ,
7149
" main holds develop's change"
7150
) ;
7151
assert! ( f
7152
.merge ( " origin/nowhere" )
7153
.unwrap_err ( )
7154
.to_string ( )
7155
.contains ( " no bookmark origin/nowhere" ) ) ;
14edbaaf Andy McCall 1 month ago ↶
7156
let r = c.pull ( " origin" ) .unwrap ( ) ;
7157
assert! ( r.changed & & r.checked_out, " {r:?}" ) ;
7158
assert_eq! ( r.heads, vec! [ d2] ) ;
7159
assert_eq! ( r.others, vec! [ " main" .to_string ( ) ] ) ;
7160
assert! ( c.resolve_heads ( " main" ) .unwrap ( ) .contains ( & m) ) ;
7161
assert_eq! (
7162
fs:: read( c_root .join ( " base.txt" ) ) .unwrap ( ) ,
7163
b " base\n d1\n d2\n "
7164
) ;
7165
let again = c.pull ( " origin" ) .unwrap ( ) ;
7166
assert! ( ! again.changed & & again.others.is_empty ( ) , " {again:?}" ) ;
7167
7168
7169
origin.config.set ( " workspace.bookmark" , " develop" ) .unwrap ( ) ;
7170
origin.save_config ( ) .unwrap ( ) ;
7171
let e = Repo:: clone( & url, & root.join ( " e" ) ) .unwrap ( ) ;
7172
assert_eq! ( e.config.bookmark, " develop" ) ;
7173
assert_eq! ( e.bookmark_heads ( ) .unwrap ( ) , vec! [ d2] ) ;
7174
let reopened = Repo:: open( & root.join ( " e" ) ) .unwrap ( ) ;
7175
assert_eq! ( reopened.config.bookmark, " develop" ) ;
7176
7177
let mut e = reopened;
7178
e.bookmark_switch ( " origin/main" , false ) .unwrap ( ) ;
7179
assert_eq! ( e.config.bookmark, " main" ) ;
7180
assert! ( e.bookmark_heads ( ) .unwrap ( ) .contains ( & m) ) ;
7181
}
7182
98a1620a Andy McCall 1 month ago ↶
7183
7184
7185
7186
7187
# [ test ]
7188
fn operations_can_be_read_restored_and_reverted ( ) {
7189
hermetic ( ) ;
7190
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7191
let root = dir.path ( ) ;
7192
let repo = Repo:: init( root) .unwrap ( ) ;
7193
write ( root, " a.txt" , b " a\n " ) ;
7194
let first = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
7195
write ( root, " a.txt" , b " a\n b\n " ) ;
7196
let second = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
7197
let at_second = repo.head_op ( ) .unwrap ( ) .0. to_hex ( ) ;
7198
repo.bookmark_create ( " keep" , None ) .unwrap ( ) ;
7199
let created = repo.head_op ( ) .unwrap ( ) .0. to_hex ( ) ;
7200
write ( root, " n.txt" , b " n\n " ) ;
7201
let third = repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
7202
7203
7204
let ops = repo.op_log ( ) .unwrap ( ) ;
7205
let changes = repo.view_changes ( & ops[ 1 ] .1. view, & ops[ 0 ] .1. view) .unwrap ( ) ;
7206
assert! (
7207
changes
7208
.iter ( )
7209
.any ( | c | matches! ( c, ViewChange:: BookmarkMoved { name, .. } if name = = " main" ) ) ,
7210
" {changes:?}"
7211
) ;
7212
assert! ( changes
7213
.iter ( )
7214
.any ( | c | matches! ( c, ViewChange:: WorkingCopy { .. } ) ) ) ;
7215
7216
7217
let old = Repo:: open( root) .unwrap ( ) .at_op ( & at_second[ .. 10 ] ) .unwrap ( ) ;
7218
assert_eq! ( old.bookmark_heads ( ) .unwrap ( ) , vec! [ second] ) ;
7219
assert! ( ! old.view ( ) .unwrap ( ) .bookmarks.contains_key ( " keep" ) ) ;
7220
write ( root, " z.txt" , b " z\n " ) ;
7221
assert! ( old.commit ( " refused" , & [ ] ) .is_err ( ) , " --at-op is read-only" ) ;
7222
fs:: remove_file( root.join ( " z.txt" ) ) .unwrap ( ) ;
7223
assert! ( repo.resolve_op ( " zz" ) .is_err ( ) ) ;
7224
assert_eq! ( repo.resolve_op ( " @" ) .unwrap ( ) .0 , repo.head_op ( ) .unwrap ( ) .0 ) ;
7225
7226
7227
repo.op_restore ( & at_second, false ) .unwrap ( ) ;
7228
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ second] ) ;
7229
assert! ( ! root.join ( " n.txt" ) .exists ( ) ) ;
7230
repo.undo ( ) .unwrap ( ) ;
7231
let mut heads = repo.bookmark_heads ( ) .unwrap ( ) ;
7232
heads.sort ( ) ;
7233
let mut want = vec! [ second, third] ;
7234
want.sort ( ) ;
7235
assert_eq! ( heads, want) ;
7236
assert! (
7237
root.join ( " n.txt" ) .exists ( ) ,
7238
" the files came back with the undo"
7239
) ;
7240
7241
7242
let r = repo.op_revert ( & created) .unwrap ( ) ;
7243
assert_eq! ( r.reverted, vec! [ " keep" .to_string ( ) ] ) ;
7244
assert! ( ! repo.view ( ) .unwrap ( ) .bookmarks.contains_key ( " keep" ) ) ;
7245
assert! ( repo
7246
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
7247
.unwrap ( )
7248
.contains ( & third) ) ;
7249
assert! ( repo.op_revert ( & created) .is_err ( ) , " nothing left to revert" ) ;
7250
let _ = first;
7251
}
7252
36041796 Andy McCall 1 month ago ↶
7253
7254
7255
7256
# [ test ]
7257
fn status_with_uncommitted_work_comes_from_the_cache_until_a_file_changes ( ) {
7258
hermetic ( ) ;
7259
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7260
let root = dir.path ( ) ;
7261
let repo = Repo:: init( root) .unwrap ( ) ;
7262
write ( root, " a.txt" , b " a\n " ) ;
7263
write ( root, " b.txt" , b " b\n " ) ;
7264
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7265
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , Some ( vec! [ ] ) , " clean" ) ;
7266
7267
write ( root, " a.txt" , b " a\n more\n " ) ;
7268
assert_eq! (
7269
repo.status_by_manifest ( ) .unwrap ( ) ,
7270
None ,
7271
" no snapshot of the edit yet"
7272
) ;
7273
let snap = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7274
assert_eq! ( snap.statuses.len ( ) , 1 ) ;
7275
let cached = repo
7276
.status_by_manifest ( )
7277
.unwrap ( )
7278
.expect ( " answered from the cache" ) ;
7279
assert_eq! ( cached.len ( ) , 1 ) ;
7280
assert_eq! (
7281
( cached[ 0 ] .path.as_str ( ) , cached[ 0 ] .status) ,
7282
( " a.txt" , ChangeStatus:: Modified)
7283
) ;
7284
7285
7286
write ( root, " a.txt" , b " a\n more\n and more\n " ) ;
7287
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None , " edited again" ) ;
7288
repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7289
assert! ( repo.status_by_manifest ( ) .unwrap ( ) .is_some ( ) ) ;
7290
write ( root, " new.txt" , b " n\n " ) ;
7291
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None , " a file added" ) ;
7292
fs:: remove_file( root.join ( " new.txt" ) ) .unwrap ( ) ;
7293
assert! (
7294
repo.status_by_manifest ( ) .unwrap ( ) .is_some ( ) ,
7295
" back as it was"
7296
) ;
7297
fs:: remove_file( root.join ( " b.txt" ) ) .unwrap ( ) ;
7298
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None , " a file removed" ) ;
7299
write ( root, " b.txt" , b " b\n " ) ;
7300
7301
7302
repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7303
repo.commit ( " more" , & [ ] ) .unwrap ( ) ;
7304
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , Some ( vec! [ ] ) ) ;
7305
}
7306
77445673 Andy McCall 1 month ago ↶
7307
7308
7309
7310
# [ test ]
7311
fn the_working_copy_diff_from_the_cache_matches_a_snapshot ( ) {
7312
hermetic ( ) ;
7313
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7314
let root = dir.path ( ) ;
7315
let repo = Repo:: init( root) .unwrap ( ) ;
7316
write ( root, " a.txt" , b " one\n two\n three\n " ) ;
7317
write ( root, " gone.txt" , b " bye\n " ) ;
7318
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7319
write ( root, " a.txt" , b " one\n 2\n three\n four\n " ) ;
7320
write ( root, " new.txt" , b " hello\n " ) ;
7321
fs:: remove_file( root.join ( " gone.txt" ) ) .unwrap ( ) ;
7322
let snap = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7323
let slow = render_change ( & snap.wc, & snap.base, & snap.base_snapshot) ;
7324
assert! (
7325
repo.status_by_manifest ( ) .unwrap ( ) .is_some ( ) ,
7326
" the cache holds"
7327
) ;
7328
let ( wc, base, snapshot) = repo.working_copy_with_base ( ) .unwrap ( ) ;
7329
assert_eq! ( render_change ( & wc, & base, & snapshot) , slow) ;
7330
assert_eq! ( repo.diff_files ( None ) .unwrap ( ) .len ( ) , 3 ) ;
74b4a3c6 Andy McCall 19 days ago ↶
7331
7332
7333
7334
repo.commit ( " all" , & [ ] ) .unwrap ( ) ;
7335
repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7336
assert! ( repo.status_by_manifest ( ) .unwrap ( ) .is_some ( ) ) ;
7337
assert! ( repo
7338
.working_copy_with_base_unless_unchanged ( )
7339
.unwrap ( )
7340
.is_none ( ) ) ;
7341
let ( wc, base, snapshot) = repo.working_copy_with_base ( ) .unwrap ( ) ;
7342
assert_eq! ( render_change ( & wc, & base, & snapshot) , " " ) ;
7343
assert! ( repo.diff_files ( None ) .unwrap ( ) .is_empty ( ) ) ;
77445673 Andy McCall 1 month ago ↶
7344
}
7345
1ec3f008 Andy McCall 1 month ago ↶
7346
7347
7348
7349
7350
# [ test ]
7351
fn the_manifest_check_skips_listing_while_no_directory_changed ( ) {
7352
hermetic ( ) ;
7353
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7354
let root = dir.path ( ) ;
7355
let repo = Repo:: init( root) .unwrap ( ) ;
7356
write ( root, " a.txt" , b " a\n " ) ;
7357
write ( root, " sub/deep/b.txt" , b " b\n " ) ;
7358
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7359
7360
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 60 ) ;
7361
for p in [ " a.txt" , " sub/deep/b.txt" , " sub/deep" , " sub" , " " ] {
7362
fs:: File:: open( root.join ( p) )
7363
.unwrap ( )
7364
.set_modified ( old)
7365
.unwrap ( ) ;
7366
}
7367
repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7368
repo.write_manifest ( false , false ) .unwrap ( ) ;
7369
let m = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) .2 ;
7370
assert! ( m.dirs.iter ( ) .any ( | ( d , _) | d = = " sub/deep" ) , " {:?}" , m.dirs) ;
7371
assert_eq! (
7372
repo.disk_differs_by_dirs ( & m) ,
7373
Some ( false ) ,
7374
" no listing needed"
7375
) ;
7376
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , Some ( vec! [ ] ) ) ;
7377
7378
7379
write ( root, " sub/deep/b.txt" , b " c\n " ) ;
7380
assert_eq! ( repo.disk_differs_by_dirs ( & m) , Some ( true ) ) ;
7381
write ( root, " sub/deep/b.txt" , b " b\n " ) ;
7382
fs:: File:: open( root.join ( " sub/deep/b.txt" ) )
7383
.unwrap ( )
7384
.set_modified ( old)
7385
.unwrap ( ) ;
7386
assert_eq! ( repo.disk_differs_by_dirs ( & m) , Some ( false ) ) ;
7387
7388
7389
write ( root, " sub/deep/new.txt" , b " n\n " ) ;
7390
assert_eq! ( repo.disk_differs_by_dirs ( & m) , None ) ;
7391
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None ) ;
7392
fs:: remove_file( root.join ( " sub/deep/new.txt" ) ) .unwrap ( ) ;
7393
7394
fs:: remove_file( root.join ( " a.txt" ) ) .unwrap ( ) ;
7395
assert_eq! ( repo.disk_differs_by_dirs ( & m) , None ) ;
7396
}
7397
e226a6cc Andy McCall 1 month ago ↶
7398
7399
7400
7401
# [ test ]
7402
fn a_commit_after_a_status_uses_the_cached_snapshot ( ) {
7403
hermetic ( ) ;
7404
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7405
let root = dir.path ( ) ;
7406
let repo = Repo:: init( root) .unwrap ( ) ;
7407
write ( root, " a.txt" , b " a\n b\n " ) ;
7408
write ( root, " .oxyattributes" , b " *.bin binary\n " ) ;
7409
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7410
write ( root, " a.txt" , b " a\n B\n " ) ;
7411
write ( root, " new.txt" , b " new\n " ) ;
7412
let snap = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7413
let cached = repo
7414
.snapshot_from_cache ( )
7415
.unwrap ( )
7416
.expect ( " the cache holds" ) ;
7417
assert_eq! ( cached.wc_hash, snap.wc_hash) ;
7418
assert_eq! ( cached.statuses, snap.statuses) ;
7419
assert! (
7420
! cached.attributes.is_empty ( ) ,
7421
" the .oxyattributes file was read"
7422
) ;
7423
let h = repo.commit ( " edit" , & [ ] ) .unwrap ( ) ;
7424
let c = repo.store ( ) .get_change ( & h) .unwrap ( ) ;
7425
assert_eq! (
7426
c.tree_ops.len ( ) + c.edits.len ( ) ,
7427
snap.wc.tree_ops.len ( ) + snap.wc.edits.len ( )
7428
) ;
7429
assert_eq! (
7430
repo.status_by_manifest ( ) .unwrap ( ) ,
7431
Some ( vec! [ ] ) ,
7432
" clean after the commit"
7433
) ;
7434
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " a\n B\n " ) ;
7435
}
7436
0389c3e8 Andy McCall 1 month ago ↶
7437
7438
7439
7440
7441
# [ test ]
7442
fn a_verified_clean_check_restamps_the_manifest_safely ( ) {
7443
hermetic ( ) ;
7444
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7445
let root = dir.path ( ) ;
7446
let repo = Repo:: init( root) .unwrap ( ) ;
7447
write ( root, " a.txt" , b " one\n " ) ;
7448
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
7449
let before = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) .2. written_ns;
7450
assert! ( repo.clean_by_manifest ( ) .unwrap ( ) ) ;
7451
let after = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) .2. written_ns;
7452
assert! ( after > before, " restamped" ) ;
7453
write ( root, " a.txt" , b " two\n " ) ;
7454
assert! (
7455
! repo.clean_by_manifest ( ) .unwrap ( ) ,
7456
" a same-size rewrite is still seen"
7457
) ;
7458
}
7459
86e1824f Andy McCall 1 month ago ↶
7460
7461
7462
7463
# [ test ]
7464
fn history_only_states_read_text_only_for_the_graphs_they_build ( ) {
7465
hermetic ( ) ;
7466
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7467
let root = dir.path ( ) ;
7468
let repo = Repo:: init( root) .unwrap ( ) ;
7469
let big: String = ( 0 .. 3000 ) .map ( | i | format! ( " big line {i} \n " ) ) .collect ( ) ;
7470
let other: String = ( 0 .. 3000 ) .map ( | i | format! ( " other line {i} \n " ) ) .collect ( ) ;
7471
write ( root, " big.txt" , big.as_bytes ( ) ) ;
7472
write ( root, " other.txt" , other.as_bytes ( ) ) ;
7473
let h = repo.commit ( " two large files" , & [ ] ) .unwrap ( ) ;
7474
write ( root, " big.txt" , format! ( " {big} one more\n " ) .as_bytes ( ) ) ;
7475
let h2 = repo.commit ( " append" , & [ ] ) .unwrap ( ) ;
7476
7477
let full = repo.load_state_full ( & [ h2] ) .unwrap ( ) .materialize ( ) ;
7478
let text = | snap : & crate:: state:: Snapshot, p : & str | match & snap.files[ p] .content {
7479
FileContent:: Text { bytes, .. } => bytes.clone ( ) ,
7480
other => panic! ( " {other:?}" ) ,
7481
} ;
7482
7483
let mut hist = repo.load_state_history ( & [ h2] ) .unwrap ( ) ;
7484
let big_node = full.files[ " big.txt" ] .node;
7485
assert! ( hist.graph_key ( & big_node) .is_some ( ) , " not built yet" ) ;
7486
repo.load_all_graphs ( & mut hist) .unwrap ( ) ;
7487
let rendered = hist.materialize ( ) ;
7488
for p in [ " big.txt" , " other.txt" ] {
7489
assert_eq! ( text ( & rendered, p) , text ( & full, p) , " {p}" ) ;
7490
}
7491
7492
7493
7494
let ( _ , texts) = repo.store ( ) .get_change_lazy ( & h) .unwrap ( ) ;
7495
assert_eq! ( texts.len ( ) , 2 ) ;
7496
let content = | r : & ObjectRef| -> Vec< u8 > {
7497
let blob = crate :: objects:: Blob:: decode( & repo.store ( ) .get ( r) .unwrap ( ) .1 ) .unwrap ( ) ;
7498
blob.chunks
7499
.iter ( )
7500
.flat_map ( | c | repo.store ( ) .get ( c) .unwrap ( ) .1 )
7501
.collect ( )
7502
} ;
7503
let other_text = texts
7504
.iter ( )
a4fad5b0 Andy McCall 1 month ago ↶
7505
.filter_map ( | t | match t {
0f93f49c Andy McCall 1 month ago ↶
7506
crate :: objects:: TextRef:: Blob( r, _ , _ ) => Some ( * r) ,
a4fad5b0 Andy McCall 1 month ago ↶
7507
_ => None ,
7508
} )
86e1824f Andy McCall 1 month ago ↶
7509
.find ( | r | content ( r) .windows ( 12 ) .any ( | w | w = = b " other line 1" ) )
7510
.unwrap ( ) ;
64818938 Andy McCall 1 month ago ↶
7511
repo.store ( ) .remove_loose_for_test ( & other_text) .unwrap ( ) ;
86e1824f Andy McCall 1 month ago ↶
7512
let mut hist = repo.load_state_history ( & [ h2] ) .unwrap ( ) ;
7513
repo.load_graphs_of ( & mut hist, & [ big_node] ) .unwrap ( ) ;
7514
assert! ( repo
7515
.load_graphs_of ( & mut hist, & [ full.files[ " other.txt" ] .node] )
7516
.is_err ( ) ) ;
7517
}
7518
7519
7520
7521
7522
# [ test ]
7523
fn a_commit_leaves_a_manifest_the_next_status_trusts ( ) {
7524
use std:: os:: unix:: fs:: PermissionsExt;
7525
hermetic ( ) ;
7526
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7527
let root = dir.path ( ) ;
7528
let repo = Repo:: init( root) .unwrap ( ) ;
7529
let files = [ " a.txt" , " b.txt" , " d/c.txt" ] ;
7530
for p in files {
7531
write ( root, p, format! ( " {p} \n " ) .as_bytes ( ) ) ;
7532
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
7533
fs:: File:: options( )
7534
.write ( true )
7535
.open ( root.join ( p) )
7536
.unwrap ( )
7537
.set_modified ( old)
7538
.unwrap ( ) ;
7539
}
7540
repo.commit ( " added" , & [ ] ) .unwrap ( ) ;
7541
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
7542
assert! (
7543
m.entries.values ( ) .all ( | e | e.graph.is_some ( ) ) ,
7544
" keys recorded at commit"
7545
) ;
7546
7547
for p in files {
7548
fs:: set_permissions( root.join ( p) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
7549
}
7550
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
7551
for p in files {
7552
fs:: set_permissions( root.join ( p) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
7553
}
7554
assert! ( ! snap.changed & & snap.statuses.is_empty ( ) ) ;
7555
assert_eq! ( snap.base_snapshot.unrendered.len ( ) , files.len ( ) ) ;
7556
}
c5ce0367 Andy McCall 1 month ago ↶
7557
7558
7559
7560
# [ test ]
7561
fn checkout_touches_only_what_differs_and_renders_in_batches ( ) {
7562
use std:: os:: unix:: fs:: PermissionsExt;
7563
hermetic ( ) ;
7564
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7565
let root = dir.path ( ) ;
7566
let origin = root.join ( " origin" ) ;
7567
let mut repo = Repo:: init( & origin) .unwrap ( ) ;
7568
7569
let n = 2100 ;
7570
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
7571
for i in 0 .. n {
7572
let p = format! ( " f/{i:04} .txt" ) ;
7573
write ( & origin, & p, format! ( " file {i} \n " ) .as_bytes ( ) ) ;
7574
fs:: File:: options( )
7575
.write ( true )
7576
.open ( origin.join ( & p) )
7577
.unwrap ( )
7578
.set_modified ( old)
7579
.unwrap ( ) ;
7580
}
7581
repo.commit ( " many" , & [ ] ) .unwrap ( ) ;
7582
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
7583
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
7584
write ( & origin, " f/0007.txt" , b " changed\n " ) ;
7585
repo.commit ( " one change" , & [ ] ) .unwrap ( ) ;
7586
7587
7588
for i in 0 .. n {
7589
if i ! = 7 {
7590
let p = origin.join ( format! ( " f/{i:04} .txt" ) ) ;
7591
fs:: set_permissions( & p, fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
7592
}
7593
}
7594
let r = repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
7595
for i in 0 .. n {
7596
let p = origin.join ( format! ( " f/{i:04} .txt" ) ) ;
7597
fs:: set_permissions( & p, fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
7598
}
7599
assert_eq! ( ( r.written, r.removed) , ( 1 , 0 ) , " {r:?}" ) ;
7600
assert_eq! ( fs:: read( origin.join ( " f/0007.txt" ) ) .unwrap ( ) , b " file 7\n " ) ;
7601
assert! ( repo.snapshot_status ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
7602
7603
7604
let c = root.join ( " clone" ) ;
7605
Repo:: clone( & origin.to_string_lossy ( ) , & c) .unwrap ( ) ;
7606
for i in [ 0 , 2047 , 2048 , n - 1 ] {
7607
assert_eq! (
7608
fs:: read( c.join ( format! ( " f/{i:04} .txt" ) ) ) .unwrap ( ) ,
7609
format! ( " file {i} \n " ) .as_bytes ( )
7610
) ;
7611
}
7612
let cloned = Repo:: open( & c) .unwrap ( ) ;
7613
assert! ( cloned
7614
.snapshot_status ( " status" )
7615
.unwrap ( )
7616
.statuses
7617
.is_empty ( ) ) ;
d3cd7ef9 Andy McCall 1 month ago ↶
7618
}
7619
7620
7621
7622
7623
# [ test ]
7624
fn a_racy_manifest_entry_is_checked_by_hash ( ) {
7625
hermetic ( ) ;
7626
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7627
let root = dir.path ( ) ;
7628
let repo = Repo:: init( root) .unwrap ( ) ;
7629
write ( root, " a.txt" , b " one\n " ) ;
7630
repo.commit ( " fresh" , & [ ] ) .unwrap ( ) ;
7631
assert! ( repo.clean_by_manifest ( ) .unwrap ( ) , " fresh but equal" ) ;
7632
let t = fs:: metadata( root.join ( " a.txt" ) )
7633
.unwrap ( )
7634
.modified ( )
7635
.unwrap ( ) ;
7636
write ( root, " a.txt" , b " two\n " ) ;
7637
fs:: File:: options( )
7638
.write ( true )
7639
.open ( root.join ( " a.txt" ) )
7640
.unwrap ( )
7641
.set_modified ( t)
7642
.unwrap ( ) ;
7643
assert! (
7644
! repo.clean_by_manifest ( ) .unwrap ( ) ,
7645
" same size and time, other bytes"
7646
) ;
7647
assert_eq! (
7648
statuses ( & repo.snapshot_status ( " status" ) .unwrap ( ) ) ,
7649
vec! [ ( " a.txt" .to_string ( ) , ChangeStatus:: Modified) ]
7650
) ;
c5ce0367 Andy McCall 1 month ago ↶
7651
}
2c75672f Andy McCall 1 month ago ↶
7652
7653
7654
7655
# [ test ]
7656
fn a_raw_bundle_without_a_changes_text_is_refused ( ) {
7657
hermetic ( ) ;
7658
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7659
let root = dir.path ( ) ;
7660
let a = Repo:: init( & root.join ( " a" ) ) .unwrap ( ) ;
7661
let big: String = ( 0 .. 3000 ) .map ( | i | format! ( " line {i} \n " ) ) .collect ( ) ;
7662
write ( & root.join ( " a" ) , " big.txt" , big.as_bytes ( ) ) ;
7663
let h = a.commit ( " big" , & [ ] ) .unwrap ( ) ;
7664
let bundle = a.bundle_raw_for ( & [ h] , None ) .unwrap ( ) ;
7665
assert_eq! ( bundle.raw_changes.len ( ) , 1 ) ;
7666
assert! ( bundle.changes.is_empty ( ) ) ;
7667
let b = Repo:: init( & root.join ( " b" ) ) .unwrap ( ) ;
7668
let mut without = bundle.clone ( ) ;
7669
without.objects.retain ( | ( ty , _) | * ty ! = ObjectType:: Blob) ;
7670
let e = b.import_bundle ( & without, false ) .unwrap_err ( ) ;
7671
assert! ( e.to_string ( ) .contains ( " came without its text blob" ) , " {e}" ) ;
7672
assert! ( ! b.store ( ) .has ( & h) ) ;
7673
assert_eq! ( b.import_bundle ( & bundle, false ) .unwrap ( ) , vec! [ h] ) ;
7674
assert_eq! ( b.store ( ) .get_change ( & h) .unwrap ( ) .message, " big" ) ;
7675
}
a87c7b9d Andy McCall 1 month ago ↶
7676
7677
7678
7679
7680
7681
# [ test ]
7682
fn git_import_brings_a_directory_back_rather_than_making_a_second ( ) {
7683
hermetic ( ) ;
7684
if std:: process:: Command:: new( " git" )
7685
.arg ( " --version" )
7686
.output ( )
7687
.is_err ( )
7688
{
7689
eprintln! ( " git not installed; skipping" ) ;
7690
return ;
7691
}
7692
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7693
let root = dir.path ( ) ;
7694
let g = root.join ( " src.git" ) ;
7695
fs:: create_dir_all( & g) .unwrap ( ) ;
7696
let git = | args : & [& str ]| -> String {
7697
let out = std:: process:: Command:: new( " git" )
7698
.arg ( " -C" )
7699
.arg ( & g)
7700
.args ( [
7701
" -c" ,
7702
" user.name=Grace" ,
7703
" -c" ,
7704
" [email protected] " ,
7705
" -c" ,
7706
" commit.gpgsign=false" ,
7707
] )
7708
.args ( args)
7709
.output ( )
7710
.unwrap ( ) ;
7711
assert! (
7712
out.status.success ( ) ,
7713
" git {args:?}: {}" ,
7714
String :: from_utf8_lossy( & out.stderr)
7715
) ;
7716
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
7717
} ;
7718
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
7719
write ( & g, " d/a.txt" , b " a\n " ) ;
7720
write ( & g, " c/x.txt" , b " x\n " ) ;
7721
git ( & [ " add" , " ." ] ) ;
7722
git ( & [ " commit" , " -qm" , " base" ] ) ;
7723
7724
git ( & [ " checkout" , " -qb" , " side" ] ) ;
7725
git ( & [ " mv" , " c/x.txt" , " d/x.txt" ] ) ;
7726
git ( & [ " commit" , " -qm" , " x into d" ] ) ;
7727
7728
git ( & [ " checkout" , " -q" , " main" ] ) ;
7729
git ( & [ " rm" , " -q" , " d/a.txt" ] ) ;
7730
git ( & [ " commit" , " -qm" , " d goes" ] ) ;
7731
write ( & g, " d/b.txt" , b " b\n " ) ;
7732
git ( & [ " add" , " ." ] ) ;
7733
git ( & [ " commit" , " -qm" , " d comes back" ] ) ;
7734
git ( & [ " mv" , " c/x.txt" , " d/x.txt" ] ) ;
7735
git ( & [ " commit" , " -qm" , " x into d again" ] ) ;
7736
git ( & [ " merge" , " -q" , " --no-edit" , " side" ] ) ;
7737
7738
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
7739
repo.import_git ( & g, " main" ) .unwrap ( ) ;
7740
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
7741
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
7742
let st = repo
7743
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
7744
.unwrap ( ) ;
7745
let view = st.tree ( ) .view ( ) ;
7746
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
7747
7748
7749
let first = repo
7750
.log ( )
7751
.unwrap ( )
7752
.into_iter ( )
7753
.find ( | e | e.change.message.starts_with ( " base" ) )
7754
.unwrap ( ) ;
7755
let base = repo
7756
.load_state_history ( & [ first.hash] )
7757
.unwrap ( )
7758
.tree ( )
7759
.view ( ) ;
7760
assert_eq! ( view.entries[ " d" ] .node, base.entries[ " d" ] .node) ;
7761
}
7762
7763
7764
7765
7766
# [ test ]
7767
fn git_import_merge_retires_a_placement_under_a_removed_directory ( ) {
7768
hermetic ( ) ;
7769
if std:: process:: Command:: new( " git" )
7770
.arg ( " --version" )
7771
.output ( )
7772
.is_err ( )
7773
{
7774
eprintln! ( " git not installed; skipping" ) ;
7775
return ;
7776
}
7777
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7778
let root = dir.path ( ) ;
7779
let g = root.join ( " src.git" ) ;
7780
fs:: create_dir_all( & g) .unwrap ( ) ;
7781
let run = | args : & [& str ]| {
7782
std:: process:: Command:: new( " git" )
7783
.arg ( " -C" )
7784
.arg ( & g)
7785
.args ( [
7786
" -c" ,
7787
" user.name=Grace" ,
7788
" -c" ,
7789
" [email protected] " ,
7790
" -c" ,
7791
" commit.gpgsign=false" ,
7792
] )
7793
.args ( args)
7794
.output ( )
7795
.unwrap ( )
7796
} ;
7797
let git = | args : & [& str ]| {
7798
let out = run ( args) ;
7799
assert! (
7800
out.status.success ( ) ,
7801
" git {args:?}: {}" ,
7802
String :: from_utf8_lossy( & out.stderr)
7803
) ;
7804
} ;
7805
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
7806
write ( & g, " c/x.txt" , b " x\n " ) ;
7807
write ( & g, " keep.txt" , b " k\n " ) ;
7808
git ( & [ " add" , " ." ] ) ;
7809
git ( & [ " commit" , " -qm" , " base" ] ) ;
7810
7811
7812
git ( & [ " checkout" , " -qb" , " side" ] ) ;
7813
git ( & [ " mv" , " c/x.txt" , " c/y.txt" ] ) ;
7814
git ( & [ " commit" , " -qm" , " rename within c" ] ) ;
7815
git ( & [ " checkout" , " -q" , " main" ] ) ;
7816
fs:: create_dir_all( g.join ( " n" ) ) .unwrap ( ) ;
7817
git ( & [ " mv" , " c/x.txt" , " n/x.txt" ] ) ;
7818
git ( & [ " commit" , " -qm" , " move out of c" ] ) ;
7819
7820
let _ = run ( & [ " merge" , " -q" , " side" ] ) ;
7821
let _ = run ( & [
7822
" rm" ,
7823
" -q" ,
7824
" --cached" ,
7825
" --ignore-unmatch" ,
7826
" c/y.txt" ,
7827
" c/x.txt" ,
7828
] ) ;
7829
let _ = std:: fs:: remove_dir_all( g.join ( " c" ) ) ;
7830
write ( & g, " n/x.txt" , b " x\n " ) ;
7831
git ( & [ " add" , " -A" ] ) ;
7832
git ( & [ " commit" , " -qm" , " merge: keep n/x.txt" ] ) ;
7833
7834
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
7835
repo.import_git ( & g, " main" ) .unwrap ( ) ;
7836
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
7837
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
7838
let st = repo
7839
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
7840
.unwrap ( ) ;
7841
let view = st.tree ( ) .view ( ) ;
7842
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
7843
assert! ( view.entries.contains_key ( " n/x.txt" ) ) ;
7844
}
7845
7846
7847
7848
7849
# [ test ]
7850
fn git_import_merge_leaves_no_orphan_behind ( ) {
7851
hermetic ( ) ;
7852
if std:: process:: Command:: new( " git" )
7853
.arg ( " --version" )
7854
.output ( )
7855
.is_err ( )
7856
{
7857
eprintln! ( " git not installed; skipping" ) ;
7858
return ;
7859
}
7860
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7861
let root = dir.path ( ) ;
7862
let g = root.join ( " src.git" ) ;
7863
fs:: create_dir_all( & g) .unwrap ( ) ;
7864
let git = | args : & [& str ]| {
7865
let out = std:: process:: Command:: new( " git" )
7866
.arg ( " -C" )
7867
.arg ( & g)
7868
.args ( [
7869
" -c" ,
7870
" user.name=Grace" ,
7871
" -c" ,
7872
" [email protected] " ,
7873
" -c" ,
7874
" commit.gpgsign=false" ,
7875
] )
7876
.args ( args)
7877
.output ( )
7878
.unwrap ( ) ;
7879
assert! (
7880
out.status.success ( ) ,
7881
" git {args:?}: {}" ,
7882
String :: from_utf8_lossy( & out.stderr)
7883
) ;
7884
} ;
7885
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
7886
write ( & g, " d/f.txt" , b " f\n " ) ;
7887
write ( & g, " keep.txt" , b " k\n " ) ;
7888
git ( & [ " add" , " ." ] ) ;
7889
git ( & [ " commit" , " -qm" , " base" ] ) ;
7890
git ( & [ " checkout" , " -qb" , " side" ] ) ;
7891
write ( & g, " d/new.txt" , b " new\n " ) ;
7892
git ( & [ " add" , " ." ] ) ;
7893
git ( & [ " commit" , " -qm" , " add into d" ] ) ;
7894
git ( & [ " checkout" , " -q" , " main" ] ) ;
7895
git ( & [ " rm" , " -q" , " d/f.txt" ] ) ;
7896
git ( & [ " commit" , " -qm" , " d goes" ] ) ;
7897
git ( & [ " merge" , " -q" , " --no-edit" , " side" ] ) ;
7898
7899
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
7900
repo.import_git ( & g, " main" ) .unwrap ( ) ;
7901
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
7902
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
7903
let st = repo
7904
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
7905
.unwrap ( ) ;
7906
let view = st.tree ( ) .view ( ) ;
7907
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
7908
assert! ( view.entries.contains_key ( " d/new.txt" ) ) ;
7909
}
d77e1702 Andy McCall 1 month ago ↶
7910
7911
7912
7913
# [ test ]
7914
fn fsck_names_a_missing_text_blob ( ) {
7915
hermetic ( ) ;
7916
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7917
let root = dir.path ( ) ;
7918
let repo = Repo:: init( root) .unwrap ( ) ;
7919
let big: String = ( 0 .. 3000 ) .map ( | i | format! ( " line {i} \n " ) ) .collect ( ) ;
7920
write ( root, " big.txt" , big.as_bytes ( ) ) ;
7921
let h = repo.commit ( " big" , & [ ] ) .unwrap ( ) ;
7922
assert_eq! ( repo.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
7923
let ( _ , texts) = repo.store ( ) .get_change_lazy ( & h) .unwrap ( ) ;
7924
let Some ( crate :: objects:: TextRef:: Blob( b, _ , _ ) ) = texts.first ( ) .copied ( ) else {
7925
panic! ( " a stored text" )
7926
} ;
64818938 Andy McCall 1 month ago ↶
7927
repo.store ( ) .remove_loose_for_test ( & b) .unwrap ( ) ;
d77e1702 Andy McCall 1 month ago ↶
7928
let rep = repo.fsck ( ) .unwrap ( ) ;
7929
assert! ( rep.errors ( ) > 0 ) ;
7930
assert! (
7931
rep.problems.iter ( ) .any ( | p | p.what.contains ( & h.short ( 12 ) ) ) ,
7932
" {:?}" ,
7933
rep.problems
7934
) ;
7935
}
a07f60a3 Andy McCall 1 month ago ↶
7936
7937
7938
7939
7940
# [ test ]
7941
fn a_subrepo_on_the_parents_hub_borrows_its_credentials ( ) {
7942
hermetic ( ) ;
7943
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7944
let mut repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
7945
repo.config
7946
.set (
7947
" remote.origin.url" ,
7948
" https://Hub.example:7343/doomsdayone/metalgear" ,
7949
)
7950
.unwrap ( ) ;
7951
repo.config.set ( " remote.origin.token" , " secret" ) .unwrap ( ) ;
7952
repo.config
7953
.set ( " remote.origin.ca" , " /tmp/hub-ca.pem" )
7954
.unwrap ( ) ;
7955
let same = repo.credentials_for ( " https://hub.example:7343/doomsdayone/rstar-api" ) ;
7956
assert_eq! ( same.token.as_deref ( ) , Some ( " secret" ) ) ;
7957
assert_eq! ( same.ca.as_deref ( ) , Some ( Path:: new( " /tmp/hub-ca.pem" ) ) ) ;
7958
for other in [
7959
" https://elsewhere.example:7343/doomsdayone/rstar-api" ,
7960
" https://hub.example/doomsdayone/rstar-api" ,
7961
" http://hub.example:7343/doomsdayone/rstar-api" ,
7962
] {
7963
let o = repo.credentials_for ( other) ;
7964
assert_ne! ( o.token.as_deref ( ) , Some ( " secret" ) , " {other}" ) ;
7965
}
7966
}
ce14f0d6 Andy McCall 1 month ago ↶
7967
ff1f62fe Andy McCall 1 month ago ↶
7968
7969
7970
ce14f0d6 Andy McCall 1 month ago ↶
7971
# [ test ]
ff1f62fe Andy McCall 1 month ago ↶
7972
fn remotes_use_the_credential_store_and_the_enclosing_checkout ( ) {
7973
hermetic ( ) ;
7974
let home = tempfile:: tempdir( ) .unwrap ( ) ;
7975
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7976
let mut parent = Repo:: init( dir.path ( ) ) .unwrap ( ) .with_home ( home.path ( ) ) ;
7977
parent
7978
.config
7979
.set ( " remote.origin.url" , " https://hub.example/o/parent" )
7980
.unwrap ( ) ;
7981
parent.save_config ( ) .unwrap ( ) ;
7982
assert_eq! ( parent.remote_options ( " origin" ) .token, None ) ;
7983
crate :: credentials:: Credentials:: at( home.path ( ) )
7984
.save ( " https://hub.example" , " stored" , Some ( " andy" ) , None )
7985
.unwrap ( ) ;
7986
assert_eq! (
7987
parent.remote_options ( " origin" ) .token.as_deref ( ) ,
7988
Some ( " stored" )
7989
) ;
7990
7991
let empty_home = tempfile:: tempdir( ) .unwrap ( ) ;
7992
let nested = dir.path ( ) .join ( " deps" ) .join ( " child" ) ;
7993
let mut child = Repo:: init( & nested) .unwrap ( ) .with_home ( empty_home.path ( ) ) ;
7994
child
7995
.config
7996
.set ( " remote.origin.url" , " https://hub.example/o/child" )
7997
.unwrap ( ) ;
7998
child.save_config ( ) .unwrap ( ) ;
7999
assert_eq! ( child.remote_options ( " origin" ) .token, None ) ;
8000
parent
8001
.config
8002
.set ( " remote.origin.token" , " checkout" )
8003
.unwrap ( ) ;
8004
parent.save_config ( ) .unwrap ( ) ;
8005
assert_eq! (
8006
child.remote_options ( " origin" ) .token.as_deref ( ) ,
8007
Some ( " checkout" )
8008
) ;
8009
8010
child
8011
.config
8012
.set ( " remote.origin.url" , " https://other.example/o/child" )
8013
.unwrap ( ) ;
8014
assert_eq! ( child.remote_options ( " origin" ) .token, None ) ;
8015
}
8016
8017
# [ test ]
ce14f0d6 Andy McCall 1 month ago ↶
8018
fn the_metadata_cache_survives_a_torn_tail ( ) {
8019
hermetic ( ) ;
8020
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8021
let root = dir.path ( ) ;
8022
let repo = Repo:: init( root) .unwrap ( ) ;
8023
write ( root, " a.txt" , b " 1\n " ) ;
8024
let h = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8025
let meta = repo.store ( ) .get_change_meta ( & h) .unwrap ( ) ;
8026
let cache = repo.store ( ) .dir ( ) .join ( " metas" ) ;
8027
let len = fs:: metadata( & cache) .unwrap ( ) .len ( ) ;
8028
assert! ( len > 0 , " a change read is cached" ) ;
8029
8030
fs:: OpenOptions:: new( )
8031
.append ( true )
8032
.open ( & cache)
8033
.unwrap ( )
8034
.write_all ( & [ 7 ; 20 ] )
8035
.unwrap ( ) ;
8036
let again = Repo:: open( root) .unwrap ( ) ;
8037
assert_eq! ( again.store ( ) .get_change_meta ( & h) .unwrap ( ) , meta) ;
8038
assert_eq! ( fs:: metadata( & cache) .unwrap ( ) .len ( ) , len) ;
8039
}
f9466021 Andy McCall 1 month ago ↶
8040
8041
# [ test ]
8042
fn a_manifest_that_does_not_check_reads_as_none ( ) {
8043
hermetic ( ) ;
8044
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8045
let root = dir.path ( ) ;
8046
let repo = Repo:: init( root) .unwrap ( ) ;
8047
write ( root, " a.txt" , b " 1\n " ) ;
8048
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8049
let path = repo.manifest_path ( ) ;
8050
let mut bytes = fs:: read( & path) .unwrap ( ) ;
8051
assert! ( Repo:: open( root) .unwrap ( ) .read_manifest ( ) .unwrap ( ) .is_some ( ) ) ;
8052
let last = bytes.len ( ) - 1 ;
8053
bytes[ last] ^ = 1 ;
8054
fs:: write( & path, & bytes) .unwrap ( ) ;
8055
assert! ( Repo:: open( root) .unwrap ( ) .read_manifest ( ) .unwrap ( ) .is_none ( ) ) ;
8056
8057
let again = Repo:: open( root) .unwrap ( ) ;
8058
assert! ( ! again.snapshot ( " status" ) .unwrap ( ) .changed) ;
8059
assert! ( again.read_manifest ( ) .unwrap ( ) .is_some ( ) ) ;
8060
}
8061
8062
# [ test ]
8063
fn a_commit_leaves_a_manifest_that_proves_the_working_copy_clean ( ) {
8064
hermetic ( ) ;
8065
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8066
let root = dir.path ( ) ;
8067
let repo = Repo:: init( root) .unwrap ( ) ;
8068
write ( root, " a.txt" , b " 1\n " ) ;
8069
write ( root, " b.txt" , b " 2\n " ) ;
8070
8071
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 60 ) ;
8072
fs:: File:: options( )
8073
.write ( true )
8074
.open ( root.join ( " b.txt" ) )
8075
.unwrap ( )
8076
.set_modified ( old)
8077
.unwrap ( ) ;
8078
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8079
assert! ( repo.clean_by_manifest ( ) .unwrap ( ) , " after the first commit" ) ;
8080
repo.snapshot_status ( " status" ) .unwrap ( ) ;
8081
write ( root, " a.txt" , b " 1\n 2\n " ) ;
8082
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
8083
assert! (
8084
repo.clean_by_manifest ( ) .unwrap ( ) ,
8085
" after a commit of one file"
8086
) ;
8087
8088
8089
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
8090
repo.snapshot_status ( " status" ) .unwrap ( ) ;
8091
write ( root, " a.txt" , b " 1\n 2\n 3\n 4\n 5\n " ) ;
8092
repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
8093
assert! (
8094
repo.clean_by_manifest ( ) .unwrap ( ) ,
8095
" after an edit a status saw"
8096
) ;
8097
}
e266b7d4 Andy McCall 1 month ago ↶
8098
8099
# [ test ]
8100
fn a_conflicted_rebase_can_be_resolved_in_the_working_copy ( ) {
8101
hermetic ( ) ;
8102
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8103
let root = dir.path ( ) ;
8104
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8105
write ( root, " a.txt" , b " 1\n 2\n " ) ;
8106
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
8107
repo.bookmark_create ( " develop" , Some ( & [ a] ) ) .unwrap ( ) ;
8108
write ( root, " a.txt" , b " 1\n X\n " ) ;
8109
let c = repo.commit ( " C: 2 to X" , & [ ] ) .unwrap ( ) ;
8110
repo.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
8111
write ( root, " a.txt" , b " 1\n Y\n " ) ;
8112
let d = repo.commit ( " D: 2 to Y" , & [ ] ) .unwrap ( ) ;
8113
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
8114
8115
let err = repo.rebase ( & c, & [ d] ) .unwrap_err ( ) ;
8116
assert! ( matches! ( err, RepoError:: Conflicted( _ ) ) , " {err}" ) ;
8117
let r = repo.rebase_into_working_copy ( & c, & [ d] ) .unwrap ( ) ;
8118
assert! ( r.changed & & r.checked_out) ;
8119
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8120
assert! (
8121
snap.base_snapshot.is_conflicted ( ) ,
8122
" the conflict is in the working copy"
8123
) ;
8124
let text = fs:: read_to_string( root.join ( " a.txt" ) ) .unwrap ( ) ;
8125
assert! ( text.contains ( ' X' ) & & text.contains ( ' Y' ) , " {text}" ) ;
8126
8127
write ( root, " a.txt" , b " 1\n XY\n " ) ;
8128
repo.commit ( " Resolve" , & [ ] ) .unwrap ( ) ;
8129
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
8130
assert! ( st.contains ( & c) & & st.contains ( & d) ) ;
8131
let files = st.materialize ( ) .files;
8132
assert! ( ! files[ " a.txt" ] .is_conflicted ( ) ) ;
8133
assert! (
8134
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 1\n XY\n " )
8135
) ;
8136
8137
8138
repo.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
8139
write ( root, " b.txt" , b " b\n " ) ;
8140
let e = repo.commit ( " E" , & [ ] ) .unwrap ( ) ;
8141
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
8142
let err = repo.rebase_into_working_copy ( & e, & [ a] ) .unwrap_err ( ) ;
8143
assert! (
8144
err.to_string ( ) .contains ( " not on the current bookmark" ) ,
8145
" {err}"
8146
) ;
8147
}
d4dbab97 Andy McCall 1 month ago ↶
8148
8149
# [ test ]
8150
fn an_ignore_rule_drops_an_added_file_but_not_a_tracked_one ( ) {
8151
hermetic ( ) ;
8152
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8153
let root = dir.path ( ) ;
8154
let repo = Repo:: init( root) .unwrap ( ) ;
8155
write ( root, " a.txt" , b " a\n " ) ;
8156
write ( root, " kept.log" , b " tracked\n " ) ;
8157
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8158
write ( root, " build.log" , b " made\n " ) ;
8159
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8160
assert! ( snap.statuses.iter ( ) .any ( | s | s.path = = " build.log" ) ) ;
8161
8162
8163
write ( root, " .gitignore" , b " *.log\n " ) ;
8164
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8165
let paths: Vec< & str > = snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect ( ) ;
8166
assert_eq! ( paths, vec! [ " .gitignore" ] , " {paths:?}" ) ;
8167
write ( root, " kept.log" , b " tracked, edited\n " ) ;
8168
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8169
assert! ( snap.statuses.iter ( ) .any ( | s | s.path = = " kept.log" ) ) ;
8170
}
fd280228 Andy McCall 1 month ago ↶
8171
8172
# [ test ]
8173
fn shed_graphs_come_back_the_same ( ) {
8174
hermetic ( ) ;
8175
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8176
let root = dir.path ( ) ;
8177
let repo = Repo:: init( root) .unwrap ( ) ;
8178
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
8179
write ( root, " b.txt" , b " x\n " ) ;
8180
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8181
write ( root, " a.txt" , b " 1\n two\n 3\n 4\n " ) ;
8182
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
8183
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
8184
let mut st = repo.load_state_history ( & heads) .unwrap ( ) ;
8185
let view = st.tree ( ) .view ( ) ;
8186
let nodes: Vec< NodeId> = [ " a.txt" , " b.txt" ]
8187
.iter ( )
8188
.map ( | p | view.entries[ * p] .node )
8189
.collect ( ) ;
8190
repo.load_graphs_of ( & mut st, & nodes) .unwrap ( ) ;
8191
let before: Vec< Vec< u8 > > = nodes.iter ( ) .map ( | n | st.render_file ( n) .0 ) .collect ( ) ;
8192
assert_eq! ( st.built_count ( ) , 2 ) ;
8193
let shed = st.shed_graphs ( | b | repo.store_blob ( b) ) .unwrap ( ) ;
8194
assert_eq! ( ( shed, st.built_count ( ) ) , ( 2 , 0 ) ) ;
8195
8196
write ( root, " a.txt" , b " 1\n two\n 3\n 4\n 5\n " ) ;
8197
let h3 = repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
8198
let ( skeleton, texts) = repo.store ( ) .get_change_lazy ( & h3) .unwrap ( ) ;
8199
st.add_known ( h3, skeleton, texts) ;
8200
repo.load_graphs_of ( & mut st, & nodes) .unwrap ( ) ;
8201
let after: Vec< Vec< u8 > > = nodes.iter ( ) .map ( | n | st.render_file ( n) .0 ) .collect ( ) ;
8202
assert_eq! ( after[ 1 ] , before[ 1 ] ) ;
8203
assert_eq! ( after[ 0 ] , b " 1\n two\n 3\n 4\n 5\n " ) ;
8204
}
360aa74a Andy McCall 1 month ago ↶
8205
8206
# [ test ]
8207
fn many_edited_files_diff_in_parallel ( ) {
8208
8209
8210
8211
hermetic ( ) ;
8212
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8213
let root = dir.path ( ) ;
8214
let repo = Repo:: init( root) .unwrap ( ) ;
8215
let text = | i : usize , round : usize | {
8216
let mut s = String :: new( ) ;
8217
for line in 0 .. 50 {
8218
if round > 0 & & line % 7 = = i % 7 {
8219
s.push_str ( & format! ( " edited {round} {i} {line} \n " ) ) ;
8220
} else {
8221
s.push_str ( & format! ( " file {i} line {line} \n " ) ) ;
8222
}
8223
}
8224
if round > 0 & & i.is_multiple_of ( 3 ) {
8225
s.push_str ( " appended\n " ) ;
8226
}
8227
s
8228
} ;
8229
for i in 0 .. 40 {
8230
write ( root, & format! ( " d/f{i} .txt" ) , text ( i, 0 ) .as_bytes ( ) ) ;
8231
}
8232
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8233
for i in 0 .. 40 {
8234
write ( root, & format! ( " d/f{i} .txt" ) , text ( i, 1 ) .as_bytes ( ) ) ;
8235
}
8236
let h = repo.commit ( " edit every file" , & [ ] ) .unwrap ( ) ;
8237
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
8238
for i in 0 .. 40 {
8239
let want = text ( i, 1 ) ;
8240
assert! (
8241
matches! ( & snap.files[ & format! ( " d/f{i} .txt" ) ] .content, FileContent:: Text { bytes, .. } if bytes = = want.as_bytes ( ) ) ,
8242
" d/f{i}.txt as committed"
8243
) ;
8244
}
8245
assert! (
8246
repo.commit ( " nothing" , & [ ] ) .is_err ( ) ,
8247
" nothing left to commit"
8248
) ;
8249
}
b35df36b Andy McCall 1 month ago ↶
8250
8251
8252
8253
8254
8255
# [ test ]
8256
fn a_fetched_change_shows_as_behind_and_resolves ( ) {
8257
hermetic ( ) ;
8258
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8259
let root = dir.path ( ) ;
8260
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
8261
drop ( origin) ;
8262
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
8263
let a = Repo:: clone( & url, & root.join ( " a" ) )
8264
.unwrap ( )
8265
.with_home ( root.join ( " home-a" ) ) ;
8266
write ( & root.join ( " a" ) , " f.txt" , b " one\n " ) ;
8267
a.commit ( " one" , & [ ] ) .unwrap ( ) ;
8268
a.push ( " origin" , false ) .unwrap ( ) ;
8269
let b = Repo:: clone( & url, & root.join ( " b" ) )
8270
.unwrap ( )
8271
.with_home ( root.join ( " home-b" ) ) ;
8272
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 0 ) ) ) ;
8273
8274
write ( & root.join ( " a" ) , " f.txt" , b " one\n two\n " ) ;
8275
let h2 = a.commit ( " two" , & [ ] ) .unwrap ( ) ;
8276
a.push ( " origin" , false ) .unwrap ( ) ;
8277
b.fetch ( " origin" ) .unwrap ( ) ;
8278
8279
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 1 ) ) ) ;
8280
assert_eq! ( b.tracking ( " origin" , " nope" ) .unwrap ( ) , None ) ;
8281
assert_eq! (
8282
b.resolve_change ( & format! ( " #{} " , & h2.to_hex ( ) [ .. 12 ] ) )
8283
.unwrap ( ) ,
8284
h2
8285
) ;
8286
assert_eq! (
8287
b.remote_only_location ( & h2) .unwrap ( ) .as_deref ( ) ,
8288
Some ( " origin/main" )
8289
) ;
8290
8291
8292
b.fetch ( " origin" ) .unwrap ( ) ;
8293
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 1 ) ) ) ;
8294
b.pull ( " origin" ) .unwrap ( ) ;
8295
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 0 ) ) ) ;
8296
assert_eq! ( b.remote_only_location ( & h2) .unwrap ( ) , None ) ;
8297
8298
8299
let mut c = Repo:: init( & root.join ( " c" ) ) .unwrap ( ) ;
8300
assert_eq! ( c.default_remote ( ) , " origin" ) ;
8301
c.config.set ( " remote.earth.url" , & url) .unwrap ( ) ;
8302
assert_eq! ( c.default_remote ( ) , " earth" ) ;
8303
c.config.set ( " remote.origin.url" , & url) .unwrap ( ) ;
8304
assert_eq! ( c.default_remote ( ) , " origin" ) ;
8305
}
827281ee Andy McCall 1 month ago ↶
8306
8307
8308
8309
# [ test ]
8310
fn a_clone_from_a_relative_path_records_it_absolute ( ) {
8311
hermetic ( ) ;
8312
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8313
let root = dir.path ( ) ;
8314
Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
8315
let rel = crate :: protocol:: stored_url( " relative/repo" ) ;
8316
assert! ( std:: path:: Path:: new( & rel) .is_absolute ( ) , " {rel}" ) ;
8317
assert_eq! (
8318
crate :: protocol:: stored_url( " https://hub.example/o/n" ) ,
8319
" https://hub.example/o/n"
8320
) ;
8321
assert! ( crate :: protocol:: stored_url( " file://x" ) .starts_with ( " file:///" ) ) ;
8d803563 Andy McCall 1 month ago ↶
8322
assert! ( crate :: protocol:: stored_url( " dir://x" ) .starts_with ( " dir:///" ) ) ;
8323
8324
assert_eq! (
8325
crate :: protocol:: stored_url( " s3://bucket/prefix?profile=p" ) ,
8326
" s3://bucket/prefix?profile=p"
8327
) ;
827281ee Andy McCall 1 month ago ↶
8328
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
8329
let b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) ;
8330
assert_eq! ( b.config.remotes[ " origin" ] , url) ;
8331
b.fetch ( " origin" ) .unwrap ( ) ;
8332
}
a46f3e4f Andy McCall 1 month ago ↶
8333
8334
8335
8336
8337
# [ test ]
8338
fn a_workspace_added_on_a_bookmark_is_on_it_with_its_subrepos ( ) {
8339
hermetic ( ) ;
8340
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8341
let root = dir.path ( ) ;
8342
Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
8343
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
8344
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
8345
.unwrap ( )
8346
.with_home ( root.join ( " home" ) ) ;
8347
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
8348
let l1 = lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
8349
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
8350
8351
let app_dir = root.join ( " app" ) ;
8352
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8353
write ( & app_dir, " main.txt" , b " app\n " ) ;
8354
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
8355
app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
8356
8357
app.bookmark_create ( " develop" , None ) .unwrap ( ) ;
8358
app.bookmark_create ( " feature" , None ) .unwrap ( ) ;
8359
let mut app = app;
8360
app.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
8361
assert_eq! ( app.config.bookmark, " feature" ) ;
8362
8363
8364
std:: fs:: rename( root.join ( " lib-origin" ) , root.join ( " lib-origin-gone" ) ) .unwrap ( ) ;
8365
let heads = app.resolve_heads ( " develop" ) .unwrap ( ) ;
8366
let ws = app
8367
.workspace_add_on ( & root.join ( " ws" ) , Some ( & heads) , Some ( " develop" ) )
8368
.unwrap ( ) ;
8369
assert_eq! (
8370
ws.config.bookmark, " develop" ,
8371
" the new workspace is on develop"
8372
) ;
8373
assert_eq! (
8374
app.config.bookmark, " feature" ,
8375
" and this one stays on its own"
8376
) ;
8377
let sub = Repo:: open( & root.join ( " ws/libs/lib" ) ) .unwrap ( ) ;
8378
assert_eq! (
8379
sub.config.remotes[ " origin" ] , lib_url,
8380
" its origin is the subrepo's URL"
8381
) ;
8382
assert_eq! ( sub.bookmark_heads ( ) .unwrap ( ) , vec! [ l1] , " at its pin" ) ;
8383
assert_eq! (
8384
std:: fs:: read( root.join ( " ws/libs/lib/lib.txt" ) ) .unwrap ( ) ,
8385
b " v1\n "
8386
) ;
8387
}
47ce8b8e Andy McCall 1 month ago ↶
8388
8389
8390
8391
8392
# [ test ]
8393
fn revsets_select_changes_by_relation_author_text_date_and_path ( ) {
8394
hermetic ( ) ;
8395
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8396
let root = dir.path ( ) ;
8397
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8398
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
8399
.unwrap ( ) ;
8400
write ( root, " src/a.rs" , b " one\n " ) ;
8401
let a = repo.commit ( " first: add a" , & [ ] ) .unwrap ( ) ;
8402
write ( root, " src/a.rs" , b " one\n two\n " ) ;
8403
let b = repo.commit ( " second: grow a" , & [ ] ) .unwrap ( ) ;
8404
8405
repo.checkout ( & [ a] , false , " checkout a" ) .unwrap ( ) ;
8406
write ( root, " docs/notes.md" , b " notes\n " ) ;
8407
let c = repo.commit ( " third: notes" , & [ ] ) .unwrap ( ) ;
8408
8409
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
8410
.unwrap ( ) ;
8411
fs:: remove_file( root.join ( " src/a.rs" ) ) .unwrap ( ) ;
8412
write ( root, " src/z.rs" , b " one\n two\n " ) ;
8413
let d = repo.commit ( " fourth: rename a to z" , & [ ] ) .unwrap ( ) ;
8414
let wc = repo.working_copy ( ) .unwrap ( ) .0 ;
8415
8416
repo.set_user_config ( " revset.mine" , " author(me) & ::main" )
8417
.unwrap ( ) ;
8418
assert! ( repo.set_user_config ( " revset.bad" , " a &" ) .is_err ( ) ) ;
8419
let set = | src : & str | -> Vec< ChangeHash> {
8420
let mut v = repo.revset ( src) .unwrap ( ) .hashes;
8421
v.sort ( ) ;
8422
v
8423
} ;
8424
let sorted = | hs : & [ChangeHash]| -> Vec< ChangeHash> {
8425
let mut v = hs.to_vec ( ) ;
8426
v.sort ( ) ;
8427
v
8428
} ;
8429
8430
8431
8432
8433
let heads = sorted ( & repo.bookmark_heads ( ) .unwrap ( ) ) ;
8434
let meta = | h : & ChangeHash| repo.store ( ) .get_change_meta ( h) .unwrap ( ) ;
8435
assert! ( heads.contains ( & d) & & heads.contains ( & c) ) ;
8436
8437
assert_eq! ( set ( " main" ) , heads) ;
8438
assert_eq! ( set ( " ::main" ) , sorted ( & [ a, b, c, d] ) ) ;
8439
assert_eq! ( set ( " @" ) , vec! [ wc] ) ;
8440
assert_eq! ( set ( " ::@" ) , sorted ( & [ a, b, c, d, wc] ) ) ;
8441
assert_eq! ( set ( & format! ( " ::#{} " , b.short ( 12 ) ) ) , sorted ( & [ a, b] ) ) ;
8442
assert_eq! ( set ( & format! ( " #{} ::" , a.short ( 12 ) ) ) , sorted ( & [ a, b, d, wc] ) ) ;
8443
assert_eq! (
8444
set ( & format! ( " #{} ::#{} " , a.short ( 12 ) , b.short ( 12 ) ) ) ,
8445
sorted ( & [ a, b] )
8446
) ;
8447
8448
assert_eq! ( set ( & format! ( " #{} ..main" , b.short ( 12 ) ) ) , sorted ( & [ c, d] ) ) ;
8449
assert_eq! ( set ( & format! ( " #{} .." , c.short ( 12 ) ) ) , sorted ( & [ a, b, d, wc] ) ) ;
8450
assert_eq! ( set ( " ..main" ) , sorted ( & [ a, b, c, d] ) ) ;
8451
8452
assert_eq! (
8453
set ( & format! ( " ::main ~ #{} " , d.short ( 12 ) ) ) ,
8454
sorted ( & [ a, b, c] )
8455
) ;
8456
assert_eq! (
8457
set ( & format! ( " ::main & #{} | #{} " , b.short ( 12 ) , c.short ( 12 ) ) ) ,
8458
sorted ( & [ b, c] )
8459
) ;
8460
assert_eq! ( set ( " all() ~ ::main" ) , vec! [ wc] ) ;
8461
8462
let mut head_deps: Vec< ChangeHash> = heads.iter ( ) .flat_map ( | h | meta ( h) .deps) .collect ( ) ;
8463
head_deps.sort ( ) ;
8464
head_deps.dedup ( ) ;
8465
assert_eq! ( set ( " deps(main)" ) , head_deps) ;
8466
let on_a: Vec< ChangeHash> = [ b, c, d]
8467
.into_iter ( )
8468
.filter ( | h | meta ( h) .deps.contains ( & a) )
8469
.collect ( ) ;
8470
assert_eq! ( set ( & format! ( " dependents(#{} )" , a.short ( 12 ) ) ) , sorted ( & on_a) ) ;
8471
assert_eq! ( set ( " heads(::main)" ) , heads) ;
8472
assert_eq! ( set ( " roots(::main)" ) , sorted ( & [ a, c] ) ) ;
8473
assert_eq! (
8474
set ( & format! (
8475
" heads(#{} | #{} | #{} )" ,
8476
a.short ( 12 ) ,
8477
b.short ( 12 ) ,
8478
c.short ( 12 )
8479
) ) ,
8480
sorted ( & [ b, c] )
8481
) ;
8482
assert_eq! ( set ( " latest(::main, 1)" ) , vec! [ d] ) ;
8483
assert_eq! ( set ( " kind(working-copy)" ) , vec! [ wc] ) ;
8484
assert_eq! ( set ( " none()" ) , vec! [ ] ) ;
8485
8486
let key = repo.config.author_key.unwrap ( ) ;
8487
assert_eq! ( set ( " author(ada) & ::main" ) , sorted ( & [ a, b, c, d] ) ) ;
8488
assert_eq! ( set ( " author(\" [email protected] \" ) & ::main" ) .len ( ) , 4 ) ;
8489
assert_eq! ( set ( & format! ( " author({} ) & ::main" , key.short ( 12 ) ) ) .len ( ) , 4 ) ;
8490
assert_eq! ( set ( " author(me) & ::main" ) .len ( ) , 4 ) ;
8491
assert_eq! ( set ( " author(nobody)" ) , vec! [ ] ) ;
8492
assert_eq! ( set ( " description(notes)" ) , vec! [ c] ) ;
8493
assert_eq! ( set ( " description(\" rename a\" )" ) , vec! [ d] ) ;
8494
assert_eq! ( set ( " date(-1h..) & ::main" ) .len ( ) , 4 ) ;
8495
assert_eq! ( set ( " date(..2000-01-01)" ) , vec! [ ] ) ;
8496
8497
assert_eq! ( set ( " touches(\" src/z.rs\" )" ) , sorted ( & [ a, b, d] ) ) ;
8498
assert_eq! ( set ( " touches(\" src/**\" ) & ::main" ) , sorted ( & [ a, b, d] ) ) ;
8499
assert_eq! ( set ( " touches(\" docs/*\" )" ) , vec! [ c] ) ;
8500
assert_eq! ( set ( " touches(\" nothing/*\" )" ) , vec! [ ] ) ;
8501
8502
assert_eq! ( repo.revset ( " ::main" ) .unwrap ( ) .hashes[ 0 ] , d) ;
8503
8504
assert_eq! ( set ( " mine()" ) .len ( ) , 4 ) ;
8505
let e = repo.revset ( " nosuch()" ) .unwrap_err ( ) .to_string ( ) ;
8506
assert! ( e.contains ( " unknown function nosuch()" ) , " {e}" ) ;
8507
let e = repo.revset ( " main &" ) .unwrap_err ( ) .to_string ( ) ;
8508
assert! ( e.contains ( " at character" ) , " {e}" ) ;
8509
let e = repo.revset ( " zzzz" ) .unwrap_err ( ) .to_string ( ) ;
8510
assert! ( e.contains ( " no change matches" ) , " {e}" ) ;
8511
let e = repo
8512
.revset_with_budget ( " ::main" , std:: time:: Duration:: ZERO )
8513
.unwrap_err ( )
8514
.to_string ( ) ;
8515
assert! ( e.contains ( " budget" ) , " {e}" ) ;
8516
8517
let entries = repo.log_revset ( " ::@" ) .unwrap ( ) ;
8518
assert! ( entries[ 0 ] .is_working_copy & & entries.iter ( ) .any ( | e | e.is_head & & e.hash = = d ) ) ;
2c9ddac3 Andy McCall 1 month ago ↶
8519
8520
8521
assert_eq! ( set ( " unsigned() & ::main" ) , sorted ( & [ a, b, c, d] ) ) ;
8522
assert_eq! ( set ( " signed_by(me)" ) , vec! [ ] ) ;
8523
repo.sign_change ( & d) .unwrap ( ) ;
8524
assert_eq! ( set ( " signed_by(me)" ) , vec! [ d] ) ;
8525
assert_eq! ( set ( " signed_by([email protected] )" ) , vec! [ d] ) ;
8526
assert_eq! ( set ( & format! ( " signed_by({} )" , key.short ( 12 ) ) ) , vec! [ d] ) ;
8527
assert_eq! ( set ( " unsigned() & ::main" ) , sorted ( & [ a, b, c] ) ) ;
8528
assert_eq! ( set ( " delegated()" ) , vec! [ ] ) ;
8529
assert_eq! ( set ( " revoked()" ) , vec! [ ] ) ;
8530
assert_eq! ( set ( " attested(tag)" ) , vec! [ ] ) ;
8531
repo.tag_create ( " v1" , & [ b] , " first" ) .unwrap ( ) ;
8532
assert_eq! ( set ( " attested(tag)" ) , sorted ( & [ a, b] ) ) ;
8533
assert_eq! ( set ( " attested(tag) & unsigned()" ) , sorted ( & [ a, b] ) ) ;
8534
assert_eq! ( set ( " attested(receipt)" ) , vec! [ ] ) ;
8535
assert_eq! ( set ( " built(test)" ) , vec! [ ] ) ;
8536
let e = repo.revset ( " attested(seal)" ) .unwrap_err ( ) .to_string ( ) ;
8537
assert! ( e.contains ( " tag, receipt or ref" ) , " {e}" ) ;
8538
repo.revoke_identity ( & key, " test" ) .unwrap ( ) ;
8539
assert_eq! ( set ( " revoked()" ) , vec! [ d] ) ;
8540
assert_eq! ( set ( " signed_by(me) ~ revoked()" ) , vec! [ ] ) ;
543bbf64 Andy McCall 1 month ago ↶
8541
8542
8543
8544
assert_eq! (
8545
set ( & format! ( " independent(#{} , ::@)" , b.short ( 12 ) ) ) ,
8546
sorted ( & [ c, d] )
8547
) ;
8548
assert_eq! (
8549
set ( & format! ( " independent(#{} , ::main)" , a.short ( 12 ) ) ) ,
8550
vec! [ c]
8551
) ;
8552
8553
assert_eq! ( set ( " conflicts()" ) , vec! [ ] ) ;
8554
assert_eq! ( set ( " conflicts(main)" ) , vec! [ ] ) ;
8555
8556
8557
repo.checkout ( & [ a] , true , " checkout a" ) .unwrap ( ) ;
8558
write ( root, " src/a.rs" , b " one\n TWO\n " ) ;
8559
let e = repo
8560
.commit ( " fifth: the other edit of line two" , & [ ] )
8561
.unwrap ( ) ;
8562
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , true , " checkout main" )
8563
.unwrap ( ) ;
8564
let conflicted = repo.revset ( " conflicts()" ) .unwrap ( ) .hashes;
8565
assert! (
8566
conflicted.contains ( & e) & & conflicted.contains ( & b) ,
8567
" {conflicted:?} should hold b and e"
8568
) ;
8569
assert! ( ! conflicted.contains ( & c) ) ;
8570
assert_eq! ( set ( " conflicts() & author(nobody)" ) , vec! [ ] ) ;
47ce8b8e Andy McCall 1 month ago ↶
8571
}
614f55e1 Andy McCall 1 month ago ↶
8572
8573
# [ test ]
8574
fn a_rotation_is_recorded_wherever_the_old_key_was_named ( ) {
8575
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8576
let root = dir.path ( ) ;
8577
let home = root.join ( " home" ) ;
8578
8579
8580
let mut a = Repo:: init( & root.join ( " a" ) ) .unwrap ( ) .with_home ( & home) ;
8581
let ( key, _ ) = a
8582
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
8583
.unwrap ( ) ;
8584
a.config.set ( " author.key" , & key.to_hex ( ) ) .unwrap ( ) ;
8585
a.save_config ( ) .unwrap ( ) ;
8586
let agent = SecretKey:: generate( ) .identity ( " Agent" , " [email protected] " , 1 ) ;
8587
let r = a
8588
.delegate_add ( agent.clone ( ) , crate :: identity:: Scope:: CHANGES , None )
8589
.unwrap ( ) ;
8590
assert_eq! (
8591
read_config_file ( & home.join ( " config.toml" ) ) .unwrap ( ) [ " author.key" ] ,
8592
r.new.to_hex ( ) ,
8593
" the user file follows"
8594
) ;
8595
assert_eq! (
8596
read_config_file ( & root.join ( " a/.oxy/config" ) ) .unwrap ( ) [ " author.key" ] ,
8597
r.new.to_hex ( ) ,
8598
" the pinning repository follows"
8599
) ;
8600
let b = Repo:: init( & root.join ( " b" ) ) .unwrap ( ) .with_home ( & home) ;
8601
assert_eq! (
8602
b.config.author_key,
8603
Some ( r.new) ,
8604
" a checkout without a pin follows the user file"
8605
) ;
8606
8607
let other_home = root.join ( " home2" ) ;
8608
let mut c = Repo:: init( & root.join ( " c" ) ) .unwrap ( ) .with_home ( & other_home) ;
8609
let ( ck, _ ) = c
8610
.create_identity ( " Cy" , " [email protected] " , IdentityScope:: Repo)
8611
.unwrap ( ) ;
8612
assert! ( ! other_home.join ( " config.toml" ) .exists ( ) ) ;
8613
let r2 = c.rotate_identity ( ) .unwrap ( ) ;
8614
assert_ne! ( r2.new, ck) ;
8615
assert_eq! (
8616
read_config_file ( & root.join ( " c/.oxy/config" ) ) .unwrap ( ) [ " author.key" ] ,
8617
r2.new.to_hex ( )
8618
) ;
8619
assert! (
8620
! other_home.join ( " config.toml" ) .exists ( ) ,
8621
" a repository-scoped identity stays repository-scoped"
8622
) ;
8623
8624
let ( d1, d2) = (
8625
SecretKey:: generate( ) .identity ( " D1" , " d1@x" , 1 ) ,
8626
SecretKey:: generate( ) .identity ( " D2" , " d2@x" , 1 ) ,
8627
) ;
8628
let r3 = c
8629
.delegate_add_many ( vec! [ d1.clone ( ) , d2] , crate :: identity:: Scope:: CHANGES , None )
8630
.unwrap ( ) ;
8631
assert_eq! ( r3.identity.delegates.len ( ) , 2 ) ;
8632
assert! ( c
8633
.delegate_add ( d1, crate :: identity:: Scope:: CHANGES , None )
8634
.is_err ( ) ) ;
8635
}
33be2409 Andy McCall 1 month ago ↶
8636
8637
8638
8639
8640
8641
# [ test ]
8642
fn git_import_joins_unrelated_histories_that_add_the_same_file ( ) {
8643
8644
8645
crate :: repo:: git:: set_merge_batch_for_tests( Some ( 1 ) ) ;
8646
hermetic ( ) ;
8647
if std:: process:: Command:: new( " git" )
8648
.arg ( " --version" )
8649
.output ( )
8650
.is_err ( )
8651
{
8652
eprintln! ( " git not installed; skipping" ) ;
8653
return ;
8654
}
8655
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8656
let root = dir.path ( ) ;
8657
let g = root.join ( " src.git" ) ;
8658
fs:: create_dir_all( & g) .unwrap ( ) ;
8659
let git = | args : & [& str ]| -> String {
8660
let out = std:: process:: Command:: new( " git" )
8661
.arg ( " -C" )
8662
.arg ( & g)
8663
.args ( [
8664
" -c" ,
8665
" user.name=Grace" ,
8666
" -c" ,
8667
" [email protected] " ,
8668
" -c" ,
8669
" commit.gpgsign=false" ,
8670
] )
8671
.args ( args)
8672
.output ( )
8673
.unwrap ( ) ;
8674
assert! (
8675
out.status.success ( ) ,
8676
" git {args:?}: {}" ,
8677
String :: from_utf8_lossy( & out.stderr)
8678
) ;
8679
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
8680
} ;
8681
let text = b " /* header */\n const a = 1;\n const b = 2;\n " ;
8682
8683
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
8684
write ( & g, " gulpfile.js" , text) ;
8685
write ( & g, " a.txt" , b " a\n " ) ;
8686
git ( & [ " add" , " ." ] ) ;
8687
git ( & [ " commit" , " -q" , " -m" , " A root" ] ) ;
8688
8689
git ( & [ " checkout" , " -q" , " --orphan" , " fork" ] ) ;
8690
git ( & [ " rm" , " -q" , " -r" , " --cached" , " ." ] ) ;
8691
for f in [ " gulpfile.js" , " a.txt" ] {
8692
let _ = fs:: remove_file( g.join ( f) ) ;
8693
}
8694
write ( & g, " gulpfile.js" , text) ;
8695
write ( & g, " b.txt" , b " b\n " ) ;
8696
git ( & [ " add" , " ." ] ) ;
8697
git ( & [ " commit" , " -q" , " -m" , " B root" ] ) ;
8698
8699
git ( & [ " checkout" , " -q" , " -b" , " one" , " main" ] ) ;
8700
git ( & [
8701
" merge" ,
8702
" -q" ,
8703
" --no-ff" ,
8704
" --allow-unrelated-histories" ,
8705
" -m" ,
8706
" join on one" ,
8707
" fork" ,
8708
] ) ;
8709
git ( & [ " checkout" , " -q" , " -b" , " two" , " main" ] ) ;
8710
write ( & g, " a.txt" , b " a\n two\n " ) ;
8711
git ( & [ " commit" , " -q" , " -am" , " work on two" ] ) ;
8712
git ( & [
8713
" merge" ,
8714
" -q" ,
8715
" --no-ff" ,
8716
" --allow-unrelated-histories" ,
8717
" -m" ,
8718
" join on two" ,
8719
" fork" ,
8720
] ) ;
8721
8722
git ( & [ " checkout" , " -q" , " main" ] ) ;
8723
git ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge one" , " one" ] ) ;
8724
git ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge two" , " two" ] ) ;
8725
write (
8726
& g,
8727
" gulpfile.js" ,
8728
b " /* header */\n const a = 1;\n const b = 3;\n " ,
8729
) ;
8730
git ( & [ " commit" , " -q" , " -am" , " after" ] ) ;
8731
8732
let dest = root.join ( " dest" ) ;
8733
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8734
repo.import_git ( & g, " main" ) .unwrap ( ) ;
8735
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
8736
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
8737
assert_eq! (
8738
fs:: read( dest.join ( " gulpfile.js" ) ) .unwrap ( ) ,
8739
b " /* header */\n const a = 1;\n const b = 3;\n "
8740
) ;
8741
}
8742
8743
8744
8745
8746
# [ test ]
8747
fn git_import_joins_where_each_join_kept_a_different_copy ( ) {
8748
8749
8750
crate :: repo:: git:: set_merge_batch_for_tests( Some ( 1 ) ) ;
8751
hermetic ( ) ;
8752
if std:: process:: Command:: new( " git" )
8753
.arg ( " --version" )
8754
.output ( )
8755
.is_err ( )
8756
{
8757
eprintln! ( " git not installed; skipping" ) ;
8758
return ;
8759
}
8760
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8761
let root = dir.path ( ) ;
8762
let g = root.join ( " src.git" ) ;
8763
fs:: create_dir_all( & g) .unwrap ( ) ;
8764
let run = | args : & [& str ]| {
8765
std:: process:: Command:: new( " git" )
8766
.arg ( " -C" )
8767
.arg ( & g)
8768
.args ( [
8769
" -c" ,
8770
" user.name=Grace" ,
8771
" -c" ,
8772
" [email protected] " ,
8773
" -c" ,
8774
" commit.gpgsign=false" ,
8775
] )
8776
.args ( args)
8777
.output ( )
8778
.unwrap ( )
8779
} ;
8780
let git = | args : & [& str ]| -> String {
8781
let out = run ( args) ;
8782
assert! (
8783
out.status.success ( ) ,
8784
" git {args:?}: {}" ,
8785
String :: from_utf8_lossy( & out.stderr)
8786
) ;
8787
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
8788
} ;
8789
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
8790
write (
8791
& g,
8792
" gulpfile.js" ,
8793
b " /* header */\n const a = 1;\n const b = 2;\n " ,
8794
) ;
8795
git ( & [ " add" , " ." ] ) ;
8796
git ( & [ " commit" , " -q" , " -m" , " A root" ] ) ;
8797
write (
8798
& g,
8799
" gulpfile.js" ,
8800
b " /* header */\n const a = 1;\n \n const b = 2;\n " ,
8801
) ;
8802
git ( & [ " commit" , " -q" , " -am" , " whitespace" ] ) ;
8803
git ( & [ " checkout" , " -q" , " --orphan" , " fork" ] ) ;
8804
git ( & [ " rm" , " -q" , " -r" , " --cached" , " ." ] ) ;
8805
let _ = fs:: remove_file( g.join ( " gulpfile.js" ) ) ;
8806
write (
8807
& g,
8808
" gulpfile.js" ,
8809
b " /* header */\n const a = 1;\n const b = 2;\n const c = 3;\n " ,
8810
) ;
8811
git ( & [ " add" , " ." ] ) ;
8812
git ( & [ " commit" , " -q" , " -m" , " B root" ] ) ;
8813
8814
for ( branch, keep) in [ ( " one" , " ours" ) , ( " two" , " theirs" ) ] {
8815
git ( & [ " checkout" , " -q" , " -b" , branch, " main" ] ) ;
8816
let _ = run ( & [
8817
" merge" ,
8818
" -q" ,
8819
" --no-ff" ,
8820
" --allow-unrelated-histories" ,
8821
" -m" ,
8822
" join" ,
8823
" fork" ,
8824
] ) ;
8825
git ( & [ " checkout" , & format! ( " --{keep} " ) , " gulpfile.js" ] ) ;
8826
git ( & [ " add" , " gulpfile.js" ] ) ;
8827
git ( & [ " commit" , " -q" , " -m" , & format! ( " join on {branch} " ) ] ) ;
8828
}
8829
8830
git ( & [ " checkout" , " -q" , " one" ] ) ;
8831
write (
8832
& g,
8833
" gulpfile.js" ,
8834
b " /* header */\n const a = 1;\n \n const b = 2;\n // tslint\n " ,
8835
) ;
8836
git ( & [ " commit" , " -q" , " -am" , " tslint on one" ] ) ;
8837
git ( & [ " checkout" , " -q" , " two" ] ) ;
8838
write (
8839
& g,
8840
" gulpfile.js" ,
8841
b " /* header */\n const a = 1;\n const b = 2;\n const c = 3;\n // fmt\n " ,
8842
) ;
8843
write ( & g, " extra.txt" , b " x\n " ) ;
8844
write ( & g, " more.txt" , b " m\n " ) ;
8845
git ( & [ " add" , " ." ] ) ;
8846
git ( & [ " commit" , " -q" , " -am" , " fmt on two" ] ) ;
8847
8848
git ( & [ " checkout" , " -q" , " one" ] ) ;
8849
let _ = run ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge two" , " two" ] ) ;
8850
write (
8851
& g,
8852
" gulpfile.js" ,
8853
b " /* header */\n const a = 1;\n const b = 2;\n const c = 3;\n // tslint\n // fmt\n " ,
8854
) ;
8855
git ( & [ " add" , " gulpfile.js" ] ) ;
8856
git ( & [ " commit" , " -q" , " -m" , " merge two into one" ] ) ;
8857
8858
let dest = root.join ( " dest" ) ;
8859
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8860
repo.import_git ( & g, " one" ) .unwrap ( ) ;
8861
let v = repo.verify_git_import ( & g, " one" ) .unwrap ( ) ;
8862
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
8863
}
dd21fd82 Andy McCall 1 month ago ↶
8864
8865
8866
8867
8868
8869
8870
# [ test ]
8871
fn over_a_store_the_operation_head_moves_by_compare_and_swap ( ) {
8872
use crate :: strata:: { MemoryStore, Store as _ } ;
8873
let backend = std:: sync:: Arc:: new( MemoryStore:: new( ) ) ;
8874
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = Some ( backend.clone ( ) ) ) ;
8875
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8876
let a = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
8877
assert! (
8878
! dir.path ( ) .join ( " .oxy/ops/head" ) .exists ( ) ,
8879
" the head is not a file"
8880
) ;
8881
let ( h0, op0) = a.head_op ( ) .unwrap ( ) ;
8882
let record = backend
8883
.read_ref ( HEAD_RECORD )
8884
.unwrap ( )
8885
.expect ( " the head is the store's record" ) ;
8886
assert_eq! ( String :: from_utf8( record.0 ) .unwrap ( ) , h0.to_hex ( ) ) ;
8887
8888
let b = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
8889
let state = a.store.put_state ( & StateObject:: new( vec! [ ] ) ) .unwrap ( ) ;
8890
let mut va = op0.view.clone ( ) ;
8891
va.bookmarks.insert ( " from-a" .into ( ) , state) ;
8892
let mut vb = op0.view.clone ( ) ;
8893
vb.bookmarks.insert ( " from-b" .into ( ) , state) ;
8894
let rb = b.record_op ( & [ h0] , " bookmark from-b" , vb) .unwrap ( ) ;
8895
assert_eq! ( b.head_ref ( ) .unwrap ( ) , rb) ;
8896
8897
let ra = a.record_op ( & [ h0] , " bookmark from-a" , va) .unwrap ( ) ;
8898
let ( head, op) = Repo:: open( dir.path ( ) ) .unwrap ( ) .head_op ( ) .unwrap ( ) ;
8899
assert_eq! ( head, ra) ;
8900
assert! (
8901
op.command.ends_with ( " (merged with a concurrent operation)" ) ,
8902
" {}" ,
8903
op.command
8904
) ;
8905
assert_eq! ( op.parents, vec! [ rb, op.parents[ 1 ] ] ) ;
8906
assert! ( op.view.bookmarks.contains_key ( " from-a" ) & & op.view.bookmarks.contains_key ( " from-b" ) ) ;
8907
8908
8909
let ( h, v) = a.read_head ( ) .unwrap ( ) ;
8910
assert! ( b.swap_head ( & rb, v.as_ref ( ) ) .unwrap ( ) ) ;
8911
assert! ( ! a.swap_head ( & h, v.as_ref ( ) ) .unwrap ( ) , " stale" ) ;
767ac6cd Andy McCall 18 days ago
8912
a.refresh ( ) .unwrap ( ) ;
dd21fd82 Andy McCall 1 month ago ↶
8913
assert_eq! ( a.head_ref ( ) .unwrap ( ) , rb) ;
8914
8915
let other = tempfile:: tempdir( ) .unwrap ( ) ;
8916
let err = Repo:: init( other.path ( ) )
8917
.err ( )
8918
.expect ( " the store already holds a log" )
8919
.to_string ( ) ;
8920
assert! (
8921
err.contains ( " already holds a repository's operation log" ) ,
8922
" {err}"
8923
) ;
8924
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = None ) ;
8925
}
d069701a Andy McCall 1 month ago ↶
8926
8927
8928
8929
8930
# [ test ]
8931
fn a_repository_runs_on_a_store_with_a_cache_in_front ( ) {
8932
hermetic ( ) ;
8933
if std:: env:: var_os( " OXY_STORE_BACKEND" ) .is_some ( ) {
8934
return ;
8935
}
8936
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8937
let ( root, bucket) = ( dir.path ( ) .join ( " work" ) , dir.path ( ) .join ( " bucket" ) ) ;
8938
let url = format! ( " dir://{} " , bucket.display ( ) ) ;
8939
let repo = Repo:: init_on( & root, [ 7 ; 16 ] , Some ( & url) ) .unwrap ( ) ;
8940
assert_eq! ( repo.config.storage_url.as_deref ( ) , Some ( url.as_str ( ) ) ) ;
8941
write ( & root, " a.txt" , b " one\n " ) ;
8942
let h1 = repo.commit ( " First" , & [ ] ) .unwrap ( ) ;
8943
write ( & root, " a.txt" , b " one\n two\n " ) ;
8944
let h2 = repo.commit ( " Second" , & [ ] ) .unwrap ( ) ;
8945
assert! (
8946
! root.join ( " .oxy/ops/head" ) .exists ( ) ,
8947
" the head is in the store"
8948
) ;
8949
assert! ( bucket.join ( " ops/head" ) .is_file ( ) ) ;
8950
assert! ( root.join ( " .oxy/cache" ) .is_dir ( ) ) ;
8951
drop ( repo) ;
8952
8953
8954
for drop_cache in [ false , true ] {
8955
if drop_cache {
8956
std:: fs:: remove_dir_all( root.join ( " .oxy/cache" ) ) .unwrap ( ) ;
8957
}
8958
let repo = Repo:: open( & root) .unwrap ( ) ;
8959
let log: Vec< String> = repo
8960
.op_log ( )
8961
.unwrap ( )
8962
.into_iter ( )
8963
.map ( | ( _, op ) | op.command )
8964
.collect ( ) ;
8965
assert! ( log.len ( ) > = 3 , " {log:?}" ) ;
8966
assert_eq! ( repo.store ( ) .get_change ( & h1) .unwrap ( ) .message, " First" ) ;
8967
let heads = repo
8968
.store ( )
8969
.get_state ( & repo.view ( ) .unwrap ( ) .bookmarks[ " main" ] )
8970
.unwrap ( )
8971
.heads;
8972
assert_eq! ( heads, vec! [ h2] ) ;
8973
assert_eq! (
8974
repo.load_state ( & heads) .unwrap ( ) .materialize ( ) .files.len ( ) ,
8975
1
8976
) ;
8977
}
8978
8979
8980
let err = Repo:: init_on( & dir.path ( ) .join ( " other" ) , [ 8 ; 16 ] , Some ( & url) )
8981
.err ( )
8982
.unwrap ( )
8983
.to_string ( ) ;
8984
assert! (
8985
err.contains ( " already holds a repository's operation log" ) ,
8986
" {err}"
8987
) ;
8988
let err = Repo:: init_on( & dir.path ( ) .join ( " third" ) , [ 9 ; 16 ] , Some ( " nope://x" ) )
8989
.err ( )
8990
.unwrap ( )
8991
.to_string ( ) ;
8992
assert! ( err.contains ( " cannot open that kind of store" ) , " {err}" ) ;
8993
}
80677b7a Andy McCall 1 month ago ↶
8994
8995
8996
8997
8998
8999
# [ test ]
9000
fn the_quick_dirty_check_compares_a_subrepo_by_its_pin ( ) {
9001
hermetic ( ) ;
9002
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9003
let root = dir.path ( ) ;
9004
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9005
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9006
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9007
.unwrap ( )
9008
.with_home ( root.join ( " home" ) ) ;
9009
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9010
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9011
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9012
let app_dir = root.join ( " app" ) ;
9013
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9014
write ( & app_dir, " main.txt" , b " app\n " ) ;
9015
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9016
app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
9017
9018
std:: thread:: sleep( std:: time:: Duration:: from_millis( 2100 ) ) ;
9019
let reopen = | | Repo:: open( & app_dir) .unwrap ( ) ;
9020
assert_eq! (
9021
reopen ( ) .quick_dirty ( ) .unwrap ( ) ,
9022
Some ( false ) ,
9023
" clean, as status says"
9024
) ;
9025
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) ) .unwrap ( ) ;
9026
write ( & app_dir.join ( " libs/lib" ) , " lib.txt" , b " v2\n " ) ;
9027
nested.commit ( " lib v2" , & [ ] ) .unwrap ( ) ;
9028
assert_eq! (
9029
reopen ( ) .quick_dirty ( ) .unwrap ( ) ,
9030
Some ( true ) ,
9031
" the subrepo moved"
9032
) ;
9033
reopen ( ) .commit ( " pin lib v2" , & [ ] ) .unwrap ( ) ;
9034
std:: thread:: sleep( std:: time:: Duration:: from_millis( 2100 ) ) ;
9035
assert_eq! (
9036
reopen ( ) .quick_dirty ( ) .unwrap ( ) ,
9037
Some ( false ) ,
9038
" the new pin is committed"
9039
) ;
9040
}
7993d465 Andy McCall 1 month ago ↶
9041
9042
# [ test ]
9043
fn a_module_entry_that_was_never_added_is_reported_as_unpinned ( ) {
9044
9045
hermetic ( ) ;
9046
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9047
let root = dir.path ( ) ;
9048
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9049
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9050
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9051
.unwrap ( )
9052
.with_home ( root.join ( " home" ) ) ;
9053
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9054
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9055
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9056
let app_dir = root.join ( " app" ) ;
9057
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9058
write (
9059
& app_dir,
9060
" .oxymodules" ,
9061
format! ( " [libs/lib]\n url = {lib_url} \n " ) .as_bytes ( ) ,
9062
) ;
9063
let unpinned = app.unpinned_modules ( ) .unwrap ( ) ;
9064
assert_eq! ( unpinned.len ( ) , 1 ) ;
9065
assert_eq! ( unpinned[ 0 ] .0 , " libs/lib" ) ;
9066
assert_eq! ( unpinned[ 0 ] .1. url, lib_url) ;
9067
assert! ( app.submodule_update ( true , & [ ] ) .unwrap ( ) .is_empty ( ) ) ;
9068
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9069
assert! ( app.unpinned_modules ( ) .unwrap ( ) .is_empty ( ) , " added now" ) ;
9070
}
bf3a6781 Andy McCall 1 month ago ↶
9071
9072
# [ test ]
9073
fn the_bookmark_revset_function_names_only_bookmarks ( ) {
9074
9075
hermetic ( ) ;
9076
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9077
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9078
write ( dir.path ( ) , " a.txt" , b " a\n " ) ;
9079
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
9080
write ( dir.path ( ) , " b.txt" , b " b\n " ) ;
9081
let b = repo.commit ( " B" , & [ ] ) .unwrap ( ) ;
9082
let mut heads = repo.revset ( " bookmark(main)" ) .unwrap ( ) .hashes;
9083
heads.sort ( ) ;
9084
let mut want = repo.bookmark_heads ( ) .unwrap ( ) ;
9085
want.sort ( ) ;
9086
assert_eq! ( heads, want) ;
9087
assert! ( heads.contains ( & b) ) ;
9088
assert_eq! ( repo.revset ( " main" ) .unwrap ( ) .hashes.len ( ) , heads.len ( ) ) ;
9089
9090
let prefix = repo.store ( ) .get_change_meta ( & a) .unwrap ( ) .change_id.short ( 8 ) ;
9091
assert! ( repo.revset ( & prefix) .is_ok ( ) ) ;
9092
let e = repo
9093
.revset ( & format! ( " bookmark({prefix} )" ) )
9094
.unwrap_err ( )
9095
.to_string ( ) ;
9096
assert! ( e.contains ( " no such bookmark" ) , " {e}" ) ;
9097
}
72484195 Andy McCall 1 month ago ↶
9098
9099
# [ test ]
9100
fn carrying_work_over_a_subrepo_already_at_the_target_pin_is_no_conflict ( ) {
9101
9102
9103
hermetic ( ) ;
9104
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9105
let root = dir.path ( ) ;
9106
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9107
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9108
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9109
.unwrap ( )
9110
.with_home ( root.join ( " home" ) ) ;
9111
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9112
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9113
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9114
let app_dir = root.join ( " app" ) ;
9115
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9116
write ( & app_dir, " main.txt" , b " app\n " ) ;
9117
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9118
9119
fs:: rename( app_dir.join ( " libs/lib" ) , root.join ( " aside" ) ) .unwrap ( ) ;
9120
app.commit ( " initial" , & [ ] ) .unwrap ( ) ;
9121
let stale = app.bookmark_heads ( ) .unwrap ( ) ;
9122
fs:: rename( root.join ( " aside" ) , app_dir.join ( " libs/lib" ) ) .unwrap ( ) ;
9123
app.commit ( " pin lib" , & [ ] ) .unwrap ( ) ;
9124
let newer = app.bookmark_heads ( ) .unwrap ( ) ;
9125
app.checkout ( & stale, false , " test" ) .unwrap ( ) ;
9126
write ( & app_dir, " notes.txt" , b " mine\n " ) ;
9127
let ( _ , carried) = app.checkout_carry ( & newer, " main" , " test" ) .unwrap ( ) ;
9128
assert_eq! ( carried, vec! [ " notes.txt" .to_string ( ) ] ) ;
9129
assert! ( app_dir.join ( " notes.txt" ) .exists ( ) ) ;
9130
}
9131
9132
# [ test ]
9133
fn a_bookmark_only_behind_its_remote_catches_up ( ) {
9134
9135
hermetic ( ) ;
9136
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9137
let root = dir.path ( ) ;
9138
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
9139
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
9140
let one = Repo:: clone( & url, & root.join ( " one" ) )
9141
.unwrap ( )
9142
.with_home ( root.join ( " home" ) ) ;
9143
write ( & root.join ( " one" ) , " a.txt" , b " a\n " ) ;
9144
one.commit ( " A" , & [ ] ) .unwrap ( ) ;
9145
assert! ( one.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9146
let two = Repo:: clone( & url, & root.join ( " two" ) )
9147
.unwrap ( )
9148
.with_home ( root.join ( " home" ) ) ;
9149
write ( & root.join ( " two" ) , " b.txt" , b " b\n " ) ;
9150
two.commit ( " B" , & [ ] ) .unwrap ( ) ;
9151
assert! ( two.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9152
assert_eq! ( one.catch_up_bookmark ( " main" ) .unwrap ( ) , None , " not fetched" ) ;
9153
one.fetch ( " origin" ) .unwrap ( ) ;
9154
assert_eq! ( one.catch_up_bookmark ( " main" ) .unwrap ( ) , Some ( 1 ) ) ;
9155
assert_eq! ( one.bookmarks ( ) .unwrap ( ) [ 0 ] .behind, Some ( 0 ) ) ;
9156
9157
write ( & root.join ( " one" ) , " c.txt" , b " c\n " ) ;
9158
one.commit ( " C" , & [ ] ) .unwrap ( ) ;
9159
write ( & root.join ( " two" ) , " d.txt" , b " d\n " ) ;
9160
two.commit ( " D" , & [ ] ) .unwrap ( ) ;
9161
assert! ( two.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9162
one.fetch ( " origin" ) .unwrap ( ) ;
9163
assert_eq! ( one.catch_up_bookmark ( " main" ) .unwrap ( ) , None ) ;
9164
}
42ccf504 Andy McCall 27 days ago ↶
9165
9166
# [ test ]
9167
fn a_bookmark_is_compared_with_a_remote_that_has_it_when_there_is_no_origin ( ) {
9168
9169
hermetic ( ) ;
9170
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9171
let root = dir.path ( ) ;
9172
let _up = Repo:: init( & root.join ( " up" ) ) .unwrap ( ) ;
9173
let url = root.join ( " up" ) .to_string_lossy ( ) .to_string ( ) ;
9174
let other = Repo:: clone( & url, & root.join ( " other" ) )
9175
.unwrap ( )
9176
.with_home ( root.join ( " home" ) ) ;
9177
write ( & root.join ( " other" ) , " a.txt" , b " a\n " ) ;
9178
other.commit ( " a" , & [ ] ) .unwrap ( ) ;
9179
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9180
let mut one = Repo:: clone( & url, & root.join ( " one" ) )
9181
.unwrap ( )
9182
.with_home ( root.join ( " home" ) ) ;
9183
one.config.set ( " remote.earth.url" , & url) .unwrap ( ) ;
9184
one.config.set ( " remote.gcs.url" , & url) .unwrap ( ) ;
9185
one.config.set ( " remote.origin.url" , " " ) .unwrap ( ) ;
9186
one.save_config ( ) .unwrap ( ) ;
9187
write ( & root.join ( " other" ) , " b.txt" , b " b\n " ) ;
9188
other.commit ( " b" , & [ ] ) .unwrap ( ) ;
9189
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9190
one.fetch ( " earth" ) .unwrap ( ) ;
9191
let b = one
9192
.bookmarks ( )
9193
.unwrap ( )
9194
.into_iter ( )
9195
.find ( | b | b.name = = " main" )
9196
.unwrap ( ) ;
9197
assert_eq! (
9198
( b.remote.as_deref ( ) , b.behind) ,
9199
( Some ( " earth" ) , Some ( 1 ) ) ,
9200
" a stale origin copy is not used"
9201
) ;
9202
assert_eq! (
9203
one.default_remote ( ) ,
9204
" origin" ,
9205
" no default chosen among several"
9206
) ;
9207
let e = one.pull ( & one.default_remote ( ) ) .unwrap_err ( ) .to_string ( ) ;
9208
assert! ( e.contains ( " earth, gcs" ) & & e.contains ( " core.remote" ) , " {e}" ) ;
9209
one.config.set ( " core.remote" , " earth" ) .unwrap ( ) ;
9210
assert_eq! ( one.default_remote ( ) , " earth" ) ;
9211
}
cbe8499a Andy McCall 27 days ago ↶
9212
9213
# [ test ]
9214
fn a_missing_author_identity_says_how_to_fix_it ( ) {
9215
9216
9217
hermetic ( ) ;
9218
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9219
let mut repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9220
let missing = KeyId:: from_hex( & " ab" .repeat ( 32 ) ) .unwrap ( ) ;
9221
repo.config.set ( " author.key" , & missing.to_hex ( ) ) .unwrap ( ) ;
9222
let e = repo.identity ( & missing) .unwrap_err ( ) .to_string ( ) ;
9223
assert! (
9224
e.contains ( " this repository has no identity" ) & & e.contains ( " oxy config author.key" ) ,
9225
" {e}"
9226
) ;
9227
assert! ( e.contains ( " this repository's config" ) , " {e}" ) ;
9228
}
f6dccc45 Andy McCall 27 days ago ↶
9229
9230
# [ test ]
9231
fn a_working_copy_behind_its_bookmark_is_flagged_and_pull_brings_it_up ( ) {
9232
9233
9234
9235
hermetic ( ) ;
9236
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9237
let root = dir.path ( ) ;
9238
let _up = Repo:: init( & root.join ( " up" ) ) .unwrap ( ) ;
9239
let url = root.join ( " up" ) .to_string_lossy ( ) .to_string ( ) ;
9240
let other = Repo:: clone( & url, & root.join ( " other" ) )
9241
.unwrap ( )
9242
.with_home ( root.join ( " home" ) ) ;
9243
write ( & root.join ( " other" ) , " a.txt" , b " a\n " ) ;
9244
other.commit ( " a" , & [ ] ) .unwrap ( ) ;
9245
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9246
let one = Repo:: clone( & url, & root.join ( " one" ) )
9247
.unwrap ( )
9248
.with_home ( root.join ( " home" ) ) ;
9249
write ( & root.join ( " other" ) , " b.txt" , b " b\n " ) ;
9250
other.commit ( " b" , & [ ] ) .unwrap ( ) ;
9251
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9252
one.fetch ( " origin" ) .unwrap ( ) ;
9253
9254
let theirs = one.resolve_heads ( " origin/main" ) .unwrap ( ) ;
9255
one.bookmark_set ( " main" , & theirs) .unwrap ( ) ;
9256
assert! ( ! root.join ( " one/b.txt" ) .exists ( ) ) ;
9257
assert_eq! ( one.working_copy_behind ( ) .unwrap ( ) , Some ( 1 ) ) ;
9258
let r = one.pull ( " origin" ) .unwrap ( ) ;
9259
assert! (
9260
! r.changed & & r.checked_out,
9261
" nothing new, but the working copy caught up"
9262
) ;
9263
assert! ( root.join ( " one/b.txt" ) .exists ( ) ) ;
9264
assert_eq! ( one.working_copy_behind ( ) .unwrap ( ) , None ) ;
9265
}
170bb436 Andy McCall 26 days ago ↶
9266
9267
# [ test ]
9268
fn touched_nodes_cache_answers_as_the_changes_do ( ) {
9269
hermetic ( ) ;
9270
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9271
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9272
write ( dir.path ( ) , " a.txt" , b " one\n " ) ;
9273
write ( dir.path ( ) , " b.txt" , b " x\n " ) ;
9274
repo.snapshot ( " t" ) .unwrap ( ) ;
9275
let c1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
9276
write ( dir.path ( ) , " b.txt" , b " y\n " ) ;
9277
repo.snapshot ( " t" ) .unwrap ( ) ;
9278
repo.commit ( " b only" , & [ ] ) .unwrap ( ) ;
9279
fs:: rename( dir.path ( ) .join ( " a.txt" ) , dir.path ( ) .join ( " c.txt" ) ) .unwrap ( ) ;
9280
write ( dir.path ( ) , " c.txt" , b " one\n two\n " ) ;
9281
repo.snapshot ( " t" ) .unwrap ( ) ;
9282
let c3 = repo.commit ( " rename and edit" , & [ ] ) .unwrap ( ) ;
9283
9284
write ( dir.path ( ) , " c.txt" , b " one\n two\n three\n " ) ;
9285
repo.snapshot ( " t" ) .unwrap ( ) ;
9286
9287
let path = dir.path ( ) .join ( " .oxy/index/touched" ) ;
9288
let hashes = | es : Vec< LogEntry> | es.iter ( ) .map ( | e | e.hash ) .collect:: < Vec< _ > > ( ) ;
9289
let cold = hashes ( repo.log_for_path ( " c.txt" ) .unwrap ( ) ) ;
9290
assert! ( path.exists ( ) , " the first query writes the cache" ) ;
9291
let warm = hashes ( repo.log_for_path ( " c.txt" ) .unwrap ( ) ) ;
9292
assert_eq! ( cold, warm, " the cache answers as the changes do" ) ;
9293
let ( wc, _ ) = repo.working_copy ( ) .unwrap ( ) ;
9294
assert_eq! (
9295
cold,
9296
vec! [ wc, c3, c1] ,
9297
" renames followed, the working copy included"
9298
) ;
9299
9300
9301
let cached = super :: touched:: read_touched_for_test( & fs:: read( & path) .unwrap ( ) ) .unwrap ( ) ;
9302
assert! ( ! cached.contains ( & wc) , " a working-copy change is not cached" ) ;
9303
assert! ( cached.contains ( & c1) & & cached.contains ( & c3) ) ;
9304
9305
9306
let mut bytes = fs:: read( & path) .unwrap ( ) ;
9307
let last = bytes.len ( ) - 40 ;
9308
bytes[ last] ^ = 1 ;
9309
fs:: write( & path, & bytes) .unwrap ( ) ;
9310
assert_eq! ( hashes ( repo.log_for_path ( " c.txt" ) .unwrap ( ) ) , cold) ;
9311
let touches = repo.log_revset ( " touches(\" c.txt\" ) ~ @" ) .unwrap ( ) ;
9312
assert_eq! ( hashes ( touches) , vec! [ c3, c1] ) ;
9313
}
e8e6e0ca Andy McCall 26 days ago ↶
9314
9315
9316
9317
9318
9319
# [ test ]
9320
fn git_import_removes_every_copy_of_a_file_at_one_place ( ) {
9321
hermetic ( ) ;
9322
if std:: process:: Command:: new( " git" )
9323
.arg ( " --version" )
9324
.output ( )
9325
.is_err ( )
9326
{
9327
eprintln! ( " git not installed; skipping" ) ;
9328
return ;
9329
}
9330
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9331
let root = dir.path ( ) ;
9332
let g = root.join ( " src.git" ) ;
9333
fs:: create_dir_all( & g) .unwrap ( ) ;
9334
let git = | args : & [& str ]| {
9335
let out = std:: process:: Command:: new( " git" )
9336
.arg ( " -C" )
9337
.arg ( & g)
9338
.args ( [
9339
" -c" ,
9340
" user.name=Grace" ,
9341
" -c" ,
9342
" [email protected] " ,
9343
" -c" ,
9344
" commit.gpgsign=false" ,
9345
] )
9346
.args ( args)
9347
.output ( )
9348
.unwrap ( ) ;
9349
assert! (
9350
out.status.success ( ) ,
9351
" git {args:?}: {}" ,
9352
String :: from_utf8_lossy( & out.stderr)
9353
) ;
9354
} ;
9355
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
9356
write ( & g, " d/list.txt" , b " one\n two\n three\n four\n " ) ;
9357
write ( & g, " keep.txt" , b " k\n " ) ;
9358
git ( & [ " add" , " ." ] ) ;
9359
git ( & [ " commit" , " -qm" , " base" ] ) ;
9360
fs:: create_dir_all( g.join ( " d/sub" ) ) .unwrap ( ) ;
9361
git ( & [ " mv" , " d/list.txt" , " d/sub/list.txt" ] ) ;
9362
git ( & [ " commit" , " -qm" , " into sub" ] ) ;
9363
git ( & [ " checkout" , " -qb" , " side" ] ) ;
9364
git ( & [ " mv" , " d/sub/list.txt" , " d/list.txt" ] ) ;
9365
git ( & [ " commit" , " -qm" , " back, on side" ] ) ;
9366
git ( & [ " checkout" , " -q" , " main" ] ) ;
9367
git ( & [ " mv" , " d/sub/list.txt" , " d/list.txt" ] ) ;
9368
git ( & [ " commit" , " -qm" , " back, on main" ] ) ;
9369
git ( & [ " merge" , " -q" , " --no-edit" , " side" ] ) ;
9370
git ( & [ " rm" , " -rq" , " d" ] ) ;
9371
git ( & [ " commit" , " -qm" , " d goes" ] ) ;
9372
9373
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
9374
repo.import_git ( & g, " main" ) .unwrap ( ) ;
9375
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
9376
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
9377
let st = repo
9378
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
9379
.unwrap ( ) ;
9380
let view = st.tree ( ) .view ( ) ;
9381
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
9382
assert_eq! ( view.entries.keys ( ) .collect:: < Vec< _ > > ( ) , vec! [ " keep.txt" ] ) ;
9383
}
5552e69c Andy McCall 26 days ago ↶
9384
9385
9386
9387
9388
# [ test ]
9389
fn a_damaged_file_history_in_the_state_index_is_rebuilt ( ) {
9390
hermetic ( ) ;
9391
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9392
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9393
for i in 0 .. 6 {
9394
write (
9395
dir.path ( ) ,
9396
& format! ( " f{i} .txt" ) ,
9397
format! ( " one {i} \n " ) .as_bytes ( ) ,
9398
) ;
9399
}
9400
repo.snapshot ( " t" ) .unwrap ( ) ;
9401
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
9402
for i in 0 .. 6 {
9403
write (
9404
dir.path ( ) ,
9405
& format! ( " f{i} .txt" ) ,
9406
format! ( " one {i} \n two\n " ) .as_bytes ( ) ,
9407
) ;
9408
}
9409
repo.snapshot ( " t" ) .unwrap ( ) ;
9410
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
9411
let want: Vec< Vec< u8 > > = ( 0 .. 6 )
9412
.map ( | i | format! ( " one {i} \n two\n " ) .into_bytes ( ) )
9413
.collect ( ) ;
9414
9415
9416
let ( _ , wc) = repo.working_copy ( ) .unwrap ( ) ;
9417
let base = repo.load_state_history ( & wc.deps) .unwrap ( ) ;
9418
repo.write_state_index ( & wc.deps, & base) ;
9419
for ( i, w) in want.iter ( ) .enumerate ( ) {
9420
assert_eq! ( & repo.file_at ( & format! ( " f{i} .txt" ) , None ) .unwrap ( ) , w) ;
9421
}
9422
let index = repo.state_indexes ( ) ;
9423
assert! ( ! index.is_empty ( ) , " a state index was written" ) ;
9424
9425
let mut bytes = fs:: read( & index[ 0 ] ) .unwrap ( ) ;
9426
let last = bytes.len ( ) - 1 ;
9427
bytes[ last] ^ = 0x40 ;
9428
fs:: write( & index[ 0 ] , & bytes) .unwrap ( ) ;
9429
9430
let reads: Vec< _ > = ( 0 .. 6 )
9431
.map ( | i | repo.file_at ( & format! ( " f{i} .txt" ) , None ) )
9432
.collect ( ) ;
9433
let damaged: Vec< String> = reads
9434
.iter ( )
9435
.filter_map ( | r | r.as_ref ( ) .err ( ) .map ( | e | e.to_string ( ) ) )
9436
.collect ( ) ;
9437
assert_eq! ( damaged.len ( ) , 1 , " {damaged:?}" ) ;
9438
assert! (
9439
damaged[ 0 ] .contains ( " state index was damaged" )
9440
& & damaged[ 0 ] .contains ( " run the command again" ) ,
9441
" {}" ,
9442
damaged[ 0 ]
9443
) ;
9444
9445
for ( i, w) in want.iter ( ) .enumerate ( ) {
9446
assert_eq! ( & repo.file_at ( & format! ( " f{i} .txt" ) , None ) .unwrap ( ) , w) ;
9447
}
9448
}
cb8b8082 Andy McCall 26 days ago ↶
9449
9450
9451
9452
9453
9454
# [ test ]
9455
fn a_long_push_goes_in_parts ( ) {
9456
hermetic ( ) ;
9457
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9458
let root = dir.path ( ) ;
9459
let origin = root.join ( " origin" ) ;
9460
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
9461
let main = root.join ( " main" ) ;
9462
let mut repo = Repo:: init( & main) .unwrap ( ) ;
9463
repo.config
9464
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
9465
.unwrap ( ) ;
9466
9467
repo.config.set ( " push.batch-bytes" , " 1" ) .unwrap ( ) ;
9468
repo.save_config ( ) .unwrap ( ) ;
9469
for i in 0 .. 6 {
9470
write (
9471
& main,
9472
& format! ( " f{i} .txt" ) ,
9473
format! ( " file {i} \n " ) .as_bytes ( ) ,
9474
) ;
9475
repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ;
9476
}
9477
let mut parts = Vec :: new( ) ;
9478
let results = repo
9479
.push_bookmark_with ( " origin" , " main" , false , & mut | m| parts.push ( m.to_string ( ) ) )
9480
.unwrap ( ) ;
9481
assert! (
9482
results.iter ( ) .any ( | r | r.ok & & r.name = = " main" ) ,
9483
" {results:?}"
9484
) ;
9485
assert_eq! ( parts.len ( ) , 6 , " {parts:?}" ) ;
9486
assert_eq! ( parts[ 0 ] , " part 1 of 6: 1 changes" ) ;
9487
9488
let remote = Repo:: open( & origin) .unwrap ( ) ;
9489
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
9490
assert_eq! ( remote.bookmark_heads ( ) .unwrap ( ) , heads) ;
9491
assert_eq! (
9492
remote.log_state ( & heads) .unwrap ( ) .len ( ) ,
9493
repo.log_state ( & heads) .unwrap ( ) .len ( )
9494
) ;
9495
9496
let got = remote
9497
.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ ] )
9498
.unwrap ( ) ;
9499
assert_eq! ( got.len ( ) , 1 ) ;
9500
assert! ( got[ 0 ] .ok & & got[ 0 ] .name = = OBJECTS_RESULT , " {got:?}" ) ;
9501
}
d6e0d5b8 Andy McCall 25 days ago ↶
9502
9503
9504
9505
9506
# [ test ]
9507
fn git_import_claims_your_own_commits ( ) {
9508
hermetic ( ) ;
9509
if std:: process:: Command:: new( " git" )
9510
.arg ( " --version" )
9511
.output ( )
9512
.is_err ( )
9513
{
9514
eprintln! ( " git not installed; skipping" ) ;
9515
return ;
9516
}
9517
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9518
let root = dir.path ( ) ;
9519
let g = root.join ( " src.git" ) ;
9520
fs:: create_dir_all( & g) .unwrap ( ) ;
9521
let git = | who : & str , args : & [& str ]| {
9522
let ( name, email) = who.split_once ( ' ' ) .unwrap ( ) ;
9523
let out = std:: process:: Command:: new( " git" )
9524
.arg ( " -C" )
9525
.arg ( & g)
9526
.args ( [
9527
" -c" ,
9528
& format! ( " user.name={name} " ) ,
9529
" -c" ,
9530
& format! ( " user.email={email} " ) ,
9531
" -c" ,
9532
" commit.gpgsign=false" ,
9533
] )
9534
.args ( args)
9535
.output ( )
9536
.unwrap ( ) ;
9537
assert! (
9538
out.status.success ( ) ,
9539
" git {args:?}: {}" ,
9540
String :: from_utf8_lossy( & out.stderr)
9541
) ;
9542
} ;
9543
git ( " Grace [email protected] " , & [ " init" , " -q" , " -b" , " main" ] ) ;
9544
write ( & g, " a.txt" , b " a\n " ) ;
9545
git ( " Grace [email protected] " , & [ " add" , " ." ] ) ;
9546
git ( " Grace [email protected] " , & [ " commit" , " -qm" , " mine" ] ) ;
9547
write ( & g, " b.txt" , b " b\n " ) ;
9548
git ( " Ada [email protected] " , & [ " add" , " ." ] ) ;
9549
git ( " Ada [email protected] " , & [ " commit" , " -qm" , " someone else's" ] ) ;
9550
9551
let mut repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
9552
let ( key, _ ) = repo
9553
.create_identity ( " Grace" , " [email protected] " , IdentityScope:: Repo)
9554
.unwrap ( ) ;
9555
repo.config.set ( " git.claim" , " true" ) .unwrap ( ) ;
9556
repo.save_config ( ) .unwrap ( ) ;
9557
let repo = Repo:: open( & root.join ( " o" ) ) .unwrap ( ) ;
9558
repo.import_git ( & g, " main" ) .unwrap ( ) ;
9559
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
9560
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
9561
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
9562
let by_message: std:: collections:: HashMap< String, KeyId> = repo
9563
.log_state ( & heads)
9564
.unwrap ( )
9565
.into_iter ( )
9566
.map ( | e | {
9567
(
9568
e.change.message.lines ( ) .next ( ) .unwrap_or ( " " ) .to_string ( ) ,
9569
e.change.author,
9570
)
9571
} )
9572
.collect ( ) ;
9573
assert_eq! ( by_message[ " mine" ] , key, " her own commit, any case of email" ) ;
9574
assert_eq! ( by_message[ " someone else's" ] , ObjectRef:: ZERO , " not hers" ) ;
9575
}
34fad4a9 Andy McCall 25 days ago ↶
9576
9577
9578
9579
# [ test ]
9580
fn removing_a_remote_leaves_nothing_of_it ( ) {
9581
hermetic ( ) ;
9582
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9583
let origin = dir.path ( ) .join ( " origin" ) ;
9584
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
9585
let main = dir.path ( ) .join ( " main" ) ;
9586
let mut repo = Repo:: init( & main) .unwrap ( ) ;
9587
repo.config
9588
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
9589
.unwrap ( ) ;
9590
repo.config
9591
.set ( " remote.origin.token" , " secret-ish" )
9592
.unwrap ( ) ;
9593
repo.config
9594
.set ( " remote.keep.url" , & origin.to_string_lossy ( ) )
9595
.unwrap ( ) ;
9596
repo.save_config ( ) .unwrap ( ) ;
9597
write ( & main, " a.txt" , b " a\n " ) ;
9598
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
9599
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9600
assert! (
9601
repo.view ( ) .unwrap ( ) .remotes.contains_key ( " origin" ) ,
9602
" a tracking bookmark"
9603
) ;
9604
9605
repo.remove_remote ( " origin" ) .unwrap ( ) ;
9606
let again = Repo:: open( & main) .unwrap ( ) ;
9607
assert! (
9608
! again
9609
.config
9610
.repo_layer ( )
9611
.keys ( )
9612
.any ( | k | k.starts_with ( " remote.origin." ) ) ,
9613
" {:?}" ,
9614
again.config.repo_layer ( )
9615
) ;
9616
assert! ( ! again.config.remotes.contains_key ( " origin" ) ) ;
9617
assert! (
9618
! again.view ( ) .unwrap ( ) .remotes.contains_key ( " origin" ) ,
9619
" its tracking bookmarks go"
9620
) ;
9621
assert! (
9622
again.config.remotes.contains_key ( " keep" ) ,
9623
" another remote stays"
9624
) ;
9625
let mut again = again;
9626
assert! (
9627
again.remove_remote ( " origin" ) .is_err ( ) ,
9628
" a second removal is refused"
9629
) ;
9630
}
9d580f21 Andy McCall 25 days ago ↶
9631
9632
9633
9634
9635
# [ test ]
9636
fn gc_keeps_the_blobs_a_host_names ( ) {
9637
hermetic ( ) ;
9638
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9639
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9640
write ( dir.path ( ) , " a.txt" , b " a\n " ) ;
9641
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
9642
9643
let body: Vec< u8 > = ( 0 .. 400_000 u32 )
9644
.flat_map ( | i | ( i.wrapping_mul ( 2654435761 ) ) .to_le_bytes ( ) )
9645
.collect ( ) ;
9646
let kept = repo.store_blob ( & body) .unwrap ( ) ;
9647
let lost = repo.store_blob ( b " an artifact nobody names" ) .unwrap ( ) ;
9648
repo.gc_keeping ( true , & [ kept] , & mut | _ | { } ) .unwrap ( ) ;
9649
assert_eq! ( repo.read_blob ( & kept) .unwrap ( ) , body, " named: kept whole" ) ;
9650
assert! ( ! repo.store ( ) .has ( & lost) , " not named: pruned" ) ;
9651
repo.gc ( true ) .unwrap ( ) ;
9652
assert! ( ! repo.store ( ) .has ( & kept) , " a plain gc prunes it" ) ;
9653
}
530a0fc6 Andy McCall 25 days ago ↶
9654
9655
9656
9657
9658
# [ test ]
9659
fn undo_is_scoped_to_its_workspace ( ) {
9660
hermetic ( ) ;
9661
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9662
let root = dir.path ( ) .join ( " main" ) ;
9663
let a = Repo:: init( & root) .unwrap ( ) ;
9664
write ( & root, " base.txt" , b " base\n " ) ;
9665
let base = a.commit ( " base" , & [ ] ) .unwrap ( ) ;
9666
a.bookmark_create ( " feature" , None ) .unwrap ( ) ;
9667
let ws_dir = dir.path ( ) .join ( " ws" ) ;
9668
let heads = a.resolve_heads ( " feature" ) .unwrap ( ) ;
9669
let b = a
9670
.workspace_add_on ( & ws_dir, Some ( & heads) , Some ( " feature" ) )
9671
.unwrap ( ) ;
9672
9673
9674
write ( & root, " a.txt" , b " a\n " ) ;
9675
let ca = a.commit ( " from a" , & [ ] ) .unwrap ( ) ;
9676
write ( & ws_dir, " b.txt" , b " b\n " ) ;
9677
let cb = b.commit ( " from b" , & [ ] ) .unwrap ( ) ;
9678
9679
9680
let u = a.undo ( ) .unwrap ( ) ;
9681
assert_eq! ( u.command, " oxy commit -m \" from a\" " ) ;
9682
assert_eq! ( u.others_kept, 1 , " B's newer commit is left" ) ;
9683
assert! ( ! u.changes.is_empty ( ) ) ;
9684
let view = a.view ( ) .unwrap ( ) ;
9685
9686
assert_eq! ( a.heads_of ( & view, " main" ) .unwrap ( ) , vec! [ base] ) ;
9687
assert! (
9688
a.heads_of ( & view, " feature" ) .unwrap ( ) .contains ( & cb) ,
9689
" B's commit stands"
9690
) ;
9691
assert! (
9692
a.store ( ) .get_change ( & ca) .is_ok ( ) ,
9693
" the change itself is still in the store"
9694
) ;
9695
9696
assert! ( ! root.join ( " a.txt" ) .exists ( ) ) ;
9697
assert! ( a.snapshot ( " oxy status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
9698
assert! ( ws_dir.join ( " b.txt" ) .exists ( ) ) ;
9699
9700
9701
let u = b.undo ( ) .unwrap ( ) ;
9702
assert_eq! ( u.command, " oxy commit -m \" from b\" " ) ;
9703
assert! ( ! b
9704
.heads_of ( & b.view ( ) .unwrap ( ) , " feature" )
9705
.unwrap ( )
9706
.contains ( & cb) ) ;
9707
assert_eq! (
9708
a.heads_of ( & a.view ( ) .unwrap ( ) , " main" ) .unwrap ( ) ,
9709
vec! [ base] ,
9710
" A's undo stands"
9711
) ;
9712
9713
9714
9715
let e = a.undo ( ) .unwrap_err ( ) .to_string ( ) ;
9716
assert! ( e.contains ( " the workspace at" ) & & e.contains ( " ws" ) , " {e}" ) ;
9717
}
9718
9719
9720
9721
# [ test ]
9722
fn undo_on_a_shared_bookmark_keeps_the_other_workspaces_head ( ) {
9723
hermetic ( ) ;
9724
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9725
let root = dir.path ( ) .join ( " main" ) ;
9726
let a = Repo:: init( & root) .unwrap ( ) ;
9727
write ( & root, " base.txt" , b " base\n " ) ;
9728
a.commit ( " base" , & [ ] ) .unwrap ( ) ;
9729
let ws_dir = dir.path ( ) .join ( " ws" ) ;
9730
let b = a.workspace_add ( & ws_dir, None ) .unwrap ( ) ;
9731
write ( & root, " a.txt" , b " a\n " ) ;
9732
let ca = a.commit ( " from a" , & [ ] ) .unwrap ( ) ;
9733
write ( & ws_dir, " b.txt" , b " b\n " ) ;
9734
let cb = b.commit ( " from b" , & [ ] ) .unwrap ( ) ;
9735
let u = a.undo ( ) .unwrap ( ) ;
9736
assert_eq! ( u.command, " oxy commit -m \" from a\" " ) ;
9737
let heads = a.heads_of ( & a.view ( ) .unwrap ( ) , " main" ) .unwrap ( ) ;
9738
assert! ( heads.contains ( & cb) & & ! heads.contains ( & ca) , " {heads:?}" ) ;
9739
}
9740
9741
9742
9743
# [ test ]
9744
fn undo_refuses_to_take_back_another_workspaces_work ( ) {
9745
hermetic ( ) ;
9746
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9747
let root = dir.path ( ) .join ( " main" ) ;
9748
let a = Repo:: init( & root) .unwrap ( ) ;
9749
write ( & root, " base.txt" , b " base\n " ) ;
9750
a.commit ( " base" , & [ ] ) .unwrap ( ) ;
9751
let ws_dir = dir.path ( ) .join ( " ws" ) ;
9752
let b = a.workspace_add ( & ws_dir, None ) .unwrap ( ) ;
9753
9754
write ( & root, " a.txt" , b " a\n " ) ;
9755
a.commit ( " from a" , & [ ] ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
9756
b.refresh ( ) .unwrap ( ) ;
530a0fc6 Andy McCall 25 days ago ↶
9757
let heads = b.resolve_heads ( " main" ) .unwrap ( ) ;
9758
b.checkout ( & heads, false , " oxy checkout" ) .unwrap ( ) ;
9759
write ( & ws_dir, " a.txt" , b " a, then b\n " ) ;
9760
b.commit ( " from b" , & [ ] ) .unwrap ( ) ;
9761
let e = a.undo ( ) .unwrap_err ( ) .to_string ( ) ;
9762
assert! (
9763
e.contains ( " bookmark main" ) & & e.contains ( " builds on" ) ,
9764
" {e}"
9765
) ;
9766
assert! ( e.contains ( " oxy op restore" ) , " {e}" ) ;
9767
}
9768
9769
9770
9771
# [ test ]
9772
fn undo_says_when_the_commit_was_pushed ( ) {
9773
hermetic ( ) ;
9774
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9775
let root = dir.path ( ) ;
9776
Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
9777
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
9778
let repo = Repo:: clone( & url, & root.join ( " work" ) )
9779
.unwrap ( )
9780
.with_home ( root.join ( " home" ) ) ;
9781
write ( & root.join ( " work" ) , " a.txt" , b " a\n " ) ;
9782
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
9783
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9784
9785
let u = repo.undo ( ) .unwrap ( ) ;
9786
assert_eq! ( u.command, " oxy commit -m \" a\" " ) ;
9787
assert_eq! ( u.pushed, vec! [ ( " main" .to_string ( ) , " origin" .to_string ( ) ) ] ) ;
9788
}
b689779e Andy McCall 25 days ago ↶
9789
9790
9791
9792
9793
# [ test ]
9794
fn large_new_files_are_left_out_of_snapshots ( ) {
9795
hermetic ( ) ;
9796
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9797
let root = dir.path ( ) ;
9798
let mut repo = Repo:: init( root) .unwrap ( ) ;
9799
repo.config.max_new_file = 1024 ;
9800
write ( root, " small.txt" , b " small\n " ) ;
9801
write ( root, " big.bin" , & vec! [ 0 u8 ; 4096 ] ) ;
9802
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
9803
let paths: Vec< _ > = s.statuses.iter ( ) .map ( | p | p.path.as_str ( ) ) .collect ( ) ;
9804
assert_eq! ( paths, [ " small.txt" ] ) ;
9805
assert_eq! ( repo.skipped_large ( ) , vec! [ ( " big.bin" .to_string ( ) , 4096 ) ] ) ;
9806
repo.commit ( " small" , & [ ] ) .unwrap ( ) ;
9807
9808
9809
write ( root, " small.txt" , & vec! [ b ' x' ; 4096 ] ) ;
9810
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
9811
assert! ( s.statuses.iter ( ) .any ( | p | p.path = = " small.txt" ) ) ;
9812
9813
repo.config.max_new_file = 0 ;
9814
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
9815
assert! ( s.statuses.iter ( ) .any ( | p | p.path = = " big.bin" ) ) ;
9816
repo.config.max_new_file = 1024 ;
9817
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
9818
assert! (
9819
s.statuses.iter ( ) .any ( | p | p.path = = " big.bin" ) ,
9820
" already in the working copy"
9821
) ;
9822
assert! ( repo.skipped_large ( ) .is_empty ( ) ) ;
9823
}
9824
9825
9826
9827
# [ test ]
9828
fn gc_prunes_what_only_expired_snapshots_held ( ) {
9829
hermetic ( ) ;
9830
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9831
let root = dir.path ( ) ;
9832
let mut repo = Repo:: init( root) .unwrap ( ) ;
9833
repo.config.gc_grace_days = 0 ;
9834
write ( root, " a.txt" , b " a\n " ) ;
9835
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
9836
let image: Vec< u8 > = ( 0 .. 200_000 u32 ) .map ( | i | ( i % 7 ) as u8 ) .collect ( ) ;
9837
write ( root, " image.tar" , & image) ;
9838
repo.snapshot ( " oxy status" ) .unwrap ( ) ;
9839
std:: fs:: remove_file( root.join ( " image.tar" ) ) .unwrap ( ) ;
9840
repo.snapshot ( " oxy status" ) .unwrap ( ) ;
9841
let blob = Repo:: blob_ref( & image) ;
9842
assert! ( repo.store ( ) .has ( & blob) ) ;
9843
repo.gc ( true ) .unwrap ( ) ;
9844
assert! ( repo.store ( ) .has ( & blob) , " a recent snapshot keeps it" ) ;
9845
repo.config.snapshot_retention_days = 0 ;
9846
repo.gc ( true ) .unwrap ( ) ;
9847
assert! ( ! repo.store ( ) .has ( & blob) , " an expired snapshot does not" ) ;
9848
assert_eq! (
9849
repo.file_at ( " a.txt" , None ) .unwrap ( ) ,
9850
b " a\n " ,
9851
" committed files stay"
9852
) ;
fe9586d9 Andy McCall 24 days ago ↶
9853
}
9854
9855
9856
9857
9858
# [ test ]
9859
fn carrying_work_the_target_already_has_is_no_conflict ( ) {
9860
hermetic ( ) ;
9861
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9862
let root = dir.path ( ) ;
9863
let mut repo = Repo:: init( root) .unwrap ( ) ;
9864
write ( root, " a.txt" , b " one\n " ) ;
9865
write ( root, " b.txt" , b " b\n " ) ;
9866
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
9867
repo.bookmark_create ( " landed" , None ) .unwrap ( ) ;
9868
9869
repo.bookmark_switch ( " landed" , false ) .unwrap ( ) ;
9870
write ( root, " a.txt" , b " two\n " ) ;
9871
repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
9872
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
9873
assert_eq! ( std:: fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n " ) ;
9874
9875
write ( root, " a.txt" , b " two\n " ) ;
9876
write ( root, " b.txt" , b " b, edited\n " ) ;
9877
let ( _ , carried) = repo.bookmark_switch_carry ( " landed" ) .unwrap ( ) ;
9878
assert_eq! (
9879
carried,
9880
vec! [ " b.txt" .to_string ( ) ] ,
9881
" only the work landed lacks is carried"
9882
) ;
9883
assert_eq! ( std:: fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " two\n " ) ;
9884
assert_eq! ( std:: fs:: read( root.join ( " b.txt" ) ) .unwrap ( ) , b " b, edited\n " ) ;
9885
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
9886
let paths: Vec< _ > = s.statuses.iter ( ) .map ( | p | p.path.as_str ( ) ) .collect ( ) ;
9887
assert_eq! ( paths, [ " b.txt" ] ) ;
9888
9889
9890
repo.bookmark_switch ( " main" , true ) .unwrap ( ) ;
9891
write ( root, " a.txt" , b " three\n " ) ;
9892
let e = repo
9893
.bookmark_switch_carry ( " landed" )
9894
.unwrap_err ( )
9895
.to_string ( ) ;
9896
assert! ( e.contains ( " a.txt" ) & & e.contains ( " --discard" ) , " {e}" ) ;
b689779e Andy McCall 25 days ago ↶
9897
}
aa4939e0 Andy McCall 24 days ago ↶
9898
9899
9900
9901
# [ test ]
9902
fn a_full_hash_resolves_without_its_hash_sign ( ) {
9903
hermetic ( ) ;
9904
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9905
let root = dir.path ( ) ;
9906
let repo = Repo:: init( root) .unwrap ( ) ;
9907
write ( root, " a.txt" , b " a\n " ) ;
9908
let h = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
9909
assert_eq! ( repo.resolve_change ( & h.to_hex ( ) ) .unwrap ( ) , h) ;
9910
assert_eq! ( repo.resolve_change ( & format! ( " #{} " , h.to_hex ( ) ) ) .unwrap ( ) , h) ;
9911
assert! ( repo
9912
.resolve_change ( & " 0" .repeat ( 64 ) )
9913
.unwrap_err ( )
9914
.to_string ( )
9915
.contains ( " no change matches" ) ) ;
9916
}
f2f91ceb Andy McCall 23 days ago ↶
9917
9918
# [ test ]
9919
fn a_pin_change_moves_only_the_pin ( ) {
9920
hermetic ( ) ;
9921
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9922
let root = dir.path ( ) ;
9923
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9924
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9925
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9926
.unwrap ( )
9927
.with_home ( root.join ( " home" ) ) ;
9928
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9929
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9930
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9931
let _app_origin = Repo:: init( & root.join ( " app-origin" ) ) .unwrap ( ) ;
9932
let app_dir = root.join ( " app" ) ;
9933
let app = Repo:: clone( & root.join ( " app-origin" ) .to_string_lossy ( ) , & app_dir)
9934
.unwrap ( )
9935
.with_home ( root.join ( " home" ) ) ;
9936
write ( & app_dir, " main.txt" , b " app\n " ) ;
9937
let pin1 = app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9938
let a1 = app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
9939
9940
write ( & root.join ( " lib" ) , " more.txt" , b " v2\n " ) ;
9941
let l2 = lib.commit ( " lib v2" , & [ ] ) .unwrap ( ) ;
9942
let crate :: subrepo:: Pin:: Oxy { repo, bookmark, .. } = pin1 else {
9943
panic! ( " an Oxygen pin" )
9944
} ;
9945
let pin2 = crate :: subrepo:: Pin:: Oxy {
9946
repo,
9947
heads: vec! [ l2] ,
9948
bookmark,
9949
} ;
9950
let author = app.store ( ) .get_change ( & a1) .unwrap ( ) .author;
9951
let change = app
9952
.build_pin_change ( & [ a1] , " libs/lib" , & pin2, " Move lib to v2" , author)
9953
.unwrap ( ) ;
9954
let mut state = app.load_state ( & [ a1] ) .unwrap ( ) ;
9955
state.add ( change) .unwrap ( ) ;
9956
let files = state.materialize ( ) .files;
9957
assert_eq! ( files[ " libs/lib" ] .kind, NodeKind:: Subrepo) ;
9958
let FileContent:: Opaque { blobs } = & files[ " libs/lib" ] .content else {
9959
panic! ( " a pin is opaque" )
9960
} ;
9961
let read = crate :: subrepo:: Pin:: decode( & app.read_blob ( & blobs[ 0 ] ) .unwrap ( ) ) .unwrap ( ) ;
9962
assert_eq! ( read, pin2, " the new pin" ) ;
9963
assert! (
9964
matches! ( & files[ " main.txt" ] .content, FileContent:: Text { bytes, .. } if bytes.as_slice ( ) = = b " app\n " ) ,
9965
" nothing else changes"
9966
) ;
9967
9968
let e = app.build_pin_change ( & [ a1] , " main.txt" , & pin2, " x" , author) ;
9969
assert! ( format! ( " {:?} " , e.unwrap_err ( ) ) .contains ( " not a subrepo" ) ) ;
9970
let e = app.build_pin_change ( & [ a1] , " libs/none" , & pin2, " x" , author) ;
9971
assert! ( format! ( " {:?} " , e.unwrap_err ( ) ) .contains ( " does not exist" ) ) ;
9972
}
767ac6cd Andy McCall 18 days ago
9973
9974
9975
9976
9977
9978
9979
# [ test ]
9980
fn one_handle_keeps_one_head_until_it_refreshes ( ) {
9981
hermetic ( ) ;
9982
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9983
let root = dir.path ( ) ;
9984
let writer = Repo:: init( root) .unwrap ( ) ;
9985
write ( root, " a.txt" , b " one\n " ) ;
9986
let h1 = writer.commit ( " one" , & [ ] ) .unwrap ( ) ;
9987
let reader = Repo:: open( root) .unwrap ( ) ;
9988
let before = reader.view ( ) .unwrap ( ) ;
9989
assert_eq! ( reader.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
9990
write ( root, " a.txt" , b " two\n " ) ;
9991
let h2 = writer.commit ( " two" , & [ ] ) .unwrap ( ) ;
9992
assert_eq! (
9993
writer.bookmark_heads ( ) .unwrap ( ) ,
9994
vec! [ h2] ,
9995
" its own operation"
9996
) ;
9997
assert_eq! ( reader.view ( ) .unwrap ( ) , before, " the head it read first" ) ;
9998
assert_eq! ( reader.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
9999
reader.refresh ( ) .unwrap ( ) ;
10000
assert_eq! ( reader.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
10001
10002
10003
let stale = Repo:: open( root) .unwrap ( ) ;
10004
assert_eq! ( stale.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
10005
write ( root, " a.txt" , b " three\n " ) ;
10006
let h3 = writer.commit ( " three" , & [ ] ) .unwrap ( ) ;
10007
stale.bookmark_create ( " feature" , Some ( & [ h1] ) ) .unwrap ( ) ;
10008
let view = stale.view ( ) .unwrap ( ) ;
10009
assert! ( view.bookmarks.contains_key ( " feature" ) ) ;
10010
assert_eq! (
10011
stale.bookmark_heads ( ) .unwrap ( ) ,
10012
vec! [ h3] ,
10013
" merged, not lost"
10014
) ;
10015
let fresh = Repo:: open( root) .unwrap ( ) ;
10016
assert_eq! ( fresh.view ( ) .unwrap ( ) , view) ;
10017
}
10018
10019
10020
# [ test ]
10021
fn over_a_store_one_handle_keeps_one_head ( ) {
10022
hermetic ( ) ;
10023
let backend = std:: sync:: Arc:: new( crate :: strata:: MemoryStore:: new( ) ) ;
10024
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = Some ( backend.clone ( ) ) ) ;
10025
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10026
let a = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
10027
let ( h0, _ ) = a.head_op ( ) .unwrap ( ) ;
10028
let b = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
10029
assert_eq! ( b.head_op ( ) .unwrap ( ) .0 , h0) ;
10030
let state = a.store.put_state ( & StateObject:: new( vec! [ ] ) ) .unwrap ( ) ;
10031
let mut view = a.view ( ) .unwrap ( ) ;
10032
view.bookmarks.insert ( " x" .into ( ) , state) ;
10033
let r = a.record_op ( & [ h0] , " bookmark x" , view) .unwrap ( ) ;
10034
assert_eq! ( a.head_op ( ) .unwrap ( ) .0 , r) ;
10035
assert_eq! ( b.head_op ( ) .unwrap ( ) .0 , h0, " b keeps its head" ) ;
10036
b.refresh ( ) .unwrap ( ) ;
10037
assert_eq! ( b.head_op ( ) .unwrap ( ) .0 , r) ;
10038
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = None ) ;
10039
}
10040
10041
10042
10043
10044
# [ test ]
10045
fn gc_retires_and_a_later_gc_deletes_after_the_hour ( ) {
10046
hermetic ( ) ;
10047
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10048
let root = dir.path ( ) ;
10049
let repo = Repo:: init( root) .unwrap ( ) ;
10050
let mut heads = Vec :: new( ) ;
10051
for i in 0 .. 4 {
10052
write ( root, & format! ( " f{i} .txt" ) , format! ( " {i} \n " ) .as_bytes ( ) ) ;
10053
heads.push ( repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ) ;
10054
}
10055
let reader = Repo:: open( root) .unwrap ( ) ;
10056
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 5 ) ;
10057
let r = repo.gc ( true ) .unwrap ( ) ;
10058
assert_eq! ( r.retired, crate :: store:: RetiredReport:: default( ) ) ;
10059
assert_eq! ( repo.store ( ) .loose_count ( ) .unwrap ( ) , 0 ) ;
10060
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 5 , " read on from what it had" ) ;
10061
10062
let r = repo.gc ( true ) .unwrap ( ) ;
10063
assert_eq! ( ( r.retired.packs, r.retired.loose) , ( 0 , 0 ) ) ;
10064
assert! ( r.retired.kept > 0 , " {r:?}" ) ;
10065
write ( root, " f9.txt" , b " 9\n " ) ;
10066
heads.push ( repo.commit ( " c9" , & [ ] ) .unwrap ( ) ) ;
10067
crate :: store:: reader_tests:: age_retired(
10068
repo.store ( ) ,
10069
crate :: store:: RETIRED_FOR + std:: time:: Duration:: from_secs( 60 ) ,
10070
) ;
10071
let r = repo.gc ( true ) .unwrap ( ) ;
10072
assert! ( r.retired.packs > = 1 & & r.retired.loose > 0 , " {r:?}" ) ;
10073
assert_eq! ( r.retired.kept, 0 , " {r:?}" ) ;
10074
let r = repo.gc ( true ) .unwrap ( ) ;
10075
assert_eq! ( ( r.retired.packs, r.retired.loose) , ( 0 , 0 ) ) ;
10076
assert! ( r.retired.kept > 0 , " what the last gc retired is young" ) ;
10077
10078
10079
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 5 ) ;
10080
for h in & heads[ .. 4 ] {
10081
reader.store ( ) .get_change ( h) .unwrap ( ) ;
10082
}
10083
reader.refresh ( ) .unwrap ( ) ;
10084
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 6 ) ;
10085
let fresh = Repo:: open( root) .unwrap ( ) ;
10086
assert_eq! (
10087
fresh.store ( ) .pack_count ( ) ,
10088
1 ,
10089
" retired packs are not loaded"
10090
) ;
10091
}
10092
10093
10094
10095
# [ test ]
10096
fn a_repository_opens_when_a_listed_index_is_gone ( ) {
10097
hermetic ( ) ;
10098
let backend = crate :: store:: reader_tests:: racing( true ) ;
10099
let dyn_backend: std:: sync:: Arc< crate:: store:: Backend> = backend.clone ( ) ;
10100
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = Some ( dyn_backend) ) ;
10101
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10102
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
10103
write ( dir.path ( ) , " a.txt" , b " a\n " ) ;
10104
let h = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10105
repo.gc ( true ) .unwrap ( ) ;
10106
let again = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
10107
assert_eq! ( again.store ( ) .pack_count ( ) , 1 ) ;
10108
assert_eq! ( again.bookmark_heads ( ) .unwrap ( ) , vec! [ h] ) ;
10109
10110
10111
use crate :: strata:: Store as _ ;
10112
let idx = backend
10113
.list ( " pack-" )
10114
.unwrap ( )
10115
.into_iter ( )
10116
.map ( | e | e.key )
10117
.find ( | k | k.ends_with ( " .idx" ) & & k ! = " pack-0000.idx" )
10118
.unwrap ( ) ;
10119
let stem = idx.trim_end_matches ( " .idx" ) ;
10120
for ext in [ " pack" , " idx" ] {
10121
let bytes = backend.get ( & format! ( " {stem} .{ext} " ) ) .unwrap ( ) ;
10122
backend
10123
.put_new ( & format! ( " pack-copy.{ext} " ) , & bytes)
10124
.unwrap ( ) ;
10125
}
10126
* backend.doomed.lock ( ) .unwrap ( ) = Some ( " pack-copy.idx" .into ( ) ) ;
10127
let again = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
10128
assert_eq! ( again.store ( ) .pack_count ( ) , 1 ) ;
10129
assert_eq! ( again.bookmark_heads ( ) .unwrap ( ) , vec! [ h] ) ;
10130
assert! ( ! backend.has ( " pack-copy.idx" ) .unwrap ( ) ) ;
10131
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = None ) ;
10132
}