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}" ) ;
137
assert! ( d.contains ( " + println!(\" hi\" );" ) , " {d}" ) ;
138
assert! ( d.contains ( " -fn main() {}" ) , " {d}" ) ;
139
140
141
assert_eq! ( repo.resolve_change ( & c2.change_id.short ( 6 ) ) .unwrap ( ) , h2) ;
75167672 Andy McCall 1 month ago
142
assert_eq! (
143
repo.resolve_change ( & format! ( " #{} " , h1.short ( 8 ) ) ) .unwrap ( ) ,
144
h1
145
) ;
98286406 Andy McCall 1 month ago ↶
146
assert! ( repo.resolve_change ( " zzzz" ) .is_err ( ) ) ;
147
148
149
repo.undo ( ) .unwrap ( ) ;
150
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
151
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
152
assert! (
153
! s.changed,
154
" files on disk still match the pre-commit working copy"
155
) ;
98286406 Andy McCall 1 month ago ↶
156
assert_eq! ( s.statuses.len ( ) , 3 ) ;
157
}
158
159
160
fn conflicted_repo ( ) -> ( tempfile:: TempDir, Repo, ChangeHash, ChangeHash, ChangeHash) {
161
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
162
let root = dir.path ( ) ;
163
let repo = Repo:: init( root) .unwrap ( ) ;
164
write ( root, " f.txt" , b " a\n b\n c\n " ) ;
165
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
166
write ( root, " f.txt" , b " a\n B\n c\n " ) ;
167
let h2 = repo.commit ( " upper" , & [ ] ) .unwrap ( ) ;
168
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
169
write ( root, " f.txt" , b " a\n X\n c\n " ) ;
170
let h3 = repo.commit ( " ex" , & [ ] ) .unwrap ( ) ;
171
( dir, repo, h1, h2, h3)
172
}
173
3652104a Andy McCall 19 days ago ↶
174
175
176
98286406 Andy McCall 1 month ago ↶
177
# [ test ]
3652104a Andy McCall 19 days ago ↶
178
fn marker_like_text_does_not_block_a_resolution ( ) {
179
hermetic ( ) ;
180
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
181
let root = dir.path ( ) ;
182
let repo = Repo:: init( root) .unwrap ( ) ;
183
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 " ;
184
write (
185
root,
186
" doc.md" ,
187
format! ( " intro\n {example} b\n end\n " ) .as_bytes ( ) ,
188
) ;
189
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
190
write (
191
root,
192
" doc.md" ,
193
format! ( " intro\n {example} B\n end\n " ) .as_bytes ( ) ,
194
) ;
195
repo.commit ( " upper" , & [ ] ) .unwrap ( ) ;
196
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
197
write (
198
root,
199
" doc.md" ,
200
format! ( " intro\n {example} X\n end\n " ) .as_bytes ( ) ,
201
) ;
202
repo.commit ( " ex" , & [ ] ) .unwrap ( ) ;
203
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
204
.unwrap ( ) ;
205
let marked = fs:: read_to_string( root.join ( " doc.md" ) ) .unwrap ( ) ;
206
assert! ( marked.contains ( " oxy conflict 1 of 1" ) , " {marked}" ) ;
207
208
write ( root, " doc.md" , marked.replace ( " B\n " , " BB\n " ) .as_bytes ( ) ) ;
209
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
210
assert_eq! (
211
statuses ( & s) ,
212
vec! [ ( " doc.md" .into ( ) , ChangeStatus:: ConflictEdited) ]
213
) ;
214
215
write (
216
root,
217
" doc.md" ,
218
format! ( " intro\n {example} X\n end\n " ) .as_bytes ( ) ,
219
) ;
220
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
221
assert_eq! (
222
statuses ( & s) ,
223
vec! [ ( " doc.md" .into ( ) , ChangeStatus:: Resolved) ]
224
) ;
225
let h = repo.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
226
assert_eq! (
227
repo.store ( ) .get_change ( & h) .unwrap ( ) .kind,
228
ChangeKind:: Resolution
229
) ;
230
assert! ( ! repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) .is_conflicted ( ) ) ;
231
}
232
9bd98cfb Andy McCall 11 days ago ↶
233
234
235
3652104a Andy McCall 19 days ago ↶
236
# [ test ]
9bd98cfb Andy McCall 11 days ago ↶
237
fn adjacent_insertions_are_settled_by_keeping_both ( ) {
238
hermetic ( ) ;
239
for reverse in [ false , true ] {
240
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
241
let root = dir.path ( ) ;
242
let repo = Repo:: init( root) .unwrap ( ) ;
243
write ( root, " f.txt" , b " a\n b\n " ) ;
244
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
245
write ( root, " f.txt" , b " a\n first\n b\n " ) ;
246
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
247
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
248
write ( root, " f.txt" , b " a\n second\n b\n " ) ;
249
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
250
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
251
.unwrap ( ) ;
252
let marked = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
253
let ( f, s) = (
254
marked.find ( " first\n " ) .unwrap ( ) ,
255
marked.find ( " second\n " ) .unwrap ( ) ,
256
) ;
257
assert! ( f < s, " the earlier change's side comes first: {marked}" ) ;
258
259
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
260
let node = snap.base_snapshot.files[ " f.txt" ] .node;
261
assert_eq! ( snap.base.both_sides ( & node) .iter ( ) .flatten ( ) .count ( ) , 1 ) ;
262
let kept = repo.keep_both ( & snap, & [ ] , reverse) .unwrap ( ) ;
263
assert_eq! (
264
kept,
265
vec! [ KeptBoth {
266
path: " f.txt" .into ( ) ,
267
settled: vec! [ 1 ] ,
268
left: vec! [ ]
269
} ]
270
) ;
271
let want: & [ u8 ] = if reverse {
272
b " a\n second\n first\n b\n "
273
} else {
274
b " a\n first\n second\n b\n "
275
} ;
276
assert_eq! ( fs:: read( root.join ( " f.txt" ) ) .unwrap ( ) , want) ;
277
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
278
assert_eq! ( statuses ( & s) , vec! [ ( " f.txt" .into ( ) , ChangeStatus:: Resolved) ] ) ;
279
let h = repo.commit ( " both" , & [ ] ) .unwrap ( ) ;
280
assert_eq! (
281
repo.store ( ) .get_change ( & h) .unwrap ( ) .kind,
282
ChangeKind:: Resolution
283
) ;
284
assert! ( ! repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) .is_conflicted ( ) ) ;
285
}
286
}
287
4afe288b Andy McCall 11 days ago ↶
288
289
290
291
# [ test ]
292
fn union_files_keep_both_sides_on_checkout ( ) {
293
hermetic ( ) ;
294
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
295
let root = dir.path ( ) ;
296
let repo = Repo:: init( root) .unwrap ( ) ;
297
write ( root, " .oxyattributes" , b " CHANGELOG.md merge=union\n " ) ;
298
write (
299
root,
300
" CHANGELOG.md" ,
301
b " # Changelog\n ## Unreleased\n ## 1.0\n " ,
302
) ;
303
write ( root, " f.txt" , b " a\n b\n " ) ;
304
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
305
write (
306
root,
307
" CHANGELOG.md" ,
308
b " # Changelog\n ## Unreleased\n - one\n ## 1.0\n " ,
309
) ;
310
write ( root, " f.txt" , b " a\n one\n b\n " ) ;
311
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
312
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
313
write (
314
root,
315
" CHANGELOG.md" ,
316
b " # Changelog\n ## Unreleased\n - two\n ## 1.0\n " ,
317
) ;
318
write ( root, " f.txt" , b " a\n two\n b\n " ) ;
319
repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
320
let r = repo
321
.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
322
.unwrap ( ) ;
323
assert! ( r.conflicted) ;
324
assert_eq! ( r.kept_both.len ( ) , 1 , " {:?}" , r.kept_both) ;
325
assert_eq! ( r.kept_both[ 0 ] .path, " CHANGELOG.md" ) ;
326
assert_eq! (
327
fs:: read_to_string( root.join ( " CHANGELOG.md" ) ) .unwrap ( ) ,
328
" # Changelog\n ## Unreleased\n - one\n - two\n ## 1.0\n "
329
) ;
330
assert! ( has_conflict_markers ( & fs:: read( root.join ( " f.txt" ) ) .unwrap ( ) ) ) ;
331
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
332
assert_eq! (
333
statuses ( & s) ,
334
vec! [ ( " CHANGELOG.md" .into ( ) , ChangeStatus:: Resolved) ]
335
) ;
336
}
337
ac4ec124 Andy McCall 11 days ago ↶
338
339
340
341
# [ test ]
342
fn a_host_settles_a_state_whose_conflicts_are_all_union ( ) {
343
hermetic ( ) ;
344
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
345
let root = dir.path ( ) ;
346
let repo = Repo:: init( root) .unwrap ( ) ;
347
write ( root, " .oxyattributes" , b " *.md merge=union\n " ) ;
348
write ( root, " A.md" , b " a\n b\n " ) ;
349
write ( root, " B.md" , b " x\n y\n " ) ;
350
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
351
write ( root, " A.md" , b " a\n one\n b\n " ) ;
352
write ( root, " B.md" , b " x\n Y1\n " ) ;
353
let h2 = repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
354
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
355
write ( root, " A.md" , b " a\n two\n b\n " ) ;
356
let h3 = repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
357
let st = repo.load_state ( & [ h2, h3] ) .unwrap ( ) ;
358
assert_eq! (
359
Repo:: union_resolution( & st, & st.materialize ( ) ) ,
360
Some ( vec! [ ( " A.md" .into ( ) , b " a\n one\n two\n b\n " .to_vec ( ) ) ] )
361
) ;
362
363
repo.checkout ( & [ h3] , false , " checkout" ) .unwrap ( ) ;
364
write ( root, " B.md" , b " x\n Y2\n " ) ;
365
let h4 = repo.commit ( " Y2" , & [ ] ) .unwrap ( ) ;
366
let st = repo.load_state ( & [ h2, h4] ) .unwrap ( ) ;
367
assert! ( st.materialize ( ) .is_conflicted ( ) ) ;
368
assert_eq! ( Repo:: union_resolution( & st, & st.materialize ( ) ) , None ) ;
369
}
370
9bd98cfb Andy McCall 11 days ago ↶
371
372
373
374
# [ test ]
375
fn keeping_both_refuses_replaced_or_edited_lines ( ) {
376
hermetic ( ) ;
377
let ( dir, repo, _ , _ , _ ) = conflicted_repo ( ) ;
378
let root = dir.path ( ) ;
379
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
380
.unwrap ( ) ;
381
let marked = fs:: read( root.join ( " f.txt" ) ) .unwrap ( ) ;
382
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
383
let node = snap.base_snapshot.files[ " f.txt" ] .node;
384
assert_eq! ( snap.base.both_sides ( & node) , vec! [ None ] ) ;
385
let kept = repo.keep_both ( & snap, & [ " f.txt" .into ( ) ] , false ) .unwrap ( ) ;
386
assert! ( kept[ 0 ] .settled.is_empty ( ) ) ;
387
assert_eq! ( kept[ 0 ] .left[ 0 ] .0 , 1 ) ;
388
assert! ( kept[ 0 ] .left[ 0 ] .1. contains ( " both sides changed" ) , " {kept:?}" ) ;
389
assert_eq! ( fs:: read( root.join ( " f.txt" ) ) .unwrap ( ) , marked) ;
390
391
392
let sides = vec! [ Some ( vec! [ b " x\n " .to_vec ( ) , b " y\n " .to_vec ( ) ] ) ] ;
393
let text = b " a\n <<<<<<< oxy conflict 1 of 1, side 1 of 2: change k \" x\" \n x!\n ======= oxy conflict 1 of 1, side 2 of 2: change q \" y\" \n y\n >>>>>>> oxy conflict 1 of 1 end\n b\n " ;
394
let ( out, settled, left) = super :: snapshot:: keep_both_text( text, & sides, false ) ;
395
assert_eq! ( out, text.to_vec ( ) ) ;
396
assert! ( settled.is_empty ( ) ) ;
397
assert! ( left[ 0 ] .1. contains ( " edited" ) , " {left:?}" ) ;
398
399
let text = b " a\n <<<<<<< oxy conflict 1 of 1, side 1 of 2: change q \" y\" \n y\n ======= oxy conflict 1 of 1, side 2 of 2: change k \" x\" \n x\n >>>>>>> oxy conflict 1 of 1 end\n b\n " ;
400
let ( out, settled, _ ) = super :: snapshot:: keep_both_text( text, & sides, false ) ;
401
assert_eq! ( out, b " a\n x\n y\n b\n " .to_vec ( ) ) ;
402
assert_eq! ( settled, vec! [ 1 ] ) ;
403
}
404
405
# [ test ]
98286406 Andy McCall 1 month ago ↶
406
fn concurrent_edits_conflict_and_can_be_resolved ( ) {
407
hermetic ( ) ;
408
let ( dir, repo, h1, h2, h3) = conflicted_repo ( ) ;
409
let root = dir.path ( ) ;
410
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
411
assert_eq! ( heads, [ h2, h3] .into_iter ( ) .collect ( ) ) ;
412
let st = repo.load_state ( & [ h2, h3] ) .unwrap ( ) ;
413
assert! ( st.materialize ( ) .is_conflicted ( ) ) ;
414
415
75167672 Andy McCall 1 month ago
416
let r = repo
417
.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
418
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
419
assert! ( r.conflicted) ;
420
let marked = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
421
assert! (
422
marked.starts_with ( " a\n <<<<<<< oxy conflict 1 of 1" ) ,
423
" {marked}"
424
) ;
425
assert! (
426
marked.contains ( " B\n " ) & & marked.contains ( " X\n " ) & & marked.ends_with ( " end\n c\n " ) ,
427
" {marked}"
428
) ;
98286406 Andy McCall 1 month ago ↶
429
assert! ( has_conflict_markers ( marked.as_bytes ( ) ) ) ;
430
431
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
432
assert! ( s.statuses.is_empty ( ) ) ;
433
assert! ( ! s.resolves) ;
434
435
436
write ( root, " f.txt" , marked.replace ( " B\n " , " BB\n " ) .as_bytes ( ) ) ;
437
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
438
assert_eq! (
439
statuses ( & s) ,
440
vec! [ ( " f.txt" .into ( ) , ChangeStatus:: ConflictEdited) ]
441
) ;
98286406 Andy McCall 1 month ago ↶
442
assert! ( repo.commit ( " no" , & [ ] ) .is_err ( ) ) ;
443
444
445
write ( root, " f.txt" , b " a\n X\n c\n d\n " ) ;
446
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
447
assert_eq! ( statuses ( & s) , vec! [ ( " f.txt" .into ( ) , ChangeStatus:: Resolved) ] ) ;
448
assert! ( s.resolves) ;
449
let h4 = repo.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
450
let c4 = repo.store ( ) .get_change ( & h4) .unwrap ( ) ;
451
assert_eq! ( c4.kind, ChangeKind:: Resolution) ;
452
let mut want = vec! [ h1, h2, h3] ;
453
want.sort ( ) ;
75167672 Andy McCall 1 month ago
454
assert_eq! (
455
c4.deps, want,
456
" a resolution depends on exactly the contributors and what it references"
457
) ;
98286406 Andy McCall 1 month ago ↶
458
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h4] ) ;
459
let snap = repo.load_state ( & [ h4] ) .unwrap ( ) .materialize ( ) ;
460
assert! ( ! snap.is_conflicted ( ) ) ;
75167672 Andy McCall 1 month ago
461
assert! (
462
matches! ( & snap.files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n X\n c\n d\n " )
463
) ;
98286406 Andy McCall 1 month ago ↶
464
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
465
466
467
let mut st = State:: new( ) ;
468
for h in [ h4, h3, h1, h2] {
469
st.add_unchecked ( repo.store ( ) .get_change ( & h) .unwrap ( ) ) ;
470
}
471
st.validate ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
472
assert! (
473
matches! ( & st.materialize ( ) .files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n X\n c\n d\n " )
474
) ;
98286406 Andy McCall 1 month ago ↶
475
}
476
69953b50 Andy McCall 19 days ago ↶
477
478
479
98286406 Andy McCall 1 month ago ↶
480
# [ test ]
69953b50 Andy McCall 19 days ago ↶
481
fn resolutions_and_amends_take_paths ( ) {
482
hermetic ( ) ;
483
let ( dir, repo, _h1, _h2, _h3) = conflicted_repo ( ) ;
484
let root = dir.path ( ) ;
485
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
486
.unwrap ( ) ;
487
488
write ( root, " f.txt" , b " a\n X\n c\n " ) ;
489
write ( root, " notes.txt" , b " pending\n " ) ;
490
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
491
assert! ( s.resolves) ;
492
let err = repo
493
.commit_selection_partial ( " resolve" , & [ ] , & Selection:: Paths( vec! [ " notes.txt" .into ( ) ] ) )
494
.unwrap_err ( )
495
.to_string ( ) ;
496
assert! (
497
err.contains ( " takes every file it resolves" ) & & err.contains ( " f.txt" ) ,
498
" {err}"
499
) ;
500
let ( h4, partial) = repo
501
.commit_selection_partial ( " resolve" , & [ ] , & Selection:: Paths( vec! [ " f.txt" .into ( ) ] ) )
502
.unwrap ( ) ;
503
assert! ( partial) ;
504
assert_eq! (
505
repo.store ( ) .get_change ( & h4) .unwrap ( ) .kind,
506
ChangeKind:: Resolution
507
) ;
508
assert! ( ! repo
509
.load_state ( & [ h4] )
510
.unwrap ( )
511
.materialize ( )
512
.is_conflicted ( ) ) ;
513
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
514
assert_eq! (
515
statuses ( & s) ,
516
vec! [ ( " notes.txt" .into ( ) , ChangeStatus:: Added) ] ,
517
" the other work waits"
518
) ;
519
520
521
522
let h5 = repo.commit ( " notes" , & [ h4] ) .unwrap ( ) ;
523
let id5 = repo.store ( ) .get_change ( & h5) .unwrap ( ) .change_id;
524
write ( root, " notes.txt" , b " pending\n more\n " ) ;
525
write ( root, " other.txt" , b " later\n " ) ;
526
let h6 = repo
527
.amend_paths ( Some ( " notes, amended" ) , & [ " notes.txt" .into ( ) ] )
528
.unwrap ( ) ;
529
let c6 = repo.store ( ) .get_change ( & h6) .unwrap ( ) ;
530
assert_eq! ( ( c6.change_id, c6.message.as_str ( ) ) , ( id5, " notes, amended" ) ) ;
531
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h6] ) ;
532
let snap6 = repo.load_state ( & [ h6] ) .unwrap ( ) .materialize ( ) ;
533
assert! (
534
matches! ( & snap6.files[ " notes.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " pending\n more\n " )
535
) ;
536
assert! (
537
! snap6.files.contains_key ( " other.txt" ) ,
538
" other.txt stays out"
539
) ;
540
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
541
assert_eq! (
542
statuses ( & s) ,
543
vec! [ ( " other.txt" .into ( ) , ChangeStatus:: Added) ]
544
) ;
545
}
546
547
# [ test ]
98286406 Andy McCall 1 month ago ↶
548
fn resolution_may_keep_both_sides_or_neither ( ) {
549
hermetic ( ) ;
550
let ( dir, repo, _h1, _h2, _h3) = conflicted_repo ( ) ;
551
let root = dir.path ( ) ;
75167672 Andy McCall 1 month ago
552
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
553
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
554
555
write ( root, " f.txt" , b " a\n X\n B\n c\n " ) ;
556
let h = repo.commit ( " both" , & [ ] ) .unwrap ( ) ;
557
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
558
assert! ( ! snap.is_conflicted ( ) ) ;
75167672 Andy McCall 1 month ago
559
assert! (
560
matches! ( & snap.files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n X\n B\n c\n " )
561
) ;
98286406 Andy McCall 1 month ago ↶
562
563
repo.undo ( ) .unwrap ( ) ;
564
write ( root, " f.txt" , b " a\n c\n " ) ;
565
let h = repo.commit ( " neither" , & [ ] ) .unwrap ( ) ;
566
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
567
assert! ( ! snap.is_conflicted ( ) ) ;
75167672 Andy McCall 1 month ago
568
assert! (
569
matches! ( & snap.files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n c\n " )
570
) ;
98286406 Andy McCall 1 month ago ↶
571
}
572
573
# [ test ]
574
fn name_conflict_resolved_by_removing_a_candidate ( ) {
575
hermetic ( ) ;
576
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
577
let root = dir.path ( ) ;
578
let repo = Repo:: init( root) .unwrap ( ) ;
579
write ( root, " keep.txt" , b " k\n " ) ;
580
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
581
write ( root, " new.txt" , b " one\n " ) ;
582
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
583
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
584
write ( root, " new.txt" , b " two\n " ) ;
585
repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
586
let r = repo
587
.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
588
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
589
assert! ( r.conflicted) ;
75167672 Andy McCall 1 month ago
590
assert! (
591
root.join ( " new.txt.oxy-conflict-1" ) .exists ( )
592
& & root.join ( " new.txt.oxy-conflict-2" ) .exists ( )
593
) ;
98286406 Andy McCall 1 month ago ↶
594
assert! ( ! root.join ( " new.txt" ) .exists ( ) ) ;
595
596
75167672 Andy McCall 1 month ago
597
let ( keep, drop) = if fs:: read( root.join ( " new.txt.oxy-conflict-1" ) ) .unwrap ( ) = = b " two\n " {
598
( 1 , 2 )
599
} else {
600
( 2 , 1 )
601
} ;
98286406 Andy McCall 1 month ago ↶
602
fs:: remove_file( root.join ( format! ( " new.txt.oxy-conflict-{drop} " ) ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
603
fs:: rename(
604
root.join ( format! ( " new.txt.oxy-conflict-{keep} " ) ) ,
605
root.join ( " new.txt" ) ,
606
)
607
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
608
let h = repo.commit ( " pick two" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
609
assert_eq! (
610
repo.store ( ) .get_change ( & h) .unwrap ( ) .kind,
611
ChangeKind:: Resolution
612
) ;
98286406 Andy McCall 1 month ago ↶
613
614
615
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
616
assert_eq! ( heads, [ h1, h] .into_iter ( ) .collect ( ) ) ;
75167672 Andy McCall 1 month ago
617
let snap = repo
618
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
619
.unwrap ( )
620
.materialize ( ) ;
98286406 Andy McCall 1 month ago ↶
621
assert! ( ! snap.is_conflicted ( ) , " {:?}" , snap.tree.conflicts) ;
75167672 Andy McCall 1 month ago
622
assert_eq! (
623
snap.files.keys ( ) .cloned ( ) .collect:: < Vec< _ > > ( ) ,
624
[ " keep.txt" , " new.txt" ]
625
) ;
626
assert! (
627
matches! ( & snap.files[ " new.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " two\n " )
628
) ;
98286406 Andy McCall 1 month ago ↶
629
}
630
631
# [ test ]
632
fn identities_and_signed_commits ( ) {
633
hermetic ( ) ;
634
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
635
let root = dir.path ( ) ;
636
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
637
write ( root, " a.txt" , b " a\n " ) ;
638
let h0 = repo.commit ( " unsigned" , & [ ] ) .unwrap ( ) ;
639
assert_eq! ( repo.verify_change ( & h0) .unwrap ( ) , SignatureStatus:: Unsigned) ;
75167672 Andy McCall 1 month ago
640
assert_eq! (
641
repo.store ( ) .get_change ( & h0) .unwrap ( ) .author,
642
ObjectRef:: ZERO
643
) ;
98286406 Andy McCall 1 month ago ↶
644
75167672 Andy McCall 1 month ago
645
let ( key, id) = repo
646
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
647
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
648
assert_eq! ( id.hash ( ) , key) ;
649
assert_eq! ( repo.config.author_key, Some ( key) ) ;
650
651
let reopened = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
652
assert_eq! ( reopened.config.author_key, Some ( key) ) ;
653
assert_eq! ( reopened.secret_key ( ) .unwrap ( ) .unwrap ( ) .0 , key) ;
654
655
656
write ( root, " a.txt" , b " a\n b\n " ) ;
657
let h1 = repo.commit ( " authored" , & [ ] ) .unwrap ( ) ;
658
assert_eq! ( repo.store ( ) .get_change ( & h1) .unwrap ( ) .author, key) ;
659
assert_eq! ( repo.verify_change ( & h1) .unwrap ( ) , SignatureStatus:: Unsigned) ;
660
661
repo.sign_change ( & h1) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
662
assert! (
663
matches! ( repo.verify_change ( & h1) .unwrap ( ) , SignatureStatus:: Valid { key: k, .. } if k = = key)
664
) ;
98286406 Andy McCall 1 month ago ↶
665
666
repo.config.set ( " core.sign" , " change" ) .unwrap ( ) ;
667
repo.save_config ( ) .unwrap ( ) ;
668
write ( root, " a.txt" , b " a\n b\n c\n " ) ;
669
let h2 = repo.commit ( " signed" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
670
assert_eq! (
671
repo.verify_change ( & h2) .unwrap ( ) ,
672
SignatureStatus:: Valid {
673
key,
674
name: " Ada" .into ( ) ,
675
email: " [email protected] " .into ( ) ,
676
sealed: false ,
677
revoked: false
678
}
679
) ;
98286406 Andy McCall 1 month ago ↶
680
05e71ed6 Andy McCall 1 month ago ↶
681
682
683
98286406 Andy McCall 1 month ago ↶
684
let other = SecretKey:: generate( ) ;
685
let other_id = other.identity ( " Mallory" , " [email protected] " , 0 ) ;
686
let other_key = repo.store ( ) .put_identity ( & other_id) .unwrap ( ) ;
05e71ed6 Andy McCall 1 month ago ↶
687
write ( root, " a.txt" , b " a\n b\n c\n d\n " ) ;
688
repo.config.set ( " core.sign" , " seal" ) .unwrap ( ) ;
689
let h3 = repo.commit ( " ada's, unsigned" , & [ ] ) .unwrap ( ) ;
690
let forged = other.sign ( Domain:: Change, & other_key, & h3) ;
691
repo.store ( ) .put_signature ( & h3, & forged) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
692
assert! (
05e71ed6 Andy McCall 1 month ago ↶
693
matches! ( repo.verify_change ( & h3) .unwrap ( ) , SignatureStatus:: Invalid( m) if m.contains ( " neither the author" ) )
75167672 Andy McCall 1 month ago
694
) ;
05e71ed6 Andy McCall 1 month ago ↶
695
let adopted = other.sign ( Domain:: Change, & other_key, & h0) ;
696
repo.store ( ) .put_signature ( & h0, & adopted) .unwrap ( ) ;
697
assert! (
698
matches! ( repo.verify_change ( & h0) .unwrap ( ) , SignatureStatus:: Valid { name, .. } if name = = " Mallory" )
699
) ;
98286406 Andy McCall 1 month ago ↶
700
701
702
let mut sig = repo.store ( ) .signatures ( & h2) .unwrap ( ) .remove ( 0 ) ;
703
sig.ed[ 0 ] ^ = 1 ;
704
repo.store ( ) .put_signature ( & h2, & sig) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
705
assert! ( matches! (
706
repo.verify_change ( & h2) .unwrap ( ) ,
707
SignatureStatus:: Invalid( _ )
708
) ) ;
98286406 Andy McCall 1 month ago ↶
709
710
711
let no_keys = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " nowhere" ) ) ;
712
write ( root, " a.txt" , b " z\n " ) ;
713
assert! ( no_keys.commit ( " cannot sign" , & [ ] ) .is_err ( ) ) ;
714
}
715
716
# [ test ]
717
fn checkout_moves_the_working_copy ( ) {
718
hermetic ( ) ;
719
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
720
let root = dir.path ( ) ;
721
let repo = Repo:: init( root) .unwrap ( ) ;
722
write ( root, " a.txt" , b " one\n " ) ;
723
write ( root, " sub/deep/b.txt" , b " b\n " ) ;
724
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
725
write ( root, " a.txt" , b " two\n " ) ;
726
fs:: remove_file( root.join ( " sub/deep/b.txt" ) ) .unwrap ( ) ;
727
write ( root, " bin.dat" , & [ 0 , 1 , 2 , 3 ] ) ;
728
let h2 = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
729
730
let r = repo.checkout ( & [ h1] , false , " oxy checkout" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
731
assert_eq! (
732
r,
733
CheckoutReport {
734
written: 2 ,
735
removed: 1 ,
9b723f69 Andy McCall 26 days ago ↶
736
removed_paths: vec! [ " bin.dat" .into ( ) ] ,
75167672 Andy McCall 1 month ago
737
conflicted: false ,
d3cd7ef9 Andy McCall 1 month ago ↶
738
notes: vec! [ ] ,
739
lacks: 1 ,
4afe288b Andy McCall 11 days ago ↶
740
kept_both: vec! [ ] ,
75167672 Andy McCall 1 month ago
741
}
742
) ;
98286406 Andy McCall 1 month ago ↶
743
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n " ) ;
744
assert_eq! ( fs:: read( root.join ( " sub/deep/b.txt" ) ) .unwrap ( ) , b " b\n " ) ;
745
assert! ( ! root.join ( " bin.dat" ) .exists ( ) ) ;
746
let ( _ , wc) = repo.working_copy ( ) .unwrap ( ) ;
747
assert_eq! ( wc.deps, vec! [ h1] ) ;
75167672 Andy McCall 1 month ago
748
assert! (
749
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
750
" checkout leaves a clean working copy"
751
) ;
98286406 Andy McCall 1 month ago ↶
752
753
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
754
755
756
write ( root, " a.txt" , b " dirty\n " ) ;
757
assert! ( repo.checkout ( & [ h2] , false , " oxy checkout" ) .is_err ( ) ) ;
75167672 Andy McCall 1 month ago
758
let r = repo
759
.checkout ( & [ h2] , true , " oxy checkout --discard" )
760
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
761
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " two\n " ) ;
762
assert_eq! ( fs:: read( root.join ( " bin.dat" ) ) .unwrap ( ) , [ 0 , 1 , 2 , 3 ] ) ;
763
assert! ( ! root.join ( " sub" ) .exists ( ) , " empty directories are pruned" ) ;
764
assert_eq! ( r.removed, 1 ) ;
765
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
766
767
768
# [ cfg ( unix ) ]
769
{
770
fs:: remove_dir_all( root.join ( " sub" ) ) .ok ( ) ;
771
std:: os:: unix:: fs:: symlink( " /tmp" , root.join ( " sub" ) ) .unwrap ( ) ;
772
assert! ( repo.checkout ( & [ h1] , true , " oxy checkout" ) .is_err ( ) ) ;
773
}
774
}
775
776
# [ test ]
777
fn large_files_are_chunked_and_read_back ( ) {
778
hermetic ( ) ;
779
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
780
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
781
let mut big = vec! [ 0 u8 ; 700 * 1024 ] ;
782
for ( i, b) in big.iter_mut ( ) .enumerate ( ) {
783
* b = ( i as u32 ) .wrapping_mul ( 2654435761 ) .to_le_bytes ( ) [ 0 ] ;
784
}
785
let r = repo.store_blob ( & big) .unwrap ( ) ;
786
let blob = Blob:: decode( & repo.store ( ) .get_typed ( & r, ObjectType:: Blob) .unwrap ( ) ) .unwrap ( ) ;
787
assert! ( blob.chunks.len ( ) > = 3 , " {}" , blob.chunks.len ( ) ) ;
788
assert_eq! ( repo.read_blob ( & r) .unwrap ( ) , big) ;
789
assert_eq! ( Repo:: blob_ref( & big) , r) ;
790
}
791
792
# [ test ]
793
fn clone_push_pull_with_seals_and_attestations ( ) {
794
hermetic ( ) ;
795
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
796
let root = dir.path ( ) ;
797
let origin_path = root.join ( " origin" ) ;
798
let origin = Repo:: init( & origin_path) .unwrap ( ) ;
799
let origin_url = origin_path.to_string_lossy ( ) .to_string ( ) ;
800
801
75167672 Andy McCall 1 month ago
802
let mut a = Repo:: clone( & origin_url, & root.join ( " a" ) )
803
.unwrap ( )
804
.with_home ( root.join ( " home-a" ) ) ;
98286406 Andy McCall 1 month ago ↶
805
assert_eq! ( a.id ( ) .unwrap ( ) , origin.id ( ) .unwrap ( ) ) ;
75167672 Andy McCall 1 month ago
806
let ( key_a, _ ) = a
807
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
808
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
809
write ( & root.join ( " a" ) , " f.txt" , b " a\n " ) ;
810
let h1 = a.commit ( " first" , & [ ] ) .unwrap ( ) ;
811
let results = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
812
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
813
assert! ( results[ 0 ] .ok, " {}" , results[ 0 ] .reason) ;
814
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
815
75167672 Andy McCall 1 month ago
816
assert! (
817
matches! ( origin.authenticate_change ( & h1) .unwrap ( ) , SignatureStatus:: Valid { key, sealed: true , .. } if key = = key_a)
818
) ;
819
assert_eq! (
820
origin.verify_change ( & h1) .unwrap ( ) ,
821
SignatureStatus:: Unsigned
822
) ;
f8c69576 Andy McCall 1 month ago ↶
823
let serial_of = | att : & SignedAttest| match att.attest.body {
824
AttestBody:: Ref { serial, .. } => serial,
825
_ => unreachable! ( ) ,
826
} ;
98286406 Andy McCall 1 month ago ↶
827
let ( _ , att) = origin.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
828
829
830
let first = serial_of ( & att) ;
831
assert! ( first > 1_000_000_000_000 , " {first}" ) ;
98286406 Andy McCall 1 month ago ↶
832
let again = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
833
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
834
assert! ( again[ 0 ] .ok & & again[ 0 ] .reason.contains ( " up to date" ) ) ;
835
let ( _ , att) = origin.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
836
assert_eq! ( serial_of ( & att) , first, " a no-op push does not attest again" ) ;
98286406 Andy McCall 1 month ago ↶
837
838
839
let b = Repo:: clone( & origin_url, & root.join ( " b" ) ) .unwrap ( ) ;
840
assert_eq! ( fs:: read( root.join ( " b/f.txt" ) ) .unwrap ( ) , b " a\n " ) ;
841
assert_eq! ( b.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
75167672 Andy McCall 1 month ago
842
assert! ( matches! (
843
b.authenticate_change ( & h1) .unwrap ( ) ,
844
SignatureStatus:: Valid { sealed: true , .. }
845
) ) ;
98286406 Andy McCall 1 month ago ↶
846
847
848
write ( & root.join ( " b" ) , " f.txt" , b " a\n b\n " ) ;
849
let h2 = b.commit ( " second" , & [ ] ) .unwrap ( ) ;
850
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
851
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
852
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
853
854
855
write ( & root.join ( " a" ) , " g.txt" , b " g\n " ) ;
856
let h3 = a.commit ( " third" , & [ ] ) .unwrap ( ) ;
857
assert! ( a.push ( " origin" , false ) .is_err ( ) ) ;
858
let pr = a.pull ( " origin" ) .unwrap ( ) ;
859
assert! ( pr.changed & & pr.checked_out) ;
860
let mut want = vec! [ h2, h3] ;
861
want.sort ( ) ;
862
assert_eq! ( pr.heads, want) ;
863
assert_eq! ( fs:: read( root.join ( " a/f.txt" ) ) .unwrap ( ) , b " a\n b\n " ) ;
864
assert_eq! ( fs:: read( root.join ( " a/g.txt" ) ) .unwrap ( ) , b " g\n " ) ;
865
let results = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
866
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
867
assert! ( results[ 0 ] .ok, " {}" , results[ 0 ] .reason) ;
868
let mut origin_heads = origin.bookmark_heads ( ) .unwrap ( ) ;
869
origin_heads.sort ( ) ;
870
assert_eq! ( origin_heads, want) ;
871
let ( _ , att) = origin.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
872
assert_eq! ( serial_of ( & att) , first + 1 ) ;
98286406 Andy McCall 1 month ago ↶
873
874
875
let pr = b.pull ( " origin" ) .unwrap ( ) ;
876
assert_eq! ( pr.heads, want) ;
877
assert_eq! ( fs:: read( root.join ( " b/g.txt" ) ) .unwrap ( ) , b " g\n " ) ;
878
assert! ( ! b.snapshot ( " status" ) .unwrap ( ) .changed) ;
879
880
881
write ( & root.join ( " a" ) , " f.txt" , b " a\n A\n " ) ;
882
a.commit ( " a-edit" , & [ ] ) .unwrap ( ) ;
883
a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
884
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
885
write ( & root.join ( " b" ) , " f.txt" , b " a\n B\n " ) ;
886
b.commit ( " b-edit" , & [ ] ) .unwrap ( ) ;
887
let pr = b.pull ( " origin" ) .unwrap ( ) ;
888
assert! ( pr.changed & & pr.checked_out) ;
889
let text = fs:: read_to_string( root.join ( " b/f.txt" ) ) .unwrap ( ) ;
890
assert! ( text.contains ( " <<<<<<< oxy conflict" ) , " {text}" ) ;
891
write ( & root.join ( " b" ) , " f.txt" , b " a\n AB\n " ) ;
892
let hr = b.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
893
assert_eq! (
894
b.store ( ) .get_change ( & hr) .unwrap ( ) .kind,
895
ChangeKind:: Resolution
896
) ;
98286406 Andy McCall 1 month ago ↶
897
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
898
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
899
a.pull ( " origin" ) .unwrap ( ) ;
900
assert_eq! ( fs:: read( root.join ( " a/f.txt" ) ) .unwrap ( ) , b " a\n AB\n " ) ;
901
902
903
let mut want = vec! [ hr, h3] ;
904
want.sort ( ) ;
905
assert_eq! ( a.bookmark_heads ( ) .unwrap ( ) , want) ;
906
assert! ( ! a.load_state ( & want) .unwrap ( ) .materialize ( ) .is_conflicted ( ) ) ;
907
}
908
909
# [ test ]
910
fn receive_push_rejects_bad_updates ( ) {
911
hermetic ( ) ;
912
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
913
let root = dir.path ( ) ;
914
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
915
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
916
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
917
.unwrap ( )
918
.with_home ( root.join ( " home" ) ) ;
919
let ( key, _ ) = a
920
.create_identity ( " Ada" , " a@b" , IdentityScope:: Repo)
921
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
922
write ( & root.join ( " a" ) , " f.txt" , b " x\n " ) ;
923
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
924
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
925
let empty = origin.store ( ) .put_state ( & StateObject:: new( vec! [ ] ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
926
let s1 = origin
927
.store ( )
928
.put_state ( & StateObject:: new( vec! [ h1] ) )
929
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
930
931
75167672 Andy McCall 1 month ago
932
let ghost = origin
933
.store ( )
934
.put_state ( & StateObject:: new( vec! [ ObjectRef( [ 9 ; 32 ] ) ] ) )
935
.unwrap ( ) ;
936
let r = origin
937
.receive_push (
938
Bundle:: default( ) ,
939
vec! [ RefUpdate {
940
name: " main" .into ( ) ,
941
old_state: s1,
942
new_state: ghost,
943
force: false ,
944
attestation: None ,
945
} ] ,
946
None ,
947
vec! [ ] ,
948
)
949
.unwrap ( ) ;
950
assert! (
951
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " missing" ) ,
952
" {}" ,
953
r[ 0 ] .reason
954
) ;
98286406 Andy McCall 1 month ago ↶
955
956
75167672 Andy McCall 1 month ago
957
let r = origin
958
.receive_push (
959
Bundle:: default( ) ,
960
vec! [ RefUpdate {
961
name: " main" .into ( ) ,
962
old_state: empty,
963
new_state: s1,
964
force: false ,
965
attestation: None ,
966
} ] ,
967
None ,
968
vec! [ ] ,
969
)
970
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
971
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " stale" ) , " {}" , r[ 0 ] .reason) ;
972
973
75167672 Andy McCall 1 month ago
974
let r = origin
975
.receive_push (
976
Bundle:: default( ) ,
977
vec! [ RefUpdate {
978
name: " main" .into ( ) ,
979
old_state: s1,
980
new_state: empty,
981
force: false ,
982
attestation: None ,
983
} ] ,
984
None ,
985
vec! [ ] ,
986
)
987
.unwrap ( ) ;
988
assert! (
989
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " fast-forward" ) ,
990
" {}" ,
991
r[ 0 ] .reason
992
) ;
993
let r = origin
994
.receive_push (
995
Bundle:: default( ) ,
996
vec! [ RefUpdate {
997
name: " main" .into ( ) ,
998
old_state: s1,
999
new_state: empty,
1000
force: true ,
1001
attestation: None ,
1002
} ] ,
1003
None ,
1004
vec! [ ] ,
1005
)
1006
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1007
assert! ( r[ 0 ] .ok) ;
1008
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ ] ) ;
1009
1010
75167672 Andy McCall 1 month ago
1011
let sa = a
1012
.sign_attest ( & Attest {
1013
repo: a.id ( ) .unwrap ( ) ,
1014
hlc: Hlc:: now( ) ,
1015
body: AttestBody:: Ref {
1016
name: " main" .into ( ) ,
1017
old_state: empty,
1018
new_state: s1,
1019
serial: 1 ,
1020
} ,
1021
} )
1022
.unwrap ( ) ;
1023
let r = origin
1024
.receive_push (
1025
Bundle:: default( ) ,
1026
vec! [ RefUpdate {
1027
name: " main" .into ( ) ,
1028
old_state: empty,
1029
new_state: s1,
1030
force: false ,
1031
attestation: Some ( sa.clone ( ) ) ,
1032
} ] ,
1033
None ,
1034
vec! [ ] ,
1035
)
1036
.unwrap ( ) ;
1037
assert! (
1038
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " serial" ) ,
1039
" {}" ,
1040
r[ 0 ] .reason
1041
) ;
98286406 Andy McCall 1 month ago ↶
1042
75167672 Andy McCall 1 month ago
1043
let sa2 = a
1044
.sign_attest ( & Attest {
1045
repo: a.id ( ) .unwrap ( ) ,
1046
hlc: Hlc:: now( ) ,
1047
body: AttestBody:: Ref {
1048
name: " main" .into ( ) ,
1049
old_state: s1,
1050
new_state: empty,
1051
serial: 5 ,
1052
} ,
1053
} )
1054
.unwrap ( ) ;
1055
let r = origin
1056
.receive_push (
1057
Bundle:: default( ) ,
1058
vec! [ RefUpdate {
1059
name: " main" .into ( ) ,
1060
old_state: empty,
1061
new_state: s1,
1062
force: false ,
1063
attestation: Some ( sa2) ,
1064
} ] ,
1065
None ,
1066
vec! [ ] ,
1067
)
1068
.unwrap ( ) ;
1069
assert! (
1070
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " describe" ) ,
1071
" {}" ,
1072
r[ 0 ] .reason
1073
) ;
98286406 Andy McCall 1 month ago ↶
1074
let _ = key;
1075
}
1076
1077
# [ test ]
1078
fn user_config_supplies_defaults_and_clones_sign ( ) {
1079
hermetic ( ) ;
1080
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1081
let root = dir.path ( ) ;
1082
let home = root.join ( " home" ) ;
1083
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1084
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1085
1086
1087
let mut a = Repo:: init( & root.join ( " a" ) ) .unwrap ( ) .with_home ( & home) ;
75167672 Andy McCall 1 month ago
1088
let ( key, _ ) = a
1089
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1090
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1091
assert! ( ! a.config.repo_layer ( ) .contains_key ( " author.key" ) ) ;
1092
assert_eq! ( a.config.author_key, Some ( key) ) ;
1093
assert! ( home.join ( " config.toml" ) .exists ( ) ) ;
1094
a.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
1095
assert! ( a.config.sign_changes) ;
1096
1097
1098
a.config.set ( " core.sign" , " seal" ) .unwrap ( ) ;
1099
a.save_config ( ) .unwrap ( ) ;
1100
let again = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) .with_home ( & home) ;
1101
assert! ( ! again.config.sign_changes) ;
1102
assert_eq! ( again.config.author_key, Some ( key) ) ;
75167672 Andy McCall 1 month ago
1103
assert_eq! (
1104
read_config_file ( & root.join ( " a/.oxy/config" ) )
1105
.unwrap ( )
1106
.get ( " core.sign" )
1107
.map ( String :: as_str) ,
1108
Some ( " seal" )
1109
) ;
98286406 Andy McCall 1 month ago ↶
1110
1111
1112
1113
let b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) .with_home ( & home) ;
1114
assert! ( b.config.sign_changes) ;
1115
write ( & root.join ( " b" ) , " f.txt" , b " f\n " ) ;
1116
let h = b.commit ( " signed from a clone" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1117
assert! (
1118
matches! ( b.verify_change ( & h) .unwrap ( ) , SignatureStatus:: Valid { key: k, .. } if k = = key)
1119
) ;
98286406 Andy McCall 1 month ago ↶
1120
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
75167672 Andy McCall 1 month ago
1121
assert! ( matches! (
1122
origin.verify_change ( & h) .unwrap ( ) ,
1123
SignatureStatus:: Valid { .. }
1124
) ) ;
98286406 Andy McCall 1 month ago ↶
1125
}
1126
1127
# [ test ]
1128
fn format1_repositories_are_migrated_on_open ( ) {
1129
hermetic ( ) ;
1130
use crate :: oplog:: { Format1Op, Format1View} ;
1131
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1132
let root = dir.path ( ) ;
1133
let repo = Repo:: init( root) .unwrap ( ) ;
1134
write ( root, " a.txt" , b " a\n " ) ;
1135
let h1 = repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
1136
let ( wc_hash, _ ) = repo.working_copy ( ) .unwrap ( ) ;
1137
1138
let mut v1 = Format1View:: default( ) ;
1139
v1.bookmarks.insert ( " main" .into ( ) , vec! [ ] ) ;
1140
v1.workspaces.insert ( repo.workspace ( ) , wc_hash) ;
75167672 Andy McCall 1 month ago
1141
let init = Format1Op {
1142
parents: vec! [ ] ,
1143
hlc: Hlc:: default( ) ,
1144
command: " oxy init" .into ( ) ,
1145
view: v1.clone ( ) ,
1146
} ;
98286406 Andy McCall 1 month ago ↶
1147
let init_ref = repo.store ( ) .put ( ObjectType:: Op, & init.encode ( ) ) .unwrap ( ) ;
1148
v1.bookmarks.insert ( " main" .into ( ) , vec! [ h1] ) ;
75167672 Andy McCall 1 month ago
1149
let commit = Format1Op {
1150
parents: vec! [ init_ref] ,
1151
hlc: Hlc:: default( ) ,
1152
command: " oxy commit" .into ( ) ,
1153
view: v1,
1154
} ;
98286406 Andy McCall 1 month ago ↶
1155
let commit_ref = repo.store ( ) .put ( ObjectType:: Op, & commit.encode ( ) ) .unwrap ( ) ;
1156
write_atomic ( & root.join ( " .oxy/ops/head" ) , commit_ref.to_hex ( ) .as_bytes ( ) ) .unwrap ( ) ;
1157
write_atomic ( & root.join ( " .oxy/format" ) , FORMAT1_LINE .as_bytes ( ) ) .unwrap ( ) ;
1158
drop ( repo) ;
1159
1160
let repo = Repo:: open( root) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1161
assert_eq! (
1162
fs:: read_to_string( root.join ( " .oxy/format" ) ) .unwrap ( ) ,
1163
FORMAT_LINE
1164
) ;
98286406 Andy McCall 1 month ago ↶
1165
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
1166
let ops = repo.op_log ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1167
assert_eq! (
1168
ops.iter ( )
1169
.map ( | ( _, o ) | o.command.as_str ( ) )
1170
.collect:: < Vec< _ > > ( ) ,
1171
[ " oxy commit" , " oxy init" ]
1172
) ;
98286406 Andy McCall 1 month ago ↶
1173
assert_eq! ( repo.heads_of ( & ops[ 1 ] .1. view, " main" ) .unwrap ( ) , vec! [ ] ) ;
1174
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
1175
1176
assert_eq! ( Repo:: open( root) .unwrap ( ) .op_log ( ) .unwrap ( ) .len ( ) , 2 ) ;
1177
}
1178
1179
# [ test ]
1180
fn undo_skips_snapshot_operations ( ) {
1181
hermetic ( ) ;
1182
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1183
let root = dir.path ( ) ;
1184
let repo = Repo:: init( root) .unwrap ( ) ;
1185
write ( root, " a.txt" , b " a\n " ) ;
1186
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
1187
1188
write ( root, " b.txt" , b " b\n " ) ;
1189
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
1190
assert! ( s.changed) ;
1191
assert! ( repo.head_op ( ) .unwrap ( ) .1. is_snapshot ( ) ) ;
1192
assert_eq! ( repo.last_command ( ) .unwrap ( ) .command, " oxy commit -m \" a\" " ) ;
1193
1194
repo.undo ( ) .unwrap ( ) ;
1195
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ ] ) ;
1196
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
1197
let paths: Vec< _ > = s.statuses.iter ( ) .map ( | p | p.path.as_str ( ) ) .collect ( ) ;
75167672 Andy McCall 1 month ago
1198
assert_eq! (
1199
paths,
1200
[ " a.txt" , " b.txt" ] ,
1201
" both files are back in the working copy"
1202
) ;
98286406 Andy McCall 1 month ago ↶
1203
assert! ( repo.store ( ) .get_change ( & h1) .unwrap ( ) .deps.is_empty ( ) ) ;
1204
1205
assert_eq! ( repo.last_command ( ) .unwrap ( ) .command, " oxy init" ) ;
1206
assert! ( repo.undo ( ) .is_err ( ) ) ;
1207
}
1208
1209
# [ test ]
1210
fn independent_changes_commute_and_make_multiple_heads ( ) {
1211
hermetic ( ) ;
1212
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1213
let root = dir.path ( ) ;
1214
let repo = Repo:: init( root) .unwrap ( ) ;
1215
write ( root, " a.txt" , b " a\n " ) ;
1216
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
1217
write ( root, " b.txt" , b " b\n " ) ;
1218
let h2 = repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
1219
let c2 = repo.store ( ) .get_change ( & h2) .unwrap ( ) ;
1220
assert! ( c2.deps.is_empty ( ) , " b.txt touches nothing from h1" ) ;
1221
let mut heads = repo.bookmark_heads ( ) .unwrap ( ) ;
1222
heads.sort ( ) ;
1223
let mut want = vec! [ h1, h2] ;
1224
want.sort ( ) ;
1225
assert_eq! ( heads, want) ;
1226
1227
write ( root, " a.txt" , b " a\n a2\n " ) ;
1228
let h3 = repo.commit ( " a2" , & [ ] ) .unwrap ( ) ;
1229
assert_eq! ( repo.store ( ) .get_change ( & h3) .unwrap ( ) .deps, vec! [ h1] ) ;
1230
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
1231
assert_eq! ( heads, [ h2, h3] .into_iter ( ) .collect ( ) ) ;
1232
1233
write ( root, " c.txt" , b " c\n " ) ;
1234
let h4 = repo.commit ( " c" , & [ h2] ) .unwrap ( ) ;
1235
assert_eq! ( repo.store ( ) .get_change ( & h4) .unwrap ( ) .deps, vec! [ h2] ) ;
1236
}
1237
1238
# [ test ]
1239
fn mode_change_and_text_binary_switch ( ) {
1240
hermetic ( ) ;
1241
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1242
let root = dir.path ( ) ;
1243
let repo = Repo:: init( root) .unwrap ( ) ;
1244
write ( root, " run.sh" , b " #!/bin/sh\n " ) ;
1245
repo.commit ( " add script" , & [ ] ) .unwrap ( ) ;
1246
# [ cfg ( unix ) ]
1247
{
1248
use std:: os:: unix:: fs:: PermissionsExt;
1249
fs:: set_permissions( root.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
1250
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1251
assert_eq! (
1252
statuses ( & s) ,
1253
vec! [ ( " run.sh" .into ( ) , ChangeStatus:: ModeChanged) ]
1254
) ;
98286406 Andy McCall 1 month ago ↶
1255
let h = repo.commit ( " exec" , & [ ] ) .unwrap ( ) ;
1256
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1257
assert! ( snap.files[ " run.sh" ] .mode.exec) ;
1258
}
1259
1260
write ( root, " run.sh" , b " \0 binary" ) ;
1261
let h = repo.commit ( " binary" , & [ ] ) .unwrap ( ) ;
1262
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1263
assert! ( snap.files[ " run.sh" ] .mode.opaque) ;
1264
write ( root, " run.sh" , b " text again\n " ) ;
1265
let h = repo.commit ( " text" , & [ ] ) .unwrap ( ) ;
1266
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1267
assert! ( ! snap.files[ " run.sh" ] .mode.opaque) ;
75167672 Andy McCall 1 month ago
1268
assert! (
1269
matches! ( & snap.files[ " run.sh" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " text again\n " )
1270
) ;
98286406 Andy McCall 1 month ago ↶
1271
assert! ( ! snap.is_conflicted ( ) ) ;
1272
}
83e0677b Andy McCall 1 month ago ↶
1273
6eae06b6 Andy McCall 29 days ago ↶
1274
1275
1276
# [ cfg ( unix ) ]
1277
# [ test ]
1278
fn a_binary_file_changed_and_made_executable_at_once_keeps_both ( ) {
1279
use std:: os:: unix:: fs:: PermissionsExt;
1280
hermetic ( ) ;
1281
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1282
let root = dir.path ( ) ;
1283
let repo = Repo:: init( root) .unwrap ( ) ;
1284
write ( root, " tool.exe" , b " MZ\0 one" ) ;
1285
repo.commit ( " add" , & [ ] ) .unwrap ( ) ;
1286
write ( root, " tool.exe" , b " MZ\0 two" ) ;
1287
fs:: set_permissions( root.join ( " tool.exe" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
1288
let h = repo.commit ( " update and +x" , & [ ] ) .unwrap ( ) ;
1289
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1290
let f = & snap.files[ " tool.exe" ] ;
1291
assert! ( f.mode.opaque & & f.mode.exec, " {:?}" , f.mode) ;
1292
assert! (
1293
repo.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ,
1294
" nothing left to commit"
1295
) ;
1296
}
1297
f0a517d2 Andy McCall 20 days ago ↶
1298
1299
1300
1301
83e0677b Andy McCall 1 month ago ↶
1302
# [ test ]
f0a517d2 Andy McCall 20 days ago ↶
1303
fn a_certified_key_pushes_under_a_subject_grant ( ) {
1304
use crate :: cert:: { Certificate, SignedCertificate} ;
1305
use crate :: identity:: Scope;
1306
hermetic ( ) ;
1307
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1308
let root = dir.path ( ) ;
1309
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1310
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1311
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1312
.unwrap ( )
1313
.with_home ( root.join ( " home-a" ) ) ;
1314
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1315
.unwrap ( ) ;
1316
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1317
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1318
a.policy_init ( ) .unwrap ( ) ;
1319
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1320
let hub_sk = SecretKey:: generate( ) ;
1321
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1322
a.policy_grant_subject (
1323
" agent:claude@ada" ,
1324
hub.clone ( ) ,
1325
Scope:: CHANGES | Scope:: REFS ,
1326
None ,
1327
)
1328
.unwrap ( ) ;
1329
assert! ( a
1330
.push ( " origin" , false )
1331
.unwrap ( )
1332
.iter ( )
1333
.any ( | r | r.name = = " policy" & & r.ok ) ) ;
1334
1335
let now = Hlc:: now( ) .wall_ms;
1336
1337
1338
let agent = | name : & str , subject : & str , from : u64 , to : u64 | {
1339
let mut r = Repo:: clone( & url, & root.join ( name) )
1340
.unwrap ( )
1341
.with_home ( root.join ( format! ( " home-{name} " ) ) ) ;
1342
let ( _ , id) = r
1343
.create_identity ( name, " [email protected] " , IdentityScope:: User)
1344
.unwrap ( ) ;
1345
let cert = SignedCertificate:: sign(
1346
Certificate {
1347
key: id,
1348
issuer: hub.hash ( ) ,
1349
subject: subject.into ( ) ,
1350
claims: vec! [ ] ,
1351
scope: Scope:: CHANGES | Scope:: REFS ,
1352
not_before: from,
1353
expires: to,
a2387d54 Andy McCall 20 days ago ↶
1354
grant: None ,
f0a517d2 Andy McCall 20 days ago ↶
1355
} ,
1356
& hub,
1357
& hub_sk,
1358
) ;
1359
r.store ( ) .put_identity ( & hub) .unwrap ( ) ;
1360
r.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1361
write ( & root.join ( name) , & format! ( " {name} .txt" ) , b " agent work\n " ) ;
1362
r.commit ( " work" , & [ ] ) .unwrap ( ) ;
1363
r
1364
} ;
1365
1366
let claude = agent ( " claude" , " agent:claude@ada" , now - 60_000 , now + 600_000 ) ;
1367
let r = claude.push ( " origin" , false ) .unwrap ( ) ;
1368
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
1369
1370
a.pull ( " origin" ) .unwrap ( ) ;
1371
assert! ( a.bookmark_heads ( ) .unwrap ( ) .iter ( ) .any ( | h | a
1372
.store ( )
1373
.get_change ( h)
1374
.map ( | c | c.message = = " work" )
1375
.unwrap_or ( false ) ) ) ;
1376
f53cbe7a Andy McCall 20 days ago ↶
1377
1378
1379
1380
1381
let host = Repo:: open( & root.join ( " origin" ) )
1382
.unwrap ( )
1383
.with_acceptor ( hub.clone ( ) , hub_sk.clone ( ) ) ;
1384
let ( attest_ref, sa) = host.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
1385
host.record_acceptance ( & attest_ref) .unwrap ( ) ;
1386
let served = host.remote_refs ( ) .unwrap ( ) ;
1387
let acc = served
1388
.refs
1389
.iter ( )
1390
.find ( | r | r.name = = " main" )
1391
.unwrap ( )
1392
.accepted
1393
.clone ( )
1394
.expect ( " served with the refs" ) ;
1395
let signer = sa.sigs[ 0 ] .key;
1396
assert_eq! (
1397
a.certified_time ( & signer, & sa, Some ( & acc) ) ,
1398
acc.attest.hlc.wall_ms
1399
) ;
1400
assert_eq! ( a.certified_time ( & signer, & sa, None ) , sa.attest.hlc.wall_ms) ;
1401
let stranger_sk = SecretKey:: generate( ) ;
1402
let stranger = stranger_sk.identity ( " stranger" , " s@x" , 1 ) ;
1403
let mut forged = acc.clone ( ) ;
1404
forged.sigs = vec! [ stranger_sk.sign ( Domain:: Attest, & stranger.hash ( ) , & acc.attest.hash ( ) ) ] ;
1405
assert_eq! (
1406
a.certified_time ( & signer, & sa, Some ( & forged) ) ,
1407
sa.attest.hlc.wall_ms,
1408
" not the issuer's"
1409
) ;
1410
let mut elsewhere = acc.clone ( ) ;
1411
elsewhere.attest.body = AttestBody:: Accepted {
1412
attest: ObjectRef( [ 7 ; 32 ] ) ,
1413
} ;
1414
elsewhere.sigs = vec! [ hub_sk.sign ( Domain:: Attest, & hub.hash ( ) , & elsewhere.attest.hash ( ) ) ] ;
1415
assert_eq! (
1416
a.certified_time ( & signer, & sa, Some ( & elsewhere) ) ,
1417
sa.attest.hlc.wall_ms,
1418
" another attestation's"
1419
) ;
1420
a.pull ( " origin" ) .unwrap ( ) ;
1421
1422
host.gc ( true ) .unwrap ( ) ;
1423
assert! ( host.acceptance_of ( & attest_ref) .unwrap ( ) .is_some ( ) ) ;
1424
f0a517d2 Andy McCall 20 days ago ↶
1425
let late = agent ( " late" , " agent:claude@ada" , now - 1_200_000 , now - 600_000 ) ;
1426
let r = late.push ( " origin" , false ) .unwrap ( ) ;
1427
assert! (
1428
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " may not update" ) ,
1429
" an expired certificate: {r:?}"
1430
) ;
1431
let other = agent ( " other" , " agent:mallory@ada" , now - 60_000 , now + 600_000 ) ;
1432
let r = other.push ( " origin" , false ) .unwrap ( ) ;
1433
assert! (
1434
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " may not update" ) ,
1435
" a subject not granted: {r:?}"
1436
) ;
1437
}
1438
d8524a9f Andy McCall 18 days ago ↶
1439
1440
1441
1442
1443
# [ test ]
1444
fn a_tag_signed_by_an_earlier_short_lived_key_is_pushed ( ) {
1445
use crate :: cert:: { Certificate, SignedCertificate} ;
1446
use crate :: identity:: Scope;
1447
hermetic ( ) ;
1448
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1449
let root = dir.path ( ) ;
1450
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1451
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1452
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1453
.unwrap ( )
1454
.with_home ( root.join ( " home-a" ) ) ;
1455
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1456
.unwrap ( ) ;
1457
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1458
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1459
a.policy_init ( ) .unwrap ( ) ;
1460
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1461
let hub_sk = SecretKey:: generate( ) ;
1462
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1463
a.policy_grant_subject (
1464
" agent:claude@ada" ,
1465
hub.clone ( ) ,
1466
Scope:: CHANGES | Scope:: REFS ,
1467
Some ( " *" ) ,
1468
)
1469
.unwrap ( ) ;
1470
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1471
1472
let now = Hlc:: now( ) .wall_ms;
1473
let mut agent = Repo:: clone( & url, & root.join ( " agent" ) )
1474
.unwrap ( )
1475
.with_home ( root.join ( " home-agent" ) ) ;
1476
agent.store ( ) .put_identity ( & hub) .unwrap ( ) ;
1477
let certify = | agent : & Repo, id | {
1478
let cert = SignedCertificate:: sign(
1479
Certificate {
1480
key: id,
1481
issuer: hub.hash ( ) ,
1482
subject: " agent:claude@ada" .into ( ) ,
1483
claims: vec! [ ] ,
1484
scope: Scope:: CHANGES | Scope:: REFS ,
1485
not_before: now - 60_000 ,
1486
expires: now + 600_000 ,
1487
grant: None ,
1488
} ,
1489
& hub,
1490
& hub_sk,
1491
) ;
1492
agent.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1493
} ;
1494
1495
let ( first, first_id) = agent
1496
.create_identity ( " claude" , " [email protected] " , IdentityScope:: User)
1497
.unwrap ( ) ;
1498
certify ( & agent, first_id) ;
1499
let heads = agent.bookmark_heads ( ) .unwrap ( ) ;
1500
agent.tag_create ( " v1" , & heads, " first" ) .unwrap ( ) ;
1501
1502
let ( second, second_id) = agent
1503
.create_identity ( " claude" , " [email protected] " , IdentityScope:: User)
1504
.unwrap ( ) ;
1505
assert_ne! ( first, second) ;
1506
certify ( & agent, second_id) ;
1507
agent
1508
.set_user_config ( " author.signer" , & second.to_hex ( ) )
1509
.unwrap ( ) ;
1510
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
1ec6fdbc Andy McCall 17 days ago ↶
1511
assert! (
1512
host.store ( ) .get_identity ( & first) .is_err ( ) ,
1513
" the host has never seen the tag's key"
1514
) ;
d8524a9f Andy McCall 18 days ago ↶
1515
let r = agent.push ( " origin" , false ) .unwrap ( ) ;
1ec6fdbc Andy McCall 17 days ago ↶
1516
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
1517
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
1518
assert! (
1519
host.tag_refs ( ) .unwrap ( ) .iter ( ) .any ( | t | t.name = = " v1" ) ,
1520
" the tag arrived"
1521
) ;
1522
}
1523
1524
1525
1526
1527
1528
1529
1530
# [ test ]
1531
fn a_short_lived_key_s_tag_is_fetched_at_its_issuer_s_acceptance ( ) {
1532
use crate :: cert:: { Certificate, SignedCertificate} ;
1533
use crate :: identity:: Scope;
1534
hermetic ( ) ;
1535
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1536
let root = dir.path ( ) ;
1537
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1538
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1539
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1540
.unwrap ( )
1541
.with_home ( root.join ( " home-a" ) ) ;
1542
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1543
.unwrap ( ) ;
1544
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1545
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1546
a.policy_init ( ) .unwrap ( ) ;
1547
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1548
let hub_sk = SecretKey:: generate( ) ;
1549
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1550
a.policy_grant_subject (
1551
" agent:claude@ada" ,
1552
hub.clone ( ) ,
1553
Scope:: CHANGES | Scope:: REFS ,
1554
Some ( " *" ) ,
1555
)
1556
.unwrap ( ) ;
1557
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1558
1559
let now = Hlc:: now( ) .wall_ms;
1560
1561
let agent = | name : & str , from : u64 , to : u64 | {
1562
let mut r = Repo:: clone( & url, & root.join ( name) )
1563
.unwrap ( )
1564
.with_home ( root.join ( format! ( " home-{name} " ) ) ) ;
1565
r.store ( ) .put_identity ( & hub) .unwrap ( ) ;
1566
let ( _ , id) = r
1567
.create_identity ( name, " [email protected] " , IdentityScope:: User)
1568
.unwrap ( ) ;
1569
let cert = SignedCertificate:: sign(
1570
Certificate {
1571
key: id,
1572
issuer: hub.hash ( ) ,
1573
subject: " agent:claude@ada" .into ( ) ,
1574
claims: vec! [ ] ,
1575
scope: Scope:: CHANGES | Scope:: REFS ,
1576
not_before: from,
1577
expires: to,
1578
grant: None ,
1579
} ,
1580
& hub,
1581
& hub_sk,
1582
) ;
1583
r.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1584
r
1585
} ;
1586
1587
1588
1589
1590
let claude = agent ( " claude" , now - 60_000 , now + 600_000 ) ;
1591
let heads = claude.bookmark_heads ( ) .unwrap ( ) ;
1592
claude.tag_create ( " v1" , & heads, " one" ) .unwrap ( ) ;
1593
let r = claude.push ( " origin" , false ) .unwrap ( ) ;
d8524a9f Andy McCall 18 days ago ↶
1594
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
1ec6fdbc Andy McCall 17 days ago ↶
1595
let b = Repo:: clone( & url, & root.join ( " b" ) )
1596
.unwrap ( )
1597
.with_home ( root.join ( " home-b" ) ) ;
1598
let rep = b.fetch ( " origin" ) .unwrap ( ) ;
1599
assert! (
1600
rep.skipped
1601
.iter ( )
1602
.any ( | ( n , why ) | n = = " tags/v1" & & why.contains ( " no time to check its certificate at" ) ) ,
1603
" {rep:?}"
1604
) ;
1605
assert_eq! ( b.tag_heads ( " v1" ) .unwrap ( ) , None ) ;
1606
1607
1608
1609
let host = Repo:: open( & root.join ( " origin" ) )
1610
.unwrap ( )
1611
.with_acceptor ( hub.clone ( ) , hub_sk.clone ( ) ) ;
1612
let ( v1, _ ) = host.tag_attest ( " v1" ) .unwrap ( ) .unwrap ( ) ;
1613
host.record_acceptance ( & v1) .unwrap ( ) ;
1614
let served = host.tag_refs ( ) .unwrap ( ) ;
1615
assert! ( served
1616
.iter ( )
1617
.any ( | t | t.name = = " v1" & & t.accepted.is_some ( ) ) ) ;
1618
let rep = b.fetch ( " origin" ) .unwrap ( ) ;
1619
assert! ( rep.skipped.is_empty ( ) , " {rep:?}" ) ;
1620
assert_eq! ( b.tag_heads ( " v1" ) .unwrap ( ) , Some ( heads.clone ( ) ) ) ;
1621
1622
host.gc ( true ) .unwrap ( ) ;
1623
assert! ( host.acceptance_of ( & v1) .unwrap ( ) .is_some ( ) ) ;
1624
1625
1626
1627
let late = agent ( " late" , now - 7_200_000 , now - 3_600_000 ) ;
1628
late.tag_create ( " v2" , & heads, " two" ) .unwrap ( ) ;
1629
let r = late.push ( " origin" , false ) .unwrap ( ) ;
1630
assert! (
1631
r.iter ( )
1632
.any ( | x | x.name = = " tags/v2" & & ! x.ok & & x.reason.contains ( " may not create" ) ) ,
1633
" {r:?}"
1634
) ;
1635
1636
1637
1638
let ( v2, _ ) = late.tag_attest ( " v2" ) .unwrap ( ) .unwrap ( ) ;
1639
let acc = Attest {
1640
repo: late.id ( ) .unwrap ( ) ,
1641
hlc: Hlc {
1642
wall_ms: now - 5_400_000 ,
1643
logical: 0 ,
1644
} ,
1645
body: AttestBody:: Accepted { attest: v2 } ,
1646
} ;
1647
let sig = hub_sk.sign ( Domain:: Attest, & hub.hash ( ) , & acc.hash ( ) ) ;
1648
1649
let stranger_sk = SecretKey:: generate( ) ;
1650
let stranger = stranger_sk.identity ( " stranger" , " [email protected] " , 1 ) ;
1651
let forged = stranger_sk.sign ( Domain:: Attest, & stranger.hash ( ) , & acc.hash ( ) ) ;
1652
late.keep_acceptance (
1653
& v2,
1654
& SignedAttest {
1655
attest: acc.clone ( ) ,
1656
sigs: vec! [ forged] ,
1657
} ,
1658
)
1659
.unwrap ( ) ;
1660
let r = late.push ( " origin" , false ) .unwrap ( ) ;
1661
assert! (
1662
r.iter ( ) .any ( | x | x.name = = " tags/v2" & & ! x.ok ) ,
1663
" not the issuer's: {r:?}"
1664
) ;
1665
let theirs = SignedAttest {
1666
attest: acc,
1667
sigs: vec! [ sig] ,
1668
} ;
1669
1670
late.keep_acceptance ( & v2, & theirs) .unwrap ( ) ;
1671
let r = late.push ( " origin" , false ) .unwrap ( ) ;
1672
1673
1674
assert! ( r.iter ( ) .any ( | x | x.name = = " tags/v2" & & x.ok ) , " {r:?}" ) ;
d8524a9f Andy McCall 18 days ago ↶
1675
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
1ec6fdbc Andy McCall 17 days ago ↶
1676
let ( hv2, _ ) = host.tag_attest ( " v2" ) .unwrap ( ) .unwrap ( ) ;
1677
assert_eq! ( hv2, v2) ;
1678
assert! ( host
1679
.store ( )
1680
.index_get_pub ( " accepted" , & v2)
1681
.unwrap ( )
1682
.contains ( & theirs.attest.hash ( ) ) ) ;
1683
let c = Repo:: clone( & url, & root.join ( " c" ) )
1684
.unwrap ( )
1685
.with_home ( root.join ( " home-c" ) ) ;
1686
assert_eq! ( c.tag_heads ( " v2" ) .unwrap ( ) , Some ( heads.clone ( ) ) ) ;
1687
assert_eq! ( c.tag_heads ( " v1" ) .unwrap ( ) , Some ( heads) ) ;
d8524a9f Andy McCall 18 days ago ↶
1688
}
1689
d36ba2f7 Andy McCall 18 days ago ↶
1690
1691
1692
1693
1694
1695
# [ test ]
1696
fn a_bookmark_moved_by_a_certified_key_alone_is_fetched ( ) {
1697
use crate :: cert:: { Certificate, SignedCertificate} ;
1698
use crate :: identity:: Scope;
1699
hermetic ( ) ;
1700
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1701
let root = dir.path ( ) ;
1702
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1703
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1704
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1705
.unwrap ( )
1706
.with_home ( root.join ( " home-a" ) ) ;
1707
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1708
.unwrap ( ) ;
1709
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1710
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1711
a.policy_init ( ) .unwrap ( ) ;
1712
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1713
let hub_sk = SecretKey:: generate( ) ;
1714
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1715
a.policy_grant_subject (
1716
" agent:claude@ada" ,
1717
hub.clone ( ) ,
1718
Scope:: CHANGES | Scope:: REFS ,
1719
None ,
1720
)
1721
.unwrap ( ) ;
1722
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1723
1724
let now = Hlc:: now( ) .wall_ms;
1725
let mut agent = Repo:: clone( & url, & root.join ( " agent" ) )
1726
.unwrap ( )
1727
.with_home ( root.join ( " home-agent" ) ) ;
1728
let ( key, id) = agent
1729
.create_identity ( " claude" , " [email protected] " , IdentityScope:: User)
1730
.unwrap ( ) ;
1731
let cert = SignedCertificate:: sign(
1732
Certificate {
1733
key: id,
1734
issuer: hub.hash ( ) ,
1735
subject: " agent:claude@ada" .into ( ) ,
1736
claims: vec! [ ] ,
1737
scope: Scope:: CHANGES | Scope:: REFS ,
1738
not_before: now - 60_000 ,
1739
expires: now + 600_000 ,
1740
grant: None ,
1741
} ,
1742
& hub,
1743
& hub_sk,
1744
) ;
1745
agent.store ( ) .put_identity ( & hub) .unwrap ( ) ;
1746
agent.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1747
let heads = agent.bookmark_heads ( ) .unwrap ( ) ;
1748
agent.bookmark_create ( " agent-work" , Some ( & heads) ) .unwrap ( ) ;
1749
let r = agent.push_bookmark ( " origin" , " agent-work" , false ) .unwrap ( ) ;
1750
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
1751
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
1752
let ( _ , sa) = host.latest_ref_attest ( " agent-work" ) .unwrap ( ) .unwrap ( ) ;
1753
assert_eq! ( sa.sigs[ 0 ] .key, key, " the certified key alone moved it" ) ;
1754
1755
1756
let bundle = host.bundle_raw_for ( & [ ] , None ) .unwrap ( ) ;
1757
assert! ( bundle.changes.is_empty ( ) & & bundle.raw_changes.is_empty ( ) ) ;
1758
assert_eq! ( bundle.certificates.len ( ) , 1 ) ;
1759
for k in [ key, hub.hash ( ) ] {
1760
assert! ( bundle
1761
.objects
1762
.iter ( )
1763
.any ( | ( t , b ) | * t = = ObjectType:: Identity & & hash_object ( * t, b) = = k) ) ;
1764
}
1765
1766
let c = Repo:: clone( & url, & root.join ( " c" ) ) .unwrap ( ) ;
1767
let tracked = c.view ( ) .unwrap ( ) .remotes[ " origin" ] .clone ( ) ;
1768
assert_eq! (
1769
tracked.get ( " agent-work" ) ,
1770
host.view ( ) .unwrap ( ) .bookmarks.get ( " agent-work" )
1771
) ;
1772
assert! ( c.store ( ) .certificate_of ( & key) .unwrap ( ) .is_some ( ) ) ;
1773
}
1774
028545df Andy McCall 19 days ago ↶
1775
1776
1777
1778
1779
1780
1781
# [ test ]
1782
fn a_fetched_attestation_is_judged_at_its_integrated_time ( ) {
1783
use super :: transparency:: LogCheck;
1784
use crate :: cert:: { Certificate, SignedCertificate} ;
1785
use crate :: identity:: Scope;
1786
use crate :: protocol:: { Integration, Logged, RefsResponse, RemoteRef} ;
1787
use crate :: tlog:: { self , Checkpoint, Entry, Hash, Integrated, Note} ;
1788
hermetic ( ) ;
1789
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1790
let root = dir.path ( ) ;
1791
let mut a = Repo:: init( & root.join ( " a" ) )
1792
.unwrap ( )
1793
.with_home ( root.join ( " home-a" ) ) ;
1794
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1795
.unwrap ( ) ;
1796
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1797
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1798
a.policy_init ( ) .unwrap ( ) ;
1799
let issuer_sk = SecretKey:: generate( ) ;
1800
let issuer = issuer_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1801
a.policy_grant_subject (
1802
" agent:claude@ada" ,
1803
issuer.clone ( ) ,
1804
Scope:: CHANGES | Scope:: REFS ,
1805
None ,
1806
)
1807
.unwrap ( ) ;
1808
let p = a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy;
1809
1810
1811
let now = Hlc:: now( ) .wall_ms;
1812
let t0 = now - 2 * 3_600_000 ;
1813
let agent_sk = SecretKey:: generate( ) ;
1814
let agent = agent_sk.identity ( " claude" , " [email protected] " , t0) ;
1815
let cert = SignedCertificate:: sign(
1816
Certificate {
1817
key: agent.clone ( ) ,
1818
issuer: issuer.hash ( ) ,
1819
subject: " agent:claude@ada" .into ( ) ,
1820
claims: vec! [ ] ,
1821
scope: Scope:: CHANGES | Scope:: REFS ,
1822
not_before: t0,
1823
expires: t0 + 600_000 ,
1824
grant: None ,
1825
} ,
1826
& issuer,
1827
& issuer_sk,
1828
) ;
1829
a.store ( ) .put_identity ( & agent) .unwrap ( ) ;
1830
a.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1831
let signer = agent.hash ( ) ;
1832
let attested = | claimed : u64 | {
1833
let attest = Attest {
1834
repo: a.id ( ) .unwrap ( ) ,
1835
hlc: Hlc {
1836
wall_ms: claimed,
1837
logical: 0 ,
1838
} ,
1839
body: AttestBody:: Ref {
1840
name: " main" .into ( ) ,
1841
old_state: ObjectRef( [ 1 ; 32 ] ) ,
1842
new_state: ObjectRef( [ 2 ; 32 ] ) ,
1843
serial: 1 ,
1844
} ,
1845
} ;
1846
let sig = agent_sk.sign ( Domain:: Attest, & signer, & attest.hash ( ) ) ;
1847
SignedAttest {
1848
attest,
1849
sigs: vec! [ sig] ,
1850
}
1851
} ;
1852
1853
1854
1855
let log_sk = SecretKey:: generate( ) ;
1856
let log_key = log_sk.identity ( " hub.example" , " [email protected] " , 1 ) ;
1857
let origin = " hub.example/log" ;
1858
let hub = " https://hub.example" ;
1859
let url = " https://hub.example/ada/a" ;
1860
let w_sk = SecretKey:: generate( ) ;
1861
let w = w_sk.identity ( " w" , " [email protected] " , 1 ) ;
1862
let stranger_sk = SecretKey:: generate( ) ;
1863
let stranger = stranger_sk.identity ( " s" , " [email protected] " , 1 ) ;
1864
let sa = attested ( t0 + 300_000 ) ;
1865
let entry_of = | i : u64 | {
1866
let ( kind, object) = if i = = 3 {
1867
( " ref" , sa.attest.hash ( ) )
1868
} else {
1869
( " seal" , ObjectRef( [ i as u8 ; 32 ] ) )
1870
} ;
1871
Entry {
1872
kind: kind.into ( ) ,
1873
repo: sa.attest.repo,
1874
object,
1875
signers: vec! [ signer] ,
1876
received: t0 + 300_000 ,
1877
}
1878
.encode ( )
1879
} ;
1880
let mut stored: Vec< Hash> = Vec :: new( ) ;
1881
for i in 0 .. 8 {
1882
let add = tlog:: stored_hashes_for_leaf( i, tlog:: leaf_hash( & entry_of ( i) ) , & stored) .unwrap ( ) ;
1883
stored.extend ( add) ;
1884
}
1885
let note_of = | n : u64 , cosigners : & [( & str , & Identity, & SecretKey, u64 ) ]| {
1886
let mut note = Checkpoint {
1887
origin: origin.into ( ) ,
1888
size: n,
1889
root: tlog:: tree_hash( n, & stored) .unwrap ( ) ,
1890
extensions: vec! [ ] ,
1891
}
1892
.sign ( & log_key, & log_sk)
1893
.unwrap ( ) ;
1894
for ( name, id, sk, t) in cosigners {
1895
note.sigs
1896
.extend ( tlog:: cosign( & note.text, name, id, sk, * t) .unwrap ( ) ) ;
1897
}
1898
note.encode ( )
1899
} ;
1900
let latest = note_of ( 8 , & [ ] ) ;
1901
let witnessed_at = ( t0 + 360_000 ) / 1000 ;
1902
let cosigned = note_of (
1903
4 ,
1904
& [
1905
( " w.example" , & w, & w_sk, witnessed_at) ,
1906
( " s.example" , & stranger, & stranger_sk, 1 ) ,
1907
] ,
1908
) ;
1909
let answer = | integration : Vec< Integration> , witnesses : Vec< ( String, Vec< u8 > ) > | RefsResponse {
1910
refs: vec! [ RemoteRef {
1911
name: " main" .into ( ) ,
1912
state: ObjectRef( [ 2 ; 32 ] ) ,
1913
heads: vec! [ ] ,
1914
attestation: Some ( sa.clone ( ) ) ,
1915
last_serial: None ,
1916
accepted: None ,
1917
logged: Some ( Logged {
1918
index: 3 ,
1919
entry: entry_of ( 3 ) ,
1920
proof: tlog:: prove_inclusion( 8 , 3 , & stored) .unwrap ( ) ,
1921
} ) ,
1922
integration,
1923
} ] ,
1924
log: Some ( latest.clone ( ) ) ,
1925
witnesses,
1926
.. RefsResponse:: default( )
1927
} ;
1928
let proof4 = tlog:: prove_inclusion( 4 , 3 , & stored) .unwrap ( ) ;
1929
let served = vec! [ Integration {
1930
note: cosigned.clone ( ) ,
1931
proof: proof4.clone ( ) ,
1932
} ] ;
1933
let listed = vec! [ ( " w.example" .to_string ( ) , w.encode ( ) ) ] ;
1934
1935
a.remember_log ( hub, & log_key, & Note:: parse( & latest) .unwrap ( ) )
1936
.unwrap ( ) ;
1937
1938
1939
1940
let resp = answer ( served.clone ( ) , listed.clone ( ) ) ;
1941
let lc = a.check_log ( url, None , & resp) .unwrap ( ) ;
1942
assert! ( lc.notes.is_empty ( ) , " {:?}" , lc.notes) ;
1943
let t = lc.time_of ( " main" ) .unwrap ( ) ;
1944
assert_eq! ( ( t.time, t.witness.as_str ( ) ) , ( witnessed_at, " w.example" ) ) ;
1945
1946
1947
assert! ( cert.cert.expires < now) ;
1948
let mut notes = Vec :: new( ) ;
1949
a.authorize_in_time ( & p, " origin" , & resp.refs[ 0 ] , & sa, & signer, & lc, & mut notes)
1950
.unwrap ( ) ;
1951
assert! ( notes.is_empty ( ) , " {notes:?}" ) ;
1952
1953
1954
1955
let lonely = vec! [ Integration {
1956
note: note_of ( 4 , & [ ( " s.example" , & stranger, & stranger_sk, 1 ) ] ) ,
1957
proof: proof4.clone ( ) ,
1958
} ] ;
1959
let resp = answer ( lonely, vec! [ ] ) ;
1960
let lc = a.check_log ( url, None , & resp) .unwrap ( ) ;
1961
assert! ( lc
1962
.time_of ( " main" )
1963
.unwrap_err ( )
1964
.contains ( " no witness this user trusts" ) ) ;
1965
let mut notes = Vec :: new( ) ;
1966
a.authorize_in_time ( & p, " origin" , & resp.refs[ 0 ] , & sa, & signer, & lc, & mut notes)
1967
.unwrap ( ) ;
1968
assert! (
1969
notes.len ( ) = = 1 & & notes[ 0 ] .contains ( " not at a witnessed time" ) ,
1970
" {notes:?}"
1971
) ;
1972
1973
1974
1975
let resp = answer (
1976
served.clone ( ) ,
1977
vec! [ ( " w.example" .into ( ) , stranger.encode ( ) ) ] ,
1978
) ;
1979
let lc = a.check_log ( url, None , & resp) .unwrap ( ) ;
1980
assert! (
1981
lc.notes.iter ( ) .any ( | n | n.contains ( " another key" ) ) ,
1982
" {:?}" ,
1983
lc.notes
1984
) ;
1985
assert! (
1986
lc.time_of ( " main" ) .is_err ( ) ,
1987
" w's cosignature is by its old key"
1988
) ;
1989
a.check_log ( url, None , & answer ( served.clone ( ) , listed.clone ( ) ) )
1990
.unwrap ( ) ;
1991
1992
1993
1994
let mut old = answer ( vec! [ ] , vec! [ ] ) ;
1995
old.log = None ;
1996
old.refs[ 0 ] .logged = None ;
1997
let lc = a.check_log ( url, None , & old) .unwrap ( ) ;
1998
assert! ( ! lc.kept & & lc.notes.is_empty ( ) ) ;
1999
let mut notes = Vec :: new( ) ;
2000
a.authorize_in_time ( & p, " origin" , & old.refs[ 0 ] , & sa, & signer, & lc, & mut notes)
2001
.unwrap ( ) ;
2002
assert! ( notes.is_empty ( ) , " {notes:?}" ) ;
2003
2004
2005
let at = | secs_after_t0 : u64 | {
2006
let mut times = std:: collections:: HashMap:: new( ) ;
2007
times.insert (
2008
" main" .to_string ( ) ,
2009
Ok ( Integrated {
2010
time: ( t0 + secs_after_t0 * 1000 ) / 1000 ,
2011
witness: " w.example" .into ( ) ,
2012
size: 4 ,
2013
} ) ,
2014
) ;
2015
LogCheck {
2016
kept: true ,
2017
times,
2018
.. LogCheck:: default( )
2019
}
2020
} ;
2021
let judged = | sa : & SignedAttest, lc : & LogCheck| {
2022
let mut notes = Vec :: new( ) ;
2023
a.authorize_in_time ( & p, " origin" , & old.refs[ 0 ] , sa, & signer, lc, & mut notes)
2024
.map ( | _| notes )
2025
} ;
2026
2027
2028
2029
let notes = judged ( & sa, & at ( 3600 ) ) .unwrap ( ) ;
2030
assert! (
2031
notes
2032
.iter ( )
2033
.any ( | n | n.contains ( " had lapsed when witness w.example" ) )
2034
& & notes
2035
.iter ( )
2036
.any ( | n | n.contains ( " 55 minutes before witness" ) ) ,
2037
" {notes:?}"
2038
) ;
2039
2040
2041
2042
let fast = attested ( now) ;
2043
let notes = judged ( & fast, & at ( 400 ) ) .unwrap ( ) ;
2044
assert! (
2045
notes.len ( ) = = 1 & & notes[ 0 ] .contains ( " after witness w.example saw it" ) ,
2046
" {notes:?}"
2047
) ;
2048
2049
assert! ( judged ( & fast, & at ( 3600 ) ) .is_err ( ) ) ;
2050
2051
assert! ( judged ( & sa, & at ( 500 ) ) .unwrap ( ) .is_empty ( ) ) ;
2052
}
2053
a2387d54 Andy McCall 20 days ago ↶
2054
2055
2056
2057
45a6f5ec Andy McCall 13 days ago ↶
2058
2059
2060
f0a517d2 Andy McCall 20 days ago ↶
2061
# [ test ]
45a6f5ec Andy McCall 13 days ago ↶
2062
fn a_grant_outlives_its_owner_s_rotation ( ) {
2063
use crate :: cert:: { Certificate, Grant, SignedCertificate, SignedGrant} ;
2064
use crate :: identity:: Scope;
2065
hermetic ( ) ;
2066
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2067
let root = dir.path ( ) ;
2068
let mut a = Repo:: init( & root.join ( " a" ) )
2069
.unwrap ( )
2070
.with_home ( root.join ( " home-a" ) ) ;
2071
let ( old, _ ) = a
2072
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
2073
.unwrap ( ) ;
2074
let ( _ , old_sk) = a.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
2075
let old_id = a.identity ( & old) .unwrap ( ) ;
2076
let hub_sk = SecretKey:: generate( ) ;
2077
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
2078
let now = Hlc:: now( ) .wall_ms;
2079
let grant = | from : u64 | {
2080
SignedGrant:: sign(
2081
Grant {
2082
grantor: old,
2083
subject: " agent:claude@ada" .into ( ) ,
2084
issuer: hub.hash ( ) ,
2085
scope: Scope:: CHANGES | Scope:: SEAL ,
2086
not_before: from,
2087
expires: now + 86_400_000 ,
2088
} ,
2089
& old_id,
2090
& old_sk,
2091
)
2092
} ;
2093
let before = grant ( now - 60_000 ) ;
2094
let after = grant ( now + 60_000 ) ;
2095
let rot = a.renew_identity ( None ) .unwrap ( ) ;
2096
assert_ne! ( rot.new, old) ;
2097
let agent = | name : & str , g : SignedGrant| {
2098
let mut r = Repo:: init( & root.join ( name) )
2099
.unwrap ( )
2100
.with_home ( root.join ( format! ( " home-{name} " ) ) ) ;
2101
let ( key, id) = r
2102
.create_identity ( name, " [email protected] " , IdentityScope:: User)
2103
.unwrap ( ) ;
2104
for i in [ & old_id, & rot.identity, & hub] {
2105
r.store ( ) .put_identity ( i) .unwrap ( ) ;
2106
}
2107
r.set_user_config ( " author.key" , & rot.new.to_hex ( ) ) .unwrap ( ) ;
2108
r.set_user_config ( " author.signer" , & key.to_hex ( ) ) .unwrap ( ) ;
2109
let cert = SignedCertificate:: sign(
2110
Certificate {
2111
key: id,
2112
issuer: hub.hash ( ) ,
2113
subject: " agent:claude@ada" .into ( ) ,
2114
claims: vec! [ ] ,
2115
scope: Scope:: CHANGES | Scope:: REFS | Scope:: SEAL ,
2116
not_before: now - 60_000 ,
2117
expires: now + 600_000 ,
2118
grant: Some ( g) ,
2119
} ,
2120
& hub,
2121
& hub_sk,
2122
) ;
2123
r.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
2124
write ( & root.join ( name) , " work.txt" , b " agent work\n " ) ;
2125
let h = r.commit ( " work" , & [ ] ) .unwrap ( ) ;
2126
assert_eq! ( r.store ( ) .get_change_meta ( & h) .unwrap ( ) .author, rot.new) ;
2127
r.seal ( & [ h] ) .unwrap ( ) ;
2128
( r, h)
2129
} ;
2130
let ( r, h) = agent ( " kept" , before) ;
2131
assert! (
2132
matches! (
2133
r.authenticate_change ( & h) .unwrap ( ) ,
2134
SignatureStatus:: Valid { .. }
2135
) ,
2136
" {:?}" ,
2137
r.authenticate_change ( & h) .unwrap ( )
2138
) ;
2139
let ( r, h) = agent ( " late" , after) ;
2140
assert! (
2141
matches! ( r.authenticate_change ( & h) .unwrap ( ) , SignatureStatus:: Invalid( e) if e.contains ( " neither" ) ) ,
2142
" a grant the old key made after the rotation: {:?}" ,
2143
r.authenticate_change ( & h) .unwrap ( )
2144
) ;
2145
}
2146
2147
# [ test ]
a2387d54 Andy McCall 20 days ago ↶
2148
fn a_certified_key_acts_for_its_author_under_a_grant ( ) {
2149
use crate :: cert:: { Certificate, Grant, SignedCertificate, SignedGrant} ;
2150
use crate :: identity:: { Identity, Scope} ;
2151
hermetic ( ) ;
2152
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2153
let root = dir.path ( ) ;
2154
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
2155
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
2156
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
2157
.unwrap ( )
2158
.with_home ( root.join ( " home-a" ) ) ;
2159
let ( ada, _ ) = a
2160
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
2161
.unwrap ( ) ;
2162
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
2163
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
2164
a.policy_init ( ) .unwrap ( ) ;
2165
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
2166
let hub_sk = SecretKey:: generate( ) ;
2167
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
2168
a.policy_grant_subject (
2169
" agent:claude@ada" ,
2170
hub.clone ( ) ,
2171
Scope:: CHANGES | Scope:: REFS ,
2172
None ,
2173
)
2174
.unwrap ( ) ;
2175
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
2176
let ( _ , ada_sk) = a.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
2177
let ada_id = a.identity ( & ada) .unwrap ( ) ;
2178
2179
let now = Hlc:: now( ) .wall_ms;
2180
let grant = | by : & Identity, sk : & SecretKey, from : u64 , to : u64 | {
2181
SignedGrant:: sign(
2182
Grant {
2183
grantor: by.hash ( ) ,
2184
subject: " agent:claude@ada" .into ( ) ,
2185
issuer: hub.hash ( ) ,
2186
scope: Scope:: CHANGES | Scope:: SEAL ,
2187
not_before: from,
2188
expires: to,
2189
} ,
2190
by,
2191
sk,
2192
)
2193
} ;
2194
2195
2196
let agent = | name : & str , g : Option< SignedGrant> | {
2197
let mut r = Repo:: clone( & url, & root.join ( name) )
2198
.unwrap ( )
2199
.with_home ( root.join ( format! ( " home-{name} " ) ) ) ;
2200
let ( key, id) = r
2201
.create_identity ( name, " [email protected] " , IdentityScope:: User)
2202
.unwrap ( ) ;
2203
r.set_user_config ( " author.key" , & ada.to_hex ( ) ) .unwrap ( ) ;
2204
r.set_user_config ( " author.signer" , & key.to_hex ( ) ) .unwrap ( ) ;
2205
let cert = SignedCertificate:: sign(
2206
Certificate {
2207
key: id,
2208
issuer: hub.hash ( ) ,
2209
subject: " agent:claude@ada" .into ( ) ,
2210
claims: vec! [ ] ,
2211
scope: Scope:: CHANGES | Scope:: REFS | Scope:: SEAL ,
2212
not_before: now - 60_000 ,
2213
expires: now + 600_000 ,
2214
grant: g,
2215
} ,
2216
& hub,
2217
& hub_sk,
2218
) ;
2219
r.store ( ) .put_identity ( & hub) .unwrap ( ) ;
2220
r.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
2221
write ( & root.join ( name) , & format! ( " {name} .txt" ) , b " agent work\n " ) ;
2222
let h = r.commit ( " work" , & [ ] ) .unwrap ( ) ;
2223
assert_eq! ( r.store ( ) .get_change_meta ( & h) .unwrap ( ) .author, ada) ;
2224
( r, key, h)
2225
} ;
2226
2227
let ( claude, key, h) = agent (
2228
" claude" ,
2229
Some ( grant ( & ada_id, & ada_sk, now - 60_000 , now + 86_400_000 ) ) ,
2230
) ;
2231
let r = claude.push ( " origin" , false ) .unwrap ( ) ;
2232
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
2233
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
68dcf21f Andy McCall 20 days ago ↶
2234
assert! (
2235
matches! (
2236
host.authenticate_change ( & h) .unwrap ( ) ,
2237
SignatureStatus:: Valid { key: k, sealed: true , name, .. } if k = = key & & name = = " agent:claude@ada"
2238
) ,
2239
" named by its certificate's subject"
2240
) ;
a2387d54 Andy McCall 20 days ago ↶
2241
a.pull ( " origin" ) .unwrap ( ) ;
2242
assert! ( matches! (
2243
a.authenticate_change ( & h) .unwrap ( ) ,
2244
SignatureStatus:: Valid { key: k, .. } if k = = key
2245
) ) ;
2246
2247
let ( none, _ , _ ) = agent ( " none" , None ) ;
2248
let r = none.push ( " origin" , false ) .unwrap ( ) ;
2249
assert! ( r.iter ( ) .all ( | x | ! x.ok) , " no grant: {r:?}" ) ;
2250
let mallory_sk = SecretKey:: generate( ) ;
2251
let mallory = mallory_sk.identity ( " Mallory" , " [email protected] " , 1 ) ;
2252
let ( stranger, _ , _ ) = agent (
2253
" stranger" ,
2254
Some ( grant ( & mallory, & mallory_sk, now - 60_000 , now + 600_000 ) ) ,
2255
) ;
2256
let r = stranger.push ( " origin" , false ) .unwrap ( ) ;
2257
assert! ( r.iter ( ) .all ( | x | ! x.ok) , " a stranger's grant: {r:?}" ) ;
2258
let ( ended, _ , eh) = agent (
2259
" ended" ,
2260
Some ( grant ( & ada_id, & ada_sk, now - 7_200_000 , now - 3_600_000 ) ) ,
2261
) ;
2262
let r = ended.push ( " origin" , false ) .unwrap ( ) ;
2263
assert! ( r.iter ( ) .all ( | x | ! x.ok) , " an ended grant: {r:?}" ) ;
2264
assert! ( matches! (
2265
ended.authenticate_change ( & eh) .unwrap ( ) ,
2266
SignatureStatus:: Invalid( e) if e.contains ( " grant did not cover" )
2267
) ) ;
2268
}
2269
2270
# [ test ]
83e0677b Andy McCall 1 month ago ↶
2271
fn trust_policy_governs_pushes_and_fetches ( ) {
2272
hermetic ( ) ;
2273
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2274
let root = dir.path ( ) ;
2275
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
2276
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
2277
2278
75167672 Andy McCall 1 month ago
2279
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
2280
.unwrap ( )
2281
.with_home ( root.join ( " home-a" ) ) ;
2282
let ( key_a, _ ) = a
2283
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
2284
.unwrap ( ) ;
2285
let mut b = Repo:: clone( & url, & root.join ( " b" ) )
2286
.unwrap ( )
2287
.with_home ( root.join ( " home-b" ) ) ;
83e0677b Andy McCall 1 month ago ↶
2288
write ( & root.join ( " b" ) , " b.txt" , b " b\n " ) ;
2289
b.commit ( " unsigned before policy" , & [ ] ) .unwrap ( ) ;
2290
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
2291
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2292
2293
2294
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
2295
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
2296
let sp = a.policy_init ( ) .unwrap ( ) ;
2297
assert_eq! ( sp.policy.serial, 1 ) ;
2298
a.pull ( " origin" ) .unwrap ( ) ;
2299
let results = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
2300
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2301
assert! ( results.iter ( ) .all ( | r | r.ok ) , " {results:?}" ) ;
2302
assert_eq! ( origin.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 1 ) ;
2303
2304
2305
b.pull ( " origin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2306
assert! (
2307
b.current_policy ( ) .unwrap ( ) .is_some ( ) ,
2308
" the policy arrived with the fetch"
2309
) ;
83e0677b Andy McCall 1 month ago ↶
2310
assert! ( b.push ( " origin" , false ) .is_err ( ) ) ;
2311
75167672 Andy McCall 1 month ago
2312
let ( key_b, id_b) = b
2313
.create_identity ( " Bob" , " [email protected] " , IdentityScope:: User)
2314
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2315
write ( & root.join ( " b" ) , " b.txt" , b " b2\n " ) ;
2316
let h_b2 = b.commit ( " b2 as Bob" , & [ ] ) .unwrap ( ) ;
2317
assert_eq! ( b.store ( ) .get_change ( & h_b2) .unwrap ( ) .author, key_b) ;
2318
let r = b.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
2319
origin.refresh ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2320
assert! (
2321
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " may not update" ) ,
2322
" {:?}" ,
2323
r
2324
) ;
83e0677b Andy McCall 1 month ago ↶
2325
2326
2327
let sp = a.policy_grant ( id_b.clone ( ) , None ) .unwrap ( ) ;
2328
assert_eq! ( sp.policy.serial, 2 ) ;
2329
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
2330
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2331
assert! ( r.iter ( ) .any ( | x | x.name = = " policy" & & x.ok ) , " {r:?}" ) ;
2332
75167672 Andy McCall 1 month ago
2333
assert! ( b
2334
.push ( " origin" , false )
2335
.unwrap_or_else ( | e | vec! [ RefResult {
2336
name: " main" .into ( ) ,
2337
ok: false ,
2338
reason: e.to_string ( )
2339
} ] )
2340
.iter ( )
2341
.all ( | x | ! x.ok) ) ;
83e0677b Andy McCall 1 month ago ↶
2342
b.pull ( " origin" ) .unwrap ( ) ;
2343
assert_eq! ( b.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 2 ) ;
2344
let r = b.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
2345
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2346
assert! ( r[ 0 ] .ok, " {:?}" , r) ;
2347
2348
let heads = origin.bookmark_heads ( ) .unwrap ( ) ;
2349
for h in & heads {
2350
let c = origin.store ( ) .get_change ( h) .unwrap ( ) ;
2351
if c.author = = key_b {
75167672 Andy McCall 1 month ago
2352
origin
2353
.change_allowed (
2354
h,
2355
& origin.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy,
2356
" main" ,
2357
)
2358
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2359
}
2360
}
2361
2362
2363
let mut forged = b.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.clone ( ) ;
2364
forged.serial = 3 ;
2365
forged.roots = vec! [ key_b] ;
2366
let fh = forged.hash ( ) ;
2367
let ( _ , sk_b) = b.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2368
let forged = SignedPolicy {
2369
policy: forged,
2370
sigs: vec! [ sk_b.sign ( Domain:: Policy, & key_b, & fh) ] ,
2371
} ;
2372
let r = origin
2373
.receive_push ( Bundle:: default( ) , vec! [ ] , Some ( forged) , vec! [ ] )
2374
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2375
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " root" ) , " {:?}" , r) ;
2376
let stale = a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1 ;
2377
let mut old = stale.clone ( ) ;
2378
old.policy.serial = 1 ;
75167672 Andy McCall 1 month ago
2379
let r = origin
2380
.receive_push ( Bundle:: default( ) , vec! [ ] , Some ( old) , vec! [ ] )
2381
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2382
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " serial" ) , " {:?}" , r) ;
2383
2384
2385
a.pull ( " origin" ) .unwrap ( ) ;
2386
a.policy_revoke ( & key_b) .unwrap ( ) ;
2387
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
2388
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2389
assert! ( r.iter ( ) .any ( | x | x.name = = " policy" & & x.ok ) , " {r:?}" ) ;
2390
b.pull ( " origin" ) .unwrap ( ) ;
2391
write ( & root.join ( " b" ) , " b.txt" , b " b3\n " ) ;
2392
b.commit ( " b3" , & [ ] ) .unwrap ( ) ;
2393
let r = b.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
2394
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2395
assert! ( ! r[ 0 ] .ok, " {:?}" , r) ;
2396
2397
75167672 Andy McCall 1 month ago
2398
let c = Repo:: clone( & url, & root.join ( " c" ) )
2399
.unwrap ( )
2400
.with_home ( root.join ( " home-c" ) ) ;
83e0677b Andy McCall 1 month ago ↶
2401
assert_eq! ( c.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 3 ) ;
75167672 Andy McCall 1 month ago
2402
assert! ( c
2403
.current_policy ( )
2404
.unwrap ( )
2405
.unwrap ( )
2406
.1
2407
.policy
2408
.roots
2409
.contains ( & key_a) ) ;
83e0677b Andy McCall 1 month ago ↶
2410
assert! ( ! c.snapshot ( " status" ) .unwrap ( ) .changed) ;
2411
}
d02e545d Andy McCall 1 month ago ↶
2412
9ae4d15a Andy McCall 1 month ago ↶
2413
d02e545d Andy McCall 1 month ago ↶
2414
# [ test ]
9ae4d15a Andy McCall 1 month ago ↶
2415
fn gc_reports_its_phases ( ) {
2416
hermetic ( ) ;
2417
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2418
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
2419
for i in 0 .. 3 {
2420
write (
2421
dir.path ( ) ,
2422
& format! ( " f{i} .txt" ) ,
2423
format! ( " {i} \n " ) .as_bytes ( ) ,
2424
) ;
2425
repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ;
2426
}
2427
let mut phases = Vec :: new( ) ;
2428
repo.gc_with ( true , & mut | p| phases.push ( p.to_string ( ) ) )
2429
.unwrap ( ) ;
2430
assert_eq! ( phases[ 0 ] , " finding what is reachable" , " {phases:?}" ) ;
2431
assert! (
2432
phases.iter ( ) .any ( | p | p.starts_with ( " pruning" ) ) ,
2433
" {phases:?}"
2434
) ;
2435
assert! (
2436
phases.iter ( ) .any ( | p | p.starts_with ( " packing 0 of " ) ) ,
2437
" {phases:?}"
2438
) ;
2439
assert! (
2440
phases
2441
.iter ( )
2442
.any ( | p | p.starts_with ( " writing the index of " ) ) ,
2443
" {phases:?}"
2444
) ;
2445
}
2446
2447
# [ test ]
d02e545d Andy McCall 1 month ago ↶
2448
fn gc_packs_objects_and_history_still_works ( ) {
2449
hermetic ( ) ;
2450
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2451
let root = dir.path ( ) ;
2452
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
2453
repo.create_identity ( " Ada" , " a@b" , IdentityScope:: User)
2454
.unwrap ( ) ;
d02e545d Andy McCall 1 month ago ↶
2455
repo.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
2456
write ( root, " a.txt" , b " one\n " ) ;
2457
let h1 = repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
2458
write ( root, " a.txt" , b " two\n " ) ;
2459
write ( root, " big.bin" , & vec! [ 0 u8 ; 100_000 ] ) ;
2460
let h2 = repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
ba160f60 Andy McCall 1 month ago ↶
2461
2462
for i in 0 .. 5 {
2463
write ( root, " a.txt" , format! ( " two\n {i} \n " ) .as_bytes ( ) ) ;
2464
repo.snapshot ( " status" ) .unwrap ( ) ;
2465
}
2466
write ( root, " a.txt" , b " two\n " ) ;
2467
repo.snapshot ( " status" ) .unwrap ( ) ;
d02e545d Andy McCall 1 month ago ↶
2468
let before = repo.store ( ) .list ( ) .unwrap ( ) ;
2469
assert! ( repo.store ( ) .loose_count ( ) .unwrap ( ) > 0 ) ;
ba160f60 Andy McCall 1 month ago ↶
2470
2471
2472
let r = repo.gc ( true ) .unwrap ( ) ;
2473
assert_eq! ( r.packed.objects, before.len ( ) ) ;
2474
assert_eq! ( r.pruned.objects, 0 ) ;
d02e545d Andy McCall 1 month ago ↶
2475
assert_eq! ( repo.store ( ) .loose_count ( ) .unwrap ( ) , 0 ) ;
2476
assert_eq! ( repo.store ( ) .list ( ) .unwrap ( ) , before) ;
ba160f60 Andy McCall 1 month ago ↶
2477
2478
2479
2480
repo.config.set ( " core.op-retention" , " 0" ) .unwrap ( ) ;
2481
repo.save_config ( ) .unwrap ( ) ;
21b2aec2 Andy McCall 1 month ago ↶
2482
let mut repo = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
ba160f60 Andy McCall 1 month ago ↶
2483
let r = repo.gc ( true ) .unwrap ( ) ;
2484
assert! ( r.pruned.objects > = 5 , " {r:?}" ) ;
2485
let after = repo.store ( ) .list ( ) .unwrap ( ) ;
2486
assert! ( after.len ( ) < before.len ( ) ) ;
2487
assert! ( after.contains ( & h1) & & after.contains ( & h2) ) ;
2488
for ( _ , op) in repo.op_log ( ) .unwrap ( ) {
2489
assert! ( ! op.command.is_empty ( ) , " operation objects are all kept" ) ;
2490
}
2491
d02e545d Andy McCall 1 month ago ↶
2492
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
75167672 Andy McCall 1 month ago
2493
assert! ( matches! (
2494
repo.verify_change ( & h2) .unwrap ( ) ,
2495
SignatureStatus:: Valid { .. }
2496
) ) ;
d02e545d Andy McCall 1 month ago ↶
2497
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 3 ) ;
2498
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
2499
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n " ) ;
2500
repo.checkout ( & [ h2] , false , " checkout" ) .unwrap ( ) ;
2501
assert_eq! ( fs:: read( root.join ( " big.bin" ) ) .unwrap ( ) .len ( ) , 100_000 ) ;
2502
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
ba160f60 Andy McCall 1 month ago ↶
2503
d02e545d Andy McCall 1 month ago ↶
2504
write ( root, " a.txt" , b " three\n " ) ;
2505
repo.commit ( " three" , & [ ] ) .unwrap ( ) ;
ba160f60 Andy McCall 1 month ago ↶
2506
let r = repo.gc ( false ) .unwrap ( ) ;
2507
assert_eq! ( r.pruned.objects, 0 ) ;
2508
assert! ( r.pruned.deferred > 0 ) ;
2509
let r = repo.gc ( true ) .unwrap ( ) ;
2510
assert! ( r.pruned.objects > 0 ) ;
2511
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 ) ;
21b2aec2 Andy McCall 1 month ago ↶
2512
2513
2514
repo.config.set ( " core.op-retention" , " 90" ) .unwrap ( ) ;
2515
repo.save_config ( ) .unwrap ( ) ;
2516
let repo = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2517
let r = repo.gc ( false ) .unwrap ( ) ;
2518
assert_eq! ( r.pruned.objects, 0 ) ;
2519
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 ) ;
d02e545d Andy McCall 1 month ago ↶
2520
}
614541c3 Andy McCall 1 month ago ↶
2521
2522
# [ test ]
2523
fn describe_amend_abandon_heads ( ) {
2524
hermetic ( ) ;
2525
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2526
let root = dir.path ( ) ;
2527
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
2528
repo.create_identity ( " Ada" , " a@b" , IdentityScope:: User)
2529
.unwrap ( ) ;
614541c3 Andy McCall 1 month ago ↶
2530
repo.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
2531
write ( root, " a.txt" , b " one\n " ) ;
2532
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
2533
write ( root, " a.txt" , b " one\n two\n " ) ;
2534
let h2 = repo.commit ( " secnod" , & [ ] ) .unwrap ( ) ;
2535
let id2 = repo.store ( ) .get_change ( & h2) .unwrap ( ) .change_id;
2536
2537
2538
let h2b = repo.describe ( & h2, " second" ) .unwrap ( ) ;
2539
assert_ne! ( h2b, h2) ;
2540
let c = repo.store ( ) .get_change ( & h2b) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2541
assert_eq! (
2542
( c.change_id, c.message.as_str ( ) , c.deps.clone ( ) ) ,
2543
( id2, " second" , vec! [ h1] )
2544
) ;
614541c3 Andy McCall 1 month ago ↶
2545
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2b] ) ;
75167672 Andy McCall 1 month ago
2546
assert! ( matches! (
2547
repo.verify_change ( & h2b) .unwrap ( ) ,
2548
SignatureStatus:: Valid { .. }
2549
) ) ;
614541c3 Andy McCall 1 month ago ↶
2550
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
2551
assert! ( s.statuses.is_empty ( ) , " {:?}" , s.statuses) ;
75167672 Andy McCall 1 month ago
2552
assert_eq! (
2553
repo.resolve_change ( & id2.short ( 8 ) ) .unwrap ( ) ,
2554
h2b,
2555
" the old object is no longer reachable from the log"
2556
) ;
c07e0e7d Andy McCall 1 month ago ↶
2557
2558
let h1b = repo.describe ( & h1, " first, described" ) .unwrap ( ) ;
2559
assert_ne! ( h1b, h1) ;
2560
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
2561
assert_eq! ( heads.len ( ) , 1 ) ;
2562
let top = repo.store ( ) .get_change ( & heads[ 0 ] ) .unwrap ( ) ;
2563
assert_eq! ( ( top.change_id, top.deps.clone ( ) ) , ( id2, vec! [ h1b] ) ) ;
75167672 Andy McCall 1 month ago
2564
assert! (
2565
matches! (
2566
repo.verify_change ( & heads[ 0 ] ) .unwrap ( ) ,
2567
SignatureStatus:: Valid { .. }
2568
) ,
2569
" rewritten own change is re-signed"
2570
) ;
c07e0e7d Andy McCall 1 month ago ↶
2571
let ( _ , wc) = repo.working_copy ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2572
assert_eq! (
2573
wc.deps,
2574
vec! [ heads[ 0 ] ] ,
2575
" the working copy follows the rewrite"
2576
) ;
c07e0e7d Andy McCall 1 month ago ↶
2577
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
2578
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
2579
assert! (
2580
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n " )
2581
) ;
614541c3 Andy McCall 1 month ago ↶
2582
2583
2584
write ( root, " a.txt" , b " one\n two\n three\n " ) ;
2585
write ( root, " b.txt" , b " b\n " ) ;
2586
let h2c = repo.amend ( None ) .unwrap ( ) ;
2587
let c = repo.store ( ) .get_change ( & h2c) .unwrap ( ) ;
2588
assert_eq! ( c.change_id, id2) ;
2589
assert_eq! ( c.message, " second" ) ;
2590
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2c] ) ;
75167672 Andy McCall 1 month ago
2591
assert_eq! (
2592
repo.log ( ) .unwrap ( ) .len ( ) ,
2593
3 ,
2594
" working copy, amended head, first"
2595
) ;
614541c3 Andy McCall 1 month ago ↶
2596
let snap = repo.load_state ( & [ h2c] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
2597
assert! (
2598
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n three\n " )
2599
) ;
614541c3 Andy McCall 1 month ago ↶
2600
assert! ( snap.files.contains_key ( " b.txt" ) ) ;
2601
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
75167672 Andy McCall 1 month ago
2602
assert! (
2603
repo.amend ( None ) .is_err ( ) ,
2604
" clean working copy and no message"
2605
) ;
614541c3 Andy McCall 1 month ago ↶
2606
let h2d = repo.amend ( Some ( " second, amended" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2607
assert_eq! (
2608
repo.store ( ) .get_change ( & h2d) .unwrap ( ) .message,
2609
" second, amended"
2610
) ;
614541c3 Andy McCall 1 month ago ↶
2611
2612
2613
repo.abandon ( & h2d) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2614
assert_eq! (
2615
repo.bookmark_heads ( ) .unwrap ( ) ,
2616
vec! [ h1b] ,
2617
" the described first change is the head again"
2618
) ;
614541c3 Andy McCall 1 month ago ↶
2619
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2620
let paths: Vec< _ > = s
2621
.statuses
2622
.iter ( )
2623
.map ( | p | ( p.path.as_str ( ) , p.status) )
2624
.collect ( ) ;
2625
assert_eq! (
2626
paths,
2627
vec! [
2628
( " a.txt" , ChangeStatus:: Modified) ,
2629
( " b.txt" , ChangeStatus:: Added)
2630
]
2631
) ;
614541c3 Andy McCall 1 month ago ↶
2632
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n two\n three\n " ) ;
2633
let h3 = repo.commit ( " recommitted" , & [ ] ) .unwrap ( ) ;
2634
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h3] ) ;
2635
2636
repo.undo ( ) .unwrap ( ) ;
2637
repo.undo ( ) .unwrap ( ) ;
2638
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2d] ) ;
c07e0e7d Andy McCall 1 month ago ↶
2639
6706bdcf Andy McCall 1 month ago ↶
2640
2641
2642
c07e0e7d Andy McCall 1 month ago ↶
2643
let base = repo.bookmark_heads ( ) .unwrap ( ) [ 0 ] ;
2644
let base_change = repo.store ( ) .get_change ( & base) .unwrap ( ) ;
2645
let h1_now = base_change.deps[ 0 ] ;
6706bdcf Andy McCall 1 month ago ↶
2646
let notes = repo.abandon ( & h1_now) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2647
assert! (
2648
! notes.is_empty ( ) ,
2649
" the dependent's edit of a.txt was reified"
2650
) ;
6706bdcf Andy McCall 1 month ago ↶
2651
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
2652
let after = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
2653
assert! (
2654
! after.files.contains_key ( " a.txt" ) & & after.files.contains_key ( " b.txt" ) ,
2655
" {:?}" ,
2656
after.files.keys ( )
2657
) ;
6706bdcf Andy McCall 1 month ago ↶
2658
repo.undo ( ) .unwrap ( ) ;
2659
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ base] ) ;
c07e0e7d Andy McCall 1 month ago ↶
2660
write ( root, " c.txt" , b " c\n " ) ;
2661
let hc = repo.commit ( " c" , & [ ] ) .unwrap ( ) ;
2662
write ( root, " d.txt" , b " d\n " ) ;
2663
let hd = repo.commit ( " d" , & [ hc] ) .unwrap ( ) ;
2664
assert_eq! ( repo.store ( ) .get_change ( & hd) .unwrap ( ) .deps, vec! [ hc] ) ;
2665
repo.abandon ( & hc) .unwrap ( ) ;
2666
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
2667
assert! ( ! heads.contains ( & hc) ) ;
75167672 Andy McCall 1 month ago
2668
let d_now = heads
2669
.iter ( )
2670
.find ( | h | repo.store ( ) .get_change ( h) .unwrap ( ) .message = = " d" )
2671
.copied ( )
2672
.unwrap ( ) ;
2673
assert! (
2674
repo.store ( ) .get_change ( & d_now) .unwrap ( ) .deps.is_empty ( ) ,
2675
" d inherited c's (empty) dependencies"
2676
) ;
c07e0e7d Andy McCall 1 month ago ↶
2677
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2678
assert_eq! (
2679
s.statuses
2680
.iter ( )
2681
.map ( | p | p.path.as_str ( ) )
2682
.collect:: < Vec< _ > > ( ) ,
2683
vec! [ " c.txt" ] ,
2684
" c's content is now uncommitted work"
2685
) ;
614541c3 Andy McCall 1 month ago ↶
2686
}
79ab8528 Andy McCall 1 month ago ↶
2687
2688
# [ test ]
2689
fn git_export_mirrors_history ( ) {
2690
hermetic ( ) ;
75167672 Andy McCall 1 month ago
2691
if std:: process:: Command:: new( " git" )
2692
.arg ( " --version" )
2693
.output ( )
2694
.is_err ( )
2695
{
79ab8528 Andy McCall 1 month ago ↶
2696
eprintln! ( " git not installed; skipping" ) ;
2697
return ;
2698
}
2699
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2700
let root = dir.path ( ) ;
75167672 Andy McCall 1 month ago
2701
let mut repo = Repo:: init( & root.join ( " r" ) )
2702
.unwrap ( )
2703
.with_home ( root.join ( " home" ) ) ;
2704
repo.create_identity ( " Ada Lovelace" , " [email protected] " , IdentityScope:: User)
2705
.unwrap ( ) ;
79ab8528 Andy McCall 1 month ago ↶
2706
let r = root.join ( " r" ) ;
2707
write ( & r, " README.md" , b " hello\n " ) ;
2708
write ( & r, " src/main.rs" , b " fn main() {}\n " ) ;
2709
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
2710
write ( & r, " src/main.rs" , b " fn main() { run(); }\n " ) ;
2711
write ( & r, " logo.png" , b " \x89 PNG\0 \0 " ) ;
2712
let h2 = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
2713
2714
let git_dir = root.join ( " mirror.git" ) ;
2715
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
2716
assert_eq! ( ( rep.exported, rep.skipped, rep.heads) , ( 2 , 0 , 1 ) ) ;
2717
let git = | args : & [& str ]| -> String {
75167672 Andy McCall 1 month ago
2718
let out = std:: process:: Command:: new( " git" )
2719
.arg ( " -C" )
2720
.arg ( & git_dir)
2721
.args ( args)
2722
.output ( )
2723
.unwrap ( ) ;
2724
assert! (
2725
out.status.success ( ) ,
2726
" {}" ,
2727
String :: from_utf8_lossy( & out.stderr)
2728
) ;
79ab8528 Andy McCall 1 month ago ↶
2729
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
2730
} ;
2731
assert_eq! ( git ( & [ " log" , " --format=%s" , " main" ] ) , " second\n first" ) ;
2732
assert_eq! ( git ( & [ " show" , " main:src/main.rs" ] ) , " fn main() { run(); }" ) ;
2733
assert_eq! ( git ( & [ " show" , " main:README.md" ] ) , " hello" ) ;
2734
let body = git ( & [ " log" , " -1" , " --format=%B" , " main" ] ) ;
75167672 Andy McCall 1 month ago
2735
assert! (
2736
body.contains ( & format! ( " Oxy-Hash: {} " , h2.to_hex ( ) ) ) ,
2737
" {body}"
2738
) ;
79ab8528 Andy McCall 1 month ago ↶
2739
assert! ( body.contains ( " Change-Id: " ) , " {body}" ) ;
75167672 Andy McCall 1 month ago
2740
assert_eq! (
2741
git ( & [ " log" , " -1" , " --format=%an <%ae>" , " main" ] ) ,
2742
" Ada Lovelace <[email protected] >"
2743
) ;
2744
assert_eq! (
2745
git ( & [ " ls-tree" , " --name-only" , " main" ] ) ,
2746
" README.md\n logo.png\n src"
2747
) ;
79ab8528 Andy McCall 1 month ago ↶
2748
2749
2750
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
2751
assert_eq! ( ( rep.exported, rep.skipped) , ( 0 , 2 ) ) ;
2752
2753
2754
2755
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
2756
write ( & r, " other.txt" , b " o\n " ) ;
2757
let h3 = repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
2758
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
2759
assert_eq! ( ( rep.exported, rep.heads) , ( 1 , 2 ) ) ;
2760
assert_eq! ( git ( & [ " log" , " --format=%s" , " main" ] ) , " third\n second\n first" ) ;
2761
let parents = git ( & [ " rev-list" , " --parents" , " -1" , " main" ] ) ;
2762
assert_eq! ( parents.split ( ' ' ) .count ( ) , 2 , " one parent: {parents}" ) ;
2763
assert_eq! ( git ( & [ " show" , " main:other.txt" ] ) , " o" ) ;
2764
assert_eq! ( git ( & [ " show" , " main:src/main.rs" ] ) , " fn main() { run(); }" ) ;
75167672 Andy McCall 1 month ago
2765
assert_eq! (
2766
git ( & [ " ls-tree" , " --name-only" , " main" ] ) ,
2767
" README.md\n logo.png\n other.txt\n src"
2768
) ;
79ab8528 Andy McCall 1 month ago ↶
2769
let body = git ( & [ " log" , " -1" , " --format=%B" , " main" ] ) ;
75167672 Andy McCall 1 month ago
2770
assert! (
2771
body.contains ( & format! ( " Oxy-Hash: {} " , h3.to_hex ( ) ) ) & & body.contains ( " Oxy-State: " ) ,
2772
" {body}"
2773
) ;
79ab8528 Andy McCall 1 month ago ↶
2774
2775
repo.describe ( & h3, " third, described" ) .unwrap ( ) ;
2776
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
2777
assert_eq! ( rep.exported, 1 ) ;
75167672 Andy McCall 1 month ago
2778
assert_eq! (
2779
git ( & [ " log" , " -1" , " --format=%s" , " main" ] ) ,
2780
" third, described"
2781
) ;
79ab8528 Andy McCall 1 month ago ↶
2782
assert_eq! ( git ( & [ " log" , " --format=%s" , " main" ] ) .lines ( ) .count ( ) , 4 ) ;
2783
let _ = h2;
2784
}
c3069537 Andy McCall 1 month ago ↶
2785
2786
# [ test ]
2787
fn git_import_reproduces_trees ( ) {
c3d2ef61 Andy McCall 1 month ago ↶
2788
git_import_reproduces_trees_with ( None ) ;
2789
}
2790
2791
2792
2793
# [ test ]
2794
fn git_import_reproduces_trees_in_batches ( ) {
2795
git_import_reproduces_trees_with ( Some ( 1 ) ) ;
2796
}
2797
2798
fn git_import_reproduces_trees_with ( batch : Option< usize > ) {
2799
crate :: repo:: git:: set_merge_batch_for_tests( batch) ;
c3069537 Andy McCall 1 month ago ↶
2800
hermetic ( ) ;
75167672 Andy McCall 1 month ago
2801
if std:: process:: Command:: new( " git" )
2802
.arg ( " --version" )
2803
.output ( )
2804
.is_err ( )
2805
{
c3069537 Andy McCall 1 month ago ↶
2806
eprintln! ( " git not installed; skipping" ) ;
2807
return ;
2808
}
2809
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2810
let root = dir.path ( ) ;
2811
let g = root.join ( " src.git" ) ;
2812
fs:: create_dir_all( & g) .unwrap ( ) ;
2813
let git = | args : & [& str ]| -> String {
2814
let out = std:: process:: Command:: new( " git" )
2815
.arg ( " -C" )
2816
.arg ( & g)
75167672 Andy McCall 1 month ago
2817
.args ( [
2818
" -c" ,
2819
" user.name=Grace" ,
2820
" -c" ,
2821
" [email protected] " ,
2822
" -c" ,
2823
" commit.gpgsign=false" ,
2824
] )
c3069537 Andy McCall 1 month ago ↶
2825
.args ( args)
2826
.output ( )
2827
.unwrap ( ) ;
75167672 Andy McCall 1 month ago
2828
assert! (
2829
out.status.success ( ) ,
2830
" git {args:?}: {}" ,
2831
String :: from_utf8_lossy( & out.stderr)
2832
) ;
c3069537 Andy McCall 1 month ago ↶
2833
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
2834
} ;
2835
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
2836
write ( & g, " README.md" , b " # Hi\n " ) ;
2837
write ( & g, " src/lib.rs" , b " pub fn a() {}\n " ) ;
2838
git ( & [ " add" , " ." ] ) ;
2839
git ( & [ " commit" , " -q" , " -m" , " initial" ] ) ;
2840
write ( & g, " src/lib.rs" , b " pub fn a() {}\n pub fn b() {}\n " ) ;
2841
write ( & g, " run.sh" , b " #!/bin/sh\n " ) ;
2842
# [ cfg ( unix ) ]
2843
{
2844
use std:: os:: unix:: fs:: PermissionsExt;
2845
fs:: set_permissions( g.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
2846
}
2847
write ( & g, " logo.png" , b " \x89 PNG\0 \0 \x01 " ) ;
2848
git ( & [ " add" , " ." ] ) ;
75167672 Andy McCall 1 month ago
2849
git ( & [
2850
" commit" ,
2851
" -q" ,
2852
" -m" ,
2853
" second\n \n Change-Id: 0123456789abcdef0123456789abcdef" ,
2854
] ) ;
c3069537 Andy McCall 1 month ago ↶
2855
2856
git ( & [ " checkout" , " -q" , " -b" , " topic" , " HEAD~1" ] ) ;
2857
write ( & g, " README.md" , b " # Hi\n \n More.\n " ) ;
2858
git ( & [ " commit" , " -q" , " -am" , " readme" ] ) ;
2859
git ( & [ " checkout" , " -q" , " main" ] ) ;
2860
git ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge topic" , " topic" ] ) ;
2861
2862
2863
let initial = git ( & [ " rev-list" , " --max-parents=0" , " HEAD" ] ) ;
2864
git ( & [ " checkout" , " -q" , " -b" , " clash" , & initial] ) ;
2865
write ( & g, " src/lib.rs" , b " pub fn a() {}\n pub fn c() {}\n " ) ;
2866
git ( & [ " commit" , " -q" , " -am" , " clash" ] ) ;
2867
git ( & [ " checkout" , " -q" , " main" ] ) ;
75167672 Andy McCall 1 month ago
2868
let _ = std:: process:: Command:: new( " git" )
2869
.arg ( " -C" )
2870
.arg ( & g)
2871
.args ( [ " merge" , " -q" , " clash" ] )
2872
.output ( ) ;
2873
write (
2874
& g,
2875
" src/lib.rs" ,
2876
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n " ,
2877
) ;
c3069537 Andy McCall 1 month ago ↶
2878
git ( & [ " add" , " ." ] ) ;
2879
git ( & [ " commit" , " -q" , " -m" , " resolve clash" ] ) ;
2880
git ( & [ " rm" , " -q" , " logo.png" ] ) ;
2881
git ( & [ " commit" , " -q" , " -m" , " drop logo" ] ) ;
6e723b81 Andy McCall 1 month ago ↶
2882
2883
git ( & [ " mv" , " src/lib.rs" , " src/lib2.rs" ] ) ;
75167672 Andy McCall 1 month ago
2884
write (
2885
& g,
2886
" src/lib2.rs" ,
2887
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n pub fn d() {}\n " ,
2888
) ;
6e723b81 Andy McCall 1 month ago ↶
2889
git ( & [ " add" , " ." ] ) ;
2890
git ( & [ " commit" , " -q" , " -m" , " rename lib" ] ) ;
958fb27d Andy McCall 1 month ago ↶
2891
2892
2893
2894
git ( & [ " checkout" , " -q" , " -b" , " discarded" ] ) ;
2895
write ( & g, " run.sh" , b " #!/bin/sh\n echo discarded\n " ) ;
2896
git ( & [ " commit" , " -q" , " -am" , " discarded edit" ] ) ;
2897
git ( & [ " checkout" , " -q" , " main" ] ) ;
314db9b4 Andy McCall 1 month ago ↶
2898
git ( & [
2899
" merge" ,
2900
" -q" ,
2901
" -s" ,
2902
" ours" ,
2903
" -m" ,
2904
" merge discarded, keeping ours" ,
2905
" discarded" ,
2906
] ) ;
c3069537 Andy McCall 1 month ago ↶
2907
let expected_count: usize = git ( & [ " rev-list" , " --count" , " main" ] ) .parse ( ) .unwrap ( ) ;
2908
2909
let dest = root.join ( " oxy" ) ;
2910
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2911
let rep = repo.import_git ( & g, " main" ) .unwrap ( ) ;
2912
assert_eq! ( rep.commits, expected_count) ;
958fb27d Andy McCall 1 month ago ↶
2913
assert_eq! ( rep.merges, 3 ) ;
314db9b4 Andy McCall 1 month ago ↶
2914
assert_eq! (
2915
fs:: read( dest.join ( " run.sh" ) ) .unwrap ( ) ,
2916
b " #!/bin/sh\n " ,
2917
" the ours merge kept main's run.sh"
2918
) ;
958fb27d Andy McCall 1 month ago ↶
2919
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
2920
assert_eq! ( v.commits, expected_count, " every commit's tree reproduced" ) ;
2921
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
c3069537 Andy McCall 1 month ago ↶
2922
75167672 Andy McCall 1 month ago
2923
assert_eq! (
2924
fs:: read( dest.join ( " src/lib2.rs" ) ) .unwrap ( ) ,
2925
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n pub fn d() {}\n "
2926
) ;
6e723b81 Andy McCall 1 month ago ↶
2927
assert! ( ! dest.join ( " src/lib.rs" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
2928
assert_eq! (
2929
fs:: read( dest.join ( " README.md" ) ) .unwrap ( ) ,
2930
b " # Hi\n \n More.\n "
2931
) ;
c3069537 Andy McCall 1 month ago ↶
2932
assert! ( ! dest.join ( " logo.png" ) .exists ( ) ) ;
2933
# [ cfg ( unix ) ]
2934
{
2935
use std:: os:: unix:: fs:: PermissionsExt;
75167672 Andy McCall 1 month ago
2936
assert! (
2937
fs:: metadata( dest.join ( " run.sh" ) )
2938
.unwrap ( )
2939
.permissions ( )
2940
.mode ( )
2941
& 0o111
2942
! = 0
2943
) ;
c3069537 Andy McCall 1 month ago ↶
2944
}
75167672 Andy McCall 1 month ago
2945
assert! (
2946
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
2947
" clean after import"
2948
) ;
c3069537 Andy McCall 1 month ago ↶
2949
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
2950
let st = repo.load_state ( & heads) .unwrap ( ) ;
2951
assert! ( ! st.materialize ( ) .is_conflicted ( ) ) ;
2952
assert_eq! ( st.len ( ) , expected_count) ;
75167672 Andy McCall 1 month ago
2953
let tip = repo
2954
.log ( )
2955
.unwrap ( )
2956
.into_iter ( )
2957
.find ( | e | e.change.message.starts_with ( " rename lib" ) )
2958
.unwrap ( ) ;
6e723b81 Andy McCall 1 month ago ↶
2959
let c = repo.store ( ) .get_change ( & tip.hash) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2960
assert! (
2961
c.tree_ops
2962
.iter ( )
2963
.any ( | op | matches! ( op, TreeOp:: Move { name, .. } if name = = " lib2.rs" ) ) ,
2964
" {:?}" ,
2965
c.tree_ops
2966
) ;
2967
assert! ( ! c
2968
.tree_ops
2969
.iter ( )
2970
.any ( | op | matches! ( op, TreeOp:: AddNode { .. } ) ) ) ;
c3069537 Andy McCall 1 month ago ↶
2971
2972
let log = repo.log ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2973
let second = log
2974
.iter ( )
2975
.find ( | e | e.change.message.starts_with ( " second" ) )
2976
.unwrap ( ) ;
2977
assert_eq! (
2978
second.change.change_id,
2979
ChangeId:: from_hex( " 0123456789abcdef0123456789abcdef" ) .unwrap ( )
2980
) ;
2981
assert! ( second
2982
.change
2983
.message
2984
.contains ( " Git-Author: Grace <[email protected] >" ) ) ;
2985
let resolve = log
2986
.iter ( )
2987
.find ( | e | e.change.message.starts_with ( " resolve clash" ) )
2988
.unwrap ( ) ;
2989
assert_eq! (
2990
resolve.change.kind,
2991
ChangeKind:: Resolution,
2992
" a conflicting Git merge becomes a resolution"
2993
) ;
c3069537 Andy McCall 1 month ago ↶
2994
75167672 Andy McCall 1 month ago
2995
let repo2 = Repo:: init( & root.join ( " oxy2" ) )
2996
.unwrap ( )
2997
.with_home ( root.join ( " home" ) ) ;
c3069537 Andy McCall 1 month ago ↶
2998
repo2.import_git ( & g, " main" ) .unwrap ( ) ;
dfb23a3a Andy McCall 1 month ago ↶
2999
3000
3001
assert_eq! (
3002
repo2.bookmark_heads ( ) .unwrap ( ) ,
3003
repo.bookmark_heads ( ) .unwrap ( )
3004
) ;
75167672 Andy McCall 1 month ago
3005
let ids: BTreeSet< ChangeId> = repo
3006
.log ( )
3007
.unwrap ( )
3008
.iter ( )
3009
.filter ( | e | ! e.is_working_copy)
3010
.map ( | e | e.change.change_id )
3011
.collect ( ) ;
3012
let ids2: BTreeSet< ChangeId> = repo2
3013
.log ( )
3014
.unwrap ( )
3015
.iter ( )
3016
.filter ( | e | ! e.is_working_copy)
3017
.map ( | e | e.change.change_id )
3018
.collect ( ) ;
c3069537 Andy McCall 1 month ago ↶
3019
assert_eq! ( ids, ids2) ;
3020
3021
let mirror = root.join ( " mirror.git" ) ;
3022
repo.git_export ( & mirror) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3023
let out = std:: process:: Command:: new( " git" )
3024
.arg ( " -C" )
3025
.arg ( & mirror)
3026
.args ( [ " show" , " main:src/lib2.rs" ] )
3027
.output ( )
3028
.unwrap ( ) ;
3029
assert_eq! (
3030
String :: from_utf8_lossy( & out.stdout) ,
3031
" pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n pub fn d() {}\n "
3032
) ;
3033
let out = std:: process:: Command:: new( " git" )
3034
.arg ( " -C" )
3035
.arg ( & mirror)
3036
.args ( [ " show" , " main:src/lib.rs" ] )
3037
.output ( )
3038
.unwrap ( ) ;
3039
assert! (
3040
! out.status.success ( ) ,
3041
" the old path is gone from the export"
3042
) ;
c3069537 Andy McCall 1 month ago ↶
3043
}
252e8cb2 Andy McCall 1 month ago ↶
3044
3045
# [ test ]
3046
fn checkpoints_accelerate_loading_and_stay_correct ( ) {
3047
hermetic ( ) ;
3048
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3049
let root = dir.path ( ) ;
3050
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
3051
repo.config.set ( " core.checkpoint-interval" , " 5" ) .unwrap ( ) ;
3052
repo.save_config ( ) .unwrap ( ) ;
3053
let repo = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
3054
let mut hashes = Vec :: new( ) ;
3055
for i in 0 .. 12 {
75167672 Andy McCall 1 month ago
3056
write (
3057
root,
3058
" a.txt" ,
3059
format! (
3060
" {} \n " ,
3061
( 0 .. = i)
3062
.map ( | k | k.to_string ( ) )
3063
.collect:: < Vec< _ > > ( )
3064
.join ( " \n " )
3065
)
3066
.as_bytes ( ) ,
3067
) ;
252e8cb2 Andy McCall 1 month ago ↶
3068
write ( root, & format! ( " f{i} .txt" ) , b " x\n " ) ;
3069
hashes.push ( repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ) ;
3070
}
75167672 Andy McCall 1 month ago
3071
let ( _ , cp) = repo
3072
.latest_checkpoint ( " main" )
3073
.unwrap ( )
3074
.expect ( " automatic checkpoint" ) ;
252e8cb2 Andy McCall 1 month ago ↶
3075
assert! ( cp.count > = 5 & & cp.count < = 12 , " {}" , cp.count) ;
3076
3077
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
3078
let fast = repo.load_state ( & heads) .unwrap ( ) ;
3079
let full = repo.load_state_full ( & heads) .unwrap ( ) ;
3080
assert_eq! ( fast.len ( ) , full.len ( ) ) ;
3081
assert_eq! ( fast.heads ( ) , full.heads ( ) ) ;
3082
let ( a, b) = ( fast.materialize ( ) , full.materialize ( ) ) ;
75167672 Andy McCall 1 month ago
3083
assert_eq! (
3084
a.files.keys ( ) .collect:: < Vec< _ > > ( ) ,
3085
b.files.keys ( ) .collect:: < Vec< _ > > ( )
3086
) ;
252e8cb2 Andy McCall 1 month ago ↶
3087
for ( p, f) in & a.files {
3088
assert_eq! ( f.content, b.files[ p] .content, " {p}" ) ;
3089
}
3090
assert! ( repo.load_state_checkpointed ( & heads) .unwrap ( ) .is_some ( ) ) ;
3091
75167672 Andy McCall 1 month ago
3092
assert! ( repo
3093
.load_state_checkpointed ( & [ hashes[ 0 ] ] )
3094
.unwrap ( )
3095
.is_none ( ) ) ;
252e8cb2 Andy McCall 1 month ago ↶
3096
assert_eq! ( repo.load_state ( & [ hashes[ 0 ] ] ) .unwrap ( ) .len ( ) , 1 ) ;
3097
3098
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
3099
let h = repo.describe ( & hashes[ 3 ] , " c3 described" ) .unwrap ( ) ;
3100
assert_eq! ( repo.store ( ) .get_change ( & h) .unwrap ( ) .message, " c3 described" ) ;
3101
let ( r, n, bytes) = repo.checkpoint ( ) .unwrap ( ) ;
3102
assert! ( n > = 12 & & bytes > 0 ) ;
3103
assert_eq! ( repo.latest_checkpoint ( " main" ) .unwrap ( ) .unwrap ( ) .0 , r) ;
3104
write ( root, " a.txt" , b " after\n " ) ;
3105
let h_after = repo.commit ( " after" , & [ ] ) .unwrap ( ) ;
3106
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
3107
assert! ( st.contains ( & h_after) ) ;
75167672 Andy McCall 1 month ago
3108
assert! (
3109
matches! ( & st.materialize ( ) .files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " after\n " )
3110
) ;
252e8cb2 Andy McCall 1 month ago ↶
3111
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 14 ) ;
3112
3113
repo.gc ( true ) .unwrap ( ) ;
3114
assert! ( repo.latest_checkpoint ( " main" ) .unwrap ( ) .is_some ( ) ) ;
75167672 Andy McCall 1 month ago
3115
assert! ( repo
3116
.load_state_checkpointed ( & repo.bookmark_heads ( ) .unwrap ( ) )
3117
.unwrap ( )
3118
.is_some ( ) ) ;
252e8cb2 Andy McCall 1 month ago ↶
3119
}
135212de Andy McCall 1 month ago ↶
3120
3121
# [ test ]
3122
fn quick_status_for_prompts ( ) {
3123
hermetic ( ) ;
3124
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3125
let root = dir.path ( ) ;
3126
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
3127
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
3128
let a = Repo:: clone( & url, & root.join ( " a" ) )
3129
.unwrap ( )
3130
.with_home ( root.join ( " home" ) ) ;
135212de Andy McCall 1 month ago ↶
3131
let ops_before = a.op_log ( ) .unwrap ( ) .len ( ) ;
3132
let info = a.quick_status ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3133
assert_eq! (
3134
(
3135
info.bookmark.as_str ( ) ,
3136
info.heads,
3137
info.dirty,
3138
info.conflicted,
3139
info.ahead,
3140
info.behind
3141
) ,
3142
( " main" , 0 , false , false , Some ( 0 ) , Some ( 0 ) )
3143
) ;
135212de Andy McCall 1 month ago ↶
3144
write ( & root.join ( " a" ) , " f.txt" , b " f\n " ) ;
3145
assert! ( a.quick_status ( ) .unwrap ( ) .dirty, " a new file is dirty" ) ;
75167672 Andy McCall 1 month ago
3146
assert_eq! (
3147
a.op_log ( ) .unwrap ( ) .len ( ) ,
3148
ops_before,
3149
" quick_status records nothing"
3150
) ;
135212de Andy McCall 1 month ago ↶
3151
3152
a.snapshot ( " status" ) .unwrap ( ) ;
3153
assert! ( a.quick_status ( ) .unwrap ( ) .dirty) ;
3154
a.commit ( " f" , & [ ] ) .unwrap ( ) ;
3155
let info = a.quick_status ( ) .unwrap ( ) ;
3156
assert! ( ! info.dirty, " clean right after a commit" ) ;
3157
assert_eq! ( ( info.ahead, info.behind) , ( Some ( 1 ) , Some ( 0 ) ) ) ;
75167672 Andy McCall 1 month ago
3158
assert_eq! (
3159
info.format ( " %b %c%d%x%A%D" ) ,
3160
format! ( " main {} +1" , info.change_id)
3161
) ;
135212de Andy McCall 1 month ago ↶
3162
assert_eq! ( info.format ( " [%h heads] %a/%B %%" ) , " [1 heads] 1/0 %" ) ;
3163
3164
3165
let p = root.join ( " a/f.txt" ) ;
3166
let content = fs:: read( & p) .unwrap ( ) ;
3167
fs:: write( & p, & content) .unwrap ( ) ;
3168
assert! ( ! a.quick_status ( ) .unwrap ( ) .dirty) ;
3169
fs:: write( & p, b " g\n " ) .unwrap ( ) ;
3170
assert! ( a.quick_status ( ) .unwrap ( ) .dirty) ;
3171
fs:: write( & p, b " f\n " ) .unwrap ( ) ;
3172
3173
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
3174
assert_eq! ( a.quick_status ( ) .unwrap ( ) .ahead, Some ( 0 ) ) ;
75167672 Andy McCall 1 month ago
3175
let b = Repo:: clone( & url, & root.join ( " b" ) )
3176
.unwrap ( )
3177
.with_home ( root.join ( " home-b" ) ) ;
135212de Andy McCall 1 month ago ↶
3178
write ( & root.join ( " b" ) , " g.txt" , b " g\n " ) ;
3179
b.commit ( " g" , & [ ] ) .unwrap ( ) ;
3180
b.push ( " origin" , false ) .unwrap ( ) ;
3181
a.fetch ( " origin" ) .unwrap ( ) ;
3182
let info = a.quick_status ( ) .unwrap ( ) ;
3183
assert_eq! ( ( info.ahead, info.behind) , ( Some ( 0 ) , Some ( 1 ) ) ) ;
3184
assert_eq! ( info.format ( " %A%D" ) , " -1" ) ;
3185
let _ = origin;
3186
}
b3c5a59a Andy McCall 1 month ago ↶
3187
3188
# [ test ]
3189
fn lazy_blobs_are_fetched_on_first_read ( ) {
3190
hermetic ( ) ;
3191
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3192
let root = dir.path ( ) ;
3193
let origin_dir = root.join ( " origin" ) ;
75167672 Andy McCall 1 month ago
3194
let origin = Repo:: init( & origin_dir)
3195
.unwrap ( )
3196
.with_home ( root.join ( " home" ) ) ;
b3c5a59a Andy McCall 1 month ago ↶
3197
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
3198
let big: Vec< u8 > = ( 0 .. 200_000 u32 ) .map ( | i | ( i % 251 ) as u8 ) .collect ( ) ;
3199
write ( & origin_dir, " big.bin" , & big) ;
3200
write ( & origin_dir, " small.bin" , & [ 0 u8 , 1 , 2 ] ) ;
3201
write ( & origin_dir, " t.txt" , b " t\n " ) ;
3202
let h = origin.commit ( " blobs" , & [ ] ) .unwrap ( ) ;
3203
let big_ref = Repo:: blob_ref( & big) ;
3204
3205
75167672 Andy McCall 1 month ago
3206
let a = Repo:: init_with_id( & root.join ( " a" ) , origin.id ( ) .unwrap ( ) )
3207
.unwrap ( )
3208
.with_home ( root.join ( " home" ) ) ;
b3c5a59a Andy McCall 1 month ago ↶
3209
let a = {
3210
let mut a = a;
3211
a.config.set ( " remote.origin.url" , & url) .unwrap ( ) ;
3212
a.config.set ( " filter.max-blob" , " 100K" ) .unwrap ( ) ;
3213
a.save_config ( ) .unwrap ( ) ;
3214
a
3215
} ;
3216
assert_eq! ( a.config.max_blob, Some ( 100 * 1024 ) ) ;
3217
a.fetch ( " origin" ) .unwrap ( ) ;
3218
assert! ( a.store ( ) .has ( & h) ) ;
3219
assert! ( ! a.store ( ) .has ( & big_ref) , " withheld" ) ;
75167672 Andy McCall 1 month ago
3220
assert! (
3221
a.store ( ) .has ( & Repo:: blob_ref( & [ 0 u8 , 1 , 2 ] ) ) ,
3222
" small blob came along"
3223
) ;
b3c5a59a Andy McCall 1 month ago ↶
3224
assert_eq! ( a.lazy_remote ( & big_ref) .as_deref ( ) , Some ( " origin" ) ) ;
3225
3226
3227
let ( op_ref, op) = a.head_op ( ) .unwrap ( ) ;
3228
let mut view = op.view.clone ( ) ;
75167672 Andy McCall 1 month ago
3229
view.bookmarks.insert (
3230
" main" .into ( ) ,
3231
a.store ( ) .put_state ( & StateObject:: new( vec! [ h] ) ) .unwrap ( ) ,
3232
) ;
b3c5a59a Andy McCall 1 month ago ↶
3233
a.record_op ( & [ op_ref] , " adopt" , view) .unwrap ( ) ;
3234
3235
a.checkout ( & [ h] , true , " checkout" ) .unwrap ( ) ;
3236
assert! ( a.store ( ) .has ( & big_ref) , " fetched on first read" ) ;
3237
assert_eq! ( fs:: read( root.join ( " a/big.bin" ) ) .unwrap ( ) , big) ;
3238
assert! ( a.lazy_remote ( & big_ref) .is_none ( ) , " no longer registered" ) ;
3239
write ( & root.join ( " a" ) , " t.txt" , b " t2\n " ) ;
3240
a.commit ( " text edit" , & [ ] ) .unwrap ( ) ;
3241
3242
7421309f Andy McCall 1 month ago ↶
3243
let b = Repo:: clone_with( & url, & root.join ( " b" ) , Some ( 1024 ) , & [ ] ) .unwrap ( ) ;
b3c5a59a Andy McCall 1 month ago ↶
3244
assert_eq! ( b.config.max_blob, Some ( 1024 ) ) ;
3245
assert_eq! ( fs:: read( root.join ( " b/big.bin" ) ) .unwrap ( ) , big) ;
3246
assert_eq! ( parse_size ( " 2M" ) .unwrap ( ) , 2 < < 20 ) ;
3247
assert! ( parse_size ( " lots" ) .is_err ( ) ) ;
3248
}
7421309f Andy McCall 1 month ago ↶
3249
3250
# [ test ]
3251
fn sparse_checkout_materializes_only_chosen_paths ( ) {
3252
hermetic ( ) ;
3253
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3254
let root = dir.path ( ) ;
3255
let origin_dir = root.join ( " origin" ) ;
75167672 Andy McCall 1 month ago
3256
let origin = Repo:: init( & origin_dir)
3257
.unwrap ( )
3258
.with_home ( root.join ( " home" ) ) ;
7421309f Andy McCall 1 month ago ↶
3259
write ( & origin_dir, " src/main.rs" , b " fn main() {}\n " ) ;
3260
write ( & origin_dir, " src/lib.rs" , b " pub fn a() {}\n " ) ;
3261
write ( & origin_dir, " docs/guide.md" , b " # guide\n " ) ;
3262
let logo: Vec< u8 > = ( 0 .. 50_000 u32 ) .map ( | i | ( i % 7 ) as u8 ) .collect ( ) ;
3263
write ( & origin_dir, " docs/logo.png" , & logo) ;
3264
write ( & origin_dir, " README.md" , b " # top\n " ) ;
3265
let h1 = origin.commit ( " tree" , & [ ] ) .unwrap ( ) ;
3266
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
3267
75167672 Andy McCall 1 month ago
3268
let a = Repo:: clone_with( & url, & root.join ( " a" ) , None , & [ " src" .into ( ) ] )
3269
.unwrap ( )
3270
.with_home ( root.join ( " home" ) ) ;
7421309f Andy McCall 1 month ago ↶
3271
assert_eq! ( a.config.sparse_paths, vec! [ " src" ] ) ;
3272
assert_eq! ( a.config.max_blob, Some ( 0 ) , " sparse implies lazy blobs" ) ;
3273
assert! ( root.join ( " a/src/main.rs" ) .exists ( ) ) ;
3274
assert! ( ! root.join ( " a/docs" ) .exists ( ) & & ! root.join ( " a/README.md" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
3275
assert! (
3276
! a.store ( ) .has ( & Repo:: blob_ref( & logo) ) ,
3277
" out-of-scope blob never fetched"
3278
) ;
3279
assert! (
3280
! a.snapshot ( " status" ) .unwrap ( ) .changed,
3281
" out-of-scope files are not removals"
3282
) ;
05e71ed6 Andy McCall 1 month ago ↶
3283
assert! ( ! a.quick_status ( ) .unwrap ( ) .dirty) ;
7421309f Andy McCall 1 month ago ↶
3284
3285
75167672 Andy McCall 1 month ago
3286
write (
3287
& root.join ( " a" ) ,
3288
" src/lib.rs" ,
3289
b " pub fn a() {}\n pub fn b() {}\n " ,
3290
) ;
7421309f Andy McCall 1 month ago ↶
3291
let h2 = a.commit ( " edit lib" , & [ ] ) .unwrap ( ) ;
3292
let st = a.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
3293
assert! (
3294
st.files.contains_key ( " docs/guide.md" ) ,
3295
" the state still has everything"
3296
) ;
7421309f Andy McCall 1 month ago ↶
3297
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
3298
origin.refresh ( ) .unwrap ( ) ;
7421309f Andy McCall 1 month ago ↶
3299
3300
write ( & root.join ( " a" ) , " docs/new.md" , b " ignored\n " ) ;
3301
assert! ( a.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
3302
3303
3304
let mut a = a;
3305
a.config.set ( " filter.paths" , " src,docs" ) .unwrap ( ) ;
3306
a.save_config ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3307
let r = a
3308
.checkout ( & a.bookmark_heads ( ) .unwrap ( ) , true , " widen" )
3309
.unwrap ( ) ;
7421309f Andy McCall 1 month ago ↶
3310
assert! ( r.written > = 2 ) ;
75167672 Andy McCall 1 month ago
3311
assert_eq! (
3312
fs:: read( root.join ( " a/docs/logo.png" ) ) .unwrap ( ) ,
3313
logo,
3314
" now fetched lazily"
3315
) ;
7421309f Andy McCall 1 month ago ↶
3316
assert! ( ! root.join ( " a/README.md" ) .exists ( ) ) ;
3317
3318
origin.pull ( " origin" ) .err ( ) ;
75167672 Andy McCall 1 month ago
3319
let full = origin
3320
.load_state ( & origin.bookmark_heads ( ) .unwrap ( ) )
3321
.unwrap ( )
3322
.materialize ( ) ;
3323
assert! (
3324
matches! ( & full.files[ " src/lib.rs" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " pub fn a() {}\n pub fn b() {}\n " )
3325
) ;
7421309f Andy McCall 1 month ago ↶
3326
assert! ( full.files.contains_key ( " README.md" ) ) ;
3327
let _ = h1;
3328
}
2863456a Andy McCall 1 month ago ↶
3329
7bd805ff Andy McCall 13 days ago ↶
3330
3331
3332
3333
2863456a Andy McCall 1 month ago ↶
3334
# [ test ]
7bd805ff Andy McCall 13 days ago ↶
3335
fn a_depth_0_clone_brings_its_binary_files ( ) {
3336
hermetic ( ) ;
3337
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3338
let root = dir.path ( ) ;
3339
let origin_dir = root.join ( " origin" ) ;
3340
let origin = Repo:: init( & origin_dir)
3341
.unwrap ( )
3342
.with_home ( root.join ( " home" ) ) ;
3343
let png: Vec< u8 > = ( 0 .. 4000 u32 )
3344
.map ( | i | ( i * 7 % 251 ) as u8 )
3345
.chain ( [ 0 , 0xff ] )
3346
.collect ( ) ;
3347
3348
3349
write ( & origin_dir, " logo.png" , & png) ;
3350
write ( & origin_dir, " a.txt" , b " first\n " ) ;
3351
origin.commit ( " logo" , & [ ] ) .unwrap ( ) ;
3352
write ( & origin_dir, " a.txt" , b " later\n " ) ;
3353
origin.commit ( " later" , & [ ] ) .unwrap ( ) ;
3354
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) .len ( ) , 1 ) ;
3355
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
3356
Repo:: clone_shallow( & url, & root.join ( " a" ) , 0 , None , & [ ] ) .unwrap ( ) ;
3357
assert_eq! ( fs:: read( root.join ( " a/logo.png" ) ) .unwrap ( ) , png) ;
3358
let lazy = Repo:: clone_shallow( & url, & root.join ( " b" ) , 0 , Some ( 0 ) , & [ ] ) .unwrap ( ) ;
3359
assert_eq! ( fs:: read( root.join ( " b/a.txt" ) ) .unwrap ( ) , b " later\n " ) ;
3360
let blob = {
3361
let heads = lazy.bookmark_heads ( ) .unwrap ( ) ;
3362
let st = lazy.load_state_history ( & heads) .unwrap ( ) ;
3363
st.opaque_nodes ( )
3364
.iter ( )
3365
.flat_map ( | n | st.live_blobs ( n) )
3366
.next ( )
3367
.unwrap ( )
3368
} ;
3369
assert_eq! ( lazy.read_blob ( & blob) .unwrap ( ) , png, " fetched on read" ) ;
3370
}
3371
3372
# [ test ]
2863456a Andy McCall 1 month ago ↶
3373
fn shallow_clone_and_deepen ( ) {
3374
hermetic ( ) ;
3375
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3376
let root = dir.path ( ) ;
3377
let origin_dir = root.join ( " origin" ) ;
75167672 Andy McCall 1 month ago
3378
let origin = Repo:: init( & origin_dir)
3379
.unwrap ( )
3380
.with_home ( root.join ( " home" ) ) ;
2863456a Andy McCall 1 month ago ↶
3381
let mut hashes = Vec :: new( ) ;
3382
for i in 0 .. 8 {
75167672 Andy McCall 1 month ago
3383
write (
3384
& origin_dir,
3385
" a.txt" ,
3386
format! (
3387
" {} \n " ,
3388
( 0 .. = i)
3389
.map ( | k | k.to_string ( ) )
3390
.collect:: < Vec< _ > > ( )
3391
.join ( " \n " )
3392
)
3393
.as_bytes ( ) ,
3394
) ;
2863456a Andy McCall 1 month ago ↶
3395
hashes.push ( origin.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ) ;
3396
}
3397
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
3398
let a = Repo:: clone_shallow( & url, & root.join ( " a" ) , 3 , None , & [ ] )
3399
.unwrap ( )
3400
.with_home ( root.join ( " home" ) ) ;
2863456a Andy McCall 1 month ago ↶
3401
assert! ( a.is_shallow ( ) ) ;
75167672 Andy McCall 1 month ago
3402
assert_eq! (
3403
fs:: read_to_string( root.join ( " a/a.txt" ) ) .unwrap ( ) ,
3404
" 0\n 1\n 2\n 3\n 4\n 5\n 6\n 7\n "
3405
) ;
3406
assert! (
3407
a.store ( ) .has ( & hashes[ 7 ] ) & & a.store ( ) .has ( & hashes[ 5 ] ) ,
3408
" three levels present"
3409
) ;
2863456a Andy McCall 1 month ago ↶
3410
assert! ( ! a.store ( ) .has ( & hashes[ 4 ] ) , " beyond the depth is absent" ) ;
3411
assert_eq! ( a.boundary ( ) , [ hashes[ 4 ] ] .into_iter ( ) .collect ( ) ) ;
75167672 Andy McCall 1 month ago
3412
assert_eq! (
3413
a.log ( ) .unwrap ( ) .len ( ) ,
3414
9 ,
3415
" the log shows every change from the checkpoint's metadata"
3416
) ;
2863456a Andy McCall 1 month ago ↶
3417
assert! ( ! a.snapshot ( " status" ) .unwrap ( ) .changed) ;
3418
assert_eq! ( a.quick_status ( ) .unwrap ( ) .heads, 1 ) ;
3419
3420
3421
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 " ) ;
3422
let h_new = a.commit ( " c8" , & [ ] ) .unwrap ( ) ;
3423
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
3424
origin.refresh ( ) .unwrap ( ) ;
2863456a Andy McCall 1 month ago ↶
3425
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ h_new] ) ;
3426
3427
write ( & origin_dir, " b.txt" , b " b\n " ) ;
3428
origin.pull ( " origin" ) .err ( ) ;
3429
let ( _ , wc) = origin.working_copy ( ) .unwrap ( ) ;
3430
let _ = wc;
75167672 Andy McCall 1 month ago
3431
origin
3432
.checkout ( & origin.bookmark_heads ( ) .unwrap ( ) , true , " co" )
3433
.unwrap ( ) ;
2863456a Andy McCall 1 month ago ↶
3434
write ( & origin_dir, " b.txt" , b " b\n " ) ;
3435
let h_b = origin.commit ( " b" , & [ ] ) .unwrap ( ) ;
3436
let pr = a.pull ( " origin" ) .unwrap ( ) ;
3437
assert! ( pr.changed) ;
3438
assert! ( a.store ( ) .has ( & h_b) ) ;
3439
assert! ( root.join ( " a/b.txt" ) .exists ( ) ) ;
3440
3441
3442
3443
3444
assert! ( a.deepen ( " origin" , 2 ) .unwrap ( ) > = 2 ) ;
3445
assert! ( a.store ( ) .has ( & hashes[ 3 ] ) ) ;
3446
assert! ( a.is_shallow ( ) ) ;
3447
a.deepen ( " origin" , 100 ) .unwrap ( ) ;
3448
assert! ( ! a.is_shallow ( ) , " boundary reached the root" ) ;
3449
assert! ( a.store ( ) .has ( & hashes[ 0 ] ) ) ;
75167672 Andy McCall 1 month ago
3450
assert_eq! (
3451
a.load_state_full ( & a.bookmark_heads ( ) .unwrap ( ) )
3452
.unwrap ( )
3453
.len ( ) ,
3454
10
3455
) ;
2863456a Andy McCall 1 month ago ↶
3456
}
132b884b Andy McCall 1 month ago ↶
3457
3458
# [ test ]
3459
fn tags_are_immutable_named_states_and_travel ( ) {
3460
hermetic ( ) ;
3461
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3462
let root = dir.path ( ) ;
3463
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
3464
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
3465
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
3466
.unwrap ( )
3467
.with_home ( root.join ( " home-a" ) ) ;
3468
let ( key_a, _ ) = a
3469
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
3470
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3471
write ( & root.join ( " a" ) , " f.txt" , b " 1\n " ) ;
3472
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
3473
write ( & root.join ( " a" ) , " f.txt" , b " 1\n 2\n " ) ;
3474
let h2 = a.commit ( " two" , & [ ] ) .unwrap ( ) ;
3475
3476
let t = a.tag_create ( " v1" , & [ h1] , " first release" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3477
assert_eq! (
3478
( t.heads.clone ( ) , t.signer.as_deref ( ) ) ,
3479
( vec! [ h1] , Some ( " Ada" ) )
3480
) ;
132b884b Andy McCall 1 month ago ↶
3481
assert! ( a.tag_create ( " v1" , & [ h2] , " " ) .is_err ( ) , " immutable" ) ;
3482
assert! ( a.tag_create ( " bad/name" , & [ h2] , " " ) .is_err ( ) ) ;
3483
assert_eq! ( a.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
3484
assert_eq! ( a.resolve_heads ( " v1" ) .unwrap ( ) , vec! [ h1] ) ;
3485
assert_eq! ( a.resolve_heads ( " main" ) .unwrap ( ) , vec! [ h2] ) ;
3486
let ( _ , sa) = a.tag_attest ( " v1" ) .unwrap ( ) .unwrap ( ) ;
3487
assert_eq! ( sa.sigs[ 0 ] .key, key_a) ;
ee130dd9 Andy McCall 17 days ago ↶
3488
3489
3490
let mut refs = a.remote_refs ( ) .unwrap ( ) ;
3491
a.refs_for_client ( & mut refs, None ) ;
3492
assert_eq! ( refs.tags.len ( ) , 1 ) ;
132b884b Andy McCall 1 month ago ↶
3493
75167672 Andy McCall 1 month ago
3494
a.checkout ( & a.resolve_heads ( " v1" ) .unwrap ( ) , false , " co" )
3495
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3496
assert_eq! ( fs:: read( root.join ( " a/f.txt" ) ) .unwrap ( ) , b " 1\n " ) ;
75167672 Andy McCall 1 month ago
3497
a.checkout ( & a.bookmark_heads ( ) .unwrap ( ) , false , " co" )
3498
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3499
3500
3501
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3502
origin.refresh ( ) .unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3503
assert! ( r.iter ( ) .any ( | x | x.name = = " tags/v1" & & x.ok ) , " {r:?}" ) ;
3504
assert_eq! ( origin.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
75167672 Andy McCall 1 month ago
3505
let mut forged = a
3506
.tag_refs ( )
3507
.unwrap ( )
3508
.into_iter ( )
3509
.find ( | t | t.name = = " v1" )
3510
.unwrap ( ) ;
3511
forged.state = origin
3512
.store ( )
3513
.put_state ( & StateObject:: new( vec! [ h2] ) )
3514
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3515
forged.heads = vec! [ h2] ;
3516
forged.attestation = None ;
75167672 Andy McCall 1 month ago
3517
let r = origin
3518
.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ forged] )
3519
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3520
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " immutable" ) , " {r:?}" ) ;
3521
3522
75167672 Andy McCall 1 month ago
3523
let b = Repo:: clone( & url, & root.join ( " b" ) )
3524
.unwrap ( )
3525
.with_home ( root.join ( " home-b" ) ) ;
132b884b Andy McCall 1 month ago ↶
3526
assert_eq! ( b.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
3527
assert! ( b.tags ( ) .unwrap ( ) [ 0 ] .signer.as_deref ( ) = = Some ( " Ada" ) ) ;
3528
a.tag_create ( " v2" , & [ h2] , " second" ) .unwrap ( ) ;
3529
a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3530
origin.refresh ( ) .unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3531
let rep = b.fetch ( " origin" ) .unwrap ( ) ;
3532
assert_eq! ( rep.new_tags, 1 ) ;
3533
assert_eq! ( b.tag_heads ( " v2" ) .unwrap ( ) , Some ( vec! [ h2] ) ) ;
3534
3535
3536
a.policy_init ( ) .unwrap ( ) ;
3537
a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3538
origin.refresh ( ) .unwrap ( ) ;
1ec6fdbc Andy McCall 17 days ago ↶
3539
let unattested = crate :: protocol:: TagRef:: new(
3540
" v3" .into ( ) ,
3541
origin
75167672 Andy McCall 1 month ago
3542
.store ( )
3543
.put_state ( & StateObject:: new( vec! [ h2] ) )
3544
.unwrap ( ) ,
1ec6fdbc Andy McCall 17 days ago ↶
3545
vec! [ h2] ,
3546
None ,
3547
) ;
75167672 Andy McCall 1 month ago
3548
let r = origin
3549
.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ unattested] )
3550
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3551
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " attestation" ) , " {r:?}" ) ;
3552
a.tag_create ( " v3" , & [ h2] , " " ) .unwrap ( ) ;
3553
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3554
origin.refresh ( ) .unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3555
assert! ( r.iter ( ) .any ( | x | x.name = = " tags/v3" & & x.ok ) , " {r:?}" ) ;
3556
assert! ( a.gc ( true ) .is_ok ( ) ) ;
3557
assert_eq! ( a.tags ( ) .unwrap ( ) .len ( ) , 3 , " tags survive gc" ) ;
e5d9b051 Andy McCall 21 days ago ↶
3558
3559
3560
3561
3562
let rot = a.rotate_identity ( ) .unwrap ( ) ;
3563
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3564
origin.refresh ( ) .unwrap ( ) ;
e5d9b051 Andy McCall 21 days ago ↶
3565
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
3566
let c = Repo:: clone( & url, & root.join ( " c" ) )
3567
.unwrap ( )
3568
.with_home ( root.join ( " home-c" ) ) ;
3569
assert_eq! ( c.tag_heads ( " v3" ) .unwrap ( ) , Some ( vec! [ h2] ) ) ;
3570
let ( _ , sp) = c.current_policy ( ) .unwrap ( ) .unwrap ( ) ;
3571
assert! ( ! sp.policy.authorizes ( & key_a, " tags/v9" ) ) ;
3572
let made = c.identity ( & rot.new) .unwrap ( ) .created;
3573
assert! ( c.predecessor_authorized ( & sp.policy, & key_a, " tags/v9" , made - 1 ) ) ;
3574
assert! ( ! c.predecessor_authorized ( & sp.policy, & key_a, " tags/v9" , made) ) ;
3575
assert! ( ! c.predecessor_authorized ( & sp.policy, & rot.new, " tags/v9" , 0 ) ) ;
132b884b Andy McCall 1 month ago ↶
3576
}
4e88e774 Andy McCall 1 month ago ↶
3577
3578
# [ test ]
3579
fn multiple_workspaces_share_a_repository ( ) {
3580
hermetic ( ) ;
3581
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3582
let root = dir.path ( ) ;
3583
let main_dir = root.join ( " main" ) ;
3584
let repo = Repo:: init( & main_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
3585
write ( & main_dir, " a.txt" , b " a\n " ) ;
3586
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
3587
assert_eq! ( repo.workspaces ( ) .unwrap ( ) .len ( ) , 1 ) ;
3588
3589
3590
let ws_dir = root.join ( " ws2" ) ;
4476cdbb Andy McCall 12 days ago ↶
3591
let ops = repo.op_log ( ) .unwrap ( ) .len ( ) ;
4e88e774 Andy McCall 1 month ago ↶
3592
let ws = repo.workspace_add ( & ws_dir, None ) .unwrap ( ) ;
4476cdbb Andy McCall 12 days ago ↶
3593
assert_eq! (
3594
ws.op_log ( ) .unwrap ( ) .len ( ) ,
3595
ops + 2 ,
3596
" the add and its checkout, and no snapshot finding the tree deleted (oxygen#137)"
3597
) ;
4e88e774 Andy McCall 1 month ago ↶
3598
assert! ( ws_dir.join ( " .oxy" ) .is_file ( ) ) ;
3599
assert_eq! ( fs:: read( ws_dir.join ( " a.txt" ) ) .unwrap ( ) , b " a\n " ) ;
3600
assert_ne! ( ws.workspace ( ) , repo.workspace ( ) ) ;
3601
let list = repo.workspaces ( ) .unwrap ( ) ;
3602
assert_eq! ( list.len ( ) , 2 ) ;
75167672 Andy McCall 1 month ago
3603
assert! ( list
3604
.iter ( )
3605
.any ( | w | w.id = = ws.workspace ( ) & & w.path = = ws_dir.canonicalize ( ) .unwrap ( ) & & ! w.current ) ) ;
4e88e774 Andy McCall 1 month ago ↶
3606
assert! ( ! ws.snapshot ( " status" ) .unwrap ( ) .changed) ;
3607
3608
3609
write ( & ws_dir, " b.txt" , b " b\n " ) ;
3610
let s = ws.snapshot ( " status" ) .unwrap ( ) ;
3611
assert_eq! ( s.statuses.len ( ) , 1 ) ;
75167672 Andy McCall 1 month ago
3612
assert! (
3613
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
3614
" the first workspace is untouched"
3615
) ;
4e88e774 Andy McCall 1 month ago ↶
3616
let h2 = ws.commit ( " b from ws2" , & [ ] ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3617
repo.refresh ( ) .unwrap ( ) ;
4e88e774 Andy McCall 1 month ago ↶
3618
let mut heads = repo.bookmark_heads ( ) .unwrap ( ) ;
3619
heads.sort ( ) ;
3620
let mut want = vec! [ h1, h2] ;
3621
want.sort ( ) ;
3622
assert_eq! ( heads, want, " both workspaces see the bookmark move" ) ;
3623
assert! ( ! main_dir.join ( " b.txt" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
3624
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " co" )
3625
.unwrap ( ) ;
4e88e774 Andy McCall 1 month ago ↶
3626
assert! ( main_dir.join ( " b.txt" ) .exists ( ) ) ;
3627
3628
3629
let reopened = Repo:: open( & ws_dir) .unwrap ( ) ;
3630
assert_eq! ( reopened.workspace ( ) , ws.workspace ( ) ) ;
3631
assert_eq! ( reopened.id ( ) .unwrap ( ) , repo.id ( ) .unwrap ( ) ) ;
3632
assert_eq! ( reopened.quick_status ( ) .unwrap ( ) .heads, 2 ) ;
3633
3634
3635
assert! ( repo.workspace_add ( & main_dir.join ( " inner" ) , None ) .is_err ( ) ) ;
3636
assert! ( repo.workspace_forget ( & repo.workspace ( ) ) .is_err ( ) ) ;
3637
repo.workspace_forget ( & ws.workspace ( ) ) .unwrap ( ) ;
3638
assert_eq! ( repo.workspaces ( ) .unwrap ( ) .len ( ) , 1 ) ;
3639
assert! ( ws_dir.join ( " b.txt" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
3640
assert! (
3641
ws.snapshot ( " status" ) .is_err ( ) ,
3642
" a forgotten workspace is no longer in the view"
3643
) ;
4e88e774 Andy McCall 1 month ago ↶
3644
6caacfff Andy McCall 1 month ago ↶
3645
3646
4e88e774 Andy McCall 1 month ago ↶
3647
let lock = repo.lock_snapshot ( ) .unwrap ( ) ;
6caacfff Andy McCall 1 month ago ↶
3648
let again = repo.lock_working_copy ( ) .unwrap ( ) ;
3649
drop ( again) ;
4e88e774 Andy McCall 1 month ago ↶
3650
drop ( lock) ;
75167672 Andy McCall 1 month ago
3651
let lock_path = main_dir
3652
.join ( " .oxy/workspaces" )
3653
.join ( repo.workspace ( ) .to_hex ( ) )
3654
.join ( " lock" ) ;
4e88e774 Andy McCall 1 month ago ↶
3655
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 120 ) ;
6caacfff Andy McCall 1 month ago ↶
3656
let age = | p : & Path| fs:: File:: open( p) .unwrap ( ) .set_modified ( old) .unwrap ( ) ;
3657
fs:: write( & lock_path, std:: process:: id( ) .to_string ( ) ) .unwrap ( ) ;
3658
age ( & lock_path) ;
3659
assert! (
3660
! super :: workspace:: lock_is_stale( & lock_path) ,
3661
" a live process keeps its lock"
3662
) ;
3663
fs:: write( & lock_path, " 4294967295" ) .unwrap ( ) ;
3664
age ( & lock_path) ;
4e88e774 Andy McCall 1 month ago ↶
3665
assert! ( repo.lock_snapshot ( ) .is_ok ( ) , " stale lock taken over" ) ;
3666
}
cdb5beba Andy McCall 1 month ago ↶
3667
3668
# [ test ]
3669
fn attributes_binary_eol_nocompress_lockable ( ) {
3670
hermetic ( ) ;
3671
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3672
let root = dir.path ( ) ;
3673
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
3674
write (
3675
root,
3676
" .oxyattributes" ,
3677
b " *.dat binary nocompress\n *.txt eol=crlf\n assets/** lockable\n " ,
3678
) ;
cdb5beba Andy McCall 1 month ago ↶
3679
write ( root, " plain.dat" , b " no nul bytes here\n " ) ;
3680
write ( root, " notes.txt" , b " one\r \n two\r \n " ) ;
3681
write ( root, " assets/model.bin" , b " \0 model" ) ;
3682
let h = repo.commit ( " attrs" , & [ ] ) .unwrap ( ) ;
3683
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
3684
assert! (
3685
snap.files[ " plain.dat" ] .mode.opaque,
3686
" binary attribute forces opaque without NUL bytes"
3687
) ;
3688
assert! (
3689
matches! ( & snap.files[ " notes.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n " ) ,
3690
" stored with LF"
3691
) ;
3692
assert! (
3693
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
3694
" CRLF on disk equals LF in the state"
3695
) ;
cdb5beba Andy McCall 1 month ago ↶
3696
3697
let r = repo.gc ( true ) .unwrap ( ) ;
3698
assert! ( r.packed.objects > 0 ) ;
3699
3700
fs:: remove_file( root.join ( " notes.txt" ) ) .unwrap ( ) ;
3701
repo.checkout ( & [ h] , true , " co" ) .unwrap ( ) ;
3702
assert_eq! ( fs:: read( root.join ( " notes.txt" ) ) .unwrap ( ) , b " one\r \n two\r \n " ) ;
75167672 Andy McCall 1 month ago
3703
assert! ( fs:: metadata( root.join ( " assets/model.bin" ) )
3704
.unwrap ( )
3705
.permissions ( )
3706
.readonly ( ) ) ;
cdb5beba Andy McCall 1 month ago ↶
3707
assert! ( repo.lock_path ( " plain.dat" ) .is_err ( ) , " not lockable" ) ;
3708
repo.lock_path ( " assets/model.bin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3709
assert! ( ! fs:: metadata( root.join ( " assets/model.bin" ) )
3710
.unwrap ( )
3711
.permissions ( )
3712
.readonly ( ) ) ;
cdb5beba Andy McCall 1 month ago ↶
3713
assert! ( repo.is_locked ( " assets/model.bin" ) ) ;
3714
write ( root, " assets/model.bin" , b " \0 model2" ) ;
3715
repo.commit ( " edit model" , & [ ] ) .unwrap ( ) ;
3716
repo.unlock_path ( " assets/model.bin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3717
assert! ( fs:: metadata( root.join ( " assets/model.bin" ) )
3718
.unwrap ( )
3719
.permissions ( )
3720
.readonly ( ) ) ;
cdb5beba Andy McCall 1 month ago ↶
3721
3722
repo.checkout ( & [ h] , true , " co" ) .unwrap ( ) ;
3723
assert_eq! ( fs:: read( root.join ( " assets/model.bin" ) ) .unwrap ( ) , b " \0 model" ) ;
3724
}
6d3eefbc Andy McCall 1 month ago ↶
3725
3726
# [ cfg ( unix ) ]
3727
# [ test ]
3728
fn hooks_can_veto_observe_and_add_dependencies ( ) {
3729
use std:: os:: unix:: fs:: PermissionsExt;
3730
hermetic ( ) ;
3731
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3732
let root = dir.path ( ) ;
3733
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
3734
let hooks = root.join ( " .oxy/hooks" ) ;
3735
fs:: create_dir_all( & hooks) .unwrap ( ) ;
3736
let hook = | name : & str , body : & str | {
3737
let p = hooks.join ( name) ;
3738
fs:: write( & p, format! ( " #!/bin/sh\n {body} \n " ) ) .unwrap ( ) ;
3739
fs:: set_permissions( & p, fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
3740
} ;
3741
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" ) ;
3742
hook ( " post-commit" , " cat > \" $OXY_ROOT/.last-post-commit\" " ) ;
3743
hook ( " post-checkout" , " cat > \" $OXY_ROOT/.last-post-checkout\" " ) ;
3744
hook ( " pre-push" , " echo pushing blocked >&2; exit 3" ) ;
3745
write ( root, " .oxyignore" , b " .last-*\n " ) ;
3746
write ( root, " a.txt" , b " a\n " ) ;
3747
let err = repo.commit ( " WIP: nope" , & [ ] ) .unwrap_err ( ) .to_string ( ) ;
3748
assert! ( err.contains ( " pre-commit refused: no WIP commits" ) , " {err}" ) ;
3749
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
3750
let pre = fs:: read_to_string( root.join ( " .last-pre-commit" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3751
assert! (
3752
pre.contains ( " \" event\" :\" pre-commit\" " )
3753
& & pre.contains ( " \" path\" :\" a.txt\" " )
3754
& & pre.contains ( " \" message\" :\" first\" " ) ,
3755
" {pre}"
3756
) ;
6d3eefbc Andy McCall 1 month ago ↶
3757
let post = fs:: read_to_string( root.join ( " .last-post-commit" ) ) .unwrap ( ) ;
3758
assert! ( post.contains ( & h1.to_hex ( ) ) , " {post}" ) ;
3759
repo.checkout ( & [ h1] , false , " co" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3760
assert! ( fs:: read_to_string( root.join ( " .last-post-checkout" ) )
3761
.unwrap ( )
3762
.contains ( " post-checkout" ) ) ;
6d3eefbc Andy McCall 1 month ago ↶
3763
repo.config.set ( " remote.origin.url" , " /nowhere" ) .unwrap ( ) ;
3764
repo.save_config ( ) .unwrap ( ) ;
3765
let err = repo.push ( " origin" , false ) .unwrap_err ( ) .to_string ( ) ;
3766
assert! ( err.contains ( " pre-push refused: pushing blocked" ) , " {err}" ) ;
3767
3768
3769
write ( root, " b.txt" , b " b\n " ) ;
75167672 Andy McCall 1 month ago
3770
hook (
3771
" semantic-deps" ,
3772
& format! (
3773
" cat > \" $OXY_ROOT/.last-semantic\" ; echo '{{ \" deps\" : [\" {} \" ]}} '" ,
3774
h1.to_hex ( )
3775
) ,
3776
) ;
6d3eefbc Andy McCall 1 month ago ↶
3777
let h2 = repo.commit ( " b without hook" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3778
assert! (
3779
repo.store ( ) .get_change ( & h2) .unwrap ( ) .deps.is_empty ( ) ,
3780
" hook disabled by default"
3781
) ;
6d3eefbc Andy McCall 1 month ago ↶
3782
repo.config.set ( " hooks.semantic-deps" , " true" ) .unwrap ( ) ;
3783
repo.save_config ( ) .unwrap ( ) ;
3784
write ( root, " c.txt" , b " c\n " ) ;
3785
let h3 = repo.commit ( " c with hook" , & [ ] ) .unwrap ( ) ;
3786
assert_eq! ( repo.store ( ) .get_change ( & h3) .unwrap ( ) .deps, vec! [ h1] ) ;
3787
let sem = fs:: read_to_string( root.join ( " .last-semantic" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3788
assert! (
3789
sem.contains ( " \" touched_paths\" :[\" c.txt\" ]" ) & & sem.contains ( " candidate_deps" ) ,
3790
" {sem}"
3791
) ;
6d3eefbc Andy McCall 1 month ago ↶
3792
assert_eq! ( json_str ( " a\" b\\ c\n " ) , " \" a\\ \" b\\ \\ c\\ n\" " ) ;
3793
}
23b217d7 Andy McCall 1 month ago ↶
3794
3795
# [ test ]
3796
fn amend_into_older_change_and_split ( ) {
3797
hermetic ( ) ;
3798
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3799
let root = dir.path ( ) ;
3800
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
3801
repo.create_identity ( " Ada" , " a@b" , IdentityScope:: User)
3802
.unwrap ( ) ;
23b217d7 Andy McCall 1 month ago ↶
3803
repo.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
3804
write ( root, " a.txt" , b " one\n two\n " ) ;
3805
let h0 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
3806
write ( root, " a.txt" , b " one\n TWO\n " ) ;
3807
let h1 = repo.commit ( " edit two" , & [ ] ) .unwrap ( ) ;
3808
write ( root, " a.txt" , b " one\n TWO\n three\n " ) ;
3809
let h2 = repo.commit ( " add three" , & [ ] ) .unwrap ( ) ;
3810
let id1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) .change_id;
3811
3812
3813
write ( root, " a.txt" , b " one\n TWO!\n three\n " ) ;
3814
let h1b = repo.amend_into ( & h1, None ) .unwrap ( ) ;
3815
assert_ne! ( h1b, h1) ;
3816
let c1b = repo.store ( ) .get_change ( & h1b) .unwrap ( ) ;
3817
assert_eq! ( c1b.change_id, id1) ;
3818
assert_eq! ( c1b.deps, vec! [ h0] ) ;
75167672 Andy McCall 1 month ago
3819
assert! ( matches! (
3820
repo.verify_change ( & h1b) .unwrap ( ) ,
3821
SignatureStatus:: Valid { .. }
3822
) ) ;
23b217d7 Andy McCall 1 month ago ↶
3823
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
3824
assert_eq! ( heads.len ( ) , 1 ) ;
3825
let top = repo.store ( ) .get_change ( & heads[ 0 ] ) .unwrap ( ) ;
3826
assert_eq! ( top.message, " add three" ) ;
75167672 Andy McCall 1 month ago
3827
assert_eq! (
3828
top.deps.iter ( ) .copied ( ) .collect:: < BTreeSet< _ > > ( ) ,
3829
[ h0, h1b] .into_iter ( ) .collect ( ) ,
3830
" depends on the file's creator and the rewritten line"
3831
) ;
23b217d7 Andy McCall 1 month ago ↶
3832
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
3833
assert! (
3834
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n TWO!\n three\n " )
3835
) ;
3836
assert! (
3837
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
3838
" the working copy is clean after folding"
3839
) ;
23b217d7 Andy McCall 1 month ago ↶
3840
3841
let only = repo.load_state ( & [ h1b] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
3842
assert! (
3843
matches! ( & only.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n TWO!\n " )
3844
) ;
23b217d7 Andy McCall 1 month ago ↶
3845
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 ) ;
3846
3847
3848
write ( root, " a.txt" , b " one\n TWO!\n three!\n " ) ;
3849
let err = repo.amend_into ( & h1b, None ) .unwrap_err ( ) .to_string ( ) ;
3850
assert! ( err.contains ( " introduced after" ) , " {err}" ) ;
75167672 Andy McCall 1 month ago
3851
assert! (
3852
repo.snapshot ( " status" ) .unwrap ( ) .statuses.len ( ) = = 1 ,
3853
" nothing was changed by the refusal"
3854
) ;
23b217d7 Andy McCall 1 month ago ↶
3855
write ( root, " a.txt" , b " one\n TWO!\n three\n " ) ;
3856
3857
3858
write ( root, " b.txt" , b " b\n " ) ;
3859
write ( root, " c.txt" , b " c\n " ) ;
3860
let h3 = repo.commit ( " b and c" , & [ ] ) .unwrap ( ) ;
3861
let id3 = repo.store ( ) .get_change ( & h3) .unwrap ( ) .change_id;
3862
let ( p1, p2) = repo.split ( & h3, & [ " b.txt" .into ( ) ] ) .unwrap ( ) ;
3863
let c_1 = repo.store ( ) .get_change ( & p1) .unwrap ( ) ;
3864
let c_2 = repo.store ( ) .get_change ( & p2) .unwrap ( ) ;
3865
assert! ( c_1 .message.contains ( " part 1: b.txt" ) ) ;
3866
assert_eq! ( c_2 .change_id, id3) ;
3867
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
75167672 Andy McCall 1 month ago
3868
assert! (
3869
heads.contains ( & p1) & & heads.contains ( & p2) ,
3870
" independent parts are both heads"
3871
) ;
3872
let snap = repo
3873
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
3874
.unwrap ( )
3875
.materialize ( ) ;
23b217d7 Andy McCall 1 month ago ↶
3876
assert! ( snap.files.contains_key ( " b.txt" ) & & snap.files.contains_key ( " c.txt" ) ) ;
3877
let only1 = repo.load_state ( & [ p1] ) .unwrap ( ) .materialize ( ) ;
3878
assert! ( only1.files.contains_key ( " b.txt" ) & & ! only1.files.contains_key ( " c.txt" ) ) ;
3879
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
3880
assert! ( repo.split ( & p2, & [ " nope.txt" .into ( ) ] ) .is_err ( ) ) ;
3881
let _ = h2;
3882
}
28fc23d7 Andy McCall 1 month ago ↶
3883
3884
# [ test ]
3885
fn identity_lifecycle ( ) {
3886
hermetic ( ) ;
3887
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3888
let root = dir.path ( ) ;
3889
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
3890
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
3891
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
3892
.unwrap ( )
3893
.with_home ( root.join ( " home-a" ) ) ;
28fc23d7 Andy McCall 1 month ago ↶
3894
a.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3895
let ( key_a, _ ) = a
3896
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
3897
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3898
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
3899
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
3900
assert! ( a.policy_init ( ) .unwrap ( ) .applied) ;
3901
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
767ac6cd Andy McCall 19 days ago
3902
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3903
3904
75167672 Andy McCall 1 month ago
3905
let mut e = Repo:: init( & root.join ( " e" ) )
3906
.unwrap ( )
3907
.with_home ( root.join ( " home-e" ) ) ;
28fc23d7 Andy McCall 1 month ago ↶
3908
e.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3909
e.create_identity_with (
3910
" Eve" ,
3911
" [email protected] " ,
3912
IdentityScope:: User,
3913
Some ( Hlc:: now( ) .wall_ms - 1 ) ,
3914
)
3915
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3916
write ( & root.join ( " e" ) , " e.txt" , b " e\n " ) ;
3917
let he = e.commit ( " late" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3918
assert! (
3919
matches! ( e.verify_change ( & he) .unwrap ( ) , SignatureStatus:: Invalid( m) if m.contains ( " expired" ) )
3920
) ;
28fc23d7 Andy McCall 1 month ago ↶
3921
75167672 Andy McCall 1 month ago
3922
let r = e
3923
.renew_identity ( Some ( Hlc:: now( ) .wall_ms + 86_400_000 ) )
3924
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3925
assert_eq! ( e.identity ( & r.new) .unwrap ( ) .previous, Some ( r.old) ) ;
3926
write ( & root.join ( " e" ) , " e.txt" , b " e2\n " ) ;
3927
let he2 = e.commit ( " in time" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3928
assert! (
3929
matches! ( e.verify_change ( & he2) .unwrap ( ) , SignatureStatus:: Valid { key, .. } if key = = r.new)
3930
) ;
28fc23d7 Andy McCall 1 month ago ↶
3931
3932
3933
let r = a.rotate_identity ( ) .unwrap ( ) ;
3934
assert_eq! ( r.old, key_a) ;
3935
assert_eq! ( a.config.author_key, Some ( r.new) ) ;
3936
assert_eq! ( a.identity ( & r.new) .unwrap ( ) .previous, Some ( key_a) ) ;
3937
assert! ( r.policy.as_ref ( ) .unwrap ( ) .applied) ;
3938
let ( _ , sp) = a.current_policy ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3939
assert_eq! (
3940
( sp.policy.serial, sp.policy.roots.clone ( ) ) ,
3941
( 2 , vec! [ r.new] )
3942
) ;
3943
assert! (
3944
! sp.policy.revoked.contains ( & key_a) & & sp.policy.authorizes ( & r.new, " main" ) ,
3945
" superseded, not revoked"
3946
) ;
28fc23d7 Andy McCall 1 month ago ↶
3947
assert! ( sp.policy.succeeds ( & r.new, & key_a) & & ! sp.policy.succeeds ( & key_a, & r.new) ) ;
75167672 Andy McCall 1 month ago
3948
assert! (
3949
matches! ( a.verify_change ( & h1) .unwrap ( ) , SignatureStatus:: Valid { key, revoked: false , .. } if key = = key_a)
3950
) ;
28fc23d7 Andy McCall 1 month ago ↶
3951
write ( & root.join ( " a" ) , " a.txt" , b " a2\n " ) ;
3952
let h2 = a.commit ( " two" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3953
assert! (
3954
matches! ( a.verify_change ( & h2) .unwrap ( ) , SignatureStatus:: Valid { key, revoked: false , .. } if key = = r.new)
3955
) ;
28fc23d7 Andy McCall 1 month ago ↶
3956
3957
let res = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3958
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3959
assert! ( res.iter ( ) .all ( | x | x.ok ) , " {res:?}" ) ;
3960
assert_eq! ( origin.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 2 ) ;
3961
let key_a = r.new;
3962
3963
3964
75167672 Andy McCall 1 month ago
3965
let mut b = Repo:: clone( & url, & root.join ( " b" ) )
3966
.unwrap ( )
3967
.with_home ( root.join ( " home-b" ) ) ;
3968
let ( key_b, id_b) = b
3969
.create_identity ( " Bob" , " [email protected] " , IdentityScope:: User)
3970
.unwrap ( ) ;
7ba155f2 Andy McCall 1 month ago ↶
3971
let r = a.delegate_add ( id_b.clone ( ) , Scope:: ALL , None ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3972
assert_eq! ( r.old, key_a) ;
3973
let key_a2 = r.new;
75167672 Andy McCall 1 month ago
3974
assert_eq! (
7ba155f2 Andy McCall 1 month ago ↶
3975
a.identity ( & key_a2) .unwrap ( ) .delegates,
3976
vec! [ Delegate:: full( key_b) ]
3977
) ;
3978
assert_eq! (
75167672 Andy McCall 1 month ago
3979
a.identity ( & key_a2) .unwrap ( ) .ed25519_pk,
3980
a.identity ( & key_a) .unwrap ( ) .ed25519_pk
7ba155f2 Andy McCall 1 month ago ↶
3981
) ;
3982
assert! (
3983
a.delegate_add ( id_b.clone ( ) , Scope:: ALL , None ) .is_err ( ) ,
3984
" already a delegate"
75167672 Andy McCall 1 month ago
3985
) ;
28fc23d7 Andy McCall 1 month ago ↶
3986
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | x | x.ok ) ) ;
767ac6cd Andy McCall 19 days ago
3987
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3988
write ( & root.join ( " a" ) , " a.txt" , b " a3\n " ) ;
3989
let h3 = a.commit ( " three" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3990
assert! (
3991
matches! ( a.verify_change ( & h3) .unwrap ( ) , SignatureStatus:: Valid { key, .. } if key = = key_a2) ,
3992
" the same secret signs under the new KeyId"
3993
) ;
28fc23d7 Andy McCall 1 month ago ↶
3994
3995
a.set_user_config ( " core.sign" , " seal" ) .unwrap ( ) ;
3996
write ( & root.join ( " a" ) , " a.txt" , b " a4\n " ) ;
3997
let h4 = a.commit ( " four" , & [ ] ) .unwrap ( ) ;
3998
assert_eq! ( a.verify_change ( & h4) .unwrap ( ) , SignatureStatus:: Unsigned) ;
3999
let ( _ , sk_b) = b.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4000
a.store ( )
4001
.put_signature ( & h4, & sk_b.sign ( Domain:: Change, & key_b, & h4) )
4002
.unwrap ( ) ;
4003
assert! (
4004
matches! ( a.verify_change ( & h4) .unwrap ( ) , SignatureStatus:: Valid { key, .. } if key = = key_b)
4005
) ;
28fc23d7 Andy McCall 1 month ago ↶
4006
a.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
4007
4008
let r = a.delegate_remove ( & key_b) .unwrap ( ) ;
4009
assert! ( a.identity ( & r.new) .unwrap ( ) .delegates.is_empty ( ) ) ;
4010
assert! ( a.delegate_remove ( & key_b) .is_err ( ) ) ;
4011
let key_a3 = r.new;
4012
let res = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
4013
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4014
assert! ( res.iter ( ) .all ( | x | x.ok ) , " {res:?}" ) ;
4015
4016
4017
4018
assert! ( a.policy_root_add ( id_b.clone ( ) ) .unwrap ( ) .applied) ;
4019
assert! ( a.policy_root_add ( id_b.clone ( ) ) .is_err ( ) ) ;
4020
assert! ( a.policy_set_threshold ( 3 ) .is_err ( ) ) ;
4021
assert! ( a.policy_set_threshold ( 2 ) .unwrap ( ) .applied) ;
4022
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | x | x.ok ) ) ;
767ac6cd Andy McCall 19 days ago
4023
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4024
b.pull ( " origin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4025
assert_eq! (
4026
b.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.root_threshold,
4027
2
4028
) ;
4029
let mut c = Repo:: init( & root.join ( " c" ) )
4030
.unwrap ( )
4031
.with_home ( root.join ( " home-c" ) ) ;
4032
let ( key_c, id_c) = c
4033
.create_identity ( " Cy" , " [email protected] " , IdentityScope:: User)
4034
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4035
let serial_before = a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial;
4036
let proposal = a.policy_grant ( id_c, None ) .unwrap ( ) ;
4037
assert! ( ! proposal.applied & & proposal.sigs.len ( ) = = 1 ) ;
4038
assert_eq! ( proposal.policy.serial, serial_before + 1 ) ;
75167672 Andy McCall 1 month ago
4039
assert_eq! (
4040
a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial,
4041
serial_before,
4042
" a proposal changes nothing yet"
4043
) ;
28fc23d7 Andy McCall 1 month ago ↶
4044
4045
let bytes = proposal.signed ( ) .encode ( ) ;
75167672 Andy McCall 1 month ago
4046
let signed = b
4047
.policy_sign ( SignedPolicy:: decode( & bytes) .unwrap ( ) )
4048
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4049
assert! ( signed.applied & & signed.sigs.len ( ) = = 2 ) ;
75167672 Andy McCall 1 month ago
4050
assert! ( b
4051
.current_policy ( )
4052
.unwrap ( )
4053
.unwrap ( )
4054
.1
4055
.policy
4056
.authorizes ( & key_c, " main" ) ) ;
4057
assert! (
4058
b.policy_sign ( signed.signed ( ) ) .is_err ( ) ,
4059
" a stale proposal is refused"
4060
) ;
28fc23d7 Andy McCall 1 month ago ↶
4061
75167672 Andy McCall 1 month ago
4062
assert! ( b
4063
.push ( " origin" , false )
4064
.unwrap ( )
4065
.iter ( )
4066
.any ( | x | x.name = = " policy" & & x.ok ) ) ;
767ac6cd Andy McCall 19 days ago
4067
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4068
a.pull ( " origin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4069
assert_eq! (
4070
a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial,
4071
serial_before + 1
4072
) ;
28fc23d7 Andy McCall 1 month ago ↶
4073
4074
assert! ( a.policy_revoke ( & key_a3) .is_err ( ) ) ;
75167672 Andy McCall 1 month ago
4075
let u = a
4076
.revoke_identity ( & key_c, " left the project" )
4077
.unwrap ( )
4078
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4079
assert! ( ! u.applied) ;
4080
assert! ( a.is_revoked ( & key_c) .unwrap ( ) ) ;
4081
assert! ( ! b.is_revoked ( & key_c) .unwrap ( ) ) ;
4082
let u = b.policy_sign ( u.signed ( ) ) .unwrap ( ) ;
4083
assert! ( u.applied & & u.policy.is_revoked ( & key_c) ) ;
4084
assert! ( b.is_revoked ( & key_c) .unwrap ( ) ) ;
4085
75167672 Andy McCall 1 month ago
4086
assert! (
4087
b.policy_revoke ( & key_a3) .is_err ( ) ,
4088
" would leave one root for a threshold of 2"
4089
) ;
28fc23d7 Andy McCall 1 month ago ↶
4090
4091
let key_c_own = c.config.author_key.unwrap ( ) ;
75167672 Andy McCall 1 month ago
4092
assert! ( c
4093
.revoke_identity ( & key_c_own, " lost laptop" )
4094
.unwrap ( )
4095
.is_none ( ) ) ;
28fc23d7 Andy McCall 1 month ago ↶
4096
assert! ( c.is_revoked ( & key_c_own) .unwrap ( ) ) ;
4097
assert_eq! ( key_c_own, key_c) ;
4098
}
4099
6e723b81 Andy McCall 1 month ago ↶
4100
# [ test ]
4101
fn renames_are_detected_and_tracked_as_moves ( ) {
4102
hermetic ( ) ;
4103
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4104
let root = dir.path ( ) ;
4105
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
4106
write (
4107
root,
4108
" src/old.rs" ,
4109
b " fn a() {}\n fn b() {}\n fn c() {}\n fn d() {}\n " ,
4110
) ;
6e723b81 Andy McCall 1 month ago ↶
4111
write ( root, " img.bin" , b " \x89 PNG\0 \0 \x01 \x02 " ) ;
4112
write ( root, " tiny.txt" , b " x\n " ) ;
4113
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4114
let node_old = repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) .files[ " src/old.rs" ] .node;
4115
let node_img = repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) .files[ " img.bin" ] .node;
4116
4117
4118
4119
fs:: remove_file( root.join ( " src/old.rs" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4120
write (
4121
root,
4122
" lib/new.rs" ,
4123
b " fn a() {}\n fn b() {}\n fn c() {}\n fn e() {}\n " ,
4124
) ;
6e723b81 Andy McCall 1 month ago ↶
4125
fs:: remove_file( root.join ( " img.bin" ) ) .unwrap ( ) ;
4126
write ( root, " assets/img.bin" , b " \x89 PNG\0 \0 \x01 \x02 " ) ;
4127
fs:: remove_file( root.join ( " tiny.txt" ) ) .unwrap ( ) ;
4128
write ( root, " other.txt" , b " completely different\n " ) ;
4129
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4130
let statuses: Vec< ( & str , ChangeStatus) > = snap
4131
.statuses
4132
.iter ( )
4133
.map ( | s | ( s.path.as_str ( ) , s.status) )
4134
.collect ( ) ;
6e723b81 Andy McCall 1 month ago ↶
4135
assert_eq! (
4136
statuses,
75167672 Andy McCall 1 month ago
4137
vec! [
4138
( " assets/img.bin" , ChangeStatus:: Renamed) ,
4139
( " lib/new.rs" , ChangeStatus:: Renamed) ,
4140
( " other.txt" , ChangeStatus:: Added) ,
4141
( " src" , ChangeStatus:: Removed) ,
4142
( " tiny.txt" , ChangeStatus:: Removed)
4143
]
6e723b81 Andy McCall 1 month ago ↶
4144
) ;
4145
let h2 = repo.commit ( " move things" , & [ ] ) .unwrap ( ) ;
4146
let c = repo.store ( ) .get_change ( & h2) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4147
let moves: Vec< _ > = c
4148
.tree_ops
4149
.iter ( )
4150
.filter ( | op | matches! ( op, TreeOp:: Move { .. } ) )
4151
.collect ( ) ;
6e723b81 Andy McCall 1 month ago ↶
4152
assert_eq! ( moves.len ( ) , 2 , " {:?}" , c.tree_ops) ;
75167672 Andy McCall 1 month ago
4153
assert! ( c.tree_ops.iter ( ) .any (
4154
| op | matches! ( op, TreeOp:: Move { node, name, .. } if * node = = node_old & & name = = " new.rs" )
4155
) ) ;
4156
assert! ( c.tree_ops.iter ( ) .any (
4157
| op | matches! ( op, TreeOp:: Move { node, name, .. } if * node = = node_img & & name = = " img.bin" )
4158
) ) ;
6e723b81 Andy McCall 1 month ago ↶
4159
let snap = repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
4160
assert_eq! (
4161
snap.files[ " lib/new.rs" ] .node, node_old,
4162
" the node survives the rename"
4163
) ;
4164
assert! (
4165
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 " )
4166
) ;
6e723b81 Andy McCall 1 month ago ↶
4167
assert! ( ! snap.files.contains_key ( " src/old.rs" ) & & ! snap.files.contains_key ( " src" ) ) ;
75167672 Andy McCall 1 month ago
4168
assert! ( render_change (
4169
& c,
4170
& repo.load_state ( & [ h1] ) .unwrap ( ) ,
4171
& repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( )
4172
)
4173
.contains ( " R " ) ) ;
6e723b81 Andy McCall 1 month ago ↶
4174
4175
let other = tempfile:: tempdir( ) .unwrap ( ) ;
4176
let clone = Repo:: clone( & root.to_string_lossy ( ) , other.path ( ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4177
assert_eq! (
4178
fs:: read( other.path ( ) .join ( " lib/new.rs" ) ) .unwrap ( ) ,
4179
b " fn a() {}\n fn b() {}\n fn c() {}\n fn e() {}\n "
4180
) ;
6e723b81 Andy McCall 1 month ago ↶
4181
assert! ( ! other.path ( ) .join ( " src" ) .exists ( ) ) ;
4182
assert! ( ! clone.snapshot ( " status" ) .unwrap ( ) .changed) ;
4183
4184
assert_eq! ( similarity ( b " a\n b\n " , b " a\n b\n " ) , 100 ) ;
4185
assert_eq! ( similarity ( b " a\n b\n c\n d\n " , b " a\n b\n x\n y\n " ) , 50 ) ;
4186
assert_eq! ( similarity ( b " hello\n " , b " world\n " ) , 0 ) ;
4187
assert_eq! ( similarity ( b " " , b " x\n " ) , 0 ) ;
4188
}
4189
30ea9c1e Andy McCall 1 month ago ↶
4190
# [ test ]
4191
fn git_fetch_and_push_round_trip ( ) {
4192
hermetic ( ) ;
75167672 Andy McCall 1 month ago
4193
if std:: process:: Command:: new( " git" )
4194
.arg ( " --version" )
4195
.output ( )
4196
.is_err ( )
4197
{
30ea9c1e Andy McCall 1 month ago ↶
4198
eprintln! ( " git not installed; skipping" ) ;
4199
return ;
4200
}
4201
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4202
let root = dir.path ( ) ;
4203
let git_in = | d : & Path, args : & [& str ]| -> String {
4204
let out = std:: process:: Command:: new( " git" )
4205
.arg ( " -C" )
4206
.arg ( d)
75167672 Andy McCall 1 month ago
4207
.args ( [
4208
" -c" ,
4209
" user.name=Grace" ,
4210
" -c" ,
4211
" [email protected] " ,
4212
" -c" ,
4213
" commit.gpgsign=false" ,
4214
] )
30ea9c1e Andy McCall 1 month ago ↶
4215
.args ( args)
4216
.output ( )
4217
.unwrap ( ) ;
75167672 Andy McCall 1 month ago
4218
assert! (
4219
out.status.success ( ) ,
4220
" git {args:?}: {}" ,
4221
String :: from_utf8_lossy( & out.stderr)
4222
) ;
30ea9c1e Andy McCall 1 month ago ↶
4223
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
4224
} ;
4225
4226
let remote = root.join ( " remote.git" ) ;
4227
fs:: create_dir_all( & remote) .unwrap ( ) ;
4228
git_in ( & remote, & [ " init" , " -q" , " --bare" , " -b" , " main" ] ) ;
4229
let work = root.join ( " work" ) ;
75167672 Andy McCall 1 month ago
4230
git_in (
4231
root,
4232
& [
4233
" clone" ,
4234
" -q" ,
4235
remote.to_str ( ) .unwrap ( ) ,
4236
work.to_str ( ) .unwrap ( ) ,
4237
] ,
4238
) ;
30ea9c1e Andy McCall 1 month ago ↶
4239
git_in ( & work, & [ " checkout" , " -q" , " -b" , " main" ] ) ;
4240
write ( & work, " README.md" , b " # Project\n " ) ;
4241
write ( & work, " src/lib.rs" , b " pub fn a() {}\n " ) ;
4242
git_in ( & work, & [ " add" , " ." ] ) ;
4243
git_in ( & work, & [ " commit" , " -q" , " -m" , " initial" ] ) ;
4244
write ( & work, " src/lib.rs" , b " pub fn a() {}\n pub fn b() {}\n " ) ;
4245
git_in ( & work, & [ " commit" , " -q" , " -am" , " second" ] ) ;
4246
git_in ( & work, & [ " push" , " -q" , " origin" , " main" ] ) ;
4247
let tip1 = git_in ( & work, & [ " rev-parse" , " HEAD" ] ) ;
4248
4249
4250
let o = root.join ( " oxy" ) ;
4251
let mut repo = Repo:: init( & o) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
4252
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
4253
.unwrap ( ) ;
30ea9c1e Andy McCall 1 month ago ↶
4254
let url = remote.to_string_lossy ( ) .to_string ( ) ;
4255
let r = repo.git_fetch ( & url, " main" ) .unwrap ( ) ;
4256
assert_eq! ( ( r.commits, r.heads) , ( 2 , 1 ) ) ;
75167672 Andy McCall 1 month ago
4257
assert_eq! (
4258
fs:: read( o.join ( " src/lib.rs" ) ) .unwrap ( ) ,
4259
b " pub fn a() {}\n pub fn b() {}\n "
4260
) ;
30ea9c1e Andy McCall 1 month ago ↶
4261
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
75167672 Andy McCall 1 month ago
4262
assert_eq! (
4263
repo.git_fetch ( & url, " main" ) .unwrap ( ) .commits,
4264
0 ,
4265
" nothing new"
4266
) ;
30ea9c1e Andy McCall 1 month ago ↶
4267
4268
75167672 Andy McCall 1 month ago
4269
write (
4270
& o,
4271
" src/lib.rs" ,
4272
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n " ,
4273
) ;
30ea9c1e Andy McCall 1 month ago ↶
4274
write ( & o, " NOTES.md" , b " from oxy\n " ) ;
4275
let h_oxy = repo.commit ( " add c and notes" , & [ ] ) .unwrap ( ) ;
4276
let r = repo.git_push ( & url, " main" ) .unwrap ( ) ;
4277
assert_eq! ( r.exported, 1 , " {r:?}" ) ;
4278
git_in ( & work, & [ " pull" , " -q" , " --ff-only" , " origin" , " main" ] ) ;
75167672 Andy McCall 1 month ago
4279
assert_eq! (
4280
git_in ( & work, & [ " log" , " --format=%s" , " main" ] ) ,
4281
" add c and notes\n second\n initial"
4282
) ;
30ea9c1e Andy McCall 1 month ago ↶
4283
assert_eq! ( git_in ( & work, & [ " rev-parse" , " HEAD~1" ] ) , tip1) ;
4284
assert_eq! ( fs:: read( work.join ( " NOTES.md" ) ) .unwrap ( ) , b " from oxy\n " ) ;
4285
assert_eq! ( git_in ( & work, & [ " log" , " -1" , " --format=%an" , " main" ] ) , " Ada" ) ;
75167672 Andy McCall 1 month ago
4286
assert! ( git_in ( & work, & [ " log" , " -1" , " --format=%B" , " main" ] )
4287
.contains ( & format! ( " Oxy-Hash: {} " , h_oxy.to_hex ( ) ) ) ) ;
4288
assert_eq! (
4289
repo.git_push ( & url, " main" ) .unwrap ( ) .exported,
4290
0 ,
4291
" already pushed"
4292
) ;
30ea9c1e Andy McCall 1 month ago ↶
4293
4294
4295
4296
write ( & work, " README.md" , b " # Project\n \n More.\n " ) ;
4297
git_in ( & work, & [ " commit" , " -q" , " -am" , " readme from git" ] ) ;
4298
git_in ( & work, & [ " push" , " -q" , " origin" , " main" ] ) ;
4299
let tip2 = git_in ( & work, & [ " rev-parse" , " HEAD" ] ) ;
75167672 Andy McCall 1 month ago
4300
assert! ( repo
4301
.git_push ( & url, " main" )
4302
.unwrap_err ( )
4303
.to_string ( )
4304
.contains ( " fetch" ) ) ;
30ea9c1e Andy McCall 1 month ago ↶
4305
let r = repo.git_fetch ( & url, " main" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4306
assert_eq! (
4307
( r.commits, r.heads) ,
4308
( 1 , 2 ) ,
4309
" the Git change and the oxy change commute"
4310
) ;
4311
assert_eq! (
4312
fs:: read( o.join ( " README.md" ) ) .unwrap ( ) ,
4313
b " # Project\n \n More.\n "
4314
) ;
30ea9c1e Andy McCall 1 month ago ↶
4315
assert_eq! ( fs:: read( o.join ( " NOTES.md" ) ) .unwrap ( ) , b " from oxy\n " ) ;
4316
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4317
4318
write ( & o, " NOTES.md" , b " from oxy\n again\n " ) ;
4319
repo.commit ( " notes again" , & [ ] ) .unwrap ( ) ;
4320
assert_eq! ( repo.git_push ( & url, " main" ) .unwrap ( ) .exported, 1 ) ;
4321
git_in ( & work, & [ " pull" , " -q" , " --ff-only" , " origin" , " main" ] ) ;
4322
assert_eq! ( git_in ( & work, & [ " rev-parse" , " HEAD~1" ] ) , tip2) ;
75167672 Andy McCall 1 month ago
4323
assert_eq! (
4324
fs:: read( work.join ( " NOTES.md" ) ) .unwrap ( ) ,
4325
b " from oxy\n again\n "
4326
) ;
4327
assert_eq! (
4328
git_in ( & work, & [ " log" , " --format=%s" , " main" ] )
4329
.lines ( )
4330
.count ( ) ,
4331
5
4332
) ;
30ea9c1e Andy McCall 1 month ago ↶
4333
}
4334
2b872d07 Andy McCall 1 month ago ↶
4335
4336
4337
# [ test ]
4338
# [ ignore ]
4339
fn bench_one_gibibyte_file ( ) {
4340
hermetic ( ) ;
4341
4342
let dir = match std:: env:: var_os( " OXY_BENCH_DIR" ) {
4343
Some ( d) => tempfile:: tempdir_in( d) .unwrap ( ) ,
4344
None => tempfile:: tempdir( ) .unwrap ( ) ,
4345
} ;
4346
let root = dir.path ( ) ;
75167672 Andy McCall 1 month ago
4347
let repo = Repo:: init( & root.join ( " r" ) )
4348
.unwrap ( )
4349
.with_home ( root.join ( " home" ) ) ;
2b872d07 Andy McCall 1 month ago ↶
4350
let size = 1 usize < < 30 ;
4351
let mut data = vec! [ 0 u8 ; size] ;
4352
let mut x = 0x9e3779b97f4a7c15 u64 ;
4353
for chunk in data.chunks_mut ( 8 ) {
4354
x ^ = x < < 13 ;
4355
x ^ = x > > 7 ;
4356
x ^ = x < < 17 ;
4357
let b = x.to_le_bytes ( ) ;
4358
chunk.copy_from_slice ( & b[ .. chunk.len ( ) ] ) ;
4359
}
4360
let t = std:: time:: Instant:: now( ) ;
4361
write ( & root.join ( " r" ) , " big.bin" , & data) ;
4362
eprintln! ( " write 1 GiB to disk: {:.2?}" , t.elapsed ( ) ) ;
4363
let t = std:: time:: Instant:: now( ) ;
4364
let h = repo.commit ( " big" , & [ ] ) .unwrap ( ) ;
4365
eprintln! ( " commit (chunk + hash + store): {:.2?}" , t.elapsed ( ) ) ;
4366
let t = std:: time:: Instant:: now( ) ;
4367
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4368
eprintln! ( " status on a clean 1 GiB working copy: {:.2?}" , t.elapsed ( ) ) ;
4369
let t = std:: time:: Instant:: now( ) ;
4370
let objects = repo.store ( ) .list ( ) .unwrap ( ) .len ( ) ;
4371
eprintln! ( " objects stored: {objects} ({:.2?} to list)" , t.elapsed ( ) ) ;
4372
let t = std:: time:: Instant:: now( ) ;
4373
let g = repo.gc ( true ) .unwrap ( ) ;
4374
eprintln! ( " gc (pack): {:.2?} {g:?}" , t.elapsed ( ) ) ;
4375
let t = std:: time:: Instant:: now( ) ;
4376
let clone = Repo:: clone( & root.join ( " r" ) .to_string_lossy ( ) , & root.join ( " c" ) ) .unwrap ( ) ;
4377
eprintln! ( " clone (fetch + checkout): {:.2?}" , t.elapsed ( ) ) ;
4378
let t = std:: time:: Instant:: now( ) ;
4379
let back = fs:: read( root.join ( " c/big.bin" ) ) .unwrap ( ) ;
4380
assert_eq! ( blake3:: hash( & back) , blake3:: hash( & data) ) ;
4381
eprintln! ( " read back and verify: {:.2?}" , t.elapsed ( ) ) ;
4382
assert_eq! ( clone.bookmark_heads ( ) .unwrap ( ) , vec! [ h] ) ;
4383
let store_bytes: u64 = walkdir_size ( & root.join ( " r/.oxy/objects" ) ) ;
75167672 Andy McCall 1 month ago
4384
eprintln! (
4385
" store size: {:.1} MiB for 1 GiB of data" ,
4386
store_bytes as f64 / ( 1024. 0 * 1024. 0 )
4387
) ;
2b872d07 Andy McCall 1 month ago ↶
4388
}
4389
4390
fn walkdir_size ( p : & Path) -> u64 {
4391
let mut total = 0 ;
4392
if let Ok ( rd) = fs:: read_dir( p) {
4393
for e in rd.flatten ( ) {
4394
let path = e.path ( ) ;
4395
if path.is_dir ( ) {
4396
total + = walkdir_size ( & path) ;
4397
} else if let Ok ( m) = fs:: metadata( & path) {
4398
total + = m.len ( ) ;
4399
}
4400
}
4401
}
4402
total
4403
}
4404
b7f102fc Andy McCall 1 month ago ↶
4405
4406
4407
# [ cfg ( unix ) ]
4408
# [ test ]
4409
fn stat_cache_skips_reading_unchanged_opaque_files ( ) {
4410
use std:: os:: unix:: fs:: PermissionsExt;
4411
hermetic ( ) ;
4412
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4413
let root = dir.path ( ) ;
4414
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4415
let mut big = vec! [ 0 u8 ; 300 * 1024 ] ;
4416
for ( i, b) in big.iter_mut ( ) .enumerate ( ) {
4417
* b = ( i as u32 ) .wrapping_mul ( 2654435761 ) .to_le_bytes ( ) [ 1 ] ;
4418
}
4419
big[ 0 ] = 0 ;
4420
write ( root, " big.bin" , & big) ;
4421
write ( root, " a.txt" , b " text\n " ) ;
4422
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4423
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4424
assert! (
4425
m.entries[ " big.bin" ] .blob.is_some ( ) ,
4426
" opaque files record their blob"
4427
) ;
b7f102fc Andy McCall 1 month ago ↶
4428
assert! ( m.entries[ " a.txt" ] .blob.is_none ( ) ) ;
4429
let blob = m.entries[ " big.bin" ] .blob.unwrap ( ) ;
4430
assert! ( repo.store ( ) .has ( & blob) ) ;
4431
4432
4433
4434
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
75167672 Andy McCall 1 month ago
4435
fs:: File:: options( )
4436
.write ( true )
4437
.open ( root.join ( " big.bin" ) )
4438
.unwrap ( )
4439
.set_modified ( old)
4440
.unwrap ( ) ;
b7f102fc Andy McCall 1 month ago ↶
4441
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4442
4443
fs:: set_permissions( root.join ( " big.bin" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
4444
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4445
assert! (
4446
! snap.changed & & snap.statuses.is_empty ( ) ,
4447
" {:?}" ,
4448
snap.statuses
4449
) ;
b7f102fc Andy McCall 1 month ago ↶
4450
fs:: set_permissions( root.join ( " big.bin" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
4451
4452
let mut big2 = big.clone ( ) ;
4453
big2[ 10 ] ^ = 0xff ;
4454
write ( root, " big.bin" , & big2) ;
4455
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4456
assert_eq! (
4457
snap.statuses
4458
.iter ( )
4459
.map ( | s | ( s.path.as_str ( ) , s.status) )
4460
.collect:: < Vec< _ > > ( ) ,
4461
vec! [ ( " big.bin" , ChangeStatus:: Modified) ]
4462
) ;
b7f102fc Andy McCall 1 month ago ↶
4463
let h2 = repo.commit ( " edit" , & [ ] ) .unwrap ( ) ;
4464
let files = repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
4465
assert_eq! (
4466
files[ " big.bin" ] .content,
4467
FileContent:: Opaque {
4468
blobs: vec! [ Repo:: blob_ref( & big2) ]
4469
}
4470
) ;
b7f102fc Andy McCall 1 month ago ↶
4471
4472
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
4473
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
4474
assert_eq! ( m.entries[ " big.bin" ] .blob, Some ( Repo:: blob_ref( & big) ) ) ;
4475
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4476
}
4477
50a046e4 Andy McCall 1 month ago ↶
4478
# [ test ]
4479
fn status_json_lists_conflicts_with_contributors ( ) {
4480
hermetic ( ) ;
4481
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4482
let root = dir.path ( ) ;
4483
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4484
write ( root, " a.txt" , b " one\n " ) ;
4485
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4486
write ( root, " a.txt" , b " one\n left\n " ) ;
4487
let ha = repo.commit ( " left" , & [ ] ) .unwrap ( ) ;
4488
repo.checkout ( & [ h1] , true , " checkout" ) .unwrap ( ) ;
4489
write ( root, " a.txt" , b " one\n right\n " ) ;
4490
let hb = repo.commit ( " right" , & [ ] ) .unwrap ( ) ;
4491
repo.checkout ( & [ ha, hb] , true , " checkout" ) .unwrap ( ) ;
4492
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4493
assert! ( snap.base_snapshot.is_conflicted ( ) ) ;
4494
let json = repo.status_json ( & snap) .unwrap ( ) ;
4495
let ida = repo.store ( ) .get_change ( & ha) .unwrap ( ) .change_id.to_hex ( ) ;
4496
let idb = repo.store ( ) .get_change ( & hb) .unwrap ( ) .change_id.to_hex ( ) ;
4497
assert! ( json.contains ( " \" conflicted\" :true" ) , " {json}" ) ;
75167672 Andy McCall 1 month ago
4498
assert! (
4499
json.contains ( " \" kind\" :\" text\" ,\" path\" :\" a.txt\" " ) ,
4500
" {json}"
4501
) ;
50a046e4 Andy McCall 1 month ago ↶
4502
assert! ( json.contains ( & ida) & & json.contains ( & idb) , " {json}" ) ;
4503
assert! ( json.contains ( " \" resolved\" :false" ) , " {json}" ) ;
4504
assert! ( json.starts_with ( ' {' ) & & json.ends_with ( ' }' ) ) ;
4505
4506
write ( root, " a.txt" , b " one\n left\n right\n " ) ;
4507
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4508
let json = repo.status_json ( & snap) .unwrap ( ) ;
4509
assert! ( json.contains ( " \" resolved\" :true" ) , " {json}" ) ;
4510
repo.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
4511
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4512
let json = repo.status_json ( & snap) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4513
assert! (
4514
json.contains ( " \" clean\" :true,\" conflicted\" :false,\" paths\" :[],\" conflicts\" :[]" ) ,
4515
" {json}"
4516
) ;
50a046e4 Andy McCall 1 month ago ↶
4517
}
4518
9ac1fbe2 Andy McCall 1 month ago ↶
4519
# [ test ]
4520
fn ignore_rules_do_not_untrack_files ( ) {
4521
hermetic ( ) ;
4522
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4523
let root = dir.path ( ) ;
4524
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4525
write ( root, " src.c" , b " int x;\n " ) ;
4526
write ( root, " build/out.o" , b " \0 obj" ) ;
4527
write ( root, " sub/keep.log" , b " keep\n " ) ;
4528
write ( root, " sub/junk.log" , b " junk\n " ) ;
4529
write ( root, " .oxyignore" , b " /build\n *.log\n !sub/keep.log\n " ) ;
4530
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4531
let added: Vec< & str > = snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect ( ) ;
75167672 Andy McCall 1 month ago
4532
assert_eq! (
4533
added,
4534
vec! [ " .oxyignore" , " src.c" , " sub/keep.log" ] ,
4535
" anchored, glob, and negated rules"
4536
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4537
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4538
4539
write ( root, " .oxyignore" , b " /build\n *.log\n !sub/keep.log\n src.c\n " ) ;
4540
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4541
assert_eq! (
4542
snap.statuses
4543
.iter ( )
4544
.map ( | s | s.path.as_str ( ) )
4545
.collect:: < Vec< _ > > ( ) ,
4546
vec! [ " .oxyignore" ]
4547
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4548
let h2 = repo.commit ( " ignore src.c" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4549
assert! ( repo
4550
.load_state ( & [ h2] )
4551
.unwrap ( )
4552
.materialize ( )
4553
.files
4554
.contains_key ( " src.c" ) ) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4555
4556
assert_eq! ( repo.quick_dirty ( ) .unwrap ( ) , Some ( false ) ) ;
4557
write ( root, " src.c" , b " int x, y;\n " ) ;
4558
assert_eq! ( repo.quick_dirty ( ) .unwrap ( ) , Some ( true ) ) ;
4559
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4560
assert_eq! (
4561
snap.statuses
4562
.iter ( )
4563
.map ( | s | ( s.path.as_str ( ) , s.status) )
4564
.collect:: < Vec< _ > > ( ) ,
4565
vec! [ ( " src.c" , ChangeStatus:: Modified) ]
4566
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4567
let h3 = repo.commit ( " edit src.c" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4568
assert! (
4569
matches! ( & repo.load_state ( & [ h3] ) .unwrap ( ) .materialize ( ) .files[ " src.c" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " int x, y;\n " )
4570
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4571
4572
fs:: remove_file( root.join ( " src.c" ) ) .unwrap ( ) ;
4573
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4574
assert_eq! (
4575
snap.statuses
4576
.iter ( )
4577
.map ( | s | ( s.path.as_str ( ) , s.status) )
4578
.collect:: < Vec< _ > > ( ) ,
4579
vec! [ ( " src.c" , ChangeStatus:: Removed) ]
4580
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4581
repo.checkout ( & [ h3] , true , " checkout" ) .unwrap ( ) ;
4582
assert_eq! ( fs:: read( root.join ( " src.c" ) ) .unwrap ( ) , b " int x, y;\n " ) ;
4583
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4584
let _ = h1;
4585
}
4586
b837d2b5 Andy McCall 1 month ago ↶
4587
# [ test ]
4588
fn split_works_on_non_heads_by_node ( ) {
4589
hermetic ( ) ;
4590
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4591
let root = dir.path ( ) ;
4592
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4593
write ( root, " a.txt" , b " one\n " ) ;
4594
write ( root, " junk/x.lock" , b " lock\n " ) ;
4595
let h1 = repo.commit ( " work plus junk" , & [ ] ) .unwrap ( ) ;
4596
write ( root, " a.txt" , b " one\n two\n " ) ;
4597
let h2 = repo.commit ( " more work" , & [ ] ) .unwrap ( ) ;
4598
assert_eq! ( repo.store ( ) .get_change ( & h2) .unwrap ( ) .deps, vec! [ h1] ) ;
4599
let id1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) .change_id;
4600
4601
let ( hj, h1b) = repo.split ( & h1, & [ " junk" .into ( ) ] ) .unwrap ( ) ;
4602
assert_ne! ( h1b, h1) ;
75167672 Andy McCall 1 month ago
4603
assert_eq! (
4604
repo.store ( ) .get_change ( & h1b) .unwrap ( ) .change_id,
4605
id1,
4606
" the rest keeps the ChangeId"
4607
) ;
b837d2b5 Andy McCall 1 month ago ↶
4608
let junk = repo.store ( ) .get_change ( & hj) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4609
assert! ( junk.tree_ops.iter ( ) .all (
4610
| op | matches! ( op, TreeOp:: AddNode { name, .. } if name = = " junk" | | name = = " x.lock" )
4611
) ) ;
b837d2b5 Andy McCall 1 month ago ↶
4612
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4613
assert! (
4614
heads.contains ( & hj) ,
4615
" the split-off part is a head of its own"
4616
) ;
b837d2b5 Andy McCall 1 month ago ↶
4617
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
4618
assert! (
4619
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n " ) ,
4620
" the dependent was rewritten onto the new hash"
4621
) ;
b837d2b5 Andy McCall 1 month ago ↶
4622
assert! ( snap.files.contains_key ( " junk/x.lock" ) ) ;
75167672 Andy McCall 1 month ago
4623
let h2b = heads
4624
.iter ( )
4625
.find ( | h | repo.store ( ) .get_change ( h) .unwrap ( ) .message = = " more work" )
4626
.copied ( )
4627
.unwrap ( ) ;
b837d2b5 Andy McCall 1 month ago ↶
4628
assert_eq! ( repo.store ( ) .get_change ( & h2b) .unwrap ( ) .deps, vec! [ h1b] ) ;
4629
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4630
4631
repo.abandon ( & hj) .unwrap ( ) ;
4632
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
4633
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
4634
assert! ( ! snap.files.contains_key ( " junk/x.lock" ) & & ! snap.files.contains_key ( " junk" ) ) ;
4635
assert! ( snap.files.contains_key ( " a.txt" ) ) ;
4636
4637
write ( root, " junk/y.lock" , b " y\n " ) ;
4638
let hb = repo.commit ( " junk base" , & [ ] ) .unwrap ( ) ;
4639
write ( root, " junk/y.lock" , b " yy\n " ) ;
4640
write ( root, " b.txt" , b " b\n " ) ;
4641
repo.commit ( " touches junk" , & [ ] ) .unwrap ( ) ;
4642
let err = repo.split ( & hb, & [ " junk" .into ( ) ] ) .unwrap_err ( ) .to_string ( ) ;
4643
assert! ( err.contains ( " depends on the split-off paths" ) , " {err}" ) ;
4644
}
4645
7b9809e6 Andy McCall 1 month ago ↶
4646
# [ test ]
4647
fn bookmarks_switch_merge_and_push ( ) {
4648
hermetic ( ) ;
4649
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4650
let root = dir.path ( ) ;
4651
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
4652
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
4653
let mut repo = Repo:: clone( & url, & root.join ( " r" ) )
4654
.unwrap ( )
4655
.with_home ( root.join ( " home" ) ) ;
7b9809e6 Andy McCall 1 month ago ↶
4656
let r = root.join ( " r" ) ;
4657
write ( & r, " a.txt" , b " a\n " ) ;
4658
let h_a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
4659
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
4660
origin.refresh ( ) .unwrap ( ) ;
7b9809e6 Andy McCall 1 month ago ↶
4661
4662
4663
assert! ( repo.bookmark_create ( " bad name.." , None ) .is_err ( ) ) ;
4664
assert! ( repo.bookmark_create ( " -x" , None ) .is_err ( ) ) ;
4665
assert_eq! ( repo.bookmark_create ( " feature" , None ) .unwrap ( ) , vec! [ h_a] ) ;
4666
assert! ( repo.bookmark_create ( " feature" , None ) .is_err ( ) , " exists" ) ;
4667
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
4668
assert_eq! ( repo.config.bookmark, " feature" ) ;
4669
write ( & r, " a.txt" , b " a\n b\n " ) ;
4670
write ( & r, " b.txt" , b " b\n " ) ;
4671
let h_b = repo.commit ( " b on feature" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4672
assert_eq! (
4673
repo.bookmark_heads ( ) .unwrap ( ) ,
4674
vec! [ h_b] ,
4675
" b edits a.txt, so it depends on a"
4676
) ;
4677
assert_eq! (
4678
repo.resolve_heads ( " main" ) .unwrap ( ) ,
4679
vec! [ h_a] ,
4680
" main is untouched"
4681
) ;
4682
assert_eq! (
4683
repo.resolve_change ( " feature" ) .unwrap ( ) ,
4684
h_b,
4685
" a one-head bookmark names its change"
4686
) ;
7b9809e6 Andy McCall 1 month ago ↶
4687
4688
let reopened = Repo:: open( & r) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4689
assert_eq! ( reopened.config.bookmark, " feature" ) ;
4690
4691
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
4692
assert! ( ! r.join ( " b.txt" ) .exists ( ) ) ;
4693
assert_eq! ( fs:: read( r.join ( " a.txt" ) ) .unwrap ( ) , b " a\n " ) ;
4694
write ( & r, " c.txt" , b " c\n " ) ;
4695
let h_c = repo.commit ( " c on main" , & [ ] ) .unwrap ( ) ;
4696
let list = repo.bookmarks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4697
assert_eq! (
4698
list.iter ( )
4699
.map ( | b | ( b.name.as_str ( ) , b.current) )
4700
.collect:: < Vec< _ > > ( ) ,
4701
vec! [ ( " feature" , false ) , ( " main" , true ) ]
4702
) ;
4703
assert_eq! (
4704
list.iter ( ) .find ( | b | b.name = = " main" ) .unwrap ( ) .ahead,
4705
Some ( 1 ) ,
4706
" c is not on origin yet"
4707
) ;
7b9809e6 Andy McCall 1 month ago ↶
4708
4709
let m = repo.merge ( " feature" ) .unwrap ( ) ;
4710
assert! ( m.changed & & m.checked_out) ;
4711
let mut want = vec! [ h_b, h_c] ;
4712
want.sort ( ) ;
4713
assert_eq! ( m.heads, want) ;
4714
assert_eq! ( fs:: read( r.join ( " b.txt" ) ) .unwrap ( ) , b " b\n " ) ;
4715
assert! ( ! repo.merge ( " feature" ) .unwrap ( ) .changed, " already merged" ) ;
4716
assert! ( repo.merge ( " main" ) .is_err ( ) ) ;
4717
4718
assert! ( repo.push_bookmark ( " origin" , " feature" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
4719
origin.refresh ( ) .unwrap ( ) ;
7b9809e6 Andy McCall 1 month ago ↶
4720
assert_eq! ( origin.resolve_heads ( " feature" ) .unwrap ( ) , vec! [ h_b] ) ;
4721
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
4722
origin.refresh ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4723
assert_eq! (
4724
repo.bookmarks ( )
4725
.unwrap ( )
4726
.iter ( )
4727
.find ( | b | b.name = = " main" )
4728
.unwrap ( )
4729
.ahead,
4730
Some ( 0 )
4731
) ;
7b9809e6 Andy McCall 1 month ago ↶
4732
4733
let ws = repo.workspace_add ( & root.join ( " ws" ) , None ) .unwrap ( ) ;
4734
assert_eq! ( ws.config.bookmark, " main" ) ;
4735
let mut ws = ws;
4736
ws.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4737
assert_eq! (
4738
Repo:: open( & r) .unwrap ( ) .config.bookmark,
4739
" main" ,
4740
" the first workspace is unaffected"
4741
) ;
4742
assert! (
4743
repo.bookmark_delete ( " feature" ) .is_err ( ) ,
4744
" current bookmark of another workspace"
4745
) ;
7b9809e6 Andy McCall 1 month ago ↶
4746
ws.bookmark_switch ( " main" , false ) .unwrap ( ) ;
4747
4748
assert_eq! ( repo.bookmark_set ( " feature" , & [ h_a] ) .unwrap ( ) , vec! [ h_a] ) ;
4749
assert! ( repo.bookmark_delete ( " main" ) .is_err ( ) , " current here" ) ;
4750
repo.bookmark_delete ( " feature" ) .unwrap ( ) ;
4751
assert! ( repo.resolve_heads ( " feature" ) .is_err ( ) ) ;
4752
assert! ( repo.bookmark_set ( " feature" , & [ h_a] ) .is_err ( ) ) ;
75167672 Andy McCall 1 month ago
4753
assert! (
4754
repo.checkout ( & [ h_a] , false , " checkout" ) .is_ok ( ) ,
4755
" changes of a deleted bookmark remain"
4756
) ;
7b9809e6 Andy McCall 1 month ago ↶
4757
}
4758
4dee277c Andy McCall 1 month ago ↶
4759
# [ test ]
4760
fn oxygen_subrepos_pin_follow_and_update ( ) {
4761
hermetic ( ) ;
4762
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4763
let root = dir.path ( ) ;
4764
4765
let lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
4766
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
4767
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
4768
.unwrap ( )
4769
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4770
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
4771
let l1 = lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
4772
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
4773
let _ = lib_origin;
4774
4775
4776
let app_origin = Repo:: init( & root.join ( " app-origin" ) ) .unwrap ( ) ;
4777
let app_url = root.join ( " app-origin" ) .to_string_lossy ( ) .to_string ( ) ;
4778
let _ = app_origin;
4779
let app_dir = root.join ( " app" ) ;
75167672 Andy McCall 1 month ago
4780
let app = Repo:: clone( & app_url, & app_dir)
4781
.unwrap ( )
4782
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4783
write ( & app_dir, " main.txt" , b " app\n " ) ;
4784
let pin = app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
4785
assert! ( matches! ( & pin, crate :: subrepo:: Pin:: Oxy { heads, .. } if heads = = & vec! [ l1] ) ) ;
4786
assert! ( app_dir.join ( " .oxymodules" ) .exists ( ) & & app_dir.join ( " libs/lib/.oxy" ) .is_dir ( ) ) ;
4787
let snap = app.snapshot ( " status" ) .unwrap ( ) ;
4788
let paths: Vec< & str > = snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect ( ) ;
75167672 Andy McCall 1 month ago
4789
assert_eq! (
4790
paths,
4791
vec! [ " .oxymodules" , " libs/lib" , " main.txt" ] ,
4792
" the nested files are not walked"
4793
) ;
4dee277c Andy McCall 1 month ago ↶
4794
let a1 = app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
4795
let files = app.load_state ( & [ a1] ) .unwrap ( ) .materialize ( ) .files;
4796
assert_eq! ( files[ " libs/lib" ] .kind, NodeKind:: Subrepo) ;
4797
assert! ( ! files.contains_key ( " libs/lib/lib.txt" ) ) ;
4798
assert! ( ! app.snapshot ( " status" ) .unwrap ( ) .changed) ;
4799
let subs = app.subrepos ( ) .unwrap ( ) ;
4800
assert_eq! ( subs.len ( ) , 1 ) ;
4801
assert_eq! ( subs[ 0 ] .actual.as_ref ( ) , Some ( & subs[ 0 ] .pin) ) ;
4802
4803
75167672 Andy McCall 1 month ago
4804
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) )
4805
.unwrap ( )
4806
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4807
write ( & app_dir.join ( " libs/lib" ) , " lib.txt" , b " v2\n " ) ;
4808
let l2 = nested.commit ( " lib v2" , & [ ] ) .unwrap ( ) ;
4809
assert! ( nested.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
4810
let snap = app.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4811
assert_eq! (
4812
snap.statuses
4813
.iter ( )
4814
.map ( | s | ( s.path.as_str ( ) , s.status) )
4815
.collect:: < Vec< _ > > ( ) ,
4816
vec! [ ( " libs/lib" , ChangeStatus:: Modified) ]
4817
) ;
4dee277c Andy McCall 1 month ago ↶
4818
let a2 = app.commit ( " bump lib" , & [ ] ) .unwrap ( ) ;
1a3b6203 Andy McCall 1 month ago ↶
4819
4820
4dee277c Andy McCall 1 month ago ↶
4821
let r = app.checkout ( & [ a1] , false , " checkout" ) .unwrap ( ) ;
1a3b6203 Andy McCall 1 month ago ↶
4822
assert! (
4823
r.notes.len ( ) = = 1 & & r.notes[ 0 ] .contains ( " keeps 1 newer change" ) ,
4824
" {:?}" ,
4825
r.notes
4826
) ;
4dee277c Andy McCall 1 month ago ↶
4827
assert_eq! ( fs:: read( app_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v1\n " ) ;
1a3b6203 Andy McCall 1 month ago ↶
4828
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) ) .unwrap ( ) ;
4829
assert_eq! ( nested.bookmark_heads ( ) .unwrap ( ) , vec! [ l2] ) ;
4830
assert_eq! ( nested.working_copy ( ) .unwrap ( ) .1. deps, vec! [ l1] ) ;
4dee277c Andy McCall 1 month ago ↶
4831
assert! ( ! app.snapshot ( " status" ) .unwrap ( ) .changed) ;
4832
4833
write ( & app_dir.join ( " libs/lib" ) , " scratch.txt" , b " wip\n " ) ;
4834
let r = app.checkout ( & [ a2] , false , " checkout" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4835
assert! (
4836
r.notes.iter ( ) .any ( | n | n.contains ( " uncommitted" ) ) ,
4837
" {:?}" ,
4838
r.notes
4839
) ;
4dee277c Andy McCall 1 month ago ↶
4840
assert_eq! ( fs:: read( app_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v1\n " ) ;
4841
fs:: remove_file( app_dir.join ( " libs/lib/scratch.txt" ) ) .unwrap ( ) ;
4842
let r = app.submodule_update ( false , & [ ] ) .unwrap ( ) ;
4843
assert_eq! ( r, vec! [ ( " libs/lib" .to_string ( ) , SubrepoSync:: Updated) ] ) ;
4844
assert_eq! ( fs:: read( app_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v2\n " ) ;
4845
let _ = l2;
4846
4847
4848
assert! ( app.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
4849
let clone_dir = root.join ( " app-clone" ) ;
75167672 Andy McCall 1 month ago
4850
let clone = Repo:: clone( & app_url, & clone_dir)
4851
.unwrap ( )
4852
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4853
assert! ( ! clone_dir.join ( " libs/lib/.oxy" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
4854
assert_eq! (
4855
clone.submodule_update ( false , & [ ] ) .unwrap ( ) ,
4856
vec! [ ( " libs/lib" .to_string ( ) , SubrepoSync:: Missing) ]
4857
) ;
4858
assert_eq! (
4859
clone.submodule_update ( true , & [ ] ) .unwrap ( ) ,
4860
vec! [ ( " libs/lib" .to_string ( ) , SubrepoSync:: Updated) ]
4861
) ;
4862
assert_eq! (
4863
fs:: read( clone_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) ,
4864
b " v2\n "
4865
) ;
4dee277c Andy McCall 1 month ago ↶
4866
assert! ( ! clone.snapshot ( " status" ) .unwrap ( ) .changed) ;
4867
4868
clone.submodule_remove ( " libs/lib" ) .unwrap ( ) ;
4869
let snap = clone.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4870
assert! (
4871
snap.statuses
4872
.iter ( )
4873
.any ( | s | s.path = = " libs/lib" & & s.status = = ChangeStatus:: Removed ) ,
4874
" {:?}" ,
4875
snap.statuses
4876
) ;
4dee277c Andy McCall 1 month ago ↶
4877
let c = clone.commit ( " drop lib" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4878
assert! ( ! clone
4879
.load_state ( & [ c] )
4880
.unwrap ( )
4881
.materialize ( )
4882
.files
4883
.contains_key ( " libs/lib" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4884
assert! ( clone_dir.join ( " libs/lib/lib.txt" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
4885
assert! (
4886
! clone.snapshot ( " status" ) .unwrap ( ) .changed,
4887
" an unregistered nested repository is ignored"
4888
) ;
4dee277c Andy McCall 1 month ago ↶
4889
}
4890
4891
# [ test ]
4892
fn git_submodules_import_and_export_as_gitlinks ( ) {
4893
hermetic ( ) ;
75167672 Andy McCall 1 month ago
4894
if std:: process:: Command:: new( " git" )
4895
.arg ( " --version" )
4896
.output ( )
4897
.is_err ( )
4898
{
4dee277c Andy McCall 1 month ago ↶
4899
eprintln! ( " git not installed; skipping" ) ;
4900
return ;
4901
}
4902
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4903
let root = dir.path ( ) ;
4904
let git_in = | d : & Path, args : & [& str ]| -> String {
4905
let out = std:: process:: Command:: new( " git" )
4906
.arg ( " -C" )
4907
.arg ( d)
75167672 Andy McCall 1 month ago
4908
.args ( [
4909
" -c" ,
4910
" user.name=Grace" ,
4911
" -c" ,
4912
" [email protected] " ,
4913
" -c" ,
4914
" commit.gpgsign=false" ,
4915
" -c" ,
4916
" protocol.file.allow=always" ,
4917
] )
4dee277c Andy McCall 1 month ago ↶
4918
.args ( args)
4919
.output ( )
4920
.unwrap ( ) ;
75167672 Andy McCall 1 month ago
4921
assert! (
4922
out.status.success ( ) ,
4923
" git {args:?}: {}" ,
4924
String :: from_utf8_lossy( & out.stderr)
4925
) ;
4dee277c Andy McCall 1 month ago ↶
4926
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
4927
} ;
4928
let sub = root.join ( " sub" ) ;
4929
fs:: create_dir_all( & sub) .unwrap ( ) ;
4930
git_in ( & sub, & [ " init" , " -q" , " -b" , " main" ] ) ;
4931
write ( & sub, " s.txt" , b " sub\n " ) ;
4932
git_in ( & sub, & [ " add" , " ." ] ) ;
4933
git_in ( & sub, & [ " commit" , " -q" , " -m" , " sub initial" ] ) ;
4934
let sub_sha = git_in ( & sub, & [ " rev-parse" , " HEAD" ] ) ;
4935
let g = root.join ( " main.git" ) ;
4936
fs:: create_dir_all( & g) .unwrap ( ) ;
4937
git_in ( & g, & [ " init" , " -q" , " -b" , " main" ] ) ;
4938
write ( & g, " README.md" , b " # app\n " ) ;
4939
git_in ( & g, & [ " add" , " ." ] ) ;
4940
git_in ( & g, & [ " commit" , " -q" , " -m" , " initial" ] ) ;
75167672 Andy McCall 1 month ago
4941
git_in (
4942
& g,
4943
& [
4944
" submodule" ,
4945
" add" ,
4946
" -q" ,
4947
sub.to_str ( ) .unwrap ( ) ,
4948
" vendor/sub" ,
4949
] ,
4950
) ;
4dee277c Andy McCall 1 month ago ↶
4951
git_in ( & g, & [ " commit" , " -q" , " -m" , " add submodule" ] ) ;
4952
4953
let dest = root.join ( " oxy" ) ;
4954
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4955
let rep = repo.import_git ( & g, " main" ) .unwrap ( ) ;
4956
assert_eq! ( ( rep.commits, rep.submodules) , ( 2 , 1 ) ) ;
75167672 Andy McCall 1 month ago
4957
let files = repo
4958
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
4959
.unwrap ( )
4960
.materialize ( )
4961
.files;
4dee277c Andy McCall 1 month ago ↶
4962
assert_eq! ( files[ " vendor/sub" ] .kind, NodeKind:: Subrepo) ;
75167672 Andy McCall 1 month ago
4963
let FileContent:: Opaque { blobs } = & files[ " vendor/sub" ] .content else {
4964
panic! ( )
4965
} ;
4966
assert_eq! (
4967
crate :: subrepo:: Pin:: decode( & repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) ) .unwrap ( ) ,
4968
crate :: subrepo:: Pin:: Git {
4969
sha: sub_sha.clone ( ) ,
4970
branch: " HEAD" .into ( )
4971
}
4972
) ;
4973
let modules =
4974
crate :: subrepo:: Modules:: parse( & fs:: read_to_string( dest.join ( " .oxymodules" ) ) .unwrap ( ) ) ;
4dee277c Andy McCall 1 month ago ↶
4975
assert_eq! ( modules.entries[ " vendor/sub" ] .url, sub.to_string_lossy ( ) ) ;
4976
assert! ( modules.entries[ " vendor/sub" ] .git) ;
75167672 Andy McCall 1 month ago
4977
assert! (
4978
files.contains_key ( " .gitmodules" ) ,
4979
" the Git file is kept for round trips"
4980
) ;
4981
assert! (
4982
! dest.join ( " vendor/sub/.git" ) .exists ( ) ,
4983
" not cloned until update --init"
4984
) ;
4dee277c Andy McCall 1 month ago ↶
4985
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4986
75167672 Andy McCall 1 month ago
4987
assert_eq! (
4988
repo.submodule_update ( true , & [ ] ) .unwrap ( ) ,
4989
vec! [ ( " vendor/sub" .to_string ( ) , SubrepoSync:: Updated) ]
4990
) ;
4991
assert_eq! (
4992
git_in ( & dest.join ( " vendor/sub" ) , & [ " rev-parse" , " HEAD" ] ) ,
4993
sub_sha
4994
) ;
4dee277c Andy McCall 1 month ago ↶
4995
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4996
4997
let mirror = root.join ( " mirror.git" ) ;
4998
let rep = repo.git_export ( & mirror) .unwrap ( ) ;
4999
assert_eq! ( rep.skipped_subrepos, 0 ) ;
5000
let ls = git_in ( & mirror, & [ " ls-tree" , " main" , " vendor/sub" ] ) ;
75167672 Andy McCall 1 month ago
5001
assert! (
5002
ls.starts_with ( " 160000 commit " ) & & ls.contains ( & sub_sha) ,
5003
" {ls}"
5004
) ;
5005
assert_eq! (
5006
git_in ( & mirror, & [ " ls-tree" , " --name-only" , " main" ] ) ,
5007
" .gitmodules\n README.md\n vendor"
5008
) ;
4dee277c Andy McCall 1 month ago ↶
5009
let orig_tree = git_in ( & g, & [ " rev-parse" , " main^{tree}" ] ) ;
75167672 Andy McCall 1 month ago
5010
assert_eq! (
5011
git_in ( & mirror, & [ " rev-parse" , " main^{tree}" ] ) ,
5012
orig_tree,
5013
" tree round trip"
5014
) ;
4dee277c Andy McCall 1 month ago ↶
5015
}
5016
957fee30 Andy McCall 1 month ago ↶
5017
# [ test ]
5018
fn partial_commits_by_path_and_by_hunk ( ) {
5019
hermetic ( ) ;
5020
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5021
let root = dir.path ( ) ;
5022
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5023
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
5024
write ( root, " b.txt" , b " b\n " ) ;
5025
let h0 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
5026
write ( root, " a.txt" , b " 0\n 1\n 2\n 3\n 4\n " ) ;
5027
write ( root, " b.txt" , b " b\n bb\n " ) ;
5028
write ( root, " c.txt" , b " c\n " ) ;
5029
75167672 Andy McCall 1 month ago
5030
let h1 = repo
5031
.commit_selection ( " a only" , & [ ] , & Selection:: Paths( vec! [ " a.txt" .into ( ) ] ) )
5032
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5033
let c1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5034
assert! (
5035
c1.tree_ops.is_empty ( ) & & c1.edits.iter ( ) .all ( | e | e.node ( ) = = c1.edits[ 0 ] .node ( ) ) ,
5036
" {:?}" ,
5037
c1.tree_ops
5038
) ;
957fee30 Andy McCall 1 month ago ↶
5039
assert_eq! ( c1.deps, vec! [ h0] ) ;
5040
let files = repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
5041
assert! (
5042
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 0\n 1\n 2\n 3\n 4\n " )
5043
) ;
957fee30 Andy McCall 1 month ago ↶
5044
assert! ( matches! ( & files[ " b.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " b\n " ) ) ;
5045
assert! ( ! files.contains_key ( " c.txt" ) ) ;
5046
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5047
assert_eq! (
5048
snap.statuses
5049
.iter ( )
5050
.map ( | s | ( s.path.as_str ( ) , s.status) )
5051
.collect:: < Vec< _ > > ( ) ,
5052
vec! [
5053
( " b.txt" , ChangeStatus:: Modified) ,
5054
( " c.txt" , ChangeStatus:: Added)
5055
]
5056
) ;
5057
assert_eq! (
5058
fs:: read( root.join ( " b.txt" ) ) .unwrap ( ) ,
5059
b " b\n bb\n " ,
5060
" disk untouched"
5061
) ;
957fee30 Andy McCall 1 month ago ↶
5062
5063
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5064
let b_hunk = hunks
5065
.iter ( )
5066
.find ( | h | h.path = = " b.txt" & & h.text.contains ( " +bb" ) )
5067
.unwrap ( )
5068
.index;
5069
let h2 = repo
5070
.commit_selection ( " b only" , & [ ] , & Selection:: Hunks( vec! [ b_hunk] ) )
5071
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5072
let files = repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
5073
assert! (
5074
matches! ( & files[ " b.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " b\n bb\n " )
5075
) ;
957fee30 Andy McCall 1 month ago ↶
5076
assert! ( ! files.contains_key ( " c.txt" ) ) ;
5077
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5078
assert_eq! (
5079
snap.statuses
5080
.iter ( )
5081
.map ( | s | s.path.as_str ( ) )
5082
.collect:: < Vec< _ > > ( ) ,
5083
vec! [ " c.txt" ]
5084
) ;
957fee30 Andy McCall 1 month ago ↶
5085
5086
5087
write ( root, " d/e.txt" , b " e\n " ) ;
5088
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5089
let e_insert = hunks
5090
.iter ( )
5091
.find ( | h | h.path = = " d/e.txt" & & h.text.contains ( " +e" ) )
5092
.unwrap ( )
5093
.index;
5094
let h3 = repo
5095
.commit_selection ( " e only" , & [ ] , & Selection:: Hunks( vec! [ e_insert] ) )
5096
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5097
let c3 = repo.store ( ) .get_change ( & h3) .unwrap ( ) ;
5098
assert_eq! ( c3.tree_ops.len ( ) , 2 , " {:?}" , c3.tree_ops) ;
5099
let files = repo.load_state ( & [ h3] ) .unwrap ( ) .materialize ( ) .files;
5100
assert! ( files.contains_key ( " d/e.txt" ) & & ! files.contains_key ( " c.txt" ) ) ;
d8c086ca Andy McCall 1 month ago ↶
5101
5102
write ( root, " f/g.txt" , b " g\n " ) ;
5103
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5104
let g_add = hunks
5105
.iter ( )
5106
.find ( | h | h.path = = " f/g.txt" & & h.text.starts_with ( " A " ) )
5107
.unwrap ( )
5108
.index;
5109
let h4 = repo
5110
.commit_selection ( " g by addition" , & [ ] , & Selection:: Hunks( vec! [ g_add] ) )
5111
.unwrap ( ) ;
d8c086ca Andy McCall 1 month ago ↶
5112
let files = repo.load_state ( & [ h4] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
5113
assert! (
5114
matches! ( & files[ " f/g.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " g\n " )
5115
) ;
5116
assert! ( ! repo
5117
.snapshot ( " status" )
5118
.unwrap ( )
5119
.statuses
5120
.iter ( )
5121
.any ( | s | s.path = = " f/g.txt" ) ) ;
957fee30 Andy McCall 1 month ago ↶
5122
5123
repo.commit ( " the rest" , & [ ] ) .unwrap ( ) ;
5124
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
75167672 Andy McCall 1 month ago
5125
let files = repo
5126
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
5127
.unwrap ( )
5128
.materialize ( )
5129
.files;
957fee30 Andy McCall 1 month ago ↶
5130
assert! ( files.contains_key ( " c.txt" ) ) ;
5131
}
5132
5133
# [ test ]
5134
fn blame_attributes_lines_to_introducing_changes_across_renames ( ) {
5135
hermetic ( ) ;
5136
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5137
let root = dir.path ( ) ;
5138
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
5139
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
5140
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5141
write ( root, " a.txt" , b " one\n " ) ;
5142
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
5143
write ( root, " a.txt" , b " one\n two\n " ) ;
5144
let h2 = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
5145
let b = repo.blame ( " a.txt" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5146
assert_eq! (
5147
b.iter ( )
5148
.map ( | l | ( l.line, l.hash, l.text.as_slice ( ) ) )
5149
.collect:: < Vec< _ > > ( ) ,
5150
vec! [ ( 1 , h1, b " one\n " .as_slice ( ) ) , ( 2 , h2, b " two\n " .as_slice ( ) ) ]
5151
) ;
957fee30 Andy McCall 1 month ago ↶
5152
assert! ( b.iter ( ) .all ( | l | l.author = = " Ada" ) ) ;
75167672 Andy McCall 1 month ago
5153
assert_eq! (
5154
b[ 0 ] .change_id,
5155
repo.store ( ) .get_change ( & h1) .unwrap ( ) .change_id
5156
) ;
957fee30 Andy McCall 1 month ago ↶
5157
fs:: rename( root.join ( " a.txt" ) , root.join ( " b.txt" ) ) .unwrap ( ) ;
5158
repo.commit ( " rename" , & [ ] ) .unwrap ( ) ;
5159
let b = repo.blame ( " b.txt" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5160
assert_eq! (
5161
b.iter ( ) .map ( | l | l.hash ) .collect:: < Vec< _ > > ( ) ,
5162
vec! [ h1, h2] ,
5163
" the node survives the rename"
5164
) ;
957fee30 Andy McCall 1 month ago ↶
5165
assert! ( repo.blame ( " a.txt" ) .is_err ( ) ) ;
5166
}
5167
5168
# [ test ]
5169
fn bisect_finds_the_first_bad_change ( ) {
5170
hermetic ( ) ;
5171
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5172
let root = dir.path ( ) ;
5173
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5174
let mut content = String :: new( ) ;
5175
let mut hashes = Vec :: new( ) ;
5176
for i in 1 .. = 8 {
5177
content.push_str ( & format! ( " line {i} {} \n " , if i = = 5 { " bug" } else { " " } ) ) ;
5178
write ( root, " f.txt" , content.as_bytes ( ) ) ;
5179
hashes.push ( repo.commit ( & format! ( " step {i} " ) , & [ ] ) .unwrap ( ) ) ;
5180
}
5181
5182
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ hashes[ 7 ] ] ) ;
5183
repo.bisect_start ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5184
assert! ( matches! (
5185
repo.bisect_mark ( Some ( true ) , & [ hashes[ 0 ] ] ) .unwrap ( ) ,
5186
BisectStep:: NeedMarks { good: 1 , bad: 0 }
5187
) ) ;
957fee30 Andy McCall 1 month ago ↶
5188
let step = repo.bisect_mark ( Some ( false ) , & [ hashes[ 7 ] ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5189
assert! (
5190
matches! ( step, BisectStep:: Testing { remaining: 7 , .. } ) ,
5191
" {step:?}"
5192
) ;
957fee30 Andy McCall 1 month ago ↶
5193
75167672 Andy McCall 1 month ago
5194
let step = repo
5195
.bisect_run ( & [ " sh" .into ( ) , " -c" .into ( ) , " ! grep -q bug f.txt" .into ( ) ] )
5196
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5197
assert_eq! ( step, BisectStep:: Found { culprit: hashes[ 4 ] } ) ;
75167672 Andy McCall 1 month ago
5198
assert_eq! (
5199
repo.bisect_status ( ) .unwrap ( ) ,
5200
Some ( BisectStep:: Found { culprit: hashes[ 4 ] } )
5201
) ;
957fee30 Andy McCall 1 month ago ↶
5202
repo.bisect_reset ( ) .unwrap ( ) ;
5203
assert_eq! ( fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) , content) ;
5204
assert! ( repo.bisect_status ( ) .unwrap ( ) .is_none ( ) ) ;
5205
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
5206
5207
repo.bisect_start ( ) .unwrap ( ) ;
5208
repo.bisect_mark ( Some ( true ) , & [ hashes[ 0 ] ] ) .unwrap ( ) ;
5209
let mut step = repo.bisect_mark ( Some ( false ) , & [ hashes[ 7 ] ] ) .unwrap ( ) ;
5210
let mut skipped_once = false ;
5211
let mut guard = 0 ;
5212
while let BisectStep:: Testing { .. } = step {
5213
guard + = 1 ;
5214
assert! ( guard < 20 ) ;
5215
let text = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
5216
if ! skipped_once & & text.contains ( " line 6" ) {
5217
5218
skipped_once = true ;
5219
step = repo.bisect_mark ( None , & [ ] ) .unwrap ( ) ;
5220
continue ;
5221
}
5222
step = repo.bisect_mark ( Some ( ! text.contains ( " bug" ) ) , & [ ] ) .unwrap ( ) ;
5223
}
5224
assert! ( skipped_once) ;
5225
assert_eq! ( step, BisectStep:: Found { culprit: hashes[ 4 ] } ) ;
5226
repo.bisect_reset ( ) .unwrap ( ) ;
5227
5228
repo.bisect_start ( ) .unwrap ( ) ;
5229
repo.bisect_mark ( Some ( true ) , & [ hashes[ 0 ] ] ) .unwrap ( ) ;
5230
let mut step = repo.bisect_mark ( Some ( false ) , & [ hashes[ 7 ] ] ) .unwrap ( ) ;
5231
let mut guard = 0 ;
5232
while let BisectStep:: Testing { heads, .. } = & step {
5233
guard + = 1 ;
5234
assert! ( guard < 20 ) ;
5235
let text = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5236
let is_culprit_state =
5237
heads.contains ( & hashes[ 4 ] ) & & ! heads.iter ( ) .any ( | h | hashes[ 5 .. ] .contains ( h) ) ;
5238
step = if is_culprit_state {
5239
repo.bisect_mark ( None , & [ ] ) .unwrap ( )
5240
} else {
5241
repo.bisect_mark ( Some ( ! text.contains ( " bug" ) ) , & [ ] ) .unwrap ( )
5242
} ;
957fee30 Andy McCall 1 month ago ↶
5243
}
5244
match step {
75167672 Andy McCall 1 month ago
5245
BisectStep:: Inconclusive { candidates } => assert! (
5246
candidates.contains ( & hashes[ 4 ] ) & & candidates.len ( ) < = 2 ,
5247
" {candidates:?}"
5248
) ,
957fee30 Andy McCall 1 month ago ↶
5249
other => panic! ( " {other:?}" ) ,
5250
}
5251
repo.bisect_reset ( ) .unwrap ( ) ;
5252
}
5253
728da0f9 Andy McCall 1 month ago ↶
5254
# [ test ]
5255
fn squash_folds_a_change_into_its_dependency ( ) {
5256
hermetic ( ) ;
5257
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5258
let root = dir.path ( ) ;
5259
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5260
write ( root, " a.txt" , b " 1\n " ) ;
5261
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
5262
write ( root, " a.txt" , b " 1\n 2\n " ) ;
5263
let c = repo.commit ( " C" , & [ ] ) .unwrap ( ) ;
5264
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
5265
let e = repo.commit ( " E" , & [ ] ) .unwrap ( ) ;
5266
write ( root, " b.txt" , b " b\n " ) ;
5267
let b = repo.commit ( " B" , & [ ] ) .unwrap ( ) ;
5268
let id_a = repo.store ( ) .get_change ( & a) .unwrap ( ) .change_id;
5269
let id_e = repo.store ( ) .get_change ( & e) .unwrap ( ) .change_id;
5270
5271
let err = repo.squash ( & e, & a) .unwrap_err ( ) .to_string ( ) ;
5272
assert! ( err.contains ( & c.short ( 12 ) ) , " {err}" ) ;
5273
5274
let a2 = repo.squash ( & c, & a) .unwrap ( ) ;
5275
let a2c = repo.store ( ) .get_change ( & a2) .unwrap ( ) ;
5276
assert_eq! ( a2c.change_id, id_a) ;
5277
assert! ( a2c.deps.is_empty ( ) ) ;
5278
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5279
let st = repo.load_state ( & heads) .unwrap ( ) ;
5280
assert! ( ! st.contains ( & c) & & ! st.contains ( & a) & & ! st.contains ( & e) ) ;
75167672 Andy McCall 1 month ago
5281
let e2 = st
5282
.changes ( )
5283
.find ( | ( _, m ) | m.change_id = = id_e )
5284
.map ( | ( h , _) | * h)
5285
.unwrap ( ) ;
5286
assert_eq! (
5287
repo.store ( ) .get_change ( & e2) .unwrap ( ) .deps,
5288
vec! [ a2] ,
5289
" E now depends on A' alone"
5290
) ;
728da0f9 Andy McCall 1 month ago ↶
5291
let files = st.materialize ( ) .files;
75167672 Andy McCall 1 month ago
5292
assert! (
5293
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 1\n 2\n 3\n " )
5294
) ;
728da0f9 Andy McCall 1 month ago ↶
5295
assert! ( files.contains_key ( " b.txt" ) ) ;
5296
assert! ( st.contains ( & b) ) ;
5297
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
5298
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 , " working copy, B, E', A'" ) ;
5299
5300
repo.undo ( ) .unwrap ( ) ;
5301
repo.undo ( ) .unwrap ( ) ;
5302
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
5303
assert! ( st.contains ( & c) & & st.contains ( & a) & & st.contains ( & e) ) ;
5304
}
5305
5306
# [ test ]
5307
fn rebase_re_expresses_a_change_against_another_base ( ) {
5308
hermetic ( ) ;
5309
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5310
let root = dir.path ( ) ;
5311
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5312
write ( root, " a.txt" , b " 1\n 2\n " ) ;
5313
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
5314
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
5315
let c = repo.commit ( " C: append 3" , & [ ] ) .unwrap ( ) ;
5316
write ( root, " a.txt" , b " 1\n 2\n 3\n 4\n " ) ;
5317
let e = repo.commit ( " E: append 4" , & [ ] ) .unwrap ( ) ;
5318
5319
repo.checkout ( & [ a] , true , " checkout" ) .unwrap ( ) ;
5320
write ( root, " a.txt" , b " 0\n 1\n 2\n " ) ;
5321
let d = repo.commit ( " D: prepend 0" , & [ ] ) .unwrap ( ) ;
5322
let id_c = repo.store ( ) .get_change ( & c) .unwrap ( ) .change_id;
5323
let id_e = repo.store ( ) .get_change ( & e) .unwrap ( ) .change_id;
5324
5325
let c2 = repo.rebase ( & c, & [ d] ) .unwrap ( ) ;
5326
let c2c = repo.store ( ) .get_change ( & c2) .unwrap ( ) ;
5327
assert_eq! ( c2c.change_id, id_c) ;
5328
assert! ( c2c.deps.contains ( & d) , " {:?}" , c2c.deps) ;
5329
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5330
let st = repo.load_state ( & heads) .unwrap ( ) ;
5331
assert! ( ! st.contains ( & c) & & st.contains ( & d) ) ;
75167672 Andy McCall 1 month ago
5332
let e2 = st
5333
.changes ( )
5334
.find ( | ( _, m ) | m.change_id = = id_e )
5335
.map ( | ( h , _) | * h)
5336
.unwrap ( ) ;
728da0f9 Andy McCall 1 month ago ↶
5337
let e2deps = repo.store ( ) .get_change ( & e2) .unwrap ( ) .deps;
75167672 Andy McCall 1 month ago
5338
assert! (
5339
e2deps.contains ( & c2) & & ! e2deps.contains ( & c) & & e2deps.contains ( & a) ,
5340
" E follows C by content and still depends on A, which introduced the file: {e2deps:?}"
5341
) ;
728da0f9 Andy McCall 1 month ago ↶
5342
let files = st.materialize ( ) .files;
75167672 Andy McCall 1 month ago
5343
assert! (
5344
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 0\n 1\n 2\n 3\n 4\n " ) ,
5345
" {:?}" ,
5346
files[ " a.txt" ] .content
5347
) ;
728da0f9 Andy McCall 1 month ago ↶
5348
assert! ( ! files[ " a.txt" ] .is_conflicted ( ) ) ;
5349
5350
assert! ( repo.rebase ( & c2, & [ c2] ) .is_err ( ) ) ;
5351
let err = repo.rebase ( & e2, & [ d] ) .unwrap_err ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
5352
assert! (
5353
err.contains ( " lacks dependencies" ) ,
5354
" E needs C, which D's state does not have: {err}"
5355
) ;
728da0f9 Andy McCall 1 month ago ↶
5356
}
5357
c792033a Andy McCall 1 month ago ↶
5358
# [ test ]
5359
fn unsigned_tags_keep_their_message ( ) {
5360
hermetic ( ) ;
5361
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5362
let root = dir.path ( ) ;
5363
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5364
write ( root, " a.txt" , b " a\n " ) ;
5365
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
5366
assert! ( repo.secret_key ( ) .unwrap ( ) .is_none ( ) ) ;
75167672 Andy McCall 1 month ago
5367
repo.tag_create ( " v1" , & repo.bookmark_heads ( ) .unwrap ( ) , " first release" )
5368
.unwrap ( ) ;
c792033a Andy McCall 1 month ago ↶
5369
let tags = repo.tags ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5370
assert_eq! (
5371
(
5372
tags[ 0 ] .name.as_str ( ) ,
5373
tags[ 0 ] .message.as_str ( ) ,
5374
tags[ 0 ] .signer.as_deref ( )
5375
) ,
5376
( " v1" , " first release" , None )
5377
) ;
5378
assert! (
5379
repo.tag_refs ( ) .unwrap ( ) [ 0 ] .attestation.is_none ( ) ,
5380
" an unsigned attestation is not advertised"
5381
) ;
c792033a Andy McCall 1 month ago ↶
5382
}
387335ff Andy McCall 1 month ago ↶
5383
5384
# [ test ]
5385
fn fsck_passes_a_sound_repository_and_names_each_fault ( ) {
64818938 Andy McCall 1 month ago ↶
5386
5387
5388
if std:: env:: var_os( " OXY_STORE_BACKEND" ) .is_some ( ) {
5389
return ;
5390
}
387335ff Andy McCall 1 month ago ↶
5391
hermetic ( ) ;
5392
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5393
let root = dir.path ( ) ;
5394
let origin_path = root.join ( " origin" ) ;
5395
let origin = Repo:: init( & origin_path) .unwrap ( ) ;
5396
let mut a = Repo:: clone( & origin_path.to_string_lossy ( ) , & root.join ( " a" ) )
5397
.unwrap ( )
5398
.with_home ( root.join ( " home-a" ) ) ;
5399
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
5400
.unwrap ( ) ;
5401
write ( & root.join ( " a" ) , " f.txt" , b " one\n " ) ;
5402
write ( & root.join ( " a" ) , " bin.dat" , b " \0 \x01 \x02 binary" ) ;
5403
let h1 = a.commit ( " first" , & [ ] ) .unwrap ( ) ;
5404
write ( & root.join ( " a" ) , " f.txt" , b " one\n two\n " ) ;
5405
let h2 = a.commit ( " second" , & [ ] ) .unwrap ( ) ;
5406
a.tag_create ( " v1" , & [ h2] , " first release" ) .unwrap ( ) ;
5407
a.checkpoint ( ) .unwrap ( ) ;
5408
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
5409
origin.refresh ( ) .unwrap ( ) ;
387335ff Andy McCall 1 month ago ↶
5410
5411
5412
for repo in [ & a, & origin] {
5413
let rep = repo.fsck ( ) .unwrap ( ) ;
5414
assert_eq! ( rep.problems, vec! [ ] , " {rep:?}" ) ;
5415
5416
assert! ( rep.changes > = 2 & & rep.signed > = 2 , " {rep:?}" ) ;
5417
assert! ( rep.ops > 1 & & rep.objects > 5 ) ;
5418
}
5419
assert! ( origin.fsck ( ) .unwrap ( ) .attestations > = 1 ) ;
5420
5421
5422
let c1 = a.store ( ) .path_for ( & h1) ;
5423
let pristine = fs:: read( & c1) .unwrap ( ) ;
5424
let mut bytes = pristine.clone ( ) ;
5425
* bytes.last_mut ( ) .unwrap ( ) ^ = 1 ;
5426
fs:: write( & c1, & bytes) .unwrap ( ) ;
5427
let rep = a.fsck ( ) .unwrap ( ) ;
5428
assert! ( rep.errors ( ) > = 1 ) ;
5429
assert! (
5430
rep.problems
5431
.iter ( )
5432
.any ( | p | p.object = = Some ( h1) & & p.what.contains ( " unreadable" ) ) ,
5433
" {rep:?}"
5434
) ;
5435
fs:: write( & c1, & pristine) .unwrap ( ) ;
5436
assert_eq! ( a.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
5437
5438
5439
fs:: remove_file( & c1) .unwrap ( ) ;
5440
let rep = a.fsck ( ) .unwrap ( ) ;
5441
assert! (
5442
rep.problems.iter ( ) .any ( | p | p.severity = = Severity:: Error
5443
& & p.object = = Some ( h1)
5444
& & p.what.contains ( " change missing, needed by change" ) ) ,
5445
" {rep:?}"
5446
) ;
5447
fs:: write( & c1, & pristine) .unwrap ( ) ;
5448
5449
5450
let c2 = a.store ( ) .get_change ( & h1) .unwrap ( ) ;
5451
let blob = c2
5452
.edits
5453
.iter ( )
5454
.find_map ( | e | match e {
5455
Edit:: Replace { new: Some ( b) , .. } => Some ( * b) ,
5456
_ => None ,
5457
} )
5458
.expect ( " the binary file is a blob" ) ;
5459
let bp = a.store ( ) .path_for ( & blob) ;
5460
let blob_bytes = fs:: read( & bp) .unwrap ( ) ;
5461
fs:: remove_file( & bp) .unwrap ( ) ;
5462
let rep = a.fsck ( ) .unwrap ( ) ;
5463
assert! (
5464
rep.problems
5465
.iter ( )
5466
.any ( | p | p.object = = Some ( blob) & & p.what.contains ( " blob missing" ) ) ,
5467
" {rep:?}"
5468
) ;
5469
fs:: write( & bp, & blob_bytes) .unwrap ( ) ;
5470
5471
5472
let head = root.join ( " a/.oxy/ops/head" ) ;
5473
let saved = fs:: read( & head) .unwrap ( ) ;
5474
fs:: write( & head, b " not a hash" ) .unwrap ( ) ;
5475
let rep = a.fsck ( ) .unwrap ( ) ;
5476
assert! (
5477
rep.problems.iter ( ) .any ( | p | p.what.contains ( " ops/head" ) ) ,
5478
" {rep:?}"
5479
) ;
5480
fs:: write( & head, saved) .unwrap ( ) ;
5481
assert_eq! ( a.fsck ( ) .unwrap ( ) .problems, vec! [ ] ) ;
5482
}
4dcb8c09 Andy McCall 1 month ago ↶
5483
5484
5485
fn copy_tree ( from : & Path, to : & Path) {
5486
fs:: create_dir_all( to) .unwrap ( ) ;
5487
for e in fs:: read_dir( from) .unwrap ( ) {
5488
let e = e.unwrap ( ) ;
5489
let target = to.join ( e.file_name ( ) ) ;
5490
if e.file_type ( ) .unwrap ( ) .is_dir ( ) {
5491
copy_tree ( & e.path ( ) , & target) ;
5492
} else {
5493
fs:: copy( e.path ( ) , & target) .unwrap ( ) ;
5494
}
5495
}
5496
}
5497
5498
5499
5500
5501
# [ test ]
5502
fn crash_at_every_step_of_commit_and_push_leaves_sound_repositories ( ) {
64818938 Andy McCall 1 month ago ↶
5503
5504
5505
if std:: env:: var_os( " OXY_STORE_BACKEND" ) .is_some ( ) {
5506
return ;
5507
}
4dcb8c09 Andy McCall 1 month ago ↶
5508
use crate :: durable:: failpoint;
5509
hermetic ( ) ;
5510
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5511
let pristine = dir.path ( ) .join ( " pristine" ) ;
5512
let origin_path = pristine.join ( " origin" ) ;
5513
Repo:: init( & origin_path) .unwrap ( ) ;
5514
let mut a = Repo:: clone( & origin_path.to_string_lossy ( ) , & pristine.join ( " a" ) )
5515
.unwrap ( )
5516
.with_home ( dir.path ( ) .join ( " home" ) ) ;
5517
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
5518
.unwrap ( ) ;
5519
write ( & pristine.join ( " a" ) , " f.txt" , b " one\n " ) ;
5520
a.commit ( " first" , & [ ] ) .unwrap ( ) ;
5521
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
5522
drop ( a) ;
5523
5524
type Op = fn ( & Repo) -> Result< ( ) > ;
5525
let edit_and_commit: Op = | r | {
5526
write ( r.root ( ) , " f.txt" , b " one\n two\n " ) ;
5527
write ( r.root ( ) , " big.bin" , & vec! [ 7 u8 ; 300_000 ] ) ;
5528
r.commit ( " second" , & [ ] ) .map ( | _| ( ) )
5529
} ;
5530
let push: Op = | r | {
5531
let res = r.push ( " origin" , false ) ? ;
5532
if res.iter ( ) .all ( | x | x.ok ) {
5533
Ok ( ( ) )
5534
} else {
5535
Err ( RepoError:: Msg( format! ( " {res:?} " ) ) )
5536
}
5537
} ;
5538
5539
for ( name, prepare, op) in [
5540
( " commit" , None , edit_and_commit) ,
5541
( " push" , Some ( edit_and_commit) , push) ,
5542
] {
5543
let mut steps = 0 ;
5544
for n in 1 .. {
5545
let run = dir.path ( ) .join ( format! ( " {name} -{n} " ) ) ;
5546
copy_tree ( & pristine, & run) ;
5547
5548
let open = | p : & Path| {
5549
let mut r = Repo:: open( p) .unwrap ( ) .with_home ( dir.path ( ) .join ( " home" ) ) ;
5550
r.config
5551
.set ( " remote.origin.url" , & run.join ( " origin" ) .to_string_lossy ( ) )
5552
.unwrap ( ) ;
5553
r.save_config ( ) .unwrap ( ) ;
5554
r
5555
} ;
5556
let a = open ( & run.join ( " a" ) ) ;
5557
if let Some ( prep) = prepare {
5558
prep ( & a) .unwrap ( ) ;
5559
}
5560
failpoint:: crash_after( n) ;
5561
let outcome = op ( & a) ;
5562
let crashed = failpoint:: disarm( ) ;
5563
drop ( a) ;
5564
if ! crashed {
5565
outcome.unwrap_or_else ( | e | panic! ( " {name} failed without a crash: {e}" ) ) ;
5566
steps = n - 1 ;
5567
break ;
5568
}
5569
assert! (
5570
outcome.is_err ( ) ,
5571
" {name}: a crash at step {n} was swallowed"
5572
) ;
5573
for repo in [ " a" , " origin" ] {
5574
let r = Repo:: open( & run.join ( repo) ) .unwrap_or_else ( | e | {
5575
panic! ( " {name} crash at step {n}: {repo} no longer opens: {e}" )
5576
} ) ;
5577
let rep = r.fsck ( ) .unwrap ( ) ;
5578
assert_eq! (
5579
rep.errors ( ) ,
5580
0 ,
5581
" {name} crash at step {n}: {repo} fails fsck: {:?}" ,
5582
rep.problems
5583
) ;
5584
}
5585
5586
let a = open ( & run.join ( " a" ) ) ;
5587
5588
let landed = a
5589
.bookmark_heads ( )
5590
.unwrap ( )
5591
.iter ( )
5592
.any ( | h | a.store ( ) .get_change ( h) .unwrap ( ) .message = = " second" ) ;
5593
let again = match ( name, landed) {
5594
( " commit" , true ) => Ok ( ( ) ) ,
5595
( " commit" , false ) => a.commit ( " second" , & [ ] ) .map ( | _| ( ) ) ,
5596
_ => op ( & a) ,
5597
} ;
5598
again.unwrap_or_else ( | e | panic! ( " {name} crash at step {n}: retry failed: {e}" ) ) ;
5599
assert_eq! ( a.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
5600
fs:: remove_dir_all( & run) .unwrap ( ) ;
5601
}
5602
assert! ( steps > = 3 , " {name} has only {steps} durable steps?" ) ;
5603
}
5604
}
19555025 Andy McCall 1 month ago ↶
5605
5606
5607
5608
5609
# [ test ]
5610
fn concurrent_commits_in_two_workspaces_lose_nothing ( ) {
5611
hermetic ( ) ;
5612
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5613
let root = dir.path ( ) ;
5614
let origin = root.join ( " origin" ) ;
5615
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
5616
let main = root.join ( " main" ) ;
5617
let mut repo = Repo:: init( & main) .unwrap ( ) ;
5618
repo.config
5619
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
5620
.unwrap ( ) ;
5621
repo.save_config ( ) .unwrap ( ) ;
5622
write ( & main, " seed.txt" , b " seed\n " ) ;
5623
repo.commit ( " seed" , & [ ] ) .unwrap ( ) ;
5624
let second = root.join ( " second" ) ;
5625
repo.workspace_add ( & second, None ) .unwrap ( ) ;
5626
drop ( repo) ;
5627
5628
const EACH : usize = 15 ;
5629
std:: thread:: scope( | s | {
5630
for ( tag, dir, push) in [ ( " a" , main.clone ( ) , true ) , ( " b" , second.clone ( ) , false ) ] {
5631
s.spawn ( move | | {
5632
let r = Repo:: open( & dir) .unwrap ( ) ;
5633
for i in 0 .. EACH {
5634
write (
5635
& dir,
5636
& format! ( " {tag} -{i} .txt" ) ,
5637
format! ( " {tag} {i} \n " ) .as_bytes ( ) ,
5638
) ;
5639
r.commit ( & format! ( " {tag} {i} " ) , & [ ] ) .unwrap ( ) ;
5640
if push & & i % 5 = = 4 {
5641
5642
let _ = r.push ( " origin" , false ) ;
5643
}
5644
}
5645
} ) ;
5646
}
5647
} ) ;
5648
5649
let repo = Repo:: open( & main) .unwrap ( ) ;
5650
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5651
let state = repo.load_state ( & heads) .unwrap ( ) ;
5652
let messages: BTreeSet< String> = state.changes ( ) .map ( | ( _, m ) | m.message.clone ( ) ) .collect ( ) ;
5653
for tag in [ " a" , " b" ] {
5654
for i in 0 .. EACH {
5655
assert! (
5656
messages.contains ( & format! ( " {tag} {i} " ) ) ,
5657
" lost commit {tag} {i}; have {messages:?}"
5658
) ;
5659
}
5660
}
5661
let rep = repo.fsck ( ) .unwrap ( ) ;
5662
assert_eq! ( rep.errors ( ) , 0 , " {:?}" , rep.problems) ;
5663
assert! ( repo.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
5664
let hosted = Repo:: open( & origin) .unwrap ( ) ;
5665
let hosted_heads = hosted.bookmark_heads ( ) .unwrap ( ) ;
5666
assert_eq! (
5667
hosted.load_state ( & hosted_heads) .unwrap ( ) .len ( ) ,
5668
state.len ( ) ,
5669
" the remote has every commit"
5670
) ;
5671
}
5672
5673
5674
5675
5676
# [ test ]
5677
fn operations_from_the_same_parent_are_merged ( ) {
5678
hermetic ( ) ;
5679
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5680
let root = dir.path ( ) ;
5681
let repo = Repo:: init( root) .unwrap ( ) ;
5682
write ( root, " a.txt" , b " a\n " ) ;
5683
let a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
5684
write ( root, " b.txt" , b " b\n " ) ;
5685
let b = repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
5686
let ( x, op) = repo.head_op ( ) .unwrap ( ) ;
5687
let state = | heads : Vec< ChangeHash> | repo.store ( ) .put_state ( & StateObject:: new( heads) ) .unwrap ( ) ;
5688
5689
5690
let mut one = op.view.clone ( ) ;
5691
one.bookmarks.insert ( " main" .into ( ) , state ( vec! [ a] ) ) ;
5692
one.bookmarks.insert ( " topic" .into ( ) , state ( vec! [ a] ) ) ;
5693
let mut two = op.view.clone ( ) ;
5694
two.bookmarks.insert ( " main" .into ( ) , state ( vec! [ b] ) ) ;
5695
repo.record_op ( & [ x] , " one" , one) .unwrap ( ) ;
5696
let merged = repo.record_op ( & [ x] , " two" , two) .unwrap ( ) ;
5697
5698
let ( head, op) = repo.head_op ( ) .unwrap ( ) ;
5699
assert_eq! ( head, merged) ;
5700
assert_eq! ( op.parents.len ( ) , 2 , " a merge of the two operations" ) ;
5701
assert! ( op.command.contains ( " merged with a concurrent operation" ) ) ;
5702
let mut main = repo
5703
.store ( )
5704
.get_state ( & op.view.bookmarks[ " main" ] )
5705
.unwrap ( )
5706
.heads;
5707
main.sort ( ) ;
5708
let mut want = vec! [ a, b] ;
5709
want.sort ( ) ;
5710
assert_eq! ( main, want, " both sides' heads are kept" ) ;
5711
assert! (
5712
op.view.bookmarks.contains_key ( " topic" ) ,
5713
" a one-sided change survives"
5714
) ;
5715
assert_eq! ( repo.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
5716
}
3e5772cc Andy McCall 1 month ago ↶
5717
5718
# [ test ]
5719
fn failed_requests_come_with_a_next_step ( ) {
5720
use crate :: protocol:: hint_for;
5721
assert! ( hint_for ( " https://h/x/oxy/v1/push: HTTP 401: bad token" )
5722
.unwrap ( )
5723
.contains ( " token" ) ) ;
5724
assert! ( hint_for ( " …: HTTP 403: forbidden" )
5725
.unwrap ( )
5726
.contains ( " repo:write" ) ) ;
5727
assert! ( hint_for ( " …: HTTP 404: no such repository" )
5728
.unwrap ( )
5729
.contains ( " owner and repository name" ) ) ;
5730
assert! ( hint_for ( " invalid peer certificate: UnknownIssuer" )
5731
.unwrap ( )
5732
.contains ( " oxyhub-ca.pem" ) ) ;
5733
assert! ( hint_for ( " Connect error: Connection refused (os error 111)" )
5734
.unwrap ( )
5735
.contains ( " running" ) ) ;
5736
assert! ( hint_for ( " Dns Failed: failed to lookup address information" )
5737
.unwrap ( )
5738
.contains ( " resolve" ) ) ;
5739
assert_eq! ( hint_for ( " something else entirely" ) , None ) ;
5740
}
5741
5742
# [ test ]
5743
fn doctor_names_problems_and_what_to_do ( ) {
5744
hermetic ( ) ;
5745
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5746
let root = dir.path ( ) ;
5747
let origin = root.join ( " origin" ) ;
5748
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
5749
let mut repo = Repo:: init( & root.join ( " r" ) )
5750
.unwrap ( )
5751
.with_home ( root.join ( " home" ) ) ;
5752
let find = | r : & DoctorReport, area : & str , h : Health| {
5753
r.checks
5754
.iter ( )
5755
.find ( | c | c.area = = area & & c.health = = h )
5756
.cloned ( )
5757
} ;
5758
5759
let r = repo.doctor ( true ) ;
5760
assert! ( find ( & r, " identity" , Health:: Warn)
5761
.unwrap ( )
5762
.hint
5763
.unwrap ( )
5764
.contains ( " oxy identity create" ) ) ;
5765
assert! ( find ( & r, " repository" , Health:: Ok) .is_some ( ) ) ;
5766
5767
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
5768
.unwrap ( ) ;
5769
repo.config.set ( " author.name" , " Ada" ) .unwrap ( ) ;
5770
repo.config.set ( " author.email" , " [email protected] " ) .unwrap ( ) ;
5771
repo.config
5772
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
5773
.unwrap ( ) ;
5774
repo.config
5775
.set ( " remote.down.url" , " http://127.0.0.1:9/a/b" )
5776
.unwrap ( ) ;
5777
repo.config.set ( " remote.down.token" , " t" ) .unwrap ( ) ;
5778
repo.config
5779
.set ( " remote.badca.url" , " https://example.invalid/a/b" )
5780
.unwrap ( ) ;
5781
repo.config
5782
.set (
5783
" remote.badca.ca" ,
5784
& root.join ( " missing.pem" ) .to_string_lossy ( ) ,
5785
)
5786
.unwrap ( ) ;
5787
repo.save_config ( ) .unwrap ( ) ;
5788
5789
let r = repo.doctor ( true ) ;
5790
assert! ( find ( & r, " identity" , Health:: Ok) .is_some ( ) , " {r:?}" ) ;
5791
assert! ( find ( & r, " author" , Health:: Ok) .is_some ( ) , " {r:?}" ) ;
5792
assert! ( find ( & r, " remote origin" , Health:: Ok)
5793
.unwrap ( )
5794
.message
5795
.contains ( " first push binds it" ) ) ;
5796
let down = find ( & r, " remote down" , Health:: Fail) .unwrap ( ) ;
5797
assert! (
5798
down.hint.as_deref ( ) .unwrap_or ( " " ) .contains ( " running" ) ,
5799
" {down:?}"
5800
) ;
5801
let badca = find ( & r, " remote badca" , Health:: Fail) .unwrap ( ) ;
5802
assert! (
5803
badca.hint.unwrap ( ) .contains ( " remote.badca.ca" ) ,
5804
" the remote's own name is in the hint"
5805
) ;
5806
assert_eq! ( r.worst ( ) , Health:: Fail) ;
5807
5808
5809
let r = repo.doctor ( false ) ;
5810
assert! ( find ( & r, " remote down" , Health:: Fail) .is_none ( ) ) ;
5811
assert! ( find ( & r, " remote down" , Health:: Ok)
5812
.unwrap ( )
5813
.message
5814
.contains ( " not contacted" ) ) ;
5815
}
62794ecc Andy McCall 1 month ago ↶
5816
5817
5818
5819
5820
# [ test ]
5821
fn deleting_a_line_after_an_insertion_below_it_is_not_a_conflict ( ) {
5822
hermetic ( ) ;
5823
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5824
let root = dir.path ( ) ;
5825
let repo = Repo:: init( root) .unwrap ( ) ;
5826
write ( root, " f" , b " a\n b\n c\n " ) ;
5827
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
5828
write ( root, " f" , b " a\n b\n x\n c\n " ) ;
5829
let insert = repo.commit ( " insert x after b" , & [ ] ) .unwrap ( ) ;
5830
write ( root, " f" , b " a\n x\n c\n " ) ;
5831
let delete = repo.commit ( " delete b" , & [ ] ) .unwrap ( ) ;
5832
assert! (
5833
repo.store ( )
5834
.get_change ( & delete)
5835
.unwrap ( )
5836
.deps
5837
.contains ( & insert) ,
5838
" the deletion depends on the insertion below the deleted line"
5839
) ;
5840
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5841
assert_eq! ( heads, vec! [ delete] ) ;
5842
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
5843
assert! ( ! snap.is_conflicted ( ) ) ;
5844
assert! (
5845
matches! ( & snap.files[ " f" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n x\n c\n " )
5846
) ;
5847
}
5848
5849
5850
5851
# [ test ]
5852
fn a_binary_file_reverted_to_an_earlier_blob_keeps_it ( ) {
5853
hermetic ( ) ;
5854
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5855
let root = dir.path ( ) ;
5856
let repo = Repo:: init( root) .unwrap ( ) ;
5857
write ( root, " bin" , b " \0 A" ) ;
5858
let v1 = repo.commit ( " v1" , & [ ] ) .unwrap ( ) ;
5859
write ( root, " bin" , b " \0 B" ) ;
5860
let v2 = repo.commit ( " v2" , & [ ] ) .unwrap ( ) ;
5861
assert! ( repo.store ( ) .get_change ( & v2) .unwrap ( ) .deps.contains ( & v1) ) ;
5862
write ( root, " bin" , b " \0 A" ) ;
5863
let v3 = repo.commit ( " back to A" , & [ ] ) .unwrap ( ) ;
5864
assert! ( repo.store ( ) .get_change ( & v3) .unwrap ( ) .deps.contains ( & v2) ) ;
5865
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ v3] ) ;
5866
let state = repo.load_state ( & [ v3] ) .unwrap ( ) ;
5867
let snap = state.materialize ( ) ;
5868
let FileContent:: Opaque { blobs } = & snap.files[ " bin" ] .content else {
5869
panic! ( " opaque" )
5870
} ;
5871
assert_eq! ( blobs.len ( ) , 1 , " one live blob, not none and not two" ) ;
5872
assert_eq! ( repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) , b " \0 A" ) ;
5873
}
5874
5875
5876
5877
5878
# [ test ]
5879
fn git_import_resolves_a_binary_merged_in_git_and_verifies ( ) {
5880
hermetic ( ) ;
5881
if std:: process:: Command:: new( " git" )
5882
.arg ( " --version" )
5883
.output ( )
5884
.is_err ( )
5885
{
5886
eprintln! ( " git not installed; skipping" ) ;
5887
return ;
5888
}
5889
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5890
let root = dir.path ( ) ;
5891
let g = root.join ( " src.git" ) ;
5892
fs:: create_dir_all( & g) .unwrap ( ) ;
5893
let git = | args : & [& str ]| -> String {
5894
let out = std:: process:: Command:: new( " git" )
5895
.arg ( " -C" )
5896
.arg ( & g)
5897
.args ( [
5898
" -c" ,
5899
" user.name=Grace" ,
5900
" -c" ,
5901
" [email protected] " ,
5902
" -c" ,
5903
" commit.gpgsign=false" ,
5904
] )
5905
.args ( args)
5906
.output ( )
5907
.unwrap ( ) ;
5908
assert! (
5909
out.status.success ( ) ,
5910
" git {args:?}: {}" ,
5911
String :: from_utf8_lossy( & out.stderr)
5912
) ;
5913
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
5914
} ;
5915
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
5916
write ( & g, " logo.bin" , b " \0 base" ) ;
5917
write ( & g, " notes.txt" , b " a\n b\n c\n " ) ;
5918
git ( & [ " add" , " ." ] ) ;
5919
git ( & [ " commit" , " -qm" , " base" ] ) ;
5920
git ( & [ " checkout" , " -qb" , " side" ] ) ;
5921
write ( & g, " logo.bin" , b " \0 side" ) ;
5922
write ( & g, " notes.txt" , b " a\n b\n x\n c\n " ) ;
5923
git ( & [ " commit" , " -qam" , " side" ] ) ;
5924
git ( & [ " checkout" , " -q" , " main" ] ) ;
5925
write ( & g, " logo.bin" , b " \0 main" ) ;
5926
git ( & [ " commit" , " -qam" , " main" ] ) ;
5927
5928
let _ = std:: process:: Command:: new( " git" )
5929
.arg ( " -C" )
5930
.arg ( & g)
5931
.args ( [
5932
" -c" ,
5933
" user.name=G" ,
5934
" -c" ,
5935
" user.email=g@e" ,
5936
" merge" ,
5937
" -q" ,
5938
" side" ,
5939
] )
5940
.output ( ) ;
5941
write ( & g, " logo.bin" , b " \0 main" ) ;
5942
git ( & [ " add" , " ." ] ) ;
5943
git ( & [ " commit" , " -qm" , " merge side" ] ) ;
5944
5945
write ( & g, " logo.bin" , b " \0 base" ) ;
5946
git ( & [ " commit" , " -qam" , " back to base" ] ) ;
5947
5948
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
5949
let rep = repo.import_git ( & g, " main" ) .unwrap ( ) ;
5950
assert_eq! ( rep.merges, 1 ) ;
5951
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
5952
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
5953
assert_eq! ( v.commits, 5 ) ;
5954
let snap = repo
5955
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
5956
.unwrap ( )
5957
.materialize ( ) ;
5958
assert! ( ! snap.is_conflicted ( ) ) ;
5959
let FileContent:: Opaque { blobs } = & snap.files[ " logo.bin" ] .content else {
5960
panic! ( " opaque" )
5961
} ;
5962
assert_eq! ( repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) , b " \0 base" ) ;
5963
}
c863c7a7 Andy McCall 1 month ago ↶
5964
5965
5966
5967
5968
# [ cfg ( unix ) ]
5969
# [ test ]
5970
fn windows_file_systems_keep_exec_bits_and_symlinks ( ) {
5971
use crate :: portable:: Platform;
5972
use std:: os:: unix:: fs:: PermissionsExt;
5973
hermetic ( ) ;
5974
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5975
let root = dir.path ( ) ;
5976
let repo = Repo:: init( root) .unwrap ( ) ;
5977
write ( root, " run.sh" , b " #!/bin/sh\n " ) ;
5978
fs:: set_permissions( root.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
5979
std:: os:: unix:: fs:: symlink( " run.sh" , root.join ( " link" ) ) .unwrap ( ) ;
5980
repo.commit ( " script and link" , & [ ] ) .unwrap ( ) ;
5981
drop ( repo) ;
5982
5983
5984
fs:: set_permissions( root.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
5985
fs:: remove_file( root.join ( " link" ) ) .unwrap ( ) ;
5986
write ( root, " link" , b " run.sh" ) ;
5987
let win = Repo:: open( root) .unwrap ( ) .with_platform ( Platform:: windows( ) ) ;
5988
let s = win.snapshot ( " oxy status" ) .unwrap ( ) ;
5989
assert! (
5990
s.statuses.is_empty ( ) ,
5991
" nothing changed on Windows: {:?}" ,
5992
statuses ( & s)
5993
) ;
5994
5995
write ( root, " run.sh" , b " #!/bin/sh\n echo hi\n " ) ;
5996
write ( root, " link" , b " other" ) ;
5997
let h = win.commit ( " edit on Windows" , & [ ] ) .unwrap ( ) ;
5998
let snap = win.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
5999
assert! ( snap.files[ " run.sh" ] .mode.exec, " the exec bit survives" ) ;
6000
assert_eq! ( snap.files[ " link" ] .kind, NodeKind:: Symlink) ;
6001
6002
let linux = Repo:: open( root) .unwrap ( ) ;
6003
assert! ( ! linux.snapshot ( " oxy status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6004
}
6005
6006
6007
6008
# [ test ]
6009
fn paths_other_platforms_cannot_hold_are_reported ( ) {
6010
use crate :: portable:: Platform;
6011
hermetic ( ) ;
6012
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6013
let root = dir.path ( ) ;
6014
let repo = Repo:: init( root) .unwrap ( ) ;
6015
write ( root, " README.md" , b " a\n " ) ;
6016
write ( root, " readme.md" , b " b\n " ) ;
6017
write ( root, " src/aux.c" , b " int x;\n " ) ;
6018
let h = repo.commit ( " unportable" , & [ ] ) .unwrap ( ) ;
6019
let r = repo.doctor ( false ) ;
6020
let port = r.checks.iter ( ) .find ( | c | c.area = = " portability" ) .unwrap ( ) ;
6021
assert_eq! ( port.health, Health:: Warn) ;
6022
assert! (
6023
port.message.contains ( " differ only in case" ) & & port.message.contains ( " aux.c" ) ,
6024
" {}" ,
6025
port.message
6026
) ;
6027
let mac = Repo:: open( root) .unwrap ( ) .with_platform ( Platform:: macos( ) ) ;
6028
let rep = mac.checkout ( & [ h] , true , " oxy checkout" ) .unwrap ( ) ;
6029
assert! (
6030
rep.notes
6031
.iter ( )
6032
.any ( | n | n.contains ( " README.md and readme.md" ) ) ,
6033
" {:?}" ,
6034
rep.notes
6035
) ;
6036
assert! (
6037
! rep.notes.iter ( ) .any ( | n | n.contains ( " aux.c" ) ) ,
6038
" macOS allows aux.c"
6039
) ;
6040
}
bc489b6c Andy McCall 1 month ago ↶
6041
6042
# [ test ]
6043
fn history_and_blame_of_a_loaded_state_match_the_full_walk ( ) {
6044
hermetic ( ) ;
6045
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6046
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
6047
write ( dir.path ( ) , " a.txt" , b " one\n " ) ;
6048
write ( dir.path ( ) , " other.txt" , b " x\n " ) ;
6049
write ( dir.path ( ) , " bin.dat" , & [ 0 u8 , 1 , 2 , 0 , 255 ] ) ;
6050
repo.snapshot ( " t" ) .unwrap ( ) ;
6051
let c1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
6052
write ( dir.path ( ) , " other.txt" , b " y\n " ) ;
6053
repo.snapshot ( " t" ) .unwrap ( ) ;
6054
repo.commit ( " other only" , & [ ] ) .unwrap ( ) ;
6055
write ( dir.path ( ) , " a.txt" , b " one\n two\n " ) ;
6056
repo.snapshot ( " t" ) .unwrap ( ) ;
6057
let c3 = repo.commit ( " second line" , & [ ] ) .unwrap ( ) ;
6058
write ( dir.path ( ) , " other.txt" , b " z\n " ) ;
6059
repo.snapshot ( " t" ) .unwrap ( ) ;
6060
repo.commit ( " other again" , & [ ] ) .unwrap ( ) ;
6061
6062
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6063
let state = repo.load_state ( & heads) .unwrap ( ) ;
6064
let full: Vec< ChangeHash> = repo
6065
.log_for_path_in ( " a.txt" , & heads)
6066
.unwrap ( )
6067
.iter ( )
6068
.map ( | e | e.hash )
6069
.collect ( ) ;
6070
assert_eq! (
6071
full,
6072
vec! [ c3, c1] ,
6073
" newest first, only changes touching the path"
6074
) ;
6075
let newest: Vec< ChangeHash> = repo
6076
.log_for_path_of ( & state, " a.txt" , Some ( 1 ) )
6077
.unwrap ( )
6078
.iter ( )
6079
.map ( | e | e.hash )
6080
.collect ( ) ;
6081
assert_eq! ( newest, vec! [ c3] , " stops at the first" ) ;
6082
assert_eq! (
6083
repo.log_for_path_of ( & state, " a.txt" , None ) .unwrap ( ) .len ( ) ,
6084
2
6085
) ;
6086
assert! ( repo
6087
.log_for_path_of ( & state, " missing.txt" , Some ( 1 ) )
6088
.is_err ( ) ) ;
6089
assert_eq! (
6090
repo.log_state ( & heads) .unwrap ( ) .len ( ) ,
6091
repo.log_of ( & state) .len ( )
6092
) ;
6093
6094
let tree = state.tree ( ) .view ( ) ;
6095
let via_state = repo.blame_of ( & state, & tree, " a.txt" ) .unwrap ( ) ;
6096
let via_heads = repo.blame_in ( " a.txt" , & heads) .unwrap ( ) ;
6097
assert_eq! (
6098
via_state
6099
.iter ( )
6100
.map ( | l | ( l.hash, l.text.clone ( ) ) )
6101
.collect:: < Vec< _ > > ( ) ,
6102
via_heads
6103
.iter ( )
6104
.map ( | l | ( l.hash, l.text.clone ( ) ) )
6105
.collect:: < Vec< _ > > ( )
6106
) ;
6107
assert_eq! (
6108
via_state.iter ( ) .map ( | l | l.hash ) .collect:: < Vec< _ > > ( ) ,
6109
vec! [ c1, c3]
6110
) ;
6111
assert! ( repo.blame_of ( & state, & tree, " missing.txt" ) .is_err ( ) ) ;
6112
}
696bbf29 Andy McCall 1 month ago ↶
6113
6114
6115
6116
6117
# [ cfg ( unix ) ]
6118
# [ test ]
6119
fn stat_cache_takes_unchanged_text_from_the_base_and_misses_no_edit ( ) {
6120
use std:: os:: unix:: fs:: PermissionsExt;
6121
hermetic ( ) ;
6122
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6123
let root = dir.path ( ) ;
6124
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6125
write ( root, " a.txt" , b " one\n two\n " ) ;
6126
write ( root, " b.txt" , b " bee\n " ) ;
6127
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6128
let age = | p : & str | {
6129
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6130
fs:: File:: options( )
6131
.write ( true )
6132
.open ( root.join ( p) )
6133
.unwrap ( )
6134
.set_modified ( old)
6135
.unwrap ( ) ;
6136
} ;
6137
age ( " a.txt" ) ;
6138
age ( " b.txt" ) ;
6139
assert! (
6140
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
6141
" records the aged stat"
6142
) ;
6143
6144
6145
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6146
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
6147
assert! (
6148
! snap.changed & & snap.statuses.is_empty ( ) ,
6149
" {:?}" ,
6150
snap.statuses
6151
) ;
6152
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6153
6154
6155
6156
write ( root, " b.txt" , b " bee edited\n " ) ;
6157
age ( " b.txt" ) ;
6158
let first = repo.snapshot ( " status" ) .unwrap ( ) ;
6159
assert_eq! (
6160
statuses ( & first) ,
6161
vec! [ ( " b.txt" .to_string ( ) , ChangeStatus:: Modified) ]
6162
) ;
6163
let again = repo.snapshot ( " status" ) .unwrap ( ) ;
6164
assert_eq! (
6165
statuses ( & again) ,
6166
vec! [ ( " b.txt" .to_string ( ) , ChangeStatus:: Modified) ] ,
6167
" still seen once stat-clean"
6168
) ;
6169
assert! ( ! again.changed, " and the working copy hashes the same" ) ;
6170
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6171
assert! (
6172
repo.snapshot ( " status" ) .is_err ( ) ,
6173
" an edited file must be read, never taken from the base"
6174
) ;
6175
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6176
6177
6178
write ( root, " a.txt" , b " one\n TWO\n " ) ;
6179
assert_eq! (
6180
statuses ( & repo.snapshot ( " status" ) .unwrap ( ) ) ,
6181
vec! [
6182
( " a.txt" .to_string ( ) , ChangeStatus:: Modified) ,
6183
( " b.txt" .to_string ( ) , ChangeStatus:: Modified)
6184
]
6185
) ;
6186
6187
repo.commit ( " edits" , & [ ] ) .unwrap ( ) ;
6188
age ( " a.txt" ) ;
6189
age ( " b.txt" ) ;
6190
assert! ( repo.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6191
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6192
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6193
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
6194
assert! ( ! snap.changed & & snap.statuses.is_empty ( ) ) ;
6195
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6196
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6197
}
afaba5f1 Andy McCall 1 month ago ↶
6198
6199
6200
6201
6202
# [ test ]
6203
fn checkpoints_keep_large_graphs_as_blobs_of_their_own ( ) {
6204
hermetic ( ) ;
6205
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6206
let root = dir.path ( ) ;
6207
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6208
let long: String = ( 0 .. 300 )
6209
.map ( | i | format! ( " line {i} of a long file\n " ) )
6210
.collect ( ) ;
6211
write ( root, " long.txt" , long.as_bytes ( ) ) ;
6212
write ( root, " short.txt" , b " short\n " ) ;
6213
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6214
let ( r1, _ , _ ) = repo.checkpoint ( ) .unwrap ( ) ;
6215
let cp1 = crate :: objects:: Checkpoint:: decode(
6216
& repo.store ( ) .get_typed ( & r1, ObjectType:: Checkpoint) .unwrap ( ) ,
6217
)
6218
.unwrap ( ) ;
6219
let graphs1 = repo.checkpoint_graph_blobs ( & cp1) .unwrap ( ) ;
6220
assert_eq! ( graphs1.len ( ) , 1 , " only the long file's graph is a blob" ) ;
6221
6222
6223
repo.gc ( true ) .unwrap ( ) ;
6224
assert! ( repo.store ( ) .has ( & graphs1[ 0 ] ) ) ;
6225
let report = repo.fsck ( ) .unwrap ( ) ;
6226
assert_eq! ( report.errors ( ) , 0 , " {:?}" , report.problems) ;
6227
6228
6229
write ( root, " short.txt" , b " short, edited\n " ) ;
6230
repo.commit ( " edit the short file" , & [ ] ) .unwrap ( ) ;
6231
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6232
let fast = repo.load_state ( & heads) .unwrap ( ) ;
6233
let full = repo.load_state_full ( & heads) .unwrap ( ) ;
6234
assert_eq! ( fast.checkpoint_payload ( ) , full.checkpoint_payload ( ) ) ;
6235
6236
6237
let ( r2, _ , _ ) = repo.checkpoint ( ) .unwrap ( ) ;
6238
let cp2 = crate :: objects:: Checkpoint:: decode(
6239
& repo.store ( ) .get_typed ( & r2, ObjectType:: Checkpoint) .unwrap ( ) ,
6240
)
6241
.unwrap ( ) ;
6242
assert_eq! ( repo.checkpoint_graph_blobs ( & cp2) .unwrap ( ) , graphs1) ;
6243
6244
6245
let bundle = repo.checkpoint_bundle_for ( & heads) .unwrap ( ) ;
6246
assert! ( bundle.objects.iter ( ) .any ( | ( t , body ) | * t = = ObjectType:: Blob
6247
& & crate :: hash:: hash_object( ObjectType:: Blob, body) = = graphs1[ 0 ] ) ) ;
6248
6249
6250
let state = repo.load_state_full ( & heads) .unwrap ( ) ;
6251
let old = repo.store_blob ( & state.checkpoint_payload ( ) ) .unwrap ( ) ;
6252
let cp_old = crate :: objects:: Checkpoint {
6253
heads: cp2.heads.clone ( ) ,
6254
count: cp2.count,
6255
payload: old,
6256
} ;
6257
let r_old = repo
6258
.store ( )
6259
.put ( ObjectType:: Checkpoint, & cp_old.encode ( ) )
6260
.unwrap ( ) ;
6261
repo.install_checkpoint ( " main" , & r_old) .unwrap ( ) ;
6262
assert_eq! (
6263
repo.load_state ( & heads) .unwrap ( ) .checkpoint_payload ( ) ,
6264
full.checkpoint_payload ( )
6265
) ;
6266
assert_eq! ( repo.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
6267
6268
6269
6270
let dir2 = tempfile:: tempdir( ) .unwrap ( ) ;
6271
let root2 = dir2.path ( ) ;
6272
let repo2 = Repo:: init( root2) .unwrap ( ) .with_home ( root2.join ( " home" ) ) ;
6273
write ( root2, " long.txt" , long.as_bytes ( ) ) ;
6274
repo2.commit ( " base" , & [ ] ) .unwrap ( ) ;
6275
let ( r, _ , _ ) = repo2.checkpoint ( ) .unwrap ( ) ;
6276
let cp = crate :: objects:: Checkpoint:: decode(
6277
& repo2.store ( ) .get_typed ( & r, ObjectType:: Checkpoint) .unwrap ( ) ,
6278
)
6279
.unwrap ( ) ;
6280
let graph = repo2.checkpoint_graph_blobs ( & cp) .unwrap ( ) [ 0 ] ;
64818938 Andy McCall 1 month ago ↶
6281
repo2.store ( ) .remove_loose_for_test ( & graph) .unwrap ( ) ;
afaba5f1 Andy McCall 1 month ago ↶
6282
assert! ( repo2.load_state ( & repo2.bookmark_heads ( ) .unwrap ( ) ) .is_err ( ) ) ;
6283
assert! ( repo2.fsck ( ) .unwrap ( ) .errors ( ) > 0 , " fsck reports it" ) ;
6284
}
e3d071ce Andy McCall 1 month ago ↶
6285
6286
6287
6288
6289
# [ test ]
6290
fn identity_is_found_in_the_key_directory_for_a_new_repository ( ) {
6291
hermetic ( ) ;
6292
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6293
let home = dir.path ( ) .join ( " home" ) ;
6294
let mut first = Repo:: init( & dir.path ( ) .join ( " a" ) )
6295
.unwrap ( )
6296
.with_home ( home.clone ( ) ) ;
6297
let ( key, _ ) = first
6298
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
6299
.unwrap ( ) ;
6300
let fresh = Repo:: init( & dir.path ( ) .join ( " b" ) )
6301
.unwrap ( )
6302
.with_home ( home.clone ( ) ) ;
6303
assert! ( ! fresh.store ( ) .has ( & key) , " nothing stored yet" ) ;
6304
let id = fresh.identity ( & key) .unwrap ( ) ;
6305
assert_eq! ( ( id.name.as_str ( ) , id.hash ( ) ) , ( " Ada" , key) ) ;
6306
6307
let keys = fresh.keys_dir.clone ( ) ;
6308
let ( other, _ ) = first
6309
.create_identity ( " Bob" , " [email protected] " , IdentityScope:: User)
6310
.unwrap ( ) ;
6311
fs:: copy(
6312
keys.join ( format! ( " {} .identity" , other.to_hex ( ) ) ) ,
6313
keys.join ( format! ( " {} .identity" , key.to_hex ( ) ) ) ,
6314
)
6315
.unwrap ( ) ;
6316
assert! ( fresh
6317
.identity ( & key)
6318
.unwrap_err ( )
6319
.to_string ( )
6320
.contains ( " does not hold identity" ) ) ;
6321
}
cc7a0056 Andy McCall 1 month ago ↶
6322
6323
6324
6325
6326
6327
# [ cfg ( unix ) ]
6328
# [ test ]
6329
fn status_snapshots_skip_files_unchanged_since_the_manifest ( ) {
6330
use std:: os:: unix:: fs:: PermissionsExt;
6331
hermetic ( ) ;
6332
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6333
let root = dir.path ( ) ;
6334
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6335
for ( p, c) in [ ( " a.txt" , " one\n " ) , ( " b.txt" , " bee\n " ) , ( " d/c.txt" , " sea\n " ) ] {
6336
write ( root, p, c.as_bytes ( ) ) ;
6337
}
6338
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6339
let age = | p : & str | {
6340
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6341
fs:: File:: options( )
6342
.write ( true )
6343
.open ( root.join ( p) )
6344
.unwrap ( )
6345
.set_modified ( old)
6346
.unwrap ( ) ;
6347
} ;
6348
let lock = | p : & str , mode : u32 | {
6349
fs:: set_permissions( root.join ( p) , fs:: Permissions:: from_mode( mode) ) .unwrap ( )
6350
} ;
6351
for p in [ " a.txt" , " b.txt" , " d/c.txt" ] {
6352
age ( p) ;
6353
}
6354
assert! ( repo.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6355
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
6356
assert! (
6357
m.entries.values ( ) .all ( | e | e.graph.is_some ( ) ) ,
6358
" keys recorded"
6359
) ;
6360
6361
6362
6363
lock ( " a.txt" , 0o000 ) ;
6364
lock ( " b.txt" , 0o000 ) ;
6365
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6366
assert! ( ! snap.changed & & snap.statuses.is_empty ( ) ) ;
6367
assert_eq! (
6368
snap.base_snapshot
6369
.unrendered
6370
.iter ( )
6371
.cloned ( )
6372
.collect:: < Vec< _ > > ( ) ,
6373
vec! [ " a.txt" , " b.txt" , " d/c.txt" ]
6374
) ;
6375
assert! ( ! snap.base.is_full ( ) ) ;
6376
assert! ( ! snap.base_snapshot.is_conflicted ( ) ) ;
6377
6378
let full = repo.snapshot ( " status" ) .unwrap ( ) ;
6379
assert! ( full.statuses.is_empty ( ) & & full.base.is_full ( ) ) ;
6380
assert! ( full.base_snapshot.unrendered.is_empty ( ) ) ;
6381
assert! ( matches! ( & full.base_snapshot.files[ " a.txt" ] .content,
6382
FileContent:: Text { bytes, .. } if bytes = = b " one\n " ) ) ;
6383
6384
6385
write ( root, " d/c.txt" , b " sea edited\n " ) ;
6386
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6387
assert_eq! (
6388
statuses ( & snap) ,
6389
vec! [ ( " d/c.txt" .to_string ( ) , ChangeStatus:: Modified) ]
6390
) ;
6391
assert! ( ! snap.base_snapshot.unrendered.contains ( " d/c.txt" ) ) ;
6392
6393
lock ( " b.txt" , 0o100 ) ;
6394
assert_eq! (
6395
statuses ( & repo.snapshot_status ( " status" ) .unwrap ( ) ) ,
6396
vec! [
6397
( " b.txt" .to_string ( ) , ChangeStatus:: ModeChanged) ,
6398
( " d/c.txt" .to_string ( ) , ChangeStatus:: Modified)
6399
]
6400
) ;
6401
lock ( " b.txt" , 0o000 ) ;
6402
6403
6404
6405
repo.commit_selection ( " edit c" , & [ ] , & Selection:: Paths( vec! [ " d/c.txt" .into ( ) ] ) )
6406
.unwrap ( ) ;
6407
age ( " d/c.txt" ) ;
6408
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6409
assert! ( snap.statuses.is_empty ( ) , " {:?}" , snap.statuses) ;
6410
assert! ( snap.base_snapshot.unrendered.contains ( " a.txt" ) ) ;
6411
lock ( " d/c.txt" , 0o000 ) ;
6412
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6413
assert! ( snap.statuses.is_empty ( ) ) ;
6414
assert! ( snap.base_snapshot.unrendered.contains ( " d/c.txt" ) ) ;
6415
for p in [ " a.txt" , " b.txt" , " d/c.txt" ] {
6416
lock ( p, 0o644 ) ;
6417
}
6418
}
6419
6420
6421
6422
# [ cfg ( unix ) ]
6423
# [ test ]
6424
fn a_changed_attributes_file_disables_skipping_unchanged_files ( ) {
6425
hermetic ( ) ;
6426
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6427
let root = dir.path ( ) ;
6428
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6429
write ( root, " a.txt" , b " crlf\r \n " ) ;
6430
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6431
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6432
fs:: File:: options( )
6433
.write ( true )
6434
.open ( root.join ( " a.txt" ) )
6435
.unwrap ( )
6436
.set_modified ( old)
6437
.unwrap ( ) ;
6438
assert! ( repo.snapshot_status ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6439
assert! ( repo
6440
.snapshot_status ( " status" )
6441
.unwrap ( )
6442
.base_snapshot
6443
.unrendered
6444
.contains ( " a.txt" ) ) ;
6445
6446
write ( root, " .oxyattributes" , b " *.txt eol=lf\n " ) ;
6447
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6448
assert_eq! (
6449
statuses ( & snap) ,
6450
vec! [
6451
( " .oxyattributes" .to_string ( ) , ChangeStatus:: Added) ,
6452
( " a.txt" .to_string ( ) , ChangeStatus:: Modified)
6453
]
6454
) ;
6455
}
54cd08a1 Andy McCall 1 month ago ↶
6456
6457
6458
6459
6460
# [ test ]
6461
fn bookmarks_with_slashes_push_and_checkpoint ( ) {
6462
hermetic ( ) ;
6463
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6464
let root = dir.path ( ) ;
6465
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
6466
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
6467
let mut repo = Repo:: clone( & url, & root.join ( " r" ) )
6468
.unwrap ( )
6469
.with_home ( root.join ( " home" ) ) ;
6470
6471
6472
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
6473
.unwrap ( ) ;
6474
let r = root.join ( " r" ) ;
6475
write ( & r, " a.txt" , b " a\n " ) ;
6476
let h_a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
6477
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
6478
origin.refresh ( ) .unwrap ( ) ;
54cd08a1 Andy McCall 1 month ago ↶
6479
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
6480
repo.bookmark_create ( " feature/x" , None ) .unwrap ( ) ;
6481
repo.bookmark_switch ( " feature/x" , false ) .unwrap ( ) ;
6482
write ( & r, " b.txt" , b " b\n " ) ;
6483
let h_b = repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
6484
for name in [ " feature" , " feature/x" ] {
6485
let res = repo.push_bookmark ( " origin" , name, false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
6486
origin.refresh ( ) .unwrap ( ) ;
54cd08a1 Andy McCall 1 month ago ↶
6487
assert! ( res[ 0 ] .ok, " {name}: {res:?}" ) ;
6488
}
6489
assert_eq! ( origin.resolve_heads ( " feature" ) .unwrap ( ) , vec! [ h_a] ) ;
6490
assert_eq! (
6491
origin.resolve_heads ( " feature/x" ) .unwrap ( ) ,
6492
repo.resolve_heads ( " feature/x" ) .unwrap ( )
6493
) ;
6494
assert! ( origin.resolve_heads ( " feature/x" ) .unwrap ( ) .contains ( & h_b) ) ;
6495
assert! ( origin.latest_ref_attest ( " feature/x" ) .unwrap ( ) .is_some ( ) ) ;
6496
assert! ( origin.latest_ref_attest ( " feature" ) .unwrap ( ) .is_some ( ) ) ;
6497
6498
6499
let ( cp, _ , _ ) = repo.checkpoint ( ) .unwrap ( ) ;
6500
assert_eq! ( repo.latest_checkpoint ( " feature/x" ) .unwrap ( ) .unwrap ( ) .0 , cp) ;
6501
assert! ( repo.latest_checkpoint ( " feature" ) .unwrap ( ) .is_none ( ) ) ;
6502
let heads = repo.resolve_heads ( " feature/x" ) .unwrap ( ) ;
6503
assert! ( repo.load_state_checkpointed ( & heads) .unwrap ( ) .is_some ( ) ) ;
f8c69576 Andy McCall 1 month ago ↶
6504
}
6505
6506
6507
6508
6509
# [ test ]
6510
fn bookmarks_are_deleted_on_a_remote ( ) {
6511
hermetic ( ) ;
6512
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6513
let root = dir.path ( ) ;
6514
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
6515
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
6516
let mut repo = Repo:: clone( & url, & root.join ( " r" ) )
6517
.unwrap ( )
6518
.with_home ( root.join ( " home" ) ) ;
6519
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
6520
.unwrap ( ) ;
6521
let r = root.join ( " r" ) ;
6522
write ( & r, " a.txt" , b " a\n " ) ;
6523
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
6524
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
6525
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6526
repo.bookmark_create ( " feature/x" , None ) .unwrap ( ) ;
6527
assert! ( repo.push_bookmark ( " origin" , " feature/x" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
6528
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6529
assert! ( origin.resolve_heads ( " feature/x" ) .is_ok ( ) ) ;
6530
6531
let res = repo.push_delete ( " origin" , " feature/x" ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
6532
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6533
assert! ( res[ 0 ] .ok, " {res:?}" ) ;
6534
assert! ( ! origin.view ( ) .unwrap ( ) .bookmarks.contains_key ( " feature/x" ) ) ;
6535
let ( _ , att) = origin.latest_ref_attest ( " feature/x" ) .unwrap ( ) .unwrap ( ) ;
6536
assert! (
6537
matches! ( att.attest.body, AttestBody:: Ref { new_state, .. } if new_state.is_zero ( ) ) ,
6538
" the deletion is attested"
6539
) ;
6540
assert! (
6541
repo.view ( ) .unwrap ( ) .bookmarks.contains_key ( " feature/x" ) ,
6542
" the local bookmark stays"
6543
) ;
6544
assert! ( ! repo
6545
.view ( )
6546
.unwrap ( )
6547
.remotes
6548
.get ( " origin" )
6549
.is_some_and ( | m | m.contains_key ( " feature/x" ) ) ) ;
6550
6551
6552
let res = repo.push_bookmark ( " origin" , " feature/x" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
6553
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6554
assert! ( res[ 0 ] .ok, " {res:?}" ) ;
6555
assert! ( origin.view ( ) .unwrap ( ) .bookmarks.contains_key ( " feature/x" ) ) ;
6556
6557
6558
assert! ( repo.push_delete ( " origin" , " nope" ) .is_err ( ) ) ;
6559
let res = repo.push_delete ( " origin" , " main" ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
6560
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6561
assert! (
6562
! res[ 0 ] .ok & & res[ 0 ] .reason.contains ( " default bookmark" ) ,
6563
" {res:?}"
6564
) ;
6565
assert! ( origin.view ( ) .unwrap ( ) .bookmarks.contains_key ( " main" ) ) ;
54cd08a1 Andy McCall 1 month ago ↶
6566
}
61e3bce7 Andy McCall 1 month ago ↶
6567
6568
6569
6570
6571
# [ test ]
6572
fn switching_bookmarks_carries_uncommitted_work ( ) {
6573
hermetic ( ) ;
6574
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6575
let root = dir.path ( ) ;
6576
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6577
write ( root, " a.txt" , b " a\n " ) ;
6578
write ( root, " b.txt" , b " b\n " ) ;
6579
write ( root, " gone.txt" , b " gone\n " ) ;
6580
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6581
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
6582
6583
write ( root, " a.txt" , b " a\n more\n " ) ;
6584
write ( root, " new.txt" , b " new\n " ) ;
6585
fs:: remove_file( root.join ( " gone.txt" ) ) .unwrap ( ) ;
44f1ebe7 Andy McCall 19 days ago ↶
6586
let ( report, carried) = repo.bookmark_switch_carry ( " feature" ) .unwrap ( ) ;
61e3bce7 Andy McCall 1 month ago ↶
6587
assert_eq! ( carried, vec! [ " a.txt" , " gone.txt" , " new.txt" ] ) ;
44f1ebe7 Andy McCall 19 days ago ↶
6588
6589
assert_eq! (
6590
( report.removed, report.removed_paths.len ( ) ) ,
6591
( 0 , 0 ) ,
6592
" {report:?}"
6593
) ;
61e3bce7 Andy McCall 1 month ago ↶
6594
assert_eq! ( repo.config.bookmark, " feature" ) ;
6595
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " a\n more\n " ) ;
6596
assert_eq! ( fs:: read( root.join ( " new.txt" ) ) .unwrap ( ) , b " new\n " ) ;
6597
assert! ( ! root.join ( " gone.txt" ) .exists ( ) ) ;
6598
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
6599
assert_eq! (
6600
statuses ( & snap) ,
6601
vec! [
6602
( " a.txt" .to_string ( ) , ChangeStatus:: Modified) ,
6603
( " gone.txt" .to_string ( ) , ChangeStatus:: Removed) ,
6604
( " new.txt" .to_string ( ) , ChangeStatus:: Added)
6605
]
6606
) ;
6607
repo.commit ( " the work" , & [ ] ) .unwrap ( ) ;
6608
assert! ( repo.resolve_heads ( " main" ) .unwrap ( ) .len ( ) = = 1 ) ;
6609
6610
6611
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
6612
write ( root, " b.txt" , b " b on main\n " ) ;
6613
repo.commit ( " b on main" , & [ ] ) .unwrap ( ) ;
6614
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
6615
write ( root, " b.txt" , b " b edited\n " ) ;
6616
let err = repo.bookmark_switch_carry ( " main" ) .unwrap_err ( ) .to_string ( ) ;
6617
assert! ( err.contains ( " b.txt" ) & & err.contains ( " --discard" ) , " {err}" ) ;
6618
assert_eq! ( repo.config.bookmark, " feature" , " nothing happened" ) ;
6619
assert_eq! ( fs:: read( root.join ( " b.txt" ) ) .unwrap ( ) , b " b edited\n " ) ;
6620
}
1c2bd9ab Andy McCall 1 month ago ↶
6621
6622
6623
6624
6625
# [ test ]
6626
fn merges_move_clean_working_copies_and_checkouts_say_what_blocks_them ( ) {
6627
hermetic ( ) ;
6628
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6629
let root = dir.path ( ) ;
6630
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6631
write ( root, " a.txt" , b " a\n " ) ;
6632
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
6633
write ( root, " b.txt" , b " b\n " ) ;
6634
repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
6635
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
6636
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
6637
write ( root, " c.txt" , b " c\n " ) ;
6638
repo.commit ( " c on feature" , & [ ] ) .unwrap ( ) ;
6639
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
6640
6641
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
6642
assert! ( ! root.join ( " b.txt" ) .exists ( ) ) ;
6643
let m = repo.merge ( " feature" ) .unwrap ( ) ;
6644
assert! ( m.checked_out, " a clean working copy follows the merge" ) ;
6645
for f in [ " a.txt" , " b.txt" , " c.txt" ] {
6646
assert! ( root.join ( f) .exists ( ) , " {f}" ) ;
6647
}
6648
6649
6650
fs:: remove_file( root.join ( " b.txt" ) ) .unwrap ( ) ;
6651
write ( root, " a.txt" , b " a edited\n " ) ;
6652
let err = repo
6653
.checkout ( & [ h1] , false , " checkout" )
6654
.unwrap_err ( )
6655
.to_string ( ) ;
6656
assert! (
6657
err.contains ( " modified a.txt" ) & & err.contains ( " removed b.txt" ) & & err.contains ( " --carry" ) ,
6658
" {err}"
6659
) ;
fe9586d9 Andy McCall 26 days ago ↶
6660
6661
6662
1c2bd9ab Andy McCall 1 month ago ↶
6663
let ( _ , carried) = repo
6664
.checkout_carry ( & [ h1] , " the first change" , " checkout" )
6665
.unwrap ( ) ;
6666
assert_eq! ( carried, vec! [ " a.txt" ] ) ;
6667
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " a edited\n " ) ;
6668
assert! ( ! root.join ( " b.txt" ) .exists ( ) , " h1 has no b.txt" ) ;
6669
}
e7ac6c2e Andy McCall 1 month ago ↶
6670
6671
6672
6673
6674
# [ test ]
6675
fn a_new_repository_is_healthy_before_its_first_commit ( ) {
6676
hermetic ( ) ;
6677
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6678
let root = dir.path ( ) ;
6679
let home = root.join ( " home" ) ;
6680
let mut first = Repo:: init( & root.join ( " first" ) )
6681
.unwrap ( )
6682
.with_home ( home.clone ( ) ) ;
6683
let ( key, _ ) = first
6684
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
6685
.unwrap ( ) ;
6686
let repo = Repo:: init( & root.join ( " second" ) )
6687
.unwrap ( )
6688
.with_home ( home.clone ( ) ) ;
6689
6690
let keys = repo.keys_dir.clone ( ) ;
6691
let _ = fs:: remove_file( keys.join ( format! ( " {} .identity" , key.to_hex ( ) ) ) ) ;
6692
assert! ( keys.join ( format! ( " {} .secret" , key.to_hex ( ) ) ) .is_file ( ) ) ;
6693
let r = repo.doctor ( true ) ;
6694
let id = r.checks.iter ( ) .find ( | c | c.area = = " identity" ) .unwrap ( ) ;
6695
assert_eq! ( id.health, Health:: Ok, " {}" , id.message) ;
6696
assert! ( id.message.contains ( " first signed change" ) , " {}" , id.message) ;
6697
6698
6699
let lib = root.join ( " lib" ) ;
6700
let l = Repo:: init( & lib) .unwrap ( ) .with_home ( home.clone ( ) ) ;
6701
write ( & lib, " x.txt" , b " x\n " ) ;
6702
l.commit ( " x" , & [ ] ) .unwrap ( ) ;
6703
let second = root.join ( " second" ) ;
6704
write ( & second, " a.txt" , b " a\n " ) ;
6705
repo.submodule_add ( & lib.to_string_lossy ( ) , " deps/lib" , None )
6706
.unwrap ( ) ;
6707
let subs = repo.subrepos ( ) .unwrap ( ) ;
6708
assert_eq! ( subs.len ( ) , 1 ) ;
6709
assert_eq! (
6710
( subs[ 0 ] .path.as_str ( ) , subs[ 0 ] .committed) ,
6711
( " deps/lib" , false )
6712
) ;
6713
assert_eq! ( subs[ 0 ] .actual.as_ref ( ) , Some ( & subs[ 0 ] .pin) ) ;
6714
repo.commit ( " add lib" , & [ ] ) .unwrap ( ) ;
6715
let subs = repo.subrepos ( ) .unwrap ( ) ;
6716
assert! ( subs[ 0 ] .committed, " committed now" ) ;
6717
}
8ab265dc Andy McCall 1 month ago ↶
6718
6719
6720
6721
6722
# [ cfg ( unix ) ]
6723
# [ test ]
6724
fn commits_and_diffs_leave_unchanged_files_alone ( ) {
6725
use std:: os:: unix:: fs:: PermissionsExt;
6726
hermetic ( ) ;
6727
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6728
let root = dir.path ( ) ;
6729
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6730
write ( root, " big.txt" , b " lots\n of\n lines\n " ) ;
6731
write ( root, " a.txt" , b " one\n two\n " ) ;
6732
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6733
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6734
for f in [ " big.txt" , " a.txt" ] {
6735
fs:: File:: options( )
6736
.write ( true )
6737
.open ( root.join ( f) )
6738
.unwrap ( )
6739
.set_modified ( old)
6740
.unwrap ( ) ;
6741
}
6742
assert! ( repo.snapshot_status ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6743
6744
fs:: set_permissions( root.join ( " big.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6745
6746
write ( root, " a.txt" , b " one\n TWO\n three\n " ) ;
6747
let diff = repo.diff_files ( None ) .unwrap ( ) ;
6748
assert_eq! (
6749
diff.iter ( ) .map ( | f | f.path.as_str ( ) ) .collect:: < Vec< _ > > ( ) ,
6750
[ " a.txt" ]
6751
) ;
6752
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
6753
assert! ( ! hunks.is_empty ( ) & & hunks.iter ( ) .all ( | h | h.path = = " a.txt" ) ) ;
6754
let h = repo.commit ( " edit a" , & [ ] ) .unwrap ( ) ;
6755
6756
6757
let c = repo.store ( ) .get_change ( & h) .unwrap ( ) ;
6758
let full = repo.load_state ( & c.deps) .unwrap ( ) ;
6759
let want = render_change ( & c, & full, & full.materialize ( ) ) ;
6760
assert_eq! ( repo.describe_change ( & h) .unwrap ( ) , want) ;
6761
let ( base, snap) = repo.change_base ( & c) .unwrap ( ) ;
6762
assert! ( ! base.is_full ( ) & & snap.unrendered.contains ( " big.txt" ) ) ;
6763
6764
6765
write ( root, " a.txt" , b " scratch\n " ) ;
6766
assert_eq! ( repo.discard ( & [ " a.txt" .into ( ) ] ) .unwrap ( ) , 1 ) ;
6767
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n TWO\n three\n " ) ;
6768
fs:: set_permissions( root.join ( " big.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6769
}
f7510b63 Andy McCall 1 month ago ↶
6770
6771
6772
6773
6774
# [ test ]
6775
fn replace_conflicts_are_settled_when_asked ( ) {
6776
hermetic ( ) ;
6777
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6778
let root = dir.path ( ) ;
6779
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6780
let blob = | n : u8 | {
6781
let mut v = vec! [ 0 u8 ; 64 ] ;
6782
v[ 1 ] = n;
6783
v
6784
} ;
6785
write ( root, " img.bin" , & blob ( 1 ) ) ;
6786
repo.commit ( " add img" , & [ ] ) .unwrap ( ) ;
6787
repo.bookmark_create ( " other" , None ) .unwrap ( ) ;
6788
write ( root, " img.bin" , & blob ( 2 ) ) ;
6789
repo.commit ( " img two" , & [ ] ) .unwrap ( ) ;
6790
repo.bookmark_switch ( " other" , false ) .unwrap ( ) ;
6791
write ( root, " img.bin" , & blob ( 3 ) ) ;
6792
repo.commit ( " img three" , & [ ] ) .unwrap ( ) ;
6793
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
6794
repo.merge ( " other" ) .unwrap ( ) ;
6795
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6796
let merged = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
6797
assert! (
6798
matches! ( & merged.files[ " img.bin" ] .content, FileContent:: Opaque { blobs } if blobs.len ( ) = = 2 ) ,
6799
" a replace conflict"
6800
) ;
6801
6802
assert! ( repo
6803
.snapshot ( " status" )
6804
.unwrap ( )
6805
.statuses
6806
.iter ( )
6807
.all ( | s | s.path ! = " img.bin" ) ) ;
6808
6809
6810
let author = repo.author ( ) ;
6811
let edits = [ FileEdit {
6812
path: " img.bin" .into ( ) ,
6813
content: Some ( blob ( 3 ) ) ,
6814
} ] ;
6815
let c = repo
6816
.build_change ( & heads, & edits, " keep three" , author)
6817
.unwrap ( ) ;
6818
assert_eq! ( c.kind, ChangeKind:: Resolution) ;
6819
let mut st = repo.load_state ( & heads) .unwrap ( ) ;
6820
for d in & c.deps {
6821
assert! ( st.contains ( d) ) ;
6822
}
6823
assert! (
6824
heads.iter ( ) .all ( | h | c.deps.contains ( h) ) ,
6825
" depends on both contributors"
6826
) ;
6827
st.add ( c) .unwrap ( ) ;
6828
let after = st.materialize ( ) ;
6829
assert! ( ! after.is_conflicted ( ) ) ;
6830
assert_eq! (
6831
after.files[ " img.bin" ] .content,
6832
FileContent:: Opaque {
6833
blobs: vec! [ Repo:: blob_ref( & blob ( 3 ) ) ]
6834
}
6835
) ;
6836
6837
6838
let edits = [ FileEdit {
6839
path: " img.bin" .into ( ) ,
6840
content: Some ( blob ( 9 ) ) ,
6841
} ] ;
6842
let c = repo.build_change ( & heads, & edits, " new" , author) .unwrap ( ) ;
6843
let mut st = repo.load_state ( & heads) .unwrap ( ) ;
6844
st.add ( c) .unwrap ( ) ;
6845
assert_eq! (
6846
st.materialize ( ) .files[ " img.bin" ] .content,
6847
FileContent:: Opaque {
6848
blobs: vec! [ Repo:: blob_ref( & blob ( 9 ) ) ]
6849
}
6850
) ;
6851
}
83d61e80 Andy McCall 1 month ago ↶
6852
6853
6854
6855
6856
# [ test ]
6857
fn line_ranges_have_exact_histories ( ) {
6858
hermetic ( ) ;
6859
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6860
let root = dir.path ( ) ;
6861
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6862
write ( root, " f.rs" , b " use x;\n fn a() {\n one();\n two();\n }\n " ) ;
6863
let base = repo.commit ( " add f" , & [ ] ) .unwrap ( ) ;
6864
write ( root, " f.rs" , b " use y;\n fn a() {\n one();\n two();\n }\n " ) ;
6865
let outside = repo.commit ( " change the import" , & [ ] ) .unwrap ( ) ;
6866
write ( root, " f.rs" , b " use y;\n fn a() {\n ONE();\n two();\n }\n " ) ;
6867
let inside = repo.commit ( " edit a line of a" , & [ ] ) .unwrap ( ) ;
6868
write ( root, " f.rs" , b " use y;\n fn a() {\n ONE();\n }\n " ) ;
6869
let deleted = repo.commit ( " delete a line of a" , & [ ] ) .unwrap ( ) ;
6870
write ( root, " f.rs" , b " use y;\n fn a() {\n ONE();\n }\n fn b() {}\n " ) ;
6871
let after = repo.commit ( " add b after a" , & [ ] ) .unwrap ( ) ;
6872
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6873
6874
6875
let hist: Vec< ChangeHash> = repo
6876
.line_history ( & heads, " f.rs" , 2 , 4 )
6877
.unwrap ( )
6878
.iter ( )
6879
.map ( | e | e.hash )
6880
.collect ( ) ;
6881
for ( h, what) in [
6882
( base, " wrote it" ) ,
6883
( inside, " edited a line" ) ,
6884
( deleted, " deleted a line from inside" ) ,
6885
] {
6886
assert! ( hist.contains ( & h) , " {what}" ) ;
6887
}
6888
for ( h, what) in [ ( outside, " an edit above" ) , ( after, " an addition below" ) ] {
6889
assert! ( ! hist.contains ( & h) , " {what}" ) ;
6890
}
6891
assert_eq! ( hist[ 0 ] , deleted, " newest first" ) ;
6892
6893
let one: Vec< ChangeHash> = repo
6894
.line_history ( & heads, " f.rs" , 1 , 1 )
6895
.unwrap ( )
6896
.iter ( )
6897
.map ( | e | e.hash )
6898
.collect ( ) ;
6899
assert! ( one.contains ( & outside) & & ! one.contains ( & inside) ) ;
6900
assert! ( repo.line_history ( & heads, " f.rs" , 3 , 9 ) .is_err ( ) ) ;
6901
assert! ( repo.line_history ( & heads, " f.rs" , 0 , 1 ) .is_err ( ) ) ;
6902
assert! ( repo.line_history ( & heads, " nope.rs" , 1 , 1 ) .is_err ( ) ) ;
6903
}
fc1a466a Andy McCall 1 month ago ↶
6904
6905
# [ test ]
6906
fn receipts_are_kept_with_their_state_and_verified ( ) {
6907
use crate :: receipt:: { Receipt, SignedReceipt} ;
6908
hermetic ( ) ;
6909
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6910
let root = dir.path ( ) ;
6911
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6912
write ( root, " a.txt" , b " 1\n " ) ;
6913
repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
6914
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6915
let state = repo
6916
.store ( )
6917
.put_state ( & StateObject:: new( heads.clone ( ) ) )
6918
.unwrap ( ) ;
6919
let out = repo.store_blob ( b " built\n " ) .unwrap ( ) ;
6920
let receipt = Receipt {
6921
job_name: " build" .into ( ) ,
6922
pipeline_hash: ObjectRef( [ 1 ; 32 ] ) ,
6923
scope_tree_hash: ObjectRef( [ 2 ; 32 ] ) ,
6924
inputs: vec! [ ] ,
6925
environment: ObjectRef( [ 3 ; 32 ] ) ,
6926
outputs: vec! [ out] ,
6927
exit_code: 0 ,
6928
duration_ms: 42 ,
6929
} ;
6930
let sk = SecretKey:: generate( ) ;
6931
let runner = sk.identity ( " earth-1" , " runner@earth" , 1 ) ;
6932
let sr = SignedReceipt:: sign(
6933
receipt.clone ( ) ,
6934
repo.id ( ) .unwrap ( ) ,
6935
state,
6936
" demo#1" ,
6937
& runner,
6938
& sk,
6939
) ;
6940
6941
let r = repo.add_receipt ( & sr) .unwrap ( ) ;
6942
let found = repo.receipts ( & heads) .unwrap ( ) ;
6943
assert_eq! ( found.len ( ) , 1 ) ;
6944
assert_eq! ( found[ 0 ] .attest, r) ;
6945
assert_eq! ( found[ 0 ] .signed, sr) ;
6946
assert! ( found[ 0 ] .problem.is_none ( ) & & ! found[ 0 ] .revoked) ;
6947
assert! ( repo.receipts ( & [ ObjectRef( [ 5 ; 32 ] ) ] ) .unwrap ( ) .is_empty ( ) ) ;
6948
6949
6950
let elsewhere = SignedReceipt:: sign( receipt.clone ( ) , [ 0 ; 16 ] , state, " x" , & runner, & sk) ;
6951
assert! ( repo
6952
.add_receipt ( & elsewhere)
6953
.unwrap_err ( )
6954
.to_string ( )
6955
.contains ( " another repository" ) ) ;
6956
let mut forged = sr.clone ( ) ;
6957
forged.receipt.exit_code = 1 ;
6958
assert! ( repo
6959
.add_receipt ( & forged)
6960
.unwrap_err ( )
6961
.to_string ( )
6962
.contains ( " does not verify" ) ) ;
6963
c63fed2e Andy McCall 20 days ago ↶
6964
6965
6966
let hub_sk = SecretKey:: generate( ) ;
6967
let hub = hub_sk.identity ( " oxygenhub.io" , " [email protected] " , 1 ) ;
6968
let job_sk = SecretKey:: generate( ) ;
6969
let job = job_sk.identity ( " job 2 test" , " [email protected] " , 1 ) ;
6970
let mut test_receipt = receipt.clone ( ) ;
6971
test_receipt.job_name = " test" .into ( ) ;
6972
let plain = SignedReceipt:: sign(
6973
test_receipt,
6974
repo.id ( ) .unwrap ( ) ,
6975
state,
6976
" demo#2" ,
6977
& job,
6978
& job_sk,
6979
) ;
6980
let now = plain.attest.hlc.wall_ms;
6981
let cert = crate :: cert:: SignedCertificate:: sign(
6982
crate :: cert:: Certificate {
6983
key: job.clone ( ) ,
6984
issuer: hub.hash ( ) ,
6985
subject: " repo:andy/demo:bookmark:main" .into ( ) ,
6986
claims: vec! [ ] ,
6987
scope: crate :: identity:: Scope:: SEAL ,
6988
not_before: now - 1_000 ,
6989
expires: now + 600_000 ,
a2387d54 Andy McCall 20 days ago ↶
6990
grant: None ,
c63fed2e Andy McCall 20 days ago ↶
6991
} ,
6992
& hub,
6993
& hub_sk,
6994
) ;
6995
let certified = plain.with_certificate ( cert, hub.clone ( ) ) ;
6996
repo.add_receipt ( & certified) .unwrap ( ) ;
6997
let found = repo.receipts ( & heads) .unwrap ( ) ;
6998
let got = found
6999
.iter ( )
7000
.find ( | i | i.signed.receipt.job_name = = " test" )
7001
.unwrap ( ) ;
7002
assert_eq! ( got.signed, certified) ;
7003
assert! ( got.problem.is_none ( ) , " {:?}" , got.problem) ;
7004
7005
7006
fc1a466a Andy McCall 1 month ago ↶
7007
repo.gc ( true ) .unwrap ( ) ;
c63fed2e Andy McCall 20 days ago ↶
7008
let found = repo.receipts ( & heads) .unwrap ( ) ;
7009
assert_eq! ( found.len ( ) , 2 ) ;
7010
let got = found
7011
.iter ( )
7012
.find ( | i | i.signed.receipt.job_name = = " test" )
7013
.unwrap ( ) ;
7014
assert_eq! ( got.signed, certified, " the certificate survives gc" ) ;
7015
assert! ( got.problem.is_none ( ) ) ;
fc1a466a Andy McCall 1 month ago ↶
7016
assert_eq! ( repo.read_blob ( & out) .unwrap ( ) , b " built\n " ) ;
7017
assert! ( repo.fsck ( ) .unwrap ( ) .problems.is_empty ( ) ) ;
dfb23a3a Andy McCall 1 month ago ↶
7018
}
7019
7020
# [ test ]
3a4b3e01 Andy McCall 21 days ago ↶
7021
fn approvals_are_kept_with_their_state_and_survive_gc ( ) {
7022
hermetic ( ) ;
7023
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7024
let root = dir.path ( ) ;
7025
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
7026
write ( root, " a.txt" , b " 1\n " ) ;
7027
repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
7028
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
7029
let state = repo.store ( ) .put_state ( & StateObject:: new( heads) ) .unwrap ( ) ;
7030
let plan = repo.store_blob ( b " the reviewed plan\n " ) .unwrap ( ) ;
7031
let sk = SecretKey:: generate( ) ;
7032
let andy = sk.identity ( " Andy" , " [email protected] " , 1 ) ;
7033
let statement = crate :: approval:: Statement {
7034
repo: repo.id ( ) .unwrap ( ) ,
7035
run: " https://hub.example/andy/infra/flow/8" .into ( ) ,
7036
job: " apply" .into ( ) ,
7037
job_hash: ObjectRef( [ 1 ; 32 ] ) ,
7038
state,
7039
inputs: vec! [ plan] ,
7040
environment: " prod" .into ( ) ,
7041
approve: true ,
7042
} ;
7043
let sa = crate :: approval:: SignedApproval:: sign( & statement, & andy, & sk) ;
7044
let r = repo.add_approval ( & sa) .unwrap ( ) ;
7045
assert_eq! ( repo.store ( ) .approvals_for ( & state) .unwrap ( ) , vec! [ r] ) ;
7046
7047
let elsewhere = crate :: approval:: SignedApproval:: sign(
7048
& crate :: approval:: Statement {
7049
repo: [ 0 ; 16 ] ,
7050
.. statement.clone ( )
7051
} ,
7052
& andy,
7053
& sk,
7054
) ;
7055
assert! ( repo
7056
.add_approval ( & elsewhere)
7057
.unwrap_err ( )
7058
.to_string ( )
7059
.contains ( " another repository" ) ) ;
7060
let mut forged = sa.clone ( ) ;
7061
if let AttestBody:: Approval { environment, .. } = & mut forged.attest.body {
7062
* environment = " dev" .into ( ) ;
7063
}
7064
assert! ( repo
7065
.add_approval ( & forged)
7066
.unwrap_err ( )
7067
.to_string ( )
7068
.contains ( " does not verify" ) ) ;
7069
7070
repo.gc ( true ) .unwrap ( ) ;
7071
assert_eq! ( repo.store ( ) .approvals_for ( & state) .unwrap ( ) , vec! [ r] ) ;
7072
assert_eq! ( repo.store ( ) .get_attest ( & r) .unwrap ( ) , sa.attest) ;
7073
assert_eq! ( repo.store ( ) .signatures ( & r) .unwrap ( ) , sa.sigs) ;
7074
assert! ( repo.fsck ( ) .unwrap ( ) .problems.is_empty ( ) ) ;
7075
}
7076
7077
# [ test ]
dfb23a3a Andy McCall 1 month ago ↶
7078
fn status_leaves_an_unchanged_manifest_alone ( ) {
7079
hermetic ( ) ;
7080
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7081
let root = dir.path ( ) ;
7082
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
7083
write ( root, " a.txt" , b " 1\n " ) ;
7084
write ( root, " b.txt" , b " 2\n " ) ;
7085
repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
7086
7087
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 60 ) ;
7088
for f in [ " a.txt" , " b.txt" ] {
7089
fs:: File:: options( )
7090
.write ( true )
7091
.open ( root.join ( f) )
7092
.unwrap ( )
7093
.set_modified ( old)
7094
.unwrap ( ) ;
7095
}
7096
repo.snapshot_status ( " status" ) .unwrap ( ) ;
7097
let path = repo.manifest_path ( ) ;
7098
let first = fs:: read( & path) .unwrap ( ) ;
7099
repo.snapshot_status ( " status" ) .unwrap ( ) ;
7100
assert_eq! (
7101
fs:: read( & path) .unwrap ( ) ,
7102
first,
7103
" an unchanged tree does not rewrite the manifest"
7104
) ;
7105
7106
write ( root, " a.txt" , b " 1\n 2\n " ) ;
7107
repo.snapshot_status ( " status" ) .unwrap ( ) ;
7108
assert_ne! ( fs:: read( & path) .unwrap ( ) , first) ;
fc1a466a Andy McCall 1 month ago ↶
7109
}
6f8aeb99 Andy McCall 1 month ago ↶
7110
7111
# [ test ]
7112
fn large_text_files_travel_and_survive_gc_as_text_blobs ( ) {
7113
hermetic ( ) ;
7114
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7115
let root = dir.path ( ) ;
7116
let origin_path = root.join ( " origin" ) ;
7117
Repo:: init( & origin_path) .unwrap ( ) ;
7118
let origin_url = origin_path.to_string_lossy ( ) .to_string ( ) ;
7119
let a = Repo:: clone( & origin_url, & root.join ( " a" ) ) .unwrap ( ) ;
7120
let big: String = ( 0 .. 5000 ) .map ( | i | format! ( " line {i} \n " ) ) .collect ( ) ;
7121
write ( & root.join ( " a" ) , " big.txt" , big.as_bytes ( ) ) ;
7122
write ( & root.join ( " a" ) , " small.txt" , b " small\n " ) ;
7123
let h1 = a.commit ( " large" , & [ ] ) .unwrap ( ) ;
7124
let ( _ , body) = a.store ( ) .get ( & h1) .unwrap ( ) ;
7125
assert! ( body.len ( ) < 2048 , " the change is {} bytes" , body.len ( ) ) ;
7126
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7127
7128
7129
let b = Repo:: clone( & origin_url, & root.join ( " b" ) ) .unwrap ( ) ;
7130
assert_eq! ( fs:: read( root.join ( " b/big.txt" ) ) .unwrap ( ) , big.as_bytes ( ) ) ;
7131
7132
let edited = big.replace ( " line 2500\n " , " line 2500, edited\n " ) ;
7133
write ( & root.join ( " b" ) , " big.txt" , edited.as_bytes ( ) ) ;
7134
let h2 = b.commit ( " edit" , & [ ] ) .unwrap ( ) ;
7135
assert! ( b.store ( ) .get_change ( & h2) .unwrap ( ) .edits.len ( ) < = 2 ) ;
7136
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7137
assert! ( a.pull ( " origin" ) .unwrap ( ) .checked_out) ;
7138
assert_eq! ( fs:: read( root.join ( " a/big.txt" ) ) .unwrap ( ) , edited.as_bytes ( ) ) ;
7139
assert_eq! (
7140
a.fsck ( ) .unwrap ( ) .errors ( ) ,
7141
0 ,
7142
" {:?}" ,
7143
a.fsck ( ) .unwrap ( ) .problems
7144
) ;
7145
7146
7147
let mut a = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) ;
7148
a.config.set ( " core.op-retention" , " 0" ) .unwrap ( ) ;
7149
a.save_config ( ) .unwrap ( ) ;
7150
let a = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) ;
7151
a.gc ( true ) .unwrap ( ) ;
7152
let a = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) ;
7153
assert_eq! ( a.store ( ) .get_change ( & h1) .unwrap ( ) .message, " large" ) ;
7154
assert_eq! (
7155
a.fsck ( ) .unwrap ( ) .errors ( ) ,
7156
0 ,
7157
" {:?}" ,
7158
a.fsck ( ) .unwrap ( ) .problems
7159
) ;
7160
7161
let c = Repo:: clone( & root.join ( " a" ) .to_string_lossy ( ) , & root.join ( " c" ) ) .unwrap ( ) ;
7162
assert_eq! ( fs:: read( root.join ( " c/big.txt" ) ) .unwrap ( ) , edited.as_bytes ( ) ) ;
7163
drop ( c) ;
6caacfff Andy McCall 1 month ago ↶
7164
}
7165
7166
# [ test ]
7167
fn operations_that_both_replace_a_working_copy_are_not_merged ( ) {
7168
hermetic ( ) ;
7169
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7170
let root = dir.path ( ) ;
7171
let repo = Repo:: init( root) .unwrap ( ) ;
7172
write ( root, " a.txt" , b " a\n " ) ;
7173
let a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
7174
write ( root, " b.txt" , b " b\n " ) ;
7175
repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
7176
let ( x, op) = repo.head_op ( ) .unwrap ( ) ;
7177
let ws = * op.view.workspaces.keys ( ) .next ( ) .unwrap ( ) ;
7178
let mut one = op.view.clone ( ) ;
7179
one.workspaces.insert ( ws, a) ;
7180
let mut two = op.view.clone ( ) ;
7181
two.workspaces.insert ( ws, ObjectRef( [ 7 ; 32 ] ) ) ;
7182
let first = repo.record_op ( & [ x] , " one" , one) .unwrap ( ) ;
7183
let e = repo.record_op ( & [ x] , " two" , two) .unwrap_err ( ) ;
7184
assert! (
7185
e.to_string ( )
7186
.contains ( " changed this working copy at the same time" ) ,
7187
" {e}"
7188
) ;
7189
assert_eq! (
7190
repo.head_op ( ) .unwrap ( ) .0 ,
7191
first,
7192
" the second is not recorded"
7193
) ;
7194
}
7195
7196
7197
7198
7199
# [ test ]
7200
fn a_snapshot_during_a_switch_never_records_the_switch_as_edits ( ) {
7201
hermetic ( ) ;
7202
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7203
let root = dir.path ( ) .to_path_buf ( ) ;
7204
let mut repo = Repo:: init( & root) .unwrap ( ) ;
7205
write ( & root, " keep.txt" , b " keep\n " ) ;
7206
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7207
repo.bookmark_create ( " small" , None ) .unwrap ( ) ;
7208
for i in 0 .. 300 {
7209
write (
7210
& root,
7211
& format! ( " many/f{i} .txt" ) ,
7212
format! ( " file {i} \n " ) .as_bytes ( ) ,
7213
) ;
7214
}
7215
repo.commit ( " many" , & [ ] ) .unwrap ( ) ;
7216
let stop = std:: sync:: Arc:: new( std:: sync:: atomic:: AtomicBool:: new( false ) ) ;
7217
let watcher = {
7218
let ( root, stop) = ( root.clone ( ) , stop.clone ( ) ) ;
7219
std:: thread:: spawn( move | | {
7220
let r = Repo:: open( & root) .unwrap ( ) ;
7221
let mut ran = 0 ;
7222
while ! stop.load ( std:: sync:: atomic:: Ordering:: Relaxed) {
7223
if r.snapshot_status ( " oxy status" ) .is_ok ( ) {
7224
ran + = 1 ;
7225
}
7226
}
7227
ran
7228
} )
7229
} ;
7230
for i in 0 .. 20 {
7231
let to = if i % 2 = = 0 { " small" } else { " main" } ;
7232
repo.bookmark_switch ( to, false ) .unwrap ( ) ;
7233
}
7234
stop.store ( true , std:: sync:: atomic:: Ordering:: Relaxed) ;
7235
assert! ( watcher.join ( ) .unwrap ( ) > 0 , " the watcher snapshotted" ) ;
7236
7237
let repo = Repo:: open( & root) .unwrap ( ) ;
7238
let s = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7239
assert! ( s.statuses.is_empty ( ) , " {:?}" , statuses ( & s) ) ;
7240
assert! ( s.wc.edits.is_empty ( ) & & s.wc.tree_ops.is_empty ( ) ) ;
7241
assert_eq! ( s.wc.deps, repo.bookmark_heads ( ) .unwrap ( ) ) ;
7242
assert! ( root.join ( " many/f299.txt" ) .exists ( ) ) ;
1a3b6203 Andy McCall 1 month ago ↶
7243
}
7244
7245
7246
7247
7248
# [ test ]
7249
fn a_parent_switch_with_carry_leaves_a_subrepo_ahead_of_its_pin_alone ( ) {
7250
hermetic ( ) ;
7251
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7252
let root = dir.path ( ) ;
7253
Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
7254
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
7255
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) ) .unwrap ( ) ;
7256
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
7257
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
7258
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7259
7260
let app_dir = root.join ( " app" ) ;
7261
let mut app = Repo:: init( & app_dir) .unwrap ( ) ;
7262
write ( & app_dir, " main.txt" , b " app\n " ) ;
7263
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
7264
app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
7265
app.bookmark_create ( " feature" , None ) .unwrap ( ) ;
7266
7267
7268
let nested_dir = app_dir.join ( " libs/lib" ) ;
7269
let nested = Repo:: open( & nested_dir) .unwrap ( ) ;
7270
write ( & nested_dir, " new.txt" , b " new\n " ) ;
7271
let l2 = nested.commit ( " lib: new file" , & [ ] ) .unwrap ( ) ;
7272
drop ( nested) ;
7273
7274
let ( _ , carried) = app.bookmark_switch_carry ( " feature" ) .unwrap ( ) ;
7275
assert! ( carried.contains ( & " libs/lib" .to_string ( ) ) , " {carried:?}" ) ;
7276
let nested = Repo:: open( & nested_dir) .unwrap ( ) ;
7277
assert! (
7278
nested.bookmark_heads ( ) .unwrap ( ) .contains ( & l2) ,
7279
" its bookmark kept its work"
7280
) ;
7281
assert! ( nested_dir.join ( " new.txt" ) .exists ( ) , " and its files" ) ;
7282
let s = app.snapshot ( " status" ) .unwrap ( ) ;
7283
assert_eq! (
7284
statuses ( & s) ,
7285
vec! [ ( " libs/lib" .to_string ( ) , ChangeStatus:: Modified) ]
7286
) ;
6f8aeb99 Andy McCall 1 month ago ↶
7287
}
7d9dab13 Andy McCall 1 month ago ↶
7288
7289
7290
7291
7292
# [ test ]
7293
fn pull_takes_a_remote_move_to_changes_already_here ( ) {
7294
hermetic ( ) ;
7295
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7296
let root = dir.path ( ) ;
7297
let origin_path = root.join ( " origin" ) ;
7298
let origin = Repo:: init( & origin_path) .unwrap ( ) ;
7299
let url = origin_path.to_string_lossy ( ) .to_string ( ) ;
7300
let mut b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) ;
7301
write ( & root.join ( " b" ) , " base.txt" , b " base\n " ) ;
7302
b.commit ( " base" , & [ ] ) .unwrap ( ) ;
7303
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
7304
origin.refresh ( ) .unwrap ( ) ;
7d9dab13 Andy McCall 1 month ago ↶
7305
7306
7307
b.bookmark_create ( " feat" , None ) .unwrap ( ) ;
7308
b.bookmark_switch ( " feat" , false ) .unwrap ( ) ;
7309
write ( & root.join ( " b" ) , " x.txt" , b " x\n " ) ;
7310
let x = b.commit ( " x" , & [ ] ) .unwrap ( ) ;
7311
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
7312
origin.refresh ( ) .unwrap ( ) ;
7d9dab13 Andy McCall 1 month ago ↶
7313
b.bookmark_switch ( " main" , false ) .unwrap ( ) ;
7314
7315
7316
let feat = origin
7317
.store ( )
7318
.get_state ( & origin.view ( ) .unwrap ( ) .bookmarks[ " feat" ] )
7319
.unwrap ( )
7320
.heads;
7321
origin.bookmark_set ( " main" , & feat) .unwrap ( ) ;
7322
7323
let r = b.pull ( " origin" ) .unwrap ( ) ;
7324
assert! ( r.changed, " {r:?}" ) ;
7325
assert! ( r.heads.contains ( & x) | | b.load_state ( & r.heads) .unwrap ( ) .contains ( & x) ) ;
7326
let main = b
7327
.bookmarks ( )
7328
.unwrap ( )
7329
.into_iter ( )
7330
.find ( | m | m.name = = " main" )
7331
.unwrap ( ) ;
7332
assert_eq! ( main.behind, Some ( 0 ) , " {main:?}" ) ;
7333
}
86e1824f Andy McCall 1 month ago ↶
7334
14edbaaf Andy McCall 1 month ago ↶
7335
7336
7337
7338
7339
# [ test ]
7340
fn remote_bookmarks_are_named_switched_to_pulled_and_cloned ( ) {
7341
hermetic ( ) ;
7342
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7343
let root = dir.path ( ) ;
7344
let origin_path = root.join ( " origin" ) ;
7345
let mut origin = Repo:: init( & origin_path) .unwrap ( ) ;
7346
let url = origin_path.to_string_lossy ( ) .to_string ( ) ;
7347
let mut a = Repo:: clone( & url, & root.join ( " a" ) ) .unwrap ( ) ;
7348
write ( & root.join ( " a" ) , " base.txt" , b " base\n " ) ;
7349
a.commit ( " base" , & [ ] ) .unwrap ( ) ;
7350
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7351
a.bookmark_create ( " develop" , None ) .unwrap ( ) ;
7352
a.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
7353
write ( & root.join ( " a" ) , " base.txt" , b " base\n d1\n " ) ;
7354
let d1 = a.commit ( " d1" , & [ ] ) .unwrap ( ) ;
7355
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7356
7357
7358
let c_root = root.join ( " c" ) ;
7359
let mut c = Repo:: clone( & url, & c_root ) .unwrap ( ) ;
7360
assert_eq! ( c.config.bookmark, " main" ) ;
7361
assert! ( ! c.view ( ) .unwrap ( ) .bookmarks.contains_key ( " develop" ) ) ;
7362
assert_eq! ( c.resolve_heads ( " origin/develop" ) .unwrap ( ) , vec! [ d1] ) ;
7363
assert_eq! ( c.resolve_change ( " origin/develop" ) .unwrap ( ) , d1) ;
7364
assert_eq! ( c.remote_only_bookmarks ( ) .unwrap ( ) .len ( ) , 1 ) ;
7365
7366
7367
c.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
7368
assert_eq! ( c.config.bookmark, " develop" ) ;
7369
assert_eq! ( c.bookmark_heads ( ) .unwrap ( ) , vec! [ d1] ) ;
7370
assert_eq! ( fs:: read( c_root .join ( " base.txt" ) ) .unwrap ( ) , b " base\n d1\n " ) ;
7371
assert! ( c.remote_only_bookmarks ( ) .unwrap ( ) .is_empty ( ) ) ;
7372
assert! ( c.bookmark_switch ( " nowhere" , false ) .is_err ( ) ) ;
7373
7374
7375
write ( & root.join ( " a" ) , " base.txt" , b " base\n d1\n d2\n " ) ;
7376
let d2 = a.commit ( " d2" , & [ ] ) .unwrap ( ) ;
7377
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7378
a.bookmark_switch ( " main" , false ) .unwrap ( ) ;
7379
write ( & root.join ( " a" ) , " m.txt" , b " m\n " ) ;
7380
let m = a.commit ( " m" , & [ ] ) .unwrap ( ) ;
7381
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7382
let r = c.pull ( " origin" ) .unwrap ( ) ;
7383
assert! ( r.changed & & r.checked_out, " {r:?}" ) ;
7384
assert_eq! ( r.heads, vec! [ d2] ) ;
7385
assert_eq! ( r.others, vec! [ " main" .to_string ( ) ] ) ;
7386
assert! ( c.resolve_heads ( " main" ) .unwrap ( ) .contains ( & m) ) ;
7387
assert_eq! (
7388
fs:: read( c_root .join ( " base.txt" ) ) .unwrap ( ) ,
7389
b " base\n d1\n d2\n "
7390
) ;
7391
let again = c.pull ( " origin" ) .unwrap ( ) ;
7392
assert! ( ! again.changed & & again.others.is_empty ( ) , " {again:?}" ) ;
7393
7394
7395
origin.config.set ( " workspace.bookmark" , " develop" ) .unwrap ( ) ;
7396
origin.save_config ( ) .unwrap ( ) ;
7397
let e = Repo:: clone( & url, & root.join ( " e" ) ) .unwrap ( ) ;
7398
assert_eq! ( e.config.bookmark, " develop" ) ;
7399
assert_eq! ( e.bookmark_heads ( ) .unwrap ( ) , vec! [ d2] ) ;
7400
let reopened = Repo:: open( & root.join ( " e" ) ) .unwrap ( ) ;
7401
assert_eq! ( reopened.config.bookmark, " develop" ) ;
7402
7403
let mut e = reopened;
7404
e.bookmark_switch ( " origin/main" , false ) .unwrap ( ) ;
7405
assert_eq! ( e.config.bookmark, " main" ) ;
7406
assert! ( e.bookmark_heads ( ) .unwrap ( ) .contains ( & m) ) ;
7407
}
7408
98a1620a Andy McCall 1 month ago ↶
7409
7410
7411
7412
7413
# [ test ]
7414
fn operations_can_be_read_restored_and_reverted ( ) {
7415
hermetic ( ) ;
7416
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7417
let root = dir.path ( ) ;
7418
let repo = Repo:: init( root) .unwrap ( ) ;
7419
write ( root, " a.txt" , b " a\n " ) ;
7420
let first = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
7421
write ( root, " a.txt" , b " a\n b\n " ) ;
7422
let second = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
7423
let at_second = repo.head_op ( ) .unwrap ( ) .0. to_hex ( ) ;
7424
repo.bookmark_create ( " keep" , None ) .unwrap ( ) ;
7425
let created = repo.head_op ( ) .unwrap ( ) .0. to_hex ( ) ;
7426
write ( root, " n.txt" , b " n\n " ) ;
7427
let third = repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
7428
7429
7430
let ops = repo.op_log ( ) .unwrap ( ) ;
7431
let changes = repo.view_changes ( & ops[ 1 ] .1. view, & ops[ 0 ] .1. view) .unwrap ( ) ;
7432
assert! (
7433
changes
7434
.iter ( )
7435
.any ( | c | matches! ( c, ViewChange:: BookmarkMoved { name, .. } if name = = " main" ) ) ,
7436
" {changes:?}"
7437
) ;
7438
assert! ( changes
7439
.iter ( )
7440
.any ( | c | matches! ( c, ViewChange:: WorkingCopy { .. } ) ) ) ;
7441
7442
7443
let old = Repo:: open( root) .unwrap ( ) .at_op ( & at_second[ .. 10 ] ) .unwrap ( ) ;
7444
assert_eq! ( old.bookmark_heads ( ) .unwrap ( ) , vec! [ second] ) ;
7445
assert! ( ! old.view ( ) .unwrap ( ) .bookmarks.contains_key ( " keep" ) ) ;
7446
write ( root, " z.txt" , b " z\n " ) ;
7447
assert! ( old.commit ( " refused" , & [ ] ) .is_err ( ) , " --at-op is read-only" ) ;
7448
fs:: remove_file( root.join ( " z.txt" ) ) .unwrap ( ) ;
7449
assert! ( repo.resolve_op ( " zz" ) .is_err ( ) ) ;
7450
assert_eq! ( repo.resolve_op ( " @" ) .unwrap ( ) .0 , repo.head_op ( ) .unwrap ( ) .0 ) ;
7451
7452
7453
repo.op_restore ( & at_second, false ) .unwrap ( ) ;
7454
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ second] ) ;
7455
assert! ( ! root.join ( " n.txt" ) .exists ( ) ) ;
7456
repo.undo ( ) .unwrap ( ) ;
7457
let mut heads = repo.bookmark_heads ( ) .unwrap ( ) ;
7458
heads.sort ( ) ;
7459
let mut want = vec! [ second, third] ;
7460
want.sort ( ) ;
7461
assert_eq! ( heads, want) ;
7462
assert! (
7463
root.join ( " n.txt" ) .exists ( ) ,
7464
" the files came back with the undo"
7465
) ;
7466
7467
7468
let r = repo.op_revert ( & created) .unwrap ( ) ;
7469
assert_eq! ( r.reverted, vec! [ " keep" .to_string ( ) ] ) ;
7470
assert! ( ! repo.view ( ) .unwrap ( ) .bookmarks.contains_key ( " keep" ) ) ;
7471
assert! ( repo
7472
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
7473
.unwrap ( )
7474
.contains ( & third) ) ;
7475
assert! ( repo.op_revert ( & created) .is_err ( ) , " nothing left to revert" ) ;
7476
let _ = first;
7477
}
7478
36041796 Andy McCall 1 month ago ↶
7479
7480
7481
7482
# [ test ]
7483
fn status_with_uncommitted_work_comes_from_the_cache_until_a_file_changes ( ) {
7484
hermetic ( ) ;
7485
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7486
let root = dir.path ( ) ;
7487
let repo = Repo:: init( root) .unwrap ( ) ;
7488
write ( root, " a.txt" , b " a\n " ) ;
7489
write ( root, " b.txt" , b " b\n " ) ;
7490
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7491
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , Some ( vec! [ ] ) , " clean" ) ;
7492
7493
write ( root, " a.txt" , b " a\n more\n " ) ;
7494
assert_eq! (
7495
repo.status_by_manifest ( ) .unwrap ( ) ,
7496
None ,
7497
" no snapshot of the edit yet"
7498
) ;
7499
let snap = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7500
assert_eq! ( snap.statuses.len ( ) , 1 ) ;
7501
let cached = repo
7502
.status_by_manifest ( )
7503
.unwrap ( )
7504
.expect ( " answered from the cache" ) ;
7505
assert_eq! ( cached.len ( ) , 1 ) ;
7506
assert_eq! (
7507
( cached[ 0 ] .path.as_str ( ) , cached[ 0 ] .status) ,
7508
( " a.txt" , ChangeStatus:: Modified)
7509
) ;
7510
7511
7512
write ( root, " a.txt" , b " a\n more\n and more\n " ) ;
7513
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None , " edited again" ) ;
7514
repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7515
assert! ( repo.status_by_manifest ( ) .unwrap ( ) .is_some ( ) ) ;
7516
write ( root, " new.txt" , b " n\n " ) ;
7517
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None , " a file added" ) ;
7518
fs:: remove_file( root.join ( " new.txt" ) ) .unwrap ( ) ;
7519
assert! (
7520
repo.status_by_manifest ( ) .unwrap ( ) .is_some ( ) ,
7521
" back as it was"
7522
) ;
7523
fs:: remove_file( root.join ( " b.txt" ) ) .unwrap ( ) ;
7524
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None , " a file removed" ) ;
7525
write ( root, " b.txt" , b " b\n " ) ;
7526
7527
7528
repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7529
repo.commit ( " more" , & [ ] ) .unwrap ( ) ;
7530
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , Some ( vec! [ ] ) ) ;
7531
}
7532
77445673 Andy McCall 1 month ago ↶
7533
7534
7535
7536
# [ test ]
7537
fn the_working_copy_diff_from_the_cache_matches_a_snapshot ( ) {
7538
hermetic ( ) ;
7539
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7540
let root = dir.path ( ) ;
7541
let repo = Repo:: init( root) .unwrap ( ) ;
7542
write ( root, " a.txt" , b " one\n two\n three\n " ) ;
7543
write ( root, " gone.txt" , b " bye\n " ) ;
7544
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7545
write ( root, " a.txt" , b " one\n 2\n three\n four\n " ) ;
7546
write ( root, " new.txt" , b " hello\n " ) ;
7547
fs:: remove_file( root.join ( " gone.txt" ) ) .unwrap ( ) ;
7548
let snap = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7549
let slow = render_change ( & snap.wc, & snap.base, & snap.base_snapshot) ;
7550
assert! (
7551
repo.status_by_manifest ( ) .unwrap ( ) .is_some ( ) ,
7552
" the cache holds"
7553
) ;
7554
let ( wc, base, snapshot) = repo.working_copy_with_base ( ) .unwrap ( ) ;
7555
assert_eq! ( render_change ( & wc, & base, & snapshot) , slow) ;
7556
assert_eq! ( repo.diff_files ( None ) .unwrap ( ) .len ( ) , 3 ) ;
7557
}
7558
1ec3f008 Andy McCall 1 month ago ↶
7559
7560
7561
7562
7563
# [ test ]
7564
fn the_manifest_check_skips_listing_while_no_directory_changed ( ) {
7565
hermetic ( ) ;
7566
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7567
let root = dir.path ( ) ;
7568
let repo = Repo:: init( root) .unwrap ( ) ;
7569
write ( root, " a.txt" , b " a\n " ) ;
7570
write ( root, " sub/deep/b.txt" , b " b\n " ) ;
7571
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7572
7573
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 60 ) ;
7574
for p in [ " a.txt" , " sub/deep/b.txt" , " sub/deep" , " sub" , " " ] {
7575
fs:: File:: open( root.join ( p) )
7576
.unwrap ( )
7577
.set_modified ( old)
7578
.unwrap ( ) ;
7579
}
7580
repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7581
repo.write_manifest ( false , false ) .unwrap ( ) ;
7582
let m = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) .2 ;
7583
assert! ( m.dirs.iter ( ) .any ( | ( d , _) | d = = " sub/deep" ) , " {:?}" , m.dirs) ;
7584
assert_eq! (
7585
repo.disk_differs_by_dirs ( & m) ,
7586
Some ( false ) ,
7587
" no listing needed"
7588
) ;
7589
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , Some ( vec! [ ] ) ) ;
7590
7591
7592
write ( root, " sub/deep/b.txt" , b " c\n " ) ;
7593
assert_eq! ( repo.disk_differs_by_dirs ( & m) , Some ( true ) ) ;
7594
write ( root, " sub/deep/b.txt" , b " b\n " ) ;
7595
fs:: File:: open( root.join ( " sub/deep/b.txt" ) )
7596
.unwrap ( )
7597
.set_modified ( old)
7598
.unwrap ( ) ;
7599
assert_eq! ( repo.disk_differs_by_dirs ( & m) , Some ( false ) ) ;
7600
7601
7602
write ( root, " sub/deep/new.txt" , b " n\n " ) ;
7603
assert_eq! ( repo.disk_differs_by_dirs ( & m) , None ) ;
7604
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None ) ;
7605
fs:: remove_file( root.join ( " sub/deep/new.txt" ) ) .unwrap ( ) ;
7606
7607
fs:: remove_file( root.join ( " a.txt" ) ) .unwrap ( ) ;
7608
assert_eq! ( repo.disk_differs_by_dirs ( & m) , None ) ;
7609
}
7610
e226a6cc Andy McCall 1 month ago ↶
7611
7612
7613
7614
# [ test ]
7615
fn a_commit_after_a_status_uses_the_cached_snapshot ( ) {
7616
hermetic ( ) ;
7617
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7618
let root = dir.path ( ) ;
7619
let repo = Repo:: init( root) .unwrap ( ) ;
7620
write ( root, " a.txt" , b " a\n b\n " ) ;
7621
write ( root, " .oxyattributes" , b " *.bin binary\n " ) ;
7622
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7623
write ( root, " a.txt" , b " a\n B\n " ) ;
7624
write ( root, " new.txt" , b " new\n " ) ;
7625
let snap = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7626
let cached = repo
7627
.snapshot_from_cache ( )
7628
.unwrap ( )
7629
.expect ( " the cache holds" ) ;
7630
assert_eq! ( cached.wc_hash, snap.wc_hash) ;
7631
assert_eq! ( cached.statuses, snap.statuses) ;
7632
assert! (
7633
! cached.attributes.is_empty ( ) ,
7634
" the .oxyattributes file was read"
7635
) ;
7636
let h = repo.commit ( " edit" , & [ ] ) .unwrap ( ) ;
7637
let c = repo.store ( ) .get_change ( & h) .unwrap ( ) ;
7638
assert_eq! (
7639
c.tree_ops.len ( ) + c.edits.len ( ) ,
7640
snap.wc.tree_ops.len ( ) + snap.wc.edits.len ( )
7641
) ;
7642
assert_eq! (
7643
repo.status_by_manifest ( ) .unwrap ( ) ,
7644
Some ( vec! [ ] ) ,
7645
" clean after the commit"
7646
) ;
7647
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " a\n B\n " ) ;
7648
}
7649
0389c3e8 Andy McCall 1 month ago ↶
7650
7651
7652
7653
7654
# [ test ]
7655
fn a_verified_clean_check_restamps_the_manifest_safely ( ) {
7656
hermetic ( ) ;
7657
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7658
let root = dir.path ( ) ;
7659
let repo = Repo:: init( root) .unwrap ( ) ;
7660
write ( root, " a.txt" , b " one\n " ) ;
7661
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
7662
let before = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) .2. written_ns;
7663
assert! ( repo.clean_by_manifest ( ) .unwrap ( ) ) ;
7664
let after = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) .2. written_ns;
7665
assert! ( after > before, " restamped" ) ;
7666
write ( root, " a.txt" , b " two\n " ) ;
7667
assert! (
7668
! repo.clean_by_manifest ( ) .unwrap ( ) ,
7669
" a same-size rewrite is still seen"
7670
) ;
7671
}
7672
86e1824f Andy McCall 1 month ago ↶
7673
7674
7675
7676
# [ test ]
7677
fn history_only_states_read_text_only_for_the_graphs_they_build ( ) {
7678
hermetic ( ) ;
7679
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7680
let root = dir.path ( ) ;
7681
let repo = Repo:: init( root) .unwrap ( ) ;
7682
let big: String = ( 0 .. 3000 ) .map ( | i | format! ( " big line {i} \n " ) ) .collect ( ) ;
7683
let other: String = ( 0 .. 3000 ) .map ( | i | format! ( " other line {i} \n " ) ) .collect ( ) ;
7684
write ( root, " big.txt" , big.as_bytes ( ) ) ;
7685
write ( root, " other.txt" , other.as_bytes ( ) ) ;
7686
let h = repo.commit ( " two large files" , & [ ] ) .unwrap ( ) ;
7687
write ( root, " big.txt" , format! ( " {big} one more\n " ) .as_bytes ( ) ) ;
7688
let h2 = repo.commit ( " append" , & [ ] ) .unwrap ( ) ;
7689
7690
let full = repo.load_state_full ( & [ h2] ) .unwrap ( ) .materialize ( ) ;
7691
let text = | snap : & crate:: state:: Snapshot, p : & str | match & snap.files[ p] .content {
7692
FileContent:: Text { bytes, .. } => bytes.clone ( ) ,
7693
other => panic! ( " {other:?}" ) ,
7694
} ;
7695
7696
let mut hist = repo.load_state_history ( & [ h2] ) .unwrap ( ) ;
7697
let big_node = full.files[ " big.txt" ] .node;
7698
assert! ( hist.graph_key ( & big_node) .is_some ( ) , " not built yet" ) ;
7699
repo.load_all_graphs ( & mut hist) .unwrap ( ) ;
7700
let rendered = hist.materialize ( ) ;
7701
for p in [ " big.txt" , " other.txt" ] {
7702
assert_eq! ( text ( & rendered, p) , text ( & full, p) , " {p}" ) ;
7703
}
7704
7705
7706
7707
let ( _ , texts) = repo.store ( ) .get_change_lazy ( & h) .unwrap ( ) ;
7708
assert_eq! ( texts.len ( ) , 2 ) ;
7709
let content = | r : & ObjectRef| -> Vec< u8 > {
7710
let blob = crate :: objects:: Blob:: decode( & repo.store ( ) .get ( r) .unwrap ( ) .1 ) .unwrap ( ) ;
7711
blob.chunks
7712
.iter ( )
7713
.flat_map ( | c | repo.store ( ) .get ( c) .unwrap ( ) .1 )
7714
.collect ( )
7715
} ;
7716
let other_text = texts
7717
.iter ( )
a4fad5b0 Andy McCall 1 month ago ↶
7718
.filter_map ( | t | match t {
0f93f49c Andy McCall 1 month ago ↶
7719
crate :: objects:: TextRef:: Blob( r, _ , _ ) => Some ( * r) ,
a4fad5b0 Andy McCall 1 month ago ↶
7720
_ => None ,
7721
} )
86e1824f Andy McCall 1 month ago ↶
7722
.find ( | r | content ( r) .windows ( 12 ) .any ( | w | w = = b " other line 1" ) )
7723
.unwrap ( ) ;
64818938 Andy McCall 1 month ago ↶
7724
repo.store ( ) .remove_loose_for_test ( & other_text) .unwrap ( ) ;
86e1824f Andy McCall 1 month ago ↶
7725
let mut hist = repo.load_state_history ( & [ h2] ) .unwrap ( ) ;
7726
repo.load_graphs_of ( & mut hist, & [ big_node] ) .unwrap ( ) ;
7727
assert! ( repo
7728
.load_graphs_of ( & mut hist, & [ full.files[ " other.txt" ] .node] )
7729
.is_err ( ) ) ;
7730
}
7731
7732
7733
7734
7735
# [ test ]
7736
fn a_commit_leaves_a_manifest_the_next_status_trusts ( ) {
7737
use std:: os:: unix:: fs:: PermissionsExt;
7738
hermetic ( ) ;
7739
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7740
let root = dir.path ( ) ;
7741
let repo = Repo:: init( root) .unwrap ( ) ;
7742
let files = [ " a.txt" , " b.txt" , " d/c.txt" ] ;
7743
for p in files {
7744
write ( root, p, format! ( " {p} \n " ) .as_bytes ( ) ) ;
7745
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
7746
fs:: File:: options( )
7747
.write ( true )
7748
.open ( root.join ( p) )
7749
.unwrap ( )
7750
.set_modified ( old)
7751
.unwrap ( ) ;
7752
}
7753
repo.commit ( " added" , & [ ] ) .unwrap ( ) ;
7754
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
7755
assert! (
7756
m.entries.values ( ) .all ( | e | e.graph.is_some ( ) ) ,
7757
" keys recorded at commit"
7758
) ;
7759
7760
for p in files {
7761
fs:: set_permissions( root.join ( p) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
7762
}
7763
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
7764
for p in files {
7765
fs:: set_permissions( root.join ( p) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
7766
}
7767
assert! ( ! snap.changed & & snap.statuses.is_empty ( ) ) ;
7768
assert_eq! ( snap.base_snapshot.unrendered.len ( ) , files.len ( ) ) ;
7769
}
c5ce0367 Andy McCall 1 month ago ↶
7770
7771
7772
7773
# [ test ]
7774
fn checkout_touches_only_what_differs_and_renders_in_batches ( ) {
7775
use std:: os:: unix:: fs:: PermissionsExt;
7776
hermetic ( ) ;
7777
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7778
let root = dir.path ( ) ;
7779
let origin = root.join ( " origin" ) ;
7780
let mut repo = Repo:: init( & origin) .unwrap ( ) ;
7781
7782
let n = 2100 ;
7783
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
7784
for i in 0 .. n {
7785
let p = format! ( " f/{i:04} .txt" ) ;
7786
write ( & origin, & p, format! ( " file {i} \n " ) .as_bytes ( ) ) ;
7787
fs:: File:: options( )
7788
.write ( true )
7789
.open ( origin.join ( & p) )
7790
.unwrap ( )
7791
.set_modified ( old)
7792
.unwrap ( ) ;
7793
}
7794
repo.commit ( " many" , & [ ] ) .unwrap ( ) ;
7795
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
7796
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
7797
write ( & origin, " f/0007.txt" , b " changed\n " ) ;
7798
repo.commit ( " one change" , & [ ] ) .unwrap ( ) ;
7799
7800
7801
for i in 0 .. n {
7802
if i ! = 7 {
7803
let p = origin.join ( format! ( " f/{i:04} .txt" ) ) ;
7804
fs:: set_permissions( & p, fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
7805
}
7806
}
7807
let r = repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
7808
for i in 0 .. n {
7809
let p = origin.join ( format! ( " f/{i:04} .txt" ) ) ;
7810
fs:: set_permissions( & p, fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
7811
}
7812
assert_eq! ( ( r.written, r.removed) , ( 1 , 0 ) , " {r:?}" ) ;
7813
assert_eq! ( fs:: read( origin.join ( " f/0007.txt" ) ) .unwrap ( ) , b " file 7\n " ) ;
7814
assert! ( repo.snapshot_status ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
7815
7816
7817
let c = root.join ( " clone" ) ;
7818
Repo:: clone( & origin.to_string_lossy ( ) , & c) .unwrap ( ) ;
7819
for i in [ 0 , 2047 , 2048 , n - 1 ] {
7820
assert_eq! (
7821
fs:: read( c.join ( format! ( " f/{i:04} .txt" ) ) ) .unwrap ( ) ,
7822
format! ( " file {i} \n " ) .as_bytes ( )
7823
) ;
7824
}
7825
let cloned = Repo:: open( & c) .unwrap ( ) ;
7826
assert! ( cloned
7827
.snapshot_status ( " status" )
7828
.unwrap ( )
7829
.statuses
7830
.is_empty ( ) ) ;
d3cd7ef9 Andy McCall 1 month ago ↶
7831
}
7832
7833
7834
7835
7836
# [ test ]
7837
fn a_racy_manifest_entry_is_checked_by_hash ( ) {
7838
hermetic ( ) ;
7839
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7840
let root = dir.path ( ) ;
7841
let repo = Repo:: init( root) .unwrap ( ) ;
7842
write ( root, " a.txt" , b " one\n " ) ;
7843
repo.commit ( " fresh" , & [ ] ) .unwrap ( ) ;
7844
assert! ( repo.clean_by_manifest ( ) .unwrap ( ) , " fresh but equal" ) ;
7845
let t = fs:: metadata( root.join ( " a.txt" ) )
7846
.unwrap ( )
7847
.modified ( )
7848
.unwrap ( ) ;
7849
write ( root, " a.txt" , b " two\n " ) ;
7850
fs:: File:: options( )
7851
.write ( true )
7852
.open ( root.join ( " a.txt" ) )
7853
.unwrap ( )
7854
.set_modified ( t)
7855
.unwrap ( ) ;
7856
assert! (
7857
! repo.clean_by_manifest ( ) .unwrap ( ) ,
7858
" same size and time, other bytes"
7859
) ;
7860
assert_eq! (
7861
statuses ( & repo.snapshot_status ( " status" ) .unwrap ( ) ) ,
7862
vec! [ ( " a.txt" .to_string ( ) , ChangeStatus:: Modified) ]
7863
) ;
c5ce0367 Andy McCall 1 month ago ↶
7864
}
2c75672f Andy McCall 1 month ago ↶
7865
7866
7867
7868
# [ test ]
7869
fn a_raw_bundle_without_a_changes_text_is_refused ( ) {
7870
hermetic ( ) ;
7871
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7872
let root = dir.path ( ) ;
7873
let a = Repo:: init( & root.join ( " a" ) ) .unwrap ( ) ;
7874
let big: String = ( 0 .. 3000 ) .map ( | i | format! ( " line {i} \n " ) ) .collect ( ) ;
7875
write ( & root.join ( " a" ) , " big.txt" , big.as_bytes ( ) ) ;
7876
let h = a.commit ( " big" , & [ ] ) .unwrap ( ) ;
7877
let bundle = a.bundle_raw_for ( & [ h] , None ) .unwrap ( ) ;
7878
assert_eq! ( bundle.raw_changes.len ( ) , 1 ) ;
7879
assert! ( bundle.changes.is_empty ( ) ) ;
7880
let b = Repo:: init( & root.join ( " b" ) ) .unwrap ( ) ;
7881
let mut without = bundle.clone ( ) ;
7882
without.objects.retain ( | ( ty , _) | * ty ! = ObjectType:: Blob) ;
7883
let e = b.import_bundle ( & without, false ) .unwrap_err ( ) ;
7884
assert! ( e.to_string ( ) .contains ( " came without its text blob" ) , " {e}" ) ;
7885
assert! ( ! b.store ( ) .has ( & h) ) ;
7886
assert_eq! ( b.import_bundle ( & bundle, false ) .unwrap ( ) , vec! [ h] ) ;
7887
assert_eq! ( b.store ( ) .get_change ( & h) .unwrap ( ) .message, " big" ) ;
7888
}
a87c7b9d Andy McCall 1 month ago ↶
7889
7890
7891
7892
7893
7894
# [ test ]
7895
fn git_import_brings_a_directory_back_rather_than_making_a_second ( ) {
7896
hermetic ( ) ;
7897
if std:: process:: Command:: new( " git" )
7898
.arg ( " --version" )
7899
.output ( )
7900
.is_err ( )
7901
{
7902
eprintln! ( " git not installed; skipping" ) ;
7903
return ;
7904
}
7905
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7906
let root = dir.path ( ) ;
7907
let g = root.join ( " src.git" ) ;
7908
fs:: create_dir_all( & g) .unwrap ( ) ;
7909
let git = | args : & [& str ]| -> String {
7910
let out = std:: process:: Command:: new( " git" )
7911
.arg ( " -C" )
7912
.arg ( & g)
7913
.args ( [
7914
" -c" ,
7915
" user.name=Grace" ,
7916
" -c" ,
7917
" [email protected] " ,
7918
" -c" ,
7919
" commit.gpgsign=false" ,
7920
] )
7921
.args ( args)
7922
.output ( )
7923
.unwrap ( ) ;
7924
assert! (
7925
out.status.success ( ) ,
7926
" git {args:?}: {}" ,
7927
String :: from_utf8_lossy( & out.stderr)
7928
) ;
7929
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
7930
} ;
7931
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
7932
write ( & g, " d/a.txt" , b " a\n " ) ;
7933
write ( & g, " c/x.txt" , b " x\n " ) ;
7934
git ( & [ " add" , " ." ] ) ;
7935
git ( & [ " commit" , " -qm" , " base" ] ) ;
7936
7937
git ( & [ " checkout" , " -qb" , " side" ] ) ;
7938
git ( & [ " mv" , " c/x.txt" , " d/x.txt" ] ) ;
7939
git ( & [ " commit" , " -qm" , " x into d" ] ) ;
7940
7941
git ( & [ " checkout" , " -q" , " main" ] ) ;
7942
git ( & [ " rm" , " -q" , " d/a.txt" ] ) ;
7943
git ( & [ " commit" , " -qm" , " d goes" ] ) ;
7944
write ( & g, " d/b.txt" , b " b\n " ) ;
7945
git ( & [ " add" , " ." ] ) ;
7946
git ( & [ " commit" , " -qm" , " d comes back" ] ) ;
7947
git ( & [ " mv" , " c/x.txt" , " d/x.txt" ] ) ;
7948
git ( & [ " commit" , " -qm" , " x into d again" ] ) ;
7949
git ( & [ " merge" , " -q" , " --no-edit" , " side" ] ) ;
7950
7951
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
7952
repo.import_git ( & g, " main" ) .unwrap ( ) ;
7953
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
7954
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
7955
let st = repo
7956
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
7957
.unwrap ( ) ;
7958
let view = st.tree ( ) .view ( ) ;
7959
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
7960
7961
7962
let first = repo
7963
.log ( )
7964
.unwrap ( )
7965
.into_iter ( )
7966
.find ( | e | e.change.message.starts_with ( " base" ) )
7967
.unwrap ( ) ;
7968
let base = repo
7969
.load_state_history ( & [ first.hash] )
7970
.unwrap ( )
7971
.tree ( )
7972
.view ( ) ;
7973
assert_eq! ( view.entries[ " d" ] .node, base.entries[ " d" ] .node) ;
7974
}
7975
7976
7977
7978
7979
# [ test ]
7980
fn git_import_merge_retires_a_placement_under_a_removed_directory ( ) {
7981
hermetic ( ) ;
7982
if std:: process:: Command:: new( " git" )
7983
.arg ( " --version" )
7984
.output ( )
7985
.is_err ( )
7986
{
7987
eprintln! ( " git not installed; skipping" ) ;
7988
return ;
7989
}
7990
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7991
let root = dir.path ( ) ;
7992
let g = root.join ( " src.git" ) ;
7993
fs:: create_dir_all( & g) .unwrap ( ) ;
7994
let run = | args : & [& str ]| {
7995
std:: process:: Command:: new( " git" )
7996
.arg ( " -C" )
7997
.arg ( & g)
7998
.args ( [
7999
" -c" ,
8000
" user.name=Grace" ,
8001
" -c" ,
8002
" [email protected] " ,
8003
" -c" ,
8004
" commit.gpgsign=false" ,
8005
] )
8006
.args ( args)
8007
.output ( )
8008
.unwrap ( )
8009
} ;
8010
let git = | args : & [& str ]| {
8011
let out = run ( args) ;
8012
assert! (
8013
out.status.success ( ) ,
8014
" git {args:?}: {}" ,
8015
String :: from_utf8_lossy( & out.stderr)
8016
) ;
8017
} ;
8018
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
8019
write ( & g, " c/x.txt" , b " x\n " ) ;
8020
write ( & g, " keep.txt" , b " k\n " ) ;
8021
git ( & [ " add" , " ." ] ) ;
8022
git ( & [ " commit" , " -qm" , " base" ] ) ;
8023
8024
8025
git ( & [ " checkout" , " -qb" , " side" ] ) ;
8026
git ( & [ " mv" , " c/x.txt" , " c/y.txt" ] ) ;
8027
git ( & [ " commit" , " -qm" , " rename within c" ] ) ;
8028
git ( & [ " checkout" , " -q" , " main" ] ) ;
8029
fs:: create_dir_all( g.join ( " n" ) ) .unwrap ( ) ;
8030
git ( & [ " mv" , " c/x.txt" , " n/x.txt" ] ) ;
8031
git ( & [ " commit" , " -qm" , " move out of c" ] ) ;
8032
8033
let _ = run ( & [ " merge" , " -q" , " side" ] ) ;
8034
let _ = run ( & [
8035
" rm" ,
8036
" -q" ,
8037
" --cached" ,
8038
" --ignore-unmatch" ,
8039
" c/y.txt" ,
8040
" c/x.txt" ,
8041
] ) ;
8042
let _ = std:: fs:: remove_dir_all( g.join ( " c" ) ) ;
8043
write ( & g, " n/x.txt" , b " x\n " ) ;
8044
git ( & [ " add" , " -A" ] ) ;
8045
git ( & [ " commit" , " -qm" , " merge: keep n/x.txt" ] ) ;
8046
8047
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
8048
repo.import_git ( & g, " main" ) .unwrap ( ) ;
8049
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
8050
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
8051
let st = repo
8052
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
8053
.unwrap ( ) ;
8054
let view = st.tree ( ) .view ( ) ;
8055
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
8056
assert! ( view.entries.contains_key ( " n/x.txt" ) ) ;
8057
}
8058
8059
8060
8061
8062
# [ test ]
8063
fn git_import_merge_leaves_no_orphan_behind ( ) {
8064
hermetic ( ) ;
8065
if std:: process:: Command:: new( " git" )
8066
.arg ( " --version" )
8067
.output ( )
8068
.is_err ( )
8069
{
8070
eprintln! ( " git not installed; skipping" ) ;
8071
return ;
8072
}
8073
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8074
let root = dir.path ( ) ;
8075
let g = root.join ( " src.git" ) ;
8076
fs:: create_dir_all( & g) .unwrap ( ) ;
8077
let git = | args : & [& str ]| {
8078
let out = std:: process:: Command:: new( " git" )
8079
.arg ( " -C" )
8080
.arg ( & g)
8081
.args ( [
8082
" -c" ,
8083
" user.name=Grace" ,
8084
" -c" ,
8085
" [email protected] " ,
8086
" -c" ,
8087
" commit.gpgsign=false" ,
8088
] )
8089
.args ( args)
8090
.output ( )
8091
.unwrap ( ) ;
8092
assert! (
8093
out.status.success ( ) ,
8094
" git {args:?}: {}" ,
8095
String :: from_utf8_lossy( & out.stderr)
8096
) ;
8097
} ;
8098
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
8099
write ( & g, " d/f.txt" , b " f\n " ) ;
8100
write ( & g, " keep.txt" , b " k\n " ) ;
8101
git ( & [ " add" , " ." ] ) ;
8102
git ( & [ " commit" , " -qm" , " base" ] ) ;
8103
git ( & [ " checkout" , " -qb" , " side" ] ) ;
8104
write ( & g, " d/new.txt" , b " new\n " ) ;
8105
git ( & [ " add" , " ." ] ) ;
8106
git ( & [ " commit" , " -qm" , " add into d" ] ) ;
8107
git ( & [ " checkout" , " -q" , " main" ] ) ;
8108
git ( & [ " rm" , " -q" , " d/f.txt" ] ) ;
8109
git ( & [ " commit" , " -qm" , " d goes" ] ) ;
8110
git ( & [ " merge" , " -q" , " --no-edit" , " side" ] ) ;
8111
8112
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
8113
repo.import_git ( & g, " main" ) .unwrap ( ) ;
8114
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
8115
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
8116
let st = repo
8117
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
8118
.unwrap ( ) ;
8119
let view = st.tree ( ) .view ( ) ;
8120
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
8121
assert! ( view.entries.contains_key ( " d/new.txt" ) ) ;
8122
}
d77e1702 Andy McCall 1 month ago ↶
8123
8124
8125
8126
# [ test ]
8127
fn fsck_names_a_missing_text_blob ( ) {
8128
hermetic ( ) ;
8129
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8130
let root = dir.path ( ) ;
8131
let repo = Repo:: init( root) .unwrap ( ) ;
8132
let big: String = ( 0 .. 3000 ) .map ( | i | format! ( " line {i} \n " ) ) .collect ( ) ;
8133
write ( root, " big.txt" , big.as_bytes ( ) ) ;
8134
let h = repo.commit ( " big" , & [ ] ) .unwrap ( ) ;
8135
assert_eq! ( repo.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
8136
let ( _ , texts) = repo.store ( ) .get_change_lazy ( & h) .unwrap ( ) ;
8137
let Some ( crate :: objects:: TextRef:: Blob( b, _ , _ ) ) = texts.first ( ) .copied ( ) else {
8138
panic! ( " a stored text" )
8139
} ;
64818938 Andy McCall 1 month ago ↶
8140
repo.store ( ) .remove_loose_for_test ( & b) .unwrap ( ) ;
d77e1702 Andy McCall 1 month ago ↶
8141
let rep = repo.fsck ( ) .unwrap ( ) ;
8142
assert! ( rep.errors ( ) > 0 ) ;
8143
assert! (
8144
rep.problems.iter ( ) .any ( | p | p.what.contains ( & h.short ( 12 ) ) ) ,
8145
" {:?}" ,
8146
rep.problems
8147
) ;
8148
}
a07f60a3 Andy McCall 1 month ago ↶
8149
8150
8151
8152
8153
# [ test ]
8154
fn a_subrepo_on_the_parents_hub_borrows_its_credentials ( ) {
8155
hermetic ( ) ;
8156
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8157
let mut repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
8158
repo.config
8159
.set (
8160
" remote.origin.url" ,
8161
" https://Hub.example:7343/doomsdayone/metalgear" ,
8162
)
8163
.unwrap ( ) ;
8164
repo.config.set ( " remote.origin.token" , " secret" ) .unwrap ( ) ;
8165
repo.config
8166
.set ( " remote.origin.ca" , " /tmp/hub-ca.pem" )
8167
.unwrap ( ) ;
8168
let same = repo.credentials_for ( " https://hub.example:7343/doomsdayone/rstar-api" ) ;
8169
assert_eq! ( same.token.as_deref ( ) , Some ( " secret" ) ) ;
8170
assert_eq! ( same.ca.as_deref ( ) , Some ( Path:: new( " /tmp/hub-ca.pem" ) ) ) ;
8171
for other in [
8172
" https://elsewhere.example:7343/doomsdayone/rstar-api" ,
8173
" https://hub.example/doomsdayone/rstar-api" ,
8174
" http://hub.example:7343/doomsdayone/rstar-api" ,
8175
] {
8176
let o = repo.credentials_for ( other) ;
8177
assert_ne! ( o.token.as_deref ( ) , Some ( " secret" ) , " {other}" ) ;
8178
}
8179
}
ce14f0d6 Andy McCall 1 month ago ↶
8180
ff1f62fe Andy McCall 1 month ago ↶
8181
8182
8183
ce14f0d6 Andy McCall 1 month ago ↶
8184
# [ test ]
ff1f62fe Andy McCall 1 month ago ↶
8185
fn remotes_use_the_credential_store_and_the_enclosing_checkout ( ) {
8186
hermetic ( ) ;
8187
let home = tempfile:: tempdir( ) .unwrap ( ) ;
8188
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8189
let mut parent = Repo:: init( dir.path ( ) ) .unwrap ( ) .with_home ( home.path ( ) ) ;
8190
parent
8191
.config
8192
.set ( " remote.origin.url" , " https://hub.example/o/parent" )
8193
.unwrap ( ) ;
8194
parent.save_config ( ) .unwrap ( ) ;
8195
assert_eq! ( parent.remote_options ( " origin" ) .token, None ) ;
8196
crate :: credentials:: Credentials:: at( home.path ( ) )
8197
.save ( " https://hub.example" , " stored" , Some ( " andy" ) , None )
8198
.unwrap ( ) ;
8199
assert_eq! (
8200
parent.remote_options ( " origin" ) .token.as_deref ( ) ,
8201
Some ( " stored" )
8202
) ;
8203
8204
let empty_home = tempfile:: tempdir( ) .unwrap ( ) ;
8205
let nested = dir.path ( ) .join ( " deps" ) .join ( " child" ) ;
8206
let mut child = Repo:: init( & nested) .unwrap ( ) .with_home ( empty_home.path ( ) ) ;
8207
child
8208
.config
8209
.set ( " remote.origin.url" , " https://hub.example/o/child" )
8210
.unwrap ( ) ;
8211
child.save_config ( ) .unwrap ( ) ;
8212
assert_eq! ( child.remote_options ( " origin" ) .token, None ) ;
8213
parent
8214
.config
8215
.set ( " remote.origin.token" , " checkout" )
8216
.unwrap ( ) ;
8217
parent.save_config ( ) .unwrap ( ) ;
8218
assert_eq! (
8219
child.remote_options ( " origin" ) .token.as_deref ( ) ,
8220
Some ( " checkout" )
8221
) ;
8222
8223
child
8224
.config
8225
.set ( " remote.origin.url" , " https://other.example/o/child" )
8226
.unwrap ( ) ;
8227
assert_eq! ( child.remote_options ( " origin" ) .token, None ) ;
8228
}
8229
8230
# [ test ]
ce14f0d6 Andy McCall 1 month ago ↶
8231
fn the_metadata_cache_survives_a_torn_tail ( ) {
8232
hermetic ( ) ;
8233
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8234
let root = dir.path ( ) ;
8235
let repo = Repo:: init( root) .unwrap ( ) ;
8236
write ( root, " a.txt" , b " 1\n " ) ;
8237
let h = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8238
let meta = repo.store ( ) .get_change_meta ( & h) .unwrap ( ) ;
8239
let cache = repo.store ( ) .dir ( ) .join ( " metas" ) ;
8240
let len = fs:: metadata( & cache) .unwrap ( ) .len ( ) ;
8241
assert! ( len > 0 , " a change read is cached" ) ;
8242
8243
fs:: OpenOptions:: new( )
8244
.append ( true )
8245
.open ( & cache)
8246
.unwrap ( )
8247
.write_all ( & [ 7 ; 20 ] )
8248
.unwrap ( ) ;
8249
let again = Repo:: open( root) .unwrap ( ) ;
8250
assert_eq! ( again.store ( ) .get_change_meta ( & h) .unwrap ( ) , meta) ;
8251
assert_eq! ( fs:: metadata( & cache) .unwrap ( ) .len ( ) , len) ;
8252
}
f9466021 Andy McCall 1 month ago ↶
8253
8254
# [ test ]
8255
fn a_manifest_that_does_not_check_reads_as_none ( ) {
8256
hermetic ( ) ;
8257
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8258
let root = dir.path ( ) ;
8259
let repo = Repo:: init( root) .unwrap ( ) ;
8260
write ( root, " a.txt" , b " 1\n " ) ;
8261
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8262
let path = repo.manifest_path ( ) ;
8263
let mut bytes = fs:: read( & path) .unwrap ( ) ;
8264
assert! ( Repo:: open( root) .unwrap ( ) .read_manifest ( ) .unwrap ( ) .is_some ( ) ) ;
8265
let last = bytes.len ( ) - 1 ;
8266
bytes[ last] ^ = 1 ;
8267
fs:: write( & path, & bytes) .unwrap ( ) ;
8268
assert! ( Repo:: open( root) .unwrap ( ) .read_manifest ( ) .unwrap ( ) .is_none ( ) ) ;
8269
8270
let again = Repo:: open( root) .unwrap ( ) ;
8271
assert! ( ! again.snapshot ( " status" ) .unwrap ( ) .changed) ;
8272
assert! ( again.read_manifest ( ) .unwrap ( ) .is_some ( ) ) ;
8273
}
8274
8275
# [ test ]
8276
fn a_commit_leaves_a_manifest_that_proves_the_working_copy_clean ( ) {
8277
hermetic ( ) ;
8278
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8279
let root = dir.path ( ) ;
8280
let repo = Repo:: init( root) .unwrap ( ) ;
8281
write ( root, " a.txt" , b " 1\n " ) ;
8282
write ( root, " b.txt" , b " 2\n " ) ;
8283
8284
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 60 ) ;
8285
fs:: File:: options( )
8286
.write ( true )
8287
.open ( root.join ( " b.txt" ) )
8288
.unwrap ( )
8289
.set_modified ( old)
8290
.unwrap ( ) ;
8291
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8292
assert! ( repo.clean_by_manifest ( ) .unwrap ( ) , " after the first commit" ) ;
8293
repo.snapshot_status ( " status" ) .unwrap ( ) ;
8294
write ( root, " a.txt" , b " 1\n 2\n " ) ;
8295
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
8296
assert! (
8297
repo.clean_by_manifest ( ) .unwrap ( ) ,
8298
" after a commit of one file"
8299
) ;
8300
8301
8302
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
8303
repo.snapshot_status ( " status" ) .unwrap ( ) ;
8304
write ( root, " a.txt" , b " 1\n 2\n 3\n 4\n 5\n " ) ;
8305
repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
8306
assert! (
8307
repo.clean_by_manifest ( ) .unwrap ( ) ,
8308
" after an edit a status saw"
8309
) ;
8310
}
e266b7d4 Andy McCall 1 month ago ↶
8311
8312
# [ test ]
8313
fn a_conflicted_rebase_can_be_resolved_in_the_working_copy ( ) {
8314
hermetic ( ) ;
8315
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8316
let root = dir.path ( ) ;
8317
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8318
write ( root, " a.txt" , b " 1\n 2\n " ) ;
8319
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
8320
repo.bookmark_create ( " develop" , Some ( & [ a] ) ) .unwrap ( ) ;
8321
write ( root, " a.txt" , b " 1\n X\n " ) ;
8322
let c = repo.commit ( " C: 2 to X" , & [ ] ) .unwrap ( ) ;
8323
repo.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
8324
write ( root, " a.txt" , b " 1\n Y\n " ) ;
8325
let d = repo.commit ( " D: 2 to Y" , & [ ] ) .unwrap ( ) ;
8326
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
8327
8328
let err = repo.rebase ( & c, & [ d] ) .unwrap_err ( ) ;
8329
assert! ( matches! ( err, RepoError:: Conflicted( _ ) ) , " {err}" ) ;
8330
let r = repo.rebase_into_working_copy ( & c, & [ d] ) .unwrap ( ) ;
8331
assert! ( r.changed & & r.checked_out) ;
8332
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8333
assert! (
8334
snap.base_snapshot.is_conflicted ( ) ,
8335
" the conflict is in the working copy"
8336
) ;
8337
let text = fs:: read_to_string( root.join ( " a.txt" ) ) .unwrap ( ) ;
8338
assert! ( text.contains ( ' X' ) & & text.contains ( ' Y' ) , " {text}" ) ;
8339
8340
write ( root, " a.txt" , b " 1\n XY\n " ) ;
8341
repo.commit ( " Resolve" , & [ ] ) .unwrap ( ) ;
8342
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
8343
assert! ( st.contains ( & c) & & st.contains ( & d) ) ;
8344
let files = st.materialize ( ) .files;
8345
assert! ( ! files[ " a.txt" ] .is_conflicted ( ) ) ;
8346
assert! (
8347
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 1\n XY\n " )
8348
) ;
8349
8350
8351
repo.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
8352
write ( root, " b.txt" , b " b\n " ) ;
8353
let e = repo.commit ( " E" , & [ ] ) .unwrap ( ) ;
8354
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
8355
let err = repo.rebase_into_working_copy ( & e, & [ a] ) .unwrap_err ( ) ;
8356
assert! (
8357
err.to_string ( ) .contains ( " not on the current bookmark" ) ,
8358
" {err}"
8359
) ;
8360
}
d4dbab97 Andy McCall 1 month ago ↶
8361
8362
# [ test ]
8363
fn an_ignore_rule_drops_an_added_file_but_not_a_tracked_one ( ) {
8364
hermetic ( ) ;
8365
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8366
let root = dir.path ( ) ;
8367
let repo = Repo:: init( root) .unwrap ( ) ;
8368
write ( root, " a.txt" , b " a\n " ) ;
8369
write ( root, " kept.log" , b " tracked\n " ) ;
8370
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8371
write ( root, " build.log" , b " made\n " ) ;
8372
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8373
assert! ( snap.statuses.iter ( ) .any ( | s | s.path = = " build.log" ) ) ;
8374
8375
8376
write ( root, " .gitignore" , b " *.log\n " ) ;
8377
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8378
let paths: Vec< & str > = snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect ( ) ;
8379
assert_eq! ( paths, vec! [ " .gitignore" ] , " {paths:?}" ) ;
8380
write ( root, " kept.log" , b " tracked, edited\n " ) ;
8381
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8382
assert! ( snap.statuses.iter ( ) .any ( | s | s.path = = " kept.log" ) ) ;
8383
}
fd280228 Andy McCall 1 month ago ↶
8384
8385
# [ test ]
8386
fn shed_graphs_come_back_the_same ( ) {
8387
hermetic ( ) ;
8388
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8389
let root = dir.path ( ) ;
8390
let repo = Repo:: init( root) .unwrap ( ) ;
8391
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
8392
write ( root, " b.txt" , b " x\n " ) ;
8393
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8394
write ( root, " a.txt" , b " 1\n two\n 3\n 4\n " ) ;
8395
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
8396
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
8397
let mut st = repo.load_state_history ( & heads) .unwrap ( ) ;
8398
let view = st.tree ( ) .view ( ) ;
8399
let nodes: Vec< NodeId> = [ " a.txt" , " b.txt" ]
8400
.iter ( )
8401
.map ( | p | view.entries[ * p] .node )
8402
.collect ( ) ;
8403
repo.load_graphs_of ( & mut st, & nodes) .unwrap ( ) ;
8404
let before: Vec< Vec< u8 > > = nodes.iter ( ) .map ( | n | st.render_file ( n) .0 ) .collect ( ) ;
8405
assert_eq! ( st.built_count ( ) , 2 ) ;
8406
let shed = st.shed_graphs ( | b | repo.store_blob ( b) ) .unwrap ( ) ;
8407
assert_eq! ( ( shed, st.built_count ( ) ) , ( 2 , 0 ) ) ;
8408
8409
write ( root, " a.txt" , b " 1\n two\n 3\n 4\n 5\n " ) ;
8410
let h3 = repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
8411
let ( skeleton, texts) = repo.store ( ) .get_change_lazy ( & h3) .unwrap ( ) ;
8412
st.add_known ( h3, skeleton, texts) ;
8413
repo.load_graphs_of ( & mut st, & nodes) .unwrap ( ) ;
8414
let after: Vec< Vec< u8 > > = nodes.iter ( ) .map ( | n | st.render_file ( n) .0 ) .collect ( ) ;
8415
assert_eq! ( after[ 1 ] , before[ 1 ] ) ;
8416
assert_eq! ( after[ 0 ] , b " 1\n two\n 3\n 4\n 5\n " ) ;
8417
}
360aa74a Andy McCall 1 month ago ↶
8418
8419
# [ test ]
8420
fn many_edited_files_diff_in_parallel ( ) {
8421
8422
8423
8424
hermetic ( ) ;
8425
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8426
let root = dir.path ( ) ;
8427
let repo = Repo:: init( root) .unwrap ( ) ;
8428
let text = | i : usize , round : usize | {
8429
let mut s = String :: new( ) ;
8430
for line in 0 .. 50 {
8431
if round > 0 & & line % 7 = = i % 7 {
8432
s.push_str ( & format! ( " edited {round} {i} {line} \n " ) ) ;
8433
} else {
8434
s.push_str ( & format! ( " file {i} line {line} \n " ) ) ;
8435
}
8436
}
8437
if round > 0 & & i.is_multiple_of ( 3 ) {
8438
s.push_str ( " appended\n " ) ;
8439
}
8440
s
8441
} ;
8442
for i in 0 .. 40 {
8443
write ( root, & format! ( " d/f{i} .txt" ) , text ( i, 0 ) .as_bytes ( ) ) ;
8444
}
8445
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8446
for i in 0 .. 40 {
8447
write ( root, & format! ( " d/f{i} .txt" ) , text ( i, 1 ) .as_bytes ( ) ) ;
8448
}
8449
let h = repo.commit ( " edit every file" , & [ ] ) .unwrap ( ) ;
8450
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
8451
for i in 0 .. 40 {
8452
let want = text ( i, 1 ) ;
8453
assert! (
8454
matches! ( & snap.files[ & format! ( " d/f{i} .txt" ) ] .content, FileContent:: Text { bytes, .. } if bytes = = want.as_bytes ( ) ) ,
8455
" d/f{i}.txt as committed"
8456
) ;
8457
}
8458
assert! (
8459
repo.commit ( " nothing" , & [ ] ) .is_err ( ) ,
8460
" nothing left to commit"
8461
) ;
8462
}
b35df36b Andy McCall 1 month ago ↶
8463
8464
8465
8466
8467
8468
# [ test ]
8469
fn a_fetched_change_shows_as_behind_and_resolves ( ) {
8470
hermetic ( ) ;
8471
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8472
let root = dir.path ( ) ;
8473
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
8474
drop ( origin) ;
8475
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
8476
let a = Repo:: clone( & url, & root.join ( " a" ) )
8477
.unwrap ( )
8478
.with_home ( root.join ( " home-a" ) ) ;
8479
write ( & root.join ( " a" ) , " f.txt" , b " one\n " ) ;
8480
a.commit ( " one" , & [ ] ) .unwrap ( ) ;
8481
a.push ( " origin" , false ) .unwrap ( ) ;
8482
let b = Repo:: clone( & url, & root.join ( " b" ) )
8483
.unwrap ( )
8484
.with_home ( root.join ( " home-b" ) ) ;
8485
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 0 ) ) ) ;
8486
8487
write ( & root.join ( " a" ) , " f.txt" , b " one\n two\n " ) ;
8488
let h2 = a.commit ( " two" , & [ ] ) .unwrap ( ) ;
8489
a.push ( " origin" , false ) .unwrap ( ) ;
8490
b.fetch ( " origin" ) .unwrap ( ) ;
8491
8492
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 1 ) ) ) ;
8493
assert_eq! ( b.tracking ( " origin" , " nope" ) .unwrap ( ) , None ) ;
8494
assert_eq! (
8495
b.resolve_change ( & format! ( " #{} " , & h2.to_hex ( ) [ .. 12 ] ) )
8496
.unwrap ( ) ,
8497
h2
8498
) ;
8499
assert_eq! (
8500
b.remote_only_location ( & h2) .unwrap ( ) .as_deref ( ) ,
8501
Some ( " origin/main" )
8502
) ;
8503
8504
8505
b.fetch ( " origin" ) .unwrap ( ) ;
8506
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 1 ) ) ) ;
8507
b.pull ( " origin" ) .unwrap ( ) ;
8508
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 0 ) ) ) ;
8509
assert_eq! ( b.remote_only_location ( & h2) .unwrap ( ) , None ) ;
8510
8511
8512
let mut c = Repo:: init( & root.join ( " c" ) ) .unwrap ( ) ;
8513
assert_eq! ( c.default_remote ( ) , " origin" ) ;
8514
c.config.set ( " remote.earth.url" , & url) .unwrap ( ) ;
8515
assert_eq! ( c.default_remote ( ) , " earth" ) ;
8516
c.config.set ( " remote.origin.url" , & url) .unwrap ( ) ;
8517
assert_eq! ( c.default_remote ( ) , " origin" ) ;
8518
}
827281ee Andy McCall 1 month ago ↶
8519
8520
8521
8522
# [ test ]
8523
fn a_clone_from_a_relative_path_records_it_absolute ( ) {
8524
hermetic ( ) ;
8525
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8526
let root = dir.path ( ) ;
8527
Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
8528
let rel = crate :: protocol:: stored_url( " relative/repo" ) ;
8529
assert! ( std:: path:: Path:: new( & rel) .is_absolute ( ) , " {rel}" ) ;
8530
assert_eq! (
8531
crate :: protocol:: stored_url( " https://hub.example/o/n" ) ,
8532
" https://hub.example/o/n"
8533
) ;
8534
assert! ( crate :: protocol:: stored_url( " file://x" ) .starts_with ( " file:///" ) ) ;
8d803563 Andy McCall 1 month ago ↶
8535
assert! ( crate :: protocol:: stored_url( " dir://x" ) .starts_with ( " dir:///" ) ) ;
8536
8537
assert_eq! (
8538
crate :: protocol:: stored_url( " s3://bucket/prefix?profile=p" ) ,
8539
" s3://bucket/prefix?profile=p"
8540
) ;
827281ee Andy McCall 1 month ago ↶
8541
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
8542
let b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) ;
8543
assert_eq! ( b.config.remotes[ " origin" ] , url) ;
8544
b.fetch ( " origin" ) .unwrap ( ) ;
8545
}
a46f3e4f Andy McCall 1 month ago ↶
8546
8547
8548
8549
8550
# [ test ]
8551
fn a_workspace_added_on_a_bookmark_is_on_it_with_its_subrepos ( ) {
8552
hermetic ( ) ;
8553
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8554
let root = dir.path ( ) ;
8555
Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
8556
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
8557
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
8558
.unwrap ( )
8559
.with_home ( root.join ( " home" ) ) ;
8560
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
8561
let l1 = lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
8562
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
8563
8564
let app_dir = root.join ( " app" ) ;
8565
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8566
write ( & app_dir, " main.txt" , b " app\n " ) ;
8567
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
8568
app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
8569
8570
app.bookmark_create ( " develop" , None ) .unwrap ( ) ;
8571
app.bookmark_create ( " feature" , None ) .unwrap ( ) ;
8572
let mut app = app;
8573
app.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
8574
assert_eq! ( app.config.bookmark, " feature" ) ;
8575
8576
8577
std:: fs:: rename( root.join ( " lib-origin" ) , root.join ( " lib-origin-gone" ) ) .unwrap ( ) ;
8578
let heads = app.resolve_heads ( " develop" ) .unwrap ( ) ;
8579
let ws = app
8580
.workspace_add_on ( & root.join ( " ws" ) , Some ( & heads) , Some ( " develop" ) )
8581
.unwrap ( ) ;
8582
assert_eq! (
8583
ws.config.bookmark, " develop" ,
8584
" the new workspace is on develop"
8585
) ;
8586
assert_eq! (
8587
app.config.bookmark, " feature" ,
8588
" and this one stays on its own"
8589
) ;
8590
let sub = Repo:: open( & root.join ( " ws/libs/lib" ) ) .unwrap ( ) ;
8591
assert_eq! (
8592
sub.config.remotes[ " origin" ] , lib_url,
8593
" its origin is the subrepo's URL"
8594
) ;
8595
assert_eq! ( sub.bookmark_heads ( ) .unwrap ( ) , vec! [ l1] , " at its pin" ) ;
8596
assert_eq! (
8597
std:: fs:: read( root.join ( " ws/libs/lib/lib.txt" ) ) .unwrap ( ) ,
8598
b " v1\n "
8599
) ;
8600
}
47ce8b8e Andy McCall 1 month ago ↶
8601
8602
8603
8604
8605
# [ test ]
8606
fn revsets_select_changes_by_relation_author_text_date_and_path ( ) {
8607
hermetic ( ) ;
8608
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8609
let root = dir.path ( ) ;
8610
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8611
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
8612
.unwrap ( ) ;
8613
write ( root, " src/a.rs" , b " one\n " ) ;
8614
let a = repo.commit ( " first: add a" , & [ ] ) .unwrap ( ) ;
8615
write ( root, " src/a.rs" , b " one\n two\n " ) ;
8616
let b = repo.commit ( " second: grow a" , & [ ] ) .unwrap ( ) ;
8617
8618
repo.checkout ( & [ a] , false , " checkout a" ) .unwrap ( ) ;
8619
write ( root, " docs/notes.md" , b " notes\n " ) ;
8620
let c = repo.commit ( " third: notes" , & [ ] ) .unwrap ( ) ;
8621
8622
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
8623
.unwrap ( ) ;
8624
fs:: remove_file( root.join ( " src/a.rs" ) ) .unwrap ( ) ;
8625
write ( root, " src/z.rs" , b " one\n two\n " ) ;
8626
let d = repo.commit ( " fourth: rename a to z" , & [ ] ) .unwrap ( ) ;
8627
let wc = repo.working_copy ( ) .unwrap ( ) .0 ;
8628
8629
repo.set_user_config ( " revset.mine" , " author(me) & ::main" )
8630
.unwrap ( ) ;
8631
assert! ( repo.set_user_config ( " revset.bad" , " a &" ) .is_err ( ) ) ;
8632
let set = | src : & str | -> Vec< ChangeHash> {
8633
let mut v = repo.revset ( src) .unwrap ( ) .hashes;
8634
v.sort ( ) ;
8635
v
8636
} ;
8637
let sorted = | hs : & [ChangeHash]| -> Vec< ChangeHash> {
8638
let mut v = hs.to_vec ( ) ;
8639
v.sort ( ) ;
8640
v
8641
} ;
8642
8643
8644
8645
8646
let heads = sorted ( & repo.bookmark_heads ( ) .unwrap ( ) ) ;
8647
let meta = | h : & ChangeHash| repo.store ( ) .get_change_meta ( h) .unwrap ( ) ;
8648
assert! ( heads.contains ( & d) & & heads.contains ( & c) ) ;
8649
8650
assert_eq! ( set ( " main" ) , heads) ;
8651
assert_eq! ( set ( " ::main" ) , sorted ( & [ a, b, c, d] ) ) ;
8652
assert_eq! ( set ( " @" ) , vec! [ wc] ) ;
8653
assert_eq! ( set ( " ::@" ) , sorted ( & [ a, b, c, d, wc] ) ) ;
8654
assert_eq! ( set ( & format! ( " ::#{} " , b.short ( 12 ) ) ) , sorted ( & [ a, b] ) ) ;
8655
assert_eq! ( set ( & format! ( " #{} ::" , a.short ( 12 ) ) ) , sorted ( & [ a, b, d, wc] ) ) ;
8656
assert_eq! (
8657
set ( & format! ( " #{} ::#{} " , a.short ( 12 ) , b.short ( 12 ) ) ) ,
8658
sorted ( & [ a, b] )
8659
) ;
8660
8661
assert_eq! ( set ( & format! ( " #{} ..main" , b.short ( 12 ) ) ) , sorted ( & [ c, d] ) ) ;
8662
assert_eq! ( set ( & format! ( " #{} .." , c.short ( 12 ) ) ) , sorted ( & [ a, b, d, wc] ) ) ;
8663
assert_eq! ( set ( " ..main" ) , sorted ( & [ a, b, c, d] ) ) ;
8664
8665
assert_eq! (
8666
set ( & format! ( " ::main ~ #{} " , d.short ( 12 ) ) ) ,
8667
sorted ( & [ a, b, c] )
8668
) ;
8669
assert_eq! (
8670
set ( & format! ( " ::main & #{} | #{} " , b.short ( 12 ) , c.short ( 12 ) ) ) ,
8671
sorted ( & [ b, c] )
8672
) ;
8673
assert_eq! ( set ( " all() ~ ::main" ) , vec! [ wc] ) ;
8674
8675
let mut head_deps: Vec< ChangeHash> = heads.iter ( ) .flat_map ( | h | meta ( h) .deps) .collect ( ) ;
8676
head_deps.sort ( ) ;
8677
head_deps.dedup ( ) ;
8678
assert_eq! ( set ( " deps(main)" ) , head_deps) ;
8679
let on_a: Vec< ChangeHash> = [ b, c, d]
8680
.into_iter ( )
8681
.filter ( | h | meta ( h) .deps.contains ( & a) )
8682
.collect ( ) ;
8683
assert_eq! ( set ( & format! ( " dependents(#{} )" , a.short ( 12 ) ) ) , sorted ( & on_a) ) ;
8684
assert_eq! ( set ( " heads(::main)" ) , heads) ;
8685
assert_eq! ( set ( " roots(::main)" ) , sorted ( & [ a, c] ) ) ;
8686
assert_eq! (
8687
set ( & format! (
8688
" heads(#{} | #{} | #{} )" ,
8689
a.short ( 12 ) ,
8690
b.short ( 12 ) ,
8691
c.short ( 12 )
8692
) ) ,
8693
sorted ( & [ b, c] )
8694
) ;
8695
assert_eq! ( set ( " latest(::main, 1)" ) , vec! [ d] ) ;
8696
assert_eq! ( set ( " kind(working-copy)" ) , vec! [ wc] ) ;
8697
assert_eq! ( set ( " none()" ) , vec! [ ] ) ;
8698
8699
let key = repo.config.author_key.unwrap ( ) ;
8700
assert_eq! ( set ( " author(ada) & ::main" ) , sorted ( & [ a, b, c, d] ) ) ;
8701
assert_eq! ( set ( " author(\" [email protected] \" ) & ::main" ) .len ( ) , 4 ) ;
8702
assert_eq! ( set ( & format! ( " author({} ) & ::main" , key.short ( 12 ) ) ) .len ( ) , 4 ) ;
8703
assert_eq! ( set ( " author(me) & ::main" ) .len ( ) , 4 ) ;
8704
assert_eq! ( set ( " author(nobody)" ) , vec! [ ] ) ;
8705
assert_eq! ( set ( " description(notes)" ) , vec! [ c] ) ;
8706
assert_eq! ( set ( " description(\" rename a\" )" ) , vec! [ d] ) ;
8707
assert_eq! ( set ( " date(-1h..) & ::main" ) .len ( ) , 4 ) ;
8708
assert_eq! ( set ( " date(..2000-01-01)" ) , vec! [ ] ) ;
8709
8710
assert_eq! ( set ( " touches(\" src/z.rs\" )" ) , sorted ( & [ a, b, d] ) ) ;
8711
assert_eq! ( set ( " touches(\" src/**\" ) & ::main" ) , sorted ( & [ a, b, d] ) ) ;
8712
assert_eq! ( set ( " touches(\" docs/*\" )" ) , vec! [ c] ) ;
8713
assert_eq! ( set ( " touches(\" nothing/*\" )" ) , vec! [ ] ) ;
8714
8715
assert_eq! ( repo.revset ( " ::main" ) .unwrap ( ) .hashes[ 0 ] , d) ;
8716
8717
assert_eq! ( set ( " mine()" ) .len ( ) , 4 ) ;
8718
let e = repo.revset ( " nosuch()" ) .unwrap_err ( ) .to_string ( ) ;
8719
assert! ( e.contains ( " unknown function nosuch()" ) , " {e}" ) ;
8720
let e = repo.revset ( " main &" ) .unwrap_err ( ) .to_string ( ) ;
8721
assert! ( e.contains ( " at character" ) , " {e}" ) ;
8722
let e = repo.revset ( " zzzz" ) .unwrap_err ( ) .to_string ( ) ;
8723
assert! ( e.contains ( " no change matches" ) , " {e}" ) ;
8724
let e = repo
8725
.revset_with_budget ( " ::main" , std:: time:: Duration:: ZERO )
8726
.unwrap_err ( )
8727
.to_string ( ) ;
8728
assert! ( e.contains ( " budget" ) , " {e}" ) ;
8729
8730
let entries = repo.log_revset ( " ::@" ) .unwrap ( ) ;
8731
assert! ( entries[ 0 ] .is_working_copy & & entries.iter ( ) .any ( | e | e.is_head & & e.hash = = d ) ) ;
2c9ddac3 Andy McCall 1 month ago ↶
8732
8733
8734
assert_eq! ( set ( " unsigned() & ::main" ) , sorted ( & [ a, b, c, d] ) ) ;
8735
assert_eq! ( set ( " signed_by(me)" ) , vec! [ ] ) ;
8736
repo.sign_change ( & d) .unwrap ( ) ;
8737
assert_eq! ( set ( " signed_by(me)" ) , vec! [ d] ) ;
8738
assert_eq! ( set ( " signed_by([email protected] )" ) , vec! [ d] ) ;
8739
assert_eq! ( set ( & format! ( " signed_by({} )" , key.short ( 12 ) ) ) , vec! [ d] ) ;
8740
assert_eq! ( set ( " unsigned() & ::main" ) , sorted ( & [ a, b, c] ) ) ;
8741
assert_eq! ( set ( " delegated()" ) , vec! [ ] ) ;
8742
assert_eq! ( set ( " revoked()" ) , vec! [ ] ) ;
8743
assert_eq! ( set ( " attested(tag)" ) , vec! [ ] ) ;
8744
repo.tag_create ( " v1" , & [ b] , " first" ) .unwrap ( ) ;
8745
assert_eq! ( set ( " attested(tag)" ) , sorted ( & [ a, b] ) ) ;
8746
assert_eq! ( set ( " attested(tag) & unsigned()" ) , sorted ( & [ a, b] ) ) ;
8747
assert_eq! ( set ( " attested(receipt)" ) , vec! [ ] ) ;
8748
assert_eq! ( set ( " built(test)" ) , vec! [ ] ) ;
8749
let e = repo.revset ( " attested(seal)" ) .unwrap_err ( ) .to_string ( ) ;
8750
assert! ( e.contains ( " tag, receipt or ref" ) , " {e}" ) ;
8751
repo.revoke_identity ( & key, " test" ) .unwrap ( ) ;
8752
assert_eq! ( set ( " revoked()" ) , vec! [ d] ) ;
8753
assert_eq! ( set ( " signed_by(me) ~ revoked()" ) , vec! [ ] ) ;
47ce8b8e Andy McCall 1 month ago ↶
8754
}
614f55e1 Andy McCall 1 month ago ↶
8755
8756
# [ test ]
8757
fn a_rotation_is_recorded_wherever_the_old_key_was_named ( ) {
8758
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8759
let root = dir.path ( ) ;
8760
let home = root.join ( " home" ) ;
8761
8762
8763
let mut a = Repo:: init( & root.join ( " a" ) ) .unwrap ( ) .with_home ( & home) ;
8764
let ( key, _ ) = a
8765
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
8766
.unwrap ( ) ;
8767
a.config.set ( " author.key" , & key.to_hex ( ) ) .unwrap ( ) ;
8768
a.save_config ( ) .unwrap ( ) ;
8769
let agent = SecretKey:: generate( ) .identity ( " Agent" , " [email protected] " , 1 ) ;
8770
let r = a
8771
.delegate_add ( agent.clone ( ) , crate :: identity:: Scope:: CHANGES , None )
8772
.unwrap ( ) ;
8773
assert_eq! (
8774
read_config_file ( & home.join ( " config.toml" ) ) .unwrap ( ) [ " author.key" ] ,
8775
r.new.to_hex ( ) ,
8776
" the user file follows"
8777
) ;
8778
assert_eq! (
8779
read_config_file ( & root.join ( " a/.oxy/config" ) ) .unwrap ( ) [ " author.key" ] ,
8780
r.new.to_hex ( ) ,
8781
" the pinning repository follows"
8782
) ;
8783
let b = Repo:: init( & root.join ( " b" ) ) .unwrap ( ) .with_home ( & home) ;
8784
assert_eq! (
8785
b.config.author_key,
8786
Some ( r.new) ,
8787
" a checkout without a pin follows the user file"
8788
) ;
8789
8790
let other_home = root.join ( " home2" ) ;
8791
let mut c = Repo:: init( & root.join ( " c" ) ) .unwrap ( ) .with_home ( & other_home) ;
8792
let ( ck, _ ) = c
8793
.create_identity ( " Cy" , " [email protected] " , IdentityScope:: Repo)
8794
.unwrap ( ) ;
8795
assert! ( ! other_home.join ( " config.toml" ) .exists ( ) ) ;
8796
let r2 = c.rotate_identity ( ) .unwrap ( ) ;
8797
assert_ne! ( r2.new, ck) ;
8798
assert_eq! (
8799
read_config_file ( & root.join ( " c/.oxy/config" ) ) .unwrap ( ) [ " author.key" ] ,
8800
r2.new.to_hex ( )
8801
) ;
8802
assert! (
8803
! other_home.join ( " config.toml" ) .exists ( ) ,
8804
" a repository-scoped identity stays repository-scoped"
8805
) ;
8806
8807
let ( d1, d2) = (
8808
SecretKey:: generate( ) .identity ( " D1" , " d1@x" , 1 ) ,
8809
SecretKey:: generate( ) .identity ( " D2" , " d2@x" , 1 ) ,
8810
) ;
8811
let r3 = c
8812
.delegate_add_many ( vec! [ d1.clone ( ) , d2] , crate :: identity:: Scope:: CHANGES , None )
8813
.unwrap ( ) ;
8814
assert_eq! ( r3.identity.delegates.len ( ) , 2 ) ;
8815
assert! ( c
8816
.delegate_add ( d1, crate :: identity:: Scope:: CHANGES , None )
8817
.is_err ( ) ) ;
8818
}
33be2409 Andy McCall 1 month ago ↶
8819
8820
8821
8822
8823
8824
# [ test ]
8825
fn git_import_joins_unrelated_histories_that_add_the_same_file ( ) {
8826
8827
8828
crate :: repo:: git:: set_merge_batch_for_tests( Some ( 1 ) ) ;
8829
hermetic ( ) ;
8830
if std:: process:: Command:: new( " git" )
8831
.arg ( " --version" )
8832
.output ( )
8833
.is_err ( )
8834
{
8835
eprintln! ( " git not installed; skipping" ) ;
8836
return ;
8837
}
8838
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8839
let root = dir.path ( ) ;
8840
let g = root.join ( " src.git" ) ;
8841
fs:: create_dir_all( & g) .unwrap ( ) ;
8842
let git = | args : & [& str ]| -> String {
8843
let out = std:: process:: Command:: new( " git" )
8844
.arg ( " -C" )
8845
.arg ( & g)
8846
.args ( [
8847
" -c" ,
8848
" user.name=Grace" ,
8849
" -c" ,
8850
" [email protected] " ,
8851
" -c" ,
8852
" commit.gpgsign=false" ,
8853
] )
8854
.args ( args)
8855
.output ( )
8856
.unwrap ( ) ;
8857
assert! (
8858
out.status.success ( ) ,
8859
" git {args:?}: {}" ,
8860
String :: from_utf8_lossy( & out.stderr)
8861
) ;
8862
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
8863
} ;
8864
let text = b " /* header */\n const a = 1;\n const b = 2;\n " ;
8865
8866
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
8867
write ( & g, " gulpfile.js" , text) ;
8868
write ( & g, " a.txt" , b " a\n " ) ;
8869
git ( & [ " add" , " ." ] ) ;
8870
git ( & [ " commit" , " -q" , " -m" , " A root" ] ) ;
8871
8872
git ( & [ " checkout" , " -q" , " --orphan" , " fork" ] ) ;
8873
git ( & [ " rm" , " -q" , " -r" , " --cached" , " ." ] ) ;
8874
for f in [ " gulpfile.js" , " a.txt" ] {
8875
let _ = fs:: remove_file( g.join ( f) ) ;
8876
}
8877
write ( & g, " gulpfile.js" , text) ;
8878
write ( & g, " b.txt" , b " b\n " ) ;
8879
git ( & [ " add" , " ." ] ) ;
8880
git ( & [ " commit" , " -q" , " -m" , " B root" ] ) ;
8881
8882
git ( & [ " checkout" , " -q" , " -b" , " one" , " main" ] ) ;
8883
git ( & [
8884
" merge" ,
8885
" -q" ,
8886
" --no-ff" ,
8887
" --allow-unrelated-histories" ,
8888
" -m" ,
8889
" join on one" ,
8890
" fork" ,
8891
] ) ;
8892
git ( & [ " checkout" , " -q" , " -b" , " two" , " main" ] ) ;
8893
write ( & g, " a.txt" , b " a\n two\n " ) ;
8894
git ( & [ " commit" , " -q" , " -am" , " work on two" ] ) ;
8895
git ( & [
8896
" merge" ,
8897
" -q" ,
8898
" --no-ff" ,
8899
" --allow-unrelated-histories" ,
8900
" -m" ,
8901
" join on two" ,
8902
" fork" ,
8903
] ) ;
8904
8905
git ( & [ " checkout" , " -q" , " main" ] ) ;
8906
git ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge one" , " one" ] ) ;
8907
git ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge two" , " two" ] ) ;
8908
write (
8909
& g,
8910
" gulpfile.js" ,
8911
b " /* header */\n const a = 1;\n const b = 3;\n " ,
8912
) ;
8913
git ( & [ " commit" , " -q" , " -am" , " after" ] ) ;
8914
8915
let dest = root.join ( " dest" ) ;
8916
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8917
repo.import_git ( & g, " main" ) .unwrap ( ) ;
8918
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
8919
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
8920
assert_eq! (
8921
fs:: read( dest.join ( " gulpfile.js" ) ) .unwrap ( ) ,
8922
b " /* header */\n const a = 1;\n const b = 3;\n "
8923
) ;
8924
}
8925
8926
8927
8928
8929
# [ test ]
8930
fn git_import_joins_where_each_join_kept_a_different_copy ( ) {
8931
8932
8933
crate :: repo:: git:: set_merge_batch_for_tests( Some ( 1 ) ) ;
8934
hermetic ( ) ;
8935
if std:: process:: Command:: new( " git" )
8936
.arg ( " --version" )
8937
.output ( )
8938
.is_err ( )
8939
{
8940
eprintln! ( " git not installed; skipping" ) ;
8941
return ;
8942
}
8943
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8944
let root = dir.path ( ) ;
8945
let g = root.join ( " src.git" ) ;
8946
fs:: create_dir_all( & g) .unwrap ( ) ;
8947
let run = | args : & [& str ]| {
8948
std:: process:: Command:: new( " git" )
8949
.arg ( " -C" )
8950
.arg ( & g)
8951
.args ( [
8952
" -c" ,
8953
" user.name=Grace" ,
8954
" -c" ,
8955
" [email protected] " ,
8956
" -c" ,
8957
" commit.gpgsign=false" ,
8958
] )
8959
.args ( args)
8960
.output ( )
8961
.unwrap ( )
8962
} ;
8963
let git = | args : & [& str ]| -> String {
8964
let out = run ( args) ;
8965
assert! (
8966
out.status.success ( ) ,
8967
" git {args:?}: {}" ,
8968
String :: from_utf8_lossy( & out.stderr)
8969
) ;
8970
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
8971
} ;
8972
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
8973
write (
8974
& g,
8975
" gulpfile.js" ,
8976
b " /* header */\n const a = 1;\n const b = 2;\n " ,
8977
) ;
8978
git ( & [ " add" , " ." ] ) ;
8979
git ( & [ " commit" , " -q" , " -m" , " A root" ] ) ;
8980
write (
8981
& g,
8982
" gulpfile.js" ,
8983
b " /* header */\n const a = 1;\n \n const b = 2;\n " ,
8984
) ;
8985
git ( & [ " commit" , " -q" , " -am" , " whitespace" ] ) ;
8986
git ( & [ " checkout" , " -q" , " --orphan" , " fork" ] ) ;
8987
git ( & [ " rm" , " -q" , " -r" , " --cached" , " ." ] ) ;
8988
let _ = fs:: remove_file( g.join ( " gulpfile.js" ) ) ;
8989
write (
8990
& g,
8991
" gulpfile.js" ,
8992
b " /* header */\n const a = 1;\n const b = 2;\n const c = 3;\n " ,
8993
) ;
8994
git ( & [ " add" , " ." ] ) ;
8995
git ( & [ " commit" , " -q" , " -m" , " B root" ] ) ;
8996
8997
for ( branch, keep) in [ ( " one" , " ours" ) , ( " two" , " theirs" ) ] {
8998
git ( & [ " checkout" , " -q" , " -b" , branch, " main" ] ) ;
8999
let _ = run ( & [
9000
" merge" ,
9001
" -q" ,
9002
" --no-ff" ,
9003
" --allow-unrelated-histories" ,
9004
" -m" ,
9005
" join" ,
9006
" fork" ,
9007
] ) ;
9008
git ( & [ " checkout" , & format! ( " --{keep} " ) , " gulpfile.js" ] ) ;
9009
git ( & [ " add" , " gulpfile.js" ] ) ;
9010
git ( & [ " commit" , " -q" , " -m" , & format! ( " join on {branch} " ) ] ) ;
9011
}
9012
9013
git ( & [ " checkout" , " -q" , " one" ] ) ;
9014
write (
9015
& g,
9016
" gulpfile.js" ,
9017
b " /* header */\n const a = 1;\n \n const b = 2;\n // tslint\n " ,
9018
) ;
9019
git ( & [ " commit" , " -q" , " -am" , " tslint on one" ] ) ;
9020
git ( & [ " checkout" , " -q" , " two" ] ) ;
9021
write (
9022
& g,
9023
" gulpfile.js" ,
9024
b " /* header */\n const a = 1;\n const b = 2;\n const c = 3;\n // fmt\n " ,
9025
) ;
9026
write ( & g, " extra.txt" , b " x\n " ) ;
9027
write ( & g, " more.txt" , b " m\n " ) ;
9028
git ( & [ " add" , " ." ] ) ;
9029
git ( & [ " commit" , " -q" , " -am" , " fmt on two" ] ) ;
9030
9031
git ( & [ " checkout" , " -q" , " one" ] ) ;
9032
let _ = run ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge two" , " two" ] ) ;
9033
write (
9034
& g,
9035
" gulpfile.js" ,
9036
b " /* header */\n const a = 1;\n const b = 2;\n const c = 3;\n // tslint\n // fmt\n " ,
9037
) ;
9038
git ( & [ " add" , " gulpfile.js" ] ) ;
9039
git ( & [ " commit" , " -q" , " -m" , " merge two into one" ] ) ;
9040
9041
let dest = root.join ( " dest" ) ;
9042
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9043
repo.import_git ( & g, " one" ) .unwrap ( ) ;
9044
let v = repo.verify_git_import ( & g, " one" ) .unwrap ( ) ;
9045
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
9046
}
dd21fd82 Andy McCall 1 month ago ↶
9047
9048
9049
9050
9051
9052
9053
# [ test ]
9054
fn over_a_store_the_operation_head_moves_by_compare_and_swap ( ) {
9055
use crate :: strata:: { MemoryStore, Store as _ } ;
9056
let backend = std:: sync:: Arc:: new( MemoryStore:: new( ) ) ;
9057
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = Some ( backend.clone ( ) ) ) ;
9058
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9059
let a = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9060
assert! (
9061
! dir.path ( ) .join ( " .oxy/ops/head" ) .exists ( ) ,
9062
" the head is not a file"
9063
) ;
9064
let ( h0, op0) = a.head_op ( ) .unwrap ( ) ;
9065
let record = backend
9066
.read_ref ( HEAD_RECORD )
9067
.unwrap ( )
9068
.expect ( " the head is the store's record" ) ;
9069
assert_eq! ( String :: from_utf8( record.0 ) .unwrap ( ) , h0.to_hex ( ) ) ;
9070
9071
let b = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
9072
let state = a.store.put_state ( & StateObject:: new( vec! [ ] ) ) .unwrap ( ) ;
9073
let mut va = op0.view.clone ( ) ;
9074
va.bookmarks.insert ( " from-a" .into ( ) , state) ;
9075
let mut vb = op0.view.clone ( ) ;
9076
vb.bookmarks.insert ( " from-b" .into ( ) , state) ;
9077
let rb = b.record_op ( & [ h0] , " bookmark from-b" , vb) .unwrap ( ) ;
9078
assert_eq! ( b.head_ref ( ) .unwrap ( ) , rb) ;
9079
9080
let ra = a.record_op ( & [ h0] , " bookmark from-a" , va) .unwrap ( ) ;
9081
let ( head, op) = Repo:: open( dir.path ( ) ) .unwrap ( ) .head_op ( ) .unwrap ( ) ;
9082
assert_eq! ( head, ra) ;
9083
assert! (
9084
op.command.ends_with ( " (merged with a concurrent operation)" ) ,
9085
" {}" ,
9086
op.command
9087
) ;
9088
assert_eq! ( op.parents, vec! [ rb, op.parents[ 1 ] ] ) ;
9089
assert! ( op.view.bookmarks.contains_key ( " from-a" ) & & op.view.bookmarks.contains_key ( " from-b" ) ) ;
9090
9091
9092
let ( h, v) = a.read_head ( ) .unwrap ( ) ;
9093
assert! ( b.swap_head ( & rb, v.as_ref ( ) ) .unwrap ( ) ) ;
9094
assert! ( ! a.swap_head ( & h, v.as_ref ( ) ) .unwrap ( ) , " stale" ) ;
767ac6cd Andy McCall 19 days ago
9095
a.refresh ( ) .unwrap ( ) ;
dd21fd82 Andy McCall 1 month ago ↶
9096
assert_eq! ( a.head_ref ( ) .unwrap ( ) , rb) ;
9097
9098
let other = tempfile:: tempdir( ) .unwrap ( ) ;
9099
let err = Repo:: init( other.path ( ) )
9100
.err ( )
9101
.expect ( " the store already holds a log" )
9102
.to_string ( ) ;
9103
assert! (
9104
err.contains ( " already holds a repository's operation log" ) ,
9105
" {err}"
9106
) ;
9107
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = None ) ;
9108
}
d069701a Andy McCall 1 month ago ↶
9109
9110
9111
9112
9113
# [ test ]
9114
fn a_repository_runs_on_a_store_with_a_cache_in_front ( ) {
9115
hermetic ( ) ;
9116
if std:: env:: var_os( " OXY_STORE_BACKEND" ) .is_some ( ) {
9117
return ;
9118
}
9119
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9120
let ( root, bucket) = ( dir.path ( ) .join ( " work" ) , dir.path ( ) .join ( " bucket" ) ) ;
9121
let url = format! ( " dir://{} " , bucket.display ( ) ) ;
9122
let repo = Repo:: init_on( & root, [ 7 ; 16 ] , Some ( & url) ) .unwrap ( ) ;
9123
assert_eq! ( repo.config.storage_url.as_deref ( ) , Some ( url.as_str ( ) ) ) ;
9124
write ( & root, " a.txt" , b " one\n " ) ;
9125
let h1 = repo.commit ( " First" , & [ ] ) .unwrap ( ) ;
9126
write ( & root, " a.txt" , b " one\n two\n " ) ;
9127
let h2 = repo.commit ( " Second" , & [ ] ) .unwrap ( ) ;
9128
assert! (
9129
! root.join ( " .oxy/ops/head" ) .exists ( ) ,
9130
" the head is in the store"
9131
) ;
9132
assert! ( bucket.join ( " ops/head" ) .is_file ( ) ) ;
9133
assert! ( root.join ( " .oxy/cache" ) .is_dir ( ) ) ;
9134
drop ( repo) ;
9135
9136
9137
for drop_cache in [ false , true ] {
9138
if drop_cache {
9139
std:: fs:: remove_dir_all( root.join ( " .oxy/cache" ) ) .unwrap ( ) ;
9140
}
9141
let repo = Repo:: open( & root) .unwrap ( ) ;
9142
let log: Vec< String> = repo
9143
.op_log ( )
9144
.unwrap ( )
9145
.into_iter ( )
9146
.map ( | ( _, op ) | op.command )
9147
.collect ( ) ;
9148
assert! ( log.len ( ) > = 3 , " {log:?}" ) ;
9149
assert_eq! ( repo.store ( ) .get_change ( & h1) .unwrap ( ) .message, " First" ) ;
9150
let heads = repo
9151
.store ( )
9152
.get_state ( & repo.view ( ) .unwrap ( ) .bookmarks[ " main" ] )
9153
.unwrap ( )
9154
.heads;
9155
assert_eq! ( heads, vec! [ h2] ) ;
9156
assert_eq! (
9157
repo.load_state ( & heads) .unwrap ( ) .materialize ( ) .files.len ( ) ,
9158
1
9159
) ;
9160
}
9161
9162
9163
let err = Repo:: init_on( & dir.path ( ) .join ( " other" ) , [ 8 ; 16 ] , Some ( & url) )
9164
.err ( )
9165
.unwrap ( )
9166
.to_string ( ) ;
9167
assert! (
9168
err.contains ( " already holds a repository's operation log" ) ,
9169
" {err}"
9170
) ;
9171
let err = Repo:: init_on( & dir.path ( ) .join ( " third" ) , [ 9 ; 16 ] , Some ( " nope://x" ) )
9172
.err ( )
9173
.unwrap ( )
9174
.to_string ( ) ;
9175
assert! ( err.contains ( " cannot open that kind of store" ) , " {err}" ) ;
9176
}
80677b7a Andy McCall 1 month ago ↶
9177
9178
9179
9180
9181
9182
# [ test ]
9183
fn the_quick_dirty_check_compares_a_subrepo_by_its_pin ( ) {
9184
hermetic ( ) ;
9185
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9186
let root = dir.path ( ) ;
9187
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9188
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9189
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9190
.unwrap ( )
9191
.with_home ( root.join ( " home" ) ) ;
9192
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9193
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9194
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9195
let app_dir = root.join ( " app" ) ;
9196
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9197
write ( & app_dir, " main.txt" , b " app\n " ) ;
9198
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9199
app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
9200
9201
std:: thread:: sleep( std:: time:: Duration:: from_millis( 2100 ) ) ;
9202
let reopen = | | Repo:: open( & app_dir) .unwrap ( ) ;
9203
assert_eq! (
9204
reopen ( ) .quick_dirty ( ) .unwrap ( ) ,
9205
Some ( false ) ,
9206
" clean, as status says"
9207
) ;
9208
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) ) .unwrap ( ) ;
9209
write ( & app_dir.join ( " libs/lib" ) , " lib.txt" , b " v2\n " ) ;
9210
nested.commit ( " lib v2" , & [ ] ) .unwrap ( ) ;
9211
assert_eq! (
9212
reopen ( ) .quick_dirty ( ) .unwrap ( ) ,
9213
Some ( true ) ,
9214
" the subrepo moved"
9215
) ;
9216
reopen ( ) .commit ( " pin lib v2" , & [ ] ) .unwrap ( ) ;
9217
std:: thread:: sleep( std:: time:: Duration:: from_millis( 2100 ) ) ;
9218
assert_eq! (
9219
reopen ( ) .quick_dirty ( ) .unwrap ( ) ,
9220
Some ( false ) ,
9221
" the new pin is committed"
9222
) ;
9223
}
7993d465 Andy McCall 1 month ago ↶
9224
9225
# [ test ]
9226
fn a_module_entry_that_was_never_added_is_reported_as_unpinned ( ) {
9227
9228
hermetic ( ) ;
9229
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9230
let root = dir.path ( ) ;
9231
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9232
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9233
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9234
.unwrap ( )
9235
.with_home ( root.join ( " home" ) ) ;
9236
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9237
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9238
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9239
let app_dir = root.join ( " app" ) ;
9240
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9241
write (
9242
& app_dir,
9243
" .oxymodules" ,
9244
format! ( " [libs/lib]\n url = {lib_url} \n " ) .as_bytes ( ) ,
9245
) ;
9246
let unpinned = app.unpinned_modules ( ) .unwrap ( ) ;
9247
assert_eq! ( unpinned.len ( ) , 1 ) ;
9248
assert_eq! ( unpinned[ 0 ] .0 , " libs/lib" ) ;
9249
assert_eq! ( unpinned[ 0 ] .1. url, lib_url) ;
9250
assert! ( app.submodule_update ( true , & [ ] ) .unwrap ( ) .is_empty ( ) ) ;
9251
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9252
assert! ( app.unpinned_modules ( ) .unwrap ( ) .is_empty ( ) , " added now" ) ;
9253
}
bf3a6781 Andy McCall 1 month ago ↶
9254
9255
# [ test ]
9256
fn the_bookmark_revset_function_names_only_bookmarks ( ) {
9257
9258
hermetic ( ) ;
9259
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9260
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9261
write ( dir.path ( ) , " a.txt" , b " a\n " ) ;
9262
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
9263
write ( dir.path ( ) , " b.txt" , b " b\n " ) ;
9264
let b = repo.commit ( " B" , & [ ] ) .unwrap ( ) ;
9265
let mut heads = repo.revset ( " bookmark(main)" ) .unwrap ( ) .hashes;
9266
heads.sort ( ) ;
9267
let mut want = repo.bookmark_heads ( ) .unwrap ( ) ;
9268
want.sort ( ) ;
9269
assert_eq! ( heads, want) ;
9270
assert! ( heads.contains ( & b) ) ;
9271
assert_eq! ( repo.revset ( " main" ) .unwrap ( ) .hashes.len ( ) , heads.len ( ) ) ;
9272
9273
let prefix = repo.store ( ) .get_change_meta ( & a) .unwrap ( ) .change_id.short ( 8 ) ;
9274
assert! ( repo.revset ( & prefix) .is_ok ( ) ) ;
9275
let e = repo
9276
.revset ( & format! ( " bookmark({prefix} )" ) )
9277
.unwrap_err ( )
9278
.to_string ( ) ;
9279
assert! ( e.contains ( " no such bookmark" ) , " {e}" ) ;
9280
}
72484195 Andy McCall 1 month ago ↶
9281
157d02e9 Andy McCall 12 days ago ↶
9282
9283
9284
72484195 Andy McCall 1 month ago ↶
9285
# [ test ]
157d02e9 Andy McCall 12 days ago ↶
9286
fn a_replace_conflict_is_settled_from_the_working_copy ( ) {
9287
hermetic ( ) ;
9288
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9289
let root = dir.path ( ) ;
9290
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9291
let blob = | n : u8 | {
9292
let mut v = vec! [ 0 u8 ; 64 ] ;
9293
v[ 1 ] = n;
9294
v
9295
} ;
9296
write ( root, " img.bin" , & blob ( 1 ) ) ;
9297
write ( root, " a.txt" , b " a\n " ) ;
9298
repo.commit ( " add img" , & [ ] ) .unwrap ( ) ;
9299
repo.bookmark_create ( " other" , None ) .unwrap ( ) ;
9300
write ( root, " img.bin" , & blob ( 2 ) ) ;
9301
repo.commit ( " img two" , & [ ] ) .unwrap ( ) ;
9302
repo.bookmark_switch ( " other" , false ) .unwrap ( ) ;
9303
write ( root, " img.bin" , & blob ( 3 ) ) ;
9304
repo.commit ( " img three" , & [ ] ) .unwrap ( ) ;
9305
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
9306
repo.merge ( " other" ) .unwrap ( ) ;
9307
9308
9309
write ( root, " img.bin" , & blob ( 9 ) ) ;
9310
write ( root, " a.txt" , b " a\n more\n " ) ;
9311
assert! (
9312
repo.commit ( " edit a" , & [ ] ) .is_err ( ) ,
9313
" refused while conflicted"
9314
) ;
9315
let snap = repo.snapshot ( " oxy resolve" ) .unwrap ( ) ;
9316
assert! (
9317
repo.choose_resolutions ( & snap.base_snapshot, & [ " a.txt" .to_string ( ) ] )
9318
.is_err ( ) ,
9319
" a.txt has no replace conflict"
9320
) ;
9321
repo.choose_resolutions ( & snap.base_snapshot, & [ " img.bin" .to_string ( ) ] )
9322
.unwrap ( ) ;
9323
let snap = repo.snapshot ( " oxy resolve" ) .unwrap ( ) ;
9324
assert! ( snap
9325
.statuses
9326
.iter ( )
9327
.any ( | s | s.path = = " img.bin" & & s.status = = ChangeStatus:: Resolved ) ) ;
9328
let c = repo.commit ( " settle img" , & [ ] ) .unwrap ( ) ;
9329
assert_eq! (
9330
repo.store ( ) .get_change ( & c) .unwrap ( ) .kind,
9331
ChangeKind:: Resolution
9332
) ;
9333
let after = repo
9334
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
9335
.unwrap ( )
9336
.materialize ( ) ;
9337
assert! ( ! after.is_conflicted ( ) ) ;
9338
assert_eq! (
9339
after.files[ " img.bin" ] .content,
9340
FileContent:: Opaque {
9341
blobs: vec! [ Repo:: blob_ref( & blob ( 9 ) ) ]
9342
}
9343
) ;
9344
assert! (
9345
matches! ( & after.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n more\n " )
9346
) ;
9347
9348
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
9349
assert! ( snap.statuses.is_empty ( ) , " {:?}" , snap.statuses) ;
9350
assert! ( repo.resolutions ( ) .unwrap ( ) .is_empty ( ) ) ;
9351
}
9352
9353
9354
# [ test ]
9355
fn a_pin_conflict_is_settled_from_the_working_copy ( ) {
9356
hermetic ( ) ;
9357
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9358
let root = dir.path ( ) ;
9359
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9360
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9361
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9362
.unwrap ( )
9363
.with_home ( root.join ( " home" ) ) ;
9364
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9365
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9366
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9367
let app_dir = root.join ( " app" ) ;
9368
let mut app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9369
write ( & app_dir, " main.txt" , b " app\n " ) ;
9370
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9371
app.commit ( " pin v1" , & [ ] ) .unwrap ( ) ;
9372
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) )
9373
.unwrap ( )
9374
.with_home ( root.join ( " home" ) ) ;
9375
fn pin ( app : & Repo, nested : & Repo, text : & [u8 ], message : & str ) {
9376
write ( nested.root ( ) , " lib.txt" , text) ;
9377
nested.commit ( message, & [ ] ) .unwrap ( ) ;
9378
app.commit ( & format! ( " pin {message} " ) , & [ ] ) .unwrap ( ) ;
9379
}
9380
app.bookmark_create ( " other" , None ) .unwrap ( ) ;
9381
pin ( & app, & nested, b " v2\n " , " v2" ) ;
9382
app.bookmark_switch ( " other" , false ) .unwrap ( ) ;
9383
pin ( & app, & nested, b " v3\n " , " v3" ) ;
9384
app.bookmark_switch ( " main" , false ) .unwrap ( ) ;
9385
app.merge ( " other" ) .unwrap ( ) ;
9386
let merged = app
9387
.load_state ( & app.bookmark_heads ( ) .unwrap ( ) )
9388
.unwrap ( )
9389
.materialize ( ) ;
9390
assert! (
9391
matches! ( & merged.files[ " libs/lib" ] .content, FileContent:: Opaque { blobs } if blobs.len ( ) = = 2 ) ,
9392
" a pin conflict"
9393
) ;
9394
9395
write ( & app_dir.join ( " libs/lib" ) , " lib.txt" , b " v4\n " ) ;
9396
nested.commit ( " v4" , & [ ] ) .unwrap ( ) ;
9397
let snap = app.snapshot ( " oxy resolve" ) .unwrap ( ) ;
9398
app.choose_resolutions ( & snap.base_snapshot, & [ " libs/lib" .to_string ( ) ] )
9399
.unwrap ( ) ;
9400
let c = app.commit ( " settle the pin" , & [ ] ) .unwrap ( ) ;
9401
assert_eq! (
9402
app.store ( ) .get_change ( & c) .unwrap ( ) .kind,
9403
ChangeKind:: Resolution
9404
) ;
9405
let after = app
9406
.load_state ( & app.bookmark_heads ( ) .unwrap ( ) )
9407
.unwrap ( )
9408
.materialize ( ) ;
9409
assert! ( ! after.is_conflicted ( ) ) ;
9410
assert! ( app.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
9411
}
9412
9413
# [ test ]
72484195 Andy McCall 1 month ago ↶
9414
fn carrying_work_over_a_subrepo_already_at_the_target_pin_is_no_conflict ( ) {
9415
9416
9417
hermetic ( ) ;
9418
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9419
let root = dir.path ( ) ;
9420
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9421
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9422
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9423
.unwrap ( )
9424
.with_home ( root.join ( " home" ) ) ;
9425
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9426
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9427
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9428
let app_dir = root.join ( " app" ) ;
9429
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9430
write ( & app_dir, " main.txt" , b " app\n " ) ;
9431
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9432
9433
fs:: rename( app_dir.join ( " libs/lib" ) , root.join ( " aside" ) ) .unwrap ( ) ;
9434
app.commit ( " initial" , & [ ] ) .unwrap ( ) ;
9435
let stale = app.bookmark_heads ( ) .unwrap ( ) ;
9436
fs:: rename( root.join ( " aside" ) , app_dir.join ( " libs/lib" ) ) .unwrap ( ) ;
9437
app.commit ( " pin lib" , & [ ] ) .unwrap ( ) ;
9438
let newer = app.bookmark_heads ( ) .unwrap ( ) ;
9439
app.checkout ( & stale, false , " test" ) .unwrap ( ) ;
9440
write ( & app_dir, " notes.txt" , b " mine\n " ) ;
9441
let ( _ , carried) = app.checkout_carry ( & newer, " main" , " test" ) .unwrap ( ) ;
9442
assert_eq! ( carried, vec! [ " notes.txt" .to_string ( ) ] ) ;
9443
assert! ( app_dir.join ( " notes.txt" ) .exists ( ) ) ;
9444
}
9445
c8d9059e Andy McCall 12 days ago ↶
9446
9447
72484195 Andy McCall 1 month ago ↶
9448
# [ test ]
c8d9059e Andy McCall 12 days ago ↶
9449
fn a_new_bookmark_sends_only_what_the_remote_lacks ( ) {
9450
hermetic ( ) ;
9451
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9452
let root = dir.path ( ) ;
9453
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
9454
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
9455
let mut one = Repo:: clone( & url, & root.join ( " one" ) )
9456
.unwrap ( )
9457
.with_home ( root.join ( " home" ) ) ;
9458
for n in 0 .. 5 {
9459
write ( & root.join ( " one" ) , " a.txt" , format! ( " {n} \n " ) .as_bytes ( ) ) ;
9460
one.commit ( & format! ( " main {n} " ) , & [ ] ) .unwrap ( ) ;
9461
}
9462
assert! ( one.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9463
one.bookmark_create ( " feature" , None ) .unwrap ( ) ;
9464
one.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
9465
write ( & root.join ( " one" ) , " b.txt" , b " feature\n " ) ;
9466
let added = one.commit ( " feature" , & [ ] ) .unwrap ( ) ;
9467
let heads = one.bookmark_heads ( ) .unwrap ( ) ;
9468
let refs = one
9469
.open_named_remote ( " origin" )
9470
.unwrap ( )
9471
.refs ( )
9472
.unwrap ( )
9473
.refs;
9474
assert! (
9475
refs.iter ( ) .all ( | r | r.name ! = " feature" ) ,
9476
" new to the remote"
9477
) ;
9478
let sent = one
9479
.negotiate ( & one.remote_holds ( & refs, & [ ] ) , & heads)
9480
.unwrap ( ) ;
9481
assert_eq! ( sent, vec! [ added] , " only the change main lacks" ) ;
9482
assert! ( one.push_bookmark ( " origin" , " feature" , false ) .unwrap ( ) [ 0 ] .ok) ;
9483
let two = Repo:: clone( & url, & root.join ( " two" ) ) .unwrap ( ) ;
9484
assert! ( two.store ( ) .has ( & added) ) ;
9485
}
9486
9487
# [ test ]
72484195 Andy McCall 1 month ago ↶
9488
fn a_bookmark_only_behind_its_remote_catches_up ( ) {
9489
9490
hermetic ( ) ;
9491
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9492
let root = dir.path ( ) ;
9493
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
9494
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
9495
let one = Repo:: clone( & url, & root.join ( " one" ) )
9496
.unwrap ( )
9497
.with_home ( root.join ( " home" ) ) ;
9498
write ( & root.join ( " one" ) , " a.txt" , b " a\n " ) ;
9499
one.commit ( " A" , & [ ] ) .unwrap ( ) ;
9500
assert! ( one.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9501
let two = Repo:: clone( & url, & root.join ( " two" ) )
9502
.unwrap ( )
9503
.with_home ( root.join ( " home" ) ) ;
9504
write ( & root.join ( " two" ) , " b.txt" , b " b\n " ) ;
9505
two.commit ( " B" , & [ ] ) .unwrap ( ) ;
9506
assert! ( two.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9507
assert_eq! ( one.catch_up_bookmark ( " main" ) .unwrap ( ) , None , " not fetched" ) ;
9508
one.fetch ( " origin" ) .unwrap ( ) ;
9509
assert_eq! ( one.catch_up_bookmark ( " main" ) .unwrap ( ) , Some ( 1 ) ) ;
9510
assert_eq! ( one.bookmarks ( ) .unwrap ( ) [ 0 ] .behind, Some ( 0 ) ) ;
9511
9512
write ( & root.join ( " one" ) , " c.txt" , b " c\n " ) ;
9513
one.commit ( " C" , & [ ] ) .unwrap ( ) ;
9514
write ( & root.join ( " two" ) , " d.txt" , b " d\n " ) ;
9515
two.commit ( " D" , & [ ] ) .unwrap ( ) ;
9516
assert! ( two.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9517
one.fetch ( " origin" ) .unwrap ( ) ;
9518
assert_eq! ( one.catch_up_bookmark ( " main" ) .unwrap ( ) , None ) ;
9519
}
42ccf504 Andy McCall 29 days ago ↶
9520
9521
# [ test ]
9522
fn a_bookmark_is_compared_with_a_remote_that_has_it_when_there_is_no_origin ( ) {
9523
9524
hermetic ( ) ;
9525
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9526
let root = dir.path ( ) ;
9527
let _up = Repo:: init( & root.join ( " up" ) ) .unwrap ( ) ;
9528
let url = root.join ( " up" ) .to_string_lossy ( ) .to_string ( ) ;
9529
let other = Repo:: clone( & url, & root.join ( " other" ) )
9530
.unwrap ( )
9531
.with_home ( root.join ( " home" ) ) ;
9532
write ( & root.join ( " other" ) , " a.txt" , b " a\n " ) ;
9533
other.commit ( " a" , & [ ] ) .unwrap ( ) ;
9534
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9535
let mut one = Repo:: clone( & url, & root.join ( " one" ) )
9536
.unwrap ( )
9537
.with_home ( root.join ( " home" ) ) ;
9538
one.config.set ( " remote.earth.url" , & url) .unwrap ( ) ;
9539
one.config.set ( " remote.gcs.url" , & url) .unwrap ( ) ;
9540
one.config.set ( " remote.origin.url" , " " ) .unwrap ( ) ;
9541
one.save_config ( ) .unwrap ( ) ;
9542
write ( & root.join ( " other" ) , " b.txt" , b " b\n " ) ;
9543
other.commit ( " b" , & [ ] ) .unwrap ( ) ;
9544
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9545
one.fetch ( " earth" ) .unwrap ( ) ;
9546
let b = one
9547
.bookmarks ( )
9548
.unwrap ( )
9549
.into_iter ( )
9550
.find ( | b | b.name = = " main" )
9551
.unwrap ( ) ;
9552
assert_eq! (
9553
( b.remote.as_deref ( ) , b.behind) ,
9554
( Some ( " earth" ) , Some ( 1 ) ) ,
9555
" a stale origin copy is not used"
9556
) ;
9557
assert_eq! (
9558
one.default_remote ( ) ,
9559
" origin" ,
9560
" no default chosen among several"
9561
) ;
9562
let e = one.pull ( & one.default_remote ( ) ) .unwrap_err ( ) .to_string ( ) ;
9563
assert! ( e.contains ( " earth, gcs" ) & & e.contains ( " core.remote" ) , " {e}" ) ;
9564
one.config.set ( " core.remote" , " earth" ) .unwrap ( ) ;
9565
assert_eq! ( one.default_remote ( ) , " earth" ) ;
9566
}
cbe8499a Andy McCall 29 days ago ↶
9567
9568
# [ test ]
9569
fn a_missing_author_identity_says_how_to_fix_it ( ) {
9570
9571
9572
hermetic ( ) ;
9573
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9574
let mut repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9575
let missing = KeyId:: from_hex( & " ab" .repeat ( 32 ) ) .unwrap ( ) ;
9576
repo.config.set ( " author.key" , & missing.to_hex ( ) ) .unwrap ( ) ;
9577
let e = repo.identity ( & missing) .unwrap_err ( ) .to_string ( ) ;
9578
assert! (
9579
e.contains ( " this repository has no identity" ) & & e.contains ( " oxy config author.key" ) ,
9580
" {e}"
9581
) ;
9582
assert! ( e.contains ( " this repository's config" ) , " {e}" ) ;
9583
}
f6dccc45 Andy McCall 28 days ago ↶
9584
9585
# [ test ]
9586
fn a_working_copy_behind_its_bookmark_is_flagged_and_pull_brings_it_up ( ) {
9587
9588
9589
9590
hermetic ( ) ;
9591
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9592
let root = dir.path ( ) ;
9593
let _up = Repo:: init( & root.join ( " up" ) ) .unwrap ( ) ;
9594
let url = root.join ( " up" ) .to_string_lossy ( ) .to_string ( ) ;
9595
let other = Repo:: clone( & url, & root.join ( " other" ) )
9596
.unwrap ( )
9597
.with_home ( root.join ( " home" ) ) ;
9598
write ( & root.join ( " other" ) , " a.txt" , b " a\n " ) ;
9599
other.commit ( " a" , & [ ] ) .unwrap ( ) ;
9600
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9601
let one = Repo:: clone( & url, & root.join ( " one" ) )
9602
.unwrap ( )
9603
.with_home ( root.join ( " home" ) ) ;
9604
write ( & root.join ( " other" ) , " b.txt" , b " b\n " ) ;
9605
other.commit ( " b" , & [ ] ) .unwrap ( ) ;
9606
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9607
one.fetch ( " origin" ) .unwrap ( ) ;
9608
9609
let theirs = one.resolve_heads ( " origin/main" ) .unwrap ( ) ;
9610
one.bookmark_set ( " main" , & theirs) .unwrap ( ) ;
9611
assert! ( ! root.join ( " one/b.txt" ) .exists ( ) ) ;
9612
assert_eq! ( one.working_copy_behind ( ) .unwrap ( ) , Some ( 1 ) ) ;
9613
let r = one.pull ( " origin" ) .unwrap ( ) ;
9614
assert! (
9615
! r.changed & & r.checked_out,
9616
" nothing new, but the working copy caught up"
9617
) ;
9618
assert! ( root.join ( " one/b.txt" ) .exists ( ) ) ;
9619
assert_eq! ( one.working_copy_behind ( ) .unwrap ( ) , None ) ;
9620
}
170bb436 Andy McCall 28 days ago ↶
9621
9622
# [ test ]
9623
fn touched_nodes_cache_answers_as_the_changes_do ( ) {
9624
hermetic ( ) ;
9625
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9626
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9627
write ( dir.path ( ) , " a.txt" , b " one\n " ) ;
9628
write ( dir.path ( ) , " b.txt" , b " x\n " ) ;
9629
repo.snapshot ( " t" ) .unwrap ( ) ;
9630
let c1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
9631
write ( dir.path ( ) , " b.txt" , b " y\n " ) ;
9632
repo.snapshot ( " t" ) .unwrap ( ) ;
9633
repo.commit ( " b only" , & [ ] ) .unwrap ( ) ;
9634
fs:: rename( dir.path ( ) .join ( " a.txt" ) , dir.path ( ) .join ( " c.txt" ) ) .unwrap ( ) ;
9635
write ( dir.path ( ) , " c.txt" , b " one\n two\n " ) ;
9636
repo.snapshot ( " t" ) .unwrap ( ) ;
9637
let c3 = repo.commit ( " rename and edit" , & [ ] ) .unwrap ( ) ;
9638
9639
write ( dir.path ( ) , " c.txt" , b " one\n two\n three\n " ) ;
9640
repo.snapshot ( " t" ) .unwrap ( ) ;
9641
9642
let path = dir.path ( ) .join ( " .oxy/index/touched" ) ;
9643
let hashes = | es : Vec< LogEntry> | es.iter ( ) .map ( | e | e.hash ) .collect:: < Vec< _ > > ( ) ;
9644
let cold = hashes ( repo.log_for_path ( " c.txt" ) .unwrap ( ) ) ;
9645
assert! ( path.exists ( ) , " the first query writes the cache" ) ;
9646
let warm = hashes ( repo.log_for_path ( " c.txt" ) .unwrap ( ) ) ;
9647
assert_eq! ( cold, warm, " the cache answers as the changes do" ) ;
9648
let ( wc, _ ) = repo.working_copy ( ) .unwrap ( ) ;
9649
assert_eq! (
9650
cold,
9651
vec! [ wc, c3, c1] ,
9652
" renames followed, the working copy included"
9653
) ;
9654
9655
9656
let cached = super :: touched:: read_touched_for_test( & fs:: read( & path) .unwrap ( ) ) .unwrap ( ) ;
9657
assert! ( ! cached.contains ( & wc) , " a working-copy change is not cached" ) ;
9658
assert! ( cached.contains ( & c1) & & cached.contains ( & c3) ) ;
9659
9660
9661
let mut bytes = fs:: read( & path) .unwrap ( ) ;
9662
let last = bytes.len ( ) - 40 ;
9663
bytes[ last] ^ = 1 ;
9664
fs:: write( & path, & bytes) .unwrap ( ) ;
9665
assert_eq! ( hashes ( repo.log_for_path ( " c.txt" ) .unwrap ( ) ) , cold) ;
9666
let touches = repo.log_revset ( " touches(\" c.txt\" ) ~ @" ) .unwrap ( ) ;
9667
assert_eq! ( hashes ( touches) , vec! [ c3, c1] ) ;
9668
}
e8e6e0ca Andy McCall 27 days ago ↶
9669
9670
9671
9672
9673
9674
# [ test ]
9675
fn git_import_removes_every_copy_of_a_file_at_one_place ( ) {
9676
hermetic ( ) ;
9677
if std:: process:: Command:: new( " git" )
9678
.arg ( " --version" )
9679
.output ( )
9680
.is_err ( )
9681
{
9682
eprintln! ( " git not installed; skipping" ) ;
9683
return ;
9684
}
9685
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9686
let root = dir.path ( ) ;
9687
let g = root.join ( " src.git" ) ;
9688
fs:: create_dir_all( & g) .unwrap ( ) ;
9689
let git = | args : & [& str ]| {
9690
let out = std:: process:: Command:: new( " git" )
9691
.arg ( " -C" )
9692
.arg ( & g)
9693
.args ( [
9694
" -c" ,
9695
" user.name=Grace" ,
9696
" -c" ,
9697
" [email protected] " ,
9698
" -c" ,
9699
" commit.gpgsign=false" ,
9700
] )
9701
.args ( args)
9702
.output ( )
9703
.unwrap ( ) ;
9704
assert! (
9705
out.status.success ( ) ,
9706
" git {args:?}: {}" ,
9707
String :: from_utf8_lossy( & out.stderr)
9708
) ;
9709
} ;
9710
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
9711
write ( & g, " d/list.txt" , b " one\n two\n three\n four\n " ) ;
9712
write ( & g, " keep.txt" , b " k\n " ) ;
9713
git ( & [ " add" , " ." ] ) ;
9714
git ( & [ " commit" , " -qm" , " base" ] ) ;
9715
fs:: create_dir_all( g.join ( " d/sub" ) ) .unwrap ( ) ;
9716
git ( & [ " mv" , " d/list.txt" , " d/sub/list.txt" ] ) ;
9717
git ( & [ " commit" , " -qm" , " into sub" ] ) ;
9718
git ( & [ " checkout" , " -qb" , " side" ] ) ;
9719
git ( & [ " mv" , " d/sub/list.txt" , " d/list.txt" ] ) ;
9720
git ( & [ " commit" , " -qm" , " back, on side" ] ) ;
9721
git ( & [ " checkout" , " -q" , " main" ] ) ;
9722
git ( & [ " mv" , " d/sub/list.txt" , " d/list.txt" ] ) ;
9723
git ( & [ " commit" , " -qm" , " back, on main" ] ) ;
9724
git ( & [ " merge" , " -q" , " --no-edit" , " side" ] ) ;
9725
git ( & [ " rm" , " -rq" , " d" ] ) ;
9726
git ( & [ " commit" , " -qm" , " d goes" ] ) ;
9727
9728
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
9729
repo.import_git ( & g, " main" ) .unwrap ( ) ;
9730
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
9731
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
9732
let st = repo
9733
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
9734
.unwrap ( ) ;
9735
let view = st.tree ( ) .view ( ) ;
9736
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
9737
assert_eq! ( view.entries.keys ( ) .collect:: < Vec< _ > > ( ) , vec! [ " keep.txt" ] ) ;
9738
}
5552e69c Andy McCall 27 days ago ↶
9739
9740
9741
9742
9743
# [ test ]
9744
fn a_damaged_file_history_in_the_state_index_is_rebuilt ( ) {
9745
hermetic ( ) ;
9746
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9747
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9748
for i in 0 .. 6 {
9749
write (
9750
dir.path ( ) ,
9751
& format! ( " f{i} .txt" ) ,
9752
format! ( " one {i} \n " ) .as_bytes ( ) ,
9753
) ;
9754
}
9755
repo.snapshot ( " t" ) .unwrap ( ) ;
9756
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
9757
for i in 0 .. 6 {
9758
write (
9759
dir.path ( ) ,
9760
& format! ( " f{i} .txt" ) ,
9761
format! ( " one {i} \n two\n " ) .as_bytes ( ) ,
9762
) ;
9763
}
9764
repo.snapshot ( " t" ) .unwrap ( ) ;
9765
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
9766
let want: Vec< Vec< u8 > > = ( 0 .. 6 )
9767
.map ( | i | format! ( " one {i} \n two\n " ) .into_bytes ( ) )
9768
.collect ( ) ;
9769
9770
9771
let ( _ , wc) = repo.working_copy ( ) .unwrap ( ) ;
9772
let base = repo.load_state_history ( & wc.deps) .unwrap ( ) ;
9773
repo.write_state_index ( & wc.deps, & base) ;
9774
for ( i, w) in want.iter ( ) .enumerate ( ) {
9775
assert_eq! ( & repo.file_at ( & format! ( " f{i} .txt" ) , None ) .unwrap ( ) , w) ;
9776
}
9777
let index = repo.state_indexes ( ) ;
9778
assert! ( ! index.is_empty ( ) , " a state index was written" ) ;
9779
9780
let mut bytes = fs:: read( & index[ 0 ] ) .unwrap ( ) ;
9781
let last = bytes.len ( ) - 1 ;
9782
bytes[ last] ^ = 0x40 ;
9783
fs:: write( & index[ 0 ] , & bytes) .unwrap ( ) ;
9784
9785
let reads: Vec< _ > = ( 0 .. 6 )
9786
.map ( | i | repo.file_at ( & format! ( " f{i} .txt" ) , None ) )
9787
.collect ( ) ;
9788
let damaged: Vec< String> = reads
9789
.iter ( )
9790
.filter_map ( | r | r.as_ref ( ) .err ( ) .map ( | e | e.to_string ( ) ) )
9791
.collect ( ) ;
9792
assert_eq! ( damaged.len ( ) , 1 , " {damaged:?}" ) ;
9793
assert! (
9794
damaged[ 0 ] .contains ( " state index was damaged" )
9795
& & damaged[ 0 ] .contains ( " run the command again" ) ,
9796
" {}" ,
9797
damaged[ 0 ]
9798
) ;
9799
9800
for ( i, w) in want.iter ( ) .enumerate ( ) {
9801
assert_eq! ( & repo.file_at ( & format! ( " f{i} .txt" ) , None ) .unwrap ( ) , w) ;
9802
}
9803
}
cb8b8082 Andy McCall 27 days ago ↶
9804
9805
9806
9807
9808
9809
# [ test ]
9810
fn a_long_push_goes_in_parts ( ) {
9811
hermetic ( ) ;
9812
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9813
let root = dir.path ( ) ;
9814
let origin = root.join ( " origin" ) ;
9815
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
9816
let main = root.join ( " main" ) ;
9817
let mut repo = Repo:: init( & main) .unwrap ( ) ;
9818
repo.config
9819
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
9820
.unwrap ( ) ;
9821
9822
repo.config.set ( " push.batch-bytes" , " 1" ) .unwrap ( ) ;
9823
repo.save_config ( ) .unwrap ( ) ;
9824
for i in 0 .. 6 {
9825
write (
9826
& main,
9827
& format! ( " f{i} .txt" ) ,
9828
format! ( " file {i} \n " ) .as_bytes ( ) ,
9829
) ;
9830
repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ;
9831
}
9832
let mut parts = Vec :: new( ) ;
9833
let results = repo
9834
.push_bookmark_with ( " origin" , " main" , false , & mut | m| parts.push ( m.to_string ( ) ) )
9835
.unwrap ( ) ;
9836
assert! (
9837
results.iter ( ) .any ( | r | r.ok & & r.name = = " main" ) ,
9838
" {results:?}"
9839
) ;
9840
assert_eq! ( parts.len ( ) , 6 , " {parts:?}" ) ;
9841
assert_eq! ( parts[ 0 ] , " part 1 of 6: 1 changes" ) ;
9842
9843
let remote = Repo:: open( & origin) .unwrap ( ) ;
9844
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
9845
assert_eq! ( remote.bookmark_heads ( ) .unwrap ( ) , heads) ;
9846
assert_eq! (
9847
remote.log_state ( & heads) .unwrap ( ) .len ( ) ,
9848
repo.log_state ( & heads) .unwrap ( ) .len ( )
9849
) ;
9850
9851
let got = remote
9852
.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ ] )
9853
.unwrap ( ) ;
9854
assert_eq! ( got.len ( ) , 1 ) ;
9855
assert! ( got[ 0 ] .ok & & got[ 0 ] .name = = OBJECTS_RESULT , " {got:?}" ) ;
9856
}
d6e0d5b8 Andy McCall 27 days ago ↶
9857
d3b27090 Andy McCall 13 days ago ↶
9858
9859
9860
9861
9862
# [ test ]
9863
fn a_heavy_change_sends_its_blobs_in_parts ( ) {
9864
hermetic ( ) ;
9865
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9866
let root = dir.path ( ) ;
9867
let origin = root.join ( " origin" ) ;
9868
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
9869
let main = root.join ( " main" ) ;
9870
let mut repo = Repo:: init( & main) .unwrap ( ) ;
9871
repo.config
9872
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
9873
.unwrap ( ) ;
9874
repo.config.set ( " push.batch-bytes" , " 50000" ) .unwrap ( ) ;
9875
repo.save_config ( ) .unwrap ( ) ;
9876
let mut x: u64 = 0x9e3779b97f4a7c15 ;
9877
let mut noise = | n : usize | -> Vec< u8 > {
9878
( 0 .. n)
9879
.map ( | _| {
9880
x ^ = x < < 13 ;
9881
x ^ = x > > 7 ;
9882
x ^ = x < < 17 ;
9883
( x > > 24 ) as u8
9884
} )
9885
.collect ( )
9886
} ;
9887
let files: Vec< Vec< u8 > > = ( 0 .. 4 ) .map ( | _| noise ( 40_000 ) ) .collect ( ) ;
9888
for ( i, f) in files.iter ( ) .enumerate ( ) {
9889
write ( & main, & format! ( " bin{i} .dat" ) , f) ;
9890
}
9891
repo.commit ( " four blobs" , & [ ] ) .unwrap ( ) ;
9892
let mut parts = Vec :: new( ) ;
9893
let results = repo
9894
.push_bookmark_with ( " origin" , " main" , false , & mut | m| parts.push ( m.to_string ( ) ) )
9895
.unwrap ( ) ;
9896
assert! (
9897
results.iter ( ) .any ( | r | r.ok & & r.name = = " main" ) ,
9898
" {results:?}"
9899
) ;
9900
assert! (
9901
parts.len ( ) > = 3 & & parts[ 0 ] .starts_with ( " objects 1 of" ) ,
9902
" {parts:?}"
9903
) ;
9904
let back = Repo:: clone( & origin.to_string_lossy ( ) , & root.join ( " back" ) ) .unwrap ( ) ;
9905
for ( i, f) in files.iter ( ) .enumerate ( ) {
9906
assert_eq! ( & fs:: read( root.join ( format! ( " back/bin{i} .dat" ) ) ) .unwrap ( ) , f) ;
9907
}
9908
drop ( back) ;
9909
}
9910
570e3531 Andy McCall 12 days ago ↶
9911
9912
9913
9914
9915
# [ test ]
9916
fn a_replacement_does_not_send_the_old_content_again ( ) {
9917
hermetic ( ) ;
9918
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9919
let root = dir.path ( ) ;
9920
let origin = root.join ( " origin" ) ;
9921
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
9922
let main = root.join ( " main" ) ;
9923
let mut repo = Repo:: init( & main) .unwrap ( ) ;
9924
repo.config
9925
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
9926
.unwrap ( ) ;
9927
repo.save_config ( ) .unwrap ( ) ;
9928
let blobs = | b : & Bundle| {
9929
b.objects
9930
.iter ( )
9931
.filter ( | ( ty , _) | * ty = = ObjectType:: Blob)
9932
.count ( )
9933
} ;
9934
write ( & main, " bin.dat" , b " \0 \x01 first binary" ) ;
9935
write ( & main, " gone.dat" , b " \0 \x02 to be removed" ) ;
9936
let added = repo.commit ( " add" , & [ ] ) .unwrap ( ) ;
9937
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9938
write ( & main, " bin.dat" , b " \0 \x01 second binary" ) ;
9939
fs:: remove_file( main.join ( " gone.dat" ) ) .unwrap ( ) ;
9940
let changed = repo.commit ( " replace and remove" , & [ ] ) .unwrap ( ) ;
9941
assert_eq! ( blobs ( & repo.bundle_for ( & [ changed] ) .unwrap ( ) ) , 1 ) ;
9942
assert_eq! ( blobs ( & repo.bundle_for ( & [ added, changed] ) .unwrap ( ) ) , 3 ) ;
9943
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9944
let origin = Repo:: open( & origin) .unwrap ( ) ;
9945
assert_eq! ( origin.fsck ( ) .unwrap ( ) .problems, vec! [ ] ) ;
9946
let back = Repo:: clone( & origin.root ( ) .to_string_lossy ( ) , & root.join ( " back" ) ) .unwrap ( ) ;
9947
assert_eq! ( back.fsck ( ) .unwrap ( ) .problems, vec! [ ] ) ;
9948
assert_eq! (
9949
fs:: read( root.join ( " back/bin.dat" ) ) .unwrap ( ) ,
9950
b " \0 \x01 second binary"
9951
) ;
9952
assert! ( ! root.join ( " back/gone.dat" ) .exists ( ) ) ;
9953
}
9954
d6e0d5b8 Andy McCall 27 days ago ↶
9955
9956
9957
9958
# [ test ]
9959
fn git_import_claims_your_own_commits ( ) {
9960
hermetic ( ) ;
9961
if std:: process:: Command:: new( " git" )
9962
.arg ( " --version" )
9963
.output ( )
9964
.is_err ( )
9965
{
9966
eprintln! ( " git not installed; skipping" ) ;
9967
return ;
9968
}
9969
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9970
let root = dir.path ( ) ;
9971
let g = root.join ( " src.git" ) ;
9972
fs:: create_dir_all( & g) .unwrap ( ) ;
9973
let git = | who : & str , args : & [& str ]| {
9974
let ( name, email) = who.split_once ( ' ' ) .unwrap ( ) ;
9975
let out = std:: process:: Command:: new( " git" )
9976
.arg ( " -C" )
9977
.arg ( & g)
9978
.args ( [
9979
" -c" ,
9980
& format! ( " user.name={name} " ) ,
9981
" -c" ,
9982
& format! ( " user.email={email} " ) ,
9983
" -c" ,
9984
" commit.gpgsign=false" ,
9985
] )
9986
.args ( args)
9987
.output ( )
9988
.unwrap ( ) ;
9989
assert! (
9990
out.status.success ( ) ,
9991
" git {args:?}: {}" ,
9992
String :: from_utf8_lossy( & out.stderr)
9993
) ;
9994
} ;
9995
git ( " Grace [email protected] " , & [ " init" , " -q" , " -b" , " main" ] ) ;
9996
write ( & g, " a.txt" , b " a\n " ) ;
9997
git ( " Grace [email protected] " , & [ " add" , " ." ] ) ;
9998
git ( " Grace [email protected] " , & [ " commit" , " -qm" , " mine" ] ) ;
9999
write ( & g, " b.txt" , b " b\n " ) ;
10000
git ( " Ada [email protected] " , & [ " add" , " ." ] ) ;
10001
git ( " Ada [email protected] " , & [ " commit" , " -qm" , " someone else's" ] ) ;
10002
10003
let mut repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
10004
let ( key, _ ) = repo
10005
.create_identity ( " Grace" , " [email protected] " , IdentityScope:: Repo)
10006
.unwrap ( ) ;
10007
repo.config.set ( " git.claim" , " true" ) .unwrap ( ) ;
10008
repo.save_config ( ) .unwrap ( ) ;
10009
let repo = Repo:: open( & root.join ( " o" ) ) .unwrap ( ) ;
10010
repo.import_git ( & g, " main" ) .unwrap ( ) ;
10011
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
10012
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
10013
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
10014
let by_message: std:: collections:: HashMap< String, KeyId> = repo
10015
.log_state ( & heads)
10016
.unwrap ( )
10017
.into_iter ( )
10018
.map ( | e | {
10019
(
10020
e.change.message.lines ( ) .next ( ) .unwrap_or ( " " ) .to_string ( ) ,
10021
e.change.author,
10022
)
10023
} )
10024
.collect ( ) ;
10025
assert_eq! ( by_message[ " mine" ] , key, " her own commit, any case of email" ) ;
10026
assert_eq! ( by_message[ " someone else's" ] , ObjectRef:: ZERO , " not hers" ) ;
10027
}
34fad4a9 Andy McCall 27 days ago ↶
10028
10029
10030
10031
# [ test ]
10032
fn removing_a_remote_leaves_nothing_of_it ( ) {
10033
hermetic ( ) ;
10034
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10035
let origin = dir.path ( ) .join ( " origin" ) ;
10036
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
10037
let main = dir.path ( ) .join ( " main" ) ;
10038
let mut repo = Repo:: init( & main) .unwrap ( ) ;
10039
repo.config
10040
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
10041
.unwrap ( ) ;
10042
repo.config
10043
.set ( " remote.origin.token" , " secret-ish" )
10044
.unwrap ( ) ;
10045
repo.config
10046
.set ( " remote.keep.url" , & origin.to_string_lossy ( ) )
10047
.unwrap ( ) ;
10048
repo.save_config ( ) .unwrap ( ) ;
10049
write ( & main, " a.txt" , b " a\n " ) ;
10050
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10051
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10052
assert! (
10053
repo.view ( ) .unwrap ( ) .remotes.contains_key ( " origin" ) ,
10054
" a tracking bookmark"
10055
) ;
10056
10057
repo.remove_remote ( " origin" ) .unwrap ( ) ;
10058
let again = Repo:: open( & main) .unwrap ( ) ;
10059
assert! (
10060
! again
10061
.config
10062
.repo_layer ( )
10063
.keys ( )
10064
.any ( | k | k.starts_with ( " remote.origin." ) ) ,
10065
" {:?}" ,
10066
again.config.repo_layer ( )
10067
) ;
10068
assert! ( ! again.config.remotes.contains_key ( " origin" ) ) ;
10069
assert! (
10070
! again.view ( ) .unwrap ( ) .remotes.contains_key ( " origin" ) ,
10071
" its tracking bookmarks go"
10072
) ;
10073
assert! (
10074
again.config.remotes.contains_key ( " keep" ) ,
10075
" another remote stays"
10076
) ;
10077
let mut again = again;
10078
assert! (
10079
again.remove_remote ( " origin" ) .is_err ( ) ,
10080
" a second removal is refused"
10081
) ;
10082
}
9d580f21 Andy McCall 27 days ago ↶
10083
10084
10085
10086
10087
# [ test ]
10088
fn gc_keeps_the_blobs_a_host_names ( ) {
10089
hermetic ( ) ;
10090
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10091
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
10092
write ( dir.path ( ) , " a.txt" , b " a\n " ) ;
10093
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10094
10095
let body: Vec< u8 > = ( 0 .. 400_000 u32 )
10096
.flat_map ( | i | ( i.wrapping_mul ( 2654435761 ) ) .to_le_bytes ( ) )
10097
.collect ( ) ;
10098
let kept = repo.store_blob ( & body) .unwrap ( ) ;
10099
let lost = repo.store_blob ( b " an artifact nobody names" ) .unwrap ( ) ;
10100
repo.gc_keeping ( true , & [ kept] , & mut | _ | { } ) .unwrap ( ) ;
10101
assert_eq! ( repo.read_blob ( & kept) .unwrap ( ) , body, " named: kept whole" ) ;
10102
assert! ( ! repo.store ( ) .has ( & lost) , " not named: pruned" ) ;
10103
repo.gc ( true ) .unwrap ( ) ;
10104
assert! ( ! repo.store ( ) .has ( & kept) , " a plain gc prunes it" ) ;
10105
}
530a0fc6 Andy McCall 26 days ago ↶
10106
10107
10108
10109
10110
# [ test ]
10111
fn undo_is_scoped_to_its_workspace ( ) {
10112
hermetic ( ) ;
10113
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10114
let root = dir.path ( ) .join ( " main" ) ;
10115
let a = Repo:: init( & root) .unwrap ( ) ;
10116
write ( & root, " base.txt" , b " base\n " ) ;
10117
let base = a.commit ( " base" , & [ ] ) .unwrap ( ) ;
10118
a.bookmark_create ( " feature" , None ) .unwrap ( ) ;
10119
let ws_dir = dir.path ( ) .join ( " ws" ) ;
10120
let heads = a.resolve_heads ( " feature" ) .unwrap ( ) ;
10121
let b = a
10122
.workspace_add_on ( & ws_dir, Some ( & heads) , Some ( " feature" ) )
10123
.unwrap ( ) ;
10124
10125
10126
write ( & root, " a.txt" , b " a\n " ) ;
10127
let ca = a.commit ( " from a" , & [ ] ) .unwrap ( ) ;
10128
write ( & ws_dir, " b.txt" , b " b\n " ) ;
10129
let cb = b.commit ( " from b" , & [ ] ) .unwrap ( ) ;
10130
10131
10132
let u = a.undo ( ) .unwrap ( ) ;
10133
assert_eq! ( u.command, " oxy commit -m \" from a\" " ) ;
10134
assert_eq! ( u.others_kept, 1 , " B's newer commit is left" ) ;
10135
assert! ( ! u.changes.is_empty ( ) ) ;
10136
let view = a.view ( ) .unwrap ( ) ;
10137
10138
assert_eq! ( a.heads_of ( & view, " main" ) .unwrap ( ) , vec! [ base] ) ;
10139
assert! (
10140
a.heads_of ( & view, " feature" ) .unwrap ( ) .contains ( & cb) ,
10141
" B's commit stands"
10142
) ;
10143
assert! (
10144
a.store ( ) .get_change ( & ca) .is_ok ( ) ,
10145
" the change itself is still in the store"
10146
) ;
10147
10148
assert! ( ! root.join ( " a.txt" ) .exists ( ) ) ;
10149
assert! ( a.snapshot ( " oxy status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
10150
assert! ( ws_dir.join ( " b.txt" ) .exists ( ) ) ;
10151
10152
10153
let u = b.undo ( ) .unwrap ( ) ;
10154
assert_eq! ( u.command, " oxy commit -m \" from b\" " ) ;
10155
assert! ( ! b
10156
.heads_of ( & b.view ( ) .unwrap ( ) , " feature" )
10157
.unwrap ( )
10158
.contains ( & cb) ) ;
10159
assert_eq! (
10160
a.heads_of ( & a.view ( ) .unwrap ( ) , " main" ) .unwrap ( ) ,
10161
vec! [ base] ,
10162
" A's undo stands"
10163
) ;
10164
10165
10166
10167
let e = a.undo ( ) .unwrap_err ( ) .to_string ( ) ;
10168
assert! ( e.contains ( " the workspace at" ) & & e.contains ( " ws" ) , " {e}" ) ;
10169
}
10170
10171
10172
10173
# [ test ]
10174
fn undo_on_a_shared_bookmark_keeps_the_other_workspaces_head ( ) {
10175
hermetic ( ) ;
10176
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10177
let root = dir.path ( ) .join ( " main" ) ;
10178
let a = Repo:: init( & root) .unwrap ( ) ;
10179
write ( & root, " base.txt" , b " base\n " ) ;
10180
a.commit ( " base" , & [ ] ) .unwrap ( ) ;
10181
let ws_dir = dir.path ( ) .join ( " ws" ) ;
10182
let b = a.workspace_add ( & ws_dir, None ) .unwrap ( ) ;
10183
write ( & root, " a.txt" , b " a\n " ) ;
10184
let ca = a.commit ( " from a" , & [ ] ) .unwrap ( ) ;
10185
write ( & ws_dir, " b.txt" , b " b\n " ) ;
10186
let cb = b.commit ( " from b" , & [ ] ) .unwrap ( ) ;
10187
let u = a.undo ( ) .unwrap ( ) ;
10188
assert_eq! ( u.command, " oxy commit -m \" from a\" " ) ;
10189
let heads = a.heads_of ( & a.view ( ) .unwrap ( ) , " main" ) .unwrap ( ) ;
10190
assert! ( heads.contains ( & cb) & & ! heads.contains ( & ca) , " {heads:?}" ) ;
10191
}
10192
10193
10194
10195
# [ test ]
10196
fn undo_refuses_to_take_back_another_workspaces_work ( ) {
10197
hermetic ( ) ;
10198
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10199
let root = dir.path ( ) .join ( " main" ) ;
10200
let a = Repo:: init( & root) .unwrap ( ) ;
10201
write ( & root, " base.txt" , b " base\n " ) ;
10202
a.commit ( " base" , & [ ] ) .unwrap ( ) ;
10203
let ws_dir = dir.path ( ) .join ( " ws" ) ;
10204
let b = a.workspace_add ( & ws_dir, None ) .unwrap ( ) ;
10205
10206
write ( & root, " a.txt" , b " a\n " ) ;
10207
a.commit ( " from a" , & [ ] ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
10208
b.refresh ( ) .unwrap ( ) ;
530a0fc6 Andy McCall 26 days ago ↶
10209
let heads = b.resolve_heads ( " main" ) .unwrap ( ) ;
10210
b.checkout ( & heads, false , " oxy checkout" ) .unwrap ( ) ;
10211
write ( & ws_dir, " a.txt" , b " a, then b\n " ) ;
10212
b.commit ( " from b" , & [ ] ) .unwrap ( ) ;
10213
let e = a.undo ( ) .unwrap_err ( ) .to_string ( ) ;
10214
assert! (
10215
e.contains ( " bookmark main" ) & & e.contains ( " builds on" ) ,
10216
" {e}"
10217
) ;
10218
assert! ( e.contains ( " oxy op restore" ) , " {e}" ) ;
10219
}
10220
10221
10222
10223
# [ test ]
10224
fn undo_says_when_the_commit_was_pushed ( ) {
10225
hermetic ( ) ;
10226
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10227
let root = dir.path ( ) ;
10228
Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
10229
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
10230
let repo = Repo:: clone( & url, & root.join ( " work" ) )
10231
.unwrap ( )
10232
.with_home ( root.join ( " home" ) ) ;
10233
write ( & root.join ( " work" ) , " a.txt" , b " a\n " ) ;
10234
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10235
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10236
10237
let u = repo.undo ( ) .unwrap ( ) ;
10238
assert_eq! ( u.command, " oxy commit -m \" a\" " ) ;
10239
assert_eq! ( u.pushed, vec! [ ( " main" .to_string ( ) , " origin" .to_string ( ) ) ] ) ;
10240
}
b689779e Andy McCall 26 days ago ↶
10241
10242
10243
10244
10245
# [ test ]
10246
fn large_new_files_are_left_out_of_snapshots ( ) {
10247
hermetic ( ) ;
10248
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10249
let root = dir.path ( ) ;
10250
let mut repo = Repo:: init( root) .unwrap ( ) ;
10251
repo.config.max_new_file = 1024 ;
10252
write ( root, " small.txt" , b " small\n " ) ;
10253
write ( root, " big.bin" , & vec! [ 0 u8 ; 4096 ] ) ;
10254
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10255
let paths: Vec< _ > = s.statuses.iter ( ) .map ( | p | p.path.as_str ( ) ) .collect ( ) ;
10256
assert_eq! ( paths, [ " small.txt" ] ) ;
10257
assert_eq! ( repo.skipped_large ( ) , vec! [ ( " big.bin" .to_string ( ) , 4096 ) ] ) ;
10258
repo.commit ( " small" , & [ ] ) .unwrap ( ) ;
10259
10260
10261
write ( root, " small.txt" , & vec! [ b ' x' ; 4096 ] ) ;
10262
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10263
assert! ( s.statuses.iter ( ) .any ( | p | p.path = = " small.txt" ) ) ;
10264
10265
repo.config.max_new_file = 0 ;
10266
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10267
assert! ( s.statuses.iter ( ) .any ( | p | p.path = = " big.bin" ) ) ;
10268
repo.config.max_new_file = 1024 ;
10269
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10270
assert! (
10271
s.statuses.iter ( ) .any ( | p | p.path = = " big.bin" ) ,
10272
" already in the working copy"
10273
) ;
10274
assert! ( repo.skipped_large ( ) .is_empty ( ) ) ;
10275
}
10276
10277
10278
10279
# [ test ]
10280
fn gc_prunes_what_only_expired_snapshots_held ( ) {
10281
hermetic ( ) ;
10282
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10283
let root = dir.path ( ) ;
10284
let mut repo = Repo:: init( root) .unwrap ( ) ;
10285
repo.config.gc_grace_days = 0 ;
10286
write ( root, " a.txt" , b " a\n " ) ;
10287
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10288
let image: Vec< u8 > = ( 0 .. 200_000 u32 ) .map ( | i | ( i % 7 ) as u8 ) .collect ( ) ;
10289
write ( root, " image.tar" , & image) ;
10290
repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10291
std:: fs:: remove_file( root.join ( " image.tar" ) ) .unwrap ( ) ;
10292
repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10293
let blob = Repo:: blob_ref( & image) ;
10294
assert! ( repo.store ( ) .has ( & blob) ) ;
10295
repo.gc ( true ) .unwrap ( ) ;
10296
assert! ( repo.store ( ) .has ( & blob) , " a recent snapshot keeps it" ) ;
10297
repo.config.snapshot_retention_days = 0 ;
10298
repo.gc ( true ) .unwrap ( ) ;
10299
assert! ( ! repo.store ( ) .has ( & blob) , " an expired snapshot does not" ) ;
10300
assert_eq! (
10301
repo.file_at ( " a.txt" , None ) .unwrap ( ) ,
10302
b " a\n " ,
10303
" committed files stay"
10304
) ;
fe9586d9 Andy McCall 26 days ago ↶
10305
}
10306
10307
10308
10309
10310
# [ test ]
10311
fn carrying_work_the_target_already_has_is_no_conflict ( ) {
10312
hermetic ( ) ;
10313
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10314
let root = dir.path ( ) ;
10315
let mut repo = Repo:: init( root) .unwrap ( ) ;
10316
write ( root, " a.txt" , b " one\n " ) ;
10317
write ( root, " b.txt" , b " b\n " ) ;
10318
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
10319
repo.bookmark_create ( " landed" , None ) .unwrap ( ) ;
10320
10321
repo.bookmark_switch ( " landed" , false ) .unwrap ( ) ;
10322
write ( root, " a.txt" , b " two\n " ) ;
10323
repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
10324
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
10325
assert_eq! ( std:: fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n " ) ;
10326
10327
write ( root, " a.txt" , b " two\n " ) ;
10328
write ( root, " b.txt" , b " b, edited\n " ) ;
10329
let ( _ , carried) = repo.bookmark_switch_carry ( " landed" ) .unwrap ( ) ;
10330
assert_eq! (
10331
carried,
10332
vec! [ " b.txt" .to_string ( ) ] ,
10333
" only the work landed lacks is carried"
10334
) ;
10335
assert_eq! ( std:: fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " two\n " ) ;
10336
assert_eq! ( std:: fs:: read( root.join ( " b.txt" ) ) .unwrap ( ) , b " b, edited\n " ) ;
10337
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10338
let paths: Vec< _ > = s.statuses.iter ( ) .map ( | p | p.path.as_str ( ) ) .collect ( ) ;
10339
assert_eq! ( paths, [ " b.txt" ] ) ;
10340
10341
10342
repo.bookmark_switch ( " main" , true ) .unwrap ( ) ;
10343
write ( root, " a.txt" , b " three\n " ) ;
10344
let e = repo
10345
.bookmark_switch_carry ( " landed" )
10346
.unwrap_err ( )
10347
.to_string ( ) ;
10348
assert! ( e.contains ( " a.txt" ) & & e.contains ( " --discard" ) , " {e}" ) ;
b689779e Andy McCall 26 days ago ↶
10349
}
aa4939e0 Andy McCall 26 days ago ↶
10350
10351
10352
10353
# [ test ]
10354
fn a_full_hash_resolves_without_its_hash_sign ( ) {
10355
hermetic ( ) ;
10356
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10357
let root = dir.path ( ) ;
10358
let repo = Repo:: init( root) .unwrap ( ) ;
10359
write ( root, " a.txt" , b " a\n " ) ;
10360
let h = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10361
assert_eq! ( repo.resolve_change ( & h.to_hex ( ) ) .unwrap ( ) , h) ;
10362
assert_eq! ( repo.resolve_change ( & format! ( " #{} " , h.to_hex ( ) ) ) .unwrap ( ) , h) ;
10363
assert! ( repo
10364
.resolve_change ( & " 0" .repeat ( 64 ) )
10365
.unwrap_err ( )
10366
.to_string ( )
10367
.contains ( " no change matches" ) ) ;
10368
}
f2f91ceb Andy McCall 25 days ago ↶
10369
10370
# [ test ]
10371
fn a_pin_change_moves_only_the_pin ( ) {
10372
hermetic ( ) ;
10373
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10374
let root = dir.path ( ) ;
10375
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
10376
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
10377
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
10378
.unwrap ( )
10379
.with_home ( root.join ( " home" ) ) ;
10380
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
10381
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
10382
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10383
let _app_origin = Repo:: init( & root.join ( " app-origin" ) ) .unwrap ( ) ;
10384
let app_dir = root.join ( " app" ) ;
10385
let app = Repo:: clone( & root.join ( " app-origin" ) .to_string_lossy ( ) , & app_dir)
10386
.unwrap ( )
10387
.with_home ( root.join ( " home" ) ) ;
10388
write ( & app_dir, " main.txt" , b " app\n " ) ;
10389
let pin1 = app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
10390
let a1 = app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
10391
10392
write ( & root.join ( " lib" ) , " more.txt" , b " v2\n " ) ;
10393
let l2 = lib.commit ( " lib v2" , & [ ] ) .unwrap ( ) ;
10394
let crate :: subrepo:: Pin:: Oxy { repo, bookmark, .. } = pin1 else {
10395
panic! ( " an Oxygen pin" )
10396
} ;
10397
let pin2 = crate :: subrepo:: Pin:: Oxy {
10398
repo,
10399
heads: vec! [ l2] ,
10400
bookmark,
10401
} ;
10402
let author = app.store ( ) .get_change ( & a1) .unwrap ( ) .author;
10403
let change = app
10404
.build_pin_change ( & [ a1] , " libs/lib" , & pin2, " Move lib to v2" , author)
10405
.unwrap ( ) ;
10406
let mut state = app.load_state ( & [ a1] ) .unwrap ( ) ;
10407
state.add ( change) .unwrap ( ) ;
10408
let files = state.materialize ( ) .files;
10409
assert_eq! ( files[ " libs/lib" ] .kind, NodeKind:: Subrepo) ;
10410
let FileContent:: Opaque { blobs } = & files[ " libs/lib" ] .content else {
10411
panic! ( " a pin is opaque" )
10412
} ;
10413
let read = crate :: subrepo:: Pin:: decode( & app.read_blob ( & blobs[ 0 ] ) .unwrap ( ) ) .unwrap ( ) ;
10414
assert_eq! ( read, pin2, " the new pin" ) ;
10415
assert! (
10416
matches! ( & files[ " main.txt" ] .content, FileContent:: Text { bytes, .. } if bytes.as_slice ( ) = = b " app\n " ) ,
10417
" nothing else changes"
10418
) ;
10419
10420
let e = app.build_pin_change ( & [ a1] , " main.txt" , & pin2, " x" , author) ;
10421
assert! ( format! ( " {:?} " , e.unwrap_err ( ) ) .contains ( " not a subrepo" ) ) ;
10422
let e = app.build_pin_change ( & [ a1] , " libs/none" , & pin2, " x" , author) ;
10423
assert! ( format! ( " {:?} " , e.unwrap_err ( ) ) .contains ( " does not exist" ) ) ;
10424
}
767ac6cd Andy McCall 19 days ago
10425
10426
10427
10428
10429
10430
10431
# [ test ]
10432
fn one_handle_keeps_one_head_until_it_refreshes ( ) {
10433
hermetic ( ) ;
10434
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10435
let root = dir.path ( ) ;
10436
let writer = Repo:: init( root) .unwrap ( ) ;
10437
write ( root, " a.txt" , b " one\n " ) ;
10438
let h1 = writer.commit ( " one" , & [ ] ) .unwrap ( ) ;
10439
let reader = Repo:: open( root) .unwrap ( ) ;
10440
let before = reader.view ( ) .unwrap ( ) ;
10441
assert_eq! ( reader.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
10442
write ( root, " a.txt" , b " two\n " ) ;
10443
let h2 = writer.commit ( " two" , & [ ] ) .unwrap ( ) ;
10444
assert_eq! (
10445
writer.bookmark_heads ( ) .unwrap ( ) ,
10446
vec! [ h2] ,
10447
" its own operation"
10448
) ;
10449
assert_eq! ( reader.view ( ) .unwrap ( ) , before, " the head it read first" ) ;
10450
assert_eq! ( reader.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
10451
reader.refresh ( ) .unwrap ( ) ;
10452
assert_eq! ( reader.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
10453
10454
10455
let stale = Repo:: open( root) .unwrap ( ) ;
10456
assert_eq! ( stale.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
10457
write ( root, " a.txt" , b " three\n " ) ;
10458
let h3 = writer.commit ( " three" , & [ ] ) .unwrap ( ) ;
10459
stale.bookmark_create ( " feature" , Some ( & [ h1] ) ) .unwrap ( ) ;
10460
let view = stale.view ( ) .unwrap ( ) ;
10461
assert! ( view.bookmarks.contains_key ( " feature" ) ) ;
10462
assert_eq! (
10463
stale.bookmark_heads ( ) .unwrap ( ) ,
10464
vec! [ h3] ,
10465
" merged, not lost"
10466
) ;
10467
let fresh = Repo:: open( root) .unwrap ( ) ;
10468
assert_eq! ( fresh.view ( ) .unwrap ( ) , view) ;
10469
}
10470
10471
10472
# [ test ]
10473
fn over_a_store_one_handle_keeps_one_head ( ) {
10474
hermetic ( ) ;
10475
let backend = std:: sync:: Arc:: new( crate :: strata:: MemoryStore:: new( ) ) ;
10476
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = Some ( backend.clone ( ) ) ) ;
10477
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10478
let a = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
10479
let ( h0, _ ) = a.head_op ( ) .unwrap ( ) ;
10480
let b = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
10481
assert_eq! ( b.head_op ( ) .unwrap ( ) .0 , h0) ;
10482
let state = a.store.put_state ( & StateObject:: new( vec! [ ] ) ) .unwrap ( ) ;
10483
let mut view = a.view ( ) .unwrap ( ) ;
10484
view.bookmarks.insert ( " x" .into ( ) , state) ;
10485
let r = a.record_op ( & [ h0] , " bookmark x" , view) .unwrap ( ) ;
10486
assert_eq! ( a.head_op ( ) .unwrap ( ) .0 , r) ;
10487
assert_eq! ( b.head_op ( ) .unwrap ( ) .0 , h0, " b keeps its head" ) ;
10488
b.refresh ( ) .unwrap ( ) ;
10489
assert_eq! ( b.head_op ( ) .unwrap ( ) .0 , r) ;
10490
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = None ) ;
10491
}
10492
10493
10494
10495
10496
# [ test ]
10497
fn gc_retires_and_a_later_gc_deletes_after_the_hour ( ) {
10498
hermetic ( ) ;
10499
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10500
let root = dir.path ( ) ;
10501
let repo = Repo:: init( root) .unwrap ( ) ;
10502
let mut heads = Vec :: new( ) ;
10503
for i in 0 .. 4 {
10504
write ( root, & format! ( " f{i} .txt" ) , format! ( " {i} \n " ) .as_bytes ( ) ) ;
10505
heads.push ( repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ) ;
10506
}
10507
let reader = Repo:: open( root) .unwrap ( ) ;
10508
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 5 ) ;
10509
let r = repo.gc ( true ) .unwrap ( ) ;
10510
assert_eq! ( r.retired, crate :: store:: RetiredReport:: default( ) ) ;
10511
assert_eq! ( repo.store ( ) .loose_count ( ) .unwrap ( ) , 0 ) ;
10512
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 5 , " read on from what it had" ) ;
10513
10514
let r = repo.gc ( true ) .unwrap ( ) ;
10515
assert_eq! ( ( r.retired.packs, r.retired.loose) , ( 0 , 0 ) ) ;
10516
assert! ( r.retired.kept > 0 , " {r:?}" ) ;
10517
write ( root, " f9.txt" , b " 9\n " ) ;
10518
heads.push ( repo.commit ( " c9" , & [ ] ) .unwrap ( ) ) ;
10519
crate :: store:: reader_tests:: age_retired(
10520
repo.store ( ) ,
10521
crate :: store:: RETIRED_FOR + std:: time:: Duration:: from_secs( 60 ) ,
10522
) ;
10523
let r = repo.gc ( true ) .unwrap ( ) ;
10524
assert! ( r.retired.packs > = 1 & & r.retired.loose > 0 , " {r:?}" ) ;
10525
assert_eq! ( r.retired.kept, 0 , " {r:?}" ) ;
10526
let r = repo.gc ( true ) .unwrap ( ) ;
10527
assert_eq! ( ( r.retired.packs, r.retired.loose) , ( 0 , 0 ) ) ;
10528
assert! ( r.retired.kept > 0 , " what the last gc retired is young" ) ;
10529
10530
10531
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 5 ) ;
10532
for h in & heads[ .. 4 ] {
10533
reader.store ( ) .get_change ( h) .unwrap ( ) ;
10534
}
10535
reader.refresh ( ) .unwrap ( ) ;
10536
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 6 ) ;
10537
let fresh = Repo:: open( root) .unwrap ( ) ;
10538
assert_eq! (
10539
fresh.store ( ) .pack_count ( ) ,
10540
1 ,
10541
" retired packs are not loaded"
10542
) ;
10543
}
10544
10545
10546
10547
# [ test ]
10548
fn a_repository_opens_when_a_listed_index_is_gone ( ) {
10549
hermetic ( ) ;
10550
let backend = crate :: store:: reader_tests:: racing( true ) ;
10551
let dyn_backend: std:: sync:: Arc< crate:: store:: Backend> = backend.clone ( ) ;
10552
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = Some ( dyn_backend) ) ;
10553
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10554
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
10555
write ( dir.path ( ) , " a.txt" , b " a\n " ) ;
10556
let h = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10557
repo.gc ( true ) .unwrap ( ) ;
10558
let again = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
10559
assert_eq! ( again.store ( ) .pack_count ( ) , 1 ) ;
10560
assert_eq! ( again.bookmark_heads ( ) .unwrap ( ) , vec! [ h] ) ;
10561
10562
10563
use crate :: strata:: Store as _ ;
10564
let idx = backend
10565
.list ( " pack-" )
10566
.unwrap ( )
10567
.into_iter ( )
10568
.map ( | e | e.key )
10569
.find ( | k | k.ends_with ( " .idx" ) & & k ! = " pack-0000.idx" )
10570
.unwrap ( ) ;
10571
let stem = idx.trim_end_matches ( " .idx" ) ;
10572
for ext in [ " pack" , " idx" ] {
10573
let bytes = backend.get ( & format! ( " {stem} .{ext} " ) ) .unwrap ( ) ;
10574
backend
10575
.put_new ( & format! ( " pack-copy.{ext} " ) , & bytes)
10576
.unwrap ( ) ;
10577
}
10578
* backend.doomed.lock ( ) .unwrap ( ) = Some ( " pack-copy.idx" .into ( ) ) ;
10579
let again = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
10580
assert_eq! ( again.store ( ) .pack_count ( ) , 1 ) ;
10581
assert_eq! ( again.bookmark_heads ( ) .unwrap ( ) , vec! [ h] ) ;
10582
assert! ( ! backend.has ( " pack-copy.idx" ) .unwrap ( ) ) ;
10583
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = None ) ;
10584
}