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
288
289
290
291
# [ test ]
292
fn keeping_both_refuses_replaced_or_edited_lines ( ) {
293
hermetic ( ) ;
294
let ( dir, repo, _ , _ , _ ) = conflicted_repo ( ) ;
295
let root = dir.path ( ) ;
296
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
297
.unwrap ( ) ;
298
let marked = fs:: read( root.join ( " f.txt" ) ) .unwrap ( ) ;
299
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
300
let node = snap.base_snapshot.files[ " f.txt" ] .node;
301
assert_eq! ( snap.base.both_sides ( & node) , vec! [ None ] ) ;
302
let kept = repo.keep_both ( & snap, & [ " f.txt" .into ( ) ] , false ) .unwrap ( ) ;
303
assert! ( kept[ 0 ] .settled.is_empty ( ) ) ;
304
assert_eq! ( kept[ 0 ] .left[ 0 ] .0 , 1 ) ;
305
assert! ( kept[ 0 ] .left[ 0 ] .1. contains ( " both sides changed" ) , " {kept:?}" ) ;
306
assert_eq! ( fs:: read( root.join ( " f.txt" ) ) .unwrap ( ) , marked) ;
307
308
309
let sides = vec! [ Some ( vec! [ b " x\n " .to_vec ( ) , b " y\n " .to_vec ( ) ] ) ] ;
310
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 " ;
311
let ( out, settled, left) = super :: snapshot:: keep_both_text( text, & sides, false ) ;
312
assert_eq! ( out, text.to_vec ( ) ) ;
313
assert! ( settled.is_empty ( ) ) ;
314
assert! ( left[ 0 ] .1. contains ( " edited" ) , " {left:?}" ) ;
315
316
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 " ;
317
let ( out, settled, _ ) = super :: snapshot:: keep_both_text( text, & sides, false ) ;
318
assert_eq! ( out, b " a\n x\n y\n b\n " .to_vec ( ) ) ;
319
assert_eq! ( settled, vec! [ 1 ] ) ;
320
}
321
322
# [ test ]
98286406 Andy McCall 1 month ago ↶
323
fn concurrent_edits_conflict_and_can_be_resolved ( ) {
324
hermetic ( ) ;
325
let ( dir, repo, h1, h2, h3) = conflicted_repo ( ) ;
326
let root = dir.path ( ) ;
327
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
328
assert_eq! ( heads, [ h2, h3] .into_iter ( ) .collect ( ) ) ;
329
let st = repo.load_state ( & [ h2, h3] ) .unwrap ( ) ;
330
assert! ( st.materialize ( ) .is_conflicted ( ) ) ;
331
332
75167672 Andy McCall 1 month ago
333
let r = repo
334
.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
335
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
336
assert! ( r.conflicted) ;
337
let marked = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
338
assert! (
339
marked.starts_with ( " a\n <<<<<<< oxy conflict 1 of 1" ) ,
340
" {marked}"
341
) ;
342
assert! (
343
marked.contains ( " B\n " ) & & marked.contains ( " X\n " ) & & marked.ends_with ( " end\n c\n " ) ,
344
" {marked}"
345
) ;
98286406 Andy McCall 1 month ago ↶
346
assert! ( has_conflict_markers ( marked.as_bytes ( ) ) ) ;
347
348
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
349
assert! ( s.statuses.is_empty ( ) ) ;
350
assert! ( ! s.resolves) ;
351
352
353
write ( root, " f.txt" , marked.replace ( " B\n " , " BB\n " ) .as_bytes ( ) ) ;
354
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
355
assert_eq! (
356
statuses ( & s) ,
357
vec! [ ( " f.txt" .into ( ) , ChangeStatus:: ConflictEdited) ]
358
) ;
98286406 Andy McCall 1 month ago ↶
359
assert! ( repo.commit ( " no" , & [ ] ) .is_err ( ) ) ;
360
361
362
write ( root, " f.txt" , b " a\n X\n c\n d\n " ) ;
363
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
364
assert_eq! ( statuses ( & s) , vec! [ ( " f.txt" .into ( ) , ChangeStatus:: Resolved) ] ) ;
365
assert! ( s.resolves) ;
366
let h4 = repo.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
367
let c4 = repo.store ( ) .get_change ( & h4) .unwrap ( ) ;
368
assert_eq! ( c4.kind, ChangeKind:: Resolution) ;
369
let mut want = vec! [ h1, h2, h3] ;
370
want.sort ( ) ;
75167672 Andy McCall 1 month ago
371
assert_eq! (
372
c4.deps, want,
373
" a resolution depends on exactly the contributors and what it references"
374
) ;
98286406 Andy McCall 1 month ago ↶
375
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h4] ) ;
376
let snap = repo.load_state ( & [ h4] ) .unwrap ( ) .materialize ( ) ;
377
assert! ( ! snap.is_conflicted ( ) ) ;
75167672 Andy McCall 1 month ago
378
assert! (
379
matches! ( & snap.files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n X\n c\n d\n " )
380
) ;
98286406 Andy McCall 1 month ago ↶
381
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
382
383
384
let mut st = State:: new( ) ;
385
for h in [ h4, h3, h1, h2] {
386
st.add_unchecked ( repo.store ( ) .get_change ( & h) .unwrap ( ) ) ;
387
}
388
st.validate ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
389
assert! (
390
matches! ( & st.materialize ( ) .files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n X\n c\n d\n " )
391
) ;
98286406 Andy McCall 1 month ago ↶
392
}
393
69953b50 Andy McCall 19 days ago ↶
394
395
396
98286406 Andy McCall 1 month ago ↶
397
# [ test ]
69953b50 Andy McCall 19 days ago ↶
398
fn resolutions_and_amends_take_paths ( ) {
399
hermetic ( ) ;
400
let ( dir, repo, _h1, _h2, _h3) = conflicted_repo ( ) ;
401
let root = dir.path ( ) ;
402
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
403
.unwrap ( ) ;
404
405
write ( root, " f.txt" , b " a\n X\n c\n " ) ;
406
write ( root, " notes.txt" , b " pending\n " ) ;
407
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
408
assert! ( s.resolves) ;
409
let err = repo
410
.commit_selection_partial ( " resolve" , & [ ] , & Selection:: Paths( vec! [ " notes.txt" .into ( ) ] ) )
411
.unwrap_err ( )
412
.to_string ( ) ;
413
assert! (
414
err.contains ( " takes every file it resolves" ) & & err.contains ( " f.txt" ) ,
415
" {err}"
416
) ;
417
let ( h4, partial) = repo
418
.commit_selection_partial ( " resolve" , & [ ] , & Selection:: Paths( vec! [ " f.txt" .into ( ) ] ) )
419
.unwrap ( ) ;
420
assert! ( partial) ;
421
assert_eq! (
422
repo.store ( ) .get_change ( & h4) .unwrap ( ) .kind,
423
ChangeKind:: Resolution
424
) ;
425
assert! ( ! repo
426
.load_state ( & [ h4] )
427
.unwrap ( )
428
.materialize ( )
429
.is_conflicted ( ) ) ;
430
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
431
assert_eq! (
432
statuses ( & s) ,
433
vec! [ ( " notes.txt" .into ( ) , ChangeStatus:: Added) ] ,
434
" the other work waits"
435
) ;
436
437
438
439
let h5 = repo.commit ( " notes" , & [ h4] ) .unwrap ( ) ;
440
let id5 = repo.store ( ) .get_change ( & h5) .unwrap ( ) .change_id;
441
write ( root, " notes.txt" , b " pending\n more\n " ) ;
442
write ( root, " other.txt" , b " later\n " ) ;
443
let h6 = repo
444
.amend_paths ( Some ( " notes, amended" ) , & [ " notes.txt" .into ( ) ] )
445
.unwrap ( ) ;
446
let c6 = repo.store ( ) .get_change ( & h6) .unwrap ( ) ;
447
assert_eq! ( ( c6.change_id, c6.message.as_str ( ) ) , ( id5, " notes, amended" ) ) ;
448
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h6] ) ;
449
let snap6 = repo.load_state ( & [ h6] ) .unwrap ( ) .materialize ( ) ;
450
assert! (
451
matches! ( & snap6.files[ " notes.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " pending\n more\n " )
452
) ;
453
assert! (
454
! snap6.files.contains_key ( " other.txt" ) ,
455
" other.txt stays out"
456
) ;
457
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
458
assert_eq! (
459
statuses ( & s) ,
460
vec! [ ( " other.txt" .into ( ) , ChangeStatus:: Added) ]
461
) ;
462
}
463
464
# [ test ]
98286406 Andy McCall 1 month ago ↶
465
fn resolution_may_keep_both_sides_or_neither ( ) {
466
hermetic ( ) ;
467
let ( dir, repo, _h1, _h2, _h3) = conflicted_repo ( ) ;
468
let root = dir.path ( ) ;
75167672 Andy McCall 1 month ago
469
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
470
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
471
472
write ( root, " f.txt" , b " a\n X\n B\n c\n " ) ;
473
let h = repo.commit ( " both" , & [ ] ) .unwrap ( ) ;
474
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
475
assert! ( ! snap.is_conflicted ( ) ) ;
75167672 Andy McCall 1 month ago
476
assert! (
477
matches! ( & snap.files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n X\n B\n c\n " )
478
) ;
98286406 Andy McCall 1 month ago ↶
479
480
repo.undo ( ) .unwrap ( ) ;
481
write ( root, " f.txt" , b " a\n c\n " ) ;
482
let h = repo.commit ( " neither" , & [ ] ) .unwrap ( ) ;
483
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
484
assert! ( ! snap.is_conflicted ( ) ) ;
75167672 Andy McCall 1 month ago
485
assert! (
486
matches! ( & snap.files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n c\n " )
487
) ;
98286406 Andy McCall 1 month ago ↶
488
}
489
490
# [ test ]
491
fn name_conflict_resolved_by_removing_a_candidate ( ) {
492
hermetic ( ) ;
493
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
494
let root = dir.path ( ) ;
495
let repo = Repo:: init( root) .unwrap ( ) ;
496
write ( root, " keep.txt" , b " k\n " ) ;
497
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
498
write ( root, " new.txt" , b " one\n " ) ;
499
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
500
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
501
write ( root, " new.txt" , b " two\n " ) ;
502
repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
503
let r = repo
504
.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
505
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
506
assert! ( r.conflicted) ;
75167672 Andy McCall 1 month ago
507
assert! (
508
root.join ( " new.txt.oxy-conflict-1" ) .exists ( )
509
& & root.join ( " new.txt.oxy-conflict-2" ) .exists ( )
510
) ;
98286406 Andy McCall 1 month ago ↶
511
assert! ( ! root.join ( " new.txt" ) .exists ( ) ) ;
512
513
75167672 Andy McCall 1 month ago
514
let ( keep, drop) = if fs:: read( root.join ( " new.txt.oxy-conflict-1" ) ) .unwrap ( ) = = b " two\n " {
515
( 1 , 2 )
516
} else {
517
( 2 , 1 )
518
} ;
98286406 Andy McCall 1 month ago ↶
519
fs:: remove_file( root.join ( format! ( " new.txt.oxy-conflict-{drop} " ) ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
520
fs:: rename(
521
root.join ( format! ( " new.txt.oxy-conflict-{keep} " ) ) ,
522
root.join ( " new.txt" ) ,
523
)
524
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
525
let h = repo.commit ( " pick two" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
526
assert_eq! (
527
repo.store ( ) .get_change ( & h) .unwrap ( ) .kind,
528
ChangeKind:: Resolution
529
) ;
98286406 Andy McCall 1 month ago ↶
530
531
532
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
533
assert_eq! ( heads, [ h1, h] .into_iter ( ) .collect ( ) ) ;
75167672 Andy McCall 1 month ago
534
let snap = repo
535
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
536
.unwrap ( )
537
.materialize ( ) ;
98286406 Andy McCall 1 month ago ↶
538
assert! ( ! snap.is_conflicted ( ) , " {:?}" , snap.tree.conflicts) ;
75167672 Andy McCall 1 month ago
539
assert_eq! (
540
snap.files.keys ( ) .cloned ( ) .collect:: < Vec< _ > > ( ) ,
541
[ " keep.txt" , " new.txt" ]
542
) ;
543
assert! (
544
matches! ( & snap.files[ " new.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " two\n " )
545
) ;
98286406 Andy McCall 1 month ago ↶
546
}
547
548
# [ test ]
549
fn identities_and_signed_commits ( ) {
550
hermetic ( ) ;
551
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
552
let root = dir.path ( ) ;
553
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
554
write ( root, " a.txt" , b " a\n " ) ;
555
let h0 = repo.commit ( " unsigned" , & [ ] ) .unwrap ( ) ;
556
assert_eq! ( repo.verify_change ( & h0) .unwrap ( ) , SignatureStatus:: Unsigned) ;
75167672 Andy McCall 1 month ago
557
assert_eq! (
558
repo.store ( ) .get_change ( & h0) .unwrap ( ) .author,
559
ObjectRef:: ZERO
560
) ;
98286406 Andy McCall 1 month ago ↶
561
75167672 Andy McCall 1 month ago
562
let ( key, id) = repo
563
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
564
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
565
assert_eq! ( id.hash ( ) , key) ;
566
assert_eq! ( repo.config.author_key, Some ( key) ) ;
567
568
let reopened = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
569
assert_eq! ( reopened.config.author_key, Some ( key) ) ;
570
assert_eq! ( reopened.secret_key ( ) .unwrap ( ) .unwrap ( ) .0 , key) ;
571
572
573
write ( root, " a.txt" , b " a\n b\n " ) ;
574
let h1 = repo.commit ( " authored" , & [ ] ) .unwrap ( ) ;
575
assert_eq! ( repo.store ( ) .get_change ( & h1) .unwrap ( ) .author, key) ;
576
assert_eq! ( repo.verify_change ( & h1) .unwrap ( ) , SignatureStatus:: Unsigned) ;
577
578
repo.sign_change ( & h1) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
579
assert! (
580
matches! ( repo.verify_change ( & h1) .unwrap ( ) , SignatureStatus:: Valid { key: k, .. } if k = = key)
581
) ;
98286406 Andy McCall 1 month ago ↶
582
583
repo.config.set ( " core.sign" , " change" ) .unwrap ( ) ;
584
repo.save_config ( ) .unwrap ( ) ;
585
write ( root, " a.txt" , b " a\n b\n c\n " ) ;
586
let h2 = repo.commit ( " signed" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
587
assert_eq! (
588
repo.verify_change ( & h2) .unwrap ( ) ,
589
SignatureStatus:: Valid {
590
key,
591
name: " Ada" .into ( ) ,
592
email: " [email protected] " .into ( ) ,
593
sealed: false ,
594
revoked: false
595
}
596
) ;
98286406 Andy McCall 1 month ago ↶
597
05e71ed6 Andy McCall 1 month ago ↶
598
599
600
98286406 Andy McCall 1 month ago ↶
601
let other = SecretKey:: generate( ) ;
602
let other_id = other.identity ( " Mallory" , " [email protected] " , 0 ) ;
603
let other_key = repo.store ( ) .put_identity ( & other_id) .unwrap ( ) ;
05e71ed6 Andy McCall 1 month ago ↶
604
write ( root, " a.txt" , b " a\n b\n c\n d\n " ) ;
605
repo.config.set ( " core.sign" , " seal" ) .unwrap ( ) ;
606
let h3 = repo.commit ( " ada's, unsigned" , & [ ] ) .unwrap ( ) ;
607
let forged = other.sign ( Domain:: Change, & other_key, & h3) ;
608
repo.store ( ) .put_signature ( & h3, & forged) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
609
assert! (
05e71ed6 Andy McCall 1 month ago ↶
610
matches! ( repo.verify_change ( & h3) .unwrap ( ) , SignatureStatus:: Invalid( m) if m.contains ( " neither the author" ) )
75167672 Andy McCall 1 month ago
611
) ;
05e71ed6 Andy McCall 1 month ago ↶
612
let adopted = other.sign ( Domain:: Change, & other_key, & h0) ;
613
repo.store ( ) .put_signature ( & h0, & adopted) .unwrap ( ) ;
614
assert! (
615
matches! ( repo.verify_change ( & h0) .unwrap ( ) , SignatureStatus:: Valid { name, .. } if name = = " Mallory" )
616
) ;
98286406 Andy McCall 1 month ago ↶
617
618
619
let mut sig = repo.store ( ) .signatures ( & h2) .unwrap ( ) .remove ( 0 ) ;
620
sig.ed[ 0 ] ^ = 1 ;
621
repo.store ( ) .put_signature ( & h2, & sig) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
622
assert! ( matches! (
623
repo.verify_change ( & h2) .unwrap ( ) ,
624
SignatureStatus:: Invalid( _ )
625
) ) ;
98286406 Andy McCall 1 month ago ↶
626
627
628
let no_keys = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " nowhere" ) ) ;
629
write ( root, " a.txt" , b " z\n " ) ;
630
assert! ( no_keys.commit ( " cannot sign" , & [ ] ) .is_err ( ) ) ;
631
}
632
633
# [ test ]
634
fn checkout_moves_the_working_copy ( ) {
635
hermetic ( ) ;
636
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
637
let root = dir.path ( ) ;
638
let repo = Repo:: init( root) .unwrap ( ) ;
639
write ( root, " a.txt" , b " one\n " ) ;
640
write ( root, " sub/deep/b.txt" , b " b\n " ) ;
641
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
642
write ( root, " a.txt" , b " two\n " ) ;
643
fs:: remove_file( root.join ( " sub/deep/b.txt" ) ) .unwrap ( ) ;
644
write ( root, " bin.dat" , & [ 0 , 1 , 2 , 3 ] ) ;
645
let h2 = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
646
647
let r = repo.checkout ( & [ h1] , false , " oxy checkout" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
648
assert_eq! (
649
r,
650
CheckoutReport {
651
written: 2 ,
652
removed: 1 ,
9b723f69 Andy McCall 26 days ago ↶
653
removed_paths: vec! [ " bin.dat" .into ( ) ] ,
75167672 Andy McCall 1 month ago
654
conflicted: false ,
d3cd7ef9 Andy McCall 1 month ago ↶
655
notes: vec! [ ] ,
656
lacks: 1 ,
75167672 Andy McCall 1 month ago
657
}
658
) ;
98286406 Andy McCall 1 month ago ↶
659
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n " ) ;
660
assert_eq! ( fs:: read( root.join ( " sub/deep/b.txt" ) ) .unwrap ( ) , b " b\n " ) ;
661
assert! ( ! root.join ( " bin.dat" ) .exists ( ) ) ;
662
let ( _ , wc) = repo.working_copy ( ) .unwrap ( ) ;
663
assert_eq! ( wc.deps, vec! [ h1] ) ;
75167672 Andy McCall 1 month ago
664
assert! (
665
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
666
" checkout leaves a clean working copy"
667
) ;
98286406 Andy McCall 1 month ago ↶
668
669
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
670
671
672
write ( root, " a.txt" , b " dirty\n " ) ;
673
assert! ( repo.checkout ( & [ h2] , false , " oxy checkout" ) .is_err ( ) ) ;
75167672 Andy McCall 1 month ago
674
let r = repo
675
.checkout ( & [ h2] , true , " oxy checkout --discard" )
676
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
677
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " two\n " ) ;
678
assert_eq! ( fs:: read( root.join ( " bin.dat" ) ) .unwrap ( ) , [ 0 , 1 , 2 , 3 ] ) ;
679
assert! ( ! root.join ( " sub" ) .exists ( ) , " empty directories are pruned" ) ;
680
assert_eq! ( r.removed, 1 ) ;
681
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
682
683
684
# [ cfg ( unix ) ]
685
{
686
fs:: remove_dir_all( root.join ( " sub" ) ) .ok ( ) ;
687
std:: os:: unix:: fs:: symlink( " /tmp" , root.join ( " sub" ) ) .unwrap ( ) ;
688
assert! ( repo.checkout ( & [ h1] , true , " oxy checkout" ) .is_err ( ) ) ;
689
}
690
}
691
692
# [ test ]
693
fn large_files_are_chunked_and_read_back ( ) {
694
hermetic ( ) ;
695
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
696
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
697
let mut big = vec! [ 0 u8 ; 700 * 1024 ] ;
698
for ( i, b) in big.iter_mut ( ) .enumerate ( ) {
699
* b = ( i as u32 ) .wrapping_mul ( 2654435761 ) .to_le_bytes ( ) [ 0 ] ;
700
}
701
let r = repo.store_blob ( & big) .unwrap ( ) ;
702
let blob = Blob:: decode( & repo.store ( ) .get_typed ( & r, ObjectType:: Blob) .unwrap ( ) ) .unwrap ( ) ;
703
assert! ( blob.chunks.len ( ) > = 3 , " {}" , blob.chunks.len ( ) ) ;
704
assert_eq! ( repo.read_blob ( & r) .unwrap ( ) , big) ;
705
assert_eq! ( Repo:: blob_ref( & big) , r) ;
706
}
707
708
# [ test ]
709
fn clone_push_pull_with_seals_and_attestations ( ) {
710
hermetic ( ) ;
711
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
712
let root = dir.path ( ) ;
713
let origin_path = root.join ( " origin" ) ;
714
let origin = Repo:: init( & origin_path) .unwrap ( ) ;
715
let origin_url = origin_path.to_string_lossy ( ) .to_string ( ) ;
716
717
75167672 Andy McCall 1 month ago
718
let mut a = Repo:: clone( & origin_url, & root.join ( " a" ) )
719
.unwrap ( )
720
.with_home ( root.join ( " home-a" ) ) ;
98286406 Andy McCall 1 month ago ↶
721
assert_eq! ( a.id ( ) .unwrap ( ) , origin.id ( ) .unwrap ( ) ) ;
75167672 Andy McCall 1 month ago
722
let ( key_a, _ ) = a
723
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
724
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
725
write ( & root.join ( " a" ) , " f.txt" , b " a\n " ) ;
726
let h1 = a.commit ( " first" , & [ ] ) .unwrap ( ) ;
727
let results = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
728
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
729
assert! ( results[ 0 ] .ok, " {}" , results[ 0 ] .reason) ;
730
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
731
75167672 Andy McCall 1 month ago
732
assert! (
733
matches! ( origin.authenticate_change ( & h1) .unwrap ( ) , SignatureStatus:: Valid { key, sealed: true , .. } if key = = key_a)
734
) ;
735
assert_eq! (
736
origin.verify_change ( & h1) .unwrap ( ) ,
737
SignatureStatus:: Unsigned
738
) ;
f8c69576 Andy McCall 1 month ago ↶
739
let serial_of = | att : & SignedAttest| match att.attest.body {
740
AttestBody:: Ref { serial, .. } => serial,
741
_ => unreachable! ( ) ,
742
} ;
98286406 Andy McCall 1 month ago ↶
743
let ( _ , att) = origin.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
744
745
746
let first = serial_of ( & att) ;
747
assert! ( first > 1_000_000_000_000 , " {first}" ) ;
98286406 Andy McCall 1 month ago ↶
748
let again = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
749
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
750
assert! ( again[ 0 ] .ok & & again[ 0 ] .reason.contains ( " up to date" ) ) ;
751
let ( _ , att) = origin.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
752
assert_eq! ( serial_of ( & att) , first, " a no-op push does not attest again" ) ;
98286406 Andy McCall 1 month ago ↶
753
754
755
let b = Repo:: clone( & origin_url, & root.join ( " b" ) ) .unwrap ( ) ;
756
assert_eq! ( fs:: read( root.join ( " b/f.txt" ) ) .unwrap ( ) , b " a\n " ) ;
757
assert_eq! ( b.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
75167672 Andy McCall 1 month ago
758
assert! ( matches! (
759
b.authenticate_change ( & h1) .unwrap ( ) ,
760
SignatureStatus:: Valid { sealed: true , .. }
761
) ) ;
98286406 Andy McCall 1 month ago ↶
762
763
764
write ( & root.join ( " b" ) , " f.txt" , b " a\n b\n " ) ;
765
let h2 = b.commit ( " second" , & [ ] ) .unwrap ( ) ;
766
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
767
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
768
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
769
770
771
write ( & root.join ( " a" ) , " g.txt" , b " g\n " ) ;
772
let h3 = a.commit ( " third" , & [ ] ) .unwrap ( ) ;
773
assert! ( a.push ( " origin" , false ) .is_err ( ) ) ;
774
let pr = a.pull ( " origin" ) .unwrap ( ) ;
775
assert! ( pr.changed & & pr.checked_out) ;
776
let mut want = vec! [ h2, h3] ;
777
want.sort ( ) ;
778
assert_eq! ( pr.heads, want) ;
779
assert_eq! ( fs:: read( root.join ( " a/f.txt" ) ) .unwrap ( ) , b " a\n b\n " ) ;
780
assert_eq! ( fs:: read( root.join ( " a/g.txt" ) ) .unwrap ( ) , b " g\n " ) ;
781
let results = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
782
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
783
assert! ( results[ 0 ] .ok, " {}" , results[ 0 ] .reason) ;
784
let mut origin_heads = origin.bookmark_heads ( ) .unwrap ( ) ;
785
origin_heads.sort ( ) ;
786
assert_eq! ( origin_heads, want) ;
787
let ( _ , att) = origin.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
788
assert_eq! ( serial_of ( & att) , first + 1 ) ;
98286406 Andy McCall 1 month ago ↶
789
790
791
let pr = b.pull ( " origin" ) .unwrap ( ) ;
792
assert_eq! ( pr.heads, want) ;
793
assert_eq! ( fs:: read( root.join ( " b/g.txt" ) ) .unwrap ( ) , b " g\n " ) ;
794
assert! ( ! b.snapshot ( " status" ) .unwrap ( ) .changed) ;
795
796
797
write ( & root.join ( " a" ) , " f.txt" , b " a\n A\n " ) ;
798
a.commit ( " a-edit" , & [ ] ) .unwrap ( ) ;
799
a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
800
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
801
write ( & root.join ( " b" ) , " f.txt" , b " a\n B\n " ) ;
802
b.commit ( " b-edit" , & [ ] ) .unwrap ( ) ;
803
let pr = b.pull ( " origin" ) .unwrap ( ) ;
804
assert! ( pr.changed & & pr.checked_out) ;
805
let text = fs:: read_to_string( root.join ( " b/f.txt" ) ) .unwrap ( ) ;
806
assert! ( text.contains ( " <<<<<<< oxy conflict" ) , " {text}" ) ;
807
write ( & root.join ( " b" ) , " f.txt" , b " a\n AB\n " ) ;
808
let hr = b.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
809
assert_eq! (
810
b.store ( ) .get_change ( & hr) .unwrap ( ) .kind,
811
ChangeKind:: Resolution
812
) ;
98286406 Andy McCall 1 month ago ↶
813
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
814
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
815
a.pull ( " origin" ) .unwrap ( ) ;
816
assert_eq! ( fs:: read( root.join ( " a/f.txt" ) ) .unwrap ( ) , b " a\n AB\n " ) ;
817
818
819
let mut want = vec! [ hr, h3] ;
820
want.sort ( ) ;
821
assert_eq! ( a.bookmark_heads ( ) .unwrap ( ) , want) ;
822
assert! ( ! a.load_state ( & want) .unwrap ( ) .materialize ( ) .is_conflicted ( ) ) ;
823
}
824
825
# [ test ]
826
fn receive_push_rejects_bad_updates ( ) {
827
hermetic ( ) ;
828
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
829
let root = dir.path ( ) ;
830
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
831
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
832
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
833
.unwrap ( )
834
.with_home ( root.join ( " home" ) ) ;
835
let ( key, _ ) = a
836
.create_identity ( " Ada" , " a@b" , IdentityScope:: Repo)
837
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
838
write ( & root.join ( " a" ) , " f.txt" , b " x\n " ) ;
839
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
840
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
841
let empty = origin.store ( ) .put_state ( & StateObject:: new( vec! [ ] ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
842
let s1 = origin
843
.store ( )
844
.put_state ( & StateObject:: new( vec! [ h1] ) )
845
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
846
847
75167672 Andy McCall 1 month ago
848
let ghost = origin
849
.store ( )
850
.put_state ( & StateObject:: new( vec! [ ObjectRef( [ 9 ; 32 ] ) ] ) )
851
.unwrap ( ) ;
852
let r = origin
853
.receive_push (
854
Bundle:: default( ) ,
855
vec! [ RefUpdate {
856
name: " main" .into ( ) ,
857
old_state: s1,
858
new_state: ghost,
859
force: false ,
860
attestation: None ,
861
} ] ,
862
None ,
863
vec! [ ] ,
864
)
865
.unwrap ( ) ;
866
assert! (
867
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " missing" ) ,
868
" {}" ,
869
r[ 0 ] .reason
870
) ;
98286406 Andy McCall 1 month ago ↶
871
872
75167672 Andy McCall 1 month ago
873
let r = origin
874
.receive_push (
875
Bundle:: default( ) ,
876
vec! [ RefUpdate {
877
name: " main" .into ( ) ,
878
old_state: empty,
879
new_state: s1,
880
force: false ,
881
attestation: None ,
882
} ] ,
883
None ,
884
vec! [ ] ,
885
)
886
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
887
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " stale" ) , " {}" , r[ 0 ] .reason) ;
888
889
75167672 Andy McCall 1 month ago
890
let r = origin
891
.receive_push (
892
Bundle:: default( ) ,
893
vec! [ RefUpdate {
894
name: " main" .into ( ) ,
895
old_state: s1,
896
new_state: empty,
897
force: false ,
898
attestation: None ,
899
} ] ,
900
None ,
901
vec! [ ] ,
902
)
903
.unwrap ( ) ;
904
assert! (
905
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " fast-forward" ) ,
906
" {}" ,
907
r[ 0 ] .reason
908
) ;
909
let r = origin
910
.receive_push (
911
Bundle:: default( ) ,
912
vec! [ RefUpdate {
913
name: " main" .into ( ) ,
914
old_state: s1,
915
new_state: empty,
916
force: true ,
917
attestation: None ,
918
} ] ,
919
None ,
920
vec! [ ] ,
921
)
922
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
923
assert! ( r[ 0 ] .ok) ;
924
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ ] ) ;
925
926
75167672 Andy McCall 1 month ago
927
let sa = a
928
.sign_attest ( & Attest {
929
repo: a.id ( ) .unwrap ( ) ,
930
hlc: Hlc:: now( ) ,
931
body: AttestBody:: Ref {
932
name: " main" .into ( ) ,
933
old_state: empty,
934
new_state: s1,
935
serial: 1 ,
936
} ,
937
} )
938
.unwrap ( ) ;
939
let r = origin
940
.receive_push (
941
Bundle:: default( ) ,
942
vec! [ RefUpdate {
943
name: " main" .into ( ) ,
944
old_state: empty,
945
new_state: s1,
946
force: false ,
947
attestation: Some ( sa.clone ( ) ) ,
948
} ] ,
949
None ,
950
vec! [ ] ,
951
)
952
.unwrap ( ) ;
953
assert! (
954
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " serial" ) ,
955
" {}" ,
956
r[ 0 ] .reason
957
) ;
98286406 Andy McCall 1 month ago ↶
958
75167672 Andy McCall 1 month ago
959
let sa2 = a
960
.sign_attest ( & Attest {
961
repo: a.id ( ) .unwrap ( ) ,
962
hlc: Hlc:: now( ) ,
963
body: AttestBody:: Ref {
964
name: " main" .into ( ) ,
965
old_state: s1,
966
new_state: empty,
967
serial: 5 ,
968
} ,
969
} )
970
.unwrap ( ) ;
971
let r = origin
972
.receive_push (
973
Bundle:: default( ) ,
974
vec! [ RefUpdate {
975
name: " main" .into ( ) ,
976
old_state: empty,
977
new_state: s1,
978
force: false ,
979
attestation: Some ( sa2) ,
980
} ] ,
981
None ,
982
vec! [ ] ,
983
)
984
.unwrap ( ) ;
985
assert! (
986
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " describe" ) ,
987
" {}" ,
988
r[ 0 ] .reason
989
) ;
98286406 Andy McCall 1 month ago ↶
990
let _ = key;
991
}
992
993
# [ test ]
994
fn user_config_supplies_defaults_and_clones_sign ( ) {
995
hermetic ( ) ;
996
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
997
let root = dir.path ( ) ;
998
let home = root.join ( " home" ) ;
999
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1000
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1001
1002
1003
let mut a = Repo:: init( & root.join ( " a" ) ) .unwrap ( ) .with_home ( & home) ;
75167672 Andy McCall 1 month ago
1004
let ( key, _ ) = a
1005
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1006
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1007
assert! ( ! a.config.repo_layer ( ) .contains_key ( " author.key" ) ) ;
1008
assert_eq! ( a.config.author_key, Some ( key) ) ;
1009
assert! ( home.join ( " config.toml" ) .exists ( ) ) ;
1010
a.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
1011
assert! ( a.config.sign_changes) ;
1012
1013
1014
a.config.set ( " core.sign" , " seal" ) .unwrap ( ) ;
1015
a.save_config ( ) .unwrap ( ) ;
1016
let again = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) .with_home ( & home) ;
1017
assert! ( ! again.config.sign_changes) ;
1018
assert_eq! ( again.config.author_key, Some ( key) ) ;
75167672 Andy McCall 1 month ago
1019
assert_eq! (
1020
read_config_file ( & root.join ( " a/.oxy/config" ) )
1021
.unwrap ( )
1022
.get ( " core.sign" )
1023
.map ( String :: as_str) ,
1024
Some ( " seal" )
1025
) ;
98286406 Andy McCall 1 month ago ↶
1026
1027
1028
1029
let b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) .with_home ( & home) ;
1030
assert! ( b.config.sign_changes) ;
1031
write ( & root.join ( " b" ) , " f.txt" , b " f\n " ) ;
1032
let h = b.commit ( " signed from a clone" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1033
assert! (
1034
matches! ( b.verify_change ( & h) .unwrap ( ) , SignatureStatus:: Valid { key: k, .. } if k = = key)
1035
) ;
98286406 Andy McCall 1 month ago ↶
1036
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
75167672 Andy McCall 1 month ago
1037
assert! ( matches! (
1038
origin.verify_change ( & h) .unwrap ( ) ,
1039
SignatureStatus:: Valid { .. }
1040
) ) ;
98286406 Andy McCall 1 month ago ↶
1041
}
1042
1043
# [ test ]
1044
fn format1_repositories_are_migrated_on_open ( ) {
1045
hermetic ( ) ;
1046
use crate :: oplog:: { Format1Op, Format1View} ;
1047
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1048
let root = dir.path ( ) ;
1049
let repo = Repo:: init( root) .unwrap ( ) ;
1050
write ( root, " a.txt" , b " a\n " ) ;
1051
let h1 = repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
1052
let ( wc_hash, _ ) = repo.working_copy ( ) .unwrap ( ) ;
1053
1054
let mut v1 = Format1View:: default( ) ;
1055
v1.bookmarks.insert ( " main" .into ( ) , vec! [ ] ) ;
1056
v1.workspaces.insert ( repo.workspace ( ) , wc_hash) ;
75167672 Andy McCall 1 month ago
1057
let init = Format1Op {
1058
parents: vec! [ ] ,
1059
hlc: Hlc:: default( ) ,
1060
command: " oxy init" .into ( ) ,
1061
view: v1.clone ( ) ,
1062
} ;
98286406 Andy McCall 1 month ago ↶
1063
let init_ref = repo.store ( ) .put ( ObjectType:: Op, & init.encode ( ) ) .unwrap ( ) ;
1064
v1.bookmarks.insert ( " main" .into ( ) , vec! [ h1] ) ;
75167672 Andy McCall 1 month ago
1065
let commit = Format1Op {
1066
parents: vec! [ init_ref] ,
1067
hlc: Hlc:: default( ) ,
1068
command: " oxy commit" .into ( ) ,
1069
view: v1,
1070
} ;
98286406 Andy McCall 1 month ago ↶
1071
let commit_ref = repo.store ( ) .put ( ObjectType:: Op, & commit.encode ( ) ) .unwrap ( ) ;
1072
write_atomic ( & root.join ( " .oxy/ops/head" ) , commit_ref.to_hex ( ) .as_bytes ( ) ) .unwrap ( ) ;
1073
write_atomic ( & root.join ( " .oxy/format" ) , FORMAT1_LINE .as_bytes ( ) ) .unwrap ( ) ;
1074
drop ( repo) ;
1075
1076
let repo = Repo:: open( root) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1077
assert_eq! (
1078
fs:: read_to_string( root.join ( " .oxy/format" ) ) .unwrap ( ) ,
1079
FORMAT_LINE
1080
) ;
98286406 Andy McCall 1 month ago ↶
1081
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
1082
let ops = repo.op_log ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1083
assert_eq! (
1084
ops.iter ( )
1085
.map ( | ( _, o ) | o.command.as_str ( ) )
1086
.collect:: < Vec< _ > > ( ) ,
1087
[ " oxy commit" , " oxy init" ]
1088
) ;
98286406 Andy McCall 1 month ago ↶
1089
assert_eq! ( repo.heads_of ( & ops[ 1 ] .1. view, " main" ) .unwrap ( ) , vec! [ ] ) ;
1090
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
1091
1092
assert_eq! ( Repo:: open( root) .unwrap ( ) .op_log ( ) .unwrap ( ) .len ( ) , 2 ) ;
1093
}
1094
1095
# [ test ]
1096
fn undo_skips_snapshot_operations ( ) {
1097
hermetic ( ) ;
1098
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1099
let root = dir.path ( ) ;
1100
let repo = Repo:: init( root) .unwrap ( ) ;
1101
write ( root, " a.txt" , b " a\n " ) ;
1102
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
1103
1104
write ( root, " b.txt" , b " b\n " ) ;
1105
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
1106
assert! ( s.changed) ;
1107
assert! ( repo.head_op ( ) .unwrap ( ) .1. is_snapshot ( ) ) ;
1108
assert_eq! ( repo.last_command ( ) .unwrap ( ) .command, " oxy commit -m \" a\" " ) ;
1109
1110
repo.undo ( ) .unwrap ( ) ;
1111
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ ] ) ;
1112
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
1113
let paths: Vec< _ > = s.statuses.iter ( ) .map ( | p | p.path.as_str ( ) ) .collect ( ) ;
75167672 Andy McCall 1 month ago
1114
assert_eq! (
1115
paths,
1116
[ " a.txt" , " b.txt" ] ,
1117
" both files are back in the working copy"
1118
) ;
98286406 Andy McCall 1 month ago ↶
1119
assert! ( repo.store ( ) .get_change ( & h1) .unwrap ( ) .deps.is_empty ( ) ) ;
1120
1121
assert_eq! ( repo.last_command ( ) .unwrap ( ) .command, " oxy init" ) ;
1122
assert! ( repo.undo ( ) .is_err ( ) ) ;
1123
}
1124
1125
# [ test ]
1126
fn independent_changes_commute_and_make_multiple_heads ( ) {
1127
hermetic ( ) ;
1128
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1129
let root = dir.path ( ) ;
1130
let repo = Repo:: init( root) .unwrap ( ) ;
1131
write ( root, " a.txt" , b " a\n " ) ;
1132
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
1133
write ( root, " b.txt" , b " b\n " ) ;
1134
let h2 = repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
1135
let c2 = repo.store ( ) .get_change ( & h2) .unwrap ( ) ;
1136
assert! ( c2.deps.is_empty ( ) , " b.txt touches nothing from h1" ) ;
1137
let mut heads = repo.bookmark_heads ( ) .unwrap ( ) ;
1138
heads.sort ( ) ;
1139
let mut want = vec! [ h1, h2] ;
1140
want.sort ( ) ;
1141
assert_eq! ( heads, want) ;
1142
1143
write ( root, " a.txt" , b " a\n a2\n " ) ;
1144
let h3 = repo.commit ( " a2" , & [ ] ) .unwrap ( ) ;
1145
assert_eq! ( repo.store ( ) .get_change ( & h3) .unwrap ( ) .deps, vec! [ h1] ) ;
1146
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
1147
assert_eq! ( heads, [ h2, h3] .into_iter ( ) .collect ( ) ) ;
1148
1149
write ( root, " c.txt" , b " c\n " ) ;
1150
let h4 = repo.commit ( " c" , & [ h2] ) .unwrap ( ) ;
1151
assert_eq! ( repo.store ( ) .get_change ( & h4) .unwrap ( ) .deps, vec! [ h2] ) ;
1152
}
1153
1154
# [ test ]
1155
fn mode_change_and_text_binary_switch ( ) {
1156
hermetic ( ) ;
1157
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1158
let root = dir.path ( ) ;
1159
let repo = Repo:: init( root) .unwrap ( ) ;
1160
write ( root, " run.sh" , b " #!/bin/sh\n " ) ;
1161
repo.commit ( " add script" , & [ ] ) .unwrap ( ) ;
1162
# [ cfg ( unix ) ]
1163
{
1164
use std:: os:: unix:: fs:: PermissionsExt;
1165
fs:: set_permissions( root.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
1166
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1167
assert_eq! (
1168
statuses ( & s) ,
1169
vec! [ ( " run.sh" .into ( ) , ChangeStatus:: ModeChanged) ]
1170
) ;
98286406 Andy McCall 1 month ago ↶
1171
let h = repo.commit ( " exec" , & [ ] ) .unwrap ( ) ;
1172
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1173
assert! ( snap.files[ " run.sh" ] .mode.exec) ;
1174
}
1175
1176
write ( root, " run.sh" , b " \0 binary" ) ;
1177
let h = repo.commit ( " binary" , & [ ] ) .unwrap ( ) ;
1178
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1179
assert! ( snap.files[ " run.sh" ] .mode.opaque) ;
1180
write ( root, " run.sh" , b " text again\n " ) ;
1181
let h = repo.commit ( " text" , & [ ] ) .unwrap ( ) ;
1182
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1183
assert! ( ! snap.files[ " run.sh" ] .mode.opaque) ;
75167672 Andy McCall 1 month ago
1184
assert! (
1185
matches! ( & snap.files[ " run.sh" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " text again\n " )
1186
) ;
98286406 Andy McCall 1 month ago ↶
1187
assert! ( ! snap.is_conflicted ( ) ) ;
1188
}
83e0677b Andy McCall 1 month ago ↶
1189
6eae06b6 Andy McCall 29 days ago ↶
1190
1191
1192
# [ cfg ( unix ) ]
1193
# [ test ]
1194
fn a_binary_file_changed_and_made_executable_at_once_keeps_both ( ) {
1195
use std:: os:: unix:: fs:: PermissionsExt;
1196
hermetic ( ) ;
1197
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1198
let root = dir.path ( ) ;
1199
let repo = Repo:: init( root) .unwrap ( ) ;
1200
write ( root, " tool.exe" , b " MZ\0 one" ) ;
1201
repo.commit ( " add" , & [ ] ) .unwrap ( ) ;
1202
write ( root, " tool.exe" , b " MZ\0 two" ) ;
1203
fs:: set_permissions( root.join ( " tool.exe" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
1204
let h = repo.commit ( " update and +x" , & [ ] ) .unwrap ( ) ;
1205
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1206
let f = & snap.files[ " tool.exe" ] ;
1207
assert! ( f.mode.opaque & & f.mode.exec, " {:?}" , f.mode) ;
1208
assert! (
1209
repo.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ,
1210
" nothing left to commit"
1211
) ;
1212
}
1213
f0a517d2 Andy McCall 20 days ago ↶
1214
1215
1216
1217
83e0677b Andy McCall 1 month ago ↶
1218
# [ test ]
f0a517d2 Andy McCall 20 days ago ↶
1219
fn a_certified_key_pushes_under_a_subject_grant ( ) {
1220
use crate :: cert:: { Certificate, SignedCertificate} ;
1221
use crate :: identity:: Scope;
1222
hermetic ( ) ;
1223
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1224
let root = dir.path ( ) ;
1225
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1226
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1227
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1228
.unwrap ( )
1229
.with_home ( root.join ( " home-a" ) ) ;
1230
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1231
.unwrap ( ) ;
1232
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1233
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1234
a.policy_init ( ) .unwrap ( ) ;
1235
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1236
let hub_sk = SecretKey:: generate( ) ;
1237
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1238
a.policy_grant_subject (
1239
" agent:claude@ada" ,
1240
hub.clone ( ) ,
1241
Scope:: CHANGES | Scope:: REFS ,
1242
None ,
1243
)
1244
.unwrap ( ) ;
1245
assert! ( a
1246
.push ( " origin" , false )
1247
.unwrap ( )
1248
.iter ( )
1249
.any ( | r | r.name = = " policy" & & r.ok ) ) ;
1250
1251
let now = Hlc:: now( ) .wall_ms;
1252
1253
1254
let agent = | name : & str , subject : & str , from : u64 , to : u64 | {
1255
let mut r = Repo:: clone( & url, & root.join ( name) )
1256
.unwrap ( )
1257
.with_home ( root.join ( format! ( " home-{name} " ) ) ) ;
1258
let ( _ , id) = r
1259
.create_identity ( name, " [email protected] " , IdentityScope:: User)
1260
.unwrap ( ) ;
1261
let cert = SignedCertificate:: sign(
1262
Certificate {
1263
key: id,
1264
issuer: hub.hash ( ) ,
1265
subject: subject.into ( ) ,
1266
claims: vec! [ ] ,
1267
scope: Scope:: CHANGES | Scope:: REFS ,
1268
not_before: from,
1269
expires: to,
a2387d54 Andy McCall 20 days ago ↶
1270
grant: None ,
f0a517d2 Andy McCall 20 days ago ↶
1271
} ,
1272
& hub,
1273
& hub_sk,
1274
) ;
1275
r.store ( ) .put_identity ( & hub) .unwrap ( ) ;
1276
r.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1277
write ( & root.join ( name) , & format! ( " {name} .txt" ) , b " agent work\n " ) ;
1278
r.commit ( " work" , & [ ] ) .unwrap ( ) ;
1279
r
1280
} ;
1281
1282
let claude = agent ( " claude" , " agent:claude@ada" , now - 60_000 , now + 600_000 ) ;
1283
let r = claude.push ( " origin" , false ) .unwrap ( ) ;
1284
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
1285
1286
a.pull ( " origin" ) .unwrap ( ) ;
1287
assert! ( a.bookmark_heads ( ) .unwrap ( ) .iter ( ) .any ( | h | a
1288
.store ( )
1289
.get_change ( h)
1290
.map ( | c | c.message = = " work" )
1291
.unwrap_or ( false ) ) ) ;
1292
f53cbe7a Andy McCall 20 days ago ↶
1293
1294
1295
1296
1297
let host = Repo:: open( & root.join ( " origin" ) )
1298
.unwrap ( )
1299
.with_acceptor ( hub.clone ( ) , hub_sk.clone ( ) ) ;
1300
let ( attest_ref, sa) = host.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
1301
host.record_acceptance ( & attest_ref) .unwrap ( ) ;
1302
let served = host.remote_refs ( ) .unwrap ( ) ;
1303
let acc = served
1304
.refs
1305
.iter ( )
1306
.find ( | r | r.name = = " main" )
1307
.unwrap ( )
1308
.accepted
1309
.clone ( )
1310
.expect ( " served with the refs" ) ;
1311
let signer = sa.sigs[ 0 ] .key;
1312
assert_eq! (
1313
a.certified_time ( & signer, & sa, Some ( & acc) ) ,
1314
acc.attest.hlc.wall_ms
1315
) ;
1316
assert_eq! ( a.certified_time ( & signer, & sa, None ) , sa.attest.hlc.wall_ms) ;
1317
let stranger_sk = SecretKey:: generate( ) ;
1318
let stranger = stranger_sk.identity ( " stranger" , " s@x" , 1 ) ;
1319
let mut forged = acc.clone ( ) ;
1320
forged.sigs = vec! [ stranger_sk.sign ( Domain:: Attest, & stranger.hash ( ) , & acc.attest.hash ( ) ) ] ;
1321
assert_eq! (
1322
a.certified_time ( & signer, & sa, Some ( & forged) ) ,
1323
sa.attest.hlc.wall_ms,
1324
" not the issuer's"
1325
) ;
1326
let mut elsewhere = acc.clone ( ) ;
1327
elsewhere.attest.body = AttestBody:: Accepted {
1328
attest: ObjectRef( [ 7 ; 32 ] ) ,
1329
} ;
1330
elsewhere.sigs = vec! [ hub_sk.sign ( Domain:: Attest, & hub.hash ( ) , & elsewhere.attest.hash ( ) ) ] ;
1331
assert_eq! (
1332
a.certified_time ( & signer, & sa, Some ( & elsewhere) ) ,
1333
sa.attest.hlc.wall_ms,
1334
" another attestation's"
1335
) ;
1336
a.pull ( " origin" ) .unwrap ( ) ;
1337
1338
host.gc ( true ) .unwrap ( ) ;
1339
assert! ( host.acceptance_of ( & attest_ref) .unwrap ( ) .is_some ( ) ) ;
1340
f0a517d2 Andy McCall 20 days ago ↶
1341
let late = agent ( " late" , " agent:claude@ada" , now - 1_200_000 , now - 600_000 ) ;
1342
let r = late.push ( " origin" , false ) .unwrap ( ) ;
1343
assert! (
1344
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " may not update" ) ,
1345
" an expired certificate: {r:?}"
1346
) ;
1347
let other = agent ( " other" , " agent:mallory@ada" , now - 60_000 , now + 600_000 ) ;
1348
let r = other.push ( " origin" , false ) .unwrap ( ) ;
1349
assert! (
1350
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " may not update" ) ,
1351
" a subject not granted: {r:?}"
1352
) ;
1353
}
1354
d8524a9f Andy McCall 18 days ago ↶
1355
1356
1357
1358
1359
# [ test ]
1360
fn a_tag_signed_by_an_earlier_short_lived_key_is_pushed ( ) {
1361
use crate :: cert:: { Certificate, SignedCertificate} ;
1362
use crate :: identity:: Scope;
1363
hermetic ( ) ;
1364
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1365
let root = dir.path ( ) ;
1366
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1367
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1368
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1369
.unwrap ( )
1370
.with_home ( root.join ( " home-a" ) ) ;
1371
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1372
.unwrap ( ) ;
1373
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1374
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1375
a.policy_init ( ) .unwrap ( ) ;
1376
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1377
let hub_sk = SecretKey:: generate( ) ;
1378
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1379
a.policy_grant_subject (
1380
" agent:claude@ada" ,
1381
hub.clone ( ) ,
1382
Scope:: CHANGES | Scope:: REFS ,
1383
Some ( " *" ) ,
1384
)
1385
.unwrap ( ) ;
1386
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1387
1388
let now = Hlc:: now( ) .wall_ms;
1389
let mut agent = Repo:: clone( & url, & root.join ( " agent" ) )
1390
.unwrap ( )
1391
.with_home ( root.join ( " home-agent" ) ) ;
1392
agent.store ( ) .put_identity ( & hub) .unwrap ( ) ;
1393
let certify = | agent : & Repo, id | {
1394
let cert = SignedCertificate:: sign(
1395
Certificate {
1396
key: id,
1397
issuer: hub.hash ( ) ,
1398
subject: " agent:claude@ada" .into ( ) ,
1399
claims: vec! [ ] ,
1400
scope: Scope:: CHANGES | Scope:: REFS ,
1401
not_before: now - 60_000 ,
1402
expires: now + 600_000 ,
1403
grant: None ,
1404
} ,
1405
& hub,
1406
& hub_sk,
1407
) ;
1408
agent.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1409
} ;
1410
1411
let ( first, first_id) = agent
1412
.create_identity ( " claude" , " [email protected] " , IdentityScope:: User)
1413
.unwrap ( ) ;
1414
certify ( & agent, first_id) ;
1415
let heads = agent.bookmark_heads ( ) .unwrap ( ) ;
1416
agent.tag_create ( " v1" , & heads, " first" ) .unwrap ( ) ;
1417
1418
let ( second, second_id) = agent
1419
.create_identity ( " claude" , " [email protected] " , IdentityScope:: User)
1420
.unwrap ( ) ;
1421
assert_ne! ( first, second) ;
1422
certify ( & agent, second_id) ;
1423
agent
1424
.set_user_config ( " author.signer" , & second.to_hex ( ) )
1425
.unwrap ( ) ;
1426
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
1ec6fdbc Andy McCall 17 days ago ↶
1427
assert! (
1428
host.store ( ) .get_identity ( & first) .is_err ( ) ,
1429
" the host has never seen the tag's key"
1430
) ;
d8524a9f Andy McCall 18 days ago ↶
1431
let r = agent.push ( " origin" , false ) .unwrap ( ) ;
1ec6fdbc Andy McCall 17 days ago ↶
1432
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
1433
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
1434
assert! (
1435
host.tag_refs ( ) .unwrap ( ) .iter ( ) .any ( | t | t.name = = " v1" ) ,
1436
" the tag arrived"
1437
) ;
1438
}
1439
1440
1441
1442
1443
1444
1445
1446
# [ test ]
1447
fn a_short_lived_key_s_tag_is_fetched_at_its_issuer_s_acceptance ( ) {
1448
use crate :: cert:: { Certificate, SignedCertificate} ;
1449
use crate :: identity:: Scope;
1450
hermetic ( ) ;
1451
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1452
let root = dir.path ( ) ;
1453
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1454
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1455
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1456
.unwrap ( )
1457
.with_home ( root.join ( " home-a" ) ) ;
1458
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1459
.unwrap ( ) ;
1460
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1461
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1462
a.policy_init ( ) .unwrap ( ) ;
1463
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1464
let hub_sk = SecretKey:: generate( ) ;
1465
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1466
a.policy_grant_subject (
1467
" agent:claude@ada" ,
1468
hub.clone ( ) ,
1469
Scope:: CHANGES | Scope:: REFS ,
1470
Some ( " *" ) ,
1471
)
1472
.unwrap ( ) ;
1473
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1474
1475
let now = Hlc:: now( ) .wall_ms;
1476
1477
let agent = | name : & str , from : u64 , to : u64 | {
1478
let mut r = Repo:: clone( & url, & root.join ( name) )
1479
.unwrap ( )
1480
.with_home ( root.join ( format! ( " home-{name} " ) ) ) ;
1481
r.store ( ) .put_identity ( & hub) .unwrap ( ) ;
1482
let ( _ , id) = r
1483
.create_identity ( name, " [email protected] " , IdentityScope:: User)
1484
.unwrap ( ) ;
1485
let cert = SignedCertificate:: sign(
1486
Certificate {
1487
key: id,
1488
issuer: hub.hash ( ) ,
1489
subject: " agent:claude@ada" .into ( ) ,
1490
claims: vec! [ ] ,
1491
scope: Scope:: CHANGES | Scope:: REFS ,
1492
not_before: from,
1493
expires: to,
1494
grant: None ,
1495
} ,
1496
& hub,
1497
& hub_sk,
1498
) ;
1499
r.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1500
r
1501
} ;
1502
1503
1504
1505
1506
let claude = agent ( " claude" , now - 60_000 , now + 600_000 ) ;
1507
let heads = claude.bookmark_heads ( ) .unwrap ( ) ;
1508
claude.tag_create ( " v1" , & heads, " one" ) .unwrap ( ) ;
1509
let r = claude.push ( " origin" , false ) .unwrap ( ) ;
d8524a9f Andy McCall 18 days ago ↶
1510
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
1ec6fdbc Andy McCall 17 days ago ↶
1511
let b = Repo:: clone( & url, & root.join ( " b" ) )
1512
.unwrap ( )
1513
.with_home ( root.join ( " home-b" ) ) ;
1514
let rep = b.fetch ( " origin" ) .unwrap ( ) ;
1515
assert! (
1516
rep.skipped
1517
.iter ( )
1518
.any ( | ( n , why ) | n = = " tags/v1" & & why.contains ( " no time to check its certificate at" ) ) ,
1519
" {rep:?}"
1520
) ;
1521
assert_eq! ( b.tag_heads ( " v1" ) .unwrap ( ) , None ) ;
1522
1523
1524
1525
let host = Repo:: open( & root.join ( " origin" ) )
1526
.unwrap ( )
1527
.with_acceptor ( hub.clone ( ) , hub_sk.clone ( ) ) ;
1528
let ( v1, _ ) = host.tag_attest ( " v1" ) .unwrap ( ) .unwrap ( ) ;
1529
host.record_acceptance ( & v1) .unwrap ( ) ;
1530
let served = host.tag_refs ( ) .unwrap ( ) ;
1531
assert! ( served
1532
.iter ( )
1533
.any ( | t | t.name = = " v1" & & t.accepted.is_some ( ) ) ) ;
1534
let rep = b.fetch ( " origin" ) .unwrap ( ) ;
1535
assert! ( rep.skipped.is_empty ( ) , " {rep:?}" ) ;
1536
assert_eq! ( b.tag_heads ( " v1" ) .unwrap ( ) , Some ( heads.clone ( ) ) ) ;
1537
1538
host.gc ( true ) .unwrap ( ) ;
1539
assert! ( host.acceptance_of ( & v1) .unwrap ( ) .is_some ( ) ) ;
1540
1541
1542
1543
let late = agent ( " late" , now - 7_200_000 , now - 3_600_000 ) ;
1544
late.tag_create ( " v2" , & heads, " two" ) .unwrap ( ) ;
1545
let r = late.push ( " origin" , false ) .unwrap ( ) ;
1546
assert! (
1547
r.iter ( )
1548
.any ( | x | x.name = = " tags/v2" & & ! x.ok & & x.reason.contains ( " may not create" ) ) ,
1549
" {r:?}"
1550
) ;
1551
1552
1553
1554
let ( v2, _ ) = late.tag_attest ( " v2" ) .unwrap ( ) .unwrap ( ) ;
1555
let acc = Attest {
1556
repo: late.id ( ) .unwrap ( ) ,
1557
hlc: Hlc {
1558
wall_ms: now - 5_400_000 ,
1559
logical: 0 ,
1560
} ,
1561
body: AttestBody:: Accepted { attest: v2 } ,
1562
} ;
1563
let sig = hub_sk.sign ( Domain:: Attest, & hub.hash ( ) , & acc.hash ( ) ) ;
1564
1565
let stranger_sk = SecretKey:: generate( ) ;
1566
let stranger = stranger_sk.identity ( " stranger" , " [email protected] " , 1 ) ;
1567
let forged = stranger_sk.sign ( Domain:: Attest, & stranger.hash ( ) , & acc.hash ( ) ) ;
1568
late.keep_acceptance (
1569
& v2,
1570
& SignedAttest {
1571
attest: acc.clone ( ) ,
1572
sigs: vec! [ forged] ,
1573
} ,
1574
)
1575
.unwrap ( ) ;
1576
let r = late.push ( " origin" , false ) .unwrap ( ) ;
1577
assert! (
1578
r.iter ( ) .any ( | x | x.name = = " tags/v2" & & ! x.ok ) ,
1579
" not the issuer's: {r:?}"
1580
) ;
1581
let theirs = SignedAttest {
1582
attest: acc,
1583
sigs: vec! [ sig] ,
1584
} ;
1585
1586
late.keep_acceptance ( & v2, & theirs) .unwrap ( ) ;
1587
let r = late.push ( " origin" , false ) .unwrap ( ) ;
1588
1589
1590
assert! ( r.iter ( ) .any ( | x | x.name = = " tags/v2" & & x.ok ) , " {r:?}" ) ;
d8524a9f Andy McCall 18 days ago ↶
1591
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
1ec6fdbc Andy McCall 17 days ago ↶
1592
let ( hv2, _ ) = host.tag_attest ( " v2" ) .unwrap ( ) .unwrap ( ) ;
1593
assert_eq! ( hv2, v2) ;
1594
assert! ( host
1595
.store ( )
1596
.index_get_pub ( " accepted" , & v2)
1597
.unwrap ( )
1598
.contains ( & theirs.attest.hash ( ) ) ) ;
1599
let c = Repo:: clone( & url, & root.join ( " c" ) )
1600
.unwrap ( )
1601
.with_home ( root.join ( " home-c" ) ) ;
1602
assert_eq! ( c.tag_heads ( " v2" ) .unwrap ( ) , Some ( heads.clone ( ) ) ) ;
1603
assert_eq! ( c.tag_heads ( " v1" ) .unwrap ( ) , Some ( heads) ) ;
d8524a9f Andy McCall 18 days ago ↶
1604
}
1605
d36ba2f7 Andy McCall 18 days ago ↶
1606
1607
1608
1609
1610
1611
# [ test ]
1612
fn a_bookmark_moved_by_a_certified_key_alone_is_fetched ( ) {
1613
use crate :: cert:: { Certificate, SignedCertificate} ;
1614
use crate :: identity:: Scope;
1615
hermetic ( ) ;
1616
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1617
let root = dir.path ( ) ;
1618
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1619
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1620
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1621
.unwrap ( )
1622
.with_home ( root.join ( " home-a" ) ) ;
1623
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1624
.unwrap ( ) ;
1625
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1626
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1627
a.policy_init ( ) .unwrap ( ) ;
1628
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1629
let hub_sk = SecretKey:: generate( ) ;
1630
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1631
a.policy_grant_subject (
1632
" agent:claude@ada" ,
1633
hub.clone ( ) ,
1634
Scope:: CHANGES | Scope:: REFS ,
1635
None ,
1636
)
1637
.unwrap ( ) ;
1638
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1639
1640
let now = Hlc:: now( ) .wall_ms;
1641
let mut agent = Repo:: clone( & url, & root.join ( " agent" ) )
1642
.unwrap ( )
1643
.with_home ( root.join ( " home-agent" ) ) ;
1644
let ( key, id) = agent
1645
.create_identity ( " claude" , " [email protected] " , IdentityScope:: User)
1646
.unwrap ( ) ;
1647
let cert = SignedCertificate:: sign(
1648
Certificate {
1649
key: id,
1650
issuer: hub.hash ( ) ,
1651
subject: " agent:claude@ada" .into ( ) ,
1652
claims: vec! [ ] ,
1653
scope: Scope:: CHANGES | Scope:: REFS ,
1654
not_before: now - 60_000 ,
1655
expires: now + 600_000 ,
1656
grant: None ,
1657
} ,
1658
& hub,
1659
& hub_sk,
1660
) ;
1661
agent.store ( ) .put_identity ( & hub) .unwrap ( ) ;
1662
agent.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1663
let heads = agent.bookmark_heads ( ) .unwrap ( ) ;
1664
agent.bookmark_create ( " agent-work" , Some ( & heads) ) .unwrap ( ) ;
1665
let r = agent.push_bookmark ( " origin" , " agent-work" , false ) .unwrap ( ) ;
1666
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
1667
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
1668
let ( _ , sa) = host.latest_ref_attest ( " agent-work" ) .unwrap ( ) .unwrap ( ) ;
1669
assert_eq! ( sa.sigs[ 0 ] .key, key, " the certified key alone moved it" ) ;
1670
1671
1672
let bundle = host.bundle_raw_for ( & [ ] , None ) .unwrap ( ) ;
1673
assert! ( bundle.changes.is_empty ( ) & & bundle.raw_changes.is_empty ( ) ) ;
1674
assert_eq! ( bundle.certificates.len ( ) , 1 ) ;
1675
for k in [ key, hub.hash ( ) ] {
1676
assert! ( bundle
1677
.objects
1678
.iter ( )
1679
.any ( | ( t , b ) | * t = = ObjectType:: Identity & & hash_object ( * t, b) = = k) ) ;
1680
}
1681
1682
let c = Repo:: clone( & url, & root.join ( " c" ) ) .unwrap ( ) ;
1683
let tracked = c.view ( ) .unwrap ( ) .remotes[ " origin" ] .clone ( ) ;
1684
assert_eq! (
1685
tracked.get ( " agent-work" ) ,
1686
host.view ( ) .unwrap ( ) .bookmarks.get ( " agent-work" )
1687
) ;
1688
assert! ( c.store ( ) .certificate_of ( & key) .unwrap ( ) .is_some ( ) ) ;
1689
}
1690
028545df Andy McCall 19 days ago ↶
1691
1692
1693
1694
1695
1696
1697
# [ test ]
1698
fn a_fetched_attestation_is_judged_at_its_integrated_time ( ) {
1699
use super :: transparency:: LogCheck;
1700
use crate :: cert:: { Certificate, SignedCertificate} ;
1701
use crate :: identity:: Scope;
1702
use crate :: protocol:: { Integration, Logged, RefsResponse, RemoteRef} ;
1703
use crate :: tlog:: { self , Checkpoint, Entry, Hash, Integrated, Note} ;
1704
hermetic ( ) ;
1705
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1706
let root = dir.path ( ) ;
1707
let mut a = Repo:: init( & root.join ( " a" ) )
1708
.unwrap ( )
1709
.with_home ( root.join ( " home-a" ) ) ;
1710
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1711
.unwrap ( ) ;
1712
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1713
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1714
a.policy_init ( ) .unwrap ( ) ;
1715
let issuer_sk = SecretKey:: generate( ) ;
1716
let issuer = issuer_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1717
a.policy_grant_subject (
1718
" agent:claude@ada" ,
1719
issuer.clone ( ) ,
1720
Scope:: CHANGES | Scope:: REFS ,
1721
None ,
1722
)
1723
.unwrap ( ) ;
1724
let p = a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy;
1725
1726
1727
let now = Hlc:: now( ) .wall_ms;
1728
let t0 = now - 2 * 3_600_000 ;
1729
let agent_sk = SecretKey:: generate( ) ;
1730
let agent = agent_sk.identity ( " claude" , " [email protected] " , t0) ;
1731
let cert = SignedCertificate:: sign(
1732
Certificate {
1733
key: agent.clone ( ) ,
1734
issuer: issuer.hash ( ) ,
1735
subject: " agent:claude@ada" .into ( ) ,
1736
claims: vec! [ ] ,
1737
scope: Scope:: CHANGES | Scope:: REFS ,
1738
not_before: t0,
1739
expires: t0 + 600_000 ,
1740
grant: None ,
1741
} ,
1742
& issuer,
1743
& issuer_sk,
1744
) ;
1745
a.store ( ) .put_identity ( & agent) .unwrap ( ) ;
1746
a.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1747
let signer = agent.hash ( ) ;
1748
let attested = | claimed : u64 | {
1749
let attest = Attest {
1750
repo: a.id ( ) .unwrap ( ) ,
1751
hlc: Hlc {
1752
wall_ms: claimed,
1753
logical: 0 ,
1754
} ,
1755
body: AttestBody:: Ref {
1756
name: " main" .into ( ) ,
1757
old_state: ObjectRef( [ 1 ; 32 ] ) ,
1758
new_state: ObjectRef( [ 2 ; 32 ] ) ,
1759
serial: 1 ,
1760
} ,
1761
} ;
1762
let sig = agent_sk.sign ( Domain:: Attest, & signer, & attest.hash ( ) ) ;
1763
SignedAttest {
1764
attest,
1765
sigs: vec! [ sig] ,
1766
}
1767
} ;
1768
1769
1770
1771
let log_sk = SecretKey:: generate( ) ;
1772
let log_key = log_sk.identity ( " hub.example" , " [email protected] " , 1 ) ;
1773
let origin = " hub.example/log" ;
1774
let hub = " https://hub.example" ;
1775
let url = " https://hub.example/ada/a" ;
1776
let w_sk = SecretKey:: generate( ) ;
1777
let w = w_sk.identity ( " w" , " [email protected] " , 1 ) ;
1778
let stranger_sk = SecretKey:: generate( ) ;
1779
let stranger = stranger_sk.identity ( " s" , " [email protected] " , 1 ) ;
1780
let sa = attested ( t0 + 300_000 ) ;
1781
let entry_of = | i : u64 | {
1782
let ( kind, object) = if i = = 3 {
1783
( " ref" , sa.attest.hash ( ) )
1784
} else {
1785
( " seal" , ObjectRef( [ i as u8 ; 32 ] ) )
1786
} ;
1787
Entry {
1788
kind: kind.into ( ) ,
1789
repo: sa.attest.repo,
1790
object,
1791
signers: vec! [ signer] ,
1792
received: t0 + 300_000 ,
1793
}
1794
.encode ( )
1795
} ;
1796
let mut stored: Vec< Hash> = Vec :: new( ) ;
1797
for i in 0 .. 8 {
1798
let add = tlog:: stored_hashes_for_leaf( i, tlog:: leaf_hash( & entry_of ( i) ) , & stored) .unwrap ( ) ;
1799
stored.extend ( add) ;
1800
}
1801
let note_of = | n : u64 , cosigners : & [( & str , & Identity, & SecretKey, u64 ) ]| {
1802
let mut note = Checkpoint {
1803
origin: origin.into ( ) ,
1804
size: n,
1805
root: tlog:: tree_hash( n, & stored) .unwrap ( ) ,
1806
extensions: vec! [ ] ,
1807
}
1808
.sign ( & log_key, & log_sk)
1809
.unwrap ( ) ;
1810
for ( name, id, sk, t) in cosigners {
1811
note.sigs
1812
.extend ( tlog:: cosign( & note.text, name, id, sk, * t) .unwrap ( ) ) ;
1813
}
1814
note.encode ( )
1815
} ;
1816
let latest = note_of ( 8 , & [ ] ) ;
1817
let witnessed_at = ( t0 + 360_000 ) / 1000 ;
1818
let cosigned = note_of (
1819
4 ,
1820
& [
1821
( " w.example" , & w, & w_sk, witnessed_at) ,
1822
( " s.example" , & stranger, & stranger_sk, 1 ) ,
1823
] ,
1824
) ;
1825
let answer = | integration : Vec< Integration> , witnesses : Vec< ( String, Vec< u8 > ) > | RefsResponse {
1826
refs: vec! [ RemoteRef {
1827
name: " main" .into ( ) ,
1828
state: ObjectRef( [ 2 ; 32 ] ) ,
1829
heads: vec! [ ] ,
1830
attestation: Some ( sa.clone ( ) ) ,
1831
last_serial: None ,
1832
accepted: None ,
1833
logged: Some ( Logged {
1834
index: 3 ,
1835
entry: entry_of ( 3 ) ,
1836
proof: tlog:: prove_inclusion( 8 , 3 , & stored) .unwrap ( ) ,
1837
} ) ,
1838
integration,
1839
} ] ,
1840
log: Some ( latest.clone ( ) ) ,
1841
witnesses,
1842
.. RefsResponse:: default( )
1843
} ;
1844
let proof4 = tlog:: prove_inclusion( 4 , 3 , & stored) .unwrap ( ) ;
1845
let served = vec! [ Integration {
1846
note: cosigned.clone ( ) ,
1847
proof: proof4.clone ( ) ,
1848
} ] ;
1849
let listed = vec! [ ( " w.example" .to_string ( ) , w.encode ( ) ) ] ;
1850
1851
a.remember_log ( hub, & log_key, & Note:: parse( & latest) .unwrap ( ) )
1852
.unwrap ( ) ;
1853
1854
1855
1856
let resp = answer ( served.clone ( ) , listed.clone ( ) ) ;
1857
let lc = a.check_log ( url, None , & resp) .unwrap ( ) ;
1858
assert! ( lc.notes.is_empty ( ) , " {:?}" , lc.notes) ;
1859
let t = lc.time_of ( " main" ) .unwrap ( ) ;
1860
assert_eq! ( ( t.time, t.witness.as_str ( ) ) , ( witnessed_at, " w.example" ) ) ;
1861
1862
1863
assert! ( cert.cert.expires < now) ;
1864
let mut notes = Vec :: new( ) ;
1865
a.authorize_in_time ( & p, " origin" , & resp.refs[ 0 ] , & sa, & signer, & lc, & mut notes)
1866
.unwrap ( ) ;
1867
assert! ( notes.is_empty ( ) , " {notes:?}" ) ;
1868
1869
1870
1871
let lonely = vec! [ Integration {
1872
note: note_of ( 4 , & [ ( " s.example" , & stranger, & stranger_sk, 1 ) ] ) ,
1873
proof: proof4.clone ( ) ,
1874
} ] ;
1875
let resp = answer ( lonely, vec! [ ] ) ;
1876
let lc = a.check_log ( url, None , & resp) .unwrap ( ) ;
1877
assert! ( lc
1878
.time_of ( " main" )
1879
.unwrap_err ( )
1880
.contains ( " no witness this user trusts" ) ) ;
1881
let mut notes = Vec :: new( ) ;
1882
a.authorize_in_time ( & p, " origin" , & resp.refs[ 0 ] , & sa, & signer, & lc, & mut notes)
1883
.unwrap ( ) ;
1884
assert! (
1885
notes.len ( ) = = 1 & & notes[ 0 ] .contains ( " not at a witnessed time" ) ,
1886
" {notes:?}"
1887
) ;
1888
1889
1890
1891
let resp = answer (
1892
served.clone ( ) ,
1893
vec! [ ( " w.example" .into ( ) , stranger.encode ( ) ) ] ,
1894
) ;
1895
let lc = a.check_log ( url, None , & resp) .unwrap ( ) ;
1896
assert! (
1897
lc.notes.iter ( ) .any ( | n | n.contains ( " another key" ) ) ,
1898
" {:?}" ,
1899
lc.notes
1900
) ;
1901
assert! (
1902
lc.time_of ( " main" ) .is_err ( ) ,
1903
" w's cosignature is by its old key"
1904
) ;
1905
a.check_log ( url, None , & answer ( served.clone ( ) , listed.clone ( ) ) )
1906
.unwrap ( ) ;
1907
1908
1909
1910
let mut old = answer ( vec! [ ] , vec! [ ] ) ;
1911
old.log = None ;
1912
old.refs[ 0 ] .logged = None ;
1913
let lc = a.check_log ( url, None , & old) .unwrap ( ) ;
1914
assert! ( ! lc.kept & & lc.notes.is_empty ( ) ) ;
1915
let mut notes = Vec :: new( ) ;
1916
a.authorize_in_time ( & p, " origin" , & old.refs[ 0 ] , & sa, & signer, & lc, & mut notes)
1917
.unwrap ( ) ;
1918
assert! ( notes.is_empty ( ) , " {notes:?}" ) ;
1919
1920
1921
let at = | secs_after_t0 : u64 | {
1922
let mut times = std:: collections:: HashMap:: new( ) ;
1923
times.insert (
1924
" main" .to_string ( ) ,
1925
Ok ( Integrated {
1926
time: ( t0 + secs_after_t0 * 1000 ) / 1000 ,
1927
witness: " w.example" .into ( ) ,
1928
size: 4 ,
1929
} ) ,
1930
) ;
1931
LogCheck {
1932
kept: true ,
1933
times,
1934
.. LogCheck:: default( )
1935
}
1936
} ;
1937
let judged = | sa : & SignedAttest, lc : & LogCheck| {
1938
let mut notes = Vec :: new( ) ;
1939
a.authorize_in_time ( & p, " origin" , & old.refs[ 0 ] , sa, & signer, lc, & mut notes)
1940
.map ( | _| notes )
1941
} ;
1942
1943
1944
1945
let notes = judged ( & sa, & at ( 3600 ) ) .unwrap ( ) ;
1946
assert! (
1947
notes
1948
.iter ( )
1949
.any ( | n | n.contains ( " had lapsed when witness w.example" ) )
1950
& & notes
1951
.iter ( )
1952
.any ( | n | n.contains ( " 55 minutes before witness" ) ) ,
1953
" {notes:?}"
1954
) ;
1955
1956
1957
1958
let fast = attested ( now) ;
1959
let notes = judged ( & fast, & at ( 400 ) ) .unwrap ( ) ;
1960
assert! (
1961
notes.len ( ) = = 1 & & notes[ 0 ] .contains ( " after witness w.example saw it" ) ,
1962
" {notes:?}"
1963
) ;
1964
1965
assert! ( judged ( & fast, & at ( 3600 ) ) .is_err ( ) ) ;
1966
1967
assert! ( judged ( & sa, & at ( 500 ) ) .unwrap ( ) .is_empty ( ) ) ;
1968
}
1969
a2387d54 Andy McCall 20 days ago ↶
1970
1971
1972
1973
45a6f5ec Andy McCall 13 days ago ↶
1974
1975
1976
f0a517d2 Andy McCall 20 days ago ↶
1977
# [ test ]
45a6f5ec Andy McCall 13 days ago ↶
1978
fn a_grant_outlives_its_owner_s_rotation ( ) {
1979
use crate :: cert:: { Certificate, Grant, SignedCertificate, SignedGrant} ;
1980
use crate :: identity:: Scope;
1981
hermetic ( ) ;
1982
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1983
let root = dir.path ( ) ;
1984
let mut a = Repo:: init( & root.join ( " a" ) )
1985
.unwrap ( )
1986
.with_home ( root.join ( " home-a" ) ) ;
1987
let ( old, _ ) = a
1988
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1989
.unwrap ( ) ;
1990
let ( _ , old_sk) = a.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
1991
let old_id = a.identity ( & old) .unwrap ( ) ;
1992
let hub_sk = SecretKey:: generate( ) ;
1993
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1994
let now = Hlc:: now( ) .wall_ms;
1995
let grant = | from : u64 | {
1996
SignedGrant:: sign(
1997
Grant {
1998
grantor: old,
1999
subject: " agent:claude@ada" .into ( ) ,
2000
issuer: hub.hash ( ) ,
2001
scope: Scope:: CHANGES | Scope:: SEAL ,
2002
not_before: from,
2003
expires: now + 86_400_000 ,
2004
} ,
2005
& old_id,
2006
& old_sk,
2007
)
2008
} ;
2009
let before = grant ( now - 60_000 ) ;
2010
let after = grant ( now + 60_000 ) ;
2011
let rot = a.renew_identity ( None ) .unwrap ( ) ;
2012
assert_ne! ( rot.new, old) ;
2013
let agent = | name : & str , g : SignedGrant| {
2014
let mut r = Repo:: init( & root.join ( name) )
2015
.unwrap ( )
2016
.with_home ( root.join ( format! ( " home-{name} " ) ) ) ;
2017
let ( key, id) = r
2018
.create_identity ( name, " [email protected] " , IdentityScope:: User)
2019
.unwrap ( ) ;
2020
for i in [ & old_id, & rot.identity, & hub] {
2021
r.store ( ) .put_identity ( i) .unwrap ( ) ;
2022
}
2023
r.set_user_config ( " author.key" , & rot.new.to_hex ( ) ) .unwrap ( ) ;
2024
r.set_user_config ( " author.signer" , & key.to_hex ( ) ) .unwrap ( ) ;
2025
let cert = SignedCertificate:: sign(
2026
Certificate {
2027
key: id,
2028
issuer: hub.hash ( ) ,
2029
subject: " agent:claude@ada" .into ( ) ,
2030
claims: vec! [ ] ,
2031
scope: Scope:: CHANGES | Scope:: REFS | Scope:: SEAL ,
2032
not_before: now - 60_000 ,
2033
expires: now + 600_000 ,
2034
grant: Some ( g) ,
2035
} ,
2036
& hub,
2037
& hub_sk,
2038
) ;
2039
r.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
2040
write ( & root.join ( name) , " work.txt" , b " agent work\n " ) ;
2041
let h = r.commit ( " work" , & [ ] ) .unwrap ( ) ;
2042
assert_eq! ( r.store ( ) .get_change_meta ( & h) .unwrap ( ) .author, rot.new) ;
2043
r.seal ( & [ h] ) .unwrap ( ) ;
2044
( r, h)
2045
} ;
2046
let ( r, h) = agent ( " kept" , before) ;
2047
assert! (
2048
matches! (
2049
r.authenticate_change ( & h) .unwrap ( ) ,
2050
SignatureStatus:: Valid { .. }
2051
) ,
2052
" {:?}" ,
2053
r.authenticate_change ( & h) .unwrap ( )
2054
) ;
2055
let ( r, h) = agent ( " late" , after) ;
2056
assert! (
2057
matches! ( r.authenticate_change ( & h) .unwrap ( ) , SignatureStatus:: Invalid( e) if e.contains ( " neither" ) ) ,
2058
" a grant the old key made after the rotation: {:?}" ,
2059
r.authenticate_change ( & h) .unwrap ( )
2060
) ;
2061
}
2062
2063
# [ test ]
a2387d54 Andy McCall 20 days ago ↶
2064
fn a_certified_key_acts_for_its_author_under_a_grant ( ) {
2065
use crate :: cert:: { Certificate, Grant, SignedCertificate, SignedGrant} ;
2066
use crate :: identity:: { Identity, Scope} ;
2067
hermetic ( ) ;
2068
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2069
let root = dir.path ( ) ;
2070
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
2071
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
2072
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
2073
.unwrap ( )
2074
.with_home ( root.join ( " home-a" ) ) ;
2075
let ( ada, _ ) = a
2076
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
2077
.unwrap ( ) ;
2078
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
2079
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
2080
a.policy_init ( ) .unwrap ( ) ;
2081
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
2082
let hub_sk = SecretKey:: generate( ) ;
2083
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
2084
a.policy_grant_subject (
2085
" agent:claude@ada" ,
2086
hub.clone ( ) ,
2087
Scope:: CHANGES | Scope:: REFS ,
2088
None ,
2089
)
2090
.unwrap ( ) ;
2091
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
2092
let ( _ , ada_sk) = a.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
2093
let ada_id = a.identity ( & ada) .unwrap ( ) ;
2094
2095
let now = Hlc:: now( ) .wall_ms;
2096
let grant = | by : & Identity, sk : & SecretKey, from : u64 , to : u64 | {
2097
SignedGrant:: sign(
2098
Grant {
2099
grantor: by.hash ( ) ,
2100
subject: " agent:claude@ada" .into ( ) ,
2101
issuer: hub.hash ( ) ,
2102
scope: Scope:: CHANGES | Scope:: SEAL ,
2103
not_before: from,
2104
expires: to,
2105
} ,
2106
by,
2107
sk,
2108
)
2109
} ;
2110
2111
2112
let agent = | name : & str , g : Option< SignedGrant> | {
2113
let mut r = Repo:: clone( & url, & root.join ( name) )
2114
.unwrap ( )
2115
.with_home ( root.join ( format! ( " home-{name} " ) ) ) ;
2116
let ( key, id) = r
2117
.create_identity ( name, " [email protected] " , IdentityScope:: User)
2118
.unwrap ( ) ;
2119
r.set_user_config ( " author.key" , & ada.to_hex ( ) ) .unwrap ( ) ;
2120
r.set_user_config ( " author.signer" , & key.to_hex ( ) ) .unwrap ( ) ;
2121
let cert = SignedCertificate:: sign(
2122
Certificate {
2123
key: id,
2124
issuer: hub.hash ( ) ,
2125
subject: " agent:claude@ada" .into ( ) ,
2126
claims: vec! [ ] ,
2127
scope: Scope:: CHANGES | Scope:: REFS | Scope:: SEAL ,
2128
not_before: now - 60_000 ,
2129
expires: now + 600_000 ,
2130
grant: g,
2131
} ,
2132
& hub,
2133
& hub_sk,
2134
) ;
2135
r.store ( ) .put_identity ( & hub) .unwrap ( ) ;
2136
r.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
2137
write ( & root.join ( name) , & format! ( " {name} .txt" ) , b " agent work\n " ) ;
2138
let h = r.commit ( " work" , & [ ] ) .unwrap ( ) ;
2139
assert_eq! ( r.store ( ) .get_change_meta ( & h) .unwrap ( ) .author, ada) ;
2140
( r, key, h)
2141
} ;
2142
2143
let ( claude, key, h) = agent (
2144
" claude" ,
2145
Some ( grant ( & ada_id, & ada_sk, now - 60_000 , now + 86_400_000 ) ) ,
2146
) ;
2147
let r = claude.push ( " origin" , false ) .unwrap ( ) ;
2148
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
2149
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
68dcf21f Andy McCall 20 days ago ↶
2150
assert! (
2151
matches! (
2152
host.authenticate_change ( & h) .unwrap ( ) ,
2153
SignatureStatus:: Valid { key: k, sealed: true , name, .. } if k = = key & & name = = " agent:claude@ada"
2154
) ,
2155
" named by its certificate's subject"
2156
) ;
a2387d54 Andy McCall 20 days ago ↶
2157
a.pull ( " origin" ) .unwrap ( ) ;
2158
assert! ( matches! (
2159
a.authenticate_change ( & h) .unwrap ( ) ,
2160
SignatureStatus:: Valid { key: k, .. } if k = = key
2161
) ) ;
2162
2163
let ( none, _ , _ ) = agent ( " none" , None ) ;
2164
let r = none.push ( " origin" , false ) .unwrap ( ) ;
2165
assert! ( r.iter ( ) .all ( | x | ! x.ok) , " no grant: {r:?}" ) ;
2166
let mallory_sk = SecretKey:: generate( ) ;
2167
let mallory = mallory_sk.identity ( " Mallory" , " [email protected] " , 1 ) ;
2168
let ( stranger, _ , _ ) = agent (
2169
" stranger" ,
2170
Some ( grant ( & mallory, & mallory_sk, now - 60_000 , now + 600_000 ) ) ,
2171
) ;
2172
let r = stranger.push ( " origin" , false ) .unwrap ( ) ;
2173
assert! ( r.iter ( ) .all ( | x | ! x.ok) , " a stranger's grant: {r:?}" ) ;
2174
let ( ended, _ , eh) = agent (
2175
" ended" ,
2176
Some ( grant ( & ada_id, & ada_sk, now - 7_200_000 , now - 3_600_000 ) ) ,
2177
) ;
2178
let r = ended.push ( " origin" , false ) .unwrap ( ) ;
2179
assert! ( r.iter ( ) .all ( | x | ! x.ok) , " an ended grant: {r:?}" ) ;
2180
assert! ( matches! (
2181
ended.authenticate_change ( & eh) .unwrap ( ) ,
2182
SignatureStatus:: Invalid( e) if e.contains ( " grant did not cover" )
2183
) ) ;
2184
}
2185
2186
# [ test ]
83e0677b Andy McCall 1 month ago ↶
2187
fn trust_policy_governs_pushes_and_fetches ( ) {
2188
hermetic ( ) ;
2189
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2190
let root = dir.path ( ) ;
2191
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
2192
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
2193
2194
75167672 Andy McCall 1 month ago
2195
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
2196
.unwrap ( )
2197
.with_home ( root.join ( " home-a" ) ) ;
2198
let ( key_a, _ ) = a
2199
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
2200
.unwrap ( ) ;
2201
let mut b = Repo:: clone( & url, & root.join ( " b" ) )
2202
.unwrap ( )
2203
.with_home ( root.join ( " home-b" ) ) ;
83e0677b Andy McCall 1 month ago ↶
2204
write ( & root.join ( " b" ) , " b.txt" , b " b\n " ) ;
2205
b.commit ( " unsigned before policy" , & [ ] ) .unwrap ( ) ;
2206
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
2207
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2208
2209
2210
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
2211
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
2212
let sp = a.policy_init ( ) .unwrap ( ) ;
2213
assert_eq! ( sp.policy.serial, 1 ) ;
2214
a.pull ( " origin" ) .unwrap ( ) ;
2215
let results = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
2216
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2217
assert! ( results.iter ( ) .all ( | r | r.ok ) , " {results:?}" ) ;
2218
assert_eq! ( origin.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 1 ) ;
2219
2220
2221
b.pull ( " origin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2222
assert! (
2223
b.current_policy ( ) .unwrap ( ) .is_some ( ) ,
2224
" the policy arrived with the fetch"
2225
) ;
83e0677b Andy McCall 1 month ago ↶
2226
assert! ( b.push ( " origin" , false ) .is_err ( ) ) ;
2227
75167672 Andy McCall 1 month ago
2228
let ( key_b, id_b) = b
2229
.create_identity ( " Bob" , " [email protected] " , IdentityScope:: User)
2230
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2231
write ( & root.join ( " b" ) , " b.txt" , b " b2\n " ) ;
2232
let h_b2 = b.commit ( " b2 as Bob" , & [ ] ) .unwrap ( ) ;
2233
assert_eq! ( b.store ( ) .get_change ( & h_b2) .unwrap ( ) .author, key_b) ;
2234
let r = b.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
2235
origin.refresh ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2236
assert! (
2237
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " may not update" ) ,
2238
" {:?}" ,
2239
r
2240
) ;
83e0677b Andy McCall 1 month ago ↶
2241
2242
2243
let sp = a.policy_grant ( id_b.clone ( ) , None ) .unwrap ( ) ;
2244
assert_eq! ( sp.policy.serial, 2 ) ;
2245
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
2246
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2247
assert! ( r.iter ( ) .any ( | x | x.name = = " policy" & & x.ok ) , " {r:?}" ) ;
2248
75167672 Andy McCall 1 month ago
2249
assert! ( b
2250
.push ( " origin" , false )
2251
.unwrap_or_else ( | e | vec! [ RefResult {
2252
name: " main" .into ( ) ,
2253
ok: false ,
2254
reason: e.to_string ( )
2255
} ] )
2256
.iter ( )
2257
.all ( | x | ! x.ok) ) ;
83e0677b Andy McCall 1 month ago ↶
2258
b.pull ( " origin" ) .unwrap ( ) ;
2259
assert_eq! ( b.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 2 ) ;
2260
let r = b.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
2261
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2262
assert! ( r[ 0 ] .ok, " {:?}" , r) ;
2263
2264
let heads = origin.bookmark_heads ( ) .unwrap ( ) ;
2265
for h in & heads {
2266
let c = origin.store ( ) .get_change ( h) .unwrap ( ) ;
2267
if c.author = = key_b {
75167672 Andy McCall 1 month ago
2268
origin
2269
.change_allowed (
2270
h,
2271
& origin.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy,
2272
" main" ,
2273
)
2274
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2275
}
2276
}
2277
2278
2279
let mut forged = b.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.clone ( ) ;
2280
forged.serial = 3 ;
2281
forged.roots = vec! [ key_b] ;
2282
let fh = forged.hash ( ) ;
2283
let ( _ , sk_b) = b.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2284
let forged = SignedPolicy {
2285
policy: forged,
2286
sigs: vec! [ sk_b.sign ( Domain:: Policy, & key_b, & fh) ] ,
2287
} ;
2288
let r = origin
2289
.receive_push ( Bundle:: default( ) , vec! [ ] , Some ( forged) , vec! [ ] )
2290
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2291
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " root" ) , " {:?}" , r) ;
2292
let stale = a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1 ;
2293
let mut old = stale.clone ( ) ;
2294
old.policy.serial = 1 ;
75167672 Andy McCall 1 month ago
2295
let r = origin
2296
.receive_push ( Bundle:: default( ) , vec! [ ] , Some ( old) , vec! [ ] )
2297
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2298
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " serial" ) , " {:?}" , r) ;
2299
2300
2301
a.pull ( " origin" ) .unwrap ( ) ;
2302
a.policy_revoke ( & key_b) .unwrap ( ) ;
2303
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
2304
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2305
assert! ( r.iter ( ) .any ( | x | x.name = = " policy" & & x.ok ) , " {r:?}" ) ;
2306
b.pull ( " origin" ) .unwrap ( ) ;
2307
write ( & root.join ( " b" ) , " b.txt" , b " b3\n " ) ;
2308
b.commit ( " b3" , & [ ] ) .unwrap ( ) ;
2309
let r = b.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
2310
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2311
assert! ( ! r[ 0 ] .ok, " {:?}" , r) ;
2312
2313
75167672 Andy McCall 1 month ago
2314
let c = Repo:: clone( & url, & root.join ( " c" ) )
2315
.unwrap ( )
2316
.with_home ( root.join ( " home-c" ) ) ;
83e0677b Andy McCall 1 month ago ↶
2317
assert_eq! ( c.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 3 ) ;
75167672 Andy McCall 1 month ago
2318
assert! ( c
2319
.current_policy ( )
2320
.unwrap ( )
2321
.unwrap ( )
2322
.1
2323
.policy
2324
.roots
2325
.contains ( & key_a) ) ;
83e0677b Andy McCall 1 month ago ↶
2326
assert! ( ! c.snapshot ( " status" ) .unwrap ( ) .changed) ;
2327
}
d02e545d Andy McCall 1 month ago ↶
2328
9ae4d15a Andy McCall 1 month ago ↶
2329
d02e545d Andy McCall 1 month ago ↶
2330
# [ test ]
9ae4d15a Andy McCall 1 month ago ↶
2331
fn gc_reports_its_phases ( ) {
2332
hermetic ( ) ;
2333
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2334
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
2335
for i in 0 .. 3 {
2336
write (
2337
dir.path ( ) ,
2338
& format! ( " f{i} .txt" ) ,
2339
format! ( " {i} \n " ) .as_bytes ( ) ,
2340
) ;
2341
repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ;
2342
}
2343
let mut phases = Vec :: new( ) ;
2344
repo.gc_with ( true , & mut | p| phases.push ( p.to_string ( ) ) )
2345
.unwrap ( ) ;
2346
assert_eq! ( phases[ 0 ] , " finding what is reachable" , " {phases:?}" ) ;
2347
assert! (
2348
phases.iter ( ) .any ( | p | p.starts_with ( " pruning" ) ) ,
2349
" {phases:?}"
2350
) ;
2351
assert! (
2352
phases.iter ( ) .any ( | p | p.starts_with ( " packing 0 of " ) ) ,
2353
" {phases:?}"
2354
) ;
2355
assert! (
2356
phases
2357
.iter ( )
2358
.any ( | p | p.starts_with ( " writing the index of " ) ) ,
2359
" {phases:?}"
2360
) ;
2361
}
2362
2363
# [ test ]
d02e545d Andy McCall 1 month ago ↶
2364
fn gc_packs_objects_and_history_still_works ( ) {
2365
hermetic ( ) ;
2366
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2367
let root = dir.path ( ) ;
2368
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
2369
repo.create_identity ( " Ada" , " a@b" , IdentityScope:: User)
2370
.unwrap ( ) ;
d02e545d Andy McCall 1 month ago ↶
2371
repo.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
2372
write ( root, " a.txt" , b " one\n " ) ;
2373
let h1 = repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
2374
write ( root, " a.txt" , b " two\n " ) ;
2375
write ( root, " big.bin" , & vec! [ 0 u8 ; 100_000 ] ) ;
2376
let h2 = repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
ba160f60 Andy McCall 1 month ago ↶
2377
2378
for i in 0 .. 5 {
2379
write ( root, " a.txt" , format! ( " two\n {i} \n " ) .as_bytes ( ) ) ;
2380
repo.snapshot ( " status" ) .unwrap ( ) ;
2381
}
2382
write ( root, " a.txt" , b " two\n " ) ;
2383
repo.snapshot ( " status" ) .unwrap ( ) ;
d02e545d Andy McCall 1 month ago ↶
2384
let before = repo.store ( ) .list ( ) .unwrap ( ) ;
2385
assert! ( repo.store ( ) .loose_count ( ) .unwrap ( ) > 0 ) ;
ba160f60 Andy McCall 1 month ago ↶
2386
2387
2388
let r = repo.gc ( true ) .unwrap ( ) ;
2389
assert_eq! ( r.packed.objects, before.len ( ) ) ;
2390
assert_eq! ( r.pruned.objects, 0 ) ;
d02e545d Andy McCall 1 month ago ↶
2391
assert_eq! ( repo.store ( ) .loose_count ( ) .unwrap ( ) , 0 ) ;
2392
assert_eq! ( repo.store ( ) .list ( ) .unwrap ( ) , before) ;
ba160f60 Andy McCall 1 month ago ↶
2393
2394
2395
2396
repo.config.set ( " core.op-retention" , " 0" ) .unwrap ( ) ;
2397
repo.save_config ( ) .unwrap ( ) ;
21b2aec2 Andy McCall 1 month ago ↶
2398
let mut repo = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
ba160f60 Andy McCall 1 month ago ↶
2399
let r = repo.gc ( true ) .unwrap ( ) ;
2400
assert! ( r.pruned.objects > = 5 , " {r:?}" ) ;
2401
let after = repo.store ( ) .list ( ) .unwrap ( ) ;
2402
assert! ( after.len ( ) < before.len ( ) ) ;
2403
assert! ( after.contains ( & h1) & & after.contains ( & h2) ) ;
2404
for ( _ , op) in repo.op_log ( ) .unwrap ( ) {
2405
assert! ( ! op.command.is_empty ( ) , " operation objects are all kept" ) ;
2406
}
2407
d02e545d Andy McCall 1 month ago ↶
2408
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
75167672 Andy McCall 1 month ago
2409
assert! ( matches! (
2410
repo.verify_change ( & h2) .unwrap ( ) ,
2411
SignatureStatus:: Valid { .. }
2412
) ) ;
d02e545d Andy McCall 1 month ago ↶
2413
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 3 ) ;
2414
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
2415
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n " ) ;
2416
repo.checkout ( & [ h2] , false , " checkout" ) .unwrap ( ) ;
2417
assert_eq! ( fs:: read( root.join ( " big.bin" ) ) .unwrap ( ) .len ( ) , 100_000 ) ;
2418
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
ba160f60 Andy McCall 1 month ago ↶
2419
d02e545d Andy McCall 1 month ago ↶
2420
write ( root, " a.txt" , b " three\n " ) ;
2421
repo.commit ( " three" , & [ ] ) .unwrap ( ) ;
ba160f60 Andy McCall 1 month ago ↶
2422
let r = repo.gc ( false ) .unwrap ( ) ;
2423
assert_eq! ( r.pruned.objects, 0 ) ;
2424
assert! ( r.pruned.deferred > 0 ) ;
2425
let r = repo.gc ( true ) .unwrap ( ) ;
2426
assert! ( r.pruned.objects > 0 ) ;
2427
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 ) ;
21b2aec2 Andy McCall 1 month ago ↶
2428
2429
2430
repo.config.set ( " core.op-retention" , " 90" ) .unwrap ( ) ;
2431
repo.save_config ( ) .unwrap ( ) ;
2432
let repo = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2433
let r = repo.gc ( false ) .unwrap ( ) ;
2434
assert_eq! ( r.pruned.objects, 0 ) ;
2435
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 ) ;
d02e545d Andy McCall 1 month ago ↶
2436
}
614541c3 Andy McCall 1 month ago ↶
2437
2438
# [ test ]
2439
fn describe_amend_abandon_heads ( ) {
2440
hermetic ( ) ;
2441
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2442
let root = dir.path ( ) ;
2443
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
2444
repo.create_identity ( " Ada" , " a@b" , IdentityScope:: User)
2445
.unwrap ( ) ;
614541c3 Andy McCall 1 month ago ↶
2446
repo.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
2447
write ( root, " a.txt" , b " one\n " ) ;
2448
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
2449
write ( root, " a.txt" , b " one\n two\n " ) ;
2450
let h2 = repo.commit ( " secnod" , & [ ] ) .unwrap ( ) ;
2451
let id2 = repo.store ( ) .get_change ( & h2) .unwrap ( ) .change_id;
2452
2453
2454
let h2b = repo.describe ( & h2, " second" ) .unwrap ( ) ;
2455
assert_ne! ( h2b, h2) ;
2456
let c = repo.store ( ) .get_change ( & h2b) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2457
assert_eq! (
2458
( c.change_id, c.message.as_str ( ) , c.deps.clone ( ) ) ,
2459
( id2, " second" , vec! [ h1] )
2460
) ;
614541c3 Andy McCall 1 month ago ↶
2461
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2b] ) ;
75167672 Andy McCall 1 month ago
2462
assert! ( matches! (
2463
repo.verify_change ( & h2b) .unwrap ( ) ,
2464
SignatureStatus:: Valid { .. }
2465
) ) ;
614541c3 Andy McCall 1 month ago ↶
2466
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
2467
assert! ( s.statuses.is_empty ( ) , " {:?}" , s.statuses) ;
75167672 Andy McCall 1 month ago
2468
assert_eq! (
2469
repo.resolve_change ( & id2.short ( 8 ) ) .unwrap ( ) ,
2470
h2b,
2471
" the old object is no longer reachable from the log"
2472
) ;
c07e0e7d Andy McCall 1 month ago ↶
2473
2474
let h1b = repo.describe ( & h1, " first, described" ) .unwrap ( ) ;
2475
assert_ne! ( h1b, h1) ;
2476
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
2477
assert_eq! ( heads.len ( ) , 1 ) ;
2478
let top = repo.store ( ) .get_change ( & heads[ 0 ] ) .unwrap ( ) ;
2479
assert_eq! ( ( top.change_id, top.deps.clone ( ) ) , ( id2, vec! [ h1b] ) ) ;
75167672 Andy McCall 1 month ago
2480
assert! (
2481
matches! (
2482
repo.verify_change ( & heads[ 0 ] ) .unwrap ( ) ,
2483
SignatureStatus:: Valid { .. }
2484
) ,
2485
" rewritten own change is re-signed"
2486
) ;
c07e0e7d Andy McCall 1 month ago ↶
2487
let ( _ , wc) = repo.working_copy ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2488
assert_eq! (
2489
wc.deps,
2490
vec! [ heads[ 0 ] ] ,
2491
" the working copy follows the rewrite"
2492
) ;
c07e0e7d Andy McCall 1 month ago ↶
2493
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
2494
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
2495
assert! (
2496
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n " )
2497
) ;
614541c3 Andy McCall 1 month ago ↶
2498
2499
2500
write ( root, " a.txt" , b " one\n two\n three\n " ) ;
2501
write ( root, " b.txt" , b " b\n " ) ;
2502
let h2c = repo.amend ( None ) .unwrap ( ) ;
2503
let c = repo.store ( ) .get_change ( & h2c) .unwrap ( ) ;
2504
assert_eq! ( c.change_id, id2) ;
2505
assert_eq! ( c.message, " second" ) ;
2506
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2c] ) ;
75167672 Andy McCall 1 month ago
2507
assert_eq! (
2508
repo.log ( ) .unwrap ( ) .len ( ) ,
2509
3 ,
2510
" working copy, amended head, first"
2511
) ;
614541c3 Andy McCall 1 month ago ↶
2512
let snap = repo.load_state ( & [ h2c] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
2513
assert! (
2514
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n three\n " )
2515
) ;
614541c3 Andy McCall 1 month ago ↶
2516
assert! ( snap.files.contains_key ( " b.txt" ) ) ;
2517
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
75167672 Andy McCall 1 month ago
2518
assert! (
2519
repo.amend ( None ) .is_err ( ) ,
2520
" clean working copy and no message"
2521
) ;
614541c3 Andy McCall 1 month ago ↶
2522
let h2d = repo.amend ( Some ( " second, amended" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2523
assert_eq! (
2524
repo.store ( ) .get_change ( & h2d) .unwrap ( ) .message,
2525
" second, amended"
2526
) ;
614541c3 Andy McCall 1 month ago ↶
2527
2528
2529
repo.abandon ( & h2d) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2530
assert_eq! (
2531
repo.bookmark_heads ( ) .unwrap ( ) ,
2532
vec! [ h1b] ,
2533
" the described first change is the head again"
2534
) ;
614541c3 Andy McCall 1 month ago ↶
2535
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2536
let paths: Vec< _ > = s
2537
.statuses
2538
.iter ( )
2539
.map ( | p | ( p.path.as_str ( ) , p.status) )
2540
.collect ( ) ;
2541
assert_eq! (
2542
paths,
2543
vec! [
2544
( " a.txt" , ChangeStatus:: Modified) ,
2545
( " b.txt" , ChangeStatus:: Added)
2546
]
2547
) ;
614541c3 Andy McCall 1 month ago ↶
2548
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n two\n three\n " ) ;
2549
let h3 = repo.commit ( " recommitted" , & [ ] ) .unwrap ( ) ;
2550
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h3] ) ;
2551
2552
repo.undo ( ) .unwrap ( ) ;
2553
repo.undo ( ) .unwrap ( ) ;
2554
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2d] ) ;
c07e0e7d Andy McCall 1 month ago ↶
2555
6706bdcf Andy McCall 1 month ago ↶
2556
2557
2558
c07e0e7d Andy McCall 1 month ago ↶
2559
let base = repo.bookmark_heads ( ) .unwrap ( ) [ 0 ] ;
2560
let base_change = repo.store ( ) .get_change ( & base) .unwrap ( ) ;
2561
let h1_now = base_change.deps[ 0 ] ;
6706bdcf Andy McCall 1 month ago ↶
2562
let notes = repo.abandon ( & h1_now) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2563
assert! (
2564
! notes.is_empty ( ) ,
2565
" the dependent's edit of a.txt was reified"
2566
) ;
6706bdcf Andy McCall 1 month ago ↶
2567
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
2568
let after = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
2569
assert! (
2570
! after.files.contains_key ( " a.txt" ) & & after.files.contains_key ( " b.txt" ) ,
2571
" {:?}" ,
2572
after.files.keys ( )
2573
) ;
6706bdcf Andy McCall 1 month ago ↶
2574
repo.undo ( ) .unwrap ( ) ;
2575
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ base] ) ;
c07e0e7d Andy McCall 1 month ago ↶
2576
write ( root, " c.txt" , b " c\n " ) ;
2577
let hc = repo.commit ( " c" , & [ ] ) .unwrap ( ) ;
2578
write ( root, " d.txt" , b " d\n " ) ;
2579
let hd = repo.commit ( " d" , & [ hc] ) .unwrap ( ) ;
2580
assert_eq! ( repo.store ( ) .get_change ( & hd) .unwrap ( ) .deps, vec! [ hc] ) ;
2581
repo.abandon ( & hc) .unwrap ( ) ;
2582
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
2583
assert! ( ! heads.contains ( & hc) ) ;
75167672 Andy McCall 1 month ago
2584
let d_now = heads
2585
.iter ( )
2586
.find ( | h | repo.store ( ) .get_change ( h) .unwrap ( ) .message = = " d" )
2587
.copied ( )
2588
.unwrap ( ) ;
2589
assert! (
2590
repo.store ( ) .get_change ( & d_now) .unwrap ( ) .deps.is_empty ( ) ,
2591
" d inherited c's (empty) dependencies"
2592
) ;
c07e0e7d Andy McCall 1 month ago ↶
2593
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2594
assert_eq! (
2595
s.statuses
2596
.iter ( )
2597
.map ( | p | p.path.as_str ( ) )
2598
.collect:: < Vec< _ > > ( ) ,
2599
vec! [ " c.txt" ] ,
2600
" c's content is now uncommitted work"
2601
) ;
614541c3 Andy McCall 1 month ago ↶
2602
}
79ab8528 Andy McCall 1 month ago ↶
2603
2604
# [ test ]
2605
fn git_export_mirrors_history ( ) {
2606
hermetic ( ) ;
75167672 Andy McCall 1 month ago
2607
if std:: process:: Command:: new( " git" )
2608
.arg ( " --version" )
2609
.output ( )
2610
.is_err ( )
2611
{
79ab8528 Andy McCall 1 month ago ↶
2612
eprintln! ( " git not installed; skipping" ) ;
2613
return ;
2614
}
2615
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2616
let root = dir.path ( ) ;
75167672 Andy McCall 1 month ago
2617
let mut repo = Repo:: init( & root.join ( " r" ) )
2618
.unwrap ( )
2619
.with_home ( root.join ( " home" ) ) ;
2620
repo.create_identity ( " Ada Lovelace" , " [email protected] " , IdentityScope:: User)
2621
.unwrap ( ) ;
79ab8528 Andy McCall 1 month ago ↶
2622
let r = root.join ( " r" ) ;
2623
write ( & r, " README.md" , b " hello\n " ) ;
2624
write ( & r, " src/main.rs" , b " fn main() {}\n " ) ;
2625
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
2626
write ( & r, " src/main.rs" , b " fn main() { run(); }\n " ) ;
2627
write ( & r, " logo.png" , b " \x89 PNG\0 \0 " ) ;
2628
let h2 = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
2629
2630
let git_dir = root.join ( " mirror.git" ) ;
2631
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
2632
assert_eq! ( ( rep.exported, rep.skipped, rep.heads) , ( 2 , 0 , 1 ) ) ;
2633
let git = | args : & [& str ]| -> String {
75167672 Andy McCall 1 month ago
2634
let out = std:: process:: Command:: new( " git" )
2635
.arg ( " -C" )
2636
.arg ( & git_dir)
2637
.args ( args)
2638
.output ( )
2639
.unwrap ( ) ;
2640
assert! (
2641
out.status.success ( ) ,
2642
" {}" ,
2643
String :: from_utf8_lossy( & out.stderr)
2644
) ;
79ab8528 Andy McCall 1 month ago ↶
2645
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
2646
} ;
2647
assert_eq! ( git ( & [ " log" , " --format=%s" , " main" ] ) , " second\n first" ) ;
2648
assert_eq! ( git ( & [ " show" , " main:src/main.rs" ] ) , " fn main() { run(); }" ) ;
2649
assert_eq! ( git ( & [ " show" , " main:README.md" ] ) , " hello" ) ;
2650
let body = git ( & [ " log" , " -1" , " --format=%B" , " main" ] ) ;
75167672 Andy McCall 1 month ago
2651
assert! (
2652
body.contains ( & format! ( " Oxy-Hash: {} " , h2.to_hex ( ) ) ) ,
2653
" {body}"
2654
) ;
79ab8528 Andy McCall 1 month ago ↶
2655
assert! ( body.contains ( " Change-Id: " ) , " {body}" ) ;
75167672 Andy McCall 1 month ago
2656
assert_eq! (
2657
git ( & [ " log" , " -1" , " --format=%an <%ae>" , " main" ] ) ,
2658
" Ada Lovelace <[email protected] >"
2659
) ;
2660
assert_eq! (
2661
git ( & [ " ls-tree" , " --name-only" , " main" ] ) ,
2662
" README.md\n logo.png\n src"
2663
) ;
79ab8528 Andy McCall 1 month ago ↶
2664
2665
2666
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
2667
assert_eq! ( ( rep.exported, rep.skipped) , ( 0 , 2 ) ) ;
2668
2669
2670
2671
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
2672
write ( & r, " other.txt" , b " o\n " ) ;
2673
let h3 = repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
2674
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
2675
assert_eq! ( ( rep.exported, rep.heads) , ( 1 , 2 ) ) ;
2676
assert_eq! ( git ( & [ " log" , " --format=%s" , " main" ] ) , " third\n second\n first" ) ;
2677
let parents = git ( & [ " rev-list" , " --parents" , " -1" , " main" ] ) ;
2678
assert_eq! ( parents.split ( ' ' ) .count ( ) , 2 , " one parent: {parents}" ) ;
2679
assert_eq! ( git ( & [ " show" , " main:other.txt" ] ) , " o" ) ;
2680
assert_eq! ( git ( & [ " show" , " main:src/main.rs" ] ) , " fn main() { run(); }" ) ;
75167672 Andy McCall 1 month ago
2681
assert_eq! (
2682
git ( & [ " ls-tree" , " --name-only" , " main" ] ) ,
2683
" README.md\n logo.png\n other.txt\n src"
2684
) ;
79ab8528 Andy McCall 1 month ago ↶
2685
let body = git ( & [ " log" , " -1" , " --format=%B" , " main" ] ) ;
75167672 Andy McCall 1 month ago
2686
assert! (
2687
body.contains ( & format! ( " Oxy-Hash: {} " , h3.to_hex ( ) ) ) & & body.contains ( " Oxy-State: " ) ,
2688
" {body}"
2689
) ;
79ab8528 Andy McCall 1 month ago ↶
2690
2691
repo.describe ( & h3, " third, described" ) .unwrap ( ) ;
2692
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
2693
assert_eq! ( rep.exported, 1 ) ;
75167672 Andy McCall 1 month ago
2694
assert_eq! (
2695
git ( & [ " log" , " -1" , " --format=%s" , " main" ] ) ,
2696
" third, described"
2697
) ;
79ab8528 Andy McCall 1 month ago ↶
2698
assert_eq! ( git ( & [ " log" , " --format=%s" , " main" ] ) .lines ( ) .count ( ) , 4 ) ;
2699
let _ = h2;
2700
}
c3069537 Andy McCall 1 month ago ↶
2701
2702
# [ test ]
2703
fn git_import_reproduces_trees ( ) {
c3d2ef61 Andy McCall 1 month ago ↶
2704
git_import_reproduces_trees_with ( None ) ;
2705
}
2706
2707
2708
2709
# [ test ]
2710
fn git_import_reproduces_trees_in_batches ( ) {
2711
git_import_reproduces_trees_with ( Some ( 1 ) ) ;
2712
}
2713
2714
fn git_import_reproduces_trees_with ( batch : Option< usize > ) {
2715
crate :: repo:: git:: set_merge_batch_for_tests( batch) ;
c3069537 Andy McCall 1 month ago ↶
2716
hermetic ( ) ;
75167672 Andy McCall 1 month ago
2717
if std:: process:: Command:: new( " git" )
2718
.arg ( " --version" )
2719
.output ( )
2720
.is_err ( )
2721
{
c3069537 Andy McCall 1 month ago ↶
2722
eprintln! ( " git not installed; skipping" ) ;
2723
return ;
2724
}
2725
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2726
let root = dir.path ( ) ;
2727
let g = root.join ( " src.git" ) ;
2728
fs:: create_dir_all( & g) .unwrap ( ) ;
2729
let git = | args : & [& str ]| -> String {
2730
let out = std:: process:: Command:: new( " git" )
2731
.arg ( " -C" )
2732
.arg ( & g)
75167672 Andy McCall 1 month ago
2733
.args ( [
2734
" -c" ,
2735
" user.name=Grace" ,
2736
" -c" ,
2737
" [email protected] " ,
2738
" -c" ,
2739
" commit.gpgsign=false" ,
2740
] )
c3069537 Andy McCall 1 month ago ↶
2741
.args ( args)
2742
.output ( )
2743
.unwrap ( ) ;
75167672 Andy McCall 1 month ago
2744
assert! (
2745
out.status.success ( ) ,
2746
" git {args:?}: {}" ,
2747
String :: from_utf8_lossy( & out.stderr)
2748
) ;
c3069537 Andy McCall 1 month ago ↶
2749
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
2750
} ;
2751
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
2752
write ( & g, " README.md" , b " # Hi\n " ) ;
2753
write ( & g, " src/lib.rs" , b " pub fn a() {}\n " ) ;
2754
git ( & [ " add" , " ." ] ) ;
2755
git ( & [ " commit" , " -q" , " -m" , " initial" ] ) ;
2756
write ( & g, " src/lib.rs" , b " pub fn a() {}\n pub fn b() {}\n " ) ;
2757
write ( & g, " run.sh" , b " #!/bin/sh\n " ) ;
2758
# [ cfg ( unix ) ]
2759
{
2760
use std:: os:: unix:: fs:: PermissionsExt;
2761
fs:: set_permissions( g.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
2762
}
2763
write ( & g, " logo.png" , b " \x89 PNG\0 \0 \x01 " ) ;
2764
git ( & [ " add" , " ." ] ) ;
75167672 Andy McCall 1 month ago
2765
git ( & [
2766
" commit" ,
2767
" -q" ,
2768
" -m" ,
2769
" second\n \n Change-Id: 0123456789abcdef0123456789abcdef" ,
2770
] ) ;
c3069537 Andy McCall 1 month ago ↶
2771
2772
git ( & [ " checkout" , " -q" , " -b" , " topic" , " HEAD~1" ] ) ;
2773
write ( & g, " README.md" , b " # Hi\n \n More.\n " ) ;
2774
git ( & [ " commit" , " -q" , " -am" , " readme" ] ) ;
2775
git ( & [ " checkout" , " -q" , " main" ] ) ;
2776
git ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge topic" , " topic" ] ) ;
2777
2778
2779
let initial = git ( & [ " rev-list" , " --max-parents=0" , " HEAD" ] ) ;
2780
git ( & [ " checkout" , " -q" , " -b" , " clash" , & initial] ) ;
2781
write ( & g, " src/lib.rs" , b " pub fn a() {}\n pub fn c() {}\n " ) ;
2782
git ( & [ " commit" , " -q" , " -am" , " clash" ] ) ;
2783
git ( & [ " checkout" , " -q" , " main" ] ) ;
75167672 Andy McCall 1 month ago
2784
let _ = std:: process:: Command:: new( " git" )
2785
.arg ( " -C" )
2786
.arg ( & g)
2787
.args ( [ " merge" , " -q" , " clash" ] )
2788
.output ( ) ;
2789
write (
2790
& g,
2791
" src/lib.rs" ,
2792
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n " ,
2793
) ;
c3069537 Andy McCall 1 month ago ↶
2794
git ( & [ " add" , " ." ] ) ;
2795
git ( & [ " commit" , " -q" , " -m" , " resolve clash" ] ) ;
2796
git ( & [ " rm" , " -q" , " logo.png" ] ) ;
2797
git ( & [ " commit" , " -q" , " -m" , " drop logo" ] ) ;
6e723b81 Andy McCall 1 month ago ↶
2798
2799
git ( & [ " mv" , " src/lib.rs" , " src/lib2.rs" ] ) ;
75167672 Andy McCall 1 month ago
2800
write (
2801
& g,
2802
" src/lib2.rs" ,
2803
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n pub fn d() {}\n " ,
2804
) ;
6e723b81 Andy McCall 1 month ago ↶
2805
git ( & [ " add" , " ." ] ) ;
2806
git ( & [ " commit" , " -q" , " -m" , " rename lib" ] ) ;
958fb27d Andy McCall 1 month ago ↶
2807
2808
2809
2810
git ( & [ " checkout" , " -q" , " -b" , " discarded" ] ) ;
2811
write ( & g, " run.sh" , b " #!/bin/sh\n echo discarded\n " ) ;
2812
git ( & [ " commit" , " -q" , " -am" , " discarded edit" ] ) ;
2813
git ( & [ " checkout" , " -q" , " main" ] ) ;
314db9b4 Andy McCall 1 month ago ↶
2814
git ( & [
2815
" merge" ,
2816
" -q" ,
2817
" -s" ,
2818
" ours" ,
2819
" -m" ,
2820
" merge discarded, keeping ours" ,
2821
" discarded" ,
2822
] ) ;
c3069537 Andy McCall 1 month ago ↶
2823
let expected_count: usize = git ( & [ " rev-list" , " --count" , " main" ] ) .parse ( ) .unwrap ( ) ;
2824
2825
let dest = root.join ( " oxy" ) ;
2826
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2827
let rep = repo.import_git ( & g, " main" ) .unwrap ( ) ;
2828
assert_eq! ( rep.commits, expected_count) ;
958fb27d Andy McCall 1 month ago ↶
2829
assert_eq! ( rep.merges, 3 ) ;
314db9b4 Andy McCall 1 month ago ↶
2830
assert_eq! (
2831
fs:: read( dest.join ( " run.sh" ) ) .unwrap ( ) ,
2832
b " #!/bin/sh\n " ,
2833
" the ours merge kept main's run.sh"
2834
) ;
958fb27d Andy McCall 1 month ago ↶
2835
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
2836
assert_eq! ( v.commits, expected_count, " every commit's tree reproduced" ) ;
2837
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
c3069537 Andy McCall 1 month ago ↶
2838
75167672 Andy McCall 1 month ago
2839
assert_eq! (
2840
fs:: read( dest.join ( " src/lib2.rs" ) ) .unwrap ( ) ,
2841
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n pub fn d() {}\n "
2842
) ;
6e723b81 Andy McCall 1 month ago ↶
2843
assert! ( ! dest.join ( " src/lib.rs" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
2844
assert_eq! (
2845
fs:: read( dest.join ( " README.md" ) ) .unwrap ( ) ,
2846
b " # Hi\n \n More.\n "
2847
) ;
c3069537 Andy McCall 1 month ago ↶
2848
assert! ( ! dest.join ( " logo.png" ) .exists ( ) ) ;
2849
# [ cfg ( unix ) ]
2850
{
2851
use std:: os:: unix:: fs:: PermissionsExt;
75167672 Andy McCall 1 month ago
2852
assert! (
2853
fs:: metadata( dest.join ( " run.sh" ) )
2854
.unwrap ( )
2855
.permissions ( )
2856
.mode ( )
2857
& 0o111
2858
! = 0
2859
) ;
c3069537 Andy McCall 1 month ago ↶
2860
}
75167672 Andy McCall 1 month ago
2861
assert! (
2862
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
2863
" clean after import"
2864
) ;
c3069537 Andy McCall 1 month ago ↶
2865
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
2866
let st = repo.load_state ( & heads) .unwrap ( ) ;
2867
assert! ( ! st.materialize ( ) .is_conflicted ( ) ) ;
2868
assert_eq! ( st.len ( ) , expected_count) ;
75167672 Andy McCall 1 month ago
2869
let tip = repo
2870
.log ( )
2871
.unwrap ( )
2872
.into_iter ( )
2873
.find ( | e | e.change.message.starts_with ( " rename lib" ) )
2874
.unwrap ( ) ;
6e723b81 Andy McCall 1 month ago ↶
2875
let c = repo.store ( ) .get_change ( & tip.hash) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2876
assert! (
2877
c.tree_ops
2878
.iter ( )
2879
.any ( | op | matches! ( op, TreeOp:: Move { name, .. } if name = = " lib2.rs" ) ) ,
2880
" {:?}" ,
2881
c.tree_ops
2882
) ;
2883
assert! ( ! c
2884
.tree_ops
2885
.iter ( )
2886
.any ( | op | matches! ( op, TreeOp:: AddNode { .. } ) ) ) ;
c3069537 Andy McCall 1 month ago ↶
2887
2888
let log = repo.log ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2889
let second = log
2890
.iter ( )
2891
.find ( | e | e.change.message.starts_with ( " second" ) )
2892
.unwrap ( ) ;
2893
assert_eq! (
2894
second.change.change_id,
2895
ChangeId:: from_hex( " 0123456789abcdef0123456789abcdef" ) .unwrap ( )
2896
) ;
2897
assert! ( second
2898
.change
2899
.message
2900
.contains ( " Git-Author: Grace <[email protected] >" ) ) ;
2901
let resolve = log
2902
.iter ( )
2903
.find ( | e | e.change.message.starts_with ( " resolve clash" ) )
2904
.unwrap ( ) ;
2905
assert_eq! (
2906
resolve.change.kind,
2907
ChangeKind:: Resolution,
2908
" a conflicting Git merge becomes a resolution"
2909
) ;
c3069537 Andy McCall 1 month ago ↶
2910
75167672 Andy McCall 1 month ago
2911
let repo2 = Repo:: init( & root.join ( " oxy2" ) )
2912
.unwrap ( )
2913
.with_home ( root.join ( " home" ) ) ;
c3069537 Andy McCall 1 month ago ↶
2914
repo2.import_git ( & g, " main" ) .unwrap ( ) ;
dfb23a3a Andy McCall 1 month ago ↶
2915
2916
2917
assert_eq! (
2918
repo2.bookmark_heads ( ) .unwrap ( ) ,
2919
repo.bookmark_heads ( ) .unwrap ( )
2920
) ;
75167672 Andy McCall 1 month ago
2921
let ids: BTreeSet< ChangeId> = repo
2922
.log ( )
2923
.unwrap ( )
2924
.iter ( )
2925
.filter ( | e | ! e.is_working_copy)
2926
.map ( | e | e.change.change_id )
2927
.collect ( ) ;
2928
let ids2: BTreeSet< ChangeId> = repo2
2929
.log ( )
2930
.unwrap ( )
2931
.iter ( )
2932
.filter ( | e | ! e.is_working_copy)
2933
.map ( | e | e.change.change_id )
2934
.collect ( ) ;
c3069537 Andy McCall 1 month ago ↶
2935
assert_eq! ( ids, ids2) ;
2936
2937
let mirror = root.join ( " mirror.git" ) ;
2938
repo.git_export ( & mirror) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2939
let out = std:: process:: Command:: new( " git" )
2940
.arg ( " -C" )
2941
.arg ( & mirror)
2942
.args ( [ " show" , " main:src/lib2.rs" ] )
2943
.output ( )
2944
.unwrap ( ) ;
2945
assert_eq! (
2946
String :: from_utf8_lossy( & out.stdout) ,
2947
" pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n pub fn d() {}\n "
2948
) ;
2949
let out = std:: process:: Command:: new( " git" )
2950
.arg ( " -C" )
2951
.arg ( & mirror)
2952
.args ( [ " show" , " main:src/lib.rs" ] )
2953
.output ( )
2954
.unwrap ( ) ;
2955
assert! (
2956
! out.status.success ( ) ,
2957
" the old path is gone from the export"
2958
) ;
c3069537 Andy McCall 1 month ago ↶
2959
}
252e8cb2 Andy McCall 1 month ago ↶
2960
2961
# [ test ]
2962
fn checkpoints_accelerate_loading_and_stay_correct ( ) {
2963
hermetic ( ) ;
2964
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2965
let root = dir.path ( ) ;
2966
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2967
repo.config.set ( " core.checkpoint-interval" , " 5" ) .unwrap ( ) ;
2968
repo.save_config ( ) .unwrap ( ) ;
2969
let repo = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2970
let mut hashes = Vec :: new( ) ;
2971
for i in 0 .. 12 {
75167672 Andy McCall 1 month ago
2972
write (
2973
root,
2974
" a.txt" ,
2975
format! (
2976
" {} \n " ,
2977
( 0 .. = i)
2978
.map ( | k | k.to_string ( ) )
2979
.collect:: < Vec< _ > > ( )
2980
.join ( " \n " )
2981
)
2982
.as_bytes ( ) ,
2983
) ;
252e8cb2 Andy McCall 1 month ago ↶
2984
write ( root, & format! ( " f{i} .txt" ) , b " x\n " ) ;
2985
hashes.push ( repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ) ;
2986
}
75167672 Andy McCall 1 month ago
2987
let ( _ , cp) = repo
2988
.latest_checkpoint ( " main" )
2989
.unwrap ( )
2990
.expect ( " automatic checkpoint" ) ;
252e8cb2 Andy McCall 1 month ago ↶
2991
assert! ( cp.count > = 5 & & cp.count < = 12 , " {}" , cp.count) ;
2992
2993
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
2994
let fast = repo.load_state ( & heads) .unwrap ( ) ;
2995
let full = repo.load_state_full ( & heads) .unwrap ( ) ;
2996
assert_eq! ( fast.len ( ) , full.len ( ) ) ;
2997
assert_eq! ( fast.heads ( ) , full.heads ( ) ) ;
2998
let ( a, b) = ( fast.materialize ( ) , full.materialize ( ) ) ;
75167672 Andy McCall 1 month ago
2999
assert_eq! (
3000
a.files.keys ( ) .collect:: < Vec< _ > > ( ) ,
3001
b.files.keys ( ) .collect:: < Vec< _ > > ( )
3002
) ;
252e8cb2 Andy McCall 1 month ago ↶
3003
for ( p, f) in & a.files {
3004
assert_eq! ( f.content, b.files[ p] .content, " {p}" ) ;
3005
}
3006
assert! ( repo.load_state_checkpointed ( & heads) .unwrap ( ) .is_some ( ) ) ;
3007
75167672 Andy McCall 1 month ago
3008
assert! ( repo
3009
.load_state_checkpointed ( & [ hashes[ 0 ] ] )
3010
.unwrap ( )
3011
.is_none ( ) ) ;
252e8cb2 Andy McCall 1 month ago ↶
3012
assert_eq! ( repo.load_state ( & [ hashes[ 0 ] ] ) .unwrap ( ) .len ( ) , 1 ) ;
3013
3014
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
3015
let h = repo.describe ( & hashes[ 3 ] , " c3 described" ) .unwrap ( ) ;
3016
assert_eq! ( repo.store ( ) .get_change ( & h) .unwrap ( ) .message, " c3 described" ) ;
3017
let ( r, n, bytes) = repo.checkpoint ( ) .unwrap ( ) ;
3018
assert! ( n > = 12 & & bytes > 0 ) ;
3019
assert_eq! ( repo.latest_checkpoint ( " main" ) .unwrap ( ) .unwrap ( ) .0 , r) ;
3020
write ( root, " a.txt" , b " after\n " ) ;
3021
let h_after = repo.commit ( " after" , & [ ] ) .unwrap ( ) ;
3022
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
3023
assert! ( st.contains ( & h_after) ) ;
75167672 Andy McCall 1 month ago
3024
assert! (
3025
matches! ( & st.materialize ( ) .files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " after\n " )
3026
) ;
252e8cb2 Andy McCall 1 month ago ↶
3027
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 14 ) ;
3028
3029
repo.gc ( true ) .unwrap ( ) ;
3030
assert! ( repo.latest_checkpoint ( " main" ) .unwrap ( ) .is_some ( ) ) ;
75167672 Andy McCall 1 month ago
3031
assert! ( repo
3032
.load_state_checkpointed ( & repo.bookmark_heads ( ) .unwrap ( ) )
3033
.unwrap ( )
3034
.is_some ( ) ) ;
252e8cb2 Andy McCall 1 month ago ↶
3035
}
135212de Andy McCall 1 month ago ↶
3036
3037
# [ test ]
3038
fn quick_status_for_prompts ( ) {
3039
hermetic ( ) ;
3040
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3041
let root = dir.path ( ) ;
3042
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
3043
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
3044
let a = Repo:: clone( & url, & root.join ( " a" ) )
3045
.unwrap ( )
3046
.with_home ( root.join ( " home" ) ) ;
135212de Andy McCall 1 month ago ↶
3047
let ops_before = a.op_log ( ) .unwrap ( ) .len ( ) ;
3048
let info = a.quick_status ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3049
assert_eq! (
3050
(
3051
info.bookmark.as_str ( ) ,
3052
info.heads,
3053
info.dirty,
3054
info.conflicted,
3055
info.ahead,
3056
info.behind
3057
) ,
3058
( " main" , 0 , false , false , Some ( 0 ) , Some ( 0 ) )
3059
) ;
135212de Andy McCall 1 month ago ↶
3060
write ( & root.join ( " a" ) , " f.txt" , b " f\n " ) ;
3061
assert! ( a.quick_status ( ) .unwrap ( ) .dirty, " a new file is dirty" ) ;
75167672 Andy McCall 1 month ago
3062
assert_eq! (
3063
a.op_log ( ) .unwrap ( ) .len ( ) ,
3064
ops_before,
3065
" quick_status records nothing"
3066
) ;
135212de Andy McCall 1 month ago ↶
3067
3068
a.snapshot ( " status" ) .unwrap ( ) ;
3069
assert! ( a.quick_status ( ) .unwrap ( ) .dirty) ;
3070
a.commit ( " f" , & [ ] ) .unwrap ( ) ;
3071
let info = a.quick_status ( ) .unwrap ( ) ;
3072
assert! ( ! info.dirty, " clean right after a commit" ) ;
3073
assert_eq! ( ( info.ahead, info.behind) , ( Some ( 1 ) , Some ( 0 ) ) ) ;
75167672 Andy McCall 1 month ago
3074
assert_eq! (
3075
info.format ( " %b %c%d%x%A%D" ) ,
3076
format! ( " main {} +1" , info.change_id)
3077
) ;
135212de Andy McCall 1 month ago ↶
3078
assert_eq! ( info.format ( " [%h heads] %a/%B %%" ) , " [1 heads] 1/0 %" ) ;
3079
3080
3081
let p = root.join ( " a/f.txt" ) ;
3082
let content = fs:: read( & p) .unwrap ( ) ;
3083
fs:: write( & p, & content) .unwrap ( ) ;
3084
assert! ( ! a.quick_status ( ) .unwrap ( ) .dirty) ;
3085
fs:: write( & p, b " g\n " ) .unwrap ( ) ;
3086
assert! ( a.quick_status ( ) .unwrap ( ) .dirty) ;
3087
fs:: write( & p, b " f\n " ) .unwrap ( ) ;
3088
3089
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
3090
assert_eq! ( a.quick_status ( ) .unwrap ( ) .ahead, Some ( 0 ) ) ;
75167672 Andy McCall 1 month ago
3091
let b = Repo:: clone( & url, & root.join ( " b" ) )
3092
.unwrap ( )
3093
.with_home ( root.join ( " home-b" ) ) ;
135212de Andy McCall 1 month ago ↶
3094
write ( & root.join ( " b" ) , " g.txt" , b " g\n " ) ;
3095
b.commit ( " g" , & [ ] ) .unwrap ( ) ;
3096
b.push ( " origin" , false ) .unwrap ( ) ;
3097
a.fetch ( " origin" ) .unwrap ( ) ;
3098
let info = a.quick_status ( ) .unwrap ( ) ;
3099
assert_eq! ( ( info.ahead, info.behind) , ( Some ( 0 ) , Some ( 1 ) ) ) ;
3100
assert_eq! ( info.format ( " %A%D" ) , " -1" ) ;
3101
let _ = origin;
3102
}
b3c5a59a Andy McCall 1 month ago ↶
3103
3104
# [ test ]
3105
fn lazy_blobs_are_fetched_on_first_read ( ) {
3106
hermetic ( ) ;
3107
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3108
let root = dir.path ( ) ;
3109
let origin_dir = root.join ( " origin" ) ;
75167672 Andy McCall 1 month ago
3110
let origin = Repo:: init( & origin_dir)
3111
.unwrap ( )
3112
.with_home ( root.join ( " home" ) ) ;
b3c5a59a Andy McCall 1 month ago ↶
3113
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
3114
let big: Vec< u8 > = ( 0 .. 200_000 u32 ) .map ( | i | ( i % 251 ) as u8 ) .collect ( ) ;
3115
write ( & origin_dir, " big.bin" , & big) ;
3116
write ( & origin_dir, " small.bin" , & [ 0 u8 , 1 , 2 ] ) ;
3117
write ( & origin_dir, " t.txt" , b " t\n " ) ;
3118
let h = origin.commit ( " blobs" , & [ ] ) .unwrap ( ) ;
3119
let big_ref = Repo:: blob_ref( & big) ;
3120
3121
75167672 Andy McCall 1 month ago
3122
let a = Repo:: init_with_id( & root.join ( " a" ) , origin.id ( ) .unwrap ( ) )
3123
.unwrap ( )
3124
.with_home ( root.join ( " home" ) ) ;
b3c5a59a Andy McCall 1 month ago ↶
3125
let a = {
3126
let mut a = a;
3127
a.config.set ( " remote.origin.url" , & url) .unwrap ( ) ;
3128
a.config.set ( " filter.max-blob" , " 100K" ) .unwrap ( ) ;
3129
a.save_config ( ) .unwrap ( ) ;
3130
a
3131
} ;
3132
assert_eq! ( a.config.max_blob, Some ( 100 * 1024 ) ) ;
3133
a.fetch ( " origin" ) .unwrap ( ) ;
3134
assert! ( a.store ( ) .has ( & h) ) ;
3135
assert! ( ! a.store ( ) .has ( & big_ref) , " withheld" ) ;
75167672 Andy McCall 1 month ago
3136
assert! (
3137
a.store ( ) .has ( & Repo:: blob_ref( & [ 0 u8 , 1 , 2 ] ) ) ,
3138
" small blob came along"
3139
) ;
b3c5a59a Andy McCall 1 month ago ↶
3140
assert_eq! ( a.lazy_remote ( & big_ref) .as_deref ( ) , Some ( " origin" ) ) ;
3141
3142
3143
let ( op_ref, op) = a.head_op ( ) .unwrap ( ) ;
3144
let mut view = op.view.clone ( ) ;
75167672 Andy McCall 1 month ago
3145
view.bookmarks.insert (
3146
" main" .into ( ) ,
3147
a.store ( ) .put_state ( & StateObject:: new( vec! [ h] ) ) .unwrap ( ) ,
3148
) ;
b3c5a59a Andy McCall 1 month ago ↶
3149
a.record_op ( & [ op_ref] , " adopt" , view) .unwrap ( ) ;
3150
3151
a.checkout ( & [ h] , true , " checkout" ) .unwrap ( ) ;
3152
assert! ( a.store ( ) .has ( & big_ref) , " fetched on first read" ) ;
3153
assert_eq! ( fs:: read( root.join ( " a/big.bin" ) ) .unwrap ( ) , big) ;
3154
assert! ( a.lazy_remote ( & big_ref) .is_none ( ) , " no longer registered" ) ;
3155
write ( & root.join ( " a" ) , " t.txt" , b " t2\n " ) ;
3156
a.commit ( " text edit" , & [ ] ) .unwrap ( ) ;
3157
3158
7421309f Andy McCall 1 month ago ↶
3159
let b = Repo:: clone_with( & url, & root.join ( " b" ) , Some ( 1024 ) , & [ ] ) .unwrap ( ) ;
b3c5a59a Andy McCall 1 month ago ↶
3160
assert_eq! ( b.config.max_blob, Some ( 1024 ) ) ;
3161
assert_eq! ( fs:: read( root.join ( " b/big.bin" ) ) .unwrap ( ) , big) ;
3162
assert_eq! ( parse_size ( " 2M" ) .unwrap ( ) , 2 < < 20 ) ;
3163
assert! ( parse_size ( " lots" ) .is_err ( ) ) ;
3164
}
7421309f Andy McCall 1 month ago ↶
3165
3166
# [ test ]
3167
fn sparse_checkout_materializes_only_chosen_paths ( ) {
3168
hermetic ( ) ;
3169
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3170
let root = dir.path ( ) ;
3171
let origin_dir = root.join ( " origin" ) ;
75167672 Andy McCall 1 month ago
3172
let origin = Repo:: init( & origin_dir)
3173
.unwrap ( )
3174
.with_home ( root.join ( " home" ) ) ;
7421309f Andy McCall 1 month ago ↶
3175
write ( & origin_dir, " src/main.rs" , b " fn main() {}\n " ) ;
3176
write ( & origin_dir, " src/lib.rs" , b " pub fn a() {}\n " ) ;
3177
write ( & origin_dir, " docs/guide.md" , b " # guide\n " ) ;
3178
let logo: Vec< u8 > = ( 0 .. 50_000 u32 ) .map ( | i | ( i % 7 ) as u8 ) .collect ( ) ;
3179
write ( & origin_dir, " docs/logo.png" , & logo) ;
3180
write ( & origin_dir, " README.md" , b " # top\n " ) ;
3181
let h1 = origin.commit ( " tree" , & [ ] ) .unwrap ( ) ;
3182
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
3183
75167672 Andy McCall 1 month ago
3184
let a = Repo:: clone_with( & url, & root.join ( " a" ) , None , & [ " src" .into ( ) ] )
3185
.unwrap ( )
3186
.with_home ( root.join ( " home" ) ) ;
7421309f Andy McCall 1 month ago ↶
3187
assert_eq! ( a.config.sparse_paths, vec! [ " src" ] ) ;
3188
assert_eq! ( a.config.max_blob, Some ( 0 ) , " sparse implies lazy blobs" ) ;
3189
assert! ( root.join ( " a/src/main.rs" ) .exists ( ) ) ;
3190
assert! ( ! root.join ( " a/docs" ) .exists ( ) & & ! root.join ( " a/README.md" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
3191
assert! (
3192
! a.store ( ) .has ( & Repo:: blob_ref( & logo) ) ,
3193
" out-of-scope blob never fetched"
3194
) ;
3195
assert! (
3196
! a.snapshot ( " status" ) .unwrap ( ) .changed,
3197
" out-of-scope files are not removals"
3198
) ;
05e71ed6 Andy McCall 1 month ago ↶
3199
assert! ( ! a.quick_status ( ) .unwrap ( ) .dirty) ;
7421309f Andy McCall 1 month ago ↶
3200
3201
75167672 Andy McCall 1 month ago
3202
write (
3203
& root.join ( " a" ) ,
3204
" src/lib.rs" ,
3205
b " pub fn a() {}\n pub fn b() {}\n " ,
3206
) ;
7421309f Andy McCall 1 month ago ↶
3207
let h2 = a.commit ( " edit lib" , & [ ] ) .unwrap ( ) ;
3208
let st = a.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
3209
assert! (
3210
st.files.contains_key ( " docs/guide.md" ) ,
3211
" the state still has everything"
3212
) ;
7421309f Andy McCall 1 month ago ↶
3213
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
3214
origin.refresh ( ) .unwrap ( ) ;
7421309f Andy McCall 1 month ago ↶
3215
3216
write ( & root.join ( " a" ) , " docs/new.md" , b " ignored\n " ) ;
3217
assert! ( a.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
3218
3219
3220
let mut a = a;
3221
a.config.set ( " filter.paths" , " src,docs" ) .unwrap ( ) ;
3222
a.save_config ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3223
let r = a
3224
.checkout ( & a.bookmark_heads ( ) .unwrap ( ) , true , " widen" )
3225
.unwrap ( ) ;
7421309f Andy McCall 1 month ago ↶
3226
assert! ( r.written > = 2 ) ;
75167672 Andy McCall 1 month ago
3227
assert_eq! (
3228
fs:: read( root.join ( " a/docs/logo.png" ) ) .unwrap ( ) ,
3229
logo,
3230
" now fetched lazily"
3231
) ;
7421309f Andy McCall 1 month ago ↶
3232
assert! ( ! root.join ( " a/README.md" ) .exists ( ) ) ;
3233
3234
origin.pull ( " origin" ) .err ( ) ;
75167672 Andy McCall 1 month ago
3235
let full = origin
3236
.load_state ( & origin.bookmark_heads ( ) .unwrap ( ) )
3237
.unwrap ( )
3238
.materialize ( ) ;
3239
assert! (
3240
matches! ( & full.files[ " src/lib.rs" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " pub fn a() {}\n pub fn b() {}\n " )
3241
) ;
7421309f Andy McCall 1 month ago ↶
3242
assert! ( full.files.contains_key ( " README.md" ) ) ;
3243
let _ = h1;
3244
}
2863456a Andy McCall 1 month ago ↶
3245
7bd805ff Andy McCall 13 days ago ↶
3246
3247
3248
3249
2863456a Andy McCall 1 month ago ↶
3250
# [ test ]
7bd805ff Andy McCall 13 days ago ↶
3251
fn a_depth_0_clone_brings_its_binary_files ( ) {
3252
hermetic ( ) ;
3253
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3254
let root = dir.path ( ) ;
3255
let origin_dir = root.join ( " origin" ) ;
3256
let origin = Repo:: init( & origin_dir)
3257
.unwrap ( )
3258
.with_home ( root.join ( " home" ) ) ;
3259
let png: Vec< u8 > = ( 0 .. 4000 u32 )
3260
.map ( | i | ( i * 7 % 251 ) as u8 )
3261
.chain ( [ 0 , 0xff ] )
3262
.collect ( ) ;
3263
3264
3265
write ( & origin_dir, " logo.png" , & png) ;
3266
write ( & origin_dir, " a.txt" , b " first\n " ) ;
3267
origin.commit ( " logo" , & [ ] ) .unwrap ( ) ;
3268
write ( & origin_dir, " a.txt" , b " later\n " ) ;
3269
origin.commit ( " later" , & [ ] ) .unwrap ( ) ;
3270
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) .len ( ) , 1 ) ;
3271
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
3272
Repo:: clone_shallow( & url, & root.join ( " a" ) , 0 , None , & [ ] ) .unwrap ( ) ;
3273
assert_eq! ( fs:: read( root.join ( " a/logo.png" ) ) .unwrap ( ) , png) ;
3274
let lazy = Repo:: clone_shallow( & url, & root.join ( " b" ) , 0 , Some ( 0 ) , & [ ] ) .unwrap ( ) ;
3275
assert_eq! ( fs:: read( root.join ( " b/a.txt" ) ) .unwrap ( ) , b " later\n " ) ;
3276
let blob = {
3277
let heads = lazy.bookmark_heads ( ) .unwrap ( ) ;
3278
let st = lazy.load_state_history ( & heads) .unwrap ( ) ;
3279
st.opaque_nodes ( )
3280
.iter ( )
3281
.flat_map ( | n | st.live_blobs ( n) )
3282
.next ( )
3283
.unwrap ( )
3284
} ;
3285
assert_eq! ( lazy.read_blob ( & blob) .unwrap ( ) , png, " fetched on read" ) ;
3286
}
3287
3288
# [ test ]
2863456a Andy McCall 1 month ago ↶
3289
fn shallow_clone_and_deepen ( ) {
3290
hermetic ( ) ;
3291
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3292
let root = dir.path ( ) ;
3293
let origin_dir = root.join ( " origin" ) ;
75167672 Andy McCall 1 month ago
3294
let origin = Repo:: init( & origin_dir)
3295
.unwrap ( )
3296
.with_home ( root.join ( " home" ) ) ;
2863456a Andy McCall 1 month ago ↶
3297
let mut hashes = Vec :: new( ) ;
3298
for i in 0 .. 8 {
75167672 Andy McCall 1 month ago
3299
write (
3300
& origin_dir,
3301
" a.txt" ,
3302
format! (
3303
" {} \n " ,
3304
( 0 .. = i)
3305
.map ( | k | k.to_string ( ) )
3306
.collect:: < Vec< _ > > ( )
3307
.join ( " \n " )
3308
)
3309
.as_bytes ( ) ,
3310
) ;
2863456a Andy McCall 1 month ago ↶
3311
hashes.push ( origin.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ) ;
3312
}
3313
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
3314
let a = Repo:: clone_shallow( & url, & root.join ( " a" ) , 3 , None , & [ ] )
3315
.unwrap ( )
3316
.with_home ( root.join ( " home" ) ) ;
2863456a Andy McCall 1 month ago ↶
3317
assert! ( a.is_shallow ( ) ) ;
75167672 Andy McCall 1 month ago
3318
assert_eq! (
3319
fs:: read_to_string( root.join ( " a/a.txt" ) ) .unwrap ( ) ,
3320
" 0\n 1\n 2\n 3\n 4\n 5\n 6\n 7\n "
3321
) ;
3322
assert! (
3323
a.store ( ) .has ( & hashes[ 7 ] ) & & a.store ( ) .has ( & hashes[ 5 ] ) ,
3324
" three levels present"
3325
) ;
2863456a Andy McCall 1 month ago ↶
3326
assert! ( ! a.store ( ) .has ( & hashes[ 4 ] ) , " beyond the depth is absent" ) ;
3327
assert_eq! ( a.boundary ( ) , [ hashes[ 4 ] ] .into_iter ( ) .collect ( ) ) ;
75167672 Andy McCall 1 month ago
3328
assert_eq! (
3329
a.log ( ) .unwrap ( ) .len ( ) ,
3330
9 ,
3331
" the log shows every change from the checkpoint's metadata"
3332
) ;
2863456a Andy McCall 1 month ago ↶
3333
assert! ( ! a.snapshot ( " status" ) .unwrap ( ) .changed) ;
3334
assert_eq! ( a.quick_status ( ) .unwrap ( ) .heads, 1 ) ;
3335
3336
3337
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 " ) ;
3338
let h_new = a.commit ( " c8" , & [ ] ) .unwrap ( ) ;
3339
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
3340
origin.refresh ( ) .unwrap ( ) ;
2863456a Andy McCall 1 month ago ↶
3341
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ h_new] ) ;
3342
3343
write ( & origin_dir, " b.txt" , b " b\n " ) ;
3344
origin.pull ( " origin" ) .err ( ) ;
3345
let ( _ , wc) = origin.working_copy ( ) .unwrap ( ) ;
3346
let _ = wc;
75167672 Andy McCall 1 month ago
3347
origin
3348
.checkout ( & origin.bookmark_heads ( ) .unwrap ( ) , true , " co" )
3349
.unwrap ( ) ;
2863456a Andy McCall 1 month ago ↶
3350
write ( & origin_dir, " b.txt" , b " b\n " ) ;
3351
let h_b = origin.commit ( " b" , & [ ] ) .unwrap ( ) ;
3352
let pr = a.pull ( " origin" ) .unwrap ( ) ;
3353
assert! ( pr.changed) ;
3354
assert! ( a.store ( ) .has ( & h_b) ) ;
3355
assert! ( root.join ( " a/b.txt" ) .exists ( ) ) ;
3356
3357
3358
3359
3360
assert! ( a.deepen ( " origin" , 2 ) .unwrap ( ) > = 2 ) ;
3361
assert! ( a.store ( ) .has ( & hashes[ 3 ] ) ) ;
3362
assert! ( a.is_shallow ( ) ) ;
3363
a.deepen ( " origin" , 100 ) .unwrap ( ) ;
3364
assert! ( ! a.is_shallow ( ) , " boundary reached the root" ) ;
3365
assert! ( a.store ( ) .has ( & hashes[ 0 ] ) ) ;
75167672 Andy McCall 1 month ago
3366
assert_eq! (
3367
a.load_state_full ( & a.bookmark_heads ( ) .unwrap ( ) )
3368
.unwrap ( )
3369
.len ( ) ,
3370
10
3371
) ;
2863456a Andy McCall 1 month ago ↶
3372
}
132b884b Andy McCall 1 month ago ↶
3373
3374
# [ test ]
3375
fn tags_are_immutable_named_states_and_travel ( ) {
3376
hermetic ( ) ;
3377
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3378
let root = dir.path ( ) ;
3379
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
3380
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
3381
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
3382
.unwrap ( )
3383
.with_home ( root.join ( " home-a" ) ) ;
3384
let ( key_a, _ ) = a
3385
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
3386
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3387
write ( & root.join ( " a" ) , " f.txt" , b " 1\n " ) ;
3388
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
3389
write ( & root.join ( " a" ) , " f.txt" , b " 1\n 2\n " ) ;
3390
let h2 = a.commit ( " two" , & [ ] ) .unwrap ( ) ;
3391
3392
let t = a.tag_create ( " v1" , & [ h1] , " first release" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3393
assert_eq! (
3394
( t.heads.clone ( ) , t.signer.as_deref ( ) ) ,
3395
( vec! [ h1] , Some ( " Ada" ) )
3396
) ;
132b884b Andy McCall 1 month ago ↶
3397
assert! ( a.tag_create ( " v1" , & [ h2] , " " ) .is_err ( ) , " immutable" ) ;
3398
assert! ( a.tag_create ( " bad/name" , & [ h2] , " " ) .is_err ( ) ) ;
3399
assert_eq! ( a.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
3400
assert_eq! ( a.resolve_heads ( " v1" ) .unwrap ( ) , vec! [ h1] ) ;
3401
assert_eq! ( a.resolve_heads ( " main" ) .unwrap ( ) , vec! [ h2] ) ;
3402
let ( _ , sa) = a.tag_attest ( " v1" ) .unwrap ( ) .unwrap ( ) ;
3403
assert_eq! ( sa.sigs[ 0 ] .key, key_a) ;
ee130dd9 Andy McCall 17 days ago ↶
3404
3405
3406
let mut refs = a.remote_refs ( ) .unwrap ( ) ;
3407
a.refs_for_client ( & mut refs, None ) ;
3408
assert_eq! ( refs.tags.len ( ) , 1 ) ;
132b884b Andy McCall 1 month ago ↶
3409
75167672 Andy McCall 1 month ago
3410
a.checkout ( & a.resolve_heads ( " v1" ) .unwrap ( ) , false , " co" )
3411
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3412
assert_eq! ( fs:: read( root.join ( " a/f.txt" ) ) .unwrap ( ) , b " 1\n " ) ;
75167672 Andy McCall 1 month ago
3413
a.checkout ( & a.bookmark_heads ( ) .unwrap ( ) , false , " co" )
3414
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3415
3416
3417
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3418
origin.refresh ( ) .unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3419
assert! ( r.iter ( ) .any ( | x | x.name = = " tags/v1" & & x.ok ) , " {r:?}" ) ;
3420
assert_eq! ( origin.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
75167672 Andy McCall 1 month ago
3421
let mut forged = a
3422
.tag_refs ( )
3423
.unwrap ( )
3424
.into_iter ( )
3425
.find ( | t | t.name = = " v1" )
3426
.unwrap ( ) ;
3427
forged.state = origin
3428
.store ( )
3429
.put_state ( & StateObject:: new( vec! [ h2] ) )
3430
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3431
forged.heads = vec! [ h2] ;
3432
forged.attestation = None ;
75167672 Andy McCall 1 month ago
3433
let r = origin
3434
.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ forged] )
3435
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3436
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " immutable" ) , " {r:?}" ) ;
3437
3438
75167672 Andy McCall 1 month ago
3439
let b = Repo:: clone( & url, & root.join ( " b" ) )
3440
.unwrap ( )
3441
.with_home ( root.join ( " home-b" ) ) ;
132b884b Andy McCall 1 month ago ↶
3442
assert_eq! ( b.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
3443
assert! ( b.tags ( ) .unwrap ( ) [ 0 ] .signer.as_deref ( ) = = Some ( " Ada" ) ) ;
3444
a.tag_create ( " v2" , & [ h2] , " second" ) .unwrap ( ) ;
3445
a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3446
origin.refresh ( ) .unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3447
let rep = b.fetch ( " origin" ) .unwrap ( ) ;
3448
assert_eq! ( rep.new_tags, 1 ) ;
3449
assert_eq! ( b.tag_heads ( " v2" ) .unwrap ( ) , Some ( vec! [ h2] ) ) ;
3450
3451
3452
a.policy_init ( ) .unwrap ( ) ;
3453
a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3454
origin.refresh ( ) .unwrap ( ) ;
1ec6fdbc Andy McCall 17 days ago ↶
3455
let unattested = crate :: protocol:: TagRef:: new(
3456
" v3" .into ( ) ,
3457
origin
75167672 Andy McCall 1 month ago
3458
.store ( )
3459
.put_state ( & StateObject:: new( vec! [ h2] ) )
3460
.unwrap ( ) ,
1ec6fdbc Andy McCall 17 days ago ↶
3461
vec! [ h2] ,
3462
None ,
3463
) ;
75167672 Andy McCall 1 month ago
3464
let r = origin
3465
.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ unattested] )
3466
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3467
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " attestation" ) , " {r:?}" ) ;
3468
a.tag_create ( " v3" , & [ h2] , " " ) .unwrap ( ) ;
3469
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3470
origin.refresh ( ) .unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3471
assert! ( r.iter ( ) .any ( | x | x.name = = " tags/v3" & & x.ok ) , " {r:?}" ) ;
3472
assert! ( a.gc ( true ) .is_ok ( ) ) ;
3473
assert_eq! ( a.tags ( ) .unwrap ( ) .len ( ) , 3 , " tags survive gc" ) ;
e5d9b051 Andy McCall 21 days ago ↶
3474
3475
3476
3477
3478
let rot = a.rotate_identity ( ) .unwrap ( ) ;
3479
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3480
origin.refresh ( ) .unwrap ( ) ;
e5d9b051 Andy McCall 21 days ago ↶
3481
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
3482
let c = Repo:: clone( & url, & root.join ( " c" ) )
3483
.unwrap ( )
3484
.with_home ( root.join ( " home-c" ) ) ;
3485
assert_eq! ( c.tag_heads ( " v3" ) .unwrap ( ) , Some ( vec! [ h2] ) ) ;
3486
let ( _ , sp) = c.current_policy ( ) .unwrap ( ) .unwrap ( ) ;
3487
assert! ( ! sp.policy.authorizes ( & key_a, " tags/v9" ) ) ;
3488
let made = c.identity ( & rot.new) .unwrap ( ) .created;
3489
assert! ( c.predecessor_authorized ( & sp.policy, & key_a, " tags/v9" , made - 1 ) ) ;
3490
assert! ( ! c.predecessor_authorized ( & sp.policy, & key_a, " tags/v9" , made) ) ;
3491
assert! ( ! c.predecessor_authorized ( & sp.policy, & rot.new, " tags/v9" , 0 ) ) ;
132b884b Andy McCall 1 month ago ↶
3492
}
4e88e774 Andy McCall 1 month ago ↶
3493
3494
# [ test ]
3495
fn multiple_workspaces_share_a_repository ( ) {
3496
hermetic ( ) ;
3497
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3498
let root = dir.path ( ) ;
3499
let main_dir = root.join ( " main" ) ;
3500
let repo = Repo:: init( & main_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
3501
write ( & main_dir, " a.txt" , b " a\n " ) ;
3502
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
3503
assert_eq! ( repo.workspaces ( ) .unwrap ( ) .len ( ) , 1 ) ;
3504
3505
3506
let ws_dir = root.join ( " ws2" ) ;
4476cdbb Andy McCall 12 days ago ↶
3507
let ops = repo.op_log ( ) .unwrap ( ) .len ( ) ;
4e88e774 Andy McCall 1 month ago ↶
3508
let ws = repo.workspace_add ( & ws_dir, None ) .unwrap ( ) ;
4476cdbb Andy McCall 12 days ago ↶
3509
assert_eq! (
3510
ws.op_log ( ) .unwrap ( ) .len ( ) ,
3511
ops + 2 ,
3512
" the add and its checkout, and no snapshot finding the tree deleted (oxygen#137)"
3513
) ;
4e88e774 Andy McCall 1 month ago ↶
3514
assert! ( ws_dir.join ( " .oxy" ) .is_file ( ) ) ;
3515
assert_eq! ( fs:: read( ws_dir.join ( " a.txt" ) ) .unwrap ( ) , b " a\n " ) ;
3516
assert_ne! ( ws.workspace ( ) , repo.workspace ( ) ) ;
3517
let list = repo.workspaces ( ) .unwrap ( ) ;
3518
assert_eq! ( list.len ( ) , 2 ) ;
75167672 Andy McCall 1 month ago
3519
assert! ( list
3520
.iter ( )
3521
.any ( | w | w.id = = ws.workspace ( ) & & w.path = = ws_dir.canonicalize ( ) .unwrap ( ) & & ! w.current ) ) ;
4e88e774 Andy McCall 1 month ago ↶
3522
assert! ( ! ws.snapshot ( " status" ) .unwrap ( ) .changed) ;
3523
3524
3525
write ( & ws_dir, " b.txt" , b " b\n " ) ;
3526
let s = ws.snapshot ( " status" ) .unwrap ( ) ;
3527
assert_eq! ( s.statuses.len ( ) , 1 ) ;
75167672 Andy McCall 1 month ago
3528
assert! (
3529
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
3530
" the first workspace is untouched"
3531
) ;
4e88e774 Andy McCall 1 month ago ↶
3532
let h2 = ws.commit ( " b from ws2" , & [ ] ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3533
repo.refresh ( ) .unwrap ( ) ;
4e88e774 Andy McCall 1 month ago ↶
3534
let mut heads = repo.bookmark_heads ( ) .unwrap ( ) ;
3535
heads.sort ( ) ;
3536
let mut want = vec! [ h1, h2] ;
3537
want.sort ( ) ;
3538
assert_eq! ( heads, want, " both workspaces see the bookmark move" ) ;
3539
assert! ( ! main_dir.join ( " b.txt" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
3540
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " co" )
3541
.unwrap ( ) ;
4e88e774 Andy McCall 1 month ago ↶
3542
assert! ( main_dir.join ( " b.txt" ) .exists ( ) ) ;
3543
3544
3545
let reopened = Repo:: open( & ws_dir) .unwrap ( ) ;
3546
assert_eq! ( reopened.workspace ( ) , ws.workspace ( ) ) ;
3547
assert_eq! ( reopened.id ( ) .unwrap ( ) , repo.id ( ) .unwrap ( ) ) ;
3548
assert_eq! ( reopened.quick_status ( ) .unwrap ( ) .heads, 2 ) ;
3549
3550
3551
assert! ( repo.workspace_add ( & main_dir.join ( " inner" ) , None ) .is_err ( ) ) ;
3552
assert! ( repo.workspace_forget ( & repo.workspace ( ) ) .is_err ( ) ) ;
3553
repo.workspace_forget ( & ws.workspace ( ) ) .unwrap ( ) ;
3554
assert_eq! ( repo.workspaces ( ) .unwrap ( ) .len ( ) , 1 ) ;
3555
assert! ( ws_dir.join ( " b.txt" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
3556
assert! (
3557
ws.snapshot ( " status" ) .is_err ( ) ,
3558
" a forgotten workspace is no longer in the view"
3559
) ;
4e88e774 Andy McCall 1 month ago ↶
3560
6caacfff Andy McCall 1 month ago ↶
3561
3562
4e88e774 Andy McCall 1 month ago ↶
3563
let lock = repo.lock_snapshot ( ) .unwrap ( ) ;
6caacfff Andy McCall 1 month ago ↶
3564
let again = repo.lock_working_copy ( ) .unwrap ( ) ;
3565
drop ( again) ;
4e88e774 Andy McCall 1 month ago ↶
3566
drop ( lock) ;
75167672 Andy McCall 1 month ago
3567
let lock_path = main_dir
3568
.join ( " .oxy/workspaces" )
3569
.join ( repo.workspace ( ) .to_hex ( ) )
3570
.join ( " lock" ) ;
4e88e774 Andy McCall 1 month ago ↶
3571
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 120 ) ;
6caacfff Andy McCall 1 month ago ↶
3572
let age = | p : & Path| fs:: File:: open( p) .unwrap ( ) .set_modified ( old) .unwrap ( ) ;
3573
fs:: write( & lock_path, std:: process:: id( ) .to_string ( ) ) .unwrap ( ) ;
3574
age ( & lock_path) ;
3575
assert! (
3576
! super :: workspace:: lock_is_stale( & lock_path) ,
3577
" a live process keeps its lock"
3578
) ;
3579
fs:: write( & lock_path, " 4294967295" ) .unwrap ( ) ;
3580
age ( & lock_path) ;
4e88e774 Andy McCall 1 month ago ↶
3581
assert! ( repo.lock_snapshot ( ) .is_ok ( ) , " stale lock taken over" ) ;
3582
}
cdb5beba Andy McCall 1 month ago ↶
3583
3584
# [ test ]
3585
fn attributes_binary_eol_nocompress_lockable ( ) {
3586
hermetic ( ) ;
3587
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3588
let root = dir.path ( ) ;
3589
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
3590
write (
3591
root,
3592
" .oxyattributes" ,
3593
b " *.dat binary nocompress\n *.txt eol=crlf\n assets/** lockable\n " ,
3594
) ;
cdb5beba Andy McCall 1 month ago ↶
3595
write ( root, " plain.dat" , b " no nul bytes here\n " ) ;
3596
write ( root, " notes.txt" , b " one\r \n two\r \n " ) ;
3597
write ( root, " assets/model.bin" , b " \0 model" ) ;
3598
let h = repo.commit ( " attrs" , & [ ] ) .unwrap ( ) ;
3599
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
3600
assert! (
3601
snap.files[ " plain.dat" ] .mode.opaque,
3602
" binary attribute forces opaque without NUL bytes"
3603
) ;
3604
assert! (
3605
matches! ( & snap.files[ " notes.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n " ) ,
3606
" stored with LF"
3607
) ;
3608
assert! (
3609
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
3610
" CRLF on disk equals LF in the state"
3611
) ;
cdb5beba Andy McCall 1 month ago ↶
3612
3613
let r = repo.gc ( true ) .unwrap ( ) ;
3614
assert! ( r.packed.objects > 0 ) ;
3615
3616
fs:: remove_file( root.join ( " notes.txt" ) ) .unwrap ( ) ;
3617
repo.checkout ( & [ h] , true , " co" ) .unwrap ( ) ;
3618
assert_eq! ( fs:: read( root.join ( " notes.txt" ) ) .unwrap ( ) , b " one\r \n two\r \n " ) ;
75167672 Andy McCall 1 month ago
3619
assert! ( fs:: metadata( root.join ( " assets/model.bin" ) )
3620
.unwrap ( )
3621
.permissions ( )
3622
.readonly ( ) ) ;
cdb5beba Andy McCall 1 month ago ↶
3623
assert! ( repo.lock_path ( " plain.dat" ) .is_err ( ) , " not lockable" ) ;
3624
repo.lock_path ( " assets/model.bin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3625
assert! ( ! fs:: metadata( root.join ( " assets/model.bin" ) )
3626
.unwrap ( )
3627
.permissions ( )
3628
.readonly ( ) ) ;
cdb5beba Andy McCall 1 month ago ↶
3629
assert! ( repo.is_locked ( " assets/model.bin" ) ) ;
3630
write ( root, " assets/model.bin" , b " \0 model2" ) ;
3631
repo.commit ( " edit model" , & [ ] ) .unwrap ( ) ;
3632
repo.unlock_path ( " assets/model.bin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3633
assert! ( fs:: metadata( root.join ( " assets/model.bin" ) )
3634
.unwrap ( )
3635
.permissions ( )
3636
.readonly ( ) ) ;
cdb5beba Andy McCall 1 month ago ↶
3637
3638
repo.checkout ( & [ h] , true , " co" ) .unwrap ( ) ;
3639
assert_eq! ( fs:: read( root.join ( " assets/model.bin" ) ) .unwrap ( ) , b " \0 model" ) ;
3640
}
6d3eefbc Andy McCall 1 month ago ↶
3641
3642
# [ cfg ( unix ) ]
3643
# [ test ]
3644
fn hooks_can_veto_observe_and_add_dependencies ( ) {
3645
use std:: os:: unix:: fs:: PermissionsExt;
3646
hermetic ( ) ;
3647
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3648
let root = dir.path ( ) ;
3649
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
3650
let hooks = root.join ( " .oxy/hooks" ) ;
3651
fs:: create_dir_all( & hooks) .unwrap ( ) ;
3652
let hook = | name : & str , body : & str | {
3653
let p = hooks.join ( name) ;
3654
fs:: write( & p, format! ( " #!/bin/sh\n {body} \n " ) ) .unwrap ( ) ;
3655
fs:: set_permissions( & p, fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
3656
} ;
3657
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" ) ;
3658
hook ( " post-commit" , " cat > \" $OXY_ROOT/.last-post-commit\" " ) ;
3659
hook ( " post-checkout" , " cat > \" $OXY_ROOT/.last-post-checkout\" " ) ;
3660
hook ( " pre-push" , " echo pushing blocked >&2; exit 3" ) ;
3661
write ( root, " .oxyignore" , b " .last-*\n " ) ;
3662
write ( root, " a.txt" , b " a\n " ) ;
3663
let err = repo.commit ( " WIP: nope" , & [ ] ) .unwrap_err ( ) .to_string ( ) ;
3664
assert! ( err.contains ( " pre-commit refused: no WIP commits" ) , " {err}" ) ;
3665
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
3666
let pre = fs:: read_to_string( root.join ( " .last-pre-commit" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3667
assert! (
3668
pre.contains ( " \" event\" :\" pre-commit\" " )
3669
& & pre.contains ( " \" path\" :\" a.txt\" " )
3670
& & pre.contains ( " \" message\" :\" first\" " ) ,
3671
" {pre}"
3672
) ;
6d3eefbc Andy McCall 1 month ago ↶
3673
let post = fs:: read_to_string( root.join ( " .last-post-commit" ) ) .unwrap ( ) ;
3674
assert! ( post.contains ( & h1.to_hex ( ) ) , " {post}" ) ;
3675
repo.checkout ( & [ h1] , false , " co" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3676
assert! ( fs:: read_to_string( root.join ( " .last-post-checkout" ) )
3677
.unwrap ( )
3678
.contains ( " post-checkout" ) ) ;
6d3eefbc Andy McCall 1 month ago ↶
3679
repo.config.set ( " remote.origin.url" , " /nowhere" ) .unwrap ( ) ;
3680
repo.save_config ( ) .unwrap ( ) ;
3681
let err = repo.push ( " origin" , false ) .unwrap_err ( ) .to_string ( ) ;
3682
assert! ( err.contains ( " pre-push refused: pushing blocked" ) , " {err}" ) ;
3683
3684
3685
write ( root, " b.txt" , b " b\n " ) ;
75167672 Andy McCall 1 month ago
3686
hook (
3687
" semantic-deps" ,
3688
& format! (
3689
" cat > \" $OXY_ROOT/.last-semantic\" ; echo '{{ \" deps\" : [\" {} \" ]}} '" ,
3690
h1.to_hex ( )
3691
) ,
3692
) ;
6d3eefbc Andy McCall 1 month ago ↶
3693
let h2 = repo.commit ( " b without hook" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3694
assert! (
3695
repo.store ( ) .get_change ( & h2) .unwrap ( ) .deps.is_empty ( ) ,
3696
" hook disabled by default"
3697
) ;
6d3eefbc Andy McCall 1 month ago ↶
3698
repo.config.set ( " hooks.semantic-deps" , " true" ) .unwrap ( ) ;
3699
repo.save_config ( ) .unwrap ( ) ;
3700
write ( root, " c.txt" , b " c\n " ) ;
3701
let h3 = repo.commit ( " c with hook" , & [ ] ) .unwrap ( ) ;
3702
assert_eq! ( repo.store ( ) .get_change ( & h3) .unwrap ( ) .deps, vec! [ h1] ) ;
3703
let sem = fs:: read_to_string( root.join ( " .last-semantic" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3704
assert! (
3705
sem.contains ( " \" touched_paths\" :[\" c.txt\" ]" ) & & sem.contains ( " candidate_deps" ) ,
3706
" {sem}"
3707
) ;
6d3eefbc Andy McCall 1 month ago ↶
3708
assert_eq! ( json_str ( " a\" b\\ c\n " ) , " \" a\\ \" b\\ \\ c\\ n\" " ) ;
3709
}
23b217d7 Andy McCall 1 month ago ↶
3710
3711
# [ test ]
3712
fn amend_into_older_change_and_split ( ) {
3713
hermetic ( ) ;
3714
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3715
let root = dir.path ( ) ;
3716
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
3717
repo.create_identity ( " Ada" , " a@b" , IdentityScope:: User)
3718
.unwrap ( ) ;
23b217d7 Andy McCall 1 month ago ↶
3719
repo.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
3720
write ( root, " a.txt" , b " one\n two\n " ) ;
3721
let h0 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
3722
write ( root, " a.txt" , b " one\n TWO\n " ) ;
3723
let h1 = repo.commit ( " edit two" , & [ ] ) .unwrap ( ) ;
3724
write ( root, " a.txt" , b " one\n TWO\n three\n " ) ;
3725
let h2 = repo.commit ( " add three" , & [ ] ) .unwrap ( ) ;
3726
let id1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) .change_id;
3727
3728
3729
write ( root, " a.txt" , b " one\n TWO!\n three\n " ) ;
3730
let h1b = repo.amend_into ( & h1, None ) .unwrap ( ) ;
3731
assert_ne! ( h1b, h1) ;
3732
let c1b = repo.store ( ) .get_change ( & h1b) .unwrap ( ) ;
3733
assert_eq! ( c1b.change_id, id1) ;
3734
assert_eq! ( c1b.deps, vec! [ h0] ) ;
75167672 Andy McCall 1 month ago
3735
assert! ( matches! (
3736
repo.verify_change ( & h1b) .unwrap ( ) ,
3737
SignatureStatus:: Valid { .. }
3738
) ) ;
23b217d7 Andy McCall 1 month ago ↶
3739
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
3740
assert_eq! ( heads.len ( ) , 1 ) ;
3741
let top = repo.store ( ) .get_change ( & heads[ 0 ] ) .unwrap ( ) ;
3742
assert_eq! ( top.message, " add three" ) ;
75167672 Andy McCall 1 month ago
3743
assert_eq! (
3744
top.deps.iter ( ) .copied ( ) .collect:: < BTreeSet< _ > > ( ) ,
3745
[ h0, h1b] .into_iter ( ) .collect ( ) ,
3746
" depends on the file's creator and the rewritten line"
3747
) ;
23b217d7 Andy McCall 1 month ago ↶
3748
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
3749
assert! (
3750
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n TWO!\n three\n " )
3751
) ;
3752
assert! (
3753
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
3754
" the working copy is clean after folding"
3755
) ;
23b217d7 Andy McCall 1 month ago ↶
3756
3757
let only = repo.load_state ( & [ h1b] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
3758
assert! (
3759
matches! ( & only.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n TWO!\n " )
3760
) ;
23b217d7 Andy McCall 1 month ago ↶
3761
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 ) ;
3762
3763
3764
write ( root, " a.txt" , b " one\n TWO!\n three!\n " ) ;
3765
let err = repo.amend_into ( & h1b, None ) .unwrap_err ( ) .to_string ( ) ;
3766
assert! ( err.contains ( " introduced after" ) , " {err}" ) ;
75167672 Andy McCall 1 month ago
3767
assert! (
3768
repo.snapshot ( " status" ) .unwrap ( ) .statuses.len ( ) = = 1 ,
3769
" nothing was changed by the refusal"
3770
) ;
23b217d7 Andy McCall 1 month ago ↶
3771
write ( root, " a.txt" , b " one\n TWO!\n three\n " ) ;
3772
3773
3774
write ( root, " b.txt" , b " b\n " ) ;
3775
write ( root, " c.txt" , b " c\n " ) ;
3776
let h3 = repo.commit ( " b and c" , & [ ] ) .unwrap ( ) ;
3777
let id3 = repo.store ( ) .get_change ( & h3) .unwrap ( ) .change_id;
3778
let ( p1, p2) = repo.split ( & h3, & [ " b.txt" .into ( ) ] ) .unwrap ( ) ;
3779
let c_1 = repo.store ( ) .get_change ( & p1) .unwrap ( ) ;
3780
let c_2 = repo.store ( ) .get_change ( & p2) .unwrap ( ) ;
3781
assert! ( c_1 .message.contains ( " part 1: b.txt" ) ) ;
3782
assert_eq! ( c_2 .change_id, id3) ;
3783
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
75167672 Andy McCall 1 month ago
3784
assert! (
3785
heads.contains ( & p1) & & heads.contains ( & p2) ,
3786
" independent parts are both heads"
3787
) ;
3788
let snap = repo
3789
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
3790
.unwrap ( )
3791
.materialize ( ) ;
23b217d7 Andy McCall 1 month ago ↶
3792
assert! ( snap.files.contains_key ( " b.txt" ) & & snap.files.contains_key ( " c.txt" ) ) ;
3793
let only1 = repo.load_state ( & [ p1] ) .unwrap ( ) .materialize ( ) ;
3794
assert! ( only1.files.contains_key ( " b.txt" ) & & ! only1.files.contains_key ( " c.txt" ) ) ;
3795
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
3796
assert! ( repo.split ( & p2, & [ " nope.txt" .into ( ) ] ) .is_err ( ) ) ;
3797
let _ = h2;
3798
}
28fc23d7 Andy McCall 1 month ago ↶
3799
3800
# [ test ]
3801
fn identity_lifecycle ( ) {
3802
hermetic ( ) ;
3803
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3804
let root = dir.path ( ) ;
3805
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
3806
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
3807
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
3808
.unwrap ( )
3809
.with_home ( root.join ( " home-a" ) ) ;
28fc23d7 Andy McCall 1 month ago ↶
3810
a.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3811
let ( key_a, _ ) = a
3812
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
3813
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3814
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
3815
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
3816
assert! ( a.policy_init ( ) .unwrap ( ) .applied) ;
3817
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
767ac6cd Andy McCall 19 days ago
3818
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3819
3820
75167672 Andy McCall 1 month ago
3821
let mut e = Repo:: init( & root.join ( " e" ) )
3822
.unwrap ( )
3823
.with_home ( root.join ( " home-e" ) ) ;
28fc23d7 Andy McCall 1 month ago ↶
3824
e.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3825
e.create_identity_with (
3826
" Eve" ,
3827
" [email protected] " ,
3828
IdentityScope:: User,
3829
Some ( Hlc:: now( ) .wall_ms - 1 ) ,
3830
)
3831
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3832
write ( & root.join ( " e" ) , " e.txt" , b " e\n " ) ;
3833
let he = e.commit ( " late" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3834
assert! (
3835
matches! ( e.verify_change ( & he) .unwrap ( ) , SignatureStatus:: Invalid( m) if m.contains ( " expired" ) )
3836
) ;
28fc23d7 Andy McCall 1 month ago ↶
3837
75167672 Andy McCall 1 month ago
3838
let r = e
3839
.renew_identity ( Some ( Hlc:: now( ) .wall_ms + 86_400_000 ) )
3840
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3841
assert_eq! ( e.identity ( & r.new) .unwrap ( ) .previous, Some ( r.old) ) ;
3842
write ( & root.join ( " e" ) , " e.txt" , b " e2\n " ) ;
3843
let he2 = e.commit ( " in time" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3844
assert! (
3845
matches! ( e.verify_change ( & he2) .unwrap ( ) , SignatureStatus:: Valid { key, .. } if key = = r.new)
3846
) ;
28fc23d7 Andy McCall 1 month ago ↶
3847
3848
3849
let r = a.rotate_identity ( ) .unwrap ( ) ;
3850
assert_eq! ( r.old, key_a) ;
3851
assert_eq! ( a.config.author_key, Some ( r.new) ) ;
3852
assert_eq! ( a.identity ( & r.new) .unwrap ( ) .previous, Some ( key_a) ) ;
3853
assert! ( r.policy.as_ref ( ) .unwrap ( ) .applied) ;
3854
let ( _ , sp) = a.current_policy ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3855
assert_eq! (
3856
( sp.policy.serial, sp.policy.roots.clone ( ) ) ,
3857
( 2 , vec! [ r.new] )
3858
) ;
3859
assert! (
3860
! sp.policy.revoked.contains ( & key_a) & & sp.policy.authorizes ( & r.new, " main" ) ,
3861
" superseded, not revoked"
3862
) ;
28fc23d7 Andy McCall 1 month ago ↶
3863
assert! ( sp.policy.succeeds ( & r.new, & key_a) & & ! sp.policy.succeeds ( & key_a, & r.new) ) ;
75167672 Andy McCall 1 month ago
3864
assert! (
3865
matches! ( a.verify_change ( & h1) .unwrap ( ) , SignatureStatus:: Valid { key, revoked: false , .. } if key = = key_a)
3866
) ;
28fc23d7 Andy McCall 1 month ago ↶
3867
write ( & root.join ( " a" ) , " a.txt" , b " a2\n " ) ;
3868
let h2 = a.commit ( " two" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3869
assert! (
3870
matches! ( a.verify_change ( & h2) .unwrap ( ) , SignatureStatus:: Valid { key, revoked: false , .. } if key = = r.new)
3871
) ;
28fc23d7 Andy McCall 1 month ago ↶
3872
3873
let res = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3874
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3875
assert! ( res.iter ( ) .all ( | x | x.ok ) , " {res:?}" ) ;
3876
assert_eq! ( origin.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 2 ) ;
3877
let key_a = r.new;
3878
3879
3880
75167672 Andy McCall 1 month ago
3881
let mut b = Repo:: clone( & url, & root.join ( " b" ) )
3882
.unwrap ( )
3883
.with_home ( root.join ( " home-b" ) ) ;
3884
let ( key_b, id_b) = b
3885
.create_identity ( " Bob" , " [email protected] " , IdentityScope:: User)
3886
.unwrap ( ) ;
7ba155f2 Andy McCall 1 month ago ↶
3887
let r = a.delegate_add ( id_b.clone ( ) , Scope:: ALL , None ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3888
assert_eq! ( r.old, key_a) ;
3889
let key_a2 = r.new;
75167672 Andy McCall 1 month ago
3890
assert_eq! (
7ba155f2 Andy McCall 1 month ago ↶
3891
a.identity ( & key_a2) .unwrap ( ) .delegates,
3892
vec! [ Delegate:: full( key_b) ]
3893
) ;
3894
assert_eq! (
75167672 Andy McCall 1 month ago
3895
a.identity ( & key_a2) .unwrap ( ) .ed25519_pk,
3896
a.identity ( & key_a) .unwrap ( ) .ed25519_pk
7ba155f2 Andy McCall 1 month ago ↶
3897
) ;
3898
assert! (
3899
a.delegate_add ( id_b.clone ( ) , Scope:: ALL , None ) .is_err ( ) ,
3900
" already a delegate"
75167672 Andy McCall 1 month ago
3901
) ;
28fc23d7 Andy McCall 1 month ago ↶
3902
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | x | x.ok ) ) ;
767ac6cd Andy McCall 19 days ago
3903
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3904
write ( & root.join ( " a" ) , " a.txt" , b " a3\n " ) ;
3905
let h3 = a.commit ( " three" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3906
assert! (
3907
matches! ( a.verify_change ( & h3) .unwrap ( ) , SignatureStatus:: Valid { key, .. } if key = = key_a2) ,
3908
" the same secret signs under the new KeyId"
3909
) ;
28fc23d7 Andy McCall 1 month ago ↶
3910
3911
a.set_user_config ( " core.sign" , " seal" ) .unwrap ( ) ;
3912
write ( & root.join ( " a" ) , " a.txt" , b " a4\n " ) ;
3913
let h4 = a.commit ( " four" , & [ ] ) .unwrap ( ) ;
3914
assert_eq! ( a.verify_change ( & h4) .unwrap ( ) , SignatureStatus:: Unsigned) ;
3915
let ( _ , sk_b) = b.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3916
a.store ( )
3917
.put_signature ( & h4, & sk_b.sign ( Domain:: Change, & key_b, & h4) )
3918
.unwrap ( ) ;
3919
assert! (
3920
matches! ( a.verify_change ( & h4) .unwrap ( ) , SignatureStatus:: Valid { key, .. } if key = = key_b)
3921
) ;
28fc23d7 Andy McCall 1 month ago ↶
3922
a.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
3923
3924
let r = a.delegate_remove ( & key_b) .unwrap ( ) ;
3925
assert! ( a.identity ( & r.new) .unwrap ( ) .delegates.is_empty ( ) ) ;
3926
assert! ( a.delegate_remove ( & key_b) .is_err ( ) ) ;
3927
let key_a3 = r.new;
3928
let res = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3929
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3930
assert! ( res.iter ( ) .all ( | x | x.ok ) , " {res:?}" ) ;
3931
3932
3933
3934
assert! ( a.policy_root_add ( id_b.clone ( ) ) .unwrap ( ) .applied) ;
3935
assert! ( a.policy_root_add ( id_b.clone ( ) ) .is_err ( ) ) ;
3936
assert! ( a.policy_set_threshold ( 3 ) .is_err ( ) ) ;
3937
assert! ( a.policy_set_threshold ( 2 ) .unwrap ( ) .applied) ;
3938
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | x | x.ok ) ) ;
767ac6cd Andy McCall 19 days ago
3939
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3940
b.pull ( " origin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3941
assert_eq! (
3942
b.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.root_threshold,
3943
2
3944
) ;
3945
let mut c = Repo:: init( & root.join ( " c" ) )
3946
.unwrap ( )
3947
.with_home ( root.join ( " home-c" ) ) ;
3948
let ( key_c, id_c) = c
3949
.create_identity ( " Cy" , " [email protected] " , IdentityScope:: User)
3950
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3951
let serial_before = a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial;
3952
let proposal = a.policy_grant ( id_c, None ) .unwrap ( ) ;
3953
assert! ( ! proposal.applied & & proposal.sigs.len ( ) = = 1 ) ;
3954
assert_eq! ( proposal.policy.serial, serial_before + 1 ) ;
75167672 Andy McCall 1 month ago
3955
assert_eq! (
3956
a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial,
3957
serial_before,
3958
" a proposal changes nothing yet"
3959
) ;
28fc23d7 Andy McCall 1 month ago ↶
3960
3961
let bytes = proposal.signed ( ) .encode ( ) ;
75167672 Andy McCall 1 month ago
3962
let signed = b
3963
.policy_sign ( SignedPolicy:: decode( & bytes) .unwrap ( ) )
3964
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3965
assert! ( signed.applied & & signed.sigs.len ( ) = = 2 ) ;
75167672 Andy McCall 1 month ago
3966
assert! ( b
3967
.current_policy ( )
3968
.unwrap ( )
3969
.unwrap ( )
3970
.1
3971
.policy
3972
.authorizes ( & key_c, " main" ) ) ;
3973
assert! (
3974
b.policy_sign ( signed.signed ( ) ) .is_err ( ) ,
3975
" a stale proposal is refused"
3976
) ;
28fc23d7 Andy McCall 1 month ago ↶
3977
75167672 Andy McCall 1 month ago
3978
assert! ( b
3979
.push ( " origin" , false )
3980
.unwrap ( )
3981
.iter ( )
3982
.any ( | x | x.name = = " policy" & & x.ok ) ) ;
767ac6cd Andy McCall 19 days ago
3983
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3984
a.pull ( " origin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3985
assert_eq! (
3986
a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial,
3987
serial_before + 1
3988
) ;
28fc23d7 Andy McCall 1 month ago ↶
3989
3990
assert! ( a.policy_revoke ( & key_a3) .is_err ( ) ) ;
75167672 Andy McCall 1 month ago
3991
let u = a
3992
.revoke_identity ( & key_c, " left the project" )
3993
.unwrap ( )
3994
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3995
assert! ( ! u.applied) ;
3996
assert! ( a.is_revoked ( & key_c) .unwrap ( ) ) ;
3997
assert! ( ! b.is_revoked ( & key_c) .unwrap ( ) ) ;
3998
let u = b.policy_sign ( u.signed ( ) ) .unwrap ( ) ;
3999
assert! ( u.applied & & u.policy.is_revoked ( & key_c) ) ;
4000
assert! ( b.is_revoked ( & key_c) .unwrap ( ) ) ;
4001
75167672 Andy McCall 1 month ago
4002
assert! (
4003
b.policy_revoke ( & key_a3) .is_err ( ) ,
4004
" would leave one root for a threshold of 2"
4005
) ;
28fc23d7 Andy McCall 1 month ago ↶
4006
4007
let key_c_own = c.config.author_key.unwrap ( ) ;
75167672 Andy McCall 1 month ago
4008
assert! ( c
4009
.revoke_identity ( & key_c_own, " lost laptop" )
4010
.unwrap ( )
4011
.is_none ( ) ) ;
28fc23d7 Andy McCall 1 month ago ↶
4012
assert! ( c.is_revoked ( & key_c_own) .unwrap ( ) ) ;
4013
assert_eq! ( key_c_own, key_c) ;
4014
}
4015
6e723b81 Andy McCall 1 month ago ↶
4016
# [ test ]
4017
fn renames_are_detected_and_tracked_as_moves ( ) {
4018
hermetic ( ) ;
4019
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4020
let root = dir.path ( ) ;
4021
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
4022
write (
4023
root,
4024
" src/old.rs" ,
4025
b " fn a() {}\n fn b() {}\n fn c() {}\n fn d() {}\n " ,
4026
) ;
6e723b81 Andy McCall 1 month ago ↶
4027
write ( root, " img.bin" , b " \x89 PNG\0 \0 \x01 \x02 " ) ;
4028
write ( root, " tiny.txt" , b " x\n " ) ;
4029
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4030
let node_old = repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) .files[ " src/old.rs" ] .node;
4031
let node_img = repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) .files[ " img.bin" ] .node;
4032
4033
4034
4035
fs:: remove_file( root.join ( " src/old.rs" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4036
write (
4037
root,
4038
" lib/new.rs" ,
4039
b " fn a() {}\n fn b() {}\n fn c() {}\n fn e() {}\n " ,
4040
) ;
6e723b81 Andy McCall 1 month ago ↶
4041
fs:: remove_file( root.join ( " img.bin" ) ) .unwrap ( ) ;
4042
write ( root, " assets/img.bin" , b " \x89 PNG\0 \0 \x01 \x02 " ) ;
4043
fs:: remove_file( root.join ( " tiny.txt" ) ) .unwrap ( ) ;
4044
write ( root, " other.txt" , b " completely different\n " ) ;
4045
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4046
let statuses: Vec< ( & str , ChangeStatus) > = snap
4047
.statuses
4048
.iter ( )
4049
.map ( | s | ( s.path.as_str ( ) , s.status) )
4050
.collect ( ) ;
6e723b81 Andy McCall 1 month ago ↶
4051
assert_eq! (
4052
statuses,
75167672 Andy McCall 1 month ago
4053
vec! [
4054
( " assets/img.bin" , ChangeStatus:: Renamed) ,
4055
( " lib/new.rs" , ChangeStatus:: Renamed) ,
4056
( " other.txt" , ChangeStatus:: Added) ,
4057
( " src" , ChangeStatus:: Removed) ,
4058
( " tiny.txt" , ChangeStatus:: Removed)
4059
]
6e723b81 Andy McCall 1 month ago ↶
4060
) ;
4061
let h2 = repo.commit ( " move things" , & [ ] ) .unwrap ( ) ;
4062
let c = repo.store ( ) .get_change ( & h2) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4063
let moves: Vec< _ > = c
4064
.tree_ops
4065
.iter ( )
4066
.filter ( | op | matches! ( op, TreeOp:: Move { .. } ) )
4067
.collect ( ) ;
6e723b81 Andy McCall 1 month ago ↶
4068
assert_eq! ( moves.len ( ) , 2 , " {:?}" , c.tree_ops) ;
75167672 Andy McCall 1 month ago
4069
assert! ( c.tree_ops.iter ( ) .any (
4070
| op | matches! ( op, TreeOp:: Move { node, name, .. } if * node = = node_old & & name = = " new.rs" )
4071
) ) ;
4072
assert! ( c.tree_ops.iter ( ) .any (
4073
| op | matches! ( op, TreeOp:: Move { node, name, .. } if * node = = node_img & & name = = " img.bin" )
4074
) ) ;
6e723b81 Andy McCall 1 month ago ↶
4075
let snap = repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
4076
assert_eq! (
4077
snap.files[ " lib/new.rs" ] .node, node_old,
4078
" the node survives the rename"
4079
) ;
4080
assert! (
4081
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 " )
4082
) ;
6e723b81 Andy McCall 1 month ago ↶
4083
assert! ( ! snap.files.contains_key ( " src/old.rs" ) & & ! snap.files.contains_key ( " src" ) ) ;
75167672 Andy McCall 1 month ago
4084
assert! ( render_change (
4085
& c,
4086
& repo.load_state ( & [ h1] ) .unwrap ( ) ,
4087
& repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( )
4088
)
4089
.contains ( " R " ) ) ;
6e723b81 Andy McCall 1 month ago ↶
4090
4091
let other = tempfile:: tempdir( ) .unwrap ( ) ;
4092
let clone = Repo:: clone( & root.to_string_lossy ( ) , other.path ( ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4093
assert_eq! (
4094
fs:: read( other.path ( ) .join ( " lib/new.rs" ) ) .unwrap ( ) ,
4095
b " fn a() {}\n fn b() {}\n fn c() {}\n fn e() {}\n "
4096
) ;
6e723b81 Andy McCall 1 month ago ↶
4097
assert! ( ! other.path ( ) .join ( " src" ) .exists ( ) ) ;
4098
assert! ( ! clone.snapshot ( " status" ) .unwrap ( ) .changed) ;
4099
4100
assert_eq! ( similarity ( b " a\n b\n " , b " a\n b\n " ) , 100 ) ;
4101
assert_eq! ( similarity ( b " a\n b\n c\n d\n " , b " a\n b\n x\n y\n " ) , 50 ) ;
4102
assert_eq! ( similarity ( b " hello\n " , b " world\n " ) , 0 ) ;
4103
assert_eq! ( similarity ( b " " , b " x\n " ) , 0 ) ;
4104
}
4105
30ea9c1e Andy McCall 1 month ago ↶
4106
# [ test ]
4107
fn git_fetch_and_push_round_trip ( ) {
4108
hermetic ( ) ;
75167672 Andy McCall 1 month ago
4109
if std:: process:: Command:: new( " git" )
4110
.arg ( " --version" )
4111
.output ( )
4112
.is_err ( )
4113
{
30ea9c1e Andy McCall 1 month ago ↶
4114
eprintln! ( " git not installed; skipping" ) ;
4115
return ;
4116
}
4117
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4118
let root = dir.path ( ) ;
4119
let git_in = | d : & Path, args : & [& str ]| -> String {
4120
let out = std:: process:: Command:: new( " git" )
4121
.arg ( " -C" )
4122
.arg ( d)
75167672 Andy McCall 1 month ago
4123
.args ( [
4124
" -c" ,
4125
" user.name=Grace" ,
4126
" -c" ,
4127
" [email protected] " ,
4128
" -c" ,
4129
" commit.gpgsign=false" ,
4130
] )
30ea9c1e Andy McCall 1 month ago ↶
4131
.args ( args)
4132
.output ( )
4133
.unwrap ( ) ;
75167672 Andy McCall 1 month ago
4134
assert! (
4135
out.status.success ( ) ,
4136
" git {args:?}: {}" ,
4137
String :: from_utf8_lossy( & out.stderr)
4138
) ;
30ea9c1e Andy McCall 1 month ago ↶
4139
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
4140
} ;
4141
4142
let remote = root.join ( " remote.git" ) ;
4143
fs:: create_dir_all( & remote) .unwrap ( ) ;
4144
git_in ( & remote, & [ " init" , " -q" , " --bare" , " -b" , " main" ] ) ;
4145
let work = root.join ( " work" ) ;
75167672 Andy McCall 1 month ago
4146
git_in (
4147
root,
4148
& [
4149
" clone" ,
4150
" -q" ,
4151
remote.to_str ( ) .unwrap ( ) ,
4152
work.to_str ( ) .unwrap ( ) ,
4153
] ,
4154
) ;
30ea9c1e Andy McCall 1 month ago ↶
4155
git_in ( & work, & [ " checkout" , " -q" , " -b" , " main" ] ) ;
4156
write ( & work, " README.md" , b " # Project\n " ) ;
4157
write ( & work, " src/lib.rs" , b " pub fn a() {}\n " ) ;
4158
git_in ( & work, & [ " add" , " ." ] ) ;
4159
git_in ( & work, & [ " commit" , " -q" , " -m" , " initial" ] ) ;
4160
write ( & work, " src/lib.rs" , b " pub fn a() {}\n pub fn b() {}\n " ) ;
4161
git_in ( & work, & [ " commit" , " -q" , " -am" , " second" ] ) ;
4162
git_in ( & work, & [ " push" , " -q" , " origin" , " main" ] ) ;
4163
let tip1 = git_in ( & work, & [ " rev-parse" , " HEAD" ] ) ;
4164
4165
4166
let o = root.join ( " oxy" ) ;
4167
let mut repo = Repo:: init( & o) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
4168
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
4169
.unwrap ( ) ;
30ea9c1e Andy McCall 1 month ago ↶
4170
let url = remote.to_string_lossy ( ) .to_string ( ) ;
4171
let r = repo.git_fetch ( & url, " main" ) .unwrap ( ) ;
4172
assert_eq! ( ( r.commits, r.heads) , ( 2 , 1 ) ) ;
75167672 Andy McCall 1 month ago
4173
assert_eq! (
4174
fs:: read( o.join ( " src/lib.rs" ) ) .unwrap ( ) ,
4175
b " pub fn a() {}\n pub fn b() {}\n "
4176
) ;
30ea9c1e Andy McCall 1 month ago ↶
4177
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
75167672 Andy McCall 1 month ago
4178
assert_eq! (
4179
repo.git_fetch ( & url, " main" ) .unwrap ( ) .commits,
4180
0 ,
4181
" nothing new"
4182
) ;
30ea9c1e Andy McCall 1 month ago ↶
4183
4184
75167672 Andy McCall 1 month ago
4185
write (
4186
& o,
4187
" src/lib.rs" ,
4188
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n " ,
4189
) ;
30ea9c1e Andy McCall 1 month ago ↶
4190
write ( & o, " NOTES.md" , b " from oxy\n " ) ;
4191
let h_oxy = repo.commit ( " add c and notes" , & [ ] ) .unwrap ( ) ;
4192
let r = repo.git_push ( & url, " main" ) .unwrap ( ) ;
4193
assert_eq! ( r.exported, 1 , " {r:?}" ) ;
4194
git_in ( & work, & [ " pull" , " -q" , " --ff-only" , " origin" , " main" ] ) ;
75167672 Andy McCall 1 month ago
4195
assert_eq! (
4196
git_in ( & work, & [ " log" , " --format=%s" , " main" ] ) ,
4197
" add c and notes\n second\n initial"
4198
) ;
30ea9c1e Andy McCall 1 month ago ↶
4199
assert_eq! ( git_in ( & work, & [ " rev-parse" , " HEAD~1" ] ) , tip1) ;
4200
assert_eq! ( fs:: read( work.join ( " NOTES.md" ) ) .unwrap ( ) , b " from oxy\n " ) ;
4201
assert_eq! ( git_in ( & work, & [ " log" , " -1" , " --format=%an" , " main" ] ) , " Ada" ) ;
75167672 Andy McCall 1 month ago
4202
assert! ( git_in ( & work, & [ " log" , " -1" , " --format=%B" , " main" ] )
4203
.contains ( & format! ( " Oxy-Hash: {} " , h_oxy.to_hex ( ) ) ) ) ;
4204
assert_eq! (
4205
repo.git_push ( & url, " main" ) .unwrap ( ) .exported,
4206
0 ,
4207
" already pushed"
4208
) ;
30ea9c1e Andy McCall 1 month ago ↶
4209
4210
4211
4212
write ( & work, " README.md" , b " # Project\n \n More.\n " ) ;
4213
git_in ( & work, & [ " commit" , " -q" , " -am" , " readme from git" ] ) ;
4214
git_in ( & work, & [ " push" , " -q" , " origin" , " main" ] ) ;
4215
let tip2 = git_in ( & work, & [ " rev-parse" , " HEAD" ] ) ;
75167672 Andy McCall 1 month ago
4216
assert! ( repo
4217
.git_push ( & url, " main" )
4218
.unwrap_err ( )
4219
.to_string ( )
4220
.contains ( " fetch" ) ) ;
30ea9c1e Andy McCall 1 month ago ↶
4221
let r = repo.git_fetch ( & url, " main" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4222
assert_eq! (
4223
( r.commits, r.heads) ,
4224
( 1 , 2 ) ,
4225
" the Git change and the oxy change commute"
4226
) ;
4227
assert_eq! (
4228
fs:: read( o.join ( " README.md" ) ) .unwrap ( ) ,
4229
b " # Project\n \n More.\n "
4230
) ;
30ea9c1e Andy McCall 1 month ago ↶
4231
assert_eq! ( fs:: read( o.join ( " NOTES.md" ) ) .unwrap ( ) , b " from oxy\n " ) ;
4232
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4233
4234
write ( & o, " NOTES.md" , b " from oxy\n again\n " ) ;
4235
repo.commit ( " notes again" , & [ ] ) .unwrap ( ) ;
4236
assert_eq! ( repo.git_push ( & url, " main" ) .unwrap ( ) .exported, 1 ) ;
4237
git_in ( & work, & [ " pull" , " -q" , " --ff-only" , " origin" , " main" ] ) ;
4238
assert_eq! ( git_in ( & work, & [ " rev-parse" , " HEAD~1" ] ) , tip2) ;
75167672 Andy McCall 1 month ago
4239
assert_eq! (
4240
fs:: read( work.join ( " NOTES.md" ) ) .unwrap ( ) ,
4241
b " from oxy\n again\n "
4242
) ;
4243
assert_eq! (
4244
git_in ( & work, & [ " log" , " --format=%s" , " main" ] )
4245
.lines ( )
4246
.count ( ) ,
4247
5
4248
) ;
30ea9c1e Andy McCall 1 month ago ↶
4249
}
4250
2b872d07 Andy McCall 1 month ago ↶
4251
4252
4253
# [ test ]
4254
# [ ignore ]
4255
fn bench_one_gibibyte_file ( ) {
4256
hermetic ( ) ;
4257
4258
let dir = match std:: env:: var_os( " OXY_BENCH_DIR" ) {
4259
Some ( d) => tempfile:: tempdir_in( d) .unwrap ( ) ,
4260
None => tempfile:: tempdir( ) .unwrap ( ) ,
4261
} ;
4262
let root = dir.path ( ) ;
75167672 Andy McCall 1 month ago
4263
let repo = Repo:: init( & root.join ( " r" ) )
4264
.unwrap ( )
4265
.with_home ( root.join ( " home" ) ) ;
2b872d07 Andy McCall 1 month ago ↶
4266
let size = 1 usize < < 30 ;
4267
let mut data = vec! [ 0 u8 ; size] ;
4268
let mut x = 0x9e3779b97f4a7c15 u64 ;
4269
for chunk in data.chunks_mut ( 8 ) {
4270
x ^ = x < < 13 ;
4271
x ^ = x > > 7 ;
4272
x ^ = x < < 17 ;
4273
let b = x.to_le_bytes ( ) ;
4274
chunk.copy_from_slice ( & b[ .. chunk.len ( ) ] ) ;
4275
}
4276
let t = std:: time:: Instant:: now( ) ;
4277
write ( & root.join ( " r" ) , " big.bin" , & data) ;
4278
eprintln! ( " write 1 GiB to disk: {:.2?}" , t.elapsed ( ) ) ;
4279
let t = std:: time:: Instant:: now( ) ;
4280
let h = repo.commit ( " big" , & [ ] ) .unwrap ( ) ;
4281
eprintln! ( " commit (chunk + hash + store): {:.2?}" , t.elapsed ( ) ) ;
4282
let t = std:: time:: Instant:: now( ) ;
4283
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4284
eprintln! ( " status on a clean 1 GiB working copy: {:.2?}" , t.elapsed ( ) ) ;
4285
let t = std:: time:: Instant:: now( ) ;
4286
let objects = repo.store ( ) .list ( ) .unwrap ( ) .len ( ) ;
4287
eprintln! ( " objects stored: {objects} ({:.2?} to list)" , t.elapsed ( ) ) ;
4288
let t = std:: time:: Instant:: now( ) ;
4289
let g = repo.gc ( true ) .unwrap ( ) ;
4290
eprintln! ( " gc (pack): {:.2?} {g:?}" , t.elapsed ( ) ) ;
4291
let t = std:: time:: Instant:: now( ) ;
4292
let clone = Repo:: clone( & root.join ( " r" ) .to_string_lossy ( ) , & root.join ( " c" ) ) .unwrap ( ) ;
4293
eprintln! ( " clone (fetch + checkout): {:.2?}" , t.elapsed ( ) ) ;
4294
let t = std:: time:: Instant:: now( ) ;
4295
let back = fs:: read( root.join ( " c/big.bin" ) ) .unwrap ( ) ;
4296
assert_eq! ( blake3:: hash( & back) , blake3:: hash( & data) ) ;
4297
eprintln! ( " read back and verify: {:.2?}" , t.elapsed ( ) ) ;
4298
assert_eq! ( clone.bookmark_heads ( ) .unwrap ( ) , vec! [ h] ) ;
4299
let store_bytes: u64 = walkdir_size ( & root.join ( " r/.oxy/objects" ) ) ;
75167672 Andy McCall 1 month ago
4300
eprintln! (
4301
" store size: {:.1} MiB for 1 GiB of data" ,
4302
store_bytes as f64 / ( 1024. 0 * 1024. 0 )
4303
) ;
2b872d07 Andy McCall 1 month ago ↶
4304
}
4305
4306
fn walkdir_size ( p : & Path) -> u64 {
4307
let mut total = 0 ;
4308
if let Ok ( rd) = fs:: read_dir( p) {
4309
for e in rd.flatten ( ) {
4310
let path = e.path ( ) ;
4311
if path.is_dir ( ) {
4312
total + = walkdir_size ( & path) ;
4313
} else if let Ok ( m) = fs:: metadata( & path) {
4314
total + = m.len ( ) ;
4315
}
4316
}
4317
}
4318
total
4319
}
4320
b7f102fc Andy McCall 1 month ago ↶
4321
4322
4323
# [ cfg ( unix ) ]
4324
# [ test ]
4325
fn stat_cache_skips_reading_unchanged_opaque_files ( ) {
4326
use std:: os:: unix:: fs:: PermissionsExt;
4327
hermetic ( ) ;
4328
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4329
let root = dir.path ( ) ;
4330
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4331
let mut big = vec! [ 0 u8 ; 300 * 1024 ] ;
4332
for ( i, b) in big.iter_mut ( ) .enumerate ( ) {
4333
* b = ( i as u32 ) .wrapping_mul ( 2654435761 ) .to_le_bytes ( ) [ 1 ] ;
4334
}
4335
big[ 0 ] = 0 ;
4336
write ( root, " big.bin" , & big) ;
4337
write ( root, " a.txt" , b " text\n " ) ;
4338
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4339
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4340
assert! (
4341
m.entries[ " big.bin" ] .blob.is_some ( ) ,
4342
" opaque files record their blob"
4343
) ;
b7f102fc Andy McCall 1 month ago ↶
4344
assert! ( m.entries[ " a.txt" ] .blob.is_none ( ) ) ;
4345
let blob = m.entries[ " big.bin" ] .blob.unwrap ( ) ;
4346
assert! ( repo.store ( ) .has ( & blob) ) ;
4347
4348
4349
4350
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
75167672 Andy McCall 1 month ago
4351
fs:: File:: options( )
4352
.write ( true )
4353
.open ( root.join ( " big.bin" ) )
4354
.unwrap ( )
4355
.set_modified ( old)
4356
.unwrap ( ) ;
b7f102fc Andy McCall 1 month ago ↶
4357
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4358
4359
fs:: set_permissions( root.join ( " big.bin" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
4360
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4361
assert! (
4362
! snap.changed & & snap.statuses.is_empty ( ) ,
4363
" {:?}" ,
4364
snap.statuses
4365
) ;
b7f102fc Andy McCall 1 month ago ↶
4366
fs:: set_permissions( root.join ( " big.bin" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
4367
4368
let mut big2 = big.clone ( ) ;
4369
big2[ 10 ] ^ = 0xff ;
4370
write ( root, " big.bin" , & big2) ;
4371
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4372
assert_eq! (
4373
snap.statuses
4374
.iter ( )
4375
.map ( | s | ( s.path.as_str ( ) , s.status) )
4376
.collect:: < Vec< _ > > ( ) ,
4377
vec! [ ( " big.bin" , ChangeStatus:: Modified) ]
4378
) ;
b7f102fc Andy McCall 1 month ago ↶
4379
let h2 = repo.commit ( " edit" , & [ ] ) .unwrap ( ) ;
4380
let files = repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
4381
assert_eq! (
4382
files[ " big.bin" ] .content,
4383
FileContent:: Opaque {
4384
blobs: vec! [ Repo:: blob_ref( & big2) ]
4385
}
4386
) ;
b7f102fc Andy McCall 1 month ago ↶
4387
4388
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
4389
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
4390
assert_eq! ( m.entries[ " big.bin" ] .blob, Some ( Repo:: blob_ref( & big) ) ) ;
4391
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4392
}
4393
50a046e4 Andy McCall 1 month ago ↶
4394
# [ test ]
4395
fn status_json_lists_conflicts_with_contributors ( ) {
4396
hermetic ( ) ;
4397
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4398
let root = dir.path ( ) ;
4399
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4400
write ( root, " a.txt" , b " one\n " ) ;
4401
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4402
write ( root, " a.txt" , b " one\n left\n " ) ;
4403
let ha = repo.commit ( " left" , & [ ] ) .unwrap ( ) ;
4404
repo.checkout ( & [ h1] , true , " checkout" ) .unwrap ( ) ;
4405
write ( root, " a.txt" , b " one\n right\n " ) ;
4406
let hb = repo.commit ( " right" , & [ ] ) .unwrap ( ) ;
4407
repo.checkout ( & [ ha, hb] , true , " checkout" ) .unwrap ( ) ;
4408
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4409
assert! ( snap.base_snapshot.is_conflicted ( ) ) ;
4410
let json = repo.status_json ( & snap) .unwrap ( ) ;
4411
let ida = repo.store ( ) .get_change ( & ha) .unwrap ( ) .change_id.to_hex ( ) ;
4412
let idb = repo.store ( ) .get_change ( & hb) .unwrap ( ) .change_id.to_hex ( ) ;
4413
assert! ( json.contains ( " \" conflicted\" :true" ) , " {json}" ) ;
75167672 Andy McCall 1 month ago
4414
assert! (
4415
json.contains ( " \" kind\" :\" text\" ,\" path\" :\" a.txt\" " ) ,
4416
" {json}"
4417
) ;
50a046e4 Andy McCall 1 month ago ↶
4418
assert! ( json.contains ( & ida) & & json.contains ( & idb) , " {json}" ) ;
4419
assert! ( json.contains ( " \" resolved\" :false" ) , " {json}" ) ;
4420
assert! ( json.starts_with ( ' {' ) & & json.ends_with ( ' }' ) ) ;
4421
4422
write ( root, " a.txt" , b " one\n left\n right\n " ) ;
4423
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4424
let json = repo.status_json ( & snap) .unwrap ( ) ;
4425
assert! ( json.contains ( " \" resolved\" :true" ) , " {json}" ) ;
4426
repo.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
4427
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4428
let json = repo.status_json ( & snap) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4429
assert! (
4430
json.contains ( " \" clean\" :true,\" conflicted\" :false,\" paths\" :[],\" conflicts\" :[]" ) ,
4431
" {json}"
4432
) ;
50a046e4 Andy McCall 1 month ago ↶
4433
}
4434
9ac1fbe2 Andy McCall 1 month ago ↶
4435
# [ test ]
4436
fn ignore_rules_do_not_untrack_files ( ) {
4437
hermetic ( ) ;
4438
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4439
let root = dir.path ( ) ;
4440
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4441
write ( root, " src.c" , b " int x;\n " ) ;
4442
write ( root, " build/out.o" , b " \0 obj" ) ;
4443
write ( root, " sub/keep.log" , b " keep\n " ) ;
4444
write ( root, " sub/junk.log" , b " junk\n " ) ;
4445
write ( root, " .oxyignore" , b " /build\n *.log\n !sub/keep.log\n " ) ;
4446
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4447
let added: Vec< & str > = snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect ( ) ;
75167672 Andy McCall 1 month ago
4448
assert_eq! (
4449
added,
4450
vec! [ " .oxyignore" , " src.c" , " sub/keep.log" ] ,
4451
" anchored, glob, and negated rules"
4452
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4453
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4454
4455
write ( root, " .oxyignore" , b " /build\n *.log\n !sub/keep.log\n src.c\n " ) ;
4456
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4457
assert_eq! (
4458
snap.statuses
4459
.iter ( )
4460
.map ( | s | s.path.as_str ( ) )
4461
.collect:: < Vec< _ > > ( ) ,
4462
vec! [ " .oxyignore" ]
4463
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4464
let h2 = repo.commit ( " ignore src.c" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4465
assert! ( repo
4466
.load_state ( & [ h2] )
4467
.unwrap ( )
4468
.materialize ( )
4469
.files
4470
.contains_key ( " src.c" ) ) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4471
4472
assert_eq! ( repo.quick_dirty ( ) .unwrap ( ) , Some ( false ) ) ;
4473
write ( root, " src.c" , b " int x, y;\n " ) ;
4474
assert_eq! ( repo.quick_dirty ( ) .unwrap ( ) , Some ( true ) ) ;
4475
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4476
assert_eq! (
4477
snap.statuses
4478
.iter ( )
4479
.map ( | s | ( s.path.as_str ( ) , s.status) )
4480
.collect:: < Vec< _ > > ( ) ,
4481
vec! [ ( " src.c" , ChangeStatus:: Modified) ]
4482
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4483
let h3 = repo.commit ( " edit src.c" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4484
assert! (
4485
matches! ( & repo.load_state ( & [ h3] ) .unwrap ( ) .materialize ( ) .files[ " src.c" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " int x, y;\n " )
4486
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4487
4488
fs:: remove_file( root.join ( " src.c" ) ) .unwrap ( ) ;
4489
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4490
assert_eq! (
4491
snap.statuses
4492
.iter ( )
4493
.map ( | s | ( s.path.as_str ( ) , s.status) )
4494
.collect:: < Vec< _ > > ( ) ,
4495
vec! [ ( " src.c" , ChangeStatus:: Removed) ]
4496
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4497
repo.checkout ( & [ h3] , true , " checkout" ) .unwrap ( ) ;
4498
assert_eq! ( fs:: read( root.join ( " src.c" ) ) .unwrap ( ) , b " int x, y;\n " ) ;
4499
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4500
let _ = h1;
4501
}
4502
b837d2b5 Andy McCall 1 month ago ↶
4503
# [ test ]
4504
fn split_works_on_non_heads_by_node ( ) {
4505
hermetic ( ) ;
4506
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4507
let root = dir.path ( ) ;
4508
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4509
write ( root, " a.txt" , b " one\n " ) ;
4510
write ( root, " junk/x.lock" , b " lock\n " ) ;
4511
let h1 = repo.commit ( " work plus junk" , & [ ] ) .unwrap ( ) ;
4512
write ( root, " a.txt" , b " one\n two\n " ) ;
4513
let h2 = repo.commit ( " more work" , & [ ] ) .unwrap ( ) ;
4514
assert_eq! ( repo.store ( ) .get_change ( & h2) .unwrap ( ) .deps, vec! [ h1] ) ;
4515
let id1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) .change_id;
4516
4517
let ( hj, h1b) = repo.split ( & h1, & [ " junk" .into ( ) ] ) .unwrap ( ) ;
4518
assert_ne! ( h1b, h1) ;
75167672 Andy McCall 1 month ago
4519
assert_eq! (
4520
repo.store ( ) .get_change ( & h1b) .unwrap ( ) .change_id,
4521
id1,
4522
" the rest keeps the ChangeId"
4523
) ;
b837d2b5 Andy McCall 1 month ago ↶
4524
let junk = repo.store ( ) .get_change ( & hj) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4525
assert! ( junk.tree_ops.iter ( ) .all (
4526
| op | matches! ( op, TreeOp:: AddNode { name, .. } if name = = " junk" | | name = = " x.lock" )
4527
) ) ;
b837d2b5 Andy McCall 1 month ago ↶
4528
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4529
assert! (
4530
heads.contains ( & hj) ,
4531
" the split-off part is a head of its own"
4532
) ;
b837d2b5 Andy McCall 1 month ago ↶
4533
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
4534
assert! (
4535
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n " ) ,
4536
" the dependent was rewritten onto the new hash"
4537
) ;
b837d2b5 Andy McCall 1 month ago ↶
4538
assert! ( snap.files.contains_key ( " junk/x.lock" ) ) ;
75167672 Andy McCall 1 month ago
4539
let h2b = heads
4540
.iter ( )
4541
.find ( | h | repo.store ( ) .get_change ( h) .unwrap ( ) .message = = " more work" )
4542
.copied ( )
4543
.unwrap ( ) ;
b837d2b5 Andy McCall 1 month ago ↶
4544
assert_eq! ( repo.store ( ) .get_change ( & h2b) .unwrap ( ) .deps, vec! [ h1b] ) ;
4545
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4546
4547
repo.abandon ( & hj) .unwrap ( ) ;
4548
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
4549
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
4550
assert! ( ! snap.files.contains_key ( " junk/x.lock" ) & & ! snap.files.contains_key ( " junk" ) ) ;
4551
assert! ( snap.files.contains_key ( " a.txt" ) ) ;
4552
4553
write ( root, " junk/y.lock" , b " y\n " ) ;
4554
let hb = repo.commit ( " junk base" , & [ ] ) .unwrap ( ) ;
4555
write ( root, " junk/y.lock" , b " yy\n " ) ;
4556
write ( root, " b.txt" , b " b\n " ) ;
4557
repo.commit ( " touches junk" , & [ ] ) .unwrap ( ) ;
4558
let err = repo.split ( & hb, & [ " junk" .into ( ) ] ) .unwrap_err ( ) .to_string ( ) ;
4559
assert! ( err.contains ( " depends on the split-off paths" ) , " {err}" ) ;
4560
}
4561
7b9809e6 Andy McCall 1 month ago ↶
4562
# [ test ]
4563
fn bookmarks_switch_merge_and_push ( ) {
4564
hermetic ( ) ;
4565
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4566
let root = dir.path ( ) ;
4567
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
4568
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
4569
let mut repo = Repo:: clone( & url, & root.join ( " r" ) )
4570
.unwrap ( )
4571
.with_home ( root.join ( " home" ) ) ;
7b9809e6 Andy McCall 1 month ago ↶
4572
let r = root.join ( " r" ) ;
4573
write ( & r, " a.txt" , b " a\n " ) ;
4574
let h_a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
4575
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
4576
origin.refresh ( ) .unwrap ( ) ;
7b9809e6 Andy McCall 1 month ago ↶
4577
4578
4579
assert! ( repo.bookmark_create ( " bad name.." , None ) .is_err ( ) ) ;
4580
assert! ( repo.bookmark_create ( " -x" , None ) .is_err ( ) ) ;
4581
assert_eq! ( repo.bookmark_create ( " feature" , None ) .unwrap ( ) , vec! [ h_a] ) ;
4582
assert! ( repo.bookmark_create ( " feature" , None ) .is_err ( ) , " exists" ) ;
4583
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
4584
assert_eq! ( repo.config.bookmark, " feature" ) ;
4585
write ( & r, " a.txt" , b " a\n b\n " ) ;
4586
write ( & r, " b.txt" , b " b\n " ) ;
4587
let h_b = repo.commit ( " b on feature" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4588
assert_eq! (
4589
repo.bookmark_heads ( ) .unwrap ( ) ,
4590
vec! [ h_b] ,
4591
" b edits a.txt, so it depends on a"
4592
) ;
4593
assert_eq! (
4594
repo.resolve_heads ( " main" ) .unwrap ( ) ,
4595
vec! [ h_a] ,
4596
" main is untouched"
4597
) ;
4598
assert_eq! (
4599
repo.resolve_change ( " feature" ) .unwrap ( ) ,
4600
h_b,
4601
" a one-head bookmark names its change"
4602
) ;
7b9809e6 Andy McCall 1 month ago ↶
4603
4604
let reopened = Repo:: open( & r) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4605
assert_eq! ( reopened.config.bookmark, " feature" ) ;
4606
4607
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
4608
assert! ( ! r.join ( " b.txt" ) .exists ( ) ) ;
4609
assert_eq! ( fs:: read( r.join ( " a.txt" ) ) .unwrap ( ) , b " a\n " ) ;
4610
write ( & r, " c.txt" , b " c\n " ) ;
4611
let h_c = repo.commit ( " c on main" , & [ ] ) .unwrap ( ) ;
4612
let list = repo.bookmarks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4613
assert_eq! (
4614
list.iter ( )
4615
.map ( | b | ( b.name.as_str ( ) , b.current) )
4616
.collect:: < Vec< _ > > ( ) ,
4617
vec! [ ( " feature" , false ) , ( " main" , true ) ]
4618
) ;
4619
assert_eq! (
4620
list.iter ( ) .find ( | b | b.name = = " main" ) .unwrap ( ) .ahead,
4621
Some ( 1 ) ,
4622
" c is not on origin yet"
4623
) ;
7b9809e6 Andy McCall 1 month ago ↶
4624
4625
let m = repo.merge ( " feature" ) .unwrap ( ) ;
4626
assert! ( m.changed & & m.checked_out) ;
4627
let mut want = vec! [ h_b, h_c] ;
4628
want.sort ( ) ;
4629
assert_eq! ( m.heads, want) ;
4630
assert_eq! ( fs:: read( r.join ( " b.txt" ) ) .unwrap ( ) , b " b\n " ) ;
4631
assert! ( ! repo.merge ( " feature" ) .unwrap ( ) .changed, " already merged" ) ;
4632
assert! ( repo.merge ( " main" ) .is_err ( ) ) ;
4633
4634
assert! ( repo.push_bookmark ( " origin" , " feature" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
4635
origin.refresh ( ) .unwrap ( ) ;
7b9809e6 Andy McCall 1 month ago ↶
4636
assert_eq! ( origin.resolve_heads ( " feature" ) .unwrap ( ) , vec! [ h_b] ) ;
4637
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
4638
origin.refresh ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4639
assert_eq! (
4640
repo.bookmarks ( )
4641
.unwrap ( )
4642
.iter ( )
4643
.find ( | b | b.name = = " main" )
4644
.unwrap ( )
4645
.ahead,
4646
Some ( 0 )
4647
) ;
7b9809e6 Andy McCall 1 month ago ↶
4648
4649
let ws = repo.workspace_add ( & root.join ( " ws" ) , None ) .unwrap ( ) ;
4650
assert_eq! ( ws.config.bookmark, " main" ) ;
4651
let mut ws = ws;
4652
ws.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4653
assert_eq! (
4654
Repo:: open( & r) .unwrap ( ) .config.bookmark,
4655
" main" ,
4656
" the first workspace is unaffected"
4657
) ;
4658
assert! (
4659
repo.bookmark_delete ( " feature" ) .is_err ( ) ,
4660
" current bookmark of another workspace"
4661
) ;
7b9809e6 Andy McCall 1 month ago ↶
4662
ws.bookmark_switch ( " main" , false ) .unwrap ( ) ;
4663
4664
assert_eq! ( repo.bookmark_set ( " feature" , & [ h_a] ) .unwrap ( ) , vec! [ h_a] ) ;
4665
assert! ( repo.bookmark_delete ( " main" ) .is_err ( ) , " current here" ) ;
4666
repo.bookmark_delete ( " feature" ) .unwrap ( ) ;
4667
assert! ( repo.resolve_heads ( " feature" ) .is_err ( ) ) ;
4668
assert! ( repo.bookmark_set ( " feature" , & [ h_a] ) .is_err ( ) ) ;
75167672 Andy McCall 1 month ago
4669
assert! (
4670
repo.checkout ( & [ h_a] , false , " checkout" ) .is_ok ( ) ,
4671
" changes of a deleted bookmark remain"
4672
) ;
7b9809e6 Andy McCall 1 month ago ↶
4673
}
4674
4dee277c Andy McCall 1 month ago ↶
4675
# [ test ]
4676
fn oxygen_subrepos_pin_follow_and_update ( ) {
4677
hermetic ( ) ;
4678
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4679
let root = dir.path ( ) ;
4680
4681
let lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
4682
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
4683
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
4684
.unwrap ( )
4685
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4686
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
4687
let l1 = lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
4688
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
4689
let _ = lib_origin;
4690
4691
4692
let app_origin = Repo:: init( & root.join ( " app-origin" ) ) .unwrap ( ) ;
4693
let app_url = root.join ( " app-origin" ) .to_string_lossy ( ) .to_string ( ) ;
4694
let _ = app_origin;
4695
let app_dir = root.join ( " app" ) ;
75167672 Andy McCall 1 month ago
4696
let app = Repo:: clone( & app_url, & app_dir)
4697
.unwrap ( )
4698
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4699
write ( & app_dir, " main.txt" , b " app\n " ) ;
4700
let pin = app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
4701
assert! ( matches! ( & pin, crate :: subrepo:: Pin:: Oxy { heads, .. } if heads = = & vec! [ l1] ) ) ;
4702
assert! ( app_dir.join ( " .oxymodules" ) .exists ( ) & & app_dir.join ( " libs/lib/.oxy" ) .is_dir ( ) ) ;
4703
let snap = app.snapshot ( " status" ) .unwrap ( ) ;
4704
let paths: Vec< & str > = snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect ( ) ;
75167672 Andy McCall 1 month ago
4705
assert_eq! (
4706
paths,
4707
vec! [ " .oxymodules" , " libs/lib" , " main.txt" ] ,
4708
" the nested files are not walked"
4709
) ;
4dee277c Andy McCall 1 month ago ↶
4710
let a1 = app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
4711
let files = app.load_state ( & [ a1] ) .unwrap ( ) .materialize ( ) .files;
4712
assert_eq! ( files[ " libs/lib" ] .kind, NodeKind:: Subrepo) ;
4713
assert! ( ! files.contains_key ( " libs/lib/lib.txt" ) ) ;
4714
assert! ( ! app.snapshot ( " status" ) .unwrap ( ) .changed) ;
4715
let subs = app.subrepos ( ) .unwrap ( ) ;
4716
assert_eq! ( subs.len ( ) , 1 ) ;
4717
assert_eq! ( subs[ 0 ] .actual.as_ref ( ) , Some ( & subs[ 0 ] .pin) ) ;
4718
4719
75167672 Andy McCall 1 month ago
4720
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) )
4721
.unwrap ( )
4722
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4723
write ( & app_dir.join ( " libs/lib" ) , " lib.txt" , b " v2\n " ) ;
4724
let l2 = nested.commit ( " lib v2" , & [ ] ) .unwrap ( ) ;
4725
assert! ( nested.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
4726
let snap = app.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4727
assert_eq! (
4728
snap.statuses
4729
.iter ( )
4730
.map ( | s | ( s.path.as_str ( ) , s.status) )
4731
.collect:: < Vec< _ > > ( ) ,
4732
vec! [ ( " libs/lib" , ChangeStatus:: Modified) ]
4733
) ;
4dee277c Andy McCall 1 month ago ↶
4734
let a2 = app.commit ( " bump lib" , & [ ] ) .unwrap ( ) ;
1a3b6203 Andy McCall 1 month ago ↶
4735
4736
4dee277c Andy McCall 1 month ago ↶
4737
let r = app.checkout ( & [ a1] , false , " checkout" ) .unwrap ( ) ;
1a3b6203 Andy McCall 1 month ago ↶
4738
assert! (
4739
r.notes.len ( ) = = 1 & & r.notes[ 0 ] .contains ( " keeps 1 newer change" ) ,
4740
" {:?}" ,
4741
r.notes
4742
) ;
4dee277c Andy McCall 1 month ago ↶
4743
assert_eq! ( fs:: read( app_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v1\n " ) ;
1a3b6203 Andy McCall 1 month ago ↶
4744
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) ) .unwrap ( ) ;
4745
assert_eq! ( nested.bookmark_heads ( ) .unwrap ( ) , vec! [ l2] ) ;
4746
assert_eq! ( nested.working_copy ( ) .unwrap ( ) .1. deps, vec! [ l1] ) ;
4dee277c Andy McCall 1 month ago ↶
4747
assert! ( ! app.snapshot ( " status" ) .unwrap ( ) .changed) ;
4748
4749
write ( & app_dir.join ( " libs/lib" ) , " scratch.txt" , b " wip\n " ) ;
4750
let r = app.checkout ( & [ a2] , false , " checkout" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4751
assert! (
4752
r.notes.iter ( ) .any ( | n | n.contains ( " uncommitted" ) ) ,
4753
" {:?}" ,
4754
r.notes
4755
) ;
4dee277c Andy McCall 1 month ago ↶
4756
assert_eq! ( fs:: read( app_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v1\n " ) ;
4757
fs:: remove_file( app_dir.join ( " libs/lib/scratch.txt" ) ) .unwrap ( ) ;
4758
let r = app.submodule_update ( false , & [ ] ) .unwrap ( ) ;
4759
assert_eq! ( r, vec! [ ( " libs/lib" .to_string ( ) , SubrepoSync:: Updated) ] ) ;
4760
assert_eq! ( fs:: read( app_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v2\n " ) ;
4761
let _ = l2;
4762
4763
4764
assert! ( app.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
4765
let clone_dir = root.join ( " app-clone" ) ;
75167672 Andy McCall 1 month ago
4766
let clone = Repo:: clone( & app_url, & clone_dir)
4767
.unwrap ( )
4768
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4769
assert! ( ! clone_dir.join ( " libs/lib/.oxy" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
4770
assert_eq! (
4771
clone.submodule_update ( false , & [ ] ) .unwrap ( ) ,
4772
vec! [ ( " libs/lib" .to_string ( ) , SubrepoSync:: Missing) ]
4773
) ;
4774
assert_eq! (
4775
clone.submodule_update ( true , & [ ] ) .unwrap ( ) ,
4776
vec! [ ( " libs/lib" .to_string ( ) , SubrepoSync:: Updated) ]
4777
) ;
4778
assert_eq! (
4779
fs:: read( clone_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) ,
4780
b " v2\n "
4781
) ;
4dee277c Andy McCall 1 month ago ↶
4782
assert! ( ! clone.snapshot ( " status" ) .unwrap ( ) .changed) ;
4783
4784
clone.submodule_remove ( " libs/lib" ) .unwrap ( ) ;
4785
let snap = clone.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4786
assert! (
4787
snap.statuses
4788
.iter ( )
4789
.any ( | s | s.path = = " libs/lib" & & s.status = = ChangeStatus:: Removed ) ,
4790
" {:?}" ,
4791
snap.statuses
4792
) ;
4dee277c Andy McCall 1 month ago ↶
4793
let c = clone.commit ( " drop lib" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4794
assert! ( ! clone
4795
.load_state ( & [ c] )
4796
.unwrap ( )
4797
.materialize ( )
4798
.files
4799
.contains_key ( " libs/lib" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4800
assert! ( clone_dir.join ( " libs/lib/lib.txt" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
4801
assert! (
4802
! clone.snapshot ( " status" ) .unwrap ( ) .changed,
4803
" an unregistered nested repository is ignored"
4804
) ;
4dee277c Andy McCall 1 month ago ↶
4805
}
4806
4807
# [ test ]
4808
fn git_submodules_import_and_export_as_gitlinks ( ) {
4809
hermetic ( ) ;
75167672 Andy McCall 1 month ago
4810
if std:: process:: Command:: new( " git" )
4811
.arg ( " --version" )
4812
.output ( )
4813
.is_err ( )
4814
{
4dee277c Andy McCall 1 month ago ↶
4815
eprintln! ( " git not installed; skipping" ) ;
4816
return ;
4817
}
4818
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4819
let root = dir.path ( ) ;
4820
let git_in = | d : & Path, args : & [& str ]| -> String {
4821
let out = std:: process:: Command:: new( " git" )
4822
.arg ( " -C" )
4823
.arg ( d)
75167672 Andy McCall 1 month ago
4824
.args ( [
4825
" -c" ,
4826
" user.name=Grace" ,
4827
" -c" ,
4828
" [email protected] " ,
4829
" -c" ,
4830
" commit.gpgsign=false" ,
4831
" -c" ,
4832
" protocol.file.allow=always" ,
4833
] )
4dee277c Andy McCall 1 month ago ↶
4834
.args ( args)
4835
.output ( )
4836
.unwrap ( ) ;
75167672 Andy McCall 1 month ago
4837
assert! (
4838
out.status.success ( ) ,
4839
" git {args:?}: {}" ,
4840
String :: from_utf8_lossy( & out.stderr)
4841
) ;
4dee277c Andy McCall 1 month ago ↶
4842
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
4843
} ;
4844
let sub = root.join ( " sub" ) ;
4845
fs:: create_dir_all( & sub) .unwrap ( ) ;
4846
git_in ( & sub, & [ " init" , " -q" , " -b" , " main" ] ) ;
4847
write ( & sub, " s.txt" , b " sub\n " ) ;
4848
git_in ( & sub, & [ " add" , " ." ] ) ;
4849
git_in ( & sub, & [ " commit" , " -q" , " -m" , " sub initial" ] ) ;
4850
let sub_sha = git_in ( & sub, & [ " rev-parse" , " HEAD" ] ) ;
4851
let g = root.join ( " main.git" ) ;
4852
fs:: create_dir_all( & g) .unwrap ( ) ;
4853
git_in ( & g, & [ " init" , " -q" , " -b" , " main" ] ) ;
4854
write ( & g, " README.md" , b " # app\n " ) ;
4855
git_in ( & g, & [ " add" , " ." ] ) ;
4856
git_in ( & g, & [ " commit" , " -q" , " -m" , " initial" ] ) ;
75167672 Andy McCall 1 month ago
4857
git_in (
4858
& g,
4859
& [
4860
" submodule" ,
4861
" add" ,
4862
" -q" ,
4863
sub.to_str ( ) .unwrap ( ) ,
4864
" vendor/sub" ,
4865
] ,
4866
) ;
4dee277c Andy McCall 1 month ago ↶
4867
git_in ( & g, & [ " commit" , " -q" , " -m" , " add submodule" ] ) ;
4868
4869
let dest = root.join ( " oxy" ) ;
4870
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4871
let rep = repo.import_git ( & g, " main" ) .unwrap ( ) ;
4872
assert_eq! ( ( rep.commits, rep.submodules) , ( 2 , 1 ) ) ;
75167672 Andy McCall 1 month ago
4873
let files = repo
4874
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
4875
.unwrap ( )
4876
.materialize ( )
4877
.files;
4dee277c Andy McCall 1 month ago ↶
4878
assert_eq! ( files[ " vendor/sub" ] .kind, NodeKind:: Subrepo) ;
75167672 Andy McCall 1 month ago
4879
let FileContent:: Opaque { blobs } = & files[ " vendor/sub" ] .content else {
4880
panic! ( )
4881
} ;
4882
assert_eq! (
4883
crate :: subrepo:: Pin:: decode( & repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) ) .unwrap ( ) ,
4884
crate :: subrepo:: Pin:: Git {
4885
sha: sub_sha.clone ( ) ,
4886
branch: " HEAD" .into ( )
4887
}
4888
) ;
4889
let modules =
4890
crate :: subrepo:: Modules:: parse( & fs:: read_to_string( dest.join ( " .oxymodules" ) ) .unwrap ( ) ) ;
4dee277c Andy McCall 1 month ago ↶
4891
assert_eq! ( modules.entries[ " vendor/sub" ] .url, sub.to_string_lossy ( ) ) ;
4892
assert! ( modules.entries[ " vendor/sub" ] .git) ;
75167672 Andy McCall 1 month ago
4893
assert! (
4894
files.contains_key ( " .gitmodules" ) ,
4895
" the Git file is kept for round trips"
4896
) ;
4897
assert! (
4898
! dest.join ( " vendor/sub/.git" ) .exists ( ) ,
4899
" not cloned until update --init"
4900
) ;
4dee277c Andy McCall 1 month ago ↶
4901
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4902
75167672 Andy McCall 1 month ago
4903
assert_eq! (
4904
repo.submodule_update ( true , & [ ] ) .unwrap ( ) ,
4905
vec! [ ( " vendor/sub" .to_string ( ) , SubrepoSync:: Updated) ]
4906
) ;
4907
assert_eq! (
4908
git_in ( & dest.join ( " vendor/sub" ) , & [ " rev-parse" , " HEAD" ] ) ,
4909
sub_sha
4910
) ;
4dee277c Andy McCall 1 month ago ↶
4911
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4912
4913
let mirror = root.join ( " mirror.git" ) ;
4914
let rep = repo.git_export ( & mirror) .unwrap ( ) ;
4915
assert_eq! ( rep.skipped_subrepos, 0 ) ;
4916
let ls = git_in ( & mirror, & [ " ls-tree" , " main" , " vendor/sub" ] ) ;
75167672 Andy McCall 1 month ago
4917
assert! (
4918
ls.starts_with ( " 160000 commit " ) & & ls.contains ( & sub_sha) ,
4919
" {ls}"
4920
) ;
4921
assert_eq! (
4922
git_in ( & mirror, & [ " ls-tree" , " --name-only" , " main" ] ) ,
4923
" .gitmodules\n README.md\n vendor"
4924
) ;
4dee277c Andy McCall 1 month ago ↶
4925
let orig_tree = git_in ( & g, & [ " rev-parse" , " main^{tree}" ] ) ;
75167672 Andy McCall 1 month ago
4926
assert_eq! (
4927
git_in ( & mirror, & [ " rev-parse" , " main^{tree}" ] ) ,
4928
orig_tree,
4929
" tree round trip"
4930
) ;
4dee277c Andy McCall 1 month ago ↶
4931
}
4932
957fee30 Andy McCall 1 month ago ↶
4933
# [ test ]
4934
fn partial_commits_by_path_and_by_hunk ( ) {
4935
hermetic ( ) ;
4936
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4937
let root = dir.path ( ) ;
4938
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4939
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
4940
write ( root, " b.txt" , b " b\n " ) ;
4941
let h0 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4942
write ( root, " a.txt" , b " 0\n 1\n 2\n 3\n 4\n " ) ;
4943
write ( root, " b.txt" , b " b\n bb\n " ) ;
4944
write ( root, " c.txt" , b " c\n " ) ;
4945
75167672 Andy McCall 1 month ago
4946
let h1 = repo
4947
.commit_selection ( " a only" , & [ ] , & Selection:: Paths( vec! [ " a.txt" .into ( ) ] ) )
4948
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
4949
let c1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4950
assert! (
4951
c1.tree_ops.is_empty ( ) & & c1.edits.iter ( ) .all ( | e | e.node ( ) = = c1.edits[ 0 ] .node ( ) ) ,
4952
" {:?}" ,
4953
c1.tree_ops
4954
) ;
957fee30 Andy McCall 1 month ago ↶
4955
assert_eq! ( c1.deps, vec! [ h0] ) ;
4956
let files = repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
4957
assert! (
4958
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 0\n 1\n 2\n 3\n 4\n " )
4959
) ;
957fee30 Andy McCall 1 month ago ↶
4960
assert! ( matches! ( & files[ " b.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " b\n " ) ) ;
4961
assert! ( ! files.contains_key ( " c.txt" ) ) ;
4962
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4963
assert_eq! (
4964
snap.statuses
4965
.iter ( )
4966
.map ( | s | ( s.path.as_str ( ) , s.status) )
4967
.collect:: < Vec< _ > > ( ) ,
4968
vec! [
4969
( " b.txt" , ChangeStatus:: Modified) ,
4970
( " c.txt" , ChangeStatus:: Added)
4971
]
4972
) ;
4973
assert_eq! (
4974
fs:: read( root.join ( " b.txt" ) ) .unwrap ( ) ,
4975
b " b\n bb\n " ,
4976
" disk untouched"
4977
) ;
957fee30 Andy McCall 1 month ago ↶
4978
4979
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4980
let b_hunk = hunks
4981
.iter ( )
4982
.find ( | h | h.path = = " b.txt" & & h.text.contains ( " +bb" ) )
4983
.unwrap ( )
4984
.index;
4985
let h2 = repo
4986
.commit_selection ( " b only" , & [ ] , & Selection:: Hunks( vec! [ b_hunk] ) )
4987
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
4988
let files = repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
4989
assert! (
4990
matches! ( & files[ " b.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " b\n bb\n " )
4991
) ;
957fee30 Andy McCall 1 month ago ↶
4992
assert! ( ! files.contains_key ( " c.txt" ) ) ;
4993
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4994
assert_eq! (
4995
snap.statuses
4996
.iter ( )
4997
.map ( | s | s.path.as_str ( ) )
4998
.collect:: < Vec< _ > > ( ) ,
4999
vec! [ " c.txt" ]
5000
) ;
957fee30 Andy McCall 1 month ago ↶
5001
5002
5003
write ( root, " d/e.txt" , b " e\n " ) ;
5004
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5005
let e_insert = hunks
5006
.iter ( )
5007
.find ( | h | h.path = = " d/e.txt" & & h.text.contains ( " +e" ) )
5008
.unwrap ( )
5009
.index;
5010
let h3 = repo
5011
.commit_selection ( " e only" , & [ ] , & Selection:: Hunks( vec! [ e_insert] ) )
5012
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5013
let c3 = repo.store ( ) .get_change ( & h3) .unwrap ( ) ;
5014
assert_eq! ( c3.tree_ops.len ( ) , 2 , " {:?}" , c3.tree_ops) ;
5015
let files = repo.load_state ( & [ h3] ) .unwrap ( ) .materialize ( ) .files;
5016
assert! ( files.contains_key ( " d/e.txt" ) & & ! files.contains_key ( " c.txt" ) ) ;
d8c086ca Andy McCall 1 month ago ↶
5017
5018
write ( root, " f/g.txt" , b " g\n " ) ;
5019
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5020
let g_add = hunks
5021
.iter ( )
5022
.find ( | h | h.path = = " f/g.txt" & & h.text.starts_with ( " A " ) )
5023
.unwrap ( )
5024
.index;
5025
let h4 = repo
5026
.commit_selection ( " g by addition" , & [ ] , & Selection:: Hunks( vec! [ g_add] ) )
5027
.unwrap ( ) ;
d8c086ca Andy McCall 1 month ago ↶
5028
let files = repo.load_state ( & [ h4] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
5029
assert! (
5030
matches! ( & files[ " f/g.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " g\n " )
5031
) ;
5032
assert! ( ! repo
5033
.snapshot ( " status" )
5034
.unwrap ( )
5035
.statuses
5036
.iter ( )
5037
.any ( | s | s.path = = " f/g.txt" ) ) ;
957fee30 Andy McCall 1 month ago ↶
5038
5039
repo.commit ( " the rest" , & [ ] ) .unwrap ( ) ;
5040
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
75167672 Andy McCall 1 month ago
5041
let files = repo
5042
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
5043
.unwrap ( )
5044
.materialize ( )
5045
.files;
957fee30 Andy McCall 1 month ago ↶
5046
assert! ( files.contains_key ( " c.txt" ) ) ;
5047
}
5048
5049
# [ test ]
5050
fn blame_attributes_lines_to_introducing_changes_across_renames ( ) {
5051
hermetic ( ) ;
5052
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5053
let root = dir.path ( ) ;
5054
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
5055
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
5056
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5057
write ( root, " a.txt" , b " one\n " ) ;
5058
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
5059
write ( root, " a.txt" , b " one\n two\n " ) ;
5060
let h2 = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
5061
let b = repo.blame ( " a.txt" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5062
assert_eq! (
5063
b.iter ( )
5064
.map ( | l | ( l.line, l.hash, l.text.as_slice ( ) ) )
5065
.collect:: < Vec< _ > > ( ) ,
5066
vec! [ ( 1 , h1, b " one\n " .as_slice ( ) ) , ( 2 , h2, b " two\n " .as_slice ( ) ) ]
5067
) ;
957fee30 Andy McCall 1 month ago ↶
5068
assert! ( b.iter ( ) .all ( | l | l.author = = " Ada" ) ) ;
75167672 Andy McCall 1 month ago
5069
assert_eq! (
5070
b[ 0 ] .change_id,
5071
repo.store ( ) .get_change ( & h1) .unwrap ( ) .change_id
5072
) ;
957fee30 Andy McCall 1 month ago ↶
5073
fs:: rename( root.join ( " a.txt" ) , root.join ( " b.txt" ) ) .unwrap ( ) ;
5074
repo.commit ( " rename" , & [ ] ) .unwrap ( ) ;
5075
let b = repo.blame ( " b.txt" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5076
assert_eq! (
5077
b.iter ( ) .map ( | l | l.hash ) .collect:: < Vec< _ > > ( ) ,
5078
vec! [ h1, h2] ,
5079
" the node survives the rename"
5080
) ;
957fee30 Andy McCall 1 month ago ↶
5081
assert! ( repo.blame ( " a.txt" ) .is_err ( ) ) ;
5082
}
5083
5084
# [ test ]
5085
fn bisect_finds_the_first_bad_change ( ) {
5086
hermetic ( ) ;
5087
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5088
let root = dir.path ( ) ;
5089
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5090
let mut content = String :: new( ) ;
5091
let mut hashes = Vec :: new( ) ;
5092
for i in 1 .. = 8 {
5093
content.push_str ( & format! ( " line {i} {} \n " , if i = = 5 { " bug" } else { " " } ) ) ;
5094
write ( root, " f.txt" , content.as_bytes ( ) ) ;
5095
hashes.push ( repo.commit ( & format! ( " step {i} " ) , & [ ] ) .unwrap ( ) ) ;
5096
}
5097
5098
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ hashes[ 7 ] ] ) ;
5099
repo.bisect_start ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5100
assert! ( matches! (
5101
repo.bisect_mark ( Some ( true ) , & [ hashes[ 0 ] ] ) .unwrap ( ) ,
5102
BisectStep:: NeedMarks { good: 1 , bad: 0 }
5103
) ) ;
957fee30 Andy McCall 1 month ago ↶
5104
let step = repo.bisect_mark ( Some ( false ) , & [ hashes[ 7 ] ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5105
assert! (
5106
matches! ( step, BisectStep:: Testing { remaining: 7 , .. } ) ,
5107
" {step:?}"
5108
) ;
957fee30 Andy McCall 1 month ago ↶
5109
75167672 Andy McCall 1 month ago
5110
let step = repo
5111
.bisect_run ( & [ " sh" .into ( ) , " -c" .into ( ) , " ! grep -q bug f.txt" .into ( ) ] )
5112
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5113
assert_eq! ( step, BisectStep:: Found { culprit: hashes[ 4 ] } ) ;
75167672 Andy McCall 1 month ago
5114
assert_eq! (
5115
repo.bisect_status ( ) .unwrap ( ) ,
5116
Some ( BisectStep:: Found { culprit: hashes[ 4 ] } )
5117
) ;
957fee30 Andy McCall 1 month ago ↶
5118
repo.bisect_reset ( ) .unwrap ( ) ;
5119
assert_eq! ( fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) , content) ;
5120
assert! ( repo.bisect_status ( ) .unwrap ( ) .is_none ( ) ) ;
5121
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
5122
5123
repo.bisect_start ( ) .unwrap ( ) ;
5124
repo.bisect_mark ( Some ( true ) , & [ hashes[ 0 ] ] ) .unwrap ( ) ;
5125
let mut step = repo.bisect_mark ( Some ( false ) , & [ hashes[ 7 ] ] ) .unwrap ( ) ;
5126
let mut skipped_once = false ;
5127
let mut guard = 0 ;
5128
while let BisectStep:: Testing { .. } = step {
5129
guard + = 1 ;
5130
assert! ( guard < 20 ) ;
5131
let text = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
5132
if ! skipped_once & & text.contains ( " line 6" ) {
5133
5134
skipped_once = true ;
5135
step = repo.bisect_mark ( None , & [ ] ) .unwrap ( ) ;
5136
continue ;
5137
}
5138
step = repo.bisect_mark ( Some ( ! text.contains ( " bug" ) ) , & [ ] ) .unwrap ( ) ;
5139
}
5140
assert! ( skipped_once) ;
5141
assert_eq! ( step, BisectStep:: Found { culprit: hashes[ 4 ] } ) ;
5142
repo.bisect_reset ( ) .unwrap ( ) ;
5143
5144
repo.bisect_start ( ) .unwrap ( ) ;
5145
repo.bisect_mark ( Some ( true ) , & [ hashes[ 0 ] ] ) .unwrap ( ) ;
5146
let mut step = repo.bisect_mark ( Some ( false ) , & [ hashes[ 7 ] ] ) .unwrap ( ) ;
5147
let mut guard = 0 ;
5148
while let BisectStep:: Testing { heads, .. } = & step {
5149
guard + = 1 ;
5150
assert! ( guard < 20 ) ;
5151
let text = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5152
let is_culprit_state =
5153
heads.contains ( & hashes[ 4 ] ) & & ! heads.iter ( ) .any ( | h | hashes[ 5 .. ] .contains ( h) ) ;
5154
step = if is_culprit_state {
5155
repo.bisect_mark ( None , & [ ] ) .unwrap ( )
5156
} else {
5157
repo.bisect_mark ( Some ( ! text.contains ( " bug" ) ) , & [ ] ) .unwrap ( )
5158
} ;
957fee30 Andy McCall 1 month ago ↶
5159
}
5160
match step {
75167672 Andy McCall 1 month ago
5161
BisectStep:: Inconclusive { candidates } => assert! (
5162
candidates.contains ( & hashes[ 4 ] ) & & candidates.len ( ) < = 2 ,
5163
" {candidates:?}"
5164
) ,
957fee30 Andy McCall 1 month ago ↶
5165
other => panic! ( " {other:?}" ) ,
5166
}
5167
repo.bisect_reset ( ) .unwrap ( ) ;
5168
}
5169
728da0f9 Andy McCall 1 month ago ↶
5170
# [ test ]
5171
fn squash_folds_a_change_into_its_dependency ( ) {
5172
hermetic ( ) ;
5173
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5174
let root = dir.path ( ) ;
5175
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5176
write ( root, " a.txt" , b " 1\n " ) ;
5177
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
5178
write ( root, " a.txt" , b " 1\n 2\n " ) ;
5179
let c = repo.commit ( " C" , & [ ] ) .unwrap ( ) ;
5180
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
5181
let e = repo.commit ( " E" , & [ ] ) .unwrap ( ) ;
5182
write ( root, " b.txt" , b " b\n " ) ;
5183
let b = repo.commit ( " B" , & [ ] ) .unwrap ( ) ;
5184
let id_a = repo.store ( ) .get_change ( & a) .unwrap ( ) .change_id;
5185
let id_e = repo.store ( ) .get_change ( & e) .unwrap ( ) .change_id;
5186
5187
let err = repo.squash ( & e, & a) .unwrap_err ( ) .to_string ( ) ;
5188
assert! ( err.contains ( & c.short ( 12 ) ) , " {err}" ) ;
5189
5190
let a2 = repo.squash ( & c, & a) .unwrap ( ) ;
5191
let a2c = repo.store ( ) .get_change ( & a2) .unwrap ( ) ;
5192
assert_eq! ( a2c.change_id, id_a) ;
5193
assert! ( a2c.deps.is_empty ( ) ) ;
5194
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5195
let st = repo.load_state ( & heads) .unwrap ( ) ;
5196
assert! ( ! st.contains ( & c) & & ! st.contains ( & a) & & ! st.contains ( & e) ) ;
75167672 Andy McCall 1 month ago
5197
let e2 = st
5198
.changes ( )
5199
.find ( | ( _, m ) | m.change_id = = id_e )
5200
.map ( | ( h , _) | * h)
5201
.unwrap ( ) ;
5202
assert_eq! (
5203
repo.store ( ) .get_change ( & e2) .unwrap ( ) .deps,
5204
vec! [ a2] ,
5205
" E now depends on A' alone"
5206
) ;
728da0f9 Andy McCall 1 month ago ↶
5207
let files = st.materialize ( ) .files;
75167672 Andy McCall 1 month ago
5208
assert! (
5209
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 1\n 2\n 3\n " )
5210
) ;
728da0f9 Andy McCall 1 month ago ↶
5211
assert! ( files.contains_key ( " b.txt" ) ) ;
5212
assert! ( st.contains ( & b) ) ;
5213
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
5214
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 , " working copy, B, E', A'" ) ;
5215
5216
repo.undo ( ) .unwrap ( ) ;
5217
repo.undo ( ) .unwrap ( ) ;
5218
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
5219
assert! ( st.contains ( & c) & & st.contains ( & a) & & st.contains ( & e) ) ;
5220
}
5221
5222
# [ test ]
5223
fn rebase_re_expresses_a_change_against_another_base ( ) {
5224
hermetic ( ) ;
5225
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5226
let root = dir.path ( ) ;
5227
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5228
write ( root, " a.txt" , b " 1\n 2\n " ) ;
5229
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
5230
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
5231
let c = repo.commit ( " C: append 3" , & [ ] ) .unwrap ( ) ;
5232
write ( root, " a.txt" , b " 1\n 2\n 3\n 4\n " ) ;
5233
let e = repo.commit ( " E: append 4" , & [ ] ) .unwrap ( ) ;
5234
5235
repo.checkout ( & [ a] , true , " checkout" ) .unwrap ( ) ;
5236
write ( root, " a.txt" , b " 0\n 1\n 2\n " ) ;
5237
let d = repo.commit ( " D: prepend 0" , & [ ] ) .unwrap ( ) ;
5238
let id_c = repo.store ( ) .get_change ( & c) .unwrap ( ) .change_id;
5239
let id_e = repo.store ( ) .get_change ( & e) .unwrap ( ) .change_id;
5240
5241
let c2 = repo.rebase ( & c, & [ d] ) .unwrap ( ) ;
5242
let c2c = repo.store ( ) .get_change ( & c2) .unwrap ( ) ;
5243
assert_eq! ( c2c.change_id, id_c) ;
5244
assert! ( c2c.deps.contains ( & d) , " {:?}" , c2c.deps) ;
5245
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5246
let st = repo.load_state ( & heads) .unwrap ( ) ;
5247
assert! ( ! st.contains ( & c) & & st.contains ( & d) ) ;
75167672 Andy McCall 1 month ago
5248
let e2 = st
5249
.changes ( )
5250
.find ( | ( _, m ) | m.change_id = = id_e )
5251
.map ( | ( h , _) | * h)
5252
.unwrap ( ) ;
728da0f9 Andy McCall 1 month ago ↶
5253
let e2deps = repo.store ( ) .get_change ( & e2) .unwrap ( ) .deps;
75167672 Andy McCall 1 month ago
5254
assert! (
5255
e2deps.contains ( & c2) & & ! e2deps.contains ( & c) & & e2deps.contains ( & a) ,
5256
" E follows C by content and still depends on A, which introduced the file: {e2deps:?}"
5257
) ;
728da0f9 Andy McCall 1 month ago ↶
5258
let files = st.materialize ( ) .files;
75167672 Andy McCall 1 month ago
5259
assert! (
5260
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 0\n 1\n 2\n 3\n 4\n " ) ,
5261
" {:?}" ,
5262
files[ " a.txt" ] .content
5263
) ;
728da0f9 Andy McCall 1 month ago ↶
5264
assert! ( ! files[ " a.txt" ] .is_conflicted ( ) ) ;
5265
5266
assert! ( repo.rebase ( & c2, & [ c2] ) .is_err ( ) ) ;
5267
let err = repo.rebase ( & e2, & [ d] ) .unwrap_err ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
5268
assert! (
5269
err.contains ( " lacks dependencies" ) ,
5270
" E needs C, which D's state does not have: {err}"
5271
) ;
728da0f9 Andy McCall 1 month ago ↶
5272
}
5273
c792033a Andy McCall 1 month ago ↶
5274
# [ test ]
5275
fn unsigned_tags_keep_their_message ( ) {
5276
hermetic ( ) ;
5277
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5278
let root = dir.path ( ) ;
5279
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5280
write ( root, " a.txt" , b " a\n " ) ;
5281
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
5282
assert! ( repo.secret_key ( ) .unwrap ( ) .is_none ( ) ) ;
75167672 Andy McCall 1 month ago
5283
repo.tag_create ( " v1" , & repo.bookmark_heads ( ) .unwrap ( ) , " first release" )
5284
.unwrap ( ) ;
c792033a Andy McCall 1 month ago ↶
5285
let tags = repo.tags ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5286
assert_eq! (
5287
(
5288
tags[ 0 ] .name.as_str ( ) ,
5289
tags[ 0 ] .message.as_str ( ) ,
5290
tags[ 0 ] .signer.as_deref ( )
5291
) ,
5292
( " v1" , " first release" , None )
5293
) ;
5294
assert! (
5295
repo.tag_refs ( ) .unwrap ( ) [ 0 ] .attestation.is_none ( ) ,
5296
" an unsigned attestation is not advertised"
5297
) ;
c792033a Andy McCall 1 month ago ↶
5298
}
387335ff Andy McCall 1 month ago ↶
5299
5300
# [ test ]
5301
fn fsck_passes_a_sound_repository_and_names_each_fault ( ) {
64818938 Andy McCall 1 month ago ↶
5302
5303
5304
if std:: env:: var_os( " OXY_STORE_BACKEND" ) .is_some ( ) {
5305
return ;
5306
}
387335ff Andy McCall 1 month ago ↶
5307
hermetic ( ) ;
5308
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5309
let root = dir.path ( ) ;
5310
let origin_path = root.join ( " origin" ) ;
5311
let origin = Repo:: init( & origin_path) .unwrap ( ) ;
5312
let mut a = Repo:: clone( & origin_path.to_string_lossy ( ) , & root.join ( " a" ) )
5313
.unwrap ( )
5314
.with_home ( root.join ( " home-a" ) ) ;
5315
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
5316
.unwrap ( ) ;
5317
write ( & root.join ( " a" ) , " f.txt" , b " one\n " ) ;
5318
write ( & root.join ( " a" ) , " bin.dat" , b " \0 \x01 \x02 binary" ) ;
5319
let h1 = a.commit ( " first" , & [ ] ) .unwrap ( ) ;
5320
write ( & root.join ( " a" ) , " f.txt" , b " one\n two\n " ) ;
5321
let h2 = a.commit ( " second" , & [ ] ) .unwrap ( ) ;
5322
a.tag_create ( " v1" , & [ h2] , " first release" ) .unwrap ( ) ;
5323
a.checkpoint ( ) .unwrap ( ) ;
5324
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
5325
origin.refresh ( ) .unwrap ( ) ;
387335ff Andy McCall 1 month ago ↶
5326
5327
5328
for repo in [ & a, & origin] {
5329
let rep = repo.fsck ( ) .unwrap ( ) ;
5330
assert_eq! ( rep.problems, vec! [ ] , " {rep:?}" ) ;
5331
5332
assert! ( rep.changes > = 2 & & rep.signed > = 2 , " {rep:?}" ) ;
5333
assert! ( rep.ops > 1 & & rep.objects > 5 ) ;
5334
}
5335
assert! ( origin.fsck ( ) .unwrap ( ) .attestations > = 1 ) ;
5336
5337
5338
let c1 = a.store ( ) .path_for ( & h1) ;
5339
let pristine = fs:: read( & c1) .unwrap ( ) ;
5340
let mut bytes = pristine.clone ( ) ;
5341
* bytes.last_mut ( ) .unwrap ( ) ^ = 1 ;
5342
fs:: write( & c1, & bytes) .unwrap ( ) ;
5343
let rep = a.fsck ( ) .unwrap ( ) ;
5344
assert! ( rep.errors ( ) > = 1 ) ;
5345
assert! (
5346
rep.problems
5347
.iter ( )
5348
.any ( | p | p.object = = Some ( h1) & & p.what.contains ( " unreadable" ) ) ,
5349
" {rep:?}"
5350
) ;
5351
fs:: write( & c1, & pristine) .unwrap ( ) ;
5352
assert_eq! ( a.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
5353
5354
5355
fs:: remove_file( & c1) .unwrap ( ) ;
5356
let rep = a.fsck ( ) .unwrap ( ) ;
5357
assert! (
5358
rep.problems.iter ( ) .any ( | p | p.severity = = Severity:: Error
5359
& & p.object = = Some ( h1)
5360
& & p.what.contains ( " change missing, needed by change" ) ) ,
5361
" {rep:?}"
5362
) ;
5363
fs:: write( & c1, & pristine) .unwrap ( ) ;
5364
5365
5366
let c2 = a.store ( ) .get_change ( & h1) .unwrap ( ) ;
5367
let blob = c2
5368
.edits
5369
.iter ( )
5370
.find_map ( | e | match e {
5371
Edit:: Replace { new: Some ( b) , .. } => Some ( * b) ,
5372
_ => None ,
5373
} )
5374
.expect ( " the binary file is a blob" ) ;
5375
let bp = a.store ( ) .path_for ( & blob) ;
5376
let blob_bytes = fs:: read( & bp) .unwrap ( ) ;
5377
fs:: remove_file( & bp) .unwrap ( ) ;
5378
let rep = a.fsck ( ) .unwrap ( ) ;
5379
assert! (
5380
rep.problems
5381
.iter ( )
5382
.any ( | p | p.object = = Some ( blob) & & p.what.contains ( " blob missing" ) ) ,
5383
" {rep:?}"
5384
) ;
5385
fs:: write( & bp, & blob_bytes) .unwrap ( ) ;
5386
5387
5388
let head = root.join ( " a/.oxy/ops/head" ) ;
5389
let saved = fs:: read( & head) .unwrap ( ) ;
5390
fs:: write( & head, b " not a hash" ) .unwrap ( ) ;
5391
let rep = a.fsck ( ) .unwrap ( ) ;
5392
assert! (
5393
rep.problems.iter ( ) .any ( | p | p.what.contains ( " ops/head" ) ) ,
5394
" {rep:?}"
5395
) ;
5396
fs:: write( & head, saved) .unwrap ( ) ;
5397
assert_eq! ( a.fsck ( ) .unwrap ( ) .problems, vec! [ ] ) ;
5398
}
4dcb8c09 Andy McCall 1 month ago ↶
5399
5400
5401
fn copy_tree ( from : & Path, to : & Path) {
5402
fs:: create_dir_all( to) .unwrap ( ) ;
5403
for e in fs:: read_dir( from) .unwrap ( ) {
5404
let e = e.unwrap ( ) ;
5405
let target = to.join ( e.file_name ( ) ) ;
5406
if e.file_type ( ) .unwrap ( ) .is_dir ( ) {
5407
copy_tree ( & e.path ( ) , & target) ;
5408
} else {
5409
fs:: copy( e.path ( ) , & target) .unwrap ( ) ;
5410
}
5411
}
5412
}
5413
5414
5415
5416
5417
# [ test ]
5418
fn crash_at_every_step_of_commit_and_push_leaves_sound_repositories ( ) {
64818938 Andy McCall 1 month ago ↶
5419
5420
5421
if std:: env:: var_os( " OXY_STORE_BACKEND" ) .is_some ( ) {
5422
return ;
5423
}
4dcb8c09 Andy McCall 1 month ago ↶
5424
use crate :: durable:: failpoint;
5425
hermetic ( ) ;
5426
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5427
let pristine = dir.path ( ) .join ( " pristine" ) ;
5428
let origin_path = pristine.join ( " origin" ) ;
5429
Repo:: init( & origin_path) .unwrap ( ) ;
5430
let mut a = Repo:: clone( & origin_path.to_string_lossy ( ) , & pristine.join ( " a" ) )
5431
.unwrap ( )
5432
.with_home ( dir.path ( ) .join ( " home" ) ) ;
5433
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
5434
.unwrap ( ) ;
5435
write ( & pristine.join ( " a" ) , " f.txt" , b " one\n " ) ;
5436
a.commit ( " first" , & [ ] ) .unwrap ( ) ;
5437
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
5438
drop ( a) ;
5439
5440
type Op = fn ( & Repo) -> Result< ( ) > ;
5441
let edit_and_commit: Op = | r | {
5442
write ( r.root ( ) , " f.txt" , b " one\n two\n " ) ;
5443
write ( r.root ( ) , " big.bin" , & vec! [ 7 u8 ; 300_000 ] ) ;
5444
r.commit ( " second" , & [ ] ) .map ( | _| ( ) )
5445
} ;
5446
let push: Op = | r | {
5447
let res = r.push ( " origin" , false ) ? ;
5448
if res.iter ( ) .all ( | x | x.ok ) {
5449
Ok ( ( ) )
5450
} else {
5451
Err ( RepoError:: Msg( format! ( " {res:?} " ) ) )
5452
}
5453
} ;
5454
5455
for ( name, prepare, op) in [
5456
( " commit" , None , edit_and_commit) ,
5457
( " push" , Some ( edit_and_commit) , push) ,
5458
] {
5459
let mut steps = 0 ;
5460
for n in 1 .. {
5461
let run = dir.path ( ) .join ( format! ( " {name} -{n} " ) ) ;
5462
copy_tree ( & pristine, & run) ;
5463
5464
let open = | p : & Path| {
5465
let mut r = Repo:: open( p) .unwrap ( ) .with_home ( dir.path ( ) .join ( " home" ) ) ;
5466
r.config
5467
.set ( " remote.origin.url" , & run.join ( " origin" ) .to_string_lossy ( ) )
5468
.unwrap ( ) ;
5469
r.save_config ( ) .unwrap ( ) ;
5470
r
5471
} ;
5472
let a = open ( & run.join ( " a" ) ) ;
5473
if let Some ( prep) = prepare {
5474
prep ( & a) .unwrap ( ) ;
5475
}
5476
failpoint:: crash_after( n) ;
5477
let outcome = op ( & a) ;
5478
let crashed = failpoint:: disarm( ) ;
5479
drop ( a) ;
5480
if ! crashed {
5481
outcome.unwrap_or_else ( | e | panic! ( " {name} failed without a crash: {e}" ) ) ;
5482
steps = n - 1 ;
5483
break ;
5484
}
5485
assert! (
5486
outcome.is_err ( ) ,
5487
" {name}: a crash at step {n} was swallowed"
5488
) ;
5489
for repo in [ " a" , " origin" ] {
5490
let r = Repo:: open( & run.join ( repo) ) .unwrap_or_else ( | e | {
5491
panic! ( " {name} crash at step {n}: {repo} no longer opens: {e}" )
5492
} ) ;
5493
let rep = r.fsck ( ) .unwrap ( ) ;
5494
assert_eq! (
5495
rep.errors ( ) ,
5496
0 ,
5497
" {name} crash at step {n}: {repo} fails fsck: {:?}" ,
5498
rep.problems
5499
) ;
5500
}
5501
5502
let a = open ( & run.join ( " a" ) ) ;
5503
5504
let landed = a
5505
.bookmark_heads ( )
5506
.unwrap ( )
5507
.iter ( )
5508
.any ( | h | a.store ( ) .get_change ( h) .unwrap ( ) .message = = " second" ) ;
5509
let again = match ( name, landed) {
5510
( " commit" , true ) => Ok ( ( ) ) ,
5511
( " commit" , false ) => a.commit ( " second" , & [ ] ) .map ( | _| ( ) ) ,
5512
_ => op ( & a) ,
5513
} ;
5514
again.unwrap_or_else ( | e | panic! ( " {name} crash at step {n}: retry failed: {e}" ) ) ;
5515
assert_eq! ( a.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
5516
fs:: remove_dir_all( & run) .unwrap ( ) ;
5517
}
5518
assert! ( steps > = 3 , " {name} has only {steps} durable steps?" ) ;
5519
}
5520
}
19555025 Andy McCall 1 month ago ↶
5521
5522
5523
5524
5525
# [ test ]
5526
fn concurrent_commits_in_two_workspaces_lose_nothing ( ) {
5527
hermetic ( ) ;
5528
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5529
let root = dir.path ( ) ;
5530
let origin = root.join ( " origin" ) ;
5531
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
5532
let main = root.join ( " main" ) ;
5533
let mut repo = Repo:: init( & main) .unwrap ( ) ;
5534
repo.config
5535
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
5536
.unwrap ( ) ;
5537
repo.save_config ( ) .unwrap ( ) ;
5538
write ( & main, " seed.txt" , b " seed\n " ) ;
5539
repo.commit ( " seed" , & [ ] ) .unwrap ( ) ;
5540
let second = root.join ( " second" ) ;
5541
repo.workspace_add ( & second, None ) .unwrap ( ) ;
5542
drop ( repo) ;
5543
5544
const EACH : usize = 15 ;
5545
std:: thread:: scope( | s | {
5546
for ( tag, dir, push) in [ ( " a" , main.clone ( ) , true ) , ( " b" , second.clone ( ) , false ) ] {
5547
s.spawn ( move | | {
5548
let r = Repo:: open( & dir) .unwrap ( ) ;
5549
for i in 0 .. EACH {
5550
write (
5551
& dir,
5552
& format! ( " {tag} -{i} .txt" ) ,
5553
format! ( " {tag} {i} \n " ) .as_bytes ( ) ,
5554
) ;
5555
r.commit ( & format! ( " {tag} {i} " ) , & [ ] ) .unwrap ( ) ;
5556
if push & & i % 5 = = 4 {
5557
5558
let _ = r.push ( " origin" , false ) ;
5559
}
5560
}
5561
} ) ;
5562
}
5563
} ) ;
5564
5565
let repo = Repo:: open( & main) .unwrap ( ) ;
5566
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5567
let state = repo.load_state ( & heads) .unwrap ( ) ;
5568
let messages: BTreeSet< String> = state.changes ( ) .map ( | ( _, m ) | m.message.clone ( ) ) .collect ( ) ;
5569
for tag in [ " a" , " b" ] {
5570
for i in 0 .. EACH {
5571
assert! (
5572
messages.contains ( & format! ( " {tag} {i} " ) ) ,
5573
" lost commit {tag} {i}; have {messages:?}"
5574
) ;
5575
}
5576
}
5577
let rep = repo.fsck ( ) .unwrap ( ) ;
5578
assert_eq! ( rep.errors ( ) , 0 , " {:?}" , rep.problems) ;
5579
assert! ( repo.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
5580
let hosted = Repo:: open( & origin) .unwrap ( ) ;
5581
let hosted_heads = hosted.bookmark_heads ( ) .unwrap ( ) ;
5582
assert_eq! (
5583
hosted.load_state ( & hosted_heads) .unwrap ( ) .len ( ) ,
5584
state.len ( ) ,
5585
" the remote has every commit"
5586
) ;
5587
}
5588
5589
5590
5591
5592
# [ test ]
5593
fn operations_from_the_same_parent_are_merged ( ) {
5594
hermetic ( ) ;
5595
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5596
let root = dir.path ( ) ;
5597
let repo = Repo:: init( root) .unwrap ( ) ;
5598
write ( root, " a.txt" , b " a\n " ) ;
5599
let a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
5600
write ( root, " b.txt" , b " b\n " ) ;
5601
let b = repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
5602
let ( x, op) = repo.head_op ( ) .unwrap ( ) ;
5603
let state = | heads : Vec< ChangeHash> | repo.store ( ) .put_state ( & StateObject:: new( heads) ) .unwrap ( ) ;
5604
5605
5606
let mut one = op.view.clone ( ) ;
5607
one.bookmarks.insert ( " main" .into ( ) , state ( vec! [ a] ) ) ;
5608
one.bookmarks.insert ( " topic" .into ( ) , state ( vec! [ a] ) ) ;
5609
let mut two = op.view.clone ( ) ;
5610
two.bookmarks.insert ( " main" .into ( ) , state ( vec! [ b] ) ) ;
5611
repo.record_op ( & [ x] , " one" , one) .unwrap ( ) ;
5612
let merged = repo.record_op ( & [ x] , " two" , two) .unwrap ( ) ;
5613
5614
let ( head, op) = repo.head_op ( ) .unwrap ( ) ;
5615
assert_eq! ( head, merged) ;
5616
assert_eq! ( op.parents.len ( ) , 2 , " a merge of the two operations" ) ;
5617
assert! ( op.command.contains ( " merged with a concurrent operation" ) ) ;
5618
let mut main = repo
5619
.store ( )
5620
.get_state ( & op.view.bookmarks[ " main" ] )
5621
.unwrap ( )
5622
.heads;
5623
main.sort ( ) ;
5624
let mut want = vec! [ a, b] ;
5625
want.sort ( ) ;
5626
assert_eq! ( main, want, " both sides' heads are kept" ) ;
5627
assert! (
5628
op.view.bookmarks.contains_key ( " topic" ) ,
5629
" a one-sided change survives"
5630
) ;
5631
assert_eq! ( repo.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
5632
}
3e5772cc Andy McCall 1 month ago ↶
5633
5634
# [ test ]
5635
fn failed_requests_come_with_a_next_step ( ) {
5636
use crate :: protocol:: hint_for;
5637
assert! ( hint_for ( " https://h/x/oxy/v1/push: HTTP 401: bad token" )
5638
.unwrap ( )
5639
.contains ( " token" ) ) ;
5640
assert! ( hint_for ( " …: HTTP 403: forbidden" )
5641
.unwrap ( )
5642
.contains ( " repo:write" ) ) ;
5643
assert! ( hint_for ( " …: HTTP 404: no such repository" )
5644
.unwrap ( )
5645
.contains ( " owner and repository name" ) ) ;
5646
assert! ( hint_for ( " invalid peer certificate: UnknownIssuer" )
5647
.unwrap ( )
5648
.contains ( " oxyhub-ca.pem" ) ) ;
5649
assert! ( hint_for ( " Connect error: Connection refused (os error 111)" )
5650
.unwrap ( )
5651
.contains ( " running" ) ) ;
5652
assert! ( hint_for ( " Dns Failed: failed to lookup address information" )
5653
.unwrap ( )
5654
.contains ( " resolve" ) ) ;
5655
assert_eq! ( hint_for ( " something else entirely" ) , None ) ;
5656
}
5657
5658
# [ test ]
5659
fn doctor_names_problems_and_what_to_do ( ) {
5660
hermetic ( ) ;
5661
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5662
let root = dir.path ( ) ;
5663
let origin = root.join ( " origin" ) ;
5664
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
5665
let mut repo = Repo:: init( & root.join ( " r" ) )
5666
.unwrap ( )
5667
.with_home ( root.join ( " home" ) ) ;
5668
let find = | r : & DoctorReport, area : & str , h : Health| {
5669
r.checks
5670
.iter ( )
5671
.find ( | c | c.area = = area & & c.health = = h )
5672
.cloned ( )
5673
} ;
5674
5675
let r = repo.doctor ( true ) ;
5676
assert! ( find ( & r, " identity" , Health:: Warn)
5677
.unwrap ( )
5678
.hint
5679
.unwrap ( )
5680
.contains ( " oxy identity create" ) ) ;
5681
assert! ( find ( & r, " repository" , Health:: Ok) .is_some ( ) ) ;
5682
5683
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
5684
.unwrap ( ) ;
5685
repo.config.set ( " author.name" , " Ada" ) .unwrap ( ) ;
5686
repo.config.set ( " author.email" , " [email protected] " ) .unwrap ( ) ;
5687
repo.config
5688
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
5689
.unwrap ( ) ;
5690
repo.config
5691
.set ( " remote.down.url" , " http://127.0.0.1:9/a/b" )
5692
.unwrap ( ) ;
5693
repo.config.set ( " remote.down.token" , " t" ) .unwrap ( ) ;
5694
repo.config
5695
.set ( " remote.badca.url" , " https://example.invalid/a/b" )
5696
.unwrap ( ) ;
5697
repo.config
5698
.set (
5699
" remote.badca.ca" ,
5700
& root.join ( " missing.pem" ) .to_string_lossy ( ) ,
5701
)
5702
.unwrap ( ) ;
5703
repo.save_config ( ) .unwrap ( ) ;
5704
5705
let r = repo.doctor ( true ) ;
5706
assert! ( find ( & r, " identity" , Health:: Ok) .is_some ( ) , " {r:?}" ) ;
5707
assert! ( find ( & r, " author" , Health:: Ok) .is_some ( ) , " {r:?}" ) ;
5708
assert! ( find ( & r, " remote origin" , Health:: Ok)
5709
.unwrap ( )
5710
.message
5711
.contains ( " first push binds it" ) ) ;
5712
let down = find ( & r, " remote down" , Health:: Fail) .unwrap ( ) ;
5713
assert! (
5714
down.hint.as_deref ( ) .unwrap_or ( " " ) .contains ( " running" ) ,
5715
" {down:?}"
5716
) ;
5717
let badca = find ( & r, " remote badca" , Health:: Fail) .unwrap ( ) ;
5718
assert! (
5719
badca.hint.unwrap ( ) .contains ( " remote.badca.ca" ) ,
5720
" the remote's own name is in the hint"
5721
) ;
5722
assert_eq! ( r.worst ( ) , Health:: Fail) ;
5723
5724
5725
let r = repo.doctor ( false ) ;
5726
assert! ( find ( & r, " remote down" , Health:: Fail) .is_none ( ) ) ;
5727
assert! ( find ( & r, " remote down" , Health:: Ok)
5728
.unwrap ( )
5729
.message
5730
.contains ( " not contacted" ) ) ;
5731
}
62794ecc Andy McCall 1 month ago ↶
5732
5733
5734
5735
5736
# [ test ]
5737
fn deleting_a_line_after_an_insertion_below_it_is_not_a_conflict ( ) {
5738
hermetic ( ) ;
5739
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5740
let root = dir.path ( ) ;
5741
let repo = Repo:: init( root) .unwrap ( ) ;
5742
write ( root, " f" , b " a\n b\n c\n " ) ;
5743
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
5744
write ( root, " f" , b " a\n b\n x\n c\n " ) ;
5745
let insert = repo.commit ( " insert x after b" , & [ ] ) .unwrap ( ) ;
5746
write ( root, " f" , b " a\n x\n c\n " ) ;
5747
let delete = repo.commit ( " delete b" , & [ ] ) .unwrap ( ) ;
5748
assert! (
5749
repo.store ( )
5750
.get_change ( & delete)
5751
.unwrap ( )
5752
.deps
5753
.contains ( & insert) ,
5754
" the deletion depends on the insertion below the deleted line"
5755
) ;
5756
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5757
assert_eq! ( heads, vec! [ delete] ) ;
5758
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
5759
assert! ( ! snap.is_conflicted ( ) ) ;
5760
assert! (
5761
matches! ( & snap.files[ " f" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n x\n c\n " )
5762
) ;
5763
}
5764
5765
5766
5767
# [ test ]
5768
fn a_binary_file_reverted_to_an_earlier_blob_keeps_it ( ) {
5769
hermetic ( ) ;
5770
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5771
let root = dir.path ( ) ;
5772
let repo = Repo:: init( root) .unwrap ( ) ;
5773
write ( root, " bin" , b " \0 A" ) ;
5774
let v1 = repo.commit ( " v1" , & [ ] ) .unwrap ( ) ;
5775
write ( root, " bin" , b " \0 B" ) ;
5776
let v2 = repo.commit ( " v2" , & [ ] ) .unwrap ( ) ;
5777
assert! ( repo.store ( ) .get_change ( & v2) .unwrap ( ) .deps.contains ( & v1) ) ;
5778
write ( root, " bin" , b " \0 A" ) ;
5779
let v3 = repo.commit ( " back to A" , & [ ] ) .unwrap ( ) ;
5780
assert! ( repo.store ( ) .get_change ( & v3) .unwrap ( ) .deps.contains ( & v2) ) ;
5781
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ v3] ) ;
5782
let state = repo.load_state ( & [ v3] ) .unwrap ( ) ;
5783
let snap = state.materialize ( ) ;
5784
let FileContent:: Opaque { blobs } = & snap.files[ " bin" ] .content else {
5785
panic! ( " opaque" )
5786
} ;
5787
assert_eq! ( blobs.len ( ) , 1 , " one live blob, not none and not two" ) ;
5788
assert_eq! ( repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) , b " \0 A" ) ;
5789
}
5790
5791
5792
5793
5794
# [ test ]
5795
fn git_import_resolves_a_binary_merged_in_git_and_verifies ( ) {
5796
hermetic ( ) ;
5797
if std:: process:: Command:: new( " git" )
5798
.arg ( " --version" )
5799
.output ( )
5800
.is_err ( )
5801
{
5802
eprintln! ( " git not installed; skipping" ) ;
5803
return ;
5804
}
5805
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5806
let root = dir.path ( ) ;
5807
let g = root.join ( " src.git" ) ;
5808
fs:: create_dir_all( & g) .unwrap ( ) ;
5809
let git = | args : & [& str ]| -> String {
5810
let out = std:: process:: Command:: new( " git" )
5811
.arg ( " -C" )
5812
.arg ( & g)
5813
.args ( [
5814
" -c" ,
5815
" user.name=Grace" ,
5816
" -c" ,
5817
" [email protected] " ,
5818
" -c" ,
5819
" commit.gpgsign=false" ,
5820
] )
5821
.args ( args)
5822
.output ( )
5823
.unwrap ( ) ;
5824
assert! (
5825
out.status.success ( ) ,
5826
" git {args:?}: {}" ,
5827
String :: from_utf8_lossy( & out.stderr)
5828
) ;
5829
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
5830
} ;
5831
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
5832
write ( & g, " logo.bin" , b " \0 base" ) ;
5833
write ( & g, " notes.txt" , b " a\n b\n c\n " ) ;
5834
git ( & [ " add" , " ." ] ) ;
5835
git ( & [ " commit" , " -qm" , " base" ] ) ;
5836
git ( & [ " checkout" , " -qb" , " side" ] ) ;
5837
write ( & g, " logo.bin" , b " \0 side" ) ;
5838
write ( & g, " notes.txt" , b " a\n b\n x\n c\n " ) ;
5839
git ( & [ " commit" , " -qam" , " side" ] ) ;
5840
git ( & [ " checkout" , " -q" , " main" ] ) ;
5841
write ( & g, " logo.bin" , b " \0 main" ) ;
5842
git ( & [ " commit" , " -qam" , " main" ] ) ;
5843
5844
let _ = std:: process:: Command:: new( " git" )
5845
.arg ( " -C" )
5846
.arg ( & g)
5847
.args ( [
5848
" -c" ,
5849
" user.name=G" ,
5850
" -c" ,
5851
" user.email=g@e" ,
5852
" merge" ,
5853
" -q" ,
5854
" side" ,
5855
] )
5856
.output ( ) ;
5857
write ( & g, " logo.bin" , b " \0 main" ) ;
5858
git ( & [ " add" , " ." ] ) ;
5859
git ( & [ " commit" , " -qm" , " merge side" ] ) ;
5860
5861
write ( & g, " logo.bin" , b " \0 base" ) ;
5862
git ( & [ " commit" , " -qam" , " back to base" ] ) ;
5863
5864
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
5865
let rep = repo.import_git ( & g, " main" ) .unwrap ( ) ;
5866
assert_eq! ( rep.merges, 1 ) ;
5867
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
5868
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
5869
assert_eq! ( v.commits, 5 ) ;
5870
let snap = repo
5871
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
5872
.unwrap ( )
5873
.materialize ( ) ;
5874
assert! ( ! snap.is_conflicted ( ) ) ;
5875
let FileContent:: Opaque { blobs } = & snap.files[ " logo.bin" ] .content else {
5876
panic! ( " opaque" )
5877
} ;
5878
assert_eq! ( repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) , b " \0 base" ) ;
5879
}
c863c7a7 Andy McCall 1 month ago ↶
5880
5881
5882
5883
5884
# [ cfg ( unix ) ]
5885
# [ test ]
5886
fn windows_file_systems_keep_exec_bits_and_symlinks ( ) {
5887
use crate :: portable:: Platform;
5888
use std:: os:: unix:: fs:: PermissionsExt;
5889
hermetic ( ) ;
5890
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5891
let root = dir.path ( ) ;
5892
let repo = Repo:: init( root) .unwrap ( ) ;
5893
write ( root, " run.sh" , b " #!/bin/sh\n " ) ;
5894
fs:: set_permissions( root.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
5895
std:: os:: unix:: fs:: symlink( " run.sh" , root.join ( " link" ) ) .unwrap ( ) ;
5896
repo.commit ( " script and link" , & [ ] ) .unwrap ( ) ;
5897
drop ( repo) ;
5898
5899
5900
fs:: set_permissions( root.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
5901
fs:: remove_file( root.join ( " link" ) ) .unwrap ( ) ;
5902
write ( root, " link" , b " run.sh" ) ;
5903
let win = Repo:: open( root) .unwrap ( ) .with_platform ( Platform:: windows( ) ) ;
5904
let s = win.snapshot ( " oxy status" ) .unwrap ( ) ;
5905
assert! (
5906
s.statuses.is_empty ( ) ,
5907
" nothing changed on Windows: {:?}" ,
5908
statuses ( & s)
5909
) ;
5910
5911
write ( root, " run.sh" , b " #!/bin/sh\n echo hi\n " ) ;
5912
write ( root, " link" , b " other" ) ;
5913
let h = win.commit ( " edit on Windows" , & [ ] ) .unwrap ( ) ;
5914
let snap = win.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
5915
assert! ( snap.files[ " run.sh" ] .mode.exec, " the exec bit survives" ) ;
5916
assert_eq! ( snap.files[ " link" ] .kind, NodeKind:: Symlink) ;
5917
5918
let linux = Repo:: open( root) .unwrap ( ) ;
5919
assert! ( ! linux.snapshot ( " oxy status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
5920
}
5921
5922
5923
5924
# [ test ]
5925
fn paths_other_platforms_cannot_hold_are_reported ( ) {
5926
use crate :: portable:: Platform;
5927
hermetic ( ) ;
5928
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5929
let root = dir.path ( ) ;
5930
let repo = Repo:: init( root) .unwrap ( ) ;
5931
write ( root, " README.md" , b " a\n " ) ;
5932
write ( root, " readme.md" , b " b\n " ) ;
5933
write ( root, " src/aux.c" , b " int x;\n " ) ;
5934
let h = repo.commit ( " unportable" , & [ ] ) .unwrap ( ) ;
5935
let r = repo.doctor ( false ) ;
5936
let port = r.checks.iter ( ) .find ( | c | c.area = = " portability" ) .unwrap ( ) ;
5937
assert_eq! ( port.health, Health:: Warn) ;
5938
assert! (
5939
port.message.contains ( " differ only in case" ) & & port.message.contains ( " aux.c" ) ,
5940
" {}" ,
5941
port.message
5942
) ;
5943
let mac = Repo:: open( root) .unwrap ( ) .with_platform ( Platform:: macos( ) ) ;
5944
let rep = mac.checkout ( & [ h] , true , " oxy checkout" ) .unwrap ( ) ;
5945
assert! (
5946
rep.notes
5947
.iter ( )
5948
.any ( | n | n.contains ( " README.md and readme.md" ) ) ,
5949
" {:?}" ,
5950
rep.notes
5951
) ;
5952
assert! (
5953
! rep.notes.iter ( ) .any ( | n | n.contains ( " aux.c" ) ) ,
5954
" macOS allows aux.c"
5955
) ;
5956
}
bc489b6c Andy McCall 1 month ago ↶
5957
5958
# [ test ]
5959
fn history_and_blame_of_a_loaded_state_match_the_full_walk ( ) {
5960
hermetic ( ) ;
5961
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5962
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
5963
write ( dir.path ( ) , " a.txt" , b " one\n " ) ;
5964
write ( dir.path ( ) , " other.txt" , b " x\n " ) ;
5965
write ( dir.path ( ) , " bin.dat" , & [ 0 u8 , 1 , 2 , 0 , 255 ] ) ;
5966
repo.snapshot ( " t" ) .unwrap ( ) ;
5967
let c1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
5968
write ( dir.path ( ) , " other.txt" , b " y\n " ) ;
5969
repo.snapshot ( " t" ) .unwrap ( ) ;
5970
repo.commit ( " other only" , & [ ] ) .unwrap ( ) ;
5971
write ( dir.path ( ) , " a.txt" , b " one\n two\n " ) ;
5972
repo.snapshot ( " t" ) .unwrap ( ) ;
5973
let c3 = repo.commit ( " second line" , & [ ] ) .unwrap ( ) ;
5974
write ( dir.path ( ) , " other.txt" , b " z\n " ) ;
5975
repo.snapshot ( " t" ) .unwrap ( ) ;
5976
repo.commit ( " other again" , & [ ] ) .unwrap ( ) ;
5977
5978
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5979
let state = repo.load_state ( & heads) .unwrap ( ) ;
5980
let full: Vec< ChangeHash> = repo
5981
.log_for_path_in ( " a.txt" , & heads)
5982
.unwrap ( )
5983
.iter ( )
5984
.map ( | e | e.hash )
5985
.collect ( ) ;
5986
assert_eq! (
5987
full,
5988
vec! [ c3, c1] ,
5989
" newest first, only changes touching the path"
5990
) ;
5991
let newest: Vec< ChangeHash> = repo
5992
.log_for_path_of ( & state, " a.txt" , Some ( 1 ) )
5993
.unwrap ( )
5994
.iter ( )
5995
.map ( | e | e.hash )
5996
.collect ( ) ;
5997
assert_eq! ( newest, vec! [ c3] , " stops at the first" ) ;
5998
assert_eq! (
5999
repo.log_for_path_of ( & state, " a.txt" , None ) .unwrap ( ) .len ( ) ,
6000
2
6001
) ;
6002
assert! ( repo
6003
.log_for_path_of ( & state, " missing.txt" , Some ( 1 ) )
6004
.is_err ( ) ) ;
6005
assert_eq! (
6006
repo.log_state ( & heads) .unwrap ( ) .len ( ) ,
6007
repo.log_of ( & state) .len ( )
6008
) ;
6009
6010
let tree = state.tree ( ) .view ( ) ;
6011
let via_state = repo.blame_of ( & state, & tree, " a.txt" ) .unwrap ( ) ;
6012
let via_heads = repo.blame_in ( " a.txt" , & heads) .unwrap ( ) ;
6013
assert_eq! (
6014
via_state
6015
.iter ( )
6016
.map ( | l | ( l.hash, l.text.clone ( ) ) )
6017
.collect:: < Vec< _ > > ( ) ,
6018
via_heads
6019
.iter ( )
6020
.map ( | l | ( l.hash, l.text.clone ( ) ) )
6021
.collect:: < Vec< _ > > ( )
6022
) ;
6023
assert_eq! (
6024
via_state.iter ( ) .map ( | l | l.hash ) .collect:: < Vec< _ > > ( ) ,
6025
vec! [ c1, c3]
6026
) ;
6027
assert! ( repo.blame_of ( & state, & tree, " missing.txt" ) .is_err ( ) ) ;
6028
}
696bbf29 Andy McCall 1 month ago ↶
6029
6030
6031
6032
6033
# [ cfg ( unix ) ]
6034
# [ test ]
6035
fn stat_cache_takes_unchanged_text_from_the_base_and_misses_no_edit ( ) {
6036
use std:: os:: unix:: fs:: PermissionsExt;
6037
hermetic ( ) ;
6038
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6039
let root = dir.path ( ) ;
6040
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6041
write ( root, " a.txt" , b " one\n two\n " ) ;
6042
write ( root, " b.txt" , b " bee\n " ) ;
6043
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6044
let age = | p : & str | {
6045
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6046
fs:: File:: options( )
6047
.write ( true )
6048
.open ( root.join ( p) )
6049
.unwrap ( )
6050
.set_modified ( old)
6051
.unwrap ( ) ;
6052
} ;
6053
age ( " a.txt" ) ;
6054
age ( " b.txt" ) ;
6055
assert! (
6056
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
6057
" records the aged stat"
6058
) ;
6059
6060
6061
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6062
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
6063
assert! (
6064
! snap.changed & & snap.statuses.is_empty ( ) ,
6065
" {:?}" ,
6066
snap.statuses
6067
) ;
6068
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6069
6070
6071
6072
write ( root, " b.txt" , b " bee edited\n " ) ;
6073
age ( " b.txt" ) ;
6074
let first = repo.snapshot ( " status" ) .unwrap ( ) ;
6075
assert_eq! (
6076
statuses ( & first) ,
6077
vec! [ ( " b.txt" .to_string ( ) , ChangeStatus:: Modified) ]
6078
) ;
6079
let again = repo.snapshot ( " status" ) .unwrap ( ) ;
6080
assert_eq! (
6081
statuses ( & again) ,
6082
vec! [ ( " b.txt" .to_string ( ) , ChangeStatus:: Modified) ] ,
6083
" still seen once stat-clean"
6084
) ;
6085
assert! ( ! again.changed, " and the working copy hashes the same" ) ;
6086
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6087
assert! (
6088
repo.snapshot ( " status" ) .is_err ( ) ,
6089
" an edited file must be read, never taken from the base"
6090
) ;
6091
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6092
6093
6094
write ( root, " a.txt" , b " one\n TWO\n " ) ;
6095
assert_eq! (
6096
statuses ( & repo.snapshot ( " status" ) .unwrap ( ) ) ,
6097
vec! [
6098
( " a.txt" .to_string ( ) , ChangeStatus:: Modified) ,
6099
( " b.txt" .to_string ( ) , ChangeStatus:: Modified)
6100
]
6101
) ;
6102
6103
repo.commit ( " edits" , & [ ] ) .unwrap ( ) ;
6104
age ( " a.txt" ) ;
6105
age ( " b.txt" ) ;
6106
assert! ( repo.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6107
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6108
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6109
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
6110
assert! ( ! snap.changed & & snap.statuses.is_empty ( ) ) ;
6111
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6112
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6113
}
afaba5f1 Andy McCall 1 month ago ↶
6114
6115
6116
6117
6118
# [ test ]
6119
fn checkpoints_keep_large_graphs_as_blobs_of_their_own ( ) {
6120
hermetic ( ) ;
6121
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6122
let root = dir.path ( ) ;
6123
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6124
let long: String = ( 0 .. 300 )
6125
.map ( | i | format! ( " line {i} of a long file\n " ) )
6126
.collect ( ) ;
6127
write ( root, " long.txt" , long.as_bytes ( ) ) ;
6128
write ( root, " short.txt" , b " short\n " ) ;
6129
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6130
let ( r1, _ , _ ) = repo.checkpoint ( ) .unwrap ( ) ;
6131
let cp1 = crate :: objects:: Checkpoint:: decode(
6132
& repo.store ( ) .get_typed ( & r1, ObjectType:: Checkpoint) .unwrap ( ) ,
6133
)
6134
.unwrap ( ) ;
6135
let graphs1 = repo.checkpoint_graph_blobs ( & cp1) .unwrap ( ) ;
6136
assert_eq! ( graphs1.len ( ) , 1 , " only the long file's graph is a blob" ) ;
6137
6138
6139
repo.gc ( true ) .unwrap ( ) ;
6140
assert! ( repo.store ( ) .has ( & graphs1[ 0 ] ) ) ;
6141
let report = repo.fsck ( ) .unwrap ( ) ;
6142
assert_eq! ( report.errors ( ) , 0 , " {:?}" , report.problems) ;
6143
6144
6145
write ( root, " short.txt" , b " short, edited\n " ) ;
6146
repo.commit ( " edit the short file" , & [ ] ) .unwrap ( ) ;
6147
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6148
let fast = repo.load_state ( & heads) .unwrap ( ) ;
6149
let full = repo.load_state_full ( & heads) .unwrap ( ) ;
6150
assert_eq! ( fast.checkpoint_payload ( ) , full.checkpoint_payload ( ) ) ;
6151
6152
6153
let ( r2, _ , _ ) = repo.checkpoint ( ) .unwrap ( ) ;
6154
let cp2 = crate :: objects:: Checkpoint:: decode(
6155
& repo.store ( ) .get_typed ( & r2, ObjectType:: Checkpoint) .unwrap ( ) ,
6156
)
6157
.unwrap ( ) ;
6158
assert_eq! ( repo.checkpoint_graph_blobs ( & cp2) .unwrap ( ) , graphs1) ;
6159
6160
6161
let bundle = repo.checkpoint_bundle_for ( & heads) .unwrap ( ) ;
6162
assert! ( bundle.objects.iter ( ) .any ( | ( t , body ) | * t = = ObjectType:: Blob
6163
& & crate :: hash:: hash_object( ObjectType:: Blob, body) = = graphs1[ 0 ] ) ) ;
6164
6165
6166
let state = repo.load_state_full ( & heads) .unwrap ( ) ;
6167
let old = repo.store_blob ( & state.checkpoint_payload ( ) ) .unwrap ( ) ;
6168
let cp_old = crate :: objects:: Checkpoint {
6169
heads: cp2.heads.clone ( ) ,
6170
count: cp2.count,
6171
payload: old,
6172
} ;
6173
let r_old = repo
6174
.store ( )
6175
.put ( ObjectType:: Checkpoint, & cp_old.encode ( ) )
6176
.unwrap ( ) ;
6177
repo.install_checkpoint ( " main" , & r_old) .unwrap ( ) ;
6178
assert_eq! (
6179
repo.load_state ( & heads) .unwrap ( ) .checkpoint_payload ( ) ,
6180
full.checkpoint_payload ( )
6181
) ;
6182
assert_eq! ( repo.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
6183
6184
6185
6186
let dir2 = tempfile:: tempdir( ) .unwrap ( ) ;
6187
let root2 = dir2.path ( ) ;
6188
let repo2 = Repo:: init( root2) .unwrap ( ) .with_home ( root2.join ( " home" ) ) ;
6189
write ( root2, " long.txt" , long.as_bytes ( ) ) ;
6190
repo2.commit ( " base" , & [ ] ) .unwrap ( ) ;
6191
let ( r, _ , _ ) = repo2.checkpoint ( ) .unwrap ( ) ;
6192
let cp = crate :: objects:: Checkpoint:: decode(
6193
& repo2.store ( ) .get_typed ( & r, ObjectType:: Checkpoint) .unwrap ( ) ,
6194
)
6195
.unwrap ( ) ;
6196
let graph = repo2.checkpoint_graph_blobs ( & cp) .unwrap ( ) [ 0 ] ;
64818938 Andy McCall 1 month ago ↶
6197
repo2.store ( ) .remove_loose_for_test ( & graph) .unwrap ( ) ;
afaba5f1 Andy McCall 1 month ago ↶
6198
assert! ( repo2.load_state ( & repo2.bookmark_heads ( ) .unwrap ( ) ) .is_err ( ) ) ;
6199
assert! ( repo2.fsck ( ) .unwrap ( ) .errors ( ) > 0 , " fsck reports it" ) ;
6200
}
e3d071ce Andy McCall 1 month ago ↶
6201
6202
6203
6204
6205
# [ test ]
6206
fn identity_is_found_in_the_key_directory_for_a_new_repository ( ) {
6207
hermetic ( ) ;
6208
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6209
let home = dir.path ( ) .join ( " home" ) ;
6210
let mut first = Repo:: init( & dir.path ( ) .join ( " a" ) )
6211
.unwrap ( )
6212
.with_home ( home.clone ( ) ) ;
6213
let ( key, _ ) = first
6214
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
6215
.unwrap ( ) ;
6216
let fresh = Repo:: init( & dir.path ( ) .join ( " b" ) )
6217
.unwrap ( )
6218
.with_home ( home.clone ( ) ) ;
6219
assert! ( ! fresh.store ( ) .has ( & key) , " nothing stored yet" ) ;
6220
let id = fresh.identity ( & key) .unwrap ( ) ;
6221
assert_eq! ( ( id.name.as_str ( ) , id.hash ( ) ) , ( " Ada" , key) ) ;
6222
6223
let keys = fresh.keys_dir.clone ( ) ;
6224
let ( other, _ ) = first
6225
.create_identity ( " Bob" , " [email protected] " , IdentityScope:: User)
6226
.unwrap ( ) ;
6227
fs:: copy(
6228
keys.join ( format! ( " {} .identity" , other.to_hex ( ) ) ) ,
6229
keys.join ( format! ( " {} .identity" , key.to_hex ( ) ) ) ,
6230
)
6231
.unwrap ( ) ;
6232
assert! ( fresh
6233
.identity ( & key)
6234
.unwrap_err ( )
6235
.to_string ( )
6236
.contains ( " does not hold identity" ) ) ;
6237
}
cc7a0056 Andy McCall 1 month ago ↶
6238
6239
6240
6241
6242
6243
# [ cfg ( unix ) ]
6244
# [ test ]
6245
fn status_snapshots_skip_files_unchanged_since_the_manifest ( ) {
6246
use std:: os:: unix:: fs:: PermissionsExt;
6247
hermetic ( ) ;
6248
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6249
let root = dir.path ( ) ;
6250
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6251
for ( p, c) in [ ( " a.txt" , " one\n " ) , ( " b.txt" , " bee\n " ) , ( " d/c.txt" , " sea\n " ) ] {
6252
write ( root, p, c.as_bytes ( ) ) ;
6253
}
6254
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6255
let age = | p : & str | {
6256
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6257
fs:: File:: options( )
6258
.write ( true )
6259
.open ( root.join ( p) )
6260
.unwrap ( )
6261
.set_modified ( old)
6262
.unwrap ( ) ;
6263
} ;
6264
let lock = | p : & str , mode : u32 | {
6265
fs:: set_permissions( root.join ( p) , fs:: Permissions:: from_mode( mode) ) .unwrap ( )
6266
} ;
6267
for p in [ " a.txt" , " b.txt" , " d/c.txt" ] {
6268
age ( p) ;
6269
}
6270
assert! ( repo.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6271
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
6272
assert! (
6273
m.entries.values ( ) .all ( | e | e.graph.is_some ( ) ) ,
6274
" keys recorded"
6275
) ;
6276
6277
6278
6279
lock ( " a.txt" , 0o000 ) ;
6280
lock ( " b.txt" , 0o000 ) ;
6281
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6282
assert! ( ! snap.changed & & snap.statuses.is_empty ( ) ) ;
6283
assert_eq! (
6284
snap.base_snapshot
6285
.unrendered
6286
.iter ( )
6287
.cloned ( )
6288
.collect:: < Vec< _ > > ( ) ,
6289
vec! [ " a.txt" , " b.txt" , " d/c.txt" ]
6290
) ;
6291
assert! ( ! snap.base.is_full ( ) ) ;
6292
assert! ( ! snap.base_snapshot.is_conflicted ( ) ) ;
6293
6294
let full = repo.snapshot ( " status" ) .unwrap ( ) ;
6295
assert! ( full.statuses.is_empty ( ) & & full.base.is_full ( ) ) ;
6296
assert! ( full.base_snapshot.unrendered.is_empty ( ) ) ;
6297
assert! ( matches! ( & full.base_snapshot.files[ " a.txt" ] .content,
6298
FileContent:: Text { bytes, .. } if bytes = = b " one\n " ) ) ;
6299
6300
6301
write ( root, " d/c.txt" , b " sea edited\n " ) ;
6302
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6303
assert_eq! (
6304
statuses ( & snap) ,
6305
vec! [ ( " d/c.txt" .to_string ( ) , ChangeStatus:: Modified) ]
6306
) ;
6307
assert! ( ! snap.base_snapshot.unrendered.contains ( " d/c.txt" ) ) ;
6308
6309
lock ( " b.txt" , 0o100 ) ;
6310
assert_eq! (
6311
statuses ( & repo.snapshot_status ( " status" ) .unwrap ( ) ) ,
6312
vec! [
6313
( " b.txt" .to_string ( ) , ChangeStatus:: ModeChanged) ,
6314
( " d/c.txt" .to_string ( ) , ChangeStatus:: Modified)
6315
]
6316
) ;
6317
lock ( " b.txt" , 0o000 ) ;
6318
6319
6320
6321
repo.commit_selection ( " edit c" , & [ ] , & Selection:: Paths( vec! [ " d/c.txt" .into ( ) ] ) )
6322
.unwrap ( ) ;
6323
age ( " d/c.txt" ) ;
6324
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6325
assert! ( snap.statuses.is_empty ( ) , " {:?}" , snap.statuses) ;
6326
assert! ( snap.base_snapshot.unrendered.contains ( " a.txt" ) ) ;
6327
lock ( " d/c.txt" , 0o000 ) ;
6328
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6329
assert! ( snap.statuses.is_empty ( ) ) ;
6330
assert! ( snap.base_snapshot.unrendered.contains ( " d/c.txt" ) ) ;
6331
for p in [ " a.txt" , " b.txt" , " d/c.txt" ] {
6332
lock ( p, 0o644 ) ;
6333
}
6334
}
6335
6336
6337
6338
# [ cfg ( unix ) ]
6339
# [ test ]
6340
fn a_changed_attributes_file_disables_skipping_unchanged_files ( ) {
6341
hermetic ( ) ;
6342
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6343
let root = dir.path ( ) ;
6344
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6345
write ( root, " a.txt" , b " crlf\r \n " ) ;
6346
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6347
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6348
fs:: File:: options( )
6349
.write ( true )
6350
.open ( root.join ( " a.txt" ) )
6351
.unwrap ( )
6352
.set_modified ( old)
6353
.unwrap ( ) ;
6354
assert! ( repo.snapshot_status ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6355
assert! ( repo
6356
.snapshot_status ( " status" )
6357
.unwrap ( )
6358
.base_snapshot
6359
.unrendered
6360
.contains ( " a.txt" ) ) ;
6361
6362
write ( root, " .oxyattributes" , b " *.txt eol=lf\n " ) ;
6363
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6364
assert_eq! (
6365
statuses ( & snap) ,
6366
vec! [
6367
( " .oxyattributes" .to_string ( ) , ChangeStatus:: Added) ,
6368
( " a.txt" .to_string ( ) , ChangeStatus:: Modified)
6369
]
6370
) ;
6371
}
54cd08a1 Andy McCall 1 month ago ↶
6372
6373
6374
6375
6376
# [ test ]
6377
fn bookmarks_with_slashes_push_and_checkpoint ( ) {
6378
hermetic ( ) ;
6379
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6380
let root = dir.path ( ) ;
6381
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
6382
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
6383
let mut repo = Repo:: clone( & url, & root.join ( " r" ) )
6384
.unwrap ( )
6385
.with_home ( root.join ( " home" ) ) ;
6386
6387
6388
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
6389
.unwrap ( ) ;
6390
let r = root.join ( " r" ) ;
6391
write ( & r, " a.txt" , b " a\n " ) ;
6392
let h_a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
6393
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
6394
origin.refresh ( ) .unwrap ( ) ;
54cd08a1 Andy McCall 1 month ago ↶
6395
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
6396
repo.bookmark_create ( " feature/x" , None ) .unwrap ( ) ;
6397
repo.bookmark_switch ( " feature/x" , false ) .unwrap ( ) ;
6398
write ( & r, " b.txt" , b " b\n " ) ;
6399
let h_b = repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
6400
for name in [ " feature" , " feature/x" ] {
6401
let res = repo.push_bookmark ( " origin" , name, false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
6402
origin.refresh ( ) .unwrap ( ) ;
54cd08a1 Andy McCall 1 month ago ↶
6403
assert! ( res[ 0 ] .ok, " {name}: {res:?}" ) ;
6404
}
6405
assert_eq! ( origin.resolve_heads ( " feature" ) .unwrap ( ) , vec! [ h_a] ) ;
6406
assert_eq! (
6407
origin.resolve_heads ( " feature/x" ) .unwrap ( ) ,
6408
repo.resolve_heads ( " feature/x" ) .unwrap ( )
6409
) ;
6410
assert! ( origin.resolve_heads ( " feature/x" ) .unwrap ( ) .contains ( & h_b) ) ;
6411
assert! ( origin.latest_ref_attest ( " feature/x" ) .unwrap ( ) .is_some ( ) ) ;
6412
assert! ( origin.latest_ref_attest ( " feature" ) .unwrap ( ) .is_some ( ) ) ;
6413
6414
6415
let ( cp, _ , _ ) = repo.checkpoint ( ) .unwrap ( ) ;
6416
assert_eq! ( repo.latest_checkpoint ( " feature/x" ) .unwrap ( ) .unwrap ( ) .0 , cp) ;
6417
assert! ( repo.latest_checkpoint ( " feature" ) .unwrap ( ) .is_none ( ) ) ;
6418
let heads = repo.resolve_heads ( " feature/x" ) .unwrap ( ) ;
6419
assert! ( repo.load_state_checkpointed ( & heads) .unwrap ( ) .is_some ( ) ) ;
f8c69576 Andy McCall 1 month ago ↶
6420
}
6421
6422
6423
6424
6425
# [ test ]
6426
fn bookmarks_are_deleted_on_a_remote ( ) {
6427
hermetic ( ) ;
6428
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6429
let root = dir.path ( ) ;
6430
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
6431
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
6432
let mut repo = Repo:: clone( & url, & root.join ( " r" ) )
6433
.unwrap ( )
6434
.with_home ( root.join ( " home" ) ) ;
6435
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
6436
.unwrap ( ) ;
6437
let r = root.join ( " r" ) ;
6438
write ( & r, " a.txt" , b " a\n " ) ;
6439
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
6440
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
6441
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6442
repo.bookmark_create ( " feature/x" , None ) .unwrap ( ) ;
6443
assert! ( repo.push_bookmark ( " origin" , " feature/x" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
6444
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6445
assert! ( origin.resolve_heads ( " feature/x" ) .is_ok ( ) ) ;
6446
6447
let res = repo.push_delete ( " origin" , " feature/x" ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
6448
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6449
assert! ( res[ 0 ] .ok, " {res:?}" ) ;
6450
assert! ( ! origin.view ( ) .unwrap ( ) .bookmarks.contains_key ( " feature/x" ) ) ;
6451
let ( _ , att) = origin.latest_ref_attest ( " feature/x" ) .unwrap ( ) .unwrap ( ) ;
6452
assert! (
6453
matches! ( att.attest.body, AttestBody:: Ref { new_state, .. } if new_state.is_zero ( ) ) ,
6454
" the deletion is attested"
6455
) ;
6456
assert! (
6457
repo.view ( ) .unwrap ( ) .bookmarks.contains_key ( " feature/x" ) ,
6458
" the local bookmark stays"
6459
) ;
6460
assert! ( ! repo
6461
.view ( )
6462
.unwrap ( )
6463
.remotes
6464
.get ( " origin" )
6465
.is_some_and ( | m | m.contains_key ( " feature/x" ) ) ) ;
6466
6467
6468
let res = repo.push_bookmark ( " origin" , " feature/x" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
6469
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6470
assert! ( res[ 0 ] .ok, " {res:?}" ) ;
6471
assert! ( origin.view ( ) .unwrap ( ) .bookmarks.contains_key ( " feature/x" ) ) ;
6472
6473
6474
assert! ( repo.push_delete ( " origin" , " nope" ) .is_err ( ) ) ;
6475
let res = repo.push_delete ( " origin" , " main" ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
6476
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6477
assert! (
6478
! res[ 0 ] .ok & & res[ 0 ] .reason.contains ( " default bookmark" ) ,
6479
" {res:?}"
6480
) ;
6481
assert! ( origin.view ( ) .unwrap ( ) .bookmarks.contains_key ( " main" ) ) ;
54cd08a1 Andy McCall 1 month ago ↶
6482
}
61e3bce7 Andy McCall 1 month ago ↶
6483
6484
6485
6486
6487
# [ test ]
6488
fn switching_bookmarks_carries_uncommitted_work ( ) {
6489
hermetic ( ) ;
6490
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6491
let root = dir.path ( ) ;
6492
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6493
write ( root, " a.txt" , b " a\n " ) ;
6494
write ( root, " b.txt" , b " b\n " ) ;
6495
write ( root, " gone.txt" , b " gone\n " ) ;
6496
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6497
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
6498
6499
write ( root, " a.txt" , b " a\n more\n " ) ;
6500
write ( root, " new.txt" , b " new\n " ) ;
6501
fs:: remove_file( root.join ( " gone.txt" ) ) .unwrap ( ) ;
6502
let ( _ , carried) = repo.bookmark_switch_carry ( " feature" ) .unwrap ( ) ;
6503
assert_eq! ( carried, vec! [ " a.txt" , " gone.txt" , " new.txt" ] ) ;
6504
assert_eq! ( repo.config.bookmark, " feature" ) ;
6505
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " a\n more\n " ) ;
6506
assert_eq! ( fs:: read( root.join ( " new.txt" ) ) .unwrap ( ) , b " new\n " ) ;
6507
assert! ( ! root.join ( " gone.txt" ) .exists ( ) ) ;
6508
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
6509
assert_eq! (
6510
statuses ( & snap) ,
6511
vec! [
6512
( " a.txt" .to_string ( ) , ChangeStatus:: Modified) ,
6513
( " gone.txt" .to_string ( ) , ChangeStatus:: Removed) ,
6514
( " new.txt" .to_string ( ) , ChangeStatus:: Added)
6515
]
6516
) ;
6517
repo.commit ( " the work" , & [ ] ) .unwrap ( ) ;
6518
assert! ( repo.resolve_heads ( " main" ) .unwrap ( ) .len ( ) = = 1 ) ;
6519
6520
6521
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
6522
write ( root, " b.txt" , b " b on main\n " ) ;
6523
repo.commit ( " b on main" , & [ ] ) .unwrap ( ) ;
6524
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
6525
write ( root, " b.txt" , b " b edited\n " ) ;
6526
let err = repo.bookmark_switch_carry ( " main" ) .unwrap_err ( ) .to_string ( ) ;
6527
assert! ( err.contains ( " b.txt" ) & & err.contains ( " --discard" ) , " {err}" ) ;
6528
assert_eq! ( repo.config.bookmark, " feature" , " nothing happened" ) ;
6529
assert_eq! ( fs:: read( root.join ( " b.txt" ) ) .unwrap ( ) , b " b edited\n " ) ;
6530
}
1c2bd9ab Andy McCall 1 month ago ↶
6531
6532
6533
6534
6535
# [ test ]
6536
fn merges_move_clean_working_copies_and_checkouts_say_what_blocks_them ( ) {
6537
hermetic ( ) ;
6538
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6539
let root = dir.path ( ) ;
6540
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6541
write ( root, " a.txt" , b " a\n " ) ;
6542
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
6543
write ( root, " b.txt" , b " b\n " ) ;
6544
repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
6545
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
6546
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
6547
write ( root, " c.txt" , b " c\n " ) ;
6548
repo.commit ( " c on feature" , & [ ] ) .unwrap ( ) ;
6549
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
6550
6551
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
6552
assert! ( ! root.join ( " b.txt" ) .exists ( ) ) ;
6553
let m = repo.merge ( " feature" ) .unwrap ( ) ;
6554
assert! ( m.checked_out, " a clean working copy follows the merge" ) ;
6555
for f in [ " a.txt" , " b.txt" , " c.txt" ] {
6556
assert! ( root.join ( f) .exists ( ) , " {f}" ) ;
6557
}
6558
6559
6560
fs:: remove_file( root.join ( " b.txt" ) ) .unwrap ( ) ;
6561
write ( root, " a.txt" , b " a edited\n " ) ;
6562
let err = repo
6563
.checkout ( & [ h1] , false , " checkout" )
6564
.unwrap_err ( )
6565
.to_string ( ) ;
6566
assert! (
6567
err.contains ( " modified a.txt" ) & & err.contains ( " removed b.txt" ) & & err.contains ( " --carry" ) ,
6568
" {err}"
6569
) ;
fe9586d9 Andy McCall 26 days ago ↶
6570
6571
6572
1c2bd9ab Andy McCall 1 month ago ↶
6573
let ( _ , carried) = repo
6574
.checkout_carry ( & [ h1] , " the first change" , " checkout" )
6575
.unwrap ( ) ;
6576
assert_eq! ( carried, vec! [ " a.txt" ] ) ;
6577
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " a edited\n " ) ;
6578
assert! ( ! root.join ( " b.txt" ) .exists ( ) , " h1 has no b.txt" ) ;
6579
}
e7ac6c2e Andy McCall 1 month ago ↶
6580
6581
6582
6583
6584
# [ test ]
6585
fn a_new_repository_is_healthy_before_its_first_commit ( ) {
6586
hermetic ( ) ;
6587
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6588
let root = dir.path ( ) ;
6589
let home = root.join ( " home" ) ;
6590
let mut first = Repo:: init( & root.join ( " first" ) )
6591
.unwrap ( )
6592
.with_home ( home.clone ( ) ) ;
6593
let ( key, _ ) = first
6594
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
6595
.unwrap ( ) ;
6596
let repo = Repo:: init( & root.join ( " second" ) )
6597
.unwrap ( )
6598
.with_home ( home.clone ( ) ) ;
6599
6600
let keys = repo.keys_dir.clone ( ) ;
6601
let _ = fs:: remove_file( keys.join ( format! ( " {} .identity" , key.to_hex ( ) ) ) ) ;
6602
assert! ( keys.join ( format! ( " {} .secret" , key.to_hex ( ) ) ) .is_file ( ) ) ;
6603
let r = repo.doctor ( true ) ;
6604
let id = r.checks.iter ( ) .find ( | c | c.area = = " identity" ) .unwrap ( ) ;
6605
assert_eq! ( id.health, Health:: Ok, " {}" , id.message) ;
6606
assert! ( id.message.contains ( " first signed change" ) , " {}" , id.message) ;
6607
6608
6609
let lib = root.join ( " lib" ) ;
6610
let l = Repo:: init( & lib) .unwrap ( ) .with_home ( home.clone ( ) ) ;
6611
write ( & lib, " x.txt" , b " x\n " ) ;
6612
l.commit ( " x" , & [ ] ) .unwrap ( ) ;
6613
let second = root.join ( " second" ) ;
6614
write ( & second, " a.txt" , b " a\n " ) ;
6615
repo.submodule_add ( & lib.to_string_lossy ( ) , " deps/lib" , None )
6616
.unwrap ( ) ;
6617
let subs = repo.subrepos ( ) .unwrap ( ) ;
6618
assert_eq! ( subs.len ( ) , 1 ) ;
6619
assert_eq! (
6620
( subs[ 0 ] .path.as_str ( ) , subs[ 0 ] .committed) ,
6621
( " deps/lib" , false )
6622
) ;
6623
assert_eq! ( subs[ 0 ] .actual.as_ref ( ) , Some ( & subs[ 0 ] .pin) ) ;
6624
repo.commit ( " add lib" , & [ ] ) .unwrap ( ) ;
6625
let subs = repo.subrepos ( ) .unwrap ( ) ;
6626
assert! ( subs[ 0 ] .committed, " committed now" ) ;
6627
}
8ab265dc Andy McCall 1 month ago ↶
6628
6629
6630
6631
6632
# [ cfg ( unix ) ]
6633
# [ test ]
6634
fn commits_and_diffs_leave_unchanged_files_alone ( ) {
6635
use std:: os:: unix:: fs:: PermissionsExt;
6636
hermetic ( ) ;
6637
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6638
let root = dir.path ( ) ;
6639
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6640
write ( root, " big.txt" , b " lots\n of\n lines\n " ) ;
6641
write ( root, " a.txt" , b " one\n two\n " ) ;
6642
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6643
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6644
for f in [ " big.txt" , " a.txt" ] {
6645
fs:: File:: options( )
6646
.write ( true )
6647
.open ( root.join ( f) )
6648
.unwrap ( )
6649
.set_modified ( old)
6650
.unwrap ( ) ;
6651
}
6652
assert! ( repo.snapshot_status ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6653
6654
fs:: set_permissions( root.join ( " big.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6655
6656
write ( root, " a.txt" , b " one\n TWO\n three\n " ) ;
6657
let diff = repo.diff_files ( None ) .unwrap ( ) ;
6658
assert_eq! (
6659
diff.iter ( ) .map ( | f | f.path.as_str ( ) ) .collect:: < Vec< _ > > ( ) ,
6660
[ " a.txt" ]
6661
) ;
6662
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
6663
assert! ( ! hunks.is_empty ( ) & & hunks.iter ( ) .all ( | h | h.path = = " a.txt" ) ) ;
6664
let h = repo.commit ( " edit a" , & [ ] ) .unwrap ( ) ;
6665
6666
6667
let c = repo.store ( ) .get_change ( & h) .unwrap ( ) ;
6668
let full = repo.load_state ( & c.deps) .unwrap ( ) ;
6669
let want = render_change ( & c, & full, & full.materialize ( ) ) ;
6670
assert_eq! ( repo.describe_change ( & h) .unwrap ( ) , want) ;
6671
let ( base, snap) = repo.change_base ( & c) .unwrap ( ) ;
6672
assert! ( ! base.is_full ( ) & & snap.unrendered.contains ( " big.txt" ) ) ;
6673
6674
6675
write ( root, " a.txt" , b " scratch\n " ) ;
6676
assert_eq! ( repo.discard ( & [ " a.txt" .into ( ) ] ) .unwrap ( ) , 1 ) ;
6677
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n TWO\n three\n " ) ;
6678
fs:: set_permissions( root.join ( " big.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6679
}
f7510b63 Andy McCall 1 month ago ↶
6680
6681
6682
6683
6684
# [ test ]
6685
fn replace_conflicts_are_settled_when_asked ( ) {
6686
hermetic ( ) ;
6687
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6688
let root = dir.path ( ) ;
6689
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6690
let blob = | n : u8 | {
6691
let mut v = vec! [ 0 u8 ; 64 ] ;
6692
v[ 1 ] = n;
6693
v
6694
} ;
6695
write ( root, " img.bin" , & blob ( 1 ) ) ;
6696
repo.commit ( " add img" , & [ ] ) .unwrap ( ) ;
6697
repo.bookmark_create ( " other" , None ) .unwrap ( ) ;
6698
write ( root, " img.bin" , & blob ( 2 ) ) ;
6699
repo.commit ( " img two" , & [ ] ) .unwrap ( ) ;
6700
repo.bookmark_switch ( " other" , false ) .unwrap ( ) ;
6701
write ( root, " img.bin" , & blob ( 3 ) ) ;
6702
repo.commit ( " img three" , & [ ] ) .unwrap ( ) ;
6703
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
6704
repo.merge ( " other" ) .unwrap ( ) ;
6705
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6706
let merged = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
6707
assert! (
6708
matches! ( & merged.files[ " img.bin" ] .content, FileContent:: Opaque { blobs } if blobs.len ( ) = = 2 ) ,
6709
" a replace conflict"
6710
) ;
6711
6712
assert! ( repo
6713
.snapshot ( " status" )
6714
.unwrap ( )
6715
.statuses
6716
.iter ( )
6717
.all ( | s | s.path ! = " img.bin" ) ) ;
6718
6719
6720
let author = repo.author ( ) ;
6721
let edits = [ FileEdit {
6722
path: " img.bin" .into ( ) ,
6723
content: Some ( blob ( 3 ) ) ,
6724
} ] ;
6725
let c = repo
6726
.build_change ( & heads, & edits, " keep three" , author)
6727
.unwrap ( ) ;
6728
assert_eq! ( c.kind, ChangeKind:: Resolution) ;
6729
let mut st = repo.load_state ( & heads) .unwrap ( ) ;
6730
for d in & c.deps {
6731
assert! ( st.contains ( d) ) ;
6732
}
6733
assert! (
6734
heads.iter ( ) .all ( | h | c.deps.contains ( h) ) ,
6735
" depends on both contributors"
6736
) ;
6737
st.add ( c) .unwrap ( ) ;
6738
let after = st.materialize ( ) ;
6739
assert! ( ! after.is_conflicted ( ) ) ;
6740
assert_eq! (
6741
after.files[ " img.bin" ] .content,
6742
FileContent:: Opaque {
6743
blobs: vec! [ Repo:: blob_ref( & blob ( 3 ) ) ]
6744
}
6745
) ;
6746
6747
6748
let edits = [ FileEdit {
6749
path: " img.bin" .into ( ) ,
6750
content: Some ( blob ( 9 ) ) ,
6751
} ] ;
6752
let c = repo.build_change ( & heads, & edits, " new" , author) .unwrap ( ) ;
6753
let mut st = repo.load_state ( & heads) .unwrap ( ) ;
6754
st.add ( c) .unwrap ( ) ;
6755
assert_eq! (
6756
st.materialize ( ) .files[ " img.bin" ] .content,
6757
FileContent:: Opaque {
6758
blobs: vec! [ Repo:: blob_ref( & blob ( 9 ) ) ]
6759
}
6760
) ;
6761
}
83d61e80 Andy McCall 1 month ago ↶
6762
6763
6764
6765
6766
# [ test ]
6767
fn line_ranges_have_exact_histories ( ) {
6768
hermetic ( ) ;
6769
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6770
let root = dir.path ( ) ;
6771
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6772
write ( root, " f.rs" , b " use x;\n fn a() {\n one();\n two();\n }\n " ) ;
6773
let base = repo.commit ( " add f" , & [ ] ) .unwrap ( ) ;
6774
write ( root, " f.rs" , b " use y;\n fn a() {\n one();\n two();\n }\n " ) ;
6775
let outside = repo.commit ( " change the import" , & [ ] ) .unwrap ( ) ;
6776
write ( root, " f.rs" , b " use y;\n fn a() {\n ONE();\n two();\n }\n " ) ;
6777
let inside = repo.commit ( " edit a line of a" , & [ ] ) .unwrap ( ) ;
6778
write ( root, " f.rs" , b " use y;\n fn a() {\n ONE();\n }\n " ) ;
6779
let deleted = repo.commit ( " delete a line of a" , & [ ] ) .unwrap ( ) ;
6780
write ( root, " f.rs" , b " use y;\n fn a() {\n ONE();\n }\n fn b() {}\n " ) ;
6781
let after = repo.commit ( " add b after a" , & [ ] ) .unwrap ( ) ;
6782
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6783
6784
6785
let hist: Vec< ChangeHash> = repo
6786
.line_history ( & heads, " f.rs" , 2 , 4 )
6787
.unwrap ( )
6788
.iter ( )
6789
.map ( | e | e.hash )
6790
.collect ( ) ;
6791
for ( h, what) in [
6792
( base, " wrote it" ) ,
6793
( inside, " edited a line" ) ,
6794
( deleted, " deleted a line from inside" ) ,
6795
] {
6796
assert! ( hist.contains ( & h) , " {what}" ) ;
6797
}
6798
for ( h, what) in [ ( outside, " an edit above" ) , ( after, " an addition below" ) ] {
6799
assert! ( ! hist.contains ( & h) , " {what}" ) ;
6800
}
6801
assert_eq! ( hist[ 0 ] , deleted, " newest first" ) ;
6802
6803
let one: Vec< ChangeHash> = repo
6804
.line_history ( & heads, " f.rs" , 1 , 1 )
6805
.unwrap ( )
6806
.iter ( )
6807
.map ( | e | e.hash )
6808
.collect ( ) ;
6809
assert! ( one.contains ( & outside) & & ! one.contains ( & inside) ) ;
6810
assert! ( repo.line_history ( & heads, " f.rs" , 3 , 9 ) .is_err ( ) ) ;
6811
assert! ( repo.line_history ( & heads, " f.rs" , 0 , 1 ) .is_err ( ) ) ;
6812
assert! ( repo.line_history ( & heads, " nope.rs" , 1 , 1 ) .is_err ( ) ) ;
6813
}
fc1a466a Andy McCall 1 month ago ↶
6814
6815
# [ test ]
6816
fn receipts_are_kept_with_their_state_and_verified ( ) {
6817
use crate :: receipt:: { Receipt, SignedReceipt} ;
6818
hermetic ( ) ;
6819
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6820
let root = dir.path ( ) ;
6821
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6822
write ( root, " a.txt" , b " 1\n " ) ;
6823
repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
6824
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6825
let state = repo
6826
.store ( )
6827
.put_state ( & StateObject:: new( heads.clone ( ) ) )
6828
.unwrap ( ) ;
6829
let out = repo.store_blob ( b " built\n " ) .unwrap ( ) ;
6830
let receipt = Receipt {
6831
job_name: " build" .into ( ) ,
6832
pipeline_hash: ObjectRef( [ 1 ; 32 ] ) ,
6833
scope_tree_hash: ObjectRef( [ 2 ; 32 ] ) ,
6834
inputs: vec! [ ] ,
6835
environment: ObjectRef( [ 3 ; 32 ] ) ,
6836
outputs: vec! [ out] ,
6837
exit_code: 0 ,
6838
duration_ms: 42 ,
6839
} ;
6840
let sk = SecretKey:: generate( ) ;
6841
let runner = sk.identity ( " earth-1" , " runner@earth" , 1 ) ;
6842
let sr = SignedReceipt:: sign(
6843
receipt.clone ( ) ,
6844
repo.id ( ) .unwrap ( ) ,
6845
state,
6846
" demo#1" ,
6847
& runner,
6848
& sk,
6849
) ;
6850
6851
let r = repo.add_receipt ( & sr) .unwrap ( ) ;
6852
let found = repo.receipts ( & heads) .unwrap ( ) ;
6853
assert_eq! ( found.len ( ) , 1 ) ;
6854
assert_eq! ( found[ 0 ] .attest, r) ;
6855
assert_eq! ( found[ 0 ] .signed, sr) ;
6856
assert! ( found[ 0 ] .problem.is_none ( ) & & ! found[ 0 ] .revoked) ;
6857
assert! ( repo.receipts ( & [ ObjectRef( [ 5 ; 32 ] ) ] ) .unwrap ( ) .is_empty ( ) ) ;
6858
6859
6860
let elsewhere = SignedReceipt:: sign( receipt.clone ( ) , [ 0 ; 16 ] , state, " x" , & runner, & sk) ;
6861
assert! ( repo
6862
.add_receipt ( & elsewhere)
6863
.unwrap_err ( )
6864
.to_string ( )
6865
.contains ( " another repository" ) ) ;
6866
let mut forged = sr.clone ( ) ;
6867
forged.receipt.exit_code = 1 ;
6868
assert! ( repo
6869
.add_receipt ( & forged)
6870
.unwrap_err ( )
6871
.to_string ( )
6872
.contains ( " does not verify" ) ) ;
6873
c63fed2e Andy McCall 20 days ago ↶
6874
6875
6876
let hub_sk = SecretKey:: generate( ) ;
6877
let hub = hub_sk.identity ( " oxygenhub.io" , " [email protected] " , 1 ) ;
6878
let job_sk = SecretKey:: generate( ) ;
6879
let job = job_sk.identity ( " job 2 test" , " [email protected] " , 1 ) ;
6880
let mut test_receipt = receipt.clone ( ) ;
6881
test_receipt.job_name = " test" .into ( ) ;
6882
let plain = SignedReceipt:: sign(
6883
test_receipt,
6884
repo.id ( ) .unwrap ( ) ,
6885
state,
6886
" demo#2" ,
6887
& job,
6888
& job_sk,
6889
) ;
6890
let now = plain.attest.hlc.wall_ms;
6891
let cert = crate :: cert:: SignedCertificate:: sign(
6892
crate :: cert:: Certificate {
6893
key: job.clone ( ) ,
6894
issuer: hub.hash ( ) ,
6895
subject: " repo:andy/demo:bookmark:main" .into ( ) ,
6896
claims: vec! [ ] ,
6897
scope: crate :: identity:: Scope:: SEAL ,
6898
not_before: now - 1_000 ,
6899
expires: now + 600_000 ,
a2387d54 Andy McCall 20 days ago ↶
6900
grant: None ,
c63fed2e Andy McCall 20 days ago ↶
6901
} ,
6902
& hub,
6903
& hub_sk,
6904
) ;
6905
let certified = plain.with_certificate ( cert, hub.clone ( ) ) ;
6906
repo.add_receipt ( & certified) .unwrap ( ) ;
6907
let found = repo.receipts ( & heads) .unwrap ( ) ;
6908
let got = found
6909
.iter ( )
6910
.find ( | i | i.signed.receipt.job_name = = " test" )
6911
.unwrap ( ) ;
6912
assert_eq! ( got.signed, certified) ;
6913
assert! ( got.problem.is_none ( ) , " {:?}" , got.problem) ;
6914
6915
6916
fc1a466a Andy McCall 1 month ago ↶
6917
repo.gc ( true ) .unwrap ( ) ;
c63fed2e Andy McCall 20 days ago ↶
6918
let found = repo.receipts ( & heads) .unwrap ( ) ;
6919
assert_eq! ( found.len ( ) , 2 ) ;
6920
let got = found
6921
.iter ( )
6922
.find ( | i | i.signed.receipt.job_name = = " test" )
6923
.unwrap ( ) ;
6924
assert_eq! ( got.signed, certified, " the certificate survives gc" ) ;
6925
assert! ( got.problem.is_none ( ) ) ;
fc1a466a Andy McCall 1 month ago ↶
6926
assert_eq! ( repo.read_blob ( & out) .unwrap ( ) , b " built\n " ) ;
6927
assert! ( repo.fsck ( ) .unwrap ( ) .problems.is_empty ( ) ) ;
dfb23a3a Andy McCall 1 month ago ↶
6928
}
6929
6930
# [ test ]
3a4b3e01 Andy McCall 21 days ago ↶
6931
fn approvals_are_kept_with_their_state_and_survive_gc ( ) {
6932
hermetic ( ) ;
6933
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6934
let root = dir.path ( ) ;
6935
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6936
write ( root, " a.txt" , b " 1\n " ) ;
6937
repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
6938
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6939
let state = repo.store ( ) .put_state ( & StateObject:: new( heads) ) .unwrap ( ) ;
6940
let plan = repo.store_blob ( b " the reviewed plan\n " ) .unwrap ( ) ;
6941
let sk = SecretKey:: generate( ) ;
6942
let andy = sk.identity ( " Andy" , " [email protected] " , 1 ) ;
6943
let statement = crate :: approval:: Statement {
6944
repo: repo.id ( ) .unwrap ( ) ,
6945
run: " https://hub.example/andy/infra/flow/8" .into ( ) ,
6946
job: " apply" .into ( ) ,
6947
job_hash: ObjectRef( [ 1 ; 32 ] ) ,
6948
state,
6949
inputs: vec! [ plan] ,
6950
environment: " prod" .into ( ) ,
6951
approve: true ,
6952
} ;
6953
let sa = crate :: approval:: SignedApproval:: sign( & statement, & andy, & sk) ;
6954
let r = repo.add_approval ( & sa) .unwrap ( ) ;
6955
assert_eq! ( repo.store ( ) .approvals_for ( & state) .unwrap ( ) , vec! [ r] ) ;
6956
6957
let elsewhere = crate :: approval:: SignedApproval:: sign(
6958
& crate :: approval:: Statement {
6959
repo: [ 0 ; 16 ] ,
6960
.. statement.clone ( )
6961
} ,
6962
& andy,
6963
& sk,
6964
) ;
6965
assert! ( repo
6966
.add_approval ( & elsewhere)
6967
.unwrap_err ( )
6968
.to_string ( )
6969
.contains ( " another repository" ) ) ;
6970
let mut forged = sa.clone ( ) ;
6971
if let AttestBody:: Approval { environment, .. } = & mut forged.attest.body {
6972
* environment = " dev" .into ( ) ;
6973
}
6974
assert! ( repo
6975
.add_approval ( & forged)
6976
.unwrap_err ( )
6977
.to_string ( )
6978
.contains ( " does not verify" ) ) ;
6979
6980
repo.gc ( true ) .unwrap ( ) ;
6981
assert_eq! ( repo.store ( ) .approvals_for ( & state) .unwrap ( ) , vec! [ r] ) ;
6982
assert_eq! ( repo.store ( ) .get_attest ( & r) .unwrap ( ) , sa.attest) ;
6983
assert_eq! ( repo.store ( ) .signatures ( & r) .unwrap ( ) , sa.sigs) ;
6984
assert! ( repo.fsck ( ) .unwrap ( ) .problems.is_empty ( ) ) ;
6985
}
6986
6987
# [ test ]
dfb23a3a Andy McCall 1 month ago ↶
6988
fn status_leaves_an_unchanged_manifest_alone ( ) {
6989
hermetic ( ) ;
6990
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6991
let root = dir.path ( ) ;
6992
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6993
write ( root, " a.txt" , b " 1\n " ) ;
6994
write ( root, " b.txt" , b " 2\n " ) ;
6995
repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
6996
6997
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 60 ) ;
6998
for f in [ " a.txt" , " b.txt" ] {
6999
fs:: File:: options( )
7000
.write ( true )
7001
.open ( root.join ( f) )
7002
.unwrap ( )
7003
.set_modified ( old)
7004
.unwrap ( ) ;
7005
}
7006
repo.snapshot_status ( " status" ) .unwrap ( ) ;
7007
let path = repo.manifest_path ( ) ;
7008
let first = fs:: read( & path) .unwrap ( ) ;
7009
repo.snapshot_status ( " status" ) .unwrap ( ) ;
7010
assert_eq! (
7011
fs:: read( & path) .unwrap ( ) ,
7012
first,
7013
" an unchanged tree does not rewrite the manifest"
7014
) ;
7015
7016
write ( root, " a.txt" , b " 1\n 2\n " ) ;
7017
repo.snapshot_status ( " status" ) .unwrap ( ) ;
7018
assert_ne! ( fs:: read( & path) .unwrap ( ) , first) ;
fc1a466a Andy McCall 1 month ago ↶
7019
}
6f8aeb99 Andy McCall 1 month ago ↶
7020
7021
# [ test ]
7022
fn large_text_files_travel_and_survive_gc_as_text_blobs ( ) {
7023
hermetic ( ) ;
7024
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7025
let root = dir.path ( ) ;
7026
let origin_path = root.join ( " origin" ) ;
7027
Repo:: init( & origin_path) .unwrap ( ) ;
7028
let origin_url = origin_path.to_string_lossy ( ) .to_string ( ) ;
7029
let a = Repo:: clone( & origin_url, & root.join ( " a" ) ) .unwrap ( ) ;
7030
let big: String = ( 0 .. 5000 ) .map ( | i | format! ( " line {i} \n " ) ) .collect ( ) ;
7031
write ( & root.join ( " a" ) , " big.txt" , big.as_bytes ( ) ) ;
7032
write ( & root.join ( " a" ) , " small.txt" , b " small\n " ) ;
7033
let h1 = a.commit ( " large" , & [ ] ) .unwrap ( ) ;
7034
let ( _ , body) = a.store ( ) .get ( & h1) .unwrap ( ) ;
7035
assert! ( body.len ( ) < 2048 , " the change is {} bytes" , body.len ( ) ) ;
7036
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7037
7038
7039
let b = Repo:: clone( & origin_url, & root.join ( " b" ) ) .unwrap ( ) ;
7040
assert_eq! ( fs:: read( root.join ( " b/big.txt" ) ) .unwrap ( ) , big.as_bytes ( ) ) ;
7041
7042
let edited = big.replace ( " line 2500\n " , " line 2500, edited\n " ) ;
7043
write ( & root.join ( " b" ) , " big.txt" , edited.as_bytes ( ) ) ;
7044
let h2 = b.commit ( " edit" , & [ ] ) .unwrap ( ) ;
7045
assert! ( b.store ( ) .get_change ( & h2) .unwrap ( ) .edits.len ( ) < = 2 ) ;
7046
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7047
assert! ( a.pull ( " origin" ) .unwrap ( ) .checked_out) ;
7048
assert_eq! ( fs:: read( root.join ( " a/big.txt" ) ) .unwrap ( ) , edited.as_bytes ( ) ) ;
7049
assert_eq! (
7050
a.fsck ( ) .unwrap ( ) .errors ( ) ,
7051
0 ,
7052
" {:?}" ,
7053
a.fsck ( ) .unwrap ( ) .problems
7054
) ;
7055
7056
7057
let mut a = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) ;
7058
a.config.set ( " core.op-retention" , " 0" ) .unwrap ( ) ;
7059
a.save_config ( ) .unwrap ( ) ;
7060
let a = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) ;
7061
a.gc ( true ) .unwrap ( ) ;
7062
let a = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) ;
7063
assert_eq! ( a.store ( ) .get_change ( & h1) .unwrap ( ) .message, " large" ) ;
7064
assert_eq! (
7065
a.fsck ( ) .unwrap ( ) .errors ( ) ,
7066
0 ,
7067
" {:?}" ,
7068
a.fsck ( ) .unwrap ( ) .problems
7069
) ;
7070
7071
let c = Repo:: clone( & root.join ( " a" ) .to_string_lossy ( ) , & root.join ( " c" ) ) .unwrap ( ) ;
7072
assert_eq! ( fs:: read( root.join ( " c/big.txt" ) ) .unwrap ( ) , edited.as_bytes ( ) ) ;
7073
drop ( c) ;
6caacfff Andy McCall 1 month ago ↶
7074
}
7075
7076
# [ test ]
7077
fn operations_that_both_replace_a_working_copy_are_not_merged ( ) {
7078
hermetic ( ) ;
7079
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7080
let root = dir.path ( ) ;
7081
let repo = Repo:: init( root) .unwrap ( ) ;
7082
write ( root, " a.txt" , b " a\n " ) ;
7083
let a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
7084
write ( root, " b.txt" , b " b\n " ) ;
7085
repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
7086
let ( x, op) = repo.head_op ( ) .unwrap ( ) ;
7087
let ws = * op.view.workspaces.keys ( ) .next ( ) .unwrap ( ) ;
7088
let mut one = op.view.clone ( ) ;
7089
one.workspaces.insert ( ws, a) ;
7090
let mut two = op.view.clone ( ) ;
7091
two.workspaces.insert ( ws, ObjectRef( [ 7 ; 32 ] ) ) ;
7092
let first = repo.record_op ( & [ x] , " one" , one) .unwrap ( ) ;
7093
let e = repo.record_op ( & [ x] , " two" , two) .unwrap_err ( ) ;
7094
assert! (
7095
e.to_string ( )
7096
.contains ( " changed this working copy at the same time" ) ,
7097
" {e}"
7098
) ;
7099
assert_eq! (
7100
repo.head_op ( ) .unwrap ( ) .0 ,
7101
first,
7102
" the second is not recorded"
7103
) ;
7104
}
7105
7106
7107
7108
7109
# [ test ]
7110
fn a_snapshot_during_a_switch_never_records_the_switch_as_edits ( ) {
7111
hermetic ( ) ;
7112
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7113
let root = dir.path ( ) .to_path_buf ( ) ;
7114
let mut repo = Repo:: init( & root) .unwrap ( ) ;
7115
write ( & root, " keep.txt" , b " keep\n " ) ;
7116
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7117
repo.bookmark_create ( " small" , None ) .unwrap ( ) ;
7118
for i in 0 .. 300 {
7119
write (
7120
& root,
7121
& format! ( " many/f{i} .txt" ) ,
7122
format! ( " file {i} \n " ) .as_bytes ( ) ,
7123
) ;
7124
}
7125
repo.commit ( " many" , & [ ] ) .unwrap ( ) ;
7126
let stop = std:: sync:: Arc:: new( std:: sync:: atomic:: AtomicBool:: new( false ) ) ;
7127
let watcher = {
7128
let ( root, stop) = ( root.clone ( ) , stop.clone ( ) ) ;
7129
std:: thread:: spawn( move | | {
7130
let r = Repo:: open( & root) .unwrap ( ) ;
7131
let mut ran = 0 ;
7132
while ! stop.load ( std:: sync:: atomic:: Ordering:: Relaxed) {
7133
if r.snapshot_status ( " oxy status" ) .is_ok ( ) {
7134
ran + = 1 ;
7135
}
7136
}
7137
ran
7138
} )
7139
} ;
7140
for i in 0 .. 20 {
7141
let to = if i % 2 = = 0 { " small" } else { " main" } ;
7142
repo.bookmark_switch ( to, false ) .unwrap ( ) ;
7143
}
7144
stop.store ( true , std:: sync:: atomic:: Ordering:: Relaxed) ;
7145
assert! ( watcher.join ( ) .unwrap ( ) > 0 , " the watcher snapshotted" ) ;
7146
7147
let repo = Repo:: open( & root) .unwrap ( ) ;
7148
let s = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7149
assert! ( s.statuses.is_empty ( ) , " {:?}" , statuses ( & s) ) ;
7150
assert! ( s.wc.edits.is_empty ( ) & & s.wc.tree_ops.is_empty ( ) ) ;
7151
assert_eq! ( s.wc.deps, repo.bookmark_heads ( ) .unwrap ( ) ) ;
7152
assert! ( root.join ( " many/f299.txt" ) .exists ( ) ) ;
1a3b6203 Andy McCall 1 month ago ↶
7153
}
7154
7155
7156
7157
7158
# [ test ]
7159
fn a_parent_switch_with_carry_leaves_a_subrepo_ahead_of_its_pin_alone ( ) {
7160
hermetic ( ) ;
7161
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7162
let root = dir.path ( ) ;
7163
Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
7164
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
7165
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) ) .unwrap ( ) ;
7166
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
7167
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
7168
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7169
7170
let app_dir = root.join ( " app" ) ;
7171
let mut app = Repo:: init( & app_dir) .unwrap ( ) ;
7172
write ( & app_dir, " main.txt" , b " app\n " ) ;
7173
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
7174
app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
7175
app.bookmark_create ( " feature" , None ) .unwrap ( ) ;
7176
7177
7178
let nested_dir = app_dir.join ( " libs/lib" ) ;
7179
let nested = Repo:: open( & nested_dir) .unwrap ( ) ;
7180
write ( & nested_dir, " new.txt" , b " new\n " ) ;
7181
let l2 = nested.commit ( " lib: new file" , & [ ] ) .unwrap ( ) ;
7182
drop ( nested) ;
7183
7184
let ( _ , carried) = app.bookmark_switch_carry ( " feature" ) .unwrap ( ) ;
7185
assert! ( carried.contains ( & " libs/lib" .to_string ( ) ) , " {carried:?}" ) ;
7186
let nested = Repo:: open( & nested_dir) .unwrap ( ) ;
7187
assert! (
7188
nested.bookmark_heads ( ) .unwrap ( ) .contains ( & l2) ,
7189
" its bookmark kept its work"
7190
) ;
7191
assert! ( nested_dir.join ( " new.txt" ) .exists ( ) , " and its files" ) ;
7192
let s = app.snapshot ( " status" ) .unwrap ( ) ;
7193
assert_eq! (
7194
statuses ( & s) ,
7195
vec! [ ( " libs/lib" .to_string ( ) , ChangeStatus:: Modified) ]
7196
) ;
6f8aeb99 Andy McCall 1 month ago ↶
7197
}
7d9dab13 Andy McCall 1 month ago ↶
7198
7199
7200
7201
7202
# [ test ]
7203
fn pull_takes_a_remote_move_to_changes_already_here ( ) {
7204
hermetic ( ) ;
7205
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7206
let root = dir.path ( ) ;
7207
let origin_path = root.join ( " origin" ) ;
7208
let origin = Repo:: init( & origin_path) .unwrap ( ) ;
7209
let url = origin_path.to_string_lossy ( ) .to_string ( ) ;
7210
let mut b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) ;
7211
write ( & root.join ( " b" ) , " base.txt" , b " base\n " ) ;
7212
b.commit ( " base" , & [ ] ) .unwrap ( ) ;
7213
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
7214
origin.refresh ( ) .unwrap ( ) ;
7d9dab13 Andy McCall 1 month ago ↶
7215
7216
7217
b.bookmark_create ( " feat" , None ) .unwrap ( ) ;
7218
b.bookmark_switch ( " feat" , false ) .unwrap ( ) ;
7219
write ( & root.join ( " b" ) , " x.txt" , b " x\n " ) ;
7220
let x = b.commit ( " x" , & [ ] ) .unwrap ( ) ;
7221
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
7222
origin.refresh ( ) .unwrap ( ) ;
7d9dab13 Andy McCall 1 month ago ↶
7223
b.bookmark_switch ( " main" , false ) .unwrap ( ) ;
7224
7225
7226
let feat = origin
7227
.store ( )
7228
.get_state ( & origin.view ( ) .unwrap ( ) .bookmarks[ " feat" ] )
7229
.unwrap ( )
7230
.heads;
7231
origin.bookmark_set ( " main" , & feat) .unwrap ( ) ;
7232
7233
let r = b.pull ( " origin" ) .unwrap ( ) ;
7234
assert! ( r.changed, " {r:?}" ) ;
7235
assert! ( r.heads.contains ( & x) | | b.load_state ( & r.heads) .unwrap ( ) .contains ( & x) ) ;
7236
let main = b
7237
.bookmarks ( )
7238
.unwrap ( )
7239
.into_iter ( )
7240
.find ( | m | m.name = = " main" )
7241
.unwrap ( ) ;
7242
assert_eq! ( main.behind, Some ( 0 ) , " {main:?}" ) ;
7243
}
86e1824f Andy McCall 1 month ago ↶
7244
14edbaaf Andy McCall 1 month ago ↶
7245
7246
7247
7248
7249
# [ test ]
7250
fn remote_bookmarks_are_named_switched_to_pulled_and_cloned ( ) {
7251
hermetic ( ) ;
7252
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7253
let root = dir.path ( ) ;
7254
let origin_path = root.join ( " origin" ) ;
7255
let mut origin = Repo:: init( & origin_path) .unwrap ( ) ;
7256
let url = origin_path.to_string_lossy ( ) .to_string ( ) ;
7257
let mut a = Repo:: clone( & url, & root.join ( " a" ) ) .unwrap ( ) ;
7258
write ( & root.join ( " a" ) , " base.txt" , b " base\n " ) ;
7259
a.commit ( " base" , & [ ] ) .unwrap ( ) ;
7260
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7261
a.bookmark_create ( " develop" , None ) .unwrap ( ) ;
7262
a.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
7263
write ( & root.join ( " a" ) , " base.txt" , b " base\n d1\n " ) ;
7264
let d1 = a.commit ( " d1" , & [ ] ) .unwrap ( ) ;
7265
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7266
7267
7268
let c_root = root.join ( " c" ) ;
7269
let mut c = Repo:: clone( & url, & c_root ) .unwrap ( ) ;
7270
assert_eq! ( c.config.bookmark, " main" ) ;
7271
assert! ( ! c.view ( ) .unwrap ( ) .bookmarks.contains_key ( " develop" ) ) ;
7272
assert_eq! ( c.resolve_heads ( " origin/develop" ) .unwrap ( ) , vec! [ d1] ) ;
7273
assert_eq! ( c.resolve_change ( " origin/develop" ) .unwrap ( ) , d1) ;
7274
assert_eq! ( c.remote_only_bookmarks ( ) .unwrap ( ) .len ( ) , 1 ) ;
7275
7276
7277
c.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
7278
assert_eq! ( c.config.bookmark, " develop" ) ;
7279
assert_eq! ( c.bookmark_heads ( ) .unwrap ( ) , vec! [ d1] ) ;
7280
assert_eq! ( fs:: read( c_root .join ( " base.txt" ) ) .unwrap ( ) , b " base\n d1\n " ) ;
7281
assert! ( c.remote_only_bookmarks ( ) .unwrap ( ) .is_empty ( ) ) ;
7282
assert! ( c.bookmark_switch ( " nowhere" , false ) .is_err ( ) ) ;
7283
7284
7285
write ( & root.join ( " a" ) , " base.txt" , b " base\n d1\n d2\n " ) ;
7286
let d2 = a.commit ( " d2" , & [ ] ) .unwrap ( ) ;
7287
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7288
a.bookmark_switch ( " main" , false ) .unwrap ( ) ;
7289
write ( & root.join ( " a" ) , " m.txt" , b " m\n " ) ;
7290
let m = a.commit ( " m" , & [ ] ) .unwrap ( ) ;
7291
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7292
let r = c.pull ( " origin" ) .unwrap ( ) ;
7293
assert! ( r.changed & & r.checked_out, " {r:?}" ) ;
7294
assert_eq! ( r.heads, vec! [ d2] ) ;
7295
assert_eq! ( r.others, vec! [ " main" .to_string ( ) ] ) ;
7296
assert! ( c.resolve_heads ( " main" ) .unwrap ( ) .contains ( & m) ) ;
7297
assert_eq! (
7298
fs:: read( c_root .join ( " base.txt" ) ) .unwrap ( ) ,
7299
b " base\n d1\n d2\n "
7300
) ;
7301
let again = c.pull ( " origin" ) .unwrap ( ) ;
7302
assert! ( ! again.changed & & again.others.is_empty ( ) , " {again:?}" ) ;
7303
7304
7305
origin.config.set ( " workspace.bookmark" , " develop" ) .unwrap ( ) ;
7306
origin.save_config ( ) .unwrap ( ) ;
7307
let e = Repo:: clone( & url, & root.join ( " e" ) ) .unwrap ( ) ;
7308
assert_eq! ( e.config.bookmark, " develop" ) ;
7309
assert_eq! ( e.bookmark_heads ( ) .unwrap ( ) , vec! [ d2] ) ;
7310
let reopened = Repo:: open( & root.join ( " e" ) ) .unwrap ( ) ;
7311
assert_eq! ( reopened.config.bookmark, " develop" ) ;
7312
7313
let mut e = reopened;
7314
e.bookmark_switch ( " origin/main" , false ) .unwrap ( ) ;
7315
assert_eq! ( e.config.bookmark, " main" ) ;
7316
assert! ( e.bookmark_heads ( ) .unwrap ( ) .contains ( & m) ) ;
7317
}
7318
98a1620a Andy McCall 1 month ago ↶
7319
7320
7321
7322
7323
# [ test ]
7324
fn operations_can_be_read_restored_and_reverted ( ) {
7325
hermetic ( ) ;
7326
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7327
let root = dir.path ( ) ;
7328
let repo = Repo:: init( root) .unwrap ( ) ;
7329
write ( root, " a.txt" , b " a\n " ) ;
7330
let first = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
7331
write ( root, " a.txt" , b " a\n b\n " ) ;
7332
let second = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
7333
let at_second = repo.head_op ( ) .unwrap ( ) .0. to_hex ( ) ;
7334
repo.bookmark_create ( " keep" , None ) .unwrap ( ) ;
7335
let created = repo.head_op ( ) .unwrap ( ) .0. to_hex ( ) ;
7336
write ( root, " n.txt" , b " n\n " ) ;
7337
let third = repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
7338
7339
7340
let ops = repo.op_log ( ) .unwrap ( ) ;
7341
let changes = repo.view_changes ( & ops[ 1 ] .1. view, & ops[ 0 ] .1. view) .unwrap ( ) ;
7342
assert! (
7343
changes
7344
.iter ( )
7345
.any ( | c | matches! ( c, ViewChange:: BookmarkMoved { name, .. } if name = = " main" ) ) ,
7346
" {changes:?}"
7347
) ;
7348
assert! ( changes
7349
.iter ( )
7350
.any ( | c | matches! ( c, ViewChange:: WorkingCopy { .. } ) ) ) ;
7351
7352
7353
let old = Repo:: open( root) .unwrap ( ) .at_op ( & at_second[ .. 10 ] ) .unwrap ( ) ;
7354
assert_eq! ( old.bookmark_heads ( ) .unwrap ( ) , vec! [ second] ) ;
7355
assert! ( ! old.view ( ) .unwrap ( ) .bookmarks.contains_key ( " keep" ) ) ;
7356
write ( root, " z.txt" , b " z\n " ) ;
7357
assert! ( old.commit ( " refused" , & [ ] ) .is_err ( ) , " --at-op is read-only" ) ;
7358
fs:: remove_file( root.join ( " z.txt" ) ) .unwrap ( ) ;
7359
assert! ( repo.resolve_op ( " zz" ) .is_err ( ) ) ;
7360
assert_eq! ( repo.resolve_op ( " @" ) .unwrap ( ) .0 , repo.head_op ( ) .unwrap ( ) .0 ) ;
7361
7362
7363
repo.op_restore ( & at_second, false ) .unwrap ( ) ;
7364
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ second] ) ;
7365
assert! ( ! root.join ( " n.txt" ) .exists ( ) ) ;
7366
repo.undo ( ) .unwrap ( ) ;
7367
let mut heads = repo.bookmark_heads ( ) .unwrap ( ) ;
7368
heads.sort ( ) ;
7369
let mut want = vec! [ second, third] ;
7370
want.sort ( ) ;
7371
assert_eq! ( heads, want) ;
7372
assert! (
7373
root.join ( " n.txt" ) .exists ( ) ,
7374
" the files came back with the undo"
7375
) ;
7376
7377
7378
let r = repo.op_revert ( & created) .unwrap ( ) ;
7379
assert_eq! ( r.reverted, vec! [ " keep" .to_string ( ) ] ) ;
7380
assert! ( ! repo.view ( ) .unwrap ( ) .bookmarks.contains_key ( " keep" ) ) ;
7381
assert! ( repo
7382
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
7383
.unwrap ( )
7384
.contains ( & third) ) ;
7385
assert! ( repo.op_revert ( & created) .is_err ( ) , " nothing left to revert" ) ;
7386
let _ = first;
7387
}
7388
36041796 Andy McCall 1 month ago ↶
7389
7390
7391
7392
# [ test ]
7393
fn status_with_uncommitted_work_comes_from_the_cache_until_a_file_changes ( ) {
7394
hermetic ( ) ;
7395
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7396
let root = dir.path ( ) ;
7397
let repo = Repo:: init( root) .unwrap ( ) ;
7398
write ( root, " a.txt" , b " a\n " ) ;
7399
write ( root, " b.txt" , b " b\n " ) ;
7400
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7401
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , Some ( vec! [ ] ) , " clean" ) ;
7402
7403
write ( root, " a.txt" , b " a\n more\n " ) ;
7404
assert_eq! (
7405
repo.status_by_manifest ( ) .unwrap ( ) ,
7406
None ,
7407
" no snapshot of the edit yet"
7408
) ;
7409
let snap = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7410
assert_eq! ( snap.statuses.len ( ) , 1 ) ;
7411
let cached = repo
7412
.status_by_manifest ( )
7413
.unwrap ( )
7414
.expect ( " answered from the cache" ) ;
7415
assert_eq! ( cached.len ( ) , 1 ) ;
7416
assert_eq! (
7417
( cached[ 0 ] .path.as_str ( ) , cached[ 0 ] .status) ,
7418
( " a.txt" , ChangeStatus:: Modified)
7419
) ;
7420
7421
7422
write ( root, " a.txt" , b " a\n more\n and more\n " ) ;
7423
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None , " edited again" ) ;
7424
repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7425
assert! ( repo.status_by_manifest ( ) .unwrap ( ) .is_some ( ) ) ;
7426
write ( root, " new.txt" , b " n\n " ) ;
7427
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None , " a file added" ) ;
7428
fs:: remove_file( root.join ( " new.txt" ) ) .unwrap ( ) ;
7429
assert! (
7430
repo.status_by_manifest ( ) .unwrap ( ) .is_some ( ) ,
7431
" back as it was"
7432
) ;
7433
fs:: remove_file( root.join ( " b.txt" ) ) .unwrap ( ) ;
7434
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None , " a file removed" ) ;
7435
write ( root, " b.txt" , b " b\n " ) ;
7436
7437
7438
repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7439
repo.commit ( " more" , & [ ] ) .unwrap ( ) ;
7440
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , Some ( vec! [ ] ) ) ;
7441
}
7442
77445673 Andy McCall 1 month ago ↶
7443
7444
7445
7446
# [ test ]
7447
fn the_working_copy_diff_from_the_cache_matches_a_snapshot ( ) {
7448
hermetic ( ) ;
7449
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7450
let root = dir.path ( ) ;
7451
let repo = Repo:: init( root) .unwrap ( ) ;
7452
write ( root, " a.txt" , b " one\n two\n three\n " ) ;
7453
write ( root, " gone.txt" , b " bye\n " ) ;
7454
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7455
write ( root, " a.txt" , b " one\n 2\n three\n four\n " ) ;
7456
write ( root, " new.txt" , b " hello\n " ) ;
7457
fs:: remove_file( root.join ( " gone.txt" ) ) .unwrap ( ) ;
7458
let snap = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7459
let slow = render_change ( & snap.wc, & snap.base, & snap.base_snapshot) ;
7460
assert! (
7461
repo.status_by_manifest ( ) .unwrap ( ) .is_some ( ) ,
7462
" the cache holds"
7463
) ;
7464
let ( wc, base, snapshot) = repo.working_copy_with_base ( ) .unwrap ( ) ;
7465
assert_eq! ( render_change ( & wc, & base, & snapshot) , slow) ;
7466
assert_eq! ( repo.diff_files ( None ) .unwrap ( ) .len ( ) , 3 ) ;
7467
}
7468
1ec3f008 Andy McCall 1 month ago ↶
7469
7470
7471
7472
7473
# [ test ]
7474
fn the_manifest_check_skips_listing_while_no_directory_changed ( ) {
7475
hermetic ( ) ;
7476
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7477
let root = dir.path ( ) ;
7478
let repo = Repo:: init( root) .unwrap ( ) ;
7479
write ( root, " a.txt" , b " a\n " ) ;
7480
write ( root, " sub/deep/b.txt" , b " b\n " ) ;
7481
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7482
7483
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 60 ) ;
7484
for p in [ " a.txt" , " sub/deep/b.txt" , " sub/deep" , " sub" , " " ] {
7485
fs:: File:: open( root.join ( p) )
7486
.unwrap ( )
7487
.set_modified ( old)
7488
.unwrap ( ) ;
7489
}
7490
repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7491
repo.write_manifest ( false , false ) .unwrap ( ) ;
7492
let m = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) .2 ;
7493
assert! ( m.dirs.iter ( ) .any ( | ( d , _) | d = = " sub/deep" ) , " {:?}" , m.dirs) ;
7494
assert_eq! (
7495
repo.disk_differs_by_dirs ( & m) ,
7496
Some ( false ) ,
7497
" no listing needed"
7498
) ;
7499
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , Some ( vec! [ ] ) ) ;
7500
7501
7502
write ( root, " sub/deep/b.txt" , b " c\n " ) ;
7503
assert_eq! ( repo.disk_differs_by_dirs ( & m) , Some ( true ) ) ;
7504
write ( root, " sub/deep/b.txt" , b " b\n " ) ;
7505
fs:: File:: open( root.join ( " sub/deep/b.txt" ) )
7506
.unwrap ( )
7507
.set_modified ( old)
7508
.unwrap ( ) ;
7509
assert_eq! ( repo.disk_differs_by_dirs ( & m) , Some ( false ) ) ;
7510
7511
7512
write ( root, " sub/deep/new.txt" , b " n\n " ) ;
7513
assert_eq! ( repo.disk_differs_by_dirs ( & m) , None ) ;
7514
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None ) ;
7515
fs:: remove_file( root.join ( " sub/deep/new.txt" ) ) .unwrap ( ) ;
7516
7517
fs:: remove_file( root.join ( " a.txt" ) ) .unwrap ( ) ;
7518
assert_eq! ( repo.disk_differs_by_dirs ( & m) , None ) ;
7519
}
7520
e226a6cc Andy McCall 1 month ago ↶
7521
7522
7523
7524
# [ test ]
7525
fn a_commit_after_a_status_uses_the_cached_snapshot ( ) {
7526
hermetic ( ) ;
7527
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7528
let root = dir.path ( ) ;
7529
let repo = Repo:: init( root) .unwrap ( ) ;
7530
write ( root, " a.txt" , b " a\n b\n " ) ;
7531
write ( root, " .oxyattributes" , b " *.bin binary\n " ) ;
7532
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7533
write ( root, " a.txt" , b " a\n B\n " ) ;
7534
write ( root, " new.txt" , b " new\n " ) ;
7535
let snap = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7536
let cached = repo
7537
.snapshot_from_cache ( )
7538
.unwrap ( )
7539
.expect ( " the cache holds" ) ;
7540
assert_eq! ( cached.wc_hash, snap.wc_hash) ;
7541
assert_eq! ( cached.statuses, snap.statuses) ;
7542
assert! (
7543
! cached.attributes.is_empty ( ) ,
7544
" the .oxyattributes file was read"
7545
) ;
7546
let h = repo.commit ( " edit" , & [ ] ) .unwrap ( ) ;
7547
let c = repo.store ( ) .get_change ( & h) .unwrap ( ) ;
7548
assert_eq! (
7549
c.tree_ops.len ( ) + c.edits.len ( ) ,
7550
snap.wc.tree_ops.len ( ) + snap.wc.edits.len ( )
7551
) ;
7552
assert_eq! (
7553
repo.status_by_manifest ( ) .unwrap ( ) ,
7554
Some ( vec! [ ] ) ,
7555
" clean after the commit"
7556
) ;
7557
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " a\n B\n " ) ;
7558
}
7559
0389c3e8 Andy McCall 1 month ago ↶
7560
7561
7562
7563
7564
# [ test ]
7565
fn a_verified_clean_check_restamps_the_manifest_safely ( ) {
7566
hermetic ( ) ;
7567
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7568
let root = dir.path ( ) ;
7569
let repo = Repo:: init( root) .unwrap ( ) ;
7570
write ( root, " a.txt" , b " one\n " ) ;
7571
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
7572
let before = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) .2. written_ns;
7573
assert! ( repo.clean_by_manifest ( ) .unwrap ( ) ) ;
7574
let after = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) .2. written_ns;
7575
assert! ( after > before, " restamped" ) ;
7576
write ( root, " a.txt" , b " two\n " ) ;
7577
assert! (
7578
! repo.clean_by_manifest ( ) .unwrap ( ) ,
7579
" a same-size rewrite is still seen"
7580
) ;
7581
}
7582
86e1824f Andy McCall 1 month ago ↶
7583
7584
7585
7586
# [ test ]
7587
fn history_only_states_read_text_only_for_the_graphs_they_build ( ) {
7588
hermetic ( ) ;
7589
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7590
let root = dir.path ( ) ;
7591
let repo = Repo:: init( root) .unwrap ( ) ;
7592
let big: String = ( 0 .. 3000 ) .map ( | i | format! ( " big line {i} \n " ) ) .collect ( ) ;
7593
let other: String = ( 0 .. 3000 ) .map ( | i | format! ( " other line {i} \n " ) ) .collect ( ) ;
7594
write ( root, " big.txt" , big.as_bytes ( ) ) ;
7595
write ( root, " other.txt" , other.as_bytes ( ) ) ;
7596
let h = repo.commit ( " two large files" , & [ ] ) .unwrap ( ) ;
7597
write ( root, " big.txt" , format! ( " {big} one more\n " ) .as_bytes ( ) ) ;
7598
let h2 = repo.commit ( " append" , & [ ] ) .unwrap ( ) ;
7599
7600
let full = repo.load_state_full ( & [ h2] ) .unwrap ( ) .materialize ( ) ;
7601
let text = | snap : & crate:: state:: Snapshot, p : & str | match & snap.files[ p] .content {
7602
FileContent:: Text { bytes, .. } => bytes.clone ( ) ,
7603
other => panic! ( " {other:?}" ) ,
7604
} ;
7605
7606
let mut hist = repo.load_state_history ( & [ h2] ) .unwrap ( ) ;
7607
let big_node = full.files[ " big.txt" ] .node;
7608
assert! ( hist.graph_key ( & big_node) .is_some ( ) , " not built yet" ) ;
7609
repo.load_all_graphs ( & mut hist) .unwrap ( ) ;
7610
let rendered = hist.materialize ( ) ;
7611
for p in [ " big.txt" , " other.txt" ] {
7612
assert_eq! ( text ( & rendered, p) , text ( & full, p) , " {p}" ) ;
7613
}
7614
7615
7616
7617
let ( _ , texts) = repo.store ( ) .get_change_lazy ( & h) .unwrap ( ) ;
7618
assert_eq! ( texts.len ( ) , 2 ) ;
7619
let content = | r : & ObjectRef| -> Vec< u8 > {
7620
let blob = crate :: objects:: Blob:: decode( & repo.store ( ) .get ( r) .unwrap ( ) .1 ) .unwrap ( ) ;
7621
blob.chunks
7622
.iter ( )
7623
.flat_map ( | c | repo.store ( ) .get ( c) .unwrap ( ) .1 )
7624
.collect ( )
7625
} ;
7626
let other_text = texts
7627
.iter ( )
a4fad5b0 Andy McCall 1 month ago ↶
7628
.filter_map ( | t | match t {
0f93f49c Andy McCall 1 month ago ↶
7629
crate :: objects:: TextRef:: Blob( r, _ , _ ) => Some ( * r) ,
a4fad5b0 Andy McCall 1 month ago ↶
7630
_ => None ,
7631
} )
86e1824f Andy McCall 1 month ago ↶
7632
.find ( | r | content ( r) .windows ( 12 ) .any ( | w | w = = b " other line 1" ) )
7633
.unwrap ( ) ;
64818938 Andy McCall 1 month ago ↶
7634
repo.store ( ) .remove_loose_for_test ( & other_text) .unwrap ( ) ;
86e1824f Andy McCall 1 month ago ↶
7635
let mut hist = repo.load_state_history ( & [ h2] ) .unwrap ( ) ;
7636
repo.load_graphs_of ( & mut hist, & [ big_node] ) .unwrap ( ) ;
7637
assert! ( repo
7638
.load_graphs_of ( & mut hist, & [ full.files[ " other.txt" ] .node] )
7639
.is_err ( ) ) ;
7640
}
7641
7642
7643
7644
7645
# [ test ]
7646
fn a_commit_leaves_a_manifest_the_next_status_trusts ( ) {
7647
use std:: os:: unix:: fs:: PermissionsExt;
7648
hermetic ( ) ;
7649
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7650
let root = dir.path ( ) ;
7651
let repo = Repo:: init( root) .unwrap ( ) ;
7652
let files = [ " a.txt" , " b.txt" , " d/c.txt" ] ;
7653
for p in files {
7654
write ( root, p, format! ( " {p} \n " ) .as_bytes ( ) ) ;
7655
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
7656
fs:: File:: options( )
7657
.write ( true )
7658
.open ( root.join ( p) )
7659
.unwrap ( )
7660
.set_modified ( old)
7661
.unwrap ( ) ;
7662
}
7663
repo.commit ( " added" , & [ ] ) .unwrap ( ) ;
7664
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
7665
assert! (
7666
m.entries.values ( ) .all ( | e | e.graph.is_some ( ) ) ,
7667
" keys recorded at commit"
7668
) ;
7669
7670
for p in files {
7671
fs:: set_permissions( root.join ( p) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
7672
}
7673
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
7674
for p in files {
7675
fs:: set_permissions( root.join ( p) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
7676
}
7677
assert! ( ! snap.changed & & snap.statuses.is_empty ( ) ) ;
7678
assert_eq! ( snap.base_snapshot.unrendered.len ( ) , files.len ( ) ) ;
7679
}
c5ce0367 Andy McCall 1 month ago ↶
7680
7681
7682
7683
# [ test ]
7684
fn checkout_touches_only_what_differs_and_renders_in_batches ( ) {
7685
use std:: os:: unix:: fs:: PermissionsExt;
7686
hermetic ( ) ;
7687
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7688
let root = dir.path ( ) ;
7689
let origin = root.join ( " origin" ) ;
7690
let mut repo = Repo:: init( & origin) .unwrap ( ) ;
7691
7692
let n = 2100 ;
7693
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
7694
for i in 0 .. n {
7695
let p = format! ( " f/{i:04} .txt" ) ;
7696
write ( & origin, & p, format! ( " file {i} \n " ) .as_bytes ( ) ) ;
7697
fs:: File:: options( )
7698
.write ( true )
7699
.open ( origin.join ( & p) )
7700
.unwrap ( )
7701
.set_modified ( old)
7702
.unwrap ( ) ;
7703
}
7704
repo.commit ( " many" , & [ ] ) .unwrap ( ) ;
7705
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
7706
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
7707
write ( & origin, " f/0007.txt" , b " changed\n " ) ;
7708
repo.commit ( " one change" , & [ ] ) .unwrap ( ) ;
7709
7710
7711
for i in 0 .. n {
7712
if i ! = 7 {
7713
let p = origin.join ( format! ( " f/{i:04} .txt" ) ) ;
7714
fs:: set_permissions( & p, fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
7715
}
7716
}
7717
let r = repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
7718
for i in 0 .. n {
7719
let p = origin.join ( format! ( " f/{i:04} .txt" ) ) ;
7720
fs:: set_permissions( & p, fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
7721
}
7722
assert_eq! ( ( r.written, r.removed) , ( 1 , 0 ) , " {r:?}" ) ;
7723
assert_eq! ( fs:: read( origin.join ( " f/0007.txt" ) ) .unwrap ( ) , b " file 7\n " ) ;
7724
assert! ( repo.snapshot_status ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
7725
7726
7727
let c = root.join ( " clone" ) ;
7728
Repo:: clone( & origin.to_string_lossy ( ) , & c) .unwrap ( ) ;
7729
for i in [ 0 , 2047 , 2048 , n - 1 ] {
7730
assert_eq! (
7731
fs:: read( c.join ( format! ( " f/{i:04} .txt" ) ) ) .unwrap ( ) ,
7732
format! ( " file {i} \n " ) .as_bytes ( )
7733
) ;
7734
}
7735
let cloned = Repo:: open( & c) .unwrap ( ) ;
7736
assert! ( cloned
7737
.snapshot_status ( " status" )
7738
.unwrap ( )
7739
.statuses
7740
.is_empty ( ) ) ;
d3cd7ef9 Andy McCall 1 month ago ↶
7741
}
7742
7743
7744
7745
7746
# [ test ]
7747
fn a_racy_manifest_entry_is_checked_by_hash ( ) {
7748
hermetic ( ) ;
7749
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7750
let root = dir.path ( ) ;
7751
let repo = Repo:: init( root) .unwrap ( ) ;
7752
write ( root, " a.txt" , b " one\n " ) ;
7753
repo.commit ( " fresh" , & [ ] ) .unwrap ( ) ;
7754
assert! ( repo.clean_by_manifest ( ) .unwrap ( ) , " fresh but equal" ) ;
7755
let t = fs:: metadata( root.join ( " a.txt" ) )
7756
.unwrap ( )
7757
.modified ( )
7758
.unwrap ( ) ;
7759
write ( root, " a.txt" , b " two\n " ) ;
7760
fs:: File:: options( )
7761
.write ( true )
7762
.open ( root.join ( " a.txt" ) )
7763
.unwrap ( )
7764
.set_modified ( t)
7765
.unwrap ( ) ;
7766
assert! (
7767
! repo.clean_by_manifest ( ) .unwrap ( ) ,
7768
" same size and time, other bytes"
7769
) ;
7770
assert_eq! (
7771
statuses ( & repo.snapshot_status ( " status" ) .unwrap ( ) ) ,
7772
vec! [ ( " a.txt" .to_string ( ) , ChangeStatus:: Modified) ]
7773
) ;
c5ce0367 Andy McCall 1 month ago ↶
7774
}
2c75672f Andy McCall 1 month ago ↶
7775
7776
7777
7778
# [ test ]
7779
fn a_raw_bundle_without_a_changes_text_is_refused ( ) {
7780
hermetic ( ) ;
7781
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7782
let root = dir.path ( ) ;
7783
let a = Repo:: init( & root.join ( " a" ) ) .unwrap ( ) ;
7784
let big: String = ( 0 .. 3000 ) .map ( | i | format! ( " line {i} \n " ) ) .collect ( ) ;
7785
write ( & root.join ( " a" ) , " big.txt" , big.as_bytes ( ) ) ;
7786
let h = a.commit ( " big" , & [ ] ) .unwrap ( ) ;
7787
let bundle = a.bundle_raw_for ( & [ h] , None ) .unwrap ( ) ;
7788
assert_eq! ( bundle.raw_changes.len ( ) , 1 ) ;
7789
assert! ( bundle.changes.is_empty ( ) ) ;
7790
let b = Repo:: init( & root.join ( " b" ) ) .unwrap ( ) ;
7791
let mut without = bundle.clone ( ) ;
7792
without.objects.retain ( | ( ty , _) | * ty ! = ObjectType:: Blob) ;
7793
let e = b.import_bundle ( & without, false ) .unwrap_err ( ) ;
7794
assert! ( e.to_string ( ) .contains ( " came without its text blob" ) , " {e}" ) ;
7795
assert! ( ! b.store ( ) .has ( & h) ) ;
7796
assert_eq! ( b.import_bundle ( & bundle, false ) .unwrap ( ) , vec! [ h] ) ;
7797
assert_eq! ( b.store ( ) .get_change ( & h) .unwrap ( ) .message, " big" ) ;
7798
}
a87c7b9d Andy McCall 1 month ago ↶
7799
7800
7801
7802
7803
7804
# [ test ]
7805
fn git_import_brings_a_directory_back_rather_than_making_a_second ( ) {
7806
hermetic ( ) ;
7807
if std:: process:: Command:: new( " git" )
7808
.arg ( " --version" )
7809
.output ( )
7810
.is_err ( )
7811
{
7812
eprintln! ( " git not installed; skipping" ) ;
7813
return ;
7814
}
7815
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7816
let root = dir.path ( ) ;
7817
let g = root.join ( " src.git" ) ;
7818
fs:: create_dir_all( & g) .unwrap ( ) ;
7819
let git = | args : & [& str ]| -> String {
7820
let out = std:: process:: Command:: new( " git" )
7821
.arg ( " -C" )
7822
.arg ( & g)
7823
.args ( [
7824
" -c" ,
7825
" user.name=Grace" ,
7826
" -c" ,
7827
" [email protected] " ,
7828
" -c" ,
7829
" commit.gpgsign=false" ,
7830
] )
7831
.args ( args)
7832
.output ( )
7833
.unwrap ( ) ;
7834
assert! (
7835
out.status.success ( ) ,
7836
" git {args:?}: {}" ,
7837
String :: from_utf8_lossy( & out.stderr)
7838
) ;
7839
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
7840
} ;
7841
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
7842
write ( & g, " d/a.txt" , b " a\n " ) ;
7843
write ( & g, " c/x.txt" , b " x\n " ) ;
7844
git ( & [ " add" , " ." ] ) ;
7845
git ( & [ " commit" , " -qm" , " base" ] ) ;
7846
7847
git ( & [ " checkout" , " -qb" , " side" ] ) ;
7848
git ( & [ " mv" , " c/x.txt" , " d/x.txt" ] ) ;
7849
git ( & [ " commit" , " -qm" , " x into d" ] ) ;
7850
7851
git ( & [ " checkout" , " -q" , " main" ] ) ;
7852
git ( & [ " rm" , " -q" , " d/a.txt" ] ) ;
7853
git ( & [ " commit" , " -qm" , " d goes" ] ) ;
7854
write ( & g, " d/b.txt" , b " b\n " ) ;
7855
git ( & [ " add" , " ." ] ) ;
7856
git ( & [ " commit" , " -qm" , " d comes back" ] ) ;
7857
git ( & [ " mv" , " c/x.txt" , " d/x.txt" ] ) ;
7858
git ( & [ " commit" , " -qm" , " x into d again" ] ) ;
7859
git ( & [ " merge" , " -q" , " --no-edit" , " side" ] ) ;
7860
7861
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
7862
repo.import_git ( & g, " main" ) .unwrap ( ) ;
7863
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
7864
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
7865
let st = repo
7866
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
7867
.unwrap ( ) ;
7868
let view = st.tree ( ) .view ( ) ;
7869
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
7870
7871
7872
let first = repo
7873
.log ( )
7874
.unwrap ( )
7875
.into_iter ( )
7876
.find ( | e | e.change.message.starts_with ( " base" ) )
7877
.unwrap ( ) ;
7878
let base = repo
7879
.load_state_history ( & [ first.hash] )
7880
.unwrap ( )
7881
.tree ( )
7882
.view ( ) ;
7883
assert_eq! ( view.entries[ " d" ] .node, base.entries[ " d" ] .node) ;
7884
}
7885
7886
7887
7888
7889
# [ test ]
7890
fn git_import_merge_retires_a_placement_under_a_removed_directory ( ) {
7891
hermetic ( ) ;
7892
if std:: process:: Command:: new( " git" )
7893
.arg ( " --version" )
7894
.output ( )
7895
.is_err ( )
7896
{
7897
eprintln! ( " git not installed; skipping" ) ;
7898
return ;
7899
}
7900
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7901
let root = dir.path ( ) ;
7902
let g = root.join ( " src.git" ) ;
7903
fs:: create_dir_all( & g) .unwrap ( ) ;
7904
let run = | args : & [& str ]| {
7905
std:: process:: Command:: new( " git" )
7906
.arg ( " -C" )
7907
.arg ( & g)
7908
.args ( [
7909
" -c" ,
7910
" user.name=Grace" ,
7911
" -c" ,
7912
" [email protected] " ,
7913
" -c" ,
7914
" commit.gpgsign=false" ,
7915
] )
7916
.args ( args)
7917
.output ( )
7918
.unwrap ( )
7919
} ;
7920
let git = | args : & [& str ]| {
7921
let out = run ( args) ;
7922
assert! (
7923
out.status.success ( ) ,
7924
" git {args:?}: {}" ,
7925
String :: from_utf8_lossy( & out.stderr)
7926
) ;
7927
} ;
7928
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
7929
write ( & g, " c/x.txt" , b " x\n " ) ;
7930
write ( & g, " keep.txt" , b " k\n " ) ;
7931
git ( & [ " add" , " ." ] ) ;
7932
git ( & [ " commit" , " -qm" , " base" ] ) ;
7933
7934
7935
git ( & [ " checkout" , " -qb" , " side" ] ) ;
7936
git ( & [ " mv" , " c/x.txt" , " c/y.txt" ] ) ;
7937
git ( & [ " commit" , " -qm" , " rename within c" ] ) ;
7938
git ( & [ " checkout" , " -q" , " main" ] ) ;
7939
fs:: create_dir_all( g.join ( " n" ) ) .unwrap ( ) ;
7940
git ( & [ " mv" , " c/x.txt" , " n/x.txt" ] ) ;
7941
git ( & [ " commit" , " -qm" , " move out of c" ] ) ;
7942
7943
let _ = run ( & [ " merge" , " -q" , " side" ] ) ;
7944
let _ = run ( & [
7945
" rm" ,
7946
" -q" ,
7947
" --cached" ,
7948
" --ignore-unmatch" ,
7949
" c/y.txt" ,
7950
" c/x.txt" ,
7951
] ) ;
7952
let _ = std:: fs:: remove_dir_all( g.join ( " c" ) ) ;
7953
write ( & g, " n/x.txt" , b " x\n " ) ;
7954
git ( & [ " add" , " -A" ] ) ;
7955
git ( & [ " commit" , " -qm" , " merge: keep n/x.txt" ] ) ;
7956
7957
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
7958
repo.import_git ( & g, " main" ) .unwrap ( ) ;
7959
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
7960
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
7961
let st = repo
7962
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
7963
.unwrap ( ) ;
7964
let view = st.tree ( ) .view ( ) ;
7965
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
7966
assert! ( view.entries.contains_key ( " n/x.txt" ) ) ;
7967
}
7968
7969
7970
7971
7972
# [ test ]
7973
fn git_import_merge_leaves_no_orphan_behind ( ) {
7974
hermetic ( ) ;
7975
if std:: process:: Command:: new( " git" )
7976
.arg ( " --version" )
7977
.output ( )
7978
.is_err ( )
7979
{
7980
eprintln! ( " git not installed; skipping" ) ;
7981
return ;
7982
}
7983
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7984
let root = dir.path ( ) ;
7985
let g = root.join ( " src.git" ) ;
7986
fs:: create_dir_all( & g) .unwrap ( ) ;
7987
let git = | args : & [& str ]| {
7988
let out = std:: process:: Command:: new( " git" )
7989
.arg ( " -C" )
7990
.arg ( & g)
7991
.args ( [
7992
" -c" ,
7993
" user.name=Grace" ,
7994
" -c" ,
7995
" [email protected] " ,
7996
" -c" ,
7997
" commit.gpgsign=false" ,
7998
] )
7999
.args ( args)
8000
.output ( )
8001
.unwrap ( ) ;
8002
assert! (
8003
out.status.success ( ) ,
8004
" git {args:?}: {}" ,
8005
String :: from_utf8_lossy( & out.stderr)
8006
) ;
8007
} ;
8008
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
8009
write ( & g, " d/f.txt" , b " f\n " ) ;
8010
write ( & g, " keep.txt" , b " k\n " ) ;
8011
git ( & [ " add" , " ." ] ) ;
8012
git ( & [ " commit" , " -qm" , " base" ] ) ;
8013
git ( & [ " checkout" , " -qb" , " side" ] ) ;
8014
write ( & g, " d/new.txt" , b " new\n " ) ;
8015
git ( & [ " add" , " ." ] ) ;
8016
git ( & [ " commit" , " -qm" , " add into d" ] ) ;
8017
git ( & [ " checkout" , " -q" , " main" ] ) ;
8018
git ( & [ " rm" , " -q" , " d/f.txt" ] ) ;
8019
git ( & [ " commit" , " -qm" , " d goes" ] ) ;
8020
git ( & [ " merge" , " -q" , " --no-edit" , " side" ] ) ;
8021
8022
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
8023
repo.import_git ( & g, " main" ) .unwrap ( ) ;
8024
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
8025
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
8026
let st = repo
8027
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
8028
.unwrap ( ) ;
8029
let view = st.tree ( ) .view ( ) ;
8030
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
8031
assert! ( view.entries.contains_key ( " d/new.txt" ) ) ;
8032
}
d77e1702 Andy McCall 1 month ago ↶
8033
8034
8035
8036
# [ test ]
8037
fn fsck_names_a_missing_text_blob ( ) {
8038
hermetic ( ) ;
8039
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8040
let root = dir.path ( ) ;
8041
let repo = Repo:: init( root) .unwrap ( ) ;
8042
let big: String = ( 0 .. 3000 ) .map ( | i | format! ( " line {i} \n " ) ) .collect ( ) ;
8043
write ( root, " big.txt" , big.as_bytes ( ) ) ;
8044
let h = repo.commit ( " big" , & [ ] ) .unwrap ( ) ;
8045
assert_eq! ( repo.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
8046
let ( _ , texts) = repo.store ( ) .get_change_lazy ( & h) .unwrap ( ) ;
8047
let Some ( crate :: objects:: TextRef:: Blob( b, _ , _ ) ) = texts.first ( ) .copied ( ) else {
8048
panic! ( " a stored text" )
8049
} ;
64818938 Andy McCall 1 month ago ↶
8050
repo.store ( ) .remove_loose_for_test ( & b) .unwrap ( ) ;
d77e1702 Andy McCall 1 month ago ↶
8051
let rep = repo.fsck ( ) .unwrap ( ) ;
8052
assert! ( rep.errors ( ) > 0 ) ;
8053
assert! (
8054
rep.problems.iter ( ) .any ( | p | p.what.contains ( & h.short ( 12 ) ) ) ,
8055
" {:?}" ,
8056
rep.problems
8057
) ;
8058
}
a07f60a3 Andy McCall 1 month ago ↶
8059
8060
8061
8062
8063
# [ test ]
8064
fn a_subrepo_on_the_parents_hub_borrows_its_credentials ( ) {
8065
hermetic ( ) ;
8066
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8067
let mut repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
8068
repo.config
8069
.set (
8070
" remote.origin.url" ,
8071
" https://Hub.example:7343/doomsdayone/metalgear" ,
8072
)
8073
.unwrap ( ) ;
8074
repo.config.set ( " remote.origin.token" , " secret" ) .unwrap ( ) ;
8075
repo.config
8076
.set ( " remote.origin.ca" , " /tmp/hub-ca.pem" )
8077
.unwrap ( ) ;
8078
let same = repo.credentials_for ( " https://hub.example:7343/doomsdayone/rstar-api" ) ;
8079
assert_eq! ( same.token.as_deref ( ) , Some ( " secret" ) ) ;
8080
assert_eq! ( same.ca.as_deref ( ) , Some ( Path:: new( " /tmp/hub-ca.pem" ) ) ) ;
8081
for other in [
8082
" https://elsewhere.example:7343/doomsdayone/rstar-api" ,
8083
" https://hub.example/doomsdayone/rstar-api" ,
8084
" http://hub.example:7343/doomsdayone/rstar-api" ,
8085
] {
8086
let o = repo.credentials_for ( other) ;
8087
assert_ne! ( o.token.as_deref ( ) , Some ( " secret" ) , " {other}" ) ;
8088
}
8089
}
ce14f0d6 Andy McCall 1 month ago ↶
8090
ff1f62fe Andy McCall 1 month ago ↶
8091
8092
8093
ce14f0d6 Andy McCall 1 month ago ↶
8094
# [ test ]
ff1f62fe Andy McCall 1 month ago ↶
8095
fn remotes_use_the_credential_store_and_the_enclosing_checkout ( ) {
8096
hermetic ( ) ;
8097
let home = tempfile:: tempdir( ) .unwrap ( ) ;
8098
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8099
let mut parent = Repo:: init( dir.path ( ) ) .unwrap ( ) .with_home ( home.path ( ) ) ;
8100
parent
8101
.config
8102
.set ( " remote.origin.url" , " https://hub.example/o/parent" )
8103
.unwrap ( ) ;
8104
parent.save_config ( ) .unwrap ( ) ;
8105
assert_eq! ( parent.remote_options ( " origin" ) .token, None ) ;
8106
crate :: credentials:: Credentials:: at( home.path ( ) )
8107
.save ( " https://hub.example" , " stored" , Some ( " andy" ) , None )
8108
.unwrap ( ) ;
8109
assert_eq! (
8110
parent.remote_options ( " origin" ) .token.as_deref ( ) ,
8111
Some ( " stored" )
8112
) ;
8113
8114
let empty_home = tempfile:: tempdir( ) .unwrap ( ) ;
8115
let nested = dir.path ( ) .join ( " deps" ) .join ( " child" ) ;
8116
let mut child = Repo:: init( & nested) .unwrap ( ) .with_home ( empty_home.path ( ) ) ;
8117
child
8118
.config
8119
.set ( " remote.origin.url" , " https://hub.example/o/child" )
8120
.unwrap ( ) ;
8121
child.save_config ( ) .unwrap ( ) ;
8122
assert_eq! ( child.remote_options ( " origin" ) .token, None ) ;
8123
parent
8124
.config
8125
.set ( " remote.origin.token" , " checkout" )
8126
.unwrap ( ) ;
8127
parent.save_config ( ) .unwrap ( ) ;
8128
assert_eq! (
8129
child.remote_options ( " origin" ) .token.as_deref ( ) ,
8130
Some ( " checkout" )
8131
) ;
8132
8133
child
8134
.config
8135
.set ( " remote.origin.url" , " https://other.example/o/child" )
8136
.unwrap ( ) ;
8137
assert_eq! ( child.remote_options ( " origin" ) .token, None ) ;
8138
}
8139
8140
# [ test ]
ce14f0d6 Andy McCall 1 month ago ↶
8141
fn the_metadata_cache_survives_a_torn_tail ( ) {
8142
hermetic ( ) ;
8143
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8144
let root = dir.path ( ) ;
8145
let repo = Repo:: init( root) .unwrap ( ) ;
8146
write ( root, " a.txt" , b " 1\n " ) ;
8147
let h = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8148
let meta = repo.store ( ) .get_change_meta ( & h) .unwrap ( ) ;
8149
let cache = repo.store ( ) .dir ( ) .join ( " metas" ) ;
8150
let len = fs:: metadata( & cache) .unwrap ( ) .len ( ) ;
8151
assert! ( len > 0 , " a change read is cached" ) ;
8152
8153
fs:: OpenOptions:: new( )
8154
.append ( true )
8155
.open ( & cache)
8156
.unwrap ( )
8157
.write_all ( & [ 7 ; 20 ] )
8158
.unwrap ( ) ;
8159
let again = Repo:: open( root) .unwrap ( ) ;
8160
assert_eq! ( again.store ( ) .get_change_meta ( & h) .unwrap ( ) , meta) ;
8161
assert_eq! ( fs:: metadata( & cache) .unwrap ( ) .len ( ) , len) ;
8162
}
f9466021 Andy McCall 1 month ago ↶
8163
8164
# [ test ]
8165
fn a_manifest_that_does_not_check_reads_as_none ( ) {
8166
hermetic ( ) ;
8167
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8168
let root = dir.path ( ) ;
8169
let repo = Repo:: init( root) .unwrap ( ) ;
8170
write ( root, " a.txt" , b " 1\n " ) ;
8171
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8172
let path = repo.manifest_path ( ) ;
8173
let mut bytes = fs:: read( & path) .unwrap ( ) ;
8174
assert! ( Repo:: open( root) .unwrap ( ) .read_manifest ( ) .unwrap ( ) .is_some ( ) ) ;
8175
let last = bytes.len ( ) - 1 ;
8176
bytes[ last] ^ = 1 ;
8177
fs:: write( & path, & bytes) .unwrap ( ) ;
8178
assert! ( Repo:: open( root) .unwrap ( ) .read_manifest ( ) .unwrap ( ) .is_none ( ) ) ;
8179
8180
let again = Repo:: open( root) .unwrap ( ) ;
8181
assert! ( ! again.snapshot ( " status" ) .unwrap ( ) .changed) ;
8182
assert! ( again.read_manifest ( ) .unwrap ( ) .is_some ( ) ) ;
8183
}
8184
8185
# [ test ]
8186
fn a_commit_leaves_a_manifest_that_proves_the_working_copy_clean ( ) {
8187
hermetic ( ) ;
8188
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8189
let root = dir.path ( ) ;
8190
let repo = Repo:: init( root) .unwrap ( ) ;
8191
write ( root, " a.txt" , b " 1\n " ) ;
8192
write ( root, " b.txt" , b " 2\n " ) ;
8193
8194
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 60 ) ;
8195
fs:: File:: options( )
8196
.write ( true )
8197
.open ( root.join ( " b.txt" ) )
8198
.unwrap ( )
8199
.set_modified ( old)
8200
.unwrap ( ) ;
8201
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8202
assert! ( repo.clean_by_manifest ( ) .unwrap ( ) , " after the first commit" ) ;
8203
repo.snapshot_status ( " status" ) .unwrap ( ) ;
8204
write ( root, " a.txt" , b " 1\n 2\n " ) ;
8205
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
8206
assert! (
8207
repo.clean_by_manifest ( ) .unwrap ( ) ,
8208
" after a commit of one file"
8209
) ;
8210
8211
8212
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
8213
repo.snapshot_status ( " status" ) .unwrap ( ) ;
8214
write ( root, " a.txt" , b " 1\n 2\n 3\n 4\n 5\n " ) ;
8215
repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
8216
assert! (
8217
repo.clean_by_manifest ( ) .unwrap ( ) ,
8218
" after an edit a status saw"
8219
) ;
8220
}
e266b7d4 Andy McCall 1 month ago ↶
8221
8222
# [ test ]
8223
fn a_conflicted_rebase_can_be_resolved_in_the_working_copy ( ) {
8224
hermetic ( ) ;
8225
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8226
let root = dir.path ( ) ;
8227
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8228
write ( root, " a.txt" , b " 1\n 2\n " ) ;
8229
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
8230
repo.bookmark_create ( " develop" , Some ( & [ a] ) ) .unwrap ( ) ;
8231
write ( root, " a.txt" , b " 1\n X\n " ) ;
8232
let c = repo.commit ( " C: 2 to X" , & [ ] ) .unwrap ( ) ;
8233
repo.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
8234
write ( root, " a.txt" , b " 1\n Y\n " ) ;
8235
let d = repo.commit ( " D: 2 to Y" , & [ ] ) .unwrap ( ) ;
8236
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
8237
8238
let err = repo.rebase ( & c, & [ d] ) .unwrap_err ( ) ;
8239
assert! ( matches! ( err, RepoError:: Conflicted( _ ) ) , " {err}" ) ;
8240
let r = repo.rebase_into_working_copy ( & c, & [ d] ) .unwrap ( ) ;
8241
assert! ( r.changed & & r.checked_out) ;
8242
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8243
assert! (
8244
snap.base_snapshot.is_conflicted ( ) ,
8245
" the conflict is in the working copy"
8246
) ;
8247
let text = fs:: read_to_string( root.join ( " a.txt" ) ) .unwrap ( ) ;
8248
assert! ( text.contains ( ' X' ) & & text.contains ( ' Y' ) , " {text}" ) ;
8249
8250
write ( root, " a.txt" , b " 1\n XY\n " ) ;
8251
repo.commit ( " Resolve" , & [ ] ) .unwrap ( ) ;
8252
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
8253
assert! ( st.contains ( & c) & & st.contains ( & d) ) ;
8254
let files = st.materialize ( ) .files;
8255
assert! ( ! files[ " a.txt" ] .is_conflicted ( ) ) ;
8256
assert! (
8257
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 1\n XY\n " )
8258
) ;
8259
8260
8261
repo.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
8262
write ( root, " b.txt" , b " b\n " ) ;
8263
let e = repo.commit ( " E" , & [ ] ) .unwrap ( ) ;
8264
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
8265
let err = repo.rebase_into_working_copy ( & e, & [ a] ) .unwrap_err ( ) ;
8266
assert! (
8267
err.to_string ( ) .contains ( " not on the current bookmark" ) ,
8268
" {err}"
8269
) ;
8270
}
d4dbab97 Andy McCall 1 month ago ↶
8271
8272
# [ test ]
8273
fn an_ignore_rule_drops_an_added_file_but_not_a_tracked_one ( ) {
8274
hermetic ( ) ;
8275
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8276
let root = dir.path ( ) ;
8277
let repo = Repo:: init( root) .unwrap ( ) ;
8278
write ( root, " a.txt" , b " a\n " ) ;
8279
write ( root, " kept.log" , b " tracked\n " ) ;
8280
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8281
write ( root, " build.log" , b " made\n " ) ;
8282
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8283
assert! ( snap.statuses.iter ( ) .any ( | s | s.path = = " build.log" ) ) ;
8284
8285
8286
write ( root, " .gitignore" , b " *.log\n " ) ;
8287
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8288
let paths: Vec< & str > = snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect ( ) ;
8289
assert_eq! ( paths, vec! [ " .gitignore" ] , " {paths:?}" ) ;
8290
write ( root, " kept.log" , b " tracked, edited\n " ) ;
8291
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8292
assert! ( snap.statuses.iter ( ) .any ( | s | s.path = = " kept.log" ) ) ;
8293
}
fd280228 Andy McCall 1 month ago ↶
8294
8295
# [ test ]
8296
fn shed_graphs_come_back_the_same ( ) {
8297
hermetic ( ) ;
8298
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8299
let root = dir.path ( ) ;
8300
let repo = Repo:: init( root) .unwrap ( ) ;
8301
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
8302
write ( root, " b.txt" , b " x\n " ) ;
8303
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8304
write ( root, " a.txt" , b " 1\n two\n 3\n 4\n " ) ;
8305
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
8306
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
8307
let mut st = repo.load_state_history ( & heads) .unwrap ( ) ;
8308
let view = st.tree ( ) .view ( ) ;
8309
let nodes: Vec< NodeId> = [ " a.txt" , " b.txt" ]
8310
.iter ( )
8311
.map ( | p | view.entries[ * p] .node )
8312
.collect ( ) ;
8313
repo.load_graphs_of ( & mut st, & nodes) .unwrap ( ) ;
8314
let before: Vec< Vec< u8 > > = nodes.iter ( ) .map ( | n | st.render_file ( n) .0 ) .collect ( ) ;
8315
assert_eq! ( st.built_count ( ) , 2 ) ;
8316
let shed = st.shed_graphs ( | b | repo.store_blob ( b) ) .unwrap ( ) ;
8317
assert_eq! ( ( shed, st.built_count ( ) ) , ( 2 , 0 ) ) ;
8318
8319
write ( root, " a.txt" , b " 1\n two\n 3\n 4\n 5\n " ) ;
8320
let h3 = repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
8321
let ( skeleton, texts) = repo.store ( ) .get_change_lazy ( & h3) .unwrap ( ) ;
8322
st.add_known ( h3, skeleton, texts) ;
8323
repo.load_graphs_of ( & mut st, & nodes) .unwrap ( ) ;
8324
let after: Vec< Vec< u8 > > = nodes.iter ( ) .map ( | n | st.render_file ( n) .0 ) .collect ( ) ;
8325
assert_eq! ( after[ 1 ] , before[ 1 ] ) ;
8326
assert_eq! ( after[ 0 ] , b " 1\n two\n 3\n 4\n 5\n " ) ;
8327
}
360aa74a Andy McCall 1 month ago ↶
8328
8329
# [ test ]
8330
fn many_edited_files_diff_in_parallel ( ) {
8331
8332
8333
8334
hermetic ( ) ;
8335
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8336
let root = dir.path ( ) ;
8337
let repo = Repo:: init( root) .unwrap ( ) ;
8338
let text = | i : usize , round : usize | {
8339
let mut s = String :: new( ) ;
8340
for line in 0 .. 50 {
8341
if round > 0 & & line % 7 = = i % 7 {
8342
s.push_str ( & format! ( " edited {round} {i} {line} \n " ) ) ;
8343
} else {
8344
s.push_str ( & format! ( " file {i} line {line} \n " ) ) ;
8345
}
8346
}
8347
if round > 0 & & i.is_multiple_of ( 3 ) {
8348
s.push_str ( " appended\n " ) ;
8349
}
8350
s
8351
} ;
8352
for i in 0 .. 40 {
8353
write ( root, & format! ( " d/f{i} .txt" ) , text ( i, 0 ) .as_bytes ( ) ) ;
8354
}
8355
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8356
for i in 0 .. 40 {
8357
write ( root, & format! ( " d/f{i} .txt" ) , text ( i, 1 ) .as_bytes ( ) ) ;
8358
}
8359
let h = repo.commit ( " edit every file" , & [ ] ) .unwrap ( ) ;
8360
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
8361
for i in 0 .. 40 {
8362
let want = text ( i, 1 ) ;
8363
assert! (
8364
matches! ( & snap.files[ & format! ( " d/f{i} .txt" ) ] .content, FileContent:: Text { bytes, .. } if bytes = = want.as_bytes ( ) ) ,
8365
" d/f{i}.txt as committed"
8366
) ;
8367
}
8368
assert! (
8369
repo.commit ( " nothing" , & [ ] ) .is_err ( ) ,
8370
" nothing left to commit"
8371
) ;
8372
}
b35df36b Andy McCall 1 month ago ↶
8373
8374
8375
8376
8377
8378
# [ test ]
8379
fn a_fetched_change_shows_as_behind_and_resolves ( ) {
8380
hermetic ( ) ;
8381
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8382
let root = dir.path ( ) ;
8383
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
8384
drop ( origin) ;
8385
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
8386
let a = Repo:: clone( & url, & root.join ( " a" ) )
8387
.unwrap ( )
8388
.with_home ( root.join ( " home-a" ) ) ;
8389
write ( & root.join ( " a" ) , " f.txt" , b " one\n " ) ;
8390
a.commit ( " one" , & [ ] ) .unwrap ( ) ;
8391
a.push ( " origin" , false ) .unwrap ( ) ;
8392
let b = Repo:: clone( & url, & root.join ( " b" ) )
8393
.unwrap ( )
8394
.with_home ( root.join ( " home-b" ) ) ;
8395
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 0 ) ) ) ;
8396
8397
write ( & root.join ( " a" ) , " f.txt" , b " one\n two\n " ) ;
8398
let h2 = a.commit ( " two" , & [ ] ) .unwrap ( ) ;
8399
a.push ( " origin" , false ) .unwrap ( ) ;
8400
b.fetch ( " origin" ) .unwrap ( ) ;
8401
8402
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 1 ) ) ) ;
8403
assert_eq! ( b.tracking ( " origin" , " nope" ) .unwrap ( ) , None ) ;
8404
assert_eq! (
8405
b.resolve_change ( & format! ( " #{} " , & h2.to_hex ( ) [ .. 12 ] ) )
8406
.unwrap ( ) ,
8407
h2
8408
) ;
8409
assert_eq! (
8410
b.remote_only_location ( & h2) .unwrap ( ) .as_deref ( ) ,
8411
Some ( " origin/main" )
8412
) ;
8413
8414
8415
b.fetch ( " origin" ) .unwrap ( ) ;
8416
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 1 ) ) ) ;
8417
b.pull ( " origin" ) .unwrap ( ) ;
8418
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 0 ) ) ) ;
8419
assert_eq! ( b.remote_only_location ( & h2) .unwrap ( ) , None ) ;
8420
8421
8422
let mut c = Repo:: init( & root.join ( " c" ) ) .unwrap ( ) ;
8423
assert_eq! ( c.default_remote ( ) , " origin" ) ;
8424
c.config.set ( " remote.earth.url" , & url) .unwrap ( ) ;
8425
assert_eq! ( c.default_remote ( ) , " earth" ) ;
8426
c.config.set ( " remote.origin.url" , & url) .unwrap ( ) ;
8427
assert_eq! ( c.default_remote ( ) , " origin" ) ;
8428
}
827281ee Andy McCall 1 month ago ↶
8429
8430
8431
8432
# [ test ]
8433
fn a_clone_from_a_relative_path_records_it_absolute ( ) {
8434
hermetic ( ) ;
8435
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8436
let root = dir.path ( ) ;
8437
Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
8438
let rel = crate :: protocol:: stored_url( " relative/repo" ) ;
8439
assert! ( std:: path:: Path:: new( & rel) .is_absolute ( ) , " {rel}" ) ;
8440
assert_eq! (
8441
crate :: protocol:: stored_url( " https://hub.example/o/n" ) ,
8442
" https://hub.example/o/n"
8443
) ;
8444
assert! ( crate :: protocol:: stored_url( " file://x" ) .starts_with ( " file:///" ) ) ;
8d803563 Andy McCall 1 month ago ↶
8445
assert! ( crate :: protocol:: stored_url( " dir://x" ) .starts_with ( " dir:///" ) ) ;
8446
8447
assert_eq! (
8448
crate :: protocol:: stored_url( " s3://bucket/prefix?profile=p" ) ,
8449
" s3://bucket/prefix?profile=p"
8450
) ;
827281ee Andy McCall 1 month ago ↶
8451
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
8452
let b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) ;
8453
assert_eq! ( b.config.remotes[ " origin" ] , url) ;
8454
b.fetch ( " origin" ) .unwrap ( ) ;
8455
}
a46f3e4f Andy McCall 1 month ago ↶
8456
8457
8458
8459
8460
# [ test ]
8461
fn a_workspace_added_on_a_bookmark_is_on_it_with_its_subrepos ( ) {
8462
hermetic ( ) ;
8463
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8464
let root = dir.path ( ) ;
8465
Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
8466
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
8467
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
8468
.unwrap ( )
8469
.with_home ( root.join ( " home" ) ) ;
8470
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
8471
let l1 = lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
8472
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
8473
8474
let app_dir = root.join ( " app" ) ;
8475
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8476
write ( & app_dir, " main.txt" , b " app\n " ) ;
8477
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
8478
app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
8479
8480
app.bookmark_create ( " develop" , None ) .unwrap ( ) ;
8481
app.bookmark_create ( " feature" , None ) .unwrap ( ) ;
8482
let mut app = app;
8483
app.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
8484
assert_eq! ( app.config.bookmark, " feature" ) ;
8485
8486
8487
std:: fs:: rename( root.join ( " lib-origin" ) , root.join ( " lib-origin-gone" ) ) .unwrap ( ) ;
8488
let heads = app.resolve_heads ( " develop" ) .unwrap ( ) ;
8489
let ws = app
8490
.workspace_add_on ( & root.join ( " ws" ) , Some ( & heads) , Some ( " develop" ) )
8491
.unwrap ( ) ;
8492
assert_eq! (
8493
ws.config.bookmark, " develop" ,
8494
" the new workspace is on develop"
8495
) ;
8496
assert_eq! (
8497
app.config.bookmark, " feature" ,
8498
" and this one stays on its own"
8499
) ;
8500
let sub = Repo:: open( & root.join ( " ws/libs/lib" ) ) .unwrap ( ) ;
8501
assert_eq! (
8502
sub.config.remotes[ " origin" ] , lib_url,
8503
" its origin is the subrepo's URL"
8504
) ;
8505
assert_eq! ( sub.bookmark_heads ( ) .unwrap ( ) , vec! [ l1] , " at its pin" ) ;
8506
assert_eq! (
8507
std:: fs:: read( root.join ( " ws/libs/lib/lib.txt" ) ) .unwrap ( ) ,
8508
b " v1\n "
8509
) ;
8510
}
47ce8b8e Andy McCall 1 month ago ↶
8511
8512
8513
8514
8515
# [ test ]
8516
fn revsets_select_changes_by_relation_author_text_date_and_path ( ) {
8517
hermetic ( ) ;
8518
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8519
let root = dir.path ( ) ;
8520
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8521
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
8522
.unwrap ( ) ;
8523
write ( root, " src/a.rs" , b " one\n " ) ;
8524
let a = repo.commit ( " first: add a" , & [ ] ) .unwrap ( ) ;
8525
write ( root, " src/a.rs" , b " one\n two\n " ) ;
8526
let b = repo.commit ( " second: grow a" , & [ ] ) .unwrap ( ) ;
8527
8528
repo.checkout ( & [ a] , false , " checkout a" ) .unwrap ( ) ;
8529
write ( root, " docs/notes.md" , b " notes\n " ) ;
8530
let c = repo.commit ( " third: notes" , & [ ] ) .unwrap ( ) ;
8531
8532
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
8533
.unwrap ( ) ;
8534
fs:: remove_file( root.join ( " src/a.rs" ) ) .unwrap ( ) ;
8535
write ( root, " src/z.rs" , b " one\n two\n " ) ;
8536
let d = repo.commit ( " fourth: rename a to z" , & [ ] ) .unwrap ( ) ;
8537
let wc = repo.working_copy ( ) .unwrap ( ) .0 ;
8538
8539
repo.set_user_config ( " revset.mine" , " author(me) & ::main" )
8540
.unwrap ( ) ;
8541
assert! ( repo.set_user_config ( " revset.bad" , " a &" ) .is_err ( ) ) ;
8542
let set = | src : & str | -> Vec< ChangeHash> {
8543
let mut v = repo.revset ( src) .unwrap ( ) .hashes;
8544
v.sort ( ) ;
8545
v
8546
} ;
8547
let sorted = | hs : & [ChangeHash]| -> Vec< ChangeHash> {
8548
let mut v = hs.to_vec ( ) ;
8549
v.sort ( ) ;
8550
v
8551
} ;
8552
8553
8554
8555
8556
let heads = sorted ( & repo.bookmark_heads ( ) .unwrap ( ) ) ;
8557
let meta = | h : & ChangeHash| repo.store ( ) .get_change_meta ( h) .unwrap ( ) ;
8558
assert! ( heads.contains ( & d) & & heads.contains ( & c) ) ;
8559
8560
assert_eq! ( set ( " main" ) , heads) ;
8561
assert_eq! ( set ( " ::main" ) , sorted ( & [ a, b, c, d] ) ) ;
8562
assert_eq! ( set ( " @" ) , vec! [ wc] ) ;
8563
assert_eq! ( set ( " ::@" ) , sorted ( & [ a, b, c, d, wc] ) ) ;
8564
assert_eq! ( set ( & format! ( " ::#{} " , b.short ( 12 ) ) ) , sorted ( & [ a, b] ) ) ;
8565
assert_eq! ( set ( & format! ( " #{} ::" , a.short ( 12 ) ) ) , sorted ( & [ a, b, d, wc] ) ) ;
8566
assert_eq! (
8567
set ( & format! ( " #{} ::#{} " , a.short ( 12 ) , b.short ( 12 ) ) ) ,
8568
sorted ( & [ a, b] )
8569
) ;
8570
8571
assert_eq! ( set ( & format! ( " #{} ..main" , b.short ( 12 ) ) ) , sorted ( & [ c, d] ) ) ;
8572
assert_eq! ( set ( & format! ( " #{} .." , c.short ( 12 ) ) ) , sorted ( & [ a, b, d, wc] ) ) ;
8573
assert_eq! ( set ( " ..main" ) , sorted ( & [ a, b, c, d] ) ) ;
8574
8575
assert_eq! (
8576
set ( & format! ( " ::main ~ #{} " , d.short ( 12 ) ) ) ,
8577
sorted ( & [ a, b, c] )
8578
) ;
8579
assert_eq! (
8580
set ( & format! ( " ::main & #{} | #{} " , b.short ( 12 ) , c.short ( 12 ) ) ) ,
8581
sorted ( & [ b, c] )
8582
) ;
8583
assert_eq! ( set ( " all() ~ ::main" ) , vec! [ wc] ) ;
8584
8585
let mut head_deps: Vec< ChangeHash> = heads.iter ( ) .flat_map ( | h | meta ( h) .deps) .collect ( ) ;
8586
head_deps.sort ( ) ;
8587
head_deps.dedup ( ) ;
8588
assert_eq! ( set ( " deps(main)" ) , head_deps) ;
8589
let on_a: Vec< ChangeHash> = [ b, c, d]
8590
.into_iter ( )
8591
.filter ( | h | meta ( h) .deps.contains ( & a) )
8592
.collect ( ) ;
8593
assert_eq! ( set ( & format! ( " dependents(#{} )" , a.short ( 12 ) ) ) , sorted ( & on_a) ) ;
8594
assert_eq! ( set ( " heads(::main)" ) , heads) ;
8595
assert_eq! ( set ( " roots(::main)" ) , sorted ( & [ a, c] ) ) ;
8596
assert_eq! (
8597
set ( & format! (
8598
" heads(#{} | #{} | #{} )" ,
8599
a.short ( 12 ) ,
8600
b.short ( 12 ) ,
8601
c.short ( 12 )
8602
) ) ,
8603
sorted ( & [ b, c] )
8604
) ;
8605
assert_eq! ( set ( " latest(::main, 1)" ) , vec! [ d] ) ;
8606
assert_eq! ( set ( " kind(working-copy)" ) , vec! [ wc] ) ;
8607
assert_eq! ( set ( " none()" ) , vec! [ ] ) ;
8608
8609
let key = repo.config.author_key.unwrap ( ) ;
8610
assert_eq! ( set ( " author(ada) & ::main" ) , sorted ( & [ a, b, c, d] ) ) ;
8611
assert_eq! ( set ( " author(\" [email protected] \" ) & ::main" ) .len ( ) , 4 ) ;
8612
assert_eq! ( set ( & format! ( " author({} ) & ::main" , key.short ( 12 ) ) ) .len ( ) , 4 ) ;
8613
assert_eq! ( set ( " author(me) & ::main" ) .len ( ) , 4 ) ;
8614
assert_eq! ( set ( " author(nobody)" ) , vec! [ ] ) ;
8615
assert_eq! ( set ( " description(notes)" ) , vec! [ c] ) ;
8616
assert_eq! ( set ( " description(\" rename a\" )" ) , vec! [ d] ) ;
8617
assert_eq! ( set ( " date(-1h..) & ::main" ) .len ( ) , 4 ) ;
8618
assert_eq! ( set ( " date(..2000-01-01)" ) , vec! [ ] ) ;
8619
8620
assert_eq! ( set ( " touches(\" src/z.rs\" )" ) , sorted ( & [ a, b, d] ) ) ;
8621
assert_eq! ( set ( " touches(\" src/**\" ) & ::main" ) , sorted ( & [ a, b, d] ) ) ;
8622
assert_eq! ( set ( " touches(\" docs/*\" )" ) , vec! [ c] ) ;
8623
assert_eq! ( set ( " touches(\" nothing/*\" )" ) , vec! [ ] ) ;
8624
8625
assert_eq! ( repo.revset ( " ::main" ) .unwrap ( ) .hashes[ 0 ] , d) ;
8626
8627
assert_eq! ( set ( " mine()" ) .len ( ) , 4 ) ;
8628
let e = repo.revset ( " nosuch()" ) .unwrap_err ( ) .to_string ( ) ;
8629
assert! ( e.contains ( " unknown function nosuch()" ) , " {e}" ) ;
8630
let e = repo.revset ( " main &" ) .unwrap_err ( ) .to_string ( ) ;
8631
assert! ( e.contains ( " at character" ) , " {e}" ) ;
8632
let e = repo.revset ( " zzzz" ) .unwrap_err ( ) .to_string ( ) ;
8633
assert! ( e.contains ( " no change matches" ) , " {e}" ) ;
8634
let e = repo
8635
.revset_with_budget ( " ::main" , std:: time:: Duration:: ZERO )
8636
.unwrap_err ( )
8637
.to_string ( ) ;
8638
assert! ( e.contains ( " budget" ) , " {e}" ) ;
8639
8640
let entries = repo.log_revset ( " ::@" ) .unwrap ( ) ;
8641
assert! ( entries[ 0 ] .is_working_copy & & entries.iter ( ) .any ( | e | e.is_head & & e.hash = = d ) ) ;
2c9ddac3 Andy McCall 1 month ago ↶
8642
8643
8644
assert_eq! ( set ( " unsigned() & ::main" ) , sorted ( & [ a, b, c, d] ) ) ;
8645
assert_eq! ( set ( " signed_by(me)" ) , vec! [ ] ) ;
8646
repo.sign_change ( & d) .unwrap ( ) ;
8647
assert_eq! ( set ( " signed_by(me)" ) , vec! [ d] ) ;
8648
assert_eq! ( set ( " signed_by([email protected] )" ) , vec! [ d] ) ;
8649
assert_eq! ( set ( & format! ( " signed_by({} )" , key.short ( 12 ) ) ) , vec! [ d] ) ;
8650
assert_eq! ( set ( " unsigned() & ::main" ) , sorted ( & [ a, b, c] ) ) ;
8651
assert_eq! ( set ( " delegated()" ) , vec! [ ] ) ;
8652
assert_eq! ( set ( " revoked()" ) , vec! [ ] ) ;
8653
assert_eq! ( set ( " attested(tag)" ) , vec! [ ] ) ;
8654
repo.tag_create ( " v1" , & [ b] , " first" ) .unwrap ( ) ;
8655
assert_eq! ( set ( " attested(tag)" ) , sorted ( & [ a, b] ) ) ;
8656
assert_eq! ( set ( " attested(tag) & unsigned()" ) , sorted ( & [ a, b] ) ) ;
8657
assert_eq! ( set ( " attested(receipt)" ) , vec! [ ] ) ;
8658
assert_eq! ( set ( " built(test)" ) , vec! [ ] ) ;
8659
let e = repo.revset ( " attested(seal)" ) .unwrap_err ( ) .to_string ( ) ;
8660
assert! ( e.contains ( " tag, receipt or ref" ) , " {e}" ) ;
8661
repo.revoke_identity ( & key, " test" ) .unwrap ( ) ;
8662
assert_eq! ( set ( " revoked()" ) , vec! [ d] ) ;
8663
assert_eq! ( set ( " signed_by(me) ~ revoked()" ) , vec! [ ] ) ;
47ce8b8e Andy McCall 1 month ago ↶
8664
}
614f55e1 Andy McCall 1 month ago ↶
8665
8666
# [ test ]
8667
fn a_rotation_is_recorded_wherever_the_old_key_was_named ( ) {
8668
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8669
let root = dir.path ( ) ;
8670
let home = root.join ( " home" ) ;
8671
8672
8673
let mut a = Repo:: init( & root.join ( " a" ) ) .unwrap ( ) .with_home ( & home) ;
8674
let ( key, _ ) = a
8675
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
8676
.unwrap ( ) ;
8677
a.config.set ( " author.key" , & key.to_hex ( ) ) .unwrap ( ) ;
8678
a.save_config ( ) .unwrap ( ) ;
8679
let agent = SecretKey:: generate( ) .identity ( " Agent" , " [email protected] " , 1 ) ;
8680
let r = a
8681
.delegate_add ( agent.clone ( ) , crate :: identity:: Scope:: CHANGES , None )
8682
.unwrap ( ) ;
8683
assert_eq! (
8684
read_config_file ( & home.join ( " config.toml" ) ) .unwrap ( ) [ " author.key" ] ,
8685
r.new.to_hex ( ) ,
8686
" the user file follows"
8687
) ;
8688
assert_eq! (
8689
read_config_file ( & root.join ( " a/.oxy/config" ) ) .unwrap ( ) [ " author.key" ] ,
8690
r.new.to_hex ( ) ,
8691
" the pinning repository follows"
8692
) ;
8693
let b = Repo:: init( & root.join ( " b" ) ) .unwrap ( ) .with_home ( & home) ;
8694
assert_eq! (
8695
b.config.author_key,
8696
Some ( r.new) ,
8697
" a checkout without a pin follows the user file"
8698
) ;
8699
8700
let other_home = root.join ( " home2" ) ;
8701
let mut c = Repo:: init( & root.join ( " c" ) ) .unwrap ( ) .with_home ( & other_home) ;
8702
let ( ck, _ ) = c
8703
.create_identity ( " Cy" , " [email protected] " , IdentityScope:: Repo)
8704
.unwrap ( ) ;
8705
assert! ( ! other_home.join ( " config.toml" ) .exists ( ) ) ;
8706
let r2 = c.rotate_identity ( ) .unwrap ( ) ;
8707
assert_ne! ( r2.new, ck) ;
8708
assert_eq! (
8709
read_config_file ( & root.join ( " c/.oxy/config" ) ) .unwrap ( ) [ " author.key" ] ,
8710
r2.new.to_hex ( )
8711
) ;
8712
assert! (
8713
! other_home.join ( " config.toml" ) .exists ( ) ,
8714
" a repository-scoped identity stays repository-scoped"
8715
) ;
8716
8717
let ( d1, d2) = (
8718
SecretKey:: generate( ) .identity ( " D1" , " d1@x" , 1 ) ,
8719
SecretKey:: generate( ) .identity ( " D2" , " d2@x" , 1 ) ,
8720
) ;
8721
let r3 = c
8722
.delegate_add_many ( vec! [ d1.clone ( ) , d2] , crate :: identity:: Scope:: CHANGES , None )
8723
.unwrap ( ) ;
8724
assert_eq! ( r3.identity.delegates.len ( ) , 2 ) ;
8725
assert! ( c
8726
.delegate_add ( d1, crate :: identity:: Scope:: CHANGES , None )
8727
.is_err ( ) ) ;
8728
}
33be2409 Andy McCall 1 month ago ↶
8729
8730
8731
8732
8733
8734
# [ test ]
8735
fn git_import_joins_unrelated_histories_that_add_the_same_file ( ) {
8736
8737
8738
crate :: repo:: git:: set_merge_batch_for_tests( Some ( 1 ) ) ;
8739
hermetic ( ) ;
8740
if std:: process:: Command:: new( " git" )
8741
.arg ( " --version" )
8742
.output ( )
8743
.is_err ( )
8744
{
8745
eprintln! ( " git not installed; skipping" ) ;
8746
return ;
8747
}
8748
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8749
let root = dir.path ( ) ;
8750
let g = root.join ( " src.git" ) ;
8751
fs:: create_dir_all( & g) .unwrap ( ) ;
8752
let git = | args : & [& str ]| -> String {
8753
let out = std:: process:: Command:: new( " git" )
8754
.arg ( " -C" )
8755
.arg ( & g)
8756
.args ( [
8757
" -c" ,
8758
" user.name=Grace" ,
8759
" -c" ,
8760
" [email protected] " ,
8761
" -c" ,
8762
" commit.gpgsign=false" ,
8763
] )
8764
.args ( args)
8765
.output ( )
8766
.unwrap ( ) ;
8767
assert! (
8768
out.status.success ( ) ,
8769
" git {args:?}: {}" ,
8770
String :: from_utf8_lossy( & out.stderr)
8771
) ;
8772
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
8773
} ;
8774
let text = b " /* header */\n const a = 1;\n const b = 2;\n " ;
8775
8776
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
8777
write ( & g, " gulpfile.js" , text) ;
8778
write ( & g, " a.txt" , b " a\n " ) ;
8779
git ( & [ " add" , " ." ] ) ;
8780
git ( & [ " commit" , " -q" , " -m" , " A root" ] ) ;
8781
8782
git ( & [ " checkout" , " -q" , " --orphan" , " fork" ] ) ;
8783
git ( & [ " rm" , " -q" , " -r" , " --cached" , " ." ] ) ;
8784
for f in [ " gulpfile.js" , " a.txt" ] {
8785
let _ = fs:: remove_file( g.join ( f) ) ;
8786
}
8787
write ( & g, " gulpfile.js" , text) ;
8788
write ( & g, " b.txt" , b " b\n " ) ;
8789
git ( & [ " add" , " ." ] ) ;
8790
git ( & [ " commit" , " -q" , " -m" , " B root" ] ) ;
8791
8792
git ( & [ " checkout" , " -q" , " -b" , " one" , " main" ] ) ;
8793
git ( & [
8794
" merge" ,
8795
" -q" ,
8796
" --no-ff" ,
8797
" --allow-unrelated-histories" ,
8798
" -m" ,
8799
" join on one" ,
8800
" fork" ,
8801
] ) ;
8802
git ( & [ " checkout" , " -q" , " -b" , " two" , " main" ] ) ;
8803
write ( & g, " a.txt" , b " a\n two\n " ) ;
8804
git ( & [ " commit" , " -q" , " -am" , " work on two" ] ) ;
8805
git ( & [
8806
" merge" ,
8807
" -q" ,
8808
" --no-ff" ,
8809
" --allow-unrelated-histories" ,
8810
" -m" ,
8811
" join on two" ,
8812
" fork" ,
8813
] ) ;
8814
8815
git ( & [ " checkout" , " -q" , " main" ] ) ;
8816
git ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge one" , " one" ] ) ;
8817
git ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge two" , " two" ] ) ;
8818
write (
8819
& g,
8820
" gulpfile.js" ,
8821
b " /* header */\n const a = 1;\n const b = 3;\n " ,
8822
) ;
8823
git ( & [ " commit" , " -q" , " -am" , " after" ] ) ;
8824
8825
let dest = root.join ( " dest" ) ;
8826
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8827
repo.import_git ( & g, " main" ) .unwrap ( ) ;
8828
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
8829
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
8830
assert_eq! (
8831
fs:: read( dest.join ( " gulpfile.js" ) ) .unwrap ( ) ,
8832
b " /* header */\n const a = 1;\n const b = 3;\n "
8833
) ;
8834
}
8835
8836
8837
8838
8839
# [ test ]
8840
fn git_import_joins_where_each_join_kept_a_different_copy ( ) {
8841
8842
8843
crate :: repo:: git:: set_merge_batch_for_tests( Some ( 1 ) ) ;
8844
hermetic ( ) ;
8845
if std:: process:: Command:: new( " git" )
8846
.arg ( " --version" )
8847
.output ( )
8848
.is_err ( )
8849
{
8850
eprintln! ( " git not installed; skipping" ) ;
8851
return ;
8852
}
8853
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8854
let root = dir.path ( ) ;
8855
let g = root.join ( " src.git" ) ;
8856
fs:: create_dir_all( & g) .unwrap ( ) ;
8857
let run = | args : & [& str ]| {
8858
std:: process:: Command:: new( " git" )
8859
.arg ( " -C" )
8860
.arg ( & g)
8861
.args ( [
8862
" -c" ,
8863
" user.name=Grace" ,
8864
" -c" ,
8865
" [email protected] " ,
8866
" -c" ,
8867
" commit.gpgsign=false" ,
8868
] )
8869
.args ( args)
8870
.output ( )
8871
.unwrap ( )
8872
} ;
8873
let git = | args : & [& str ]| -> String {
8874
let out = run ( args) ;
8875
assert! (
8876
out.status.success ( ) ,
8877
" git {args:?}: {}" ,
8878
String :: from_utf8_lossy( & out.stderr)
8879
) ;
8880
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
8881
} ;
8882
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
8883
write (
8884
& g,
8885
" gulpfile.js" ,
8886
b " /* header */\n const a = 1;\n const b = 2;\n " ,
8887
) ;
8888
git ( & [ " add" , " ." ] ) ;
8889
git ( & [ " commit" , " -q" , " -m" , " A root" ] ) ;
8890
write (
8891
& g,
8892
" gulpfile.js" ,
8893
b " /* header */\n const a = 1;\n \n const b = 2;\n " ,
8894
) ;
8895
git ( & [ " commit" , " -q" , " -am" , " whitespace" ] ) ;
8896
git ( & [ " checkout" , " -q" , " --orphan" , " fork" ] ) ;
8897
git ( & [ " rm" , " -q" , " -r" , " --cached" , " ." ] ) ;
8898
let _ = fs:: remove_file( g.join ( " gulpfile.js" ) ) ;
8899
write (
8900
& g,
8901
" gulpfile.js" ,
8902
b " /* header */\n const a = 1;\n const b = 2;\n const c = 3;\n " ,
8903
) ;
8904
git ( & [ " add" , " ." ] ) ;
8905
git ( & [ " commit" , " -q" , " -m" , " B root" ] ) ;
8906
8907
for ( branch, keep) in [ ( " one" , " ours" ) , ( " two" , " theirs" ) ] {
8908
git ( & [ " checkout" , " -q" , " -b" , branch, " main" ] ) ;
8909
let _ = run ( & [
8910
" merge" ,
8911
" -q" ,
8912
" --no-ff" ,
8913
" --allow-unrelated-histories" ,
8914
" -m" ,
8915
" join" ,
8916
" fork" ,
8917
] ) ;
8918
git ( & [ " checkout" , & format! ( " --{keep} " ) , " gulpfile.js" ] ) ;
8919
git ( & [ " add" , " gulpfile.js" ] ) ;
8920
git ( & [ " commit" , " -q" , " -m" , & format! ( " join on {branch} " ) ] ) ;
8921
}
8922
8923
git ( & [ " checkout" , " -q" , " one" ] ) ;
8924
write (
8925
& g,
8926
" gulpfile.js" ,
8927
b " /* header */\n const a = 1;\n \n const b = 2;\n // tslint\n " ,
8928
) ;
8929
git ( & [ " commit" , " -q" , " -am" , " tslint on one" ] ) ;
8930
git ( & [ " checkout" , " -q" , " two" ] ) ;
8931
write (
8932
& g,
8933
" gulpfile.js" ,
8934
b " /* header */\n const a = 1;\n const b = 2;\n const c = 3;\n // fmt\n " ,
8935
) ;
8936
write ( & g, " extra.txt" , b " x\n " ) ;
8937
write ( & g, " more.txt" , b " m\n " ) ;
8938
git ( & [ " add" , " ." ] ) ;
8939
git ( & [ " commit" , " -q" , " -am" , " fmt on two" ] ) ;
8940
8941
git ( & [ " checkout" , " -q" , " one" ] ) ;
8942
let _ = run ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge two" , " two" ] ) ;
8943
write (
8944
& g,
8945
" gulpfile.js" ,
8946
b " /* header */\n const a = 1;\n const b = 2;\n const c = 3;\n // tslint\n // fmt\n " ,
8947
) ;
8948
git ( & [ " add" , " gulpfile.js" ] ) ;
8949
git ( & [ " commit" , " -q" , " -m" , " merge two into one" ] ) ;
8950
8951
let dest = root.join ( " dest" ) ;
8952
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8953
repo.import_git ( & g, " one" ) .unwrap ( ) ;
8954
let v = repo.verify_git_import ( & g, " one" ) .unwrap ( ) ;
8955
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
8956
}
dd21fd82 Andy McCall 1 month ago ↶
8957
8958
8959
8960
8961
8962
8963
# [ test ]
8964
fn over_a_store_the_operation_head_moves_by_compare_and_swap ( ) {
8965
use crate :: strata:: { MemoryStore, Store as _ } ;
8966
let backend = std:: sync:: Arc:: new( MemoryStore:: new( ) ) ;
8967
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = Some ( backend.clone ( ) ) ) ;
8968
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8969
let a = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
8970
assert! (
8971
! dir.path ( ) .join ( " .oxy/ops/head" ) .exists ( ) ,
8972
" the head is not a file"
8973
) ;
8974
let ( h0, op0) = a.head_op ( ) .unwrap ( ) ;
8975
let record = backend
8976
.read_ref ( HEAD_RECORD )
8977
.unwrap ( )
8978
.expect ( " the head is the store's record" ) ;
8979
assert_eq! ( String :: from_utf8( record.0 ) .unwrap ( ) , h0.to_hex ( ) ) ;
8980
8981
let b = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
8982
let state = a.store.put_state ( & StateObject:: new( vec! [ ] ) ) .unwrap ( ) ;
8983
let mut va = op0.view.clone ( ) ;
8984
va.bookmarks.insert ( " from-a" .into ( ) , state) ;
8985
let mut vb = op0.view.clone ( ) ;
8986
vb.bookmarks.insert ( " from-b" .into ( ) , state) ;
8987
let rb = b.record_op ( & [ h0] , " bookmark from-b" , vb) .unwrap ( ) ;
8988
assert_eq! ( b.head_ref ( ) .unwrap ( ) , rb) ;
8989
8990
let ra = a.record_op ( & [ h0] , " bookmark from-a" , va) .unwrap ( ) ;
8991
let ( head, op) = Repo:: open( dir.path ( ) ) .unwrap ( ) .head_op ( ) .unwrap ( ) ;
8992
assert_eq! ( head, ra) ;
8993
assert! (
8994
op.command.ends_with ( " (merged with a concurrent operation)" ) ,
8995
" {}" ,
8996
op.command
8997
) ;
8998
assert_eq! ( op.parents, vec! [ rb, op.parents[ 1 ] ] ) ;
8999
assert! ( op.view.bookmarks.contains_key ( " from-a" ) & & op.view.bookmarks.contains_key ( " from-b" ) ) ;
9000
9001
9002
let ( h, v) = a.read_head ( ) .unwrap ( ) ;
9003
assert! ( b.swap_head ( & rb, v.as_ref ( ) ) .unwrap ( ) ) ;
9004
assert! ( ! a.swap_head ( & h, v.as_ref ( ) ) .unwrap ( ) , " stale" ) ;
767ac6cd Andy McCall 19 days ago
9005
a.refresh ( ) .unwrap ( ) ;
dd21fd82 Andy McCall 1 month ago ↶
9006
assert_eq! ( a.head_ref ( ) .unwrap ( ) , rb) ;
9007
9008
let other = tempfile:: tempdir( ) .unwrap ( ) ;
9009
let err = Repo:: init( other.path ( ) )
9010
.err ( )
9011
.expect ( " the store already holds a log" )
9012
.to_string ( ) ;
9013
assert! (
9014
err.contains ( " already holds a repository's operation log" ) ,
9015
" {err}"
9016
) ;
9017
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = None ) ;
9018
}
d069701a Andy McCall 1 month ago ↶
9019
9020
9021
9022
9023
# [ test ]
9024
fn a_repository_runs_on_a_store_with_a_cache_in_front ( ) {
9025
hermetic ( ) ;
9026
if std:: env:: var_os( " OXY_STORE_BACKEND" ) .is_some ( ) {
9027
return ;
9028
}
9029
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9030
let ( root, bucket) = ( dir.path ( ) .join ( " work" ) , dir.path ( ) .join ( " bucket" ) ) ;
9031
let url = format! ( " dir://{} " , bucket.display ( ) ) ;
9032
let repo = Repo:: init_on( & root, [ 7 ; 16 ] , Some ( & url) ) .unwrap ( ) ;
9033
assert_eq! ( repo.config.storage_url.as_deref ( ) , Some ( url.as_str ( ) ) ) ;
9034
write ( & root, " a.txt" , b " one\n " ) ;
9035
let h1 = repo.commit ( " First" , & [ ] ) .unwrap ( ) ;
9036
write ( & root, " a.txt" , b " one\n two\n " ) ;
9037
let h2 = repo.commit ( " Second" , & [ ] ) .unwrap ( ) ;
9038
assert! (
9039
! root.join ( " .oxy/ops/head" ) .exists ( ) ,
9040
" the head is in the store"
9041
) ;
9042
assert! ( bucket.join ( " ops/head" ) .is_file ( ) ) ;
9043
assert! ( root.join ( " .oxy/cache" ) .is_dir ( ) ) ;
9044
drop ( repo) ;
9045
9046
9047
for drop_cache in [ false , true ] {
9048
if drop_cache {
9049
std:: fs:: remove_dir_all( root.join ( " .oxy/cache" ) ) .unwrap ( ) ;
9050
}
9051
let repo = Repo:: open( & root) .unwrap ( ) ;
9052
let log: Vec< String> = repo
9053
.op_log ( )
9054
.unwrap ( )
9055
.into_iter ( )
9056
.map ( | ( _, op ) | op.command )
9057
.collect ( ) ;
9058
assert! ( log.len ( ) > = 3 , " {log:?}" ) ;
9059
assert_eq! ( repo.store ( ) .get_change ( & h1) .unwrap ( ) .message, " First" ) ;
9060
let heads = repo
9061
.store ( )
9062
.get_state ( & repo.view ( ) .unwrap ( ) .bookmarks[ " main" ] )
9063
.unwrap ( )
9064
.heads;
9065
assert_eq! ( heads, vec! [ h2] ) ;
9066
assert_eq! (
9067
repo.load_state ( & heads) .unwrap ( ) .materialize ( ) .files.len ( ) ,
9068
1
9069
) ;
9070
}
9071
9072
9073
let err = Repo:: init_on( & dir.path ( ) .join ( " other" ) , [ 8 ; 16 ] , Some ( & url) )
9074
.err ( )
9075
.unwrap ( )
9076
.to_string ( ) ;
9077
assert! (
9078
err.contains ( " already holds a repository's operation log" ) ,
9079
" {err}"
9080
) ;
9081
let err = Repo:: init_on( & dir.path ( ) .join ( " third" ) , [ 9 ; 16 ] , Some ( " nope://x" ) )
9082
.err ( )
9083
.unwrap ( )
9084
.to_string ( ) ;
9085
assert! ( err.contains ( " cannot open that kind of store" ) , " {err}" ) ;
9086
}
80677b7a Andy McCall 1 month ago ↶
9087
9088
9089
9090
9091
9092
# [ test ]
9093
fn the_quick_dirty_check_compares_a_subrepo_by_its_pin ( ) {
9094
hermetic ( ) ;
9095
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9096
let root = dir.path ( ) ;
9097
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9098
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9099
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9100
.unwrap ( )
9101
.with_home ( root.join ( " home" ) ) ;
9102
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9103
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9104
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9105
let app_dir = root.join ( " app" ) ;
9106
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9107
write ( & app_dir, " main.txt" , b " app\n " ) ;
9108
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9109
app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
9110
9111
std:: thread:: sleep( std:: time:: Duration:: from_millis( 2100 ) ) ;
9112
let reopen = | | Repo:: open( & app_dir) .unwrap ( ) ;
9113
assert_eq! (
9114
reopen ( ) .quick_dirty ( ) .unwrap ( ) ,
9115
Some ( false ) ,
9116
" clean, as status says"
9117
) ;
9118
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) ) .unwrap ( ) ;
9119
write ( & app_dir.join ( " libs/lib" ) , " lib.txt" , b " v2\n " ) ;
9120
nested.commit ( " lib v2" , & [ ] ) .unwrap ( ) ;
9121
assert_eq! (
9122
reopen ( ) .quick_dirty ( ) .unwrap ( ) ,
9123
Some ( true ) ,
9124
" the subrepo moved"
9125
) ;
9126
reopen ( ) .commit ( " pin lib v2" , & [ ] ) .unwrap ( ) ;
9127
std:: thread:: sleep( std:: time:: Duration:: from_millis( 2100 ) ) ;
9128
assert_eq! (
9129
reopen ( ) .quick_dirty ( ) .unwrap ( ) ,
9130
Some ( false ) ,
9131
" the new pin is committed"
9132
) ;
9133
}
7993d465 Andy McCall 1 month ago ↶
9134
9135
# [ test ]
9136
fn a_module_entry_that_was_never_added_is_reported_as_unpinned ( ) {
9137
9138
hermetic ( ) ;
9139
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9140
let root = dir.path ( ) ;
9141
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9142
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9143
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9144
.unwrap ( )
9145
.with_home ( root.join ( " home" ) ) ;
9146
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9147
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9148
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9149
let app_dir = root.join ( " app" ) ;
9150
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9151
write (
9152
& app_dir,
9153
" .oxymodules" ,
9154
format! ( " [libs/lib]\n url = {lib_url} \n " ) .as_bytes ( ) ,
9155
) ;
9156
let unpinned = app.unpinned_modules ( ) .unwrap ( ) ;
9157
assert_eq! ( unpinned.len ( ) , 1 ) ;
9158
assert_eq! ( unpinned[ 0 ] .0 , " libs/lib" ) ;
9159
assert_eq! ( unpinned[ 0 ] .1. url, lib_url) ;
9160
assert! ( app.submodule_update ( true , & [ ] ) .unwrap ( ) .is_empty ( ) ) ;
9161
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9162
assert! ( app.unpinned_modules ( ) .unwrap ( ) .is_empty ( ) , " added now" ) ;
9163
}
bf3a6781 Andy McCall 1 month ago ↶
9164
9165
# [ test ]
9166
fn the_bookmark_revset_function_names_only_bookmarks ( ) {
9167
9168
hermetic ( ) ;
9169
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9170
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9171
write ( dir.path ( ) , " a.txt" , b " a\n " ) ;
9172
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
9173
write ( dir.path ( ) , " b.txt" , b " b\n " ) ;
9174
let b = repo.commit ( " B" , & [ ] ) .unwrap ( ) ;
9175
let mut heads = repo.revset ( " bookmark(main)" ) .unwrap ( ) .hashes;
9176
heads.sort ( ) ;
9177
let mut want = repo.bookmark_heads ( ) .unwrap ( ) ;
9178
want.sort ( ) ;
9179
assert_eq! ( heads, want) ;
9180
assert! ( heads.contains ( & b) ) ;
9181
assert_eq! ( repo.revset ( " main" ) .unwrap ( ) .hashes.len ( ) , heads.len ( ) ) ;
9182
9183
let prefix = repo.store ( ) .get_change_meta ( & a) .unwrap ( ) .change_id.short ( 8 ) ;
9184
assert! ( repo.revset ( & prefix) .is_ok ( ) ) ;
9185
let e = repo
9186
.revset ( & format! ( " bookmark({prefix} )" ) )
9187
.unwrap_err ( )
9188
.to_string ( ) ;
9189
assert! ( e.contains ( " no such bookmark" ) , " {e}" ) ;
9190
}
72484195 Andy McCall 1 month ago ↶
9191
157d02e9 Andy McCall 12 days ago ↶
9192
9193
9194
72484195 Andy McCall 1 month ago ↶
9195
# [ test ]
157d02e9 Andy McCall 12 days ago ↶
9196
fn a_replace_conflict_is_settled_from_the_working_copy ( ) {
9197
hermetic ( ) ;
9198
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9199
let root = dir.path ( ) ;
9200
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9201
let blob = | n : u8 | {
9202
let mut v = vec! [ 0 u8 ; 64 ] ;
9203
v[ 1 ] = n;
9204
v
9205
} ;
9206
write ( root, " img.bin" , & blob ( 1 ) ) ;
9207
write ( root, " a.txt" , b " a\n " ) ;
9208
repo.commit ( " add img" , & [ ] ) .unwrap ( ) ;
9209
repo.bookmark_create ( " other" , None ) .unwrap ( ) ;
9210
write ( root, " img.bin" , & blob ( 2 ) ) ;
9211
repo.commit ( " img two" , & [ ] ) .unwrap ( ) ;
9212
repo.bookmark_switch ( " other" , false ) .unwrap ( ) ;
9213
write ( root, " img.bin" , & blob ( 3 ) ) ;
9214
repo.commit ( " img three" , & [ ] ) .unwrap ( ) ;
9215
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
9216
repo.merge ( " other" ) .unwrap ( ) ;
9217
9218
9219
write ( root, " img.bin" , & blob ( 9 ) ) ;
9220
write ( root, " a.txt" , b " a\n more\n " ) ;
9221
assert! (
9222
repo.commit ( " edit a" , & [ ] ) .is_err ( ) ,
9223
" refused while conflicted"
9224
) ;
9225
let snap = repo.snapshot ( " oxy resolve" ) .unwrap ( ) ;
9226
assert! (
9227
repo.choose_resolutions ( & snap.base_snapshot, & [ " a.txt" .to_string ( ) ] )
9228
.is_err ( ) ,
9229
" a.txt has no replace conflict"
9230
) ;
9231
repo.choose_resolutions ( & snap.base_snapshot, & [ " img.bin" .to_string ( ) ] )
9232
.unwrap ( ) ;
9233
let snap = repo.snapshot ( " oxy resolve" ) .unwrap ( ) ;
9234
assert! ( snap
9235
.statuses
9236
.iter ( )
9237
.any ( | s | s.path = = " img.bin" & & s.status = = ChangeStatus:: Resolved ) ) ;
9238
let c = repo.commit ( " settle img" , & [ ] ) .unwrap ( ) ;
9239
assert_eq! (
9240
repo.store ( ) .get_change ( & c) .unwrap ( ) .kind,
9241
ChangeKind:: Resolution
9242
) ;
9243
let after = repo
9244
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
9245
.unwrap ( )
9246
.materialize ( ) ;
9247
assert! ( ! after.is_conflicted ( ) ) ;
9248
assert_eq! (
9249
after.files[ " img.bin" ] .content,
9250
FileContent:: Opaque {
9251
blobs: vec! [ Repo:: blob_ref( & blob ( 9 ) ) ]
9252
}
9253
) ;
9254
assert! (
9255
matches! ( & after.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n more\n " )
9256
) ;
9257
9258
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
9259
assert! ( snap.statuses.is_empty ( ) , " {:?}" , snap.statuses) ;
9260
assert! ( repo.resolutions ( ) .unwrap ( ) .is_empty ( ) ) ;
9261
}
9262
9263
9264
# [ test ]
9265
fn a_pin_conflict_is_settled_from_the_working_copy ( ) {
9266
hermetic ( ) ;
9267
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9268
let root = dir.path ( ) ;
9269
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9270
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9271
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9272
.unwrap ( )
9273
.with_home ( root.join ( " home" ) ) ;
9274
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9275
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9276
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9277
let app_dir = root.join ( " app" ) ;
9278
let mut app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9279
write ( & app_dir, " main.txt" , b " app\n " ) ;
9280
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9281
app.commit ( " pin v1" , & [ ] ) .unwrap ( ) ;
9282
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) )
9283
.unwrap ( )
9284
.with_home ( root.join ( " home" ) ) ;
9285
fn pin ( app : & Repo, nested : & Repo, text : & [u8 ], message : & str ) {
9286
write ( nested.root ( ) , " lib.txt" , text) ;
9287
nested.commit ( message, & [ ] ) .unwrap ( ) ;
9288
app.commit ( & format! ( " pin {message} " ) , & [ ] ) .unwrap ( ) ;
9289
}
9290
app.bookmark_create ( " other" , None ) .unwrap ( ) ;
9291
pin ( & app, & nested, b " v2\n " , " v2" ) ;
9292
app.bookmark_switch ( " other" , false ) .unwrap ( ) ;
9293
pin ( & app, & nested, b " v3\n " , " v3" ) ;
9294
app.bookmark_switch ( " main" , false ) .unwrap ( ) ;
9295
app.merge ( " other" ) .unwrap ( ) ;
9296
let merged = app
9297
.load_state ( & app.bookmark_heads ( ) .unwrap ( ) )
9298
.unwrap ( )
9299
.materialize ( ) ;
9300
assert! (
9301
matches! ( & merged.files[ " libs/lib" ] .content, FileContent:: Opaque { blobs } if blobs.len ( ) = = 2 ) ,
9302
" a pin conflict"
9303
) ;
9304
9305
write ( & app_dir.join ( " libs/lib" ) , " lib.txt" , b " v4\n " ) ;
9306
nested.commit ( " v4" , & [ ] ) .unwrap ( ) ;
9307
let snap = app.snapshot ( " oxy resolve" ) .unwrap ( ) ;
9308
app.choose_resolutions ( & snap.base_snapshot, & [ " libs/lib" .to_string ( ) ] )
9309
.unwrap ( ) ;
9310
let c = app.commit ( " settle the pin" , & [ ] ) .unwrap ( ) ;
9311
assert_eq! (
9312
app.store ( ) .get_change ( & c) .unwrap ( ) .kind,
9313
ChangeKind:: Resolution
9314
) ;
9315
let after = app
9316
.load_state ( & app.bookmark_heads ( ) .unwrap ( ) )
9317
.unwrap ( )
9318
.materialize ( ) ;
9319
assert! ( ! after.is_conflicted ( ) ) ;
9320
assert! ( app.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
9321
}
9322
9323
# [ test ]
72484195 Andy McCall 1 month ago ↶
9324
fn carrying_work_over_a_subrepo_already_at_the_target_pin_is_no_conflict ( ) {
9325
9326
9327
hermetic ( ) ;
9328
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9329
let root = dir.path ( ) ;
9330
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9331
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9332
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9333
.unwrap ( )
9334
.with_home ( root.join ( " home" ) ) ;
9335
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9336
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9337
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9338
let app_dir = root.join ( " app" ) ;
9339
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9340
write ( & app_dir, " main.txt" , b " app\n " ) ;
9341
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9342
9343
fs:: rename( app_dir.join ( " libs/lib" ) , root.join ( " aside" ) ) .unwrap ( ) ;
9344
app.commit ( " initial" , & [ ] ) .unwrap ( ) ;
9345
let stale = app.bookmark_heads ( ) .unwrap ( ) ;
9346
fs:: rename( root.join ( " aside" ) , app_dir.join ( " libs/lib" ) ) .unwrap ( ) ;
9347
app.commit ( " pin lib" , & [ ] ) .unwrap ( ) ;
9348
let newer = app.bookmark_heads ( ) .unwrap ( ) ;
9349
app.checkout ( & stale, false , " test" ) .unwrap ( ) ;
9350
write ( & app_dir, " notes.txt" , b " mine\n " ) ;
9351
let ( _ , carried) = app.checkout_carry ( & newer, " main" , " test" ) .unwrap ( ) ;
9352
assert_eq! ( carried, vec! [ " notes.txt" .to_string ( ) ] ) ;
9353
assert! ( app_dir.join ( " notes.txt" ) .exists ( ) ) ;
9354
}
9355
c8d9059e Andy McCall 12 days ago ↶
9356
9357
72484195 Andy McCall 1 month ago ↶
9358
# [ test ]
c8d9059e Andy McCall 12 days ago ↶
9359
fn a_new_bookmark_sends_only_what_the_remote_lacks ( ) {
9360
hermetic ( ) ;
9361
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9362
let root = dir.path ( ) ;
9363
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
9364
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
9365
let mut one = Repo:: clone( & url, & root.join ( " one" ) )
9366
.unwrap ( )
9367
.with_home ( root.join ( " home" ) ) ;
9368
for n in 0 .. 5 {
9369
write ( & root.join ( " one" ) , " a.txt" , format! ( " {n} \n " ) .as_bytes ( ) ) ;
9370
one.commit ( & format! ( " main {n} " ) , & [ ] ) .unwrap ( ) ;
9371
}
9372
assert! ( one.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9373
one.bookmark_create ( " feature" , None ) .unwrap ( ) ;
9374
one.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
9375
write ( & root.join ( " one" ) , " b.txt" , b " feature\n " ) ;
9376
let added = one.commit ( " feature" , & [ ] ) .unwrap ( ) ;
9377
let heads = one.bookmark_heads ( ) .unwrap ( ) ;
9378
let refs = one
9379
.open_named_remote ( " origin" )
9380
.unwrap ( )
9381
.refs ( )
9382
.unwrap ( )
9383
.refs;
9384
assert! (
9385
refs.iter ( ) .all ( | r | r.name ! = " feature" ) ,
9386
" new to the remote"
9387
) ;
9388
let sent = one
9389
.negotiate ( & one.remote_holds ( & refs, & [ ] ) , & heads)
9390
.unwrap ( ) ;
9391
assert_eq! ( sent, vec! [ added] , " only the change main lacks" ) ;
9392
assert! ( one.push_bookmark ( " origin" , " feature" , false ) .unwrap ( ) [ 0 ] .ok) ;
9393
let two = Repo:: clone( & url, & root.join ( " two" ) ) .unwrap ( ) ;
9394
assert! ( two.store ( ) .has ( & added) ) ;
9395
}
9396
9397
# [ test ]
72484195 Andy McCall 1 month ago ↶
9398
fn a_bookmark_only_behind_its_remote_catches_up ( ) {
9399
9400
hermetic ( ) ;
9401
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9402
let root = dir.path ( ) ;
9403
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
9404
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
9405
let one = Repo:: clone( & url, & root.join ( " one" ) )
9406
.unwrap ( )
9407
.with_home ( root.join ( " home" ) ) ;
9408
write ( & root.join ( " one" ) , " a.txt" , b " a\n " ) ;
9409
one.commit ( " A" , & [ ] ) .unwrap ( ) ;
9410
assert! ( one.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9411
let two = Repo:: clone( & url, & root.join ( " two" ) )
9412
.unwrap ( )
9413
.with_home ( root.join ( " home" ) ) ;
9414
write ( & root.join ( " two" ) , " b.txt" , b " b\n " ) ;
9415
two.commit ( " B" , & [ ] ) .unwrap ( ) ;
9416
assert! ( two.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9417
assert_eq! ( one.catch_up_bookmark ( " main" ) .unwrap ( ) , None , " not fetched" ) ;
9418
one.fetch ( " origin" ) .unwrap ( ) ;
9419
assert_eq! ( one.catch_up_bookmark ( " main" ) .unwrap ( ) , Some ( 1 ) ) ;
9420
assert_eq! ( one.bookmarks ( ) .unwrap ( ) [ 0 ] .behind, Some ( 0 ) ) ;
9421
9422
write ( & root.join ( " one" ) , " c.txt" , b " c\n " ) ;
9423
one.commit ( " C" , & [ ] ) .unwrap ( ) ;
9424
write ( & root.join ( " two" ) , " d.txt" , b " d\n " ) ;
9425
two.commit ( " D" , & [ ] ) .unwrap ( ) ;
9426
assert! ( two.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9427
one.fetch ( " origin" ) .unwrap ( ) ;
9428
assert_eq! ( one.catch_up_bookmark ( " main" ) .unwrap ( ) , None ) ;
9429
}
42ccf504 Andy McCall 29 days ago ↶
9430
9431
# [ test ]
9432
fn a_bookmark_is_compared_with_a_remote_that_has_it_when_there_is_no_origin ( ) {
9433
9434
hermetic ( ) ;
9435
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9436
let root = dir.path ( ) ;
9437
let _up = Repo:: init( & root.join ( " up" ) ) .unwrap ( ) ;
9438
let url = root.join ( " up" ) .to_string_lossy ( ) .to_string ( ) ;
9439
let other = Repo:: clone( & url, & root.join ( " other" ) )
9440
.unwrap ( )
9441
.with_home ( root.join ( " home" ) ) ;
9442
write ( & root.join ( " other" ) , " a.txt" , b " a\n " ) ;
9443
other.commit ( " a" , & [ ] ) .unwrap ( ) ;
9444
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9445
let mut one = Repo:: clone( & url, & root.join ( " one" ) )
9446
.unwrap ( )
9447
.with_home ( root.join ( " home" ) ) ;
9448
one.config.set ( " remote.earth.url" , & url) .unwrap ( ) ;
9449
one.config.set ( " remote.gcs.url" , & url) .unwrap ( ) ;
9450
one.config.set ( " remote.origin.url" , " " ) .unwrap ( ) ;
9451
one.save_config ( ) .unwrap ( ) ;
9452
write ( & root.join ( " other" ) , " b.txt" , b " b\n " ) ;
9453
other.commit ( " b" , & [ ] ) .unwrap ( ) ;
9454
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9455
one.fetch ( " earth" ) .unwrap ( ) ;
9456
let b = one
9457
.bookmarks ( )
9458
.unwrap ( )
9459
.into_iter ( )
9460
.find ( | b | b.name = = " main" )
9461
.unwrap ( ) ;
9462
assert_eq! (
9463
( b.remote.as_deref ( ) , b.behind) ,
9464
( Some ( " earth" ) , Some ( 1 ) ) ,
9465
" a stale origin copy is not used"
9466
) ;
9467
assert_eq! (
9468
one.default_remote ( ) ,
9469
" origin" ,
9470
" no default chosen among several"
9471
) ;
9472
let e = one.pull ( & one.default_remote ( ) ) .unwrap_err ( ) .to_string ( ) ;
9473
assert! ( e.contains ( " earth, gcs" ) & & e.contains ( " core.remote" ) , " {e}" ) ;
9474
one.config.set ( " core.remote" , " earth" ) .unwrap ( ) ;
9475
assert_eq! ( one.default_remote ( ) , " earth" ) ;
9476
}
cbe8499a Andy McCall 29 days ago ↶
9477
9478
# [ test ]
9479
fn a_missing_author_identity_says_how_to_fix_it ( ) {
9480
9481
9482
hermetic ( ) ;
9483
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9484
let mut repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9485
let missing = KeyId:: from_hex( & " ab" .repeat ( 32 ) ) .unwrap ( ) ;
9486
repo.config.set ( " author.key" , & missing.to_hex ( ) ) .unwrap ( ) ;
9487
let e = repo.identity ( & missing) .unwrap_err ( ) .to_string ( ) ;
9488
assert! (
9489
e.contains ( " this repository has no identity" ) & & e.contains ( " oxy config author.key" ) ,
9490
" {e}"
9491
) ;
9492
assert! ( e.contains ( " this repository's config" ) , " {e}" ) ;
9493
}
f6dccc45 Andy McCall 28 days ago ↶
9494
9495
# [ test ]
9496
fn a_working_copy_behind_its_bookmark_is_flagged_and_pull_brings_it_up ( ) {
9497
9498
9499
9500
hermetic ( ) ;
9501
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9502
let root = dir.path ( ) ;
9503
let _up = Repo:: init( & root.join ( " up" ) ) .unwrap ( ) ;
9504
let url = root.join ( " up" ) .to_string_lossy ( ) .to_string ( ) ;
9505
let other = Repo:: clone( & url, & root.join ( " other" ) )
9506
.unwrap ( )
9507
.with_home ( root.join ( " home" ) ) ;
9508
write ( & root.join ( " other" ) , " a.txt" , b " a\n " ) ;
9509
other.commit ( " a" , & [ ] ) .unwrap ( ) ;
9510
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9511
let one = Repo:: clone( & url, & root.join ( " one" ) )
9512
.unwrap ( )
9513
.with_home ( root.join ( " home" ) ) ;
9514
write ( & root.join ( " other" ) , " b.txt" , b " b\n " ) ;
9515
other.commit ( " b" , & [ ] ) .unwrap ( ) ;
9516
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9517
one.fetch ( " origin" ) .unwrap ( ) ;
9518
9519
let theirs = one.resolve_heads ( " origin/main" ) .unwrap ( ) ;
9520
one.bookmark_set ( " main" , & theirs) .unwrap ( ) ;
9521
assert! ( ! root.join ( " one/b.txt" ) .exists ( ) ) ;
9522
assert_eq! ( one.working_copy_behind ( ) .unwrap ( ) , Some ( 1 ) ) ;
9523
let r = one.pull ( " origin" ) .unwrap ( ) ;
9524
assert! (
9525
! r.changed & & r.checked_out,
9526
" nothing new, but the working copy caught up"
9527
) ;
9528
assert! ( root.join ( " one/b.txt" ) .exists ( ) ) ;
9529
assert_eq! ( one.working_copy_behind ( ) .unwrap ( ) , None ) ;
9530
}
170bb436 Andy McCall 28 days ago ↶
9531
9532
# [ test ]
9533
fn touched_nodes_cache_answers_as_the_changes_do ( ) {
9534
hermetic ( ) ;
9535
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9536
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9537
write ( dir.path ( ) , " a.txt" , b " one\n " ) ;
9538
write ( dir.path ( ) , " b.txt" , b " x\n " ) ;
9539
repo.snapshot ( " t" ) .unwrap ( ) ;
9540
let c1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
9541
write ( dir.path ( ) , " b.txt" , b " y\n " ) ;
9542
repo.snapshot ( " t" ) .unwrap ( ) ;
9543
repo.commit ( " b only" , & [ ] ) .unwrap ( ) ;
9544
fs:: rename( dir.path ( ) .join ( " a.txt" ) , dir.path ( ) .join ( " c.txt" ) ) .unwrap ( ) ;
9545
write ( dir.path ( ) , " c.txt" , b " one\n two\n " ) ;
9546
repo.snapshot ( " t" ) .unwrap ( ) ;
9547
let c3 = repo.commit ( " rename and edit" , & [ ] ) .unwrap ( ) ;
9548
9549
write ( dir.path ( ) , " c.txt" , b " one\n two\n three\n " ) ;
9550
repo.snapshot ( " t" ) .unwrap ( ) ;
9551
9552
let path = dir.path ( ) .join ( " .oxy/index/touched" ) ;
9553
let hashes = | es : Vec< LogEntry> | es.iter ( ) .map ( | e | e.hash ) .collect:: < Vec< _ > > ( ) ;
9554
let cold = hashes ( repo.log_for_path ( " c.txt" ) .unwrap ( ) ) ;
9555
assert! ( path.exists ( ) , " the first query writes the cache" ) ;
9556
let warm = hashes ( repo.log_for_path ( " c.txt" ) .unwrap ( ) ) ;
9557
assert_eq! ( cold, warm, " the cache answers as the changes do" ) ;
9558
let ( wc, _ ) = repo.working_copy ( ) .unwrap ( ) ;
9559
assert_eq! (
9560
cold,
9561
vec! [ wc, c3, c1] ,
9562
" renames followed, the working copy included"
9563
) ;
9564
9565
9566
let cached = super :: touched:: read_touched_for_test( & fs:: read( & path) .unwrap ( ) ) .unwrap ( ) ;
9567
assert! ( ! cached.contains ( & wc) , " a working-copy change is not cached" ) ;
9568
assert! ( cached.contains ( & c1) & & cached.contains ( & c3) ) ;
9569
9570
9571
let mut bytes = fs:: read( & path) .unwrap ( ) ;
9572
let last = bytes.len ( ) - 40 ;
9573
bytes[ last] ^ = 1 ;
9574
fs:: write( & path, & bytes) .unwrap ( ) ;
9575
assert_eq! ( hashes ( repo.log_for_path ( " c.txt" ) .unwrap ( ) ) , cold) ;
9576
let touches = repo.log_revset ( " touches(\" c.txt\" ) ~ @" ) .unwrap ( ) ;
9577
assert_eq! ( hashes ( touches) , vec! [ c3, c1] ) ;
9578
}
e8e6e0ca Andy McCall 27 days ago ↶
9579
9580
9581
9582
9583
9584
# [ test ]
9585
fn git_import_removes_every_copy_of_a_file_at_one_place ( ) {
9586
hermetic ( ) ;
9587
if std:: process:: Command:: new( " git" )
9588
.arg ( " --version" )
9589
.output ( )
9590
.is_err ( )
9591
{
9592
eprintln! ( " git not installed; skipping" ) ;
9593
return ;
9594
}
9595
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9596
let root = dir.path ( ) ;
9597
let g = root.join ( " src.git" ) ;
9598
fs:: create_dir_all( & g) .unwrap ( ) ;
9599
let git = | args : & [& str ]| {
9600
let out = std:: process:: Command:: new( " git" )
9601
.arg ( " -C" )
9602
.arg ( & g)
9603
.args ( [
9604
" -c" ,
9605
" user.name=Grace" ,
9606
" -c" ,
9607
" [email protected] " ,
9608
" -c" ,
9609
" commit.gpgsign=false" ,
9610
] )
9611
.args ( args)
9612
.output ( )
9613
.unwrap ( ) ;
9614
assert! (
9615
out.status.success ( ) ,
9616
" git {args:?}: {}" ,
9617
String :: from_utf8_lossy( & out.stderr)
9618
) ;
9619
} ;
9620
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
9621
write ( & g, " d/list.txt" , b " one\n two\n three\n four\n " ) ;
9622
write ( & g, " keep.txt" , b " k\n " ) ;
9623
git ( & [ " add" , " ." ] ) ;
9624
git ( & [ " commit" , " -qm" , " base" ] ) ;
9625
fs:: create_dir_all( g.join ( " d/sub" ) ) .unwrap ( ) ;
9626
git ( & [ " mv" , " d/list.txt" , " d/sub/list.txt" ] ) ;
9627
git ( & [ " commit" , " -qm" , " into sub" ] ) ;
9628
git ( & [ " checkout" , " -qb" , " side" ] ) ;
9629
git ( & [ " mv" , " d/sub/list.txt" , " d/list.txt" ] ) ;
9630
git ( & [ " commit" , " -qm" , " back, on side" ] ) ;
9631
git ( & [ " checkout" , " -q" , " main" ] ) ;
9632
git ( & [ " mv" , " d/sub/list.txt" , " d/list.txt" ] ) ;
9633
git ( & [ " commit" , " -qm" , " back, on main" ] ) ;
9634
git ( & [ " merge" , " -q" , " --no-edit" , " side" ] ) ;
9635
git ( & [ " rm" , " -rq" , " d" ] ) ;
9636
git ( & [ " commit" , " -qm" , " d goes" ] ) ;
9637
9638
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
9639
repo.import_git ( & g, " main" ) .unwrap ( ) ;
9640
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
9641
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
9642
let st = repo
9643
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
9644
.unwrap ( ) ;
9645
let view = st.tree ( ) .view ( ) ;
9646
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
9647
assert_eq! ( view.entries.keys ( ) .collect:: < Vec< _ > > ( ) , vec! [ " keep.txt" ] ) ;
9648
}
5552e69c Andy McCall 27 days ago ↶
9649
9650
9651
9652
9653
# [ test ]
9654
fn a_damaged_file_history_in_the_state_index_is_rebuilt ( ) {
9655
hermetic ( ) ;
9656
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9657
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9658
for i in 0 .. 6 {
9659
write (
9660
dir.path ( ) ,
9661
& format! ( " f{i} .txt" ) ,
9662
format! ( " one {i} \n " ) .as_bytes ( ) ,
9663
) ;
9664
}
9665
repo.snapshot ( " t" ) .unwrap ( ) ;
9666
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
9667
for i in 0 .. 6 {
9668
write (
9669
dir.path ( ) ,
9670
& format! ( " f{i} .txt" ) ,
9671
format! ( " one {i} \n two\n " ) .as_bytes ( ) ,
9672
) ;
9673
}
9674
repo.snapshot ( " t" ) .unwrap ( ) ;
9675
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
9676
let want: Vec< Vec< u8 > > = ( 0 .. 6 )
9677
.map ( | i | format! ( " one {i} \n two\n " ) .into_bytes ( ) )
9678
.collect ( ) ;
9679
9680
9681
let ( _ , wc) = repo.working_copy ( ) .unwrap ( ) ;
9682
let base = repo.load_state_history ( & wc.deps) .unwrap ( ) ;
9683
repo.write_state_index ( & wc.deps, & base) ;
9684
for ( i, w) in want.iter ( ) .enumerate ( ) {
9685
assert_eq! ( & repo.file_at ( & format! ( " f{i} .txt" ) , None ) .unwrap ( ) , w) ;
9686
}
9687
let index = repo.state_indexes ( ) ;
9688
assert! ( ! index.is_empty ( ) , " a state index was written" ) ;
9689
9690
let mut bytes = fs:: read( & index[ 0 ] ) .unwrap ( ) ;
9691
let last = bytes.len ( ) - 1 ;
9692
bytes[ last] ^ = 0x40 ;
9693
fs:: write( & index[ 0 ] , & bytes) .unwrap ( ) ;
9694
9695
let reads: Vec< _ > = ( 0 .. 6 )
9696
.map ( | i | repo.file_at ( & format! ( " f{i} .txt" ) , None ) )
9697
.collect ( ) ;
9698
let damaged: Vec< String> = reads
9699
.iter ( )
9700
.filter_map ( | r | r.as_ref ( ) .err ( ) .map ( | e | e.to_string ( ) ) )
9701
.collect ( ) ;
9702
assert_eq! ( damaged.len ( ) , 1 , " {damaged:?}" ) ;
9703
assert! (
9704
damaged[ 0 ] .contains ( " state index was damaged" )
9705
& & damaged[ 0 ] .contains ( " run the command again" ) ,
9706
" {}" ,
9707
damaged[ 0 ]
9708
) ;
9709
9710
for ( i, w) in want.iter ( ) .enumerate ( ) {
9711
assert_eq! ( & repo.file_at ( & format! ( " f{i} .txt" ) , None ) .unwrap ( ) , w) ;
9712
}
9713
}
cb8b8082 Andy McCall 27 days ago ↶
9714
9715
9716
9717
9718
9719
# [ test ]
9720
fn a_long_push_goes_in_parts ( ) {
9721
hermetic ( ) ;
9722
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9723
let root = dir.path ( ) ;
9724
let origin = root.join ( " origin" ) ;
9725
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
9726
let main = root.join ( " main" ) ;
9727
let mut repo = Repo:: init( & main) .unwrap ( ) ;
9728
repo.config
9729
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
9730
.unwrap ( ) ;
9731
9732
repo.config.set ( " push.batch-bytes" , " 1" ) .unwrap ( ) ;
9733
repo.save_config ( ) .unwrap ( ) ;
9734
for i in 0 .. 6 {
9735
write (
9736
& main,
9737
& format! ( " f{i} .txt" ) ,
9738
format! ( " file {i} \n " ) .as_bytes ( ) ,
9739
) ;
9740
repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ;
9741
}
9742
let mut parts = Vec :: new( ) ;
9743
let results = repo
9744
.push_bookmark_with ( " origin" , " main" , false , & mut | m| parts.push ( m.to_string ( ) ) )
9745
.unwrap ( ) ;
9746
assert! (
9747
results.iter ( ) .any ( | r | r.ok & & r.name = = " main" ) ,
9748
" {results:?}"
9749
) ;
9750
assert_eq! ( parts.len ( ) , 6 , " {parts:?}" ) ;
9751
assert_eq! ( parts[ 0 ] , " part 1 of 6: 1 changes" ) ;
9752
9753
let remote = Repo:: open( & origin) .unwrap ( ) ;
9754
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
9755
assert_eq! ( remote.bookmark_heads ( ) .unwrap ( ) , heads) ;
9756
assert_eq! (
9757
remote.log_state ( & heads) .unwrap ( ) .len ( ) ,
9758
repo.log_state ( & heads) .unwrap ( ) .len ( )
9759
) ;
9760
9761
let got = remote
9762
.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ ] )
9763
.unwrap ( ) ;
9764
assert_eq! ( got.len ( ) , 1 ) ;
9765
assert! ( got[ 0 ] .ok & & got[ 0 ] .name = = OBJECTS_RESULT , " {got:?}" ) ;
9766
}
d6e0d5b8 Andy McCall 27 days ago ↶
9767
d3b27090 Andy McCall 13 days ago ↶
9768
9769
9770
9771
9772
# [ test ]
9773
fn a_heavy_change_sends_its_blobs_in_parts ( ) {
9774
hermetic ( ) ;
9775
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9776
let root = dir.path ( ) ;
9777
let origin = root.join ( " origin" ) ;
9778
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
9779
let main = root.join ( " main" ) ;
9780
let mut repo = Repo:: init( & main) .unwrap ( ) ;
9781
repo.config
9782
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
9783
.unwrap ( ) ;
9784
repo.config.set ( " push.batch-bytes" , " 50000" ) .unwrap ( ) ;
9785
repo.save_config ( ) .unwrap ( ) ;
9786
let mut x: u64 = 0x9e3779b97f4a7c15 ;
9787
let mut noise = | n : usize | -> Vec< u8 > {
9788
( 0 .. n)
9789
.map ( | _| {
9790
x ^ = x < < 13 ;
9791
x ^ = x > > 7 ;
9792
x ^ = x < < 17 ;
9793
( x > > 24 ) as u8
9794
} )
9795
.collect ( )
9796
} ;
9797
let files: Vec< Vec< u8 > > = ( 0 .. 4 ) .map ( | _| noise ( 40_000 ) ) .collect ( ) ;
9798
for ( i, f) in files.iter ( ) .enumerate ( ) {
9799
write ( & main, & format! ( " bin{i} .dat" ) , f) ;
9800
}
9801
repo.commit ( " four blobs" , & [ ] ) .unwrap ( ) ;
9802
let mut parts = Vec :: new( ) ;
9803
let results = repo
9804
.push_bookmark_with ( " origin" , " main" , false , & mut | m| parts.push ( m.to_string ( ) ) )
9805
.unwrap ( ) ;
9806
assert! (
9807
results.iter ( ) .any ( | r | r.ok & & r.name = = " main" ) ,
9808
" {results:?}"
9809
) ;
9810
assert! (
9811
parts.len ( ) > = 3 & & parts[ 0 ] .starts_with ( " objects 1 of" ) ,
9812
" {parts:?}"
9813
) ;
9814
let back = Repo:: clone( & origin.to_string_lossy ( ) , & root.join ( " back" ) ) .unwrap ( ) ;
9815
for ( i, f) in files.iter ( ) .enumerate ( ) {
9816
assert_eq! ( & fs:: read( root.join ( format! ( " back/bin{i} .dat" ) ) ) .unwrap ( ) , f) ;
9817
}
9818
drop ( back) ;
9819
}
9820
570e3531 Andy McCall 12 days ago ↶
9821
9822
9823
9824
9825
# [ test ]
9826
fn a_replacement_does_not_send_the_old_content_again ( ) {
9827
hermetic ( ) ;
9828
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9829
let root = dir.path ( ) ;
9830
let origin = root.join ( " origin" ) ;
9831
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
9832
let main = root.join ( " main" ) ;
9833
let mut repo = Repo:: init( & main) .unwrap ( ) ;
9834
repo.config
9835
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
9836
.unwrap ( ) ;
9837
repo.save_config ( ) .unwrap ( ) ;
9838
let blobs = | b : & Bundle| {
9839
b.objects
9840
.iter ( )
9841
.filter ( | ( ty , _) | * ty = = ObjectType:: Blob)
9842
.count ( )
9843
} ;
9844
write ( & main, " bin.dat" , b " \0 \x01 first binary" ) ;
9845
write ( & main, " gone.dat" , b " \0 \x02 to be removed" ) ;
9846
let added = repo.commit ( " add" , & [ ] ) .unwrap ( ) ;
9847
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9848
write ( & main, " bin.dat" , b " \0 \x01 second binary" ) ;
9849
fs:: remove_file( main.join ( " gone.dat" ) ) .unwrap ( ) ;
9850
let changed = repo.commit ( " replace and remove" , & [ ] ) .unwrap ( ) ;
9851
assert_eq! ( blobs ( & repo.bundle_for ( & [ changed] ) .unwrap ( ) ) , 1 ) ;
9852
assert_eq! ( blobs ( & repo.bundle_for ( & [ added, changed] ) .unwrap ( ) ) , 3 ) ;
9853
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9854
let origin = Repo:: open( & origin) .unwrap ( ) ;
9855
assert_eq! ( origin.fsck ( ) .unwrap ( ) .problems, vec! [ ] ) ;
9856
let back = Repo:: clone( & origin.root ( ) .to_string_lossy ( ) , & root.join ( " back" ) ) .unwrap ( ) ;
9857
assert_eq! ( back.fsck ( ) .unwrap ( ) .problems, vec! [ ] ) ;
9858
assert_eq! (
9859
fs:: read( root.join ( " back/bin.dat" ) ) .unwrap ( ) ,
9860
b " \0 \x01 second binary"
9861
) ;
9862
assert! ( ! root.join ( " back/gone.dat" ) .exists ( ) ) ;
9863
}
9864
d6e0d5b8 Andy McCall 27 days ago ↶
9865
9866
9867
9868
# [ test ]
9869
fn git_import_claims_your_own_commits ( ) {
9870
hermetic ( ) ;
9871
if std:: process:: Command:: new( " git" )
9872
.arg ( " --version" )
9873
.output ( )
9874
.is_err ( )
9875
{
9876
eprintln! ( " git not installed; skipping" ) ;
9877
return ;
9878
}
9879
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9880
let root = dir.path ( ) ;
9881
let g = root.join ( " src.git" ) ;
9882
fs:: create_dir_all( & g) .unwrap ( ) ;
9883
let git = | who : & str , args : & [& str ]| {
9884
let ( name, email) = who.split_once ( ' ' ) .unwrap ( ) ;
9885
let out = std:: process:: Command:: new( " git" )
9886
.arg ( " -C" )
9887
.arg ( & g)
9888
.args ( [
9889
" -c" ,
9890
& format! ( " user.name={name} " ) ,
9891
" -c" ,
9892
& format! ( " user.email={email} " ) ,
9893
" -c" ,
9894
" commit.gpgsign=false" ,
9895
] )
9896
.args ( args)
9897
.output ( )
9898
.unwrap ( ) ;
9899
assert! (
9900
out.status.success ( ) ,
9901
" git {args:?}: {}" ,
9902
String :: from_utf8_lossy( & out.stderr)
9903
) ;
9904
} ;
9905
git ( " Grace [email protected] " , & [ " init" , " -q" , " -b" , " main" ] ) ;
9906
write ( & g, " a.txt" , b " a\n " ) ;
9907
git ( " Grace [email protected] " , & [ " add" , " ." ] ) ;
9908
git ( " Grace [email protected] " , & [ " commit" , " -qm" , " mine" ] ) ;
9909
write ( & g, " b.txt" , b " b\n " ) ;
9910
git ( " Ada [email protected] " , & [ " add" , " ." ] ) ;
9911
git ( " Ada [email protected] " , & [ " commit" , " -qm" , " someone else's" ] ) ;
9912
9913
let mut repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
9914
let ( key, _ ) = repo
9915
.create_identity ( " Grace" , " [email protected] " , IdentityScope:: Repo)
9916
.unwrap ( ) ;
9917
repo.config.set ( " git.claim" , " true" ) .unwrap ( ) ;
9918
repo.save_config ( ) .unwrap ( ) ;
9919
let repo = Repo:: open( & root.join ( " o" ) ) .unwrap ( ) ;
9920
repo.import_git ( & g, " main" ) .unwrap ( ) ;
9921
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
9922
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
9923
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
9924
let by_message: std:: collections:: HashMap< String, KeyId> = repo
9925
.log_state ( & heads)
9926
.unwrap ( )
9927
.into_iter ( )
9928
.map ( | e | {
9929
(
9930
e.change.message.lines ( ) .next ( ) .unwrap_or ( " " ) .to_string ( ) ,
9931
e.change.author,
9932
)
9933
} )
9934
.collect ( ) ;
9935
assert_eq! ( by_message[ " mine" ] , key, " her own commit, any case of email" ) ;
9936
assert_eq! ( by_message[ " someone else's" ] , ObjectRef:: ZERO , " not hers" ) ;
9937
}
34fad4a9 Andy McCall 27 days ago ↶
9938
9939
9940
9941
# [ test ]
9942
fn removing_a_remote_leaves_nothing_of_it ( ) {
9943
hermetic ( ) ;
9944
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9945
let origin = dir.path ( ) .join ( " origin" ) ;
9946
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
9947
let main = dir.path ( ) .join ( " main" ) ;
9948
let mut repo = Repo:: init( & main) .unwrap ( ) ;
9949
repo.config
9950
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
9951
.unwrap ( ) ;
9952
repo.config
9953
.set ( " remote.origin.token" , " secret-ish" )
9954
.unwrap ( ) ;
9955
repo.config
9956
.set ( " remote.keep.url" , & origin.to_string_lossy ( ) )
9957
.unwrap ( ) ;
9958
repo.save_config ( ) .unwrap ( ) ;
9959
write ( & main, " a.txt" , b " a\n " ) ;
9960
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
9961
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9962
assert! (
9963
repo.view ( ) .unwrap ( ) .remotes.contains_key ( " origin" ) ,
9964
" a tracking bookmark"
9965
) ;
9966
9967
repo.remove_remote ( " origin" ) .unwrap ( ) ;
9968
let again = Repo:: open( & main) .unwrap ( ) ;
9969
assert! (
9970
! again
9971
.config
9972
.repo_layer ( )
9973
.keys ( )
9974
.any ( | k | k.starts_with ( " remote.origin." ) ) ,
9975
" {:?}" ,
9976
again.config.repo_layer ( )
9977
) ;
9978
assert! ( ! again.config.remotes.contains_key ( " origin" ) ) ;
9979
assert! (
9980
! again.view ( ) .unwrap ( ) .remotes.contains_key ( " origin" ) ,
9981
" its tracking bookmarks go"
9982
) ;
9983
assert! (
9984
again.config.remotes.contains_key ( " keep" ) ,
9985
" another remote stays"
9986
) ;
9987
let mut again = again;
9988
assert! (
9989
again.remove_remote ( " origin" ) .is_err ( ) ,
9990
" a second removal is refused"
9991
) ;
9992
}
9d580f21 Andy McCall 27 days ago ↶
9993
9994
9995
9996
9997
# [ test ]
9998
fn gc_keeps_the_blobs_a_host_names ( ) {
9999
hermetic ( ) ;
10000
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10001
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
10002
write ( dir.path ( ) , " a.txt" , b " a\n " ) ;
10003
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10004
10005
let body: Vec< u8 > = ( 0 .. 400_000 u32 )
10006
.flat_map ( | i | ( i.wrapping_mul ( 2654435761 ) ) .to_le_bytes ( ) )
10007
.collect ( ) ;
10008
let kept = repo.store_blob ( & body) .unwrap ( ) ;
10009
let lost = repo.store_blob ( b " an artifact nobody names" ) .unwrap ( ) ;
10010
repo.gc_keeping ( true , & [ kept] , & mut | _ | { } ) .unwrap ( ) ;
10011
assert_eq! ( repo.read_blob ( & kept) .unwrap ( ) , body, " named: kept whole" ) ;
10012
assert! ( ! repo.store ( ) .has ( & lost) , " not named: pruned" ) ;
10013
repo.gc ( true ) .unwrap ( ) ;
10014
assert! ( ! repo.store ( ) .has ( & kept) , " a plain gc prunes it" ) ;
10015
}
530a0fc6 Andy McCall 26 days ago ↶
10016
10017
10018
10019
10020
# [ test ]
10021
fn undo_is_scoped_to_its_workspace ( ) {
10022
hermetic ( ) ;
10023
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10024
let root = dir.path ( ) .join ( " main" ) ;
10025
let a = Repo:: init( & root) .unwrap ( ) ;
10026
write ( & root, " base.txt" , b " base\n " ) ;
10027
let base = a.commit ( " base" , & [ ] ) .unwrap ( ) ;
10028
a.bookmark_create ( " feature" , None ) .unwrap ( ) ;
10029
let ws_dir = dir.path ( ) .join ( " ws" ) ;
10030
let heads = a.resolve_heads ( " feature" ) .unwrap ( ) ;
10031
let b = a
10032
.workspace_add_on ( & ws_dir, Some ( & heads) , Some ( " feature" ) )
10033
.unwrap ( ) ;
10034
10035
10036
write ( & root, " a.txt" , b " a\n " ) ;
10037
let ca = a.commit ( " from a" , & [ ] ) .unwrap ( ) ;
10038
write ( & ws_dir, " b.txt" , b " b\n " ) ;
10039
let cb = b.commit ( " from b" , & [ ] ) .unwrap ( ) ;
10040
10041
10042
let u = a.undo ( ) .unwrap ( ) ;
10043
assert_eq! ( u.command, " oxy commit -m \" from a\" " ) ;
10044
assert_eq! ( u.others_kept, 1 , " B's newer commit is left" ) ;
10045
assert! ( ! u.changes.is_empty ( ) ) ;
10046
let view = a.view ( ) .unwrap ( ) ;
10047
10048
assert_eq! ( a.heads_of ( & view, " main" ) .unwrap ( ) , vec! [ base] ) ;
10049
assert! (
10050
a.heads_of ( & view, " feature" ) .unwrap ( ) .contains ( & cb) ,
10051
" B's commit stands"
10052
) ;
10053
assert! (
10054
a.store ( ) .get_change ( & ca) .is_ok ( ) ,
10055
" the change itself is still in the store"
10056
) ;
10057
10058
assert! ( ! root.join ( " a.txt" ) .exists ( ) ) ;
10059
assert! ( a.snapshot ( " oxy status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
10060
assert! ( ws_dir.join ( " b.txt" ) .exists ( ) ) ;
10061
10062
10063
let u = b.undo ( ) .unwrap ( ) ;
10064
assert_eq! ( u.command, " oxy commit -m \" from b\" " ) ;
10065
assert! ( ! b
10066
.heads_of ( & b.view ( ) .unwrap ( ) , " feature" )
10067
.unwrap ( )
10068
.contains ( & cb) ) ;
10069
assert_eq! (
10070
a.heads_of ( & a.view ( ) .unwrap ( ) , " main" ) .unwrap ( ) ,
10071
vec! [ base] ,
10072
" A's undo stands"
10073
) ;
10074
10075
10076
10077
let e = a.undo ( ) .unwrap_err ( ) .to_string ( ) ;
10078
assert! ( e.contains ( " the workspace at" ) & & e.contains ( " ws" ) , " {e}" ) ;
10079
}
10080
10081
10082
10083
# [ test ]
10084
fn undo_on_a_shared_bookmark_keeps_the_other_workspaces_head ( ) {
10085
hermetic ( ) ;
10086
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10087
let root = dir.path ( ) .join ( " main" ) ;
10088
let a = Repo:: init( & root) .unwrap ( ) ;
10089
write ( & root, " base.txt" , b " base\n " ) ;
10090
a.commit ( " base" , & [ ] ) .unwrap ( ) ;
10091
let ws_dir = dir.path ( ) .join ( " ws" ) ;
10092
let b = a.workspace_add ( & ws_dir, None ) .unwrap ( ) ;
10093
write ( & root, " a.txt" , b " a\n " ) ;
10094
let ca = a.commit ( " from a" , & [ ] ) .unwrap ( ) ;
10095
write ( & ws_dir, " b.txt" , b " b\n " ) ;
10096
let cb = b.commit ( " from b" , & [ ] ) .unwrap ( ) ;
10097
let u = a.undo ( ) .unwrap ( ) ;
10098
assert_eq! ( u.command, " oxy commit -m \" from a\" " ) ;
10099
let heads = a.heads_of ( & a.view ( ) .unwrap ( ) , " main" ) .unwrap ( ) ;
10100
assert! ( heads.contains ( & cb) & & ! heads.contains ( & ca) , " {heads:?}" ) ;
10101
}
10102
10103
10104
10105
# [ test ]
10106
fn undo_refuses_to_take_back_another_workspaces_work ( ) {
10107
hermetic ( ) ;
10108
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10109
let root = dir.path ( ) .join ( " main" ) ;
10110
let a = Repo:: init( & root) .unwrap ( ) ;
10111
write ( & root, " base.txt" , b " base\n " ) ;
10112
a.commit ( " base" , & [ ] ) .unwrap ( ) ;
10113
let ws_dir = dir.path ( ) .join ( " ws" ) ;
10114
let b = a.workspace_add ( & ws_dir, None ) .unwrap ( ) ;
10115
10116
write ( & root, " a.txt" , b " a\n " ) ;
10117
a.commit ( " from a" , & [ ] ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
10118
b.refresh ( ) .unwrap ( ) ;
530a0fc6 Andy McCall 26 days ago ↶
10119
let heads = b.resolve_heads ( " main" ) .unwrap ( ) ;
10120
b.checkout ( & heads, false , " oxy checkout" ) .unwrap ( ) ;
10121
write ( & ws_dir, " a.txt" , b " a, then b\n " ) ;
10122
b.commit ( " from b" , & [ ] ) .unwrap ( ) ;
10123
let e = a.undo ( ) .unwrap_err ( ) .to_string ( ) ;
10124
assert! (
10125
e.contains ( " bookmark main" ) & & e.contains ( " builds on" ) ,
10126
" {e}"
10127
) ;
10128
assert! ( e.contains ( " oxy op restore" ) , " {e}" ) ;
10129
}
10130
10131
10132
10133
# [ test ]
10134
fn undo_says_when_the_commit_was_pushed ( ) {
10135
hermetic ( ) ;
10136
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10137
let root = dir.path ( ) ;
10138
Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
10139
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
10140
let repo = Repo:: clone( & url, & root.join ( " work" ) )
10141
.unwrap ( )
10142
.with_home ( root.join ( " home" ) ) ;
10143
write ( & root.join ( " work" ) , " a.txt" , b " a\n " ) ;
10144
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10145
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10146
10147
let u = repo.undo ( ) .unwrap ( ) ;
10148
assert_eq! ( u.command, " oxy commit -m \" a\" " ) ;
10149
assert_eq! ( u.pushed, vec! [ ( " main" .to_string ( ) , " origin" .to_string ( ) ) ] ) ;
10150
}
b689779e Andy McCall 26 days ago ↶
10151
10152
10153
10154
10155
# [ test ]
10156
fn large_new_files_are_left_out_of_snapshots ( ) {
10157
hermetic ( ) ;
10158
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10159
let root = dir.path ( ) ;
10160
let mut repo = Repo:: init( root) .unwrap ( ) ;
10161
repo.config.max_new_file = 1024 ;
10162
write ( root, " small.txt" , b " small\n " ) ;
10163
write ( root, " big.bin" , & vec! [ 0 u8 ; 4096 ] ) ;
10164
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10165
let paths: Vec< _ > = s.statuses.iter ( ) .map ( | p | p.path.as_str ( ) ) .collect ( ) ;
10166
assert_eq! ( paths, [ " small.txt" ] ) ;
10167
assert_eq! ( repo.skipped_large ( ) , vec! [ ( " big.bin" .to_string ( ) , 4096 ) ] ) ;
10168
repo.commit ( " small" , & [ ] ) .unwrap ( ) ;
10169
10170
10171
write ( root, " small.txt" , & vec! [ b ' x' ; 4096 ] ) ;
10172
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10173
assert! ( s.statuses.iter ( ) .any ( | p | p.path = = " small.txt" ) ) ;
10174
10175
repo.config.max_new_file = 0 ;
10176
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10177
assert! ( s.statuses.iter ( ) .any ( | p | p.path = = " big.bin" ) ) ;
10178
repo.config.max_new_file = 1024 ;
10179
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10180
assert! (
10181
s.statuses.iter ( ) .any ( | p | p.path = = " big.bin" ) ,
10182
" already in the working copy"
10183
) ;
10184
assert! ( repo.skipped_large ( ) .is_empty ( ) ) ;
10185
}
10186
10187
10188
10189
# [ test ]
10190
fn gc_prunes_what_only_expired_snapshots_held ( ) {
10191
hermetic ( ) ;
10192
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10193
let root = dir.path ( ) ;
10194
let mut repo = Repo:: init( root) .unwrap ( ) ;
10195
repo.config.gc_grace_days = 0 ;
10196
write ( root, " a.txt" , b " a\n " ) ;
10197
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10198
let image: Vec< u8 > = ( 0 .. 200_000 u32 ) .map ( | i | ( i % 7 ) as u8 ) .collect ( ) ;
10199
write ( root, " image.tar" , & image) ;
10200
repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10201
std:: fs:: remove_file( root.join ( " image.tar" ) ) .unwrap ( ) ;
10202
repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10203
let blob = Repo:: blob_ref( & image) ;
10204
assert! ( repo.store ( ) .has ( & blob) ) ;
10205
repo.gc ( true ) .unwrap ( ) ;
10206
assert! ( repo.store ( ) .has ( & blob) , " a recent snapshot keeps it" ) ;
10207
repo.config.snapshot_retention_days = 0 ;
10208
repo.gc ( true ) .unwrap ( ) ;
10209
assert! ( ! repo.store ( ) .has ( & blob) , " an expired snapshot does not" ) ;
10210
assert_eq! (
10211
repo.file_at ( " a.txt" , None ) .unwrap ( ) ,
10212
b " a\n " ,
10213
" committed files stay"
10214
) ;
fe9586d9 Andy McCall 26 days ago ↶
10215
}
10216
10217
10218
10219
10220
# [ test ]
10221
fn carrying_work_the_target_already_has_is_no_conflict ( ) {
10222
hermetic ( ) ;
10223
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10224
let root = dir.path ( ) ;
10225
let mut repo = Repo:: init( root) .unwrap ( ) ;
10226
write ( root, " a.txt" , b " one\n " ) ;
10227
write ( root, " b.txt" , b " b\n " ) ;
10228
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
10229
repo.bookmark_create ( " landed" , None ) .unwrap ( ) ;
10230
10231
repo.bookmark_switch ( " landed" , false ) .unwrap ( ) ;
10232
write ( root, " a.txt" , b " two\n " ) ;
10233
repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
10234
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
10235
assert_eq! ( std:: fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n " ) ;
10236
10237
write ( root, " a.txt" , b " two\n " ) ;
10238
write ( root, " b.txt" , b " b, edited\n " ) ;
10239
let ( _ , carried) = repo.bookmark_switch_carry ( " landed" ) .unwrap ( ) ;
10240
assert_eq! (
10241
carried,
10242
vec! [ " b.txt" .to_string ( ) ] ,
10243
" only the work landed lacks is carried"
10244
) ;
10245
assert_eq! ( std:: fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " two\n " ) ;
10246
assert_eq! ( std:: fs:: read( root.join ( " b.txt" ) ) .unwrap ( ) , b " b, edited\n " ) ;
10247
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10248
let paths: Vec< _ > = s.statuses.iter ( ) .map ( | p | p.path.as_str ( ) ) .collect ( ) ;
10249
assert_eq! ( paths, [ " b.txt" ] ) ;
10250
10251
10252
repo.bookmark_switch ( " main" , true ) .unwrap ( ) ;
10253
write ( root, " a.txt" , b " three\n " ) ;
10254
let e = repo
10255
.bookmark_switch_carry ( " landed" )
10256
.unwrap_err ( )
10257
.to_string ( ) ;
10258
assert! ( e.contains ( " a.txt" ) & & e.contains ( " --discard" ) , " {e}" ) ;
b689779e Andy McCall 26 days ago ↶
10259
}
aa4939e0 Andy McCall 26 days ago ↶
10260
10261
10262
10263
# [ test ]
10264
fn a_full_hash_resolves_without_its_hash_sign ( ) {
10265
hermetic ( ) ;
10266
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10267
let root = dir.path ( ) ;
10268
let repo = Repo:: init( root) .unwrap ( ) ;
10269
write ( root, " a.txt" , b " a\n " ) ;
10270
let h = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10271
assert_eq! ( repo.resolve_change ( & h.to_hex ( ) ) .unwrap ( ) , h) ;
10272
assert_eq! ( repo.resolve_change ( & format! ( " #{} " , h.to_hex ( ) ) ) .unwrap ( ) , h) ;
10273
assert! ( repo
10274
.resolve_change ( & " 0" .repeat ( 64 ) )
10275
.unwrap_err ( )
10276
.to_string ( )
10277
.contains ( " no change matches" ) ) ;
10278
}
f2f91ceb Andy McCall 25 days ago ↶
10279
10280
# [ test ]
10281
fn a_pin_change_moves_only_the_pin ( ) {
10282
hermetic ( ) ;
10283
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10284
let root = dir.path ( ) ;
10285
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
10286
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
10287
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
10288
.unwrap ( )
10289
.with_home ( root.join ( " home" ) ) ;
10290
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
10291
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
10292
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10293
let _app_origin = Repo:: init( & root.join ( " app-origin" ) ) .unwrap ( ) ;
10294
let app_dir = root.join ( " app" ) ;
10295
let app = Repo:: clone( & root.join ( " app-origin" ) .to_string_lossy ( ) , & app_dir)
10296
.unwrap ( )
10297
.with_home ( root.join ( " home" ) ) ;
10298
write ( & app_dir, " main.txt" , b " app\n " ) ;
10299
let pin1 = app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
10300
let a1 = app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
10301
10302
write ( & root.join ( " lib" ) , " more.txt" , b " v2\n " ) ;
10303
let l2 = lib.commit ( " lib v2" , & [ ] ) .unwrap ( ) ;
10304
let crate :: subrepo:: Pin:: Oxy { repo, bookmark, .. } = pin1 else {
10305
panic! ( " an Oxygen pin" )
10306
} ;
10307
let pin2 = crate :: subrepo:: Pin:: Oxy {
10308
repo,
10309
heads: vec! [ l2] ,
10310
bookmark,
10311
} ;
10312
let author = app.store ( ) .get_change ( & a1) .unwrap ( ) .author;
10313
let change = app
10314
.build_pin_change ( & [ a1] , " libs/lib" , & pin2, " Move lib to v2" , author)
10315
.unwrap ( ) ;
10316
let mut state = app.load_state ( & [ a1] ) .unwrap ( ) ;
10317
state.add ( change) .unwrap ( ) ;
10318
let files = state.materialize ( ) .files;
10319
assert_eq! ( files[ " libs/lib" ] .kind, NodeKind:: Subrepo) ;
10320
let FileContent:: Opaque { blobs } = & files[ " libs/lib" ] .content else {
10321
panic! ( " a pin is opaque" )
10322
} ;
10323
let read = crate :: subrepo:: Pin:: decode( & app.read_blob ( & blobs[ 0 ] ) .unwrap ( ) ) .unwrap ( ) ;
10324
assert_eq! ( read, pin2, " the new pin" ) ;
10325
assert! (
10326
matches! ( & files[ " main.txt" ] .content, FileContent:: Text { bytes, .. } if bytes.as_slice ( ) = = b " app\n " ) ,
10327
" nothing else changes"
10328
) ;
10329
10330
let e = app.build_pin_change ( & [ a1] , " main.txt" , & pin2, " x" , author) ;
10331
assert! ( format! ( " {:?} " , e.unwrap_err ( ) ) .contains ( " not a subrepo" ) ) ;
10332
let e = app.build_pin_change ( & [ a1] , " libs/none" , & pin2, " x" , author) ;
10333
assert! ( format! ( " {:?} " , e.unwrap_err ( ) ) .contains ( " does not exist" ) ) ;
10334
}
767ac6cd Andy McCall 19 days ago
10335
10336
10337
10338
10339
10340
10341
# [ test ]
10342
fn one_handle_keeps_one_head_until_it_refreshes ( ) {
10343
hermetic ( ) ;
10344
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10345
let root = dir.path ( ) ;
10346
let writer = Repo:: init( root) .unwrap ( ) ;
10347
write ( root, " a.txt" , b " one\n " ) ;
10348
let h1 = writer.commit ( " one" , & [ ] ) .unwrap ( ) ;
10349
let reader = Repo:: open( root) .unwrap ( ) ;
10350
let before = reader.view ( ) .unwrap ( ) ;
10351
assert_eq! ( reader.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
10352
write ( root, " a.txt" , b " two\n " ) ;
10353
let h2 = writer.commit ( " two" , & [ ] ) .unwrap ( ) ;
10354
assert_eq! (
10355
writer.bookmark_heads ( ) .unwrap ( ) ,
10356
vec! [ h2] ,
10357
" its own operation"
10358
) ;
10359
assert_eq! ( reader.view ( ) .unwrap ( ) , before, " the head it read first" ) ;
10360
assert_eq! ( reader.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
10361
reader.refresh ( ) .unwrap ( ) ;
10362
assert_eq! ( reader.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
10363
10364
10365
let stale = Repo:: open( root) .unwrap ( ) ;
10366
assert_eq! ( stale.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
10367
write ( root, " a.txt" , b " three\n " ) ;
10368
let h3 = writer.commit ( " three" , & [ ] ) .unwrap ( ) ;
10369
stale.bookmark_create ( " feature" , Some ( & [ h1] ) ) .unwrap ( ) ;
10370
let view = stale.view ( ) .unwrap ( ) ;
10371
assert! ( view.bookmarks.contains_key ( " feature" ) ) ;
10372
assert_eq! (
10373
stale.bookmark_heads ( ) .unwrap ( ) ,
10374
vec! [ h3] ,
10375
" merged, not lost"
10376
) ;
10377
let fresh = Repo:: open( root) .unwrap ( ) ;
10378
assert_eq! ( fresh.view ( ) .unwrap ( ) , view) ;
10379
}
10380
10381
10382
# [ test ]
10383
fn over_a_store_one_handle_keeps_one_head ( ) {
10384
hermetic ( ) ;
10385
let backend = std:: sync:: Arc:: new( crate :: strata:: MemoryStore:: new( ) ) ;
10386
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = Some ( backend.clone ( ) ) ) ;
10387
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10388
let a = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
10389
let ( h0, _ ) = a.head_op ( ) .unwrap ( ) ;
10390
let b = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
10391
assert_eq! ( b.head_op ( ) .unwrap ( ) .0 , h0) ;
10392
let state = a.store.put_state ( & StateObject:: new( vec! [ ] ) ) .unwrap ( ) ;
10393
let mut view = a.view ( ) .unwrap ( ) ;
10394
view.bookmarks.insert ( " x" .into ( ) , state) ;
10395
let r = a.record_op ( & [ h0] , " bookmark x" , view) .unwrap ( ) ;
10396
assert_eq! ( a.head_op ( ) .unwrap ( ) .0 , r) ;
10397
assert_eq! ( b.head_op ( ) .unwrap ( ) .0 , h0, " b keeps its head" ) ;
10398
b.refresh ( ) .unwrap ( ) ;
10399
assert_eq! ( b.head_op ( ) .unwrap ( ) .0 , r) ;
10400
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = None ) ;
10401
}
10402
10403
10404
10405
10406
# [ test ]
10407
fn gc_retires_and_a_later_gc_deletes_after_the_hour ( ) {
10408
hermetic ( ) ;
10409
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10410
let root = dir.path ( ) ;
10411
let repo = Repo:: init( root) .unwrap ( ) ;
10412
let mut heads = Vec :: new( ) ;
10413
for i in 0 .. 4 {
10414
write ( root, & format! ( " f{i} .txt" ) , format! ( " {i} \n " ) .as_bytes ( ) ) ;
10415
heads.push ( repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ) ;
10416
}
10417
let reader = Repo:: open( root) .unwrap ( ) ;
10418
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 5 ) ;
10419
let r = repo.gc ( true ) .unwrap ( ) ;
10420
assert_eq! ( r.retired, crate :: store:: RetiredReport:: default( ) ) ;
10421
assert_eq! ( repo.store ( ) .loose_count ( ) .unwrap ( ) , 0 ) ;
10422
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 5 , " read on from what it had" ) ;
10423
10424
let r = repo.gc ( true ) .unwrap ( ) ;
10425
assert_eq! ( ( r.retired.packs, r.retired.loose) , ( 0 , 0 ) ) ;
10426
assert! ( r.retired.kept > 0 , " {r:?}" ) ;
10427
write ( root, " f9.txt" , b " 9\n " ) ;
10428
heads.push ( repo.commit ( " c9" , & [ ] ) .unwrap ( ) ) ;
10429
crate :: store:: reader_tests:: age_retired(
10430
repo.store ( ) ,
10431
crate :: store:: RETIRED_FOR + std:: time:: Duration:: from_secs( 60 ) ,
10432
) ;
10433
let r = repo.gc ( true ) .unwrap ( ) ;
10434
assert! ( r.retired.packs > = 1 & & r.retired.loose > 0 , " {r:?}" ) ;
10435
assert_eq! ( r.retired.kept, 0 , " {r:?}" ) ;
10436
let r = repo.gc ( true ) .unwrap ( ) ;
10437
assert_eq! ( ( r.retired.packs, r.retired.loose) , ( 0 , 0 ) ) ;
10438
assert! ( r.retired.kept > 0 , " what the last gc retired is young" ) ;
10439
10440
10441
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 5 ) ;
10442
for h in & heads[ .. 4 ] {
10443
reader.store ( ) .get_change ( h) .unwrap ( ) ;
10444
}
10445
reader.refresh ( ) .unwrap ( ) ;
10446
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 6 ) ;
10447
let fresh = Repo:: open( root) .unwrap ( ) ;
10448
assert_eq! (
10449
fresh.store ( ) .pack_count ( ) ,
10450
1 ,
10451
" retired packs are not loaded"
10452
) ;
10453
}
10454
10455
10456
10457
# [ test ]
10458
fn a_repository_opens_when_a_listed_index_is_gone ( ) {
10459
hermetic ( ) ;
10460
let backend = crate :: store:: reader_tests:: racing( true ) ;
10461
let dyn_backend: std:: sync:: Arc< crate:: store:: Backend> = backend.clone ( ) ;
10462
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = Some ( dyn_backend) ) ;
10463
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10464
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
10465
write ( dir.path ( ) , " a.txt" , b " a\n " ) ;
10466
let h = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10467
repo.gc ( true ) .unwrap ( ) ;
10468
let again = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
10469
assert_eq! ( again.store ( ) .pack_count ( ) , 1 ) ;
10470
assert_eq! ( again.bookmark_heads ( ) .unwrap ( ) , vec! [ h] ) ;
10471
10472
10473
use crate :: strata:: Store as _ ;
10474
let idx = backend
10475
.list ( " pack-" )
10476
.unwrap ( )
10477
.into_iter ( )
10478
.map ( | e | e.key )
10479
.find ( | k | k.ends_with ( " .idx" ) & & k ! = " pack-0000.idx" )
10480
.unwrap ( ) ;
10481
let stem = idx.trim_end_matches ( " .idx" ) ;
10482
for ext in [ " pack" , " idx" ] {
10483
let bytes = backend.get ( & format! ( " {stem} .{ext} " ) ) .unwrap ( ) ;
10484
backend
10485
.put_new ( & format! ( " pack-copy.{ext} " ) , & bytes)
10486
.unwrap ( ) ;
10487
}
10488
* backend.doomed.lock ( ) .unwrap ( ) = Some ( " pack-copy.idx" .into ( ) ) ;
10489
let again = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
10490
assert_eq! ( again.store ( ) .pack_count ( ) , 1 ) ;
10491
assert_eq! ( again.bookmark_heads ( ) .unwrap ( ) , vec! [ h] ) ;
10492
assert! ( ! backend.has ( " pack-copy.idx" ) .unwrap ( ) ) ;
10493
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = None ) ;
10494
}