98286406 Andy McCall 1 month ago ↶
1
2
3
use super :: * ;
75167672 Andy McCall 1 month ago
4
use crate :: protocol:: SignedPolicy;
957fee30 Andy McCall 1 month ago ↶
5
use crate :: repo:: { BisectStep, Selection} ;
98286406 Andy McCall 1 month ago ↶
6
7
8
9
fn hermetic ( ) {
10
static ONCE : std:: sync:: Once = std:: sync:: Once:: new( ) ;
11
static DIR : std:: sync:: OnceLock< tempfile:: TempDir> = std:: sync:: OnceLock:: new( ) ;
12
ONCE .call_once ( | | {
13
let d = tempfile:: tempdir( ) .unwrap ( ) ;
14
std:: env:: set_var( " OXY_HOME" , d.path ( ) ) ;
15
DIR .set ( d) .ok ( ) ;
16
} ) ;
17
}
18
19
fn write ( root : & Path, rel : & str , content : & [u8 ]) {
20
let p = root.join ( rel) ;
21
fs:: create_dir_all( p.parent ( ) .unwrap ( ) ) .unwrap ( ) ;
22
fs:: write( p, content) .unwrap ( ) ;
23
}
24
25
fn statuses ( s : & SnapshotResult) -> Vec< ( String, ChangeStatus) > {
75167672 Andy McCall 1 month ago
26
s.statuses
27
.iter ( )
28
.map ( | p | ( p.path.clone ( ) , p.status) )
29
.collect ( )
98286406 Andy McCall 1 month ago ↶
30
}
31
32
# [ test ]
33
fn init_snapshot_commit_log_undo ( ) {
34
hermetic ( ) ;
35
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
36
let root = dir.path ( ) ;
37
let repo = Repo:: init( root) .unwrap ( ) ;
38
assert! ( Repo:: init( root) .is_err ( ) ) ;
39
let ops = repo.op_log ( ) .unwrap ( ) ;
40
assert_eq! ( ops.len ( ) , 1 ) ;
41
assert_eq! ( ops[ 0 ] .1. command, " oxy init" ) ;
42
43
44
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
45
assert! ( ! s.changed) ;
46
assert_eq! ( repo.op_log ( ) .unwrap ( ) .len ( ) , 1 ) ;
47
48
write ( root, " src/main.rs" , b " fn main() {}\n " ) ;
49
write ( root, " README.md" , b " # Hello\n " ) ;
50
write ( root, " .oxyignore" , b " /target\n " ) ;
51
write ( root, " target/junk.o" , b " \0 \0 \0 " ) ;
52
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
53
assert! ( s.changed) ;
54
assert_eq! (
55
statuses ( & s) ,
56
vec! [
57
( " .oxyignore" .into ( ) , ChangeStatus:: Added) ,
58
( " README.md" .into ( ) , ChangeStatus:: Added) ,
59
( " src/main.rs" .into ( ) , ChangeStatus:: Added) ,
60
]
61
) ;
62
assert_eq! ( repo.op_log ( ) .unwrap ( ) .len ( ) , 2 ) ;
63
64
let s2 = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
65
assert! ( ! s2.changed) ;
66
assert_eq! ( s2.wc_hash, s.wc_hash) ;
67
assert_eq! ( repo.op_log ( ) .unwrap ( ) .len ( ) , 2 ) ;
68
69
let h1 = repo.commit ( " Initial import" , & [ ] ) .unwrap ( ) ;
70
let c1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) ;
71
assert_eq! ( c1.kind, ChangeKind:: Normal) ;
72
assert! ( c1.deps.is_empty ( ) , " first change has nothing to depend on" ) ;
73
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
74
let ( wc_hash, wc) = repo.working_copy ( ) .unwrap ( ) ;
75
assert_eq! ( wc.deps, vec! [ h1] ) ;
76
assert! ( wc.edits.is_empty ( ) ) ;
77
assert! ( repo.commit ( " nothing" , & [ ] ) .is_err ( ) ) ;
78
79
75167672 Andy McCall 1 month ago
80
write (
81
root,
82
" src/main.rs" ,
83
b " fn main() {\n println!(\" hi\" );\n }\n " ,
84
) ;
98286406 Andy McCall 1 month ago ↶
85
fs:: remove_file( root.join ( " README.md" ) ) .unwrap ( ) ;
86
write ( root, " logo.png" , b " \x89 PNG\0 \0 \x01 " ) ;
87
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
88
assert_eq! (
89
statuses ( & s) ,
90
vec! [
91
( " README.md" .into ( ) , ChangeStatus:: Removed) ,
92
( " logo.png" .into ( ) , ChangeStatus:: Added) ,
93
( " src/main.rs" .into ( ) , ChangeStatus:: Modified) ,
94
]
95
) ;
96
assert_ne! ( s.wc_hash, wc_hash) ;
97
98
assert_eq! ( s.wc.change_id, wc.change_id) ;
99
assert! ( s.wc.deps.contains ( & h1) ) ;
100
101
let h2 = repo.commit ( " Second" , & [ ] ) .unwrap ( ) ;
102
let c2 = repo.store ( ) .get_change ( & h2) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
103
assert_eq! (
104
c2.deps,
105
vec! [ h1] ,
106
" everything it touches was introduced by h1"
107
) ;
98286406 Andy McCall 1 month ago ↶
108
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
109
110
111
let st = repo.load_state ( & [ h2] ) .unwrap ( ) ;
112
let snap = st.materialize ( ) ;
113
assert! ( ! snap.is_conflicted ( ) ) ;
75167672 Andy McCall 1 month ago
114
assert_eq! (
115
snap.files.keys ( ) .cloned ( ) .collect:: < Vec< _ > > ( ) ,
116
[ " .oxyignore" , " logo.png" , " src" , " src/main.rs" ]
117
) ;
118
assert! (
119
matches! ( & snap.files[ " src/main.rs" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " fn main() {\n println!(\" hi\" );\n }\n " )
120
) ;
98286406 Andy McCall 1 month ago ↶
121
assert! ( snap.files[ " logo.png" ] .mode.opaque) ;
122
if let FileContent:: Opaque { blobs } = & snap.files[ " logo.png" ] .content {
123
assert_eq! ( repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) , b " \x89 PNG\0 \0 \x01 " ) ;
124
} else {
125
panic! ( " logo should be opaque" ) ;
126
}
127
128
129
let log = repo.log ( ) .unwrap ( ) ;
130
assert_eq! ( log.len ( ) , 3 ) ;
131
assert! ( log[ 0 ] .is_working_copy) ;
132
assert_eq! ( log[ 1 ] .hash, h2) ;
133
assert! ( log[ 1 ] .is_head) ;
134
assert_eq! ( log[ 2 ] .hash, h1) ;
135
let d = repo.describe_change ( & h2) .unwrap ( ) ;
136
assert! ( d.contains ( " D README.md" ) , " {d}" ) ;
137
assert! ( d.contains ( " + println!(\" hi\" );" ) , " {d}" ) ;
138
assert! ( d.contains ( " -fn main() {}" ) , " {d}" ) ;
139
140
141
assert_eq! ( repo.resolve_change ( & c2.change_id.short ( 6 ) ) .unwrap ( ) , h2) ;
75167672 Andy McCall 1 month ago
142
assert_eq! (
143
repo.resolve_change ( & format! ( " #{} " , h1.short ( 8 ) ) ) .unwrap ( ) ,
144
h1
145
) ;
98286406 Andy McCall 1 month ago ↶
146
assert! ( repo.resolve_change ( " zzzz" ) .is_err ( ) ) ;
147
148
149
repo.undo ( ) .unwrap ( ) ;
150
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
151
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
152
assert! (
153
! s.changed,
154
" files on disk still match the pre-commit working copy"
155
) ;
98286406 Andy McCall 1 month ago ↶
156
assert_eq! ( s.statuses.len ( ) , 3 ) ;
157
}
158
159
160
fn conflicted_repo ( ) -> ( tempfile:: TempDir, Repo, ChangeHash, ChangeHash, ChangeHash) {
161
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
162
let root = dir.path ( ) ;
163
let repo = Repo:: init( root) .unwrap ( ) ;
164
write ( root, " f.txt" , b " a\n b\n c\n " ) ;
165
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
166
write ( root, " f.txt" , b " a\n B\n c\n " ) ;
167
let h2 = repo.commit ( " upper" , & [ ] ) .unwrap ( ) ;
168
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
169
write ( root, " f.txt" , b " a\n X\n c\n " ) ;
170
let h3 = repo.commit ( " ex" , & [ ] ) .unwrap ( ) ;
171
( dir, repo, h1, h2, h3)
172
}
173
3652104a Andy McCall 19 days ago ↶
174
175
176
98286406 Andy McCall 1 month ago ↶
177
# [ test ]
3652104a Andy McCall 19 days ago ↶
178
fn marker_like_text_does_not_block_a_resolution ( ) {
179
hermetic ( ) ;
180
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
181
let root = dir.path ( ) ;
182
let repo = Repo:: init( root) .unwrap ( ) ;
183
let example = " ```\n <<<<<<< oxy conflict 1 of 2, side 1: change k7m2p9 \" Add retry loop\" \n ... lines ...\n ======= oxy conflict 1 of 2, side 2: change q3x8w1 \" Refactor client\" \n >>>>>>> oxy conflict 1 of 2 end\n ```\n " ;
184
write (
185
root,
186
" doc.md" ,
187
format! ( " intro\n {example} b\n end\n " ) .as_bytes ( ) ,
188
) ;
189
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
190
write (
191
root,
192
" doc.md" ,
193
format! ( " intro\n {example} B\n end\n " ) .as_bytes ( ) ,
194
) ;
195
repo.commit ( " upper" , & [ ] ) .unwrap ( ) ;
196
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
197
write (
198
root,
199
" doc.md" ,
200
format! ( " intro\n {example} X\n end\n " ) .as_bytes ( ) ,
201
) ;
202
repo.commit ( " ex" , & [ ] ) .unwrap ( ) ;
203
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
204
.unwrap ( ) ;
205
let marked = fs:: read_to_string( root.join ( " doc.md" ) ) .unwrap ( ) ;
206
assert! ( marked.contains ( " oxy conflict 1 of 1" ) , " {marked}" ) ;
207
208
write ( root, " doc.md" , marked.replace ( " B\n " , " BB\n " ) .as_bytes ( ) ) ;
209
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
210
assert_eq! (
211
statuses ( & s) ,
212
vec! [ ( " doc.md" .into ( ) , ChangeStatus:: ConflictEdited) ]
213
) ;
214
215
write (
216
root,
217
" doc.md" ,
218
format! ( " intro\n {example} X\n end\n " ) .as_bytes ( ) ,
219
) ;
220
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
221
assert_eq! (
222
statuses ( & s) ,
223
vec! [ ( " doc.md" .into ( ) , ChangeStatus:: Resolved) ]
224
) ;
225
let h = repo.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
226
assert_eq! (
227
repo.store ( ) .get_change ( & h) .unwrap ( ) .kind,
228
ChangeKind:: Resolution
229
) ;
230
assert! ( ! repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) .is_conflicted ( ) ) ;
231
}
232
9bd98cfb Andy McCall 11 days ago ↶
233
234
235
3652104a Andy McCall 19 days ago ↶
236
# [ test ]
9bd98cfb Andy McCall 11 days ago ↶
237
fn adjacent_insertions_are_settled_by_keeping_both ( ) {
238
hermetic ( ) ;
239
for reverse in [ false , true ] {
240
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
241
let root = dir.path ( ) ;
242
let repo = Repo:: init( root) .unwrap ( ) ;
243
write ( root, " f.txt" , b " a\n b\n " ) ;
244
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
245
write ( root, " f.txt" , b " a\n first\n b\n " ) ;
246
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
247
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
248
write ( root, " f.txt" , b " a\n second\n b\n " ) ;
249
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
250
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
251
.unwrap ( ) ;
252
let marked = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
253
let ( f, s) = (
254
marked.find ( " first\n " ) .unwrap ( ) ,
255
marked.find ( " second\n " ) .unwrap ( ) ,
256
) ;
257
assert! ( f < s, " the earlier change's side comes first: {marked}" ) ;
258
259
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
260
let node = snap.base_snapshot.files[ " f.txt" ] .node;
261
assert_eq! ( snap.base.both_sides ( & node) .iter ( ) .flatten ( ) .count ( ) , 1 ) ;
262
let kept = repo.keep_both ( & snap, & [ ] , reverse) .unwrap ( ) ;
263
assert_eq! (
264
kept,
265
vec! [ KeptBoth {
266
path: " f.txt" .into ( ) ,
267
settled: vec! [ 1 ] ,
268
left: vec! [ ]
269
} ]
270
) ;
271
let want: & [ u8 ] = if reverse {
272
b " a\n second\n first\n b\n "
273
} else {
274
b " a\n first\n second\n b\n "
275
} ;
276
assert_eq! ( fs:: read( root.join ( " f.txt" ) ) .unwrap ( ) , want) ;
277
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
278
assert_eq! ( statuses ( & s) , vec! [ ( " f.txt" .into ( ) , ChangeStatus:: Resolved) ] ) ;
279
let h = repo.commit ( " both" , & [ ] ) .unwrap ( ) ;
280
assert_eq! (
281
repo.store ( ) .get_change ( & h) .unwrap ( ) .kind,
282
ChangeKind:: Resolution
283
) ;
284
assert! ( ! repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) .is_conflicted ( ) ) ;
285
}
286
}
287
4afe288b Andy McCall 11 days ago ↶
288
289
290
291
# [ test ]
292
fn union_files_keep_both_sides_on_checkout ( ) {
293
hermetic ( ) ;
294
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
295
let root = dir.path ( ) ;
296
let repo = Repo:: init( root) .unwrap ( ) ;
297
write ( root, " .oxyattributes" , b " CHANGELOG.md merge=union\n " ) ;
298
write (
299
root,
300
" CHANGELOG.md" ,
301
b " # Changelog\n ## Unreleased\n ## 1.0\n " ,
302
) ;
303
write ( root, " f.txt" , b " a\n b\n " ) ;
304
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
305
write (
306
root,
307
" CHANGELOG.md" ,
308
b " # Changelog\n ## Unreleased\n - one\n ## 1.0\n " ,
309
) ;
310
write ( root, " f.txt" , b " a\n one\n b\n " ) ;
311
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
312
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
313
write (
314
root,
315
" CHANGELOG.md" ,
316
b " # Changelog\n ## Unreleased\n - two\n ## 1.0\n " ,
317
) ;
318
write ( root, " f.txt" , b " a\n two\n b\n " ) ;
319
repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
320
let r = repo
321
.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
322
.unwrap ( ) ;
323
assert! ( r.conflicted) ;
324
assert_eq! ( r.kept_both.len ( ) , 1 , " {:?}" , r.kept_both) ;
325
assert_eq! ( r.kept_both[ 0 ] .path, " CHANGELOG.md" ) ;
326
assert_eq! (
327
fs:: read_to_string( root.join ( " CHANGELOG.md" ) ) .unwrap ( ) ,
328
" # Changelog\n ## Unreleased\n - one\n - two\n ## 1.0\n "
329
) ;
330
assert! ( has_conflict_markers ( & fs:: read( root.join ( " f.txt" ) ) .unwrap ( ) ) ) ;
331
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
332
assert_eq! (
333
statuses ( & s) ,
334
vec! [ ( " CHANGELOG.md" .into ( ) , ChangeStatus:: Resolved) ]
335
) ;
336
}
337
9bd98cfb Andy McCall 11 days ago ↶
338
339
340
341
# [ test ]
342
fn keeping_both_refuses_replaced_or_edited_lines ( ) {
343
hermetic ( ) ;
344
let ( dir, repo, _ , _ , _ ) = conflicted_repo ( ) ;
345
let root = dir.path ( ) ;
346
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
347
.unwrap ( ) ;
348
let marked = fs:: read( root.join ( " f.txt" ) ) .unwrap ( ) ;
349
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
350
let node = snap.base_snapshot.files[ " f.txt" ] .node;
351
assert_eq! ( snap.base.both_sides ( & node) , vec! [ None ] ) ;
352
let kept = repo.keep_both ( & snap, & [ " f.txt" .into ( ) ] , false ) .unwrap ( ) ;
353
assert! ( kept[ 0 ] .settled.is_empty ( ) ) ;
354
assert_eq! ( kept[ 0 ] .left[ 0 ] .0 , 1 ) ;
355
assert! ( kept[ 0 ] .left[ 0 ] .1. contains ( " both sides changed" ) , " {kept:?}" ) ;
356
assert_eq! ( fs:: read( root.join ( " f.txt" ) ) .unwrap ( ) , marked) ;
357
358
359
let sides = vec! [ Some ( vec! [ b " x\n " .to_vec ( ) , b " y\n " .to_vec ( ) ] ) ] ;
360
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 " ;
361
let ( out, settled, left) = super :: snapshot:: keep_both_text( text, & sides, false ) ;
362
assert_eq! ( out, text.to_vec ( ) ) ;
363
assert! ( settled.is_empty ( ) ) ;
364
assert! ( left[ 0 ] .1. contains ( " edited" ) , " {left:?}" ) ;
365
366
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 " ;
367
let ( out, settled, _ ) = super :: snapshot:: keep_both_text( text, & sides, false ) ;
368
assert_eq! ( out, b " a\n x\n y\n b\n " .to_vec ( ) ) ;
369
assert_eq! ( settled, vec! [ 1 ] ) ;
370
}
371
372
# [ test ]
98286406 Andy McCall 1 month ago ↶
373
fn concurrent_edits_conflict_and_can_be_resolved ( ) {
374
hermetic ( ) ;
375
let ( dir, repo, h1, h2, h3) = conflicted_repo ( ) ;
376
let root = dir.path ( ) ;
377
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
378
assert_eq! ( heads, [ h2, h3] .into_iter ( ) .collect ( ) ) ;
379
let st = repo.load_state ( & [ h2, h3] ) .unwrap ( ) ;
380
assert! ( st.materialize ( ) .is_conflicted ( ) ) ;
381
382
75167672 Andy McCall 1 month ago
383
let r = repo
384
.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
385
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
386
assert! ( r.conflicted) ;
387
let marked = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
388
assert! (
389
marked.starts_with ( " a\n <<<<<<< oxy conflict 1 of 1" ) ,
390
" {marked}"
391
) ;
392
assert! (
393
marked.contains ( " B\n " ) & & marked.contains ( " X\n " ) & & marked.ends_with ( " end\n c\n " ) ,
394
" {marked}"
395
) ;
98286406 Andy McCall 1 month ago ↶
396
assert! ( has_conflict_markers ( marked.as_bytes ( ) ) ) ;
397
398
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
399
assert! ( s.statuses.is_empty ( ) ) ;
400
assert! ( ! s.resolves) ;
401
402
403
write ( root, " f.txt" , marked.replace ( " B\n " , " BB\n " ) .as_bytes ( ) ) ;
404
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
405
assert_eq! (
406
statuses ( & s) ,
407
vec! [ ( " f.txt" .into ( ) , ChangeStatus:: ConflictEdited) ]
408
) ;
98286406 Andy McCall 1 month ago ↶
409
assert! ( repo.commit ( " no" , & [ ] ) .is_err ( ) ) ;
410
411
412
write ( root, " f.txt" , b " a\n X\n c\n d\n " ) ;
413
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
414
assert_eq! ( statuses ( & s) , vec! [ ( " f.txt" .into ( ) , ChangeStatus:: Resolved) ] ) ;
415
assert! ( s.resolves) ;
416
let h4 = repo.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
417
let c4 = repo.store ( ) .get_change ( & h4) .unwrap ( ) ;
418
assert_eq! ( c4.kind, ChangeKind:: Resolution) ;
419
let mut want = vec! [ h1, h2, h3] ;
420
want.sort ( ) ;
75167672 Andy McCall 1 month ago
421
assert_eq! (
422
c4.deps, want,
423
" a resolution depends on exactly the contributors and what it references"
424
) ;
98286406 Andy McCall 1 month ago ↶
425
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h4] ) ;
426
let snap = repo.load_state ( & [ h4] ) .unwrap ( ) .materialize ( ) ;
427
assert! ( ! snap.is_conflicted ( ) ) ;
75167672 Andy McCall 1 month ago
428
assert! (
429
matches! ( & snap.files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n X\n c\n d\n " )
430
) ;
98286406 Andy McCall 1 month ago ↶
431
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
432
433
434
let mut st = State:: new( ) ;
435
for h in [ h4, h3, h1, h2] {
436
st.add_unchecked ( repo.store ( ) .get_change ( & h) .unwrap ( ) ) ;
437
}
438
st.validate ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
439
assert! (
440
matches! ( & st.materialize ( ) .files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n X\n c\n d\n " )
441
) ;
98286406 Andy McCall 1 month ago ↶
442
}
443
69953b50 Andy McCall 19 days ago ↶
444
445
446
98286406 Andy McCall 1 month ago ↶
447
# [ test ]
69953b50 Andy McCall 19 days ago ↶
448
fn resolutions_and_amends_take_paths ( ) {
449
hermetic ( ) ;
450
let ( dir, repo, _h1, _h2, _h3) = conflicted_repo ( ) ;
451
let root = dir.path ( ) ;
452
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
453
.unwrap ( ) ;
454
455
write ( root, " f.txt" , b " a\n X\n c\n " ) ;
456
write ( root, " notes.txt" , b " pending\n " ) ;
457
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
458
assert! ( s.resolves) ;
459
let err = repo
460
.commit_selection_partial ( " resolve" , & [ ] , & Selection:: Paths( vec! [ " notes.txt" .into ( ) ] ) )
461
.unwrap_err ( )
462
.to_string ( ) ;
463
assert! (
464
err.contains ( " takes every file it resolves" ) & & err.contains ( " f.txt" ) ,
465
" {err}"
466
) ;
467
let ( h4, partial) = repo
468
.commit_selection_partial ( " resolve" , & [ ] , & Selection:: Paths( vec! [ " f.txt" .into ( ) ] ) )
469
.unwrap ( ) ;
470
assert! ( partial) ;
471
assert_eq! (
472
repo.store ( ) .get_change ( & h4) .unwrap ( ) .kind,
473
ChangeKind:: Resolution
474
) ;
475
assert! ( ! repo
476
.load_state ( & [ h4] )
477
.unwrap ( )
478
.materialize ( )
479
.is_conflicted ( ) ) ;
480
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
481
assert_eq! (
482
statuses ( & s) ,
483
vec! [ ( " notes.txt" .into ( ) , ChangeStatus:: Added) ] ,
484
" the other work waits"
485
) ;
486
487
488
489
let h5 = repo.commit ( " notes" , & [ h4] ) .unwrap ( ) ;
490
let id5 = repo.store ( ) .get_change ( & h5) .unwrap ( ) .change_id;
491
write ( root, " notes.txt" , b " pending\n more\n " ) ;
492
write ( root, " other.txt" , b " later\n " ) ;
493
let h6 = repo
494
.amend_paths ( Some ( " notes, amended" ) , & [ " notes.txt" .into ( ) ] )
495
.unwrap ( ) ;
496
let c6 = repo.store ( ) .get_change ( & h6) .unwrap ( ) ;
497
assert_eq! ( ( c6.change_id, c6.message.as_str ( ) ) , ( id5, " notes, amended" ) ) ;
498
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h6] ) ;
499
let snap6 = repo.load_state ( & [ h6] ) .unwrap ( ) .materialize ( ) ;
500
assert! (
501
matches! ( & snap6.files[ " notes.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " pending\n more\n " )
502
) ;
503
assert! (
504
! snap6.files.contains_key ( " other.txt" ) ,
505
" other.txt stays out"
506
) ;
507
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
508
assert_eq! (
509
statuses ( & s) ,
510
vec! [ ( " other.txt" .into ( ) , ChangeStatus:: Added) ]
511
) ;
512
}
513
514
# [ test ]
98286406 Andy McCall 1 month ago ↶
515
fn resolution_may_keep_both_sides_or_neither ( ) {
516
hermetic ( ) ;
517
let ( dir, repo, _h1, _h2, _h3) = conflicted_repo ( ) ;
518
let root = dir.path ( ) ;
75167672 Andy McCall 1 month ago
519
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
520
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
521
522
write ( root, " f.txt" , b " a\n X\n B\n c\n " ) ;
523
let h = repo.commit ( " both" , & [ ] ) .unwrap ( ) ;
524
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
525
assert! ( ! snap.is_conflicted ( ) ) ;
75167672 Andy McCall 1 month ago
526
assert! (
527
matches! ( & snap.files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n X\n B\n c\n " )
528
) ;
98286406 Andy McCall 1 month ago ↶
529
530
repo.undo ( ) .unwrap ( ) ;
531
write ( root, " f.txt" , b " a\n c\n " ) ;
532
let h = repo.commit ( " neither" , & [ ] ) .unwrap ( ) ;
533
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
534
assert! ( ! snap.is_conflicted ( ) ) ;
75167672 Andy McCall 1 month ago
535
assert! (
536
matches! ( & snap.files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n c\n " )
537
) ;
98286406 Andy McCall 1 month ago ↶
538
}
539
540
# [ test ]
541
fn name_conflict_resolved_by_removing_a_candidate ( ) {
542
hermetic ( ) ;
543
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
544
let root = dir.path ( ) ;
545
let repo = Repo:: init( root) .unwrap ( ) ;
546
write ( root, " keep.txt" , b " k\n " ) ;
547
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
548
write ( root, " new.txt" , b " one\n " ) ;
549
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
550
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
551
write ( root, " new.txt" , b " two\n " ) ;
552
repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
553
let r = repo
554
.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
555
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
556
assert! ( r.conflicted) ;
75167672 Andy McCall 1 month ago
557
assert! (
558
root.join ( " new.txt.oxy-conflict-1" ) .exists ( )
559
& & root.join ( " new.txt.oxy-conflict-2" ) .exists ( )
560
) ;
98286406 Andy McCall 1 month ago ↶
561
assert! ( ! root.join ( " new.txt" ) .exists ( ) ) ;
562
563
75167672 Andy McCall 1 month ago
564
let ( keep, drop) = if fs:: read( root.join ( " new.txt.oxy-conflict-1" ) ) .unwrap ( ) = = b " two\n " {
565
( 1 , 2 )
566
} else {
567
( 2 , 1 )
568
} ;
98286406 Andy McCall 1 month ago ↶
569
fs:: remove_file( root.join ( format! ( " new.txt.oxy-conflict-{drop} " ) ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
570
fs:: rename(
571
root.join ( format! ( " new.txt.oxy-conflict-{keep} " ) ) ,
572
root.join ( " new.txt" ) ,
573
)
574
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
575
let h = repo.commit ( " pick two" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
576
assert_eq! (
577
repo.store ( ) .get_change ( & h) .unwrap ( ) .kind,
578
ChangeKind:: Resolution
579
) ;
98286406 Andy McCall 1 month ago ↶
580
581
582
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
583
assert_eq! ( heads, [ h1, h] .into_iter ( ) .collect ( ) ) ;
75167672 Andy McCall 1 month ago
584
let snap = repo
585
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
586
.unwrap ( )
587
.materialize ( ) ;
98286406 Andy McCall 1 month ago ↶
588
assert! ( ! snap.is_conflicted ( ) , " {:?}" , snap.tree.conflicts) ;
75167672 Andy McCall 1 month ago
589
assert_eq! (
590
snap.files.keys ( ) .cloned ( ) .collect:: < Vec< _ > > ( ) ,
591
[ " keep.txt" , " new.txt" ]
592
) ;
593
assert! (
594
matches! ( & snap.files[ " new.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " two\n " )
595
) ;
98286406 Andy McCall 1 month ago ↶
596
}
597
598
# [ test ]
599
fn identities_and_signed_commits ( ) {
600
hermetic ( ) ;
601
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
602
let root = dir.path ( ) ;
603
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
604
write ( root, " a.txt" , b " a\n " ) ;
605
let h0 = repo.commit ( " unsigned" , & [ ] ) .unwrap ( ) ;
606
assert_eq! ( repo.verify_change ( & h0) .unwrap ( ) , SignatureStatus:: Unsigned) ;
75167672 Andy McCall 1 month ago
607
assert_eq! (
608
repo.store ( ) .get_change ( & h0) .unwrap ( ) .author,
609
ObjectRef:: ZERO
610
) ;
98286406 Andy McCall 1 month ago ↶
611
75167672 Andy McCall 1 month ago
612
let ( key, id) = repo
613
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
614
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
615
assert_eq! ( id.hash ( ) , key) ;
616
assert_eq! ( repo.config.author_key, Some ( key) ) ;
617
618
let reopened = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
619
assert_eq! ( reopened.config.author_key, Some ( key) ) ;
620
assert_eq! ( reopened.secret_key ( ) .unwrap ( ) .unwrap ( ) .0 , key) ;
621
622
623
write ( root, " a.txt" , b " a\n b\n " ) ;
624
let h1 = repo.commit ( " authored" , & [ ] ) .unwrap ( ) ;
625
assert_eq! ( repo.store ( ) .get_change ( & h1) .unwrap ( ) .author, key) ;
626
assert_eq! ( repo.verify_change ( & h1) .unwrap ( ) , SignatureStatus:: Unsigned) ;
627
628
repo.sign_change ( & h1) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
629
assert! (
630
matches! ( repo.verify_change ( & h1) .unwrap ( ) , SignatureStatus:: Valid { key: k, .. } if k = = key)
631
) ;
98286406 Andy McCall 1 month ago ↶
632
633
repo.config.set ( " core.sign" , " change" ) .unwrap ( ) ;
634
repo.save_config ( ) .unwrap ( ) ;
635
write ( root, " a.txt" , b " a\n b\n c\n " ) ;
636
let h2 = repo.commit ( " signed" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
637
assert_eq! (
638
repo.verify_change ( & h2) .unwrap ( ) ,
639
SignatureStatus:: Valid {
640
key,
641
name: " Ada" .into ( ) ,
642
email: " [email protected] " .into ( ) ,
643
sealed: false ,
644
revoked: false
645
}
646
) ;
98286406 Andy McCall 1 month ago ↶
647
05e71ed6 Andy McCall 1 month ago ↶
648
649
650
98286406 Andy McCall 1 month ago ↶
651
let other = SecretKey:: generate( ) ;
652
let other_id = other.identity ( " Mallory" , " [email protected] " , 0 ) ;
653
let other_key = repo.store ( ) .put_identity ( & other_id) .unwrap ( ) ;
05e71ed6 Andy McCall 1 month ago ↶
654
write ( root, " a.txt" , b " a\n b\n c\n d\n " ) ;
655
repo.config.set ( " core.sign" , " seal" ) .unwrap ( ) ;
656
let h3 = repo.commit ( " ada's, unsigned" , & [ ] ) .unwrap ( ) ;
657
let forged = other.sign ( Domain:: Change, & other_key, & h3) ;
658
repo.store ( ) .put_signature ( & h3, & forged) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
659
assert! (
05e71ed6 Andy McCall 1 month ago ↶
660
matches! ( repo.verify_change ( & h3) .unwrap ( ) , SignatureStatus:: Invalid( m) if m.contains ( " neither the author" ) )
75167672 Andy McCall 1 month ago
661
) ;
05e71ed6 Andy McCall 1 month ago ↶
662
let adopted = other.sign ( Domain:: Change, & other_key, & h0) ;
663
repo.store ( ) .put_signature ( & h0, & adopted) .unwrap ( ) ;
664
assert! (
665
matches! ( repo.verify_change ( & h0) .unwrap ( ) , SignatureStatus:: Valid { name, .. } if name = = " Mallory" )
666
) ;
98286406 Andy McCall 1 month ago ↶
667
668
669
let mut sig = repo.store ( ) .signatures ( & h2) .unwrap ( ) .remove ( 0 ) ;
670
sig.ed[ 0 ] ^ = 1 ;
671
repo.store ( ) .put_signature ( & h2, & sig) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
672
assert! ( matches! (
673
repo.verify_change ( & h2) .unwrap ( ) ,
674
SignatureStatus:: Invalid( _ )
675
) ) ;
98286406 Andy McCall 1 month ago ↶
676
677
678
let no_keys = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " nowhere" ) ) ;
679
write ( root, " a.txt" , b " z\n " ) ;
680
assert! ( no_keys.commit ( " cannot sign" , & [ ] ) .is_err ( ) ) ;
681
}
682
683
# [ test ]
684
fn checkout_moves_the_working_copy ( ) {
685
hermetic ( ) ;
686
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
687
let root = dir.path ( ) ;
688
let repo = Repo:: init( root) .unwrap ( ) ;
689
write ( root, " a.txt" , b " one\n " ) ;
690
write ( root, " sub/deep/b.txt" , b " b\n " ) ;
691
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
692
write ( root, " a.txt" , b " two\n " ) ;
693
fs:: remove_file( root.join ( " sub/deep/b.txt" ) ) .unwrap ( ) ;
694
write ( root, " bin.dat" , & [ 0 , 1 , 2 , 3 ] ) ;
695
let h2 = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
696
697
let r = repo.checkout ( & [ h1] , false , " oxy checkout" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
698
assert_eq! (
699
r,
700
CheckoutReport {
701
written: 2 ,
702
removed: 1 ,
9b723f69 Andy McCall 26 days ago ↶
703
removed_paths: vec! [ " bin.dat" .into ( ) ] ,
75167672 Andy McCall 1 month ago
704
conflicted: false ,
d3cd7ef9 Andy McCall 1 month ago ↶
705
notes: vec! [ ] ,
706
lacks: 1 ,
4afe288b Andy McCall 11 days ago ↶
707
kept_both: vec! [ ] ,
75167672 Andy McCall 1 month ago
708
}
709
) ;
98286406 Andy McCall 1 month ago ↶
710
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n " ) ;
711
assert_eq! ( fs:: read( root.join ( " sub/deep/b.txt" ) ) .unwrap ( ) , b " b\n " ) ;
712
assert! ( ! root.join ( " bin.dat" ) .exists ( ) ) ;
713
let ( _ , wc) = repo.working_copy ( ) .unwrap ( ) ;
714
assert_eq! ( wc.deps, vec! [ h1] ) ;
75167672 Andy McCall 1 month ago
715
assert! (
716
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
717
" checkout leaves a clean working copy"
718
) ;
98286406 Andy McCall 1 month ago ↶
719
720
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
721
722
723
write ( root, " a.txt" , b " dirty\n " ) ;
724
assert! ( repo.checkout ( & [ h2] , false , " oxy checkout" ) .is_err ( ) ) ;
75167672 Andy McCall 1 month ago
725
let r = repo
726
.checkout ( & [ h2] , true , " oxy checkout --discard" )
727
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
728
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " two\n " ) ;
729
assert_eq! ( fs:: read( root.join ( " bin.dat" ) ) .unwrap ( ) , [ 0 , 1 , 2 , 3 ] ) ;
730
assert! ( ! root.join ( " sub" ) .exists ( ) , " empty directories are pruned" ) ;
731
assert_eq! ( r.removed, 1 ) ;
732
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
733
734
735
# [ cfg ( unix ) ]
736
{
737
fs:: remove_dir_all( root.join ( " sub" ) ) .ok ( ) ;
738
std:: os:: unix:: fs:: symlink( " /tmp" , root.join ( " sub" ) ) .unwrap ( ) ;
739
assert! ( repo.checkout ( & [ h1] , true , " oxy checkout" ) .is_err ( ) ) ;
740
}
741
}
742
743
# [ test ]
744
fn large_files_are_chunked_and_read_back ( ) {
745
hermetic ( ) ;
746
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
747
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
748
let mut big = vec! [ 0 u8 ; 700 * 1024 ] ;
749
for ( i, b) in big.iter_mut ( ) .enumerate ( ) {
750
* b = ( i as u32 ) .wrapping_mul ( 2654435761 ) .to_le_bytes ( ) [ 0 ] ;
751
}
752
let r = repo.store_blob ( & big) .unwrap ( ) ;
753
let blob = Blob:: decode( & repo.store ( ) .get_typed ( & r, ObjectType:: Blob) .unwrap ( ) ) .unwrap ( ) ;
754
assert! ( blob.chunks.len ( ) > = 3 , " {}" , blob.chunks.len ( ) ) ;
755
assert_eq! ( repo.read_blob ( & r) .unwrap ( ) , big) ;
756
assert_eq! ( Repo:: blob_ref( & big) , r) ;
757
}
758
759
# [ test ]
760
fn clone_push_pull_with_seals_and_attestations ( ) {
761
hermetic ( ) ;
762
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
763
let root = dir.path ( ) ;
764
let origin_path = root.join ( " origin" ) ;
765
let origin = Repo:: init( & origin_path) .unwrap ( ) ;
766
let origin_url = origin_path.to_string_lossy ( ) .to_string ( ) ;
767
768
75167672 Andy McCall 1 month ago
769
let mut a = Repo:: clone( & origin_url, & root.join ( " a" ) )
770
.unwrap ( )
771
.with_home ( root.join ( " home-a" ) ) ;
98286406 Andy McCall 1 month ago ↶
772
assert_eq! ( a.id ( ) .unwrap ( ) , origin.id ( ) .unwrap ( ) ) ;
75167672 Andy McCall 1 month ago
773
let ( key_a, _ ) = a
774
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
775
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
776
write ( & root.join ( " a" ) , " f.txt" , b " a\n " ) ;
777
let h1 = a.commit ( " first" , & [ ] ) .unwrap ( ) ;
778
let results = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
779
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
780
assert! ( results[ 0 ] .ok, " {}" , results[ 0 ] .reason) ;
781
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
782
75167672 Andy McCall 1 month ago
783
assert! (
784
matches! ( origin.authenticate_change ( & h1) .unwrap ( ) , SignatureStatus:: Valid { key, sealed: true , .. } if key = = key_a)
785
) ;
786
assert_eq! (
787
origin.verify_change ( & h1) .unwrap ( ) ,
788
SignatureStatus:: Unsigned
789
) ;
f8c69576 Andy McCall 1 month ago ↶
790
let serial_of = | att : & SignedAttest| match att.attest.body {
791
AttestBody:: Ref { serial, .. } => serial,
792
_ => unreachable! ( ) ,
793
} ;
98286406 Andy McCall 1 month ago ↶
794
let ( _ , att) = origin.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
795
796
797
let first = serial_of ( & att) ;
798
assert! ( first > 1_000_000_000_000 , " {first}" ) ;
98286406 Andy McCall 1 month ago ↶
799
let again = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
800
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
801
assert! ( again[ 0 ] .ok & & again[ 0 ] .reason.contains ( " up to date" ) ) ;
802
let ( _ , att) = origin.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
803
assert_eq! ( serial_of ( & att) , first, " a no-op push does not attest again" ) ;
98286406 Andy McCall 1 month ago ↶
804
805
806
let b = Repo:: clone( & origin_url, & root.join ( " b" ) ) .unwrap ( ) ;
807
assert_eq! ( fs:: read( root.join ( " b/f.txt" ) ) .unwrap ( ) , b " a\n " ) ;
808
assert_eq! ( b.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
75167672 Andy McCall 1 month ago
809
assert! ( matches! (
810
b.authenticate_change ( & h1) .unwrap ( ) ,
811
SignatureStatus:: Valid { sealed: true , .. }
812
) ) ;
98286406 Andy McCall 1 month ago ↶
813
814
815
write ( & root.join ( " b" ) , " f.txt" , b " a\n b\n " ) ;
816
let h2 = b.commit ( " second" , & [ ] ) .unwrap ( ) ;
817
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
818
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
819
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
820
821
822
write ( & root.join ( " a" ) , " g.txt" , b " g\n " ) ;
823
let h3 = a.commit ( " third" , & [ ] ) .unwrap ( ) ;
824
assert! ( a.push ( " origin" , false ) .is_err ( ) ) ;
825
let pr = a.pull ( " origin" ) .unwrap ( ) ;
826
assert! ( pr.changed & & pr.checked_out) ;
827
let mut want = vec! [ h2, h3] ;
828
want.sort ( ) ;
829
assert_eq! ( pr.heads, want) ;
830
assert_eq! ( fs:: read( root.join ( " a/f.txt" ) ) .unwrap ( ) , b " a\n b\n " ) ;
831
assert_eq! ( fs:: read( root.join ( " a/g.txt" ) ) .unwrap ( ) , b " g\n " ) ;
832
let results = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
833
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
834
assert! ( results[ 0 ] .ok, " {}" , results[ 0 ] .reason) ;
835
let mut origin_heads = origin.bookmark_heads ( ) .unwrap ( ) ;
836
origin_heads.sort ( ) ;
837
assert_eq! ( origin_heads, want) ;
838
let ( _ , att) = origin.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
839
assert_eq! ( serial_of ( & att) , first + 1 ) ;
98286406 Andy McCall 1 month ago ↶
840
841
842
let pr = b.pull ( " origin" ) .unwrap ( ) ;
843
assert_eq! ( pr.heads, want) ;
844
assert_eq! ( fs:: read( root.join ( " b/g.txt" ) ) .unwrap ( ) , b " g\n " ) ;
845
assert! ( ! b.snapshot ( " status" ) .unwrap ( ) .changed) ;
846
847
848
write ( & root.join ( " a" ) , " f.txt" , b " a\n A\n " ) ;
849
a.commit ( " a-edit" , & [ ] ) .unwrap ( ) ;
850
a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
851
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
852
write ( & root.join ( " b" ) , " f.txt" , b " a\n B\n " ) ;
853
b.commit ( " b-edit" , & [ ] ) .unwrap ( ) ;
854
let pr = b.pull ( " origin" ) .unwrap ( ) ;
855
assert! ( pr.changed & & pr.checked_out) ;
856
let text = fs:: read_to_string( root.join ( " b/f.txt" ) ) .unwrap ( ) ;
857
assert! ( text.contains ( " <<<<<<< oxy conflict" ) , " {text}" ) ;
858
write ( & root.join ( " b" ) , " f.txt" , b " a\n AB\n " ) ;
859
let hr = b.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
860
assert_eq! (
861
b.store ( ) .get_change ( & hr) .unwrap ( ) .kind,
862
ChangeKind:: Resolution
863
) ;
98286406 Andy McCall 1 month ago ↶
864
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
865
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
866
a.pull ( " origin" ) .unwrap ( ) ;
867
assert_eq! ( fs:: read( root.join ( " a/f.txt" ) ) .unwrap ( ) , b " a\n AB\n " ) ;
868
869
870
let mut want = vec! [ hr, h3] ;
871
want.sort ( ) ;
872
assert_eq! ( a.bookmark_heads ( ) .unwrap ( ) , want) ;
873
assert! ( ! a.load_state ( & want) .unwrap ( ) .materialize ( ) .is_conflicted ( ) ) ;
874
}
875
876
# [ test ]
877
fn receive_push_rejects_bad_updates ( ) {
878
hermetic ( ) ;
879
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
880
let root = dir.path ( ) ;
881
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
882
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
883
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
884
.unwrap ( )
885
.with_home ( root.join ( " home" ) ) ;
886
let ( key, _ ) = a
887
.create_identity ( " Ada" , " a@b" , IdentityScope:: Repo)
888
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
889
write ( & root.join ( " a" ) , " f.txt" , b " x\n " ) ;
890
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
891
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
892
let empty = origin.store ( ) .put_state ( & StateObject:: new( vec! [ ] ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
893
let s1 = origin
894
.store ( )
895
.put_state ( & StateObject:: new( vec! [ h1] ) )
896
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
897
898
75167672 Andy McCall 1 month ago
899
let ghost = origin
900
.store ( )
901
.put_state ( & StateObject:: new( vec! [ ObjectRef( [ 9 ; 32 ] ) ] ) )
902
.unwrap ( ) ;
903
let r = origin
904
.receive_push (
905
Bundle:: default( ) ,
906
vec! [ RefUpdate {
907
name: " main" .into ( ) ,
908
old_state: s1,
909
new_state: ghost,
910
force: false ,
911
attestation: None ,
912
} ] ,
913
None ,
914
vec! [ ] ,
915
)
916
.unwrap ( ) ;
917
assert! (
918
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " missing" ) ,
919
" {}" ,
920
r[ 0 ] .reason
921
) ;
98286406 Andy McCall 1 month ago ↶
922
923
75167672 Andy McCall 1 month ago
924
let r = origin
925
.receive_push (
926
Bundle:: default( ) ,
927
vec! [ RefUpdate {
928
name: " main" .into ( ) ,
929
old_state: empty,
930
new_state: s1,
931
force: false ,
932
attestation: None ,
933
} ] ,
934
None ,
935
vec! [ ] ,
936
)
937
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
938
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " stale" ) , " {}" , r[ 0 ] .reason) ;
939
940
75167672 Andy McCall 1 month ago
941
let r = origin
942
.receive_push (
943
Bundle:: default( ) ,
944
vec! [ RefUpdate {
945
name: " main" .into ( ) ,
946
old_state: s1,
947
new_state: empty,
948
force: false ,
949
attestation: None ,
950
} ] ,
951
None ,
952
vec! [ ] ,
953
)
954
.unwrap ( ) ;
955
assert! (
956
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " fast-forward" ) ,
957
" {}" ,
958
r[ 0 ] .reason
959
) ;
960
let r = origin
961
.receive_push (
962
Bundle:: default( ) ,
963
vec! [ RefUpdate {
964
name: " main" .into ( ) ,
965
old_state: s1,
966
new_state: empty,
967
force: true ,
968
attestation: None ,
969
} ] ,
970
None ,
971
vec! [ ] ,
972
)
973
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
974
assert! ( r[ 0 ] .ok) ;
975
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ ] ) ;
976
977
75167672 Andy McCall 1 month ago
978
let sa = a
979
.sign_attest ( & Attest {
980
repo: a.id ( ) .unwrap ( ) ,
981
hlc: Hlc:: now( ) ,
982
body: AttestBody:: Ref {
983
name: " main" .into ( ) ,
984
old_state: empty,
985
new_state: s1,
986
serial: 1 ,
987
} ,
988
} )
989
.unwrap ( ) ;
990
let r = origin
991
.receive_push (
992
Bundle:: default( ) ,
993
vec! [ RefUpdate {
994
name: " main" .into ( ) ,
995
old_state: empty,
996
new_state: s1,
997
force: false ,
998
attestation: Some ( sa.clone ( ) ) ,
999
} ] ,
1000
None ,
1001
vec! [ ] ,
1002
)
1003
.unwrap ( ) ;
1004
assert! (
1005
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " serial" ) ,
1006
" {}" ,
1007
r[ 0 ] .reason
1008
) ;
98286406 Andy McCall 1 month ago ↶
1009
75167672 Andy McCall 1 month ago
1010
let sa2 = a
1011
.sign_attest ( & Attest {
1012
repo: a.id ( ) .unwrap ( ) ,
1013
hlc: Hlc:: now( ) ,
1014
body: AttestBody:: Ref {
1015
name: " main" .into ( ) ,
1016
old_state: s1,
1017
new_state: empty,
1018
serial: 5 ,
1019
} ,
1020
} )
1021
.unwrap ( ) ;
1022
let r = origin
1023
.receive_push (
1024
Bundle:: default( ) ,
1025
vec! [ RefUpdate {
1026
name: " main" .into ( ) ,
1027
old_state: empty,
1028
new_state: s1,
1029
force: false ,
1030
attestation: Some ( sa2) ,
1031
} ] ,
1032
None ,
1033
vec! [ ] ,
1034
)
1035
.unwrap ( ) ;
1036
assert! (
1037
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " describe" ) ,
1038
" {}" ,
1039
r[ 0 ] .reason
1040
) ;
98286406 Andy McCall 1 month ago ↶
1041
let _ = key;
1042
}
1043
1044
# [ test ]
1045
fn user_config_supplies_defaults_and_clones_sign ( ) {
1046
hermetic ( ) ;
1047
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1048
let root = dir.path ( ) ;
1049
let home = root.join ( " home" ) ;
1050
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1051
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1052
1053
1054
let mut a = Repo:: init( & root.join ( " a" ) ) .unwrap ( ) .with_home ( & home) ;
75167672 Andy McCall 1 month ago
1055
let ( key, _ ) = a
1056
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1057
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1058
assert! ( ! a.config.repo_layer ( ) .contains_key ( " author.key" ) ) ;
1059
assert_eq! ( a.config.author_key, Some ( key) ) ;
1060
assert! ( home.join ( " config.toml" ) .exists ( ) ) ;
1061
a.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
1062
assert! ( a.config.sign_changes) ;
1063
1064
1065
a.config.set ( " core.sign" , " seal" ) .unwrap ( ) ;
1066
a.save_config ( ) .unwrap ( ) ;
1067
let again = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) .with_home ( & home) ;
1068
assert! ( ! again.config.sign_changes) ;
1069
assert_eq! ( again.config.author_key, Some ( key) ) ;
75167672 Andy McCall 1 month ago
1070
assert_eq! (
1071
read_config_file ( & root.join ( " a/.oxy/config" ) )
1072
.unwrap ( )
1073
.get ( " core.sign" )
1074
.map ( String :: as_str) ,
1075
Some ( " seal" )
1076
) ;
98286406 Andy McCall 1 month ago ↶
1077
1078
1079
1080
let b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) .with_home ( & home) ;
1081
assert! ( b.config.sign_changes) ;
1082
write ( & root.join ( " b" ) , " f.txt" , b " f\n " ) ;
1083
let h = b.commit ( " signed from a clone" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1084
assert! (
1085
matches! ( b.verify_change ( & h) .unwrap ( ) , SignatureStatus:: Valid { key: k, .. } if k = = key)
1086
) ;
98286406 Andy McCall 1 month ago ↶
1087
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
75167672 Andy McCall 1 month ago
1088
assert! ( matches! (
1089
origin.verify_change ( & h) .unwrap ( ) ,
1090
SignatureStatus:: Valid { .. }
1091
) ) ;
98286406 Andy McCall 1 month ago ↶
1092
}
1093
1094
# [ test ]
1095
fn format1_repositories_are_migrated_on_open ( ) {
1096
hermetic ( ) ;
1097
use crate :: oplog:: { Format1Op, Format1View} ;
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 ( " one" , & [ ] ) .unwrap ( ) ;
1103
let ( wc_hash, _ ) = repo.working_copy ( ) .unwrap ( ) ;
1104
1105
let mut v1 = Format1View:: default( ) ;
1106
v1.bookmarks.insert ( " main" .into ( ) , vec! [ ] ) ;
1107
v1.workspaces.insert ( repo.workspace ( ) , wc_hash) ;
75167672 Andy McCall 1 month ago
1108
let init = Format1Op {
1109
parents: vec! [ ] ,
1110
hlc: Hlc:: default( ) ,
1111
command: " oxy init" .into ( ) ,
1112
view: v1.clone ( ) ,
1113
} ;
98286406 Andy McCall 1 month ago ↶
1114
let init_ref = repo.store ( ) .put ( ObjectType:: Op, & init.encode ( ) ) .unwrap ( ) ;
1115
v1.bookmarks.insert ( " main" .into ( ) , vec! [ h1] ) ;
75167672 Andy McCall 1 month ago
1116
let commit = Format1Op {
1117
parents: vec! [ init_ref] ,
1118
hlc: Hlc:: default( ) ,
1119
command: " oxy commit" .into ( ) ,
1120
view: v1,
1121
} ;
98286406 Andy McCall 1 month ago ↶
1122
let commit_ref = repo.store ( ) .put ( ObjectType:: Op, & commit.encode ( ) ) .unwrap ( ) ;
1123
write_atomic ( & root.join ( " .oxy/ops/head" ) , commit_ref.to_hex ( ) .as_bytes ( ) ) .unwrap ( ) ;
1124
write_atomic ( & root.join ( " .oxy/format" ) , FORMAT1_LINE .as_bytes ( ) ) .unwrap ( ) ;
1125
drop ( repo) ;
1126
1127
let repo = Repo:: open( root) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1128
assert_eq! (
1129
fs:: read_to_string( root.join ( " .oxy/format" ) ) .unwrap ( ) ,
1130
FORMAT_LINE
1131
) ;
98286406 Andy McCall 1 month ago ↶
1132
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
1133
let ops = repo.op_log ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1134
assert_eq! (
1135
ops.iter ( )
1136
.map ( | ( _, o ) | o.command.as_str ( ) )
1137
.collect:: < Vec< _ > > ( ) ,
1138
[ " oxy commit" , " oxy init" ]
1139
) ;
98286406 Andy McCall 1 month ago ↶
1140
assert_eq! ( repo.heads_of ( & ops[ 1 ] .1. view, " main" ) .unwrap ( ) , vec! [ ] ) ;
1141
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
1142
1143
assert_eq! ( Repo:: open( root) .unwrap ( ) .op_log ( ) .unwrap ( ) .len ( ) , 2 ) ;
1144
}
1145
1146
# [ test ]
1147
fn undo_skips_snapshot_operations ( ) {
1148
hermetic ( ) ;
1149
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1150
let root = dir.path ( ) ;
1151
let repo = Repo:: init( root) .unwrap ( ) ;
1152
write ( root, " a.txt" , b " a\n " ) ;
1153
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
1154
1155
write ( root, " b.txt" , b " b\n " ) ;
1156
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
1157
assert! ( s.changed) ;
1158
assert! ( repo.head_op ( ) .unwrap ( ) .1. is_snapshot ( ) ) ;
1159
assert_eq! ( repo.last_command ( ) .unwrap ( ) .command, " oxy commit -m \" a\" " ) ;
1160
1161
repo.undo ( ) .unwrap ( ) ;
1162
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ ] ) ;
1163
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
1164
let paths: Vec< _ > = s.statuses.iter ( ) .map ( | p | p.path.as_str ( ) ) .collect ( ) ;
75167672 Andy McCall 1 month ago
1165
assert_eq! (
1166
paths,
1167
[ " a.txt" , " b.txt" ] ,
1168
" both files are back in the working copy"
1169
) ;
98286406 Andy McCall 1 month ago ↶
1170
assert! ( repo.store ( ) .get_change ( & h1) .unwrap ( ) .deps.is_empty ( ) ) ;
1171
1172
assert_eq! ( repo.last_command ( ) .unwrap ( ) .command, " oxy init" ) ;
1173
assert! ( repo.undo ( ) .is_err ( ) ) ;
1174
}
1175
1176
# [ test ]
1177
fn independent_changes_commute_and_make_multiple_heads ( ) {
1178
hermetic ( ) ;
1179
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1180
let root = dir.path ( ) ;
1181
let repo = Repo:: init( root) .unwrap ( ) ;
1182
write ( root, " a.txt" , b " a\n " ) ;
1183
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
1184
write ( root, " b.txt" , b " b\n " ) ;
1185
let h2 = repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
1186
let c2 = repo.store ( ) .get_change ( & h2) .unwrap ( ) ;
1187
assert! ( c2.deps.is_empty ( ) , " b.txt touches nothing from h1" ) ;
1188
let mut heads = repo.bookmark_heads ( ) .unwrap ( ) ;
1189
heads.sort ( ) ;
1190
let mut want = vec! [ h1, h2] ;
1191
want.sort ( ) ;
1192
assert_eq! ( heads, want) ;
1193
1194
write ( root, " a.txt" , b " a\n a2\n " ) ;
1195
let h3 = repo.commit ( " a2" , & [ ] ) .unwrap ( ) ;
1196
assert_eq! ( repo.store ( ) .get_change ( & h3) .unwrap ( ) .deps, vec! [ h1] ) ;
1197
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
1198
assert_eq! ( heads, [ h2, h3] .into_iter ( ) .collect ( ) ) ;
1199
1200
write ( root, " c.txt" , b " c\n " ) ;
1201
let h4 = repo.commit ( " c" , & [ h2] ) .unwrap ( ) ;
1202
assert_eq! ( repo.store ( ) .get_change ( & h4) .unwrap ( ) .deps, vec! [ h2] ) ;
1203
}
1204
1205
# [ test ]
1206
fn mode_change_and_text_binary_switch ( ) {
1207
hermetic ( ) ;
1208
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1209
let root = dir.path ( ) ;
1210
let repo = Repo:: init( root) .unwrap ( ) ;
1211
write ( root, " run.sh" , b " #!/bin/sh\n " ) ;
1212
repo.commit ( " add script" , & [ ] ) .unwrap ( ) ;
1213
# [ cfg ( unix ) ]
1214
{
1215
use std:: os:: unix:: fs:: PermissionsExt;
1216
fs:: set_permissions( root.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
1217
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1218
assert_eq! (
1219
statuses ( & s) ,
1220
vec! [ ( " run.sh" .into ( ) , ChangeStatus:: ModeChanged) ]
1221
) ;
98286406 Andy McCall 1 month ago ↶
1222
let h = repo.commit ( " exec" , & [ ] ) .unwrap ( ) ;
1223
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1224
assert! ( snap.files[ " run.sh" ] .mode.exec) ;
1225
}
1226
1227
write ( root, " run.sh" , b " \0 binary" ) ;
1228
let h = repo.commit ( " binary" , & [ ] ) .unwrap ( ) ;
1229
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1230
assert! ( snap.files[ " run.sh" ] .mode.opaque) ;
1231
write ( root, " run.sh" , b " text again\n " ) ;
1232
let h = repo.commit ( " text" , & [ ] ) .unwrap ( ) ;
1233
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1234
assert! ( ! snap.files[ " run.sh" ] .mode.opaque) ;
75167672 Andy McCall 1 month ago
1235
assert! (
1236
matches! ( & snap.files[ " run.sh" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " text again\n " )
1237
) ;
98286406 Andy McCall 1 month ago ↶
1238
assert! ( ! snap.is_conflicted ( ) ) ;
1239
}
83e0677b Andy McCall 1 month ago ↶
1240
6eae06b6 Andy McCall 29 days ago ↶
1241
1242
1243
# [ cfg ( unix ) ]
1244
# [ test ]
1245
fn a_binary_file_changed_and_made_executable_at_once_keeps_both ( ) {
1246
use std:: os:: unix:: fs:: PermissionsExt;
1247
hermetic ( ) ;
1248
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1249
let root = dir.path ( ) ;
1250
let repo = Repo:: init( root) .unwrap ( ) ;
1251
write ( root, " tool.exe" , b " MZ\0 one" ) ;
1252
repo.commit ( " add" , & [ ] ) .unwrap ( ) ;
1253
write ( root, " tool.exe" , b " MZ\0 two" ) ;
1254
fs:: set_permissions( root.join ( " tool.exe" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
1255
let h = repo.commit ( " update and +x" , & [ ] ) .unwrap ( ) ;
1256
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1257
let f = & snap.files[ " tool.exe" ] ;
1258
assert! ( f.mode.opaque & & f.mode.exec, " {:?}" , f.mode) ;
1259
assert! (
1260
repo.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ,
1261
" nothing left to commit"
1262
) ;
1263
}
1264
f0a517d2 Andy McCall 20 days ago ↶
1265
1266
1267
1268
83e0677b Andy McCall 1 month ago ↶
1269
# [ test ]
f0a517d2 Andy McCall 20 days ago ↶
1270
fn a_certified_key_pushes_under_a_subject_grant ( ) {
1271
use crate :: cert:: { Certificate, SignedCertificate} ;
1272
use crate :: identity:: Scope;
1273
hermetic ( ) ;
1274
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1275
let root = dir.path ( ) ;
1276
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1277
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1278
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1279
.unwrap ( )
1280
.with_home ( root.join ( " home-a" ) ) ;
1281
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1282
.unwrap ( ) ;
1283
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1284
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1285
a.policy_init ( ) .unwrap ( ) ;
1286
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1287
let hub_sk = SecretKey:: generate( ) ;
1288
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1289
a.policy_grant_subject (
1290
" agent:claude@ada" ,
1291
hub.clone ( ) ,
1292
Scope:: CHANGES | Scope:: REFS ,
1293
None ,
1294
)
1295
.unwrap ( ) ;
1296
assert! ( a
1297
.push ( " origin" , false )
1298
.unwrap ( )
1299
.iter ( )
1300
.any ( | r | r.name = = " policy" & & r.ok ) ) ;
1301
1302
let now = Hlc:: now( ) .wall_ms;
1303
1304
1305
let agent = | name : & str , subject : & str , from : u64 , to : u64 | {
1306
let mut r = Repo:: clone( & url, & root.join ( name) )
1307
.unwrap ( )
1308
.with_home ( root.join ( format! ( " home-{name} " ) ) ) ;
1309
let ( _ , id) = r
1310
.create_identity ( name, " [email protected] " , IdentityScope:: User)
1311
.unwrap ( ) ;
1312
let cert = SignedCertificate:: sign(
1313
Certificate {
1314
key: id,
1315
issuer: hub.hash ( ) ,
1316
subject: subject.into ( ) ,
1317
claims: vec! [ ] ,
1318
scope: Scope:: CHANGES | Scope:: REFS ,
1319
not_before: from,
1320
expires: to,
a2387d54 Andy McCall 20 days ago ↶
1321
grant: None ,
f0a517d2 Andy McCall 20 days ago ↶
1322
} ,
1323
& hub,
1324
& hub_sk,
1325
) ;
1326
r.store ( ) .put_identity ( & hub) .unwrap ( ) ;
1327
r.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1328
write ( & root.join ( name) , & format! ( " {name} .txt" ) , b " agent work\n " ) ;
1329
r.commit ( " work" , & [ ] ) .unwrap ( ) ;
1330
r
1331
} ;
1332
1333
let claude = agent ( " claude" , " agent:claude@ada" , now - 60_000 , now + 600_000 ) ;
1334
let r = claude.push ( " origin" , false ) .unwrap ( ) ;
1335
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
1336
1337
a.pull ( " origin" ) .unwrap ( ) ;
1338
assert! ( a.bookmark_heads ( ) .unwrap ( ) .iter ( ) .any ( | h | a
1339
.store ( )
1340
.get_change ( h)
1341
.map ( | c | c.message = = " work" )
1342
.unwrap_or ( false ) ) ) ;
1343
f53cbe7a Andy McCall 20 days ago ↶
1344
1345
1346
1347
1348
let host = Repo:: open( & root.join ( " origin" ) )
1349
.unwrap ( )
1350
.with_acceptor ( hub.clone ( ) , hub_sk.clone ( ) ) ;
1351
let ( attest_ref, sa) = host.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
1352
host.record_acceptance ( & attest_ref) .unwrap ( ) ;
1353
let served = host.remote_refs ( ) .unwrap ( ) ;
1354
let acc = served
1355
.refs
1356
.iter ( )
1357
.find ( | r | r.name = = " main" )
1358
.unwrap ( )
1359
.accepted
1360
.clone ( )
1361
.expect ( " served with the refs" ) ;
1362
let signer = sa.sigs[ 0 ] .key;
1363
assert_eq! (
1364
a.certified_time ( & signer, & sa, Some ( & acc) ) ,
1365
acc.attest.hlc.wall_ms
1366
) ;
1367
assert_eq! ( a.certified_time ( & signer, & sa, None ) , sa.attest.hlc.wall_ms) ;
1368
let stranger_sk = SecretKey:: generate( ) ;
1369
let stranger = stranger_sk.identity ( " stranger" , " s@x" , 1 ) ;
1370
let mut forged = acc.clone ( ) ;
1371
forged.sigs = vec! [ stranger_sk.sign ( Domain:: Attest, & stranger.hash ( ) , & acc.attest.hash ( ) ) ] ;
1372
assert_eq! (
1373
a.certified_time ( & signer, & sa, Some ( & forged) ) ,
1374
sa.attest.hlc.wall_ms,
1375
" not the issuer's"
1376
) ;
1377
let mut elsewhere = acc.clone ( ) ;
1378
elsewhere.attest.body = AttestBody:: Accepted {
1379
attest: ObjectRef( [ 7 ; 32 ] ) ,
1380
} ;
1381
elsewhere.sigs = vec! [ hub_sk.sign ( Domain:: Attest, & hub.hash ( ) , & elsewhere.attest.hash ( ) ) ] ;
1382
assert_eq! (
1383
a.certified_time ( & signer, & sa, Some ( & elsewhere) ) ,
1384
sa.attest.hlc.wall_ms,
1385
" another attestation's"
1386
) ;
1387
a.pull ( " origin" ) .unwrap ( ) ;
1388
1389
host.gc ( true ) .unwrap ( ) ;
1390
assert! ( host.acceptance_of ( & attest_ref) .unwrap ( ) .is_some ( ) ) ;
1391
f0a517d2 Andy McCall 20 days ago ↶
1392
let late = agent ( " late" , " agent:claude@ada" , now - 1_200_000 , now - 600_000 ) ;
1393
let r = late.push ( " origin" , false ) .unwrap ( ) ;
1394
assert! (
1395
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " may not update" ) ,
1396
" an expired certificate: {r:?}"
1397
) ;
1398
let other = agent ( " other" , " agent:mallory@ada" , now - 60_000 , now + 600_000 ) ;
1399
let r = other.push ( " origin" , false ) .unwrap ( ) ;
1400
assert! (
1401
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " may not update" ) ,
1402
" a subject not granted: {r:?}"
1403
) ;
1404
}
1405
d8524a9f Andy McCall 18 days ago ↶
1406
1407
1408
1409
1410
# [ test ]
1411
fn a_tag_signed_by_an_earlier_short_lived_key_is_pushed ( ) {
1412
use crate :: cert:: { Certificate, SignedCertificate} ;
1413
use crate :: identity:: Scope;
1414
hermetic ( ) ;
1415
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1416
let root = dir.path ( ) ;
1417
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1418
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1419
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1420
.unwrap ( )
1421
.with_home ( root.join ( " home-a" ) ) ;
1422
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1423
.unwrap ( ) ;
1424
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1425
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1426
a.policy_init ( ) .unwrap ( ) ;
1427
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1428
let hub_sk = SecretKey:: generate( ) ;
1429
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1430
a.policy_grant_subject (
1431
" agent:claude@ada" ,
1432
hub.clone ( ) ,
1433
Scope:: CHANGES | Scope:: REFS ,
1434
Some ( " *" ) ,
1435
)
1436
.unwrap ( ) ;
1437
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1438
1439
let now = Hlc:: now( ) .wall_ms;
1440
let mut agent = Repo:: clone( & url, & root.join ( " agent" ) )
1441
.unwrap ( )
1442
.with_home ( root.join ( " home-agent" ) ) ;
1443
agent.store ( ) .put_identity ( & hub) .unwrap ( ) ;
1444
let certify = | agent : & Repo, id | {
1445
let cert = SignedCertificate:: sign(
1446
Certificate {
1447
key: id,
1448
issuer: hub.hash ( ) ,
1449
subject: " agent:claude@ada" .into ( ) ,
1450
claims: vec! [ ] ,
1451
scope: Scope:: CHANGES | Scope:: REFS ,
1452
not_before: now - 60_000 ,
1453
expires: now + 600_000 ,
1454
grant: None ,
1455
} ,
1456
& hub,
1457
& hub_sk,
1458
) ;
1459
agent.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1460
} ;
1461
1462
let ( first, first_id) = agent
1463
.create_identity ( " claude" , " [email protected] " , IdentityScope:: User)
1464
.unwrap ( ) ;
1465
certify ( & agent, first_id) ;
1466
let heads = agent.bookmark_heads ( ) .unwrap ( ) ;
1467
agent.tag_create ( " v1" , & heads, " first" ) .unwrap ( ) ;
1468
1469
let ( second, second_id) = agent
1470
.create_identity ( " claude" , " [email protected] " , IdentityScope:: User)
1471
.unwrap ( ) ;
1472
assert_ne! ( first, second) ;
1473
certify ( & agent, second_id) ;
1474
agent
1475
.set_user_config ( " author.signer" , & second.to_hex ( ) )
1476
.unwrap ( ) ;
1477
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
1ec6fdbc Andy McCall 18 days ago ↶
1478
assert! (
1479
host.store ( ) .get_identity ( & first) .is_err ( ) ,
1480
" the host has never seen the tag's key"
1481
) ;
d8524a9f Andy McCall 18 days ago ↶
1482
let r = agent.push ( " origin" , false ) .unwrap ( ) ;
1ec6fdbc Andy McCall 18 days ago ↶
1483
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
1484
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
1485
assert! (
1486
host.tag_refs ( ) .unwrap ( ) .iter ( ) .any ( | t | t.name = = " v1" ) ,
1487
" the tag arrived"
1488
) ;
1489
}
1490
1491
1492
1493
1494
1495
1496
1497
# [ test ]
1498
fn a_short_lived_key_s_tag_is_fetched_at_its_issuer_s_acceptance ( ) {
1499
use crate :: cert:: { Certificate, SignedCertificate} ;
1500
use crate :: identity:: Scope;
1501
hermetic ( ) ;
1502
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1503
let root = dir.path ( ) ;
1504
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1505
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1506
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1507
.unwrap ( )
1508
.with_home ( root.join ( " home-a" ) ) ;
1509
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1510
.unwrap ( ) ;
1511
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1512
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1513
a.policy_init ( ) .unwrap ( ) ;
1514
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1515
let hub_sk = SecretKey:: generate( ) ;
1516
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1517
a.policy_grant_subject (
1518
" agent:claude@ada" ,
1519
hub.clone ( ) ,
1520
Scope:: CHANGES | Scope:: REFS ,
1521
Some ( " *" ) ,
1522
)
1523
.unwrap ( ) ;
1524
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1525
1526
let now = Hlc:: now( ) .wall_ms;
1527
1528
let agent = | name : & str , from : u64 , to : u64 | {
1529
let mut r = Repo:: clone( & url, & root.join ( name) )
1530
.unwrap ( )
1531
.with_home ( root.join ( format! ( " home-{name} " ) ) ) ;
1532
r.store ( ) .put_identity ( & hub) .unwrap ( ) ;
1533
let ( _ , id) = r
1534
.create_identity ( name, " [email protected] " , IdentityScope:: User)
1535
.unwrap ( ) ;
1536
let cert = SignedCertificate:: sign(
1537
Certificate {
1538
key: id,
1539
issuer: hub.hash ( ) ,
1540
subject: " agent:claude@ada" .into ( ) ,
1541
claims: vec! [ ] ,
1542
scope: Scope:: CHANGES | Scope:: REFS ,
1543
not_before: from,
1544
expires: to,
1545
grant: None ,
1546
} ,
1547
& hub,
1548
& hub_sk,
1549
) ;
1550
r.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1551
r
1552
} ;
1553
1554
1555
1556
1557
let claude = agent ( " claude" , now - 60_000 , now + 600_000 ) ;
1558
let heads = claude.bookmark_heads ( ) .unwrap ( ) ;
1559
claude.tag_create ( " v1" , & heads, " one" ) .unwrap ( ) ;
1560
let r = claude.push ( " origin" , false ) .unwrap ( ) ;
d8524a9f Andy McCall 18 days ago ↶
1561
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
1ec6fdbc Andy McCall 18 days ago ↶
1562
let b = Repo:: clone( & url, & root.join ( " b" ) )
1563
.unwrap ( )
1564
.with_home ( root.join ( " home-b" ) ) ;
1565
let rep = b.fetch ( " origin" ) .unwrap ( ) ;
1566
assert! (
1567
rep.skipped
1568
.iter ( )
1569
.any ( | ( n , why ) | n = = " tags/v1" & & why.contains ( " no time to check its certificate at" ) ) ,
1570
" {rep:?}"
1571
) ;
1572
assert_eq! ( b.tag_heads ( " v1" ) .unwrap ( ) , None ) ;
1573
1574
1575
1576
let host = Repo:: open( & root.join ( " origin" ) )
1577
.unwrap ( )
1578
.with_acceptor ( hub.clone ( ) , hub_sk.clone ( ) ) ;
1579
let ( v1, _ ) = host.tag_attest ( " v1" ) .unwrap ( ) .unwrap ( ) ;
1580
host.record_acceptance ( & v1) .unwrap ( ) ;
1581
let served = host.tag_refs ( ) .unwrap ( ) ;
1582
assert! ( served
1583
.iter ( )
1584
.any ( | t | t.name = = " v1" & & t.accepted.is_some ( ) ) ) ;
1585
let rep = b.fetch ( " origin" ) .unwrap ( ) ;
1586
assert! ( rep.skipped.is_empty ( ) , " {rep:?}" ) ;
1587
assert_eq! ( b.tag_heads ( " v1" ) .unwrap ( ) , Some ( heads.clone ( ) ) ) ;
1588
1589
host.gc ( true ) .unwrap ( ) ;
1590
assert! ( host.acceptance_of ( & v1) .unwrap ( ) .is_some ( ) ) ;
1591
1592
1593
1594
let late = agent ( " late" , now - 7_200_000 , now - 3_600_000 ) ;
1595
late.tag_create ( " v2" , & heads, " two" ) .unwrap ( ) ;
1596
let r = late.push ( " origin" , false ) .unwrap ( ) ;
1597
assert! (
1598
r.iter ( )
1599
.any ( | x | x.name = = " tags/v2" & & ! x.ok & & x.reason.contains ( " may not create" ) ) ,
1600
" {r:?}"
1601
) ;
1602
1603
1604
1605
let ( v2, _ ) = late.tag_attest ( " v2" ) .unwrap ( ) .unwrap ( ) ;
1606
let acc = Attest {
1607
repo: late.id ( ) .unwrap ( ) ,
1608
hlc: Hlc {
1609
wall_ms: now - 5_400_000 ,
1610
logical: 0 ,
1611
} ,
1612
body: AttestBody:: Accepted { attest: v2 } ,
1613
} ;
1614
let sig = hub_sk.sign ( Domain:: Attest, & hub.hash ( ) , & acc.hash ( ) ) ;
1615
1616
let stranger_sk = SecretKey:: generate( ) ;
1617
let stranger = stranger_sk.identity ( " stranger" , " [email protected] " , 1 ) ;
1618
let forged = stranger_sk.sign ( Domain:: Attest, & stranger.hash ( ) , & acc.hash ( ) ) ;
1619
late.keep_acceptance (
1620
& v2,
1621
& SignedAttest {
1622
attest: acc.clone ( ) ,
1623
sigs: vec! [ forged] ,
1624
} ,
1625
)
1626
.unwrap ( ) ;
1627
let r = late.push ( " origin" , false ) .unwrap ( ) ;
1628
assert! (
1629
r.iter ( ) .any ( | x | x.name = = " tags/v2" & & ! x.ok ) ,
1630
" not the issuer's: {r:?}"
1631
) ;
1632
let theirs = SignedAttest {
1633
attest: acc,
1634
sigs: vec! [ sig] ,
1635
} ;
1636
1637
late.keep_acceptance ( & v2, & theirs) .unwrap ( ) ;
1638
let r = late.push ( " origin" , false ) .unwrap ( ) ;
1639
1640
1641
assert! ( r.iter ( ) .any ( | x | x.name = = " tags/v2" & & x.ok ) , " {r:?}" ) ;
d8524a9f Andy McCall 18 days ago ↶
1642
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
1ec6fdbc Andy McCall 18 days ago ↶
1643
let ( hv2, _ ) = host.tag_attest ( " v2" ) .unwrap ( ) .unwrap ( ) ;
1644
assert_eq! ( hv2, v2) ;
1645
assert! ( host
1646
.store ( )
1647
.index_get_pub ( " accepted" , & v2)
1648
.unwrap ( )
1649
.contains ( & theirs.attest.hash ( ) ) ) ;
1650
let c = Repo:: clone( & url, & root.join ( " c" ) )
1651
.unwrap ( )
1652
.with_home ( root.join ( " home-c" ) ) ;
1653
assert_eq! ( c.tag_heads ( " v2" ) .unwrap ( ) , Some ( heads.clone ( ) ) ) ;
1654
assert_eq! ( c.tag_heads ( " v1" ) .unwrap ( ) , Some ( heads) ) ;
d8524a9f Andy McCall 18 days ago ↶
1655
}
1656
d36ba2f7 Andy McCall 18 days ago ↶
1657
1658
1659
1660
1661
1662
# [ test ]
1663
fn a_bookmark_moved_by_a_certified_key_alone_is_fetched ( ) {
1664
use crate :: cert:: { Certificate, SignedCertificate} ;
1665
use crate :: identity:: Scope;
1666
hermetic ( ) ;
1667
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1668
let root = dir.path ( ) ;
1669
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1670
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1671
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1672
.unwrap ( )
1673
.with_home ( root.join ( " home-a" ) ) ;
1674
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1675
.unwrap ( ) ;
1676
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1677
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1678
a.policy_init ( ) .unwrap ( ) ;
1679
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1680
let hub_sk = SecretKey:: generate( ) ;
1681
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1682
a.policy_grant_subject (
1683
" agent:claude@ada" ,
1684
hub.clone ( ) ,
1685
Scope:: CHANGES | Scope:: REFS ,
1686
None ,
1687
)
1688
.unwrap ( ) ;
1689
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1690
1691
let now = Hlc:: now( ) .wall_ms;
1692
let mut agent = Repo:: clone( & url, & root.join ( " agent" ) )
1693
.unwrap ( )
1694
.with_home ( root.join ( " home-agent" ) ) ;
1695
let ( key, id) = agent
1696
.create_identity ( " claude" , " [email protected] " , IdentityScope:: User)
1697
.unwrap ( ) ;
1698
let cert = SignedCertificate:: sign(
1699
Certificate {
1700
key: id,
1701
issuer: hub.hash ( ) ,
1702
subject: " agent:claude@ada" .into ( ) ,
1703
claims: vec! [ ] ,
1704
scope: Scope:: CHANGES | Scope:: REFS ,
1705
not_before: now - 60_000 ,
1706
expires: now + 600_000 ,
1707
grant: None ,
1708
} ,
1709
& hub,
1710
& hub_sk,
1711
) ;
1712
agent.store ( ) .put_identity ( & hub) .unwrap ( ) ;
1713
agent.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1714
let heads = agent.bookmark_heads ( ) .unwrap ( ) ;
1715
agent.bookmark_create ( " agent-work" , Some ( & heads) ) .unwrap ( ) ;
1716
let r = agent.push_bookmark ( " origin" , " agent-work" , false ) .unwrap ( ) ;
1717
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
1718
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
1719
let ( _ , sa) = host.latest_ref_attest ( " agent-work" ) .unwrap ( ) .unwrap ( ) ;
1720
assert_eq! ( sa.sigs[ 0 ] .key, key, " the certified key alone moved it" ) ;
1721
1722
1723
let bundle = host.bundle_raw_for ( & [ ] , None ) .unwrap ( ) ;
1724
assert! ( bundle.changes.is_empty ( ) & & bundle.raw_changes.is_empty ( ) ) ;
1725
assert_eq! ( bundle.certificates.len ( ) , 1 ) ;
1726
for k in [ key, hub.hash ( ) ] {
1727
assert! ( bundle
1728
.objects
1729
.iter ( )
1730
.any ( | ( t , b ) | * t = = ObjectType:: Identity & & hash_object ( * t, b) = = k) ) ;
1731
}
1732
1733
let c = Repo:: clone( & url, & root.join ( " c" ) ) .unwrap ( ) ;
1734
let tracked = c.view ( ) .unwrap ( ) .remotes[ " origin" ] .clone ( ) ;
1735
assert_eq! (
1736
tracked.get ( " agent-work" ) ,
1737
host.view ( ) .unwrap ( ) .bookmarks.get ( " agent-work" )
1738
) ;
1739
assert! ( c.store ( ) .certificate_of ( & key) .unwrap ( ) .is_some ( ) ) ;
1740
}
1741
028545df Andy McCall 19 days ago ↶
1742
1743
1744
1745
1746
1747
1748
# [ test ]
1749
fn a_fetched_attestation_is_judged_at_its_integrated_time ( ) {
1750
use super :: transparency:: LogCheck;
1751
use crate :: cert:: { Certificate, SignedCertificate} ;
1752
use crate :: identity:: Scope;
1753
use crate :: protocol:: { Integration, Logged, RefsResponse, RemoteRef} ;
1754
use crate :: tlog:: { self , Checkpoint, Entry, Hash, Integrated, Note} ;
1755
hermetic ( ) ;
1756
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1757
let root = dir.path ( ) ;
1758
let mut a = Repo:: init( & root.join ( " a" ) )
1759
.unwrap ( )
1760
.with_home ( root.join ( " home-a" ) ) ;
1761
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1762
.unwrap ( ) ;
1763
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1764
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1765
a.policy_init ( ) .unwrap ( ) ;
1766
let issuer_sk = SecretKey:: generate( ) ;
1767
let issuer = issuer_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1768
a.policy_grant_subject (
1769
" agent:claude@ada" ,
1770
issuer.clone ( ) ,
1771
Scope:: CHANGES | Scope:: REFS ,
1772
None ,
1773
)
1774
.unwrap ( ) ;
1775
let p = a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy;
1776
1777
1778
let now = Hlc:: now( ) .wall_ms;
1779
let t0 = now - 2 * 3_600_000 ;
1780
let agent_sk = SecretKey:: generate( ) ;
1781
let agent = agent_sk.identity ( " claude" , " [email protected] " , t0) ;
1782
let cert = SignedCertificate:: sign(
1783
Certificate {
1784
key: agent.clone ( ) ,
1785
issuer: issuer.hash ( ) ,
1786
subject: " agent:claude@ada" .into ( ) ,
1787
claims: vec! [ ] ,
1788
scope: Scope:: CHANGES | Scope:: REFS ,
1789
not_before: t0,
1790
expires: t0 + 600_000 ,
1791
grant: None ,
1792
} ,
1793
& issuer,
1794
& issuer_sk,
1795
) ;
1796
a.store ( ) .put_identity ( & agent) .unwrap ( ) ;
1797
a.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1798
let signer = agent.hash ( ) ;
1799
let attested = | claimed : u64 | {
1800
let attest = Attest {
1801
repo: a.id ( ) .unwrap ( ) ,
1802
hlc: Hlc {
1803
wall_ms: claimed,
1804
logical: 0 ,
1805
} ,
1806
body: AttestBody:: Ref {
1807
name: " main" .into ( ) ,
1808
old_state: ObjectRef( [ 1 ; 32 ] ) ,
1809
new_state: ObjectRef( [ 2 ; 32 ] ) ,
1810
serial: 1 ,
1811
} ,
1812
} ;
1813
let sig = agent_sk.sign ( Domain:: Attest, & signer, & attest.hash ( ) ) ;
1814
SignedAttest {
1815
attest,
1816
sigs: vec! [ sig] ,
1817
}
1818
} ;
1819
1820
1821
1822
let log_sk = SecretKey:: generate( ) ;
1823
let log_key = log_sk.identity ( " hub.example" , " [email protected] " , 1 ) ;
1824
let origin = " hub.example/log" ;
1825
let hub = " https://hub.example" ;
1826
let url = " https://hub.example/ada/a" ;
1827
let w_sk = SecretKey:: generate( ) ;
1828
let w = w_sk.identity ( " w" , " [email protected] " , 1 ) ;
1829
let stranger_sk = SecretKey:: generate( ) ;
1830
let stranger = stranger_sk.identity ( " s" , " [email protected] " , 1 ) ;
1831
let sa = attested ( t0 + 300_000 ) ;
1832
let entry_of = | i : u64 | {
1833
let ( kind, object) = if i = = 3 {
1834
( " ref" , sa.attest.hash ( ) )
1835
} else {
1836
( " seal" , ObjectRef( [ i as u8 ; 32 ] ) )
1837
} ;
1838
Entry {
1839
kind: kind.into ( ) ,
1840
repo: sa.attest.repo,
1841
object,
1842
signers: vec! [ signer] ,
1843
received: t0 + 300_000 ,
1844
}
1845
.encode ( )
1846
} ;
1847
let mut stored: Vec< Hash> = Vec :: new( ) ;
1848
for i in 0 .. 8 {
1849
let add = tlog:: stored_hashes_for_leaf( i, tlog:: leaf_hash( & entry_of ( i) ) , & stored) .unwrap ( ) ;
1850
stored.extend ( add) ;
1851
}
1852
let note_of = | n : u64 , cosigners : & [( & str , & Identity, & SecretKey, u64 ) ]| {
1853
let mut note = Checkpoint {
1854
origin: origin.into ( ) ,
1855
size: n,
1856
root: tlog:: tree_hash( n, & stored) .unwrap ( ) ,
1857
extensions: vec! [ ] ,
1858
}
1859
.sign ( & log_key, & log_sk)
1860
.unwrap ( ) ;
1861
for ( name, id, sk, t) in cosigners {
1862
note.sigs
1863
.extend ( tlog:: cosign( & note.text, name, id, sk, * t) .unwrap ( ) ) ;
1864
}
1865
note.encode ( )
1866
} ;
1867
let latest = note_of ( 8 , & [ ] ) ;
1868
let witnessed_at = ( t0 + 360_000 ) / 1000 ;
1869
let cosigned = note_of (
1870
4 ,
1871
& [
1872
( " w.example" , & w, & w_sk, witnessed_at) ,
1873
( " s.example" , & stranger, & stranger_sk, 1 ) ,
1874
] ,
1875
) ;
1876
let answer = | integration : Vec< Integration> , witnesses : Vec< ( String, Vec< u8 > ) > | RefsResponse {
1877
refs: vec! [ RemoteRef {
1878
name: " main" .into ( ) ,
1879
state: ObjectRef( [ 2 ; 32 ] ) ,
1880
heads: vec! [ ] ,
1881
attestation: Some ( sa.clone ( ) ) ,
1882
last_serial: None ,
1883
accepted: None ,
1884
logged: Some ( Logged {
1885
index: 3 ,
1886
entry: entry_of ( 3 ) ,
1887
proof: tlog:: prove_inclusion( 8 , 3 , & stored) .unwrap ( ) ,
1888
} ) ,
1889
integration,
1890
} ] ,
1891
log: Some ( latest.clone ( ) ) ,
1892
witnesses,
1893
.. RefsResponse:: default( )
1894
} ;
1895
let proof4 = tlog:: prove_inclusion( 4 , 3 , & stored) .unwrap ( ) ;
1896
let served = vec! [ Integration {
1897
note: cosigned.clone ( ) ,
1898
proof: proof4.clone ( ) ,
1899
} ] ;
1900
let listed = vec! [ ( " w.example" .to_string ( ) , w.encode ( ) ) ] ;
1901
1902
a.remember_log ( hub, & log_key, & Note:: parse( & latest) .unwrap ( ) )
1903
.unwrap ( ) ;
1904
1905
1906
1907
let resp = answer ( served.clone ( ) , listed.clone ( ) ) ;
1908
let lc = a.check_log ( url, None , & resp) .unwrap ( ) ;
1909
assert! ( lc.notes.is_empty ( ) , " {:?}" , lc.notes) ;
1910
let t = lc.time_of ( " main" ) .unwrap ( ) ;
1911
assert_eq! ( ( t.time, t.witness.as_str ( ) ) , ( witnessed_at, " w.example" ) ) ;
1912
1913
1914
assert! ( cert.cert.expires < now) ;
1915
let mut notes = Vec :: new( ) ;
1916
a.authorize_in_time ( & p, " origin" , & resp.refs[ 0 ] , & sa, & signer, & lc, & mut notes)
1917
.unwrap ( ) ;
1918
assert! ( notes.is_empty ( ) , " {notes:?}" ) ;
1919
1920
1921
1922
let lonely = vec! [ Integration {
1923
note: note_of ( 4 , & [ ( " s.example" , & stranger, & stranger_sk, 1 ) ] ) ,
1924
proof: proof4.clone ( ) ,
1925
} ] ;
1926
let resp = answer ( lonely, vec! [ ] ) ;
1927
let lc = a.check_log ( url, None , & resp) .unwrap ( ) ;
1928
assert! ( lc
1929
.time_of ( " main" )
1930
.unwrap_err ( )
1931
.contains ( " no witness this user trusts" ) ) ;
1932
let mut notes = Vec :: new( ) ;
1933
a.authorize_in_time ( & p, " origin" , & resp.refs[ 0 ] , & sa, & signer, & lc, & mut notes)
1934
.unwrap ( ) ;
1935
assert! (
1936
notes.len ( ) = = 1 & & notes[ 0 ] .contains ( " not at a witnessed time" ) ,
1937
" {notes:?}"
1938
) ;
1939
1940
1941
1942
let resp = answer (
1943
served.clone ( ) ,
1944
vec! [ ( " w.example" .into ( ) , stranger.encode ( ) ) ] ,
1945
) ;
1946
let lc = a.check_log ( url, None , & resp) .unwrap ( ) ;
1947
assert! (
1948
lc.notes.iter ( ) .any ( | n | n.contains ( " another key" ) ) ,
1949
" {:?}" ,
1950
lc.notes
1951
) ;
1952
assert! (
1953
lc.time_of ( " main" ) .is_err ( ) ,
1954
" w's cosignature is by its old key"
1955
) ;
1956
a.check_log ( url, None , & answer ( served.clone ( ) , listed.clone ( ) ) )
1957
.unwrap ( ) ;
1958
1959
1960
1961
let mut old = answer ( vec! [ ] , vec! [ ] ) ;
1962
old.log = None ;
1963
old.refs[ 0 ] .logged = None ;
1964
let lc = a.check_log ( url, None , & old) .unwrap ( ) ;
1965
assert! ( ! lc.kept & & lc.notes.is_empty ( ) ) ;
1966
let mut notes = Vec :: new( ) ;
1967
a.authorize_in_time ( & p, " origin" , & old.refs[ 0 ] , & sa, & signer, & lc, & mut notes)
1968
.unwrap ( ) ;
1969
assert! ( notes.is_empty ( ) , " {notes:?}" ) ;
1970
1971
1972
let at = | secs_after_t0 : u64 | {
1973
let mut times = std:: collections:: HashMap:: new( ) ;
1974
times.insert (
1975
" main" .to_string ( ) ,
1976
Ok ( Integrated {
1977
time: ( t0 + secs_after_t0 * 1000 ) / 1000 ,
1978
witness: " w.example" .into ( ) ,
1979
size: 4 ,
1980
} ) ,
1981
) ;
1982
LogCheck {
1983
kept: true ,
1984
times,
1985
.. LogCheck:: default( )
1986
}
1987
} ;
1988
let judged = | sa : & SignedAttest, lc : & LogCheck| {
1989
let mut notes = Vec :: new( ) ;
1990
a.authorize_in_time ( & p, " origin" , & old.refs[ 0 ] , sa, & signer, lc, & mut notes)
1991
.map ( | _| notes )
1992
} ;
1993
1994
1995
1996
let notes = judged ( & sa, & at ( 3600 ) ) .unwrap ( ) ;
1997
assert! (
1998
notes
1999
.iter ( )
2000
.any ( | n | n.contains ( " had lapsed when witness w.example" ) )
2001
& & notes
2002
.iter ( )
2003
.any ( | n | n.contains ( " 55 minutes before witness" ) ) ,
2004
" {notes:?}"
2005
) ;
2006
2007
2008
2009
let fast = attested ( now) ;
2010
let notes = judged ( & fast, & at ( 400 ) ) .unwrap ( ) ;
2011
assert! (
2012
notes.len ( ) = = 1 & & notes[ 0 ] .contains ( " after witness w.example saw it" ) ,
2013
" {notes:?}"
2014
) ;
2015
2016
assert! ( judged ( & fast, & at ( 3600 ) ) .is_err ( ) ) ;
2017
2018
assert! ( judged ( & sa, & at ( 500 ) ) .unwrap ( ) .is_empty ( ) ) ;
2019
}
2020
a2387d54 Andy McCall 20 days ago ↶
2021
2022
2023
2024
45a6f5ec Andy McCall 13 days ago ↶
2025
2026
2027
f0a517d2 Andy McCall 20 days ago ↶
2028
# [ test ]
45a6f5ec Andy McCall 13 days ago ↶
2029
fn a_grant_outlives_its_owner_s_rotation ( ) {
2030
use crate :: cert:: { Certificate, Grant, SignedCertificate, SignedGrant} ;
2031
use crate :: identity:: Scope;
2032
hermetic ( ) ;
2033
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2034
let root = dir.path ( ) ;
2035
let mut a = Repo:: init( & root.join ( " a" ) )
2036
.unwrap ( )
2037
.with_home ( root.join ( " home-a" ) ) ;
2038
let ( old, _ ) = a
2039
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
2040
.unwrap ( ) ;
2041
let ( _ , old_sk) = a.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
2042
let old_id = a.identity ( & old) .unwrap ( ) ;
2043
let hub_sk = SecretKey:: generate( ) ;
2044
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
2045
let now = Hlc:: now( ) .wall_ms;
2046
let grant = | from : u64 | {
2047
SignedGrant:: sign(
2048
Grant {
2049
grantor: old,
2050
subject: " agent:claude@ada" .into ( ) ,
2051
issuer: hub.hash ( ) ,
2052
scope: Scope:: CHANGES | Scope:: SEAL ,
2053
not_before: from,
2054
expires: now + 86_400_000 ,
2055
} ,
2056
& old_id,
2057
& old_sk,
2058
)
2059
} ;
2060
let before = grant ( now - 60_000 ) ;
2061
let after = grant ( now + 60_000 ) ;
2062
let rot = a.renew_identity ( None ) .unwrap ( ) ;
2063
assert_ne! ( rot.new, old) ;
2064
let agent = | name : & str , g : SignedGrant| {
2065
let mut r = Repo:: init( & root.join ( name) )
2066
.unwrap ( )
2067
.with_home ( root.join ( format! ( " home-{name} " ) ) ) ;
2068
let ( key, id) = r
2069
.create_identity ( name, " [email protected] " , IdentityScope:: User)
2070
.unwrap ( ) ;
2071
for i in [ & old_id, & rot.identity, & hub] {
2072
r.store ( ) .put_identity ( i) .unwrap ( ) ;
2073
}
2074
r.set_user_config ( " author.key" , & rot.new.to_hex ( ) ) .unwrap ( ) ;
2075
r.set_user_config ( " author.signer" , & key.to_hex ( ) ) .unwrap ( ) ;
2076
let cert = SignedCertificate:: sign(
2077
Certificate {
2078
key: id,
2079
issuer: hub.hash ( ) ,
2080
subject: " agent:claude@ada" .into ( ) ,
2081
claims: vec! [ ] ,
2082
scope: Scope:: CHANGES | Scope:: REFS | Scope:: SEAL ,
2083
not_before: now - 60_000 ,
2084
expires: now + 600_000 ,
2085
grant: Some ( g) ,
2086
} ,
2087
& hub,
2088
& hub_sk,
2089
) ;
2090
r.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
2091
write ( & root.join ( name) , " work.txt" , b " agent work\n " ) ;
2092
let h = r.commit ( " work" , & [ ] ) .unwrap ( ) ;
2093
assert_eq! ( r.store ( ) .get_change_meta ( & h) .unwrap ( ) .author, rot.new) ;
2094
r.seal ( & [ h] ) .unwrap ( ) ;
2095
( r, h)
2096
} ;
2097
let ( r, h) = agent ( " kept" , before) ;
2098
assert! (
2099
matches! (
2100
r.authenticate_change ( & h) .unwrap ( ) ,
2101
SignatureStatus:: Valid { .. }
2102
) ,
2103
" {:?}" ,
2104
r.authenticate_change ( & h) .unwrap ( )
2105
) ;
2106
let ( r, h) = agent ( " late" , after) ;
2107
assert! (
2108
matches! ( r.authenticate_change ( & h) .unwrap ( ) , SignatureStatus:: Invalid( e) if e.contains ( " neither" ) ) ,
2109
" a grant the old key made after the rotation: {:?}" ,
2110
r.authenticate_change ( & h) .unwrap ( )
2111
) ;
2112
}
2113
2114
# [ test ]
a2387d54 Andy McCall 20 days ago ↶
2115
fn a_certified_key_acts_for_its_author_under_a_grant ( ) {
2116
use crate :: cert:: { Certificate, Grant, SignedCertificate, SignedGrant} ;
2117
use crate :: identity:: { Identity, Scope} ;
2118
hermetic ( ) ;
2119
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2120
let root = dir.path ( ) ;
2121
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
2122
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
2123
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
2124
.unwrap ( )
2125
.with_home ( root.join ( " home-a" ) ) ;
2126
let ( ada, _ ) = a
2127
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
2128
.unwrap ( ) ;
2129
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
2130
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
2131
a.policy_init ( ) .unwrap ( ) ;
2132
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
2133
let hub_sk = SecretKey:: generate( ) ;
2134
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
2135
a.policy_grant_subject (
2136
" agent:claude@ada" ,
2137
hub.clone ( ) ,
2138
Scope:: CHANGES | Scope:: REFS ,
2139
None ,
2140
)
2141
.unwrap ( ) ;
2142
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
2143
let ( _ , ada_sk) = a.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
2144
let ada_id = a.identity ( & ada) .unwrap ( ) ;
2145
2146
let now = Hlc:: now( ) .wall_ms;
2147
let grant = | by : & Identity, sk : & SecretKey, from : u64 , to : u64 | {
2148
SignedGrant:: sign(
2149
Grant {
2150
grantor: by.hash ( ) ,
2151
subject: " agent:claude@ada" .into ( ) ,
2152
issuer: hub.hash ( ) ,
2153
scope: Scope:: CHANGES | Scope:: SEAL ,
2154
not_before: from,
2155
expires: to,
2156
} ,
2157
by,
2158
sk,
2159
)
2160
} ;
2161
2162
2163
let agent = | name : & str , g : Option< SignedGrant> | {
2164
let mut r = Repo:: clone( & url, & root.join ( name) )
2165
.unwrap ( )
2166
.with_home ( root.join ( format! ( " home-{name} " ) ) ) ;
2167
let ( key, id) = r
2168
.create_identity ( name, " [email protected] " , IdentityScope:: User)
2169
.unwrap ( ) ;
2170
r.set_user_config ( " author.key" , & ada.to_hex ( ) ) .unwrap ( ) ;
2171
r.set_user_config ( " author.signer" , & key.to_hex ( ) ) .unwrap ( ) ;
2172
let cert = SignedCertificate:: sign(
2173
Certificate {
2174
key: id,
2175
issuer: hub.hash ( ) ,
2176
subject: " agent:claude@ada" .into ( ) ,
2177
claims: vec! [ ] ,
2178
scope: Scope:: CHANGES | Scope:: REFS | Scope:: SEAL ,
2179
not_before: now - 60_000 ,
2180
expires: now + 600_000 ,
2181
grant: g,
2182
} ,
2183
& hub,
2184
& hub_sk,
2185
) ;
2186
r.store ( ) .put_identity ( & hub) .unwrap ( ) ;
2187
r.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
2188
write ( & root.join ( name) , & format! ( " {name} .txt" ) , b " agent work\n " ) ;
2189
let h = r.commit ( " work" , & [ ] ) .unwrap ( ) ;
2190
assert_eq! ( r.store ( ) .get_change_meta ( & h) .unwrap ( ) .author, ada) ;
2191
( r, key, h)
2192
} ;
2193
2194
let ( claude, key, h) = agent (
2195
" claude" ,
2196
Some ( grant ( & ada_id, & ada_sk, now - 60_000 , now + 86_400_000 ) ) ,
2197
) ;
2198
let r = claude.push ( " origin" , false ) .unwrap ( ) ;
2199
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
2200
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
68dcf21f Andy McCall 20 days ago ↶
2201
assert! (
2202
matches! (
2203
host.authenticate_change ( & h) .unwrap ( ) ,
2204
SignatureStatus:: Valid { key: k, sealed: true , name, .. } if k = = key & & name = = " agent:claude@ada"
2205
) ,
2206
" named by its certificate's subject"
2207
) ;
a2387d54 Andy McCall 20 days ago ↶
2208
a.pull ( " origin" ) .unwrap ( ) ;
2209
assert! ( matches! (
2210
a.authenticate_change ( & h) .unwrap ( ) ,
2211
SignatureStatus:: Valid { key: k, .. } if k = = key
2212
) ) ;
2213
2214
let ( none, _ , _ ) = agent ( " none" , None ) ;
2215
let r = none.push ( " origin" , false ) .unwrap ( ) ;
2216
assert! ( r.iter ( ) .all ( | x | ! x.ok) , " no grant: {r:?}" ) ;
2217
let mallory_sk = SecretKey:: generate( ) ;
2218
let mallory = mallory_sk.identity ( " Mallory" , " [email protected] " , 1 ) ;
2219
let ( stranger, _ , _ ) = agent (
2220
" stranger" ,
2221
Some ( grant ( & mallory, & mallory_sk, now - 60_000 , now + 600_000 ) ) ,
2222
) ;
2223
let r = stranger.push ( " origin" , false ) .unwrap ( ) ;
2224
assert! ( r.iter ( ) .all ( | x | ! x.ok) , " a stranger's grant: {r:?}" ) ;
2225
let ( ended, _ , eh) = agent (
2226
" ended" ,
2227
Some ( grant ( & ada_id, & ada_sk, now - 7_200_000 , now - 3_600_000 ) ) ,
2228
) ;
2229
let r = ended.push ( " origin" , false ) .unwrap ( ) ;
2230
assert! ( r.iter ( ) .all ( | x | ! x.ok) , " an ended grant: {r:?}" ) ;
2231
assert! ( matches! (
2232
ended.authenticate_change ( & eh) .unwrap ( ) ,
2233
SignatureStatus:: Invalid( e) if e.contains ( " grant did not cover" )
2234
) ) ;
2235
}
2236
2237
# [ test ]
83e0677b Andy McCall 1 month ago ↶
2238
fn trust_policy_governs_pushes_and_fetches ( ) {
2239
hermetic ( ) ;
2240
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2241
let root = dir.path ( ) ;
2242
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
2243
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
2244
2245
75167672 Andy McCall 1 month ago
2246
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
2247
.unwrap ( )
2248
.with_home ( root.join ( " home-a" ) ) ;
2249
let ( key_a, _ ) = a
2250
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
2251
.unwrap ( ) ;
2252
let mut b = Repo:: clone( & url, & root.join ( " b" ) )
2253
.unwrap ( )
2254
.with_home ( root.join ( " home-b" ) ) ;
83e0677b Andy McCall 1 month ago ↶
2255
write ( & root.join ( " b" ) , " b.txt" , b " b\n " ) ;
2256
b.commit ( " unsigned before policy" , & [ ] ) .unwrap ( ) ;
2257
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
2258
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2259
2260
2261
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
2262
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
2263
let sp = a.policy_init ( ) .unwrap ( ) ;
2264
assert_eq! ( sp.policy.serial, 1 ) ;
2265
a.pull ( " origin" ) .unwrap ( ) ;
2266
let results = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
2267
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2268
assert! ( results.iter ( ) .all ( | r | r.ok ) , " {results:?}" ) ;
2269
assert_eq! ( origin.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 1 ) ;
2270
2271
2272
b.pull ( " origin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2273
assert! (
2274
b.current_policy ( ) .unwrap ( ) .is_some ( ) ,
2275
" the policy arrived with the fetch"
2276
) ;
83e0677b Andy McCall 1 month ago ↶
2277
assert! ( b.push ( " origin" , false ) .is_err ( ) ) ;
2278
75167672 Andy McCall 1 month ago
2279
let ( key_b, id_b) = b
2280
.create_identity ( " Bob" , " [email protected] " , IdentityScope:: User)
2281
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2282
write ( & root.join ( " b" ) , " b.txt" , b " b2\n " ) ;
2283
let h_b2 = b.commit ( " b2 as Bob" , & [ ] ) .unwrap ( ) ;
2284
assert_eq! ( b.store ( ) .get_change ( & h_b2) .unwrap ( ) .author, key_b) ;
2285
let r = b.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
2286
origin.refresh ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2287
assert! (
2288
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " may not update" ) ,
2289
" {:?}" ,
2290
r
2291
) ;
83e0677b Andy McCall 1 month ago ↶
2292
2293
2294
let sp = a.policy_grant ( id_b.clone ( ) , None ) .unwrap ( ) ;
2295
assert_eq! ( sp.policy.serial, 2 ) ;
2296
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
2297
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2298
assert! ( r.iter ( ) .any ( | x | x.name = = " policy" & & x.ok ) , " {r:?}" ) ;
2299
75167672 Andy McCall 1 month ago
2300
assert! ( b
2301
.push ( " origin" , false )
2302
.unwrap_or_else ( | e | vec! [ RefResult {
2303
name: " main" .into ( ) ,
2304
ok: false ,
2305
reason: e.to_string ( )
2306
} ] )
2307
.iter ( )
2308
.all ( | x | ! x.ok) ) ;
83e0677b Andy McCall 1 month ago ↶
2309
b.pull ( " origin" ) .unwrap ( ) ;
2310
assert_eq! ( b.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 2 ) ;
2311
let r = b.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
2312
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2313
assert! ( r[ 0 ] .ok, " {:?}" , r) ;
2314
2315
let heads = origin.bookmark_heads ( ) .unwrap ( ) ;
2316
for h in & heads {
2317
let c = origin.store ( ) .get_change ( h) .unwrap ( ) ;
2318
if c.author = = key_b {
75167672 Andy McCall 1 month ago
2319
origin
2320
.change_allowed (
2321
h,
2322
& origin.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy,
2323
" main" ,
2324
)
2325
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2326
}
2327
}
2328
2329
2330
let mut forged = b.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.clone ( ) ;
2331
forged.serial = 3 ;
2332
forged.roots = vec! [ key_b] ;
2333
let fh = forged.hash ( ) ;
2334
let ( _ , sk_b) = b.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2335
let forged = SignedPolicy {
2336
policy: forged,
2337
sigs: vec! [ sk_b.sign ( Domain:: Policy, & key_b, & fh) ] ,
2338
} ;
2339
let r = origin
2340
.receive_push ( Bundle:: default( ) , vec! [ ] , Some ( forged) , vec! [ ] )
2341
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2342
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " root" ) , " {:?}" , r) ;
2343
let stale = a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1 ;
2344
let mut old = stale.clone ( ) ;
2345
old.policy.serial = 1 ;
75167672 Andy McCall 1 month ago
2346
let r = origin
2347
.receive_push ( Bundle:: default( ) , vec! [ ] , Some ( old) , vec! [ ] )
2348
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2349
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " serial" ) , " {:?}" , r) ;
2350
2351
2352
a.pull ( " origin" ) .unwrap ( ) ;
2353
a.policy_revoke ( & key_b) .unwrap ( ) ;
2354
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
2355
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2356
assert! ( r.iter ( ) .any ( | x | x.name = = " policy" & & x.ok ) , " {r:?}" ) ;
2357
b.pull ( " origin" ) .unwrap ( ) ;
2358
write ( & root.join ( " b" ) , " b.txt" , b " b3\n " ) ;
2359
b.commit ( " b3" , & [ ] ) .unwrap ( ) ;
2360
let r = b.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
2361
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2362
assert! ( ! r[ 0 ] .ok, " {:?}" , r) ;
2363
2364
75167672 Andy McCall 1 month ago
2365
let c = Repo:: clone( & url, & root.join ( " c" ) )
2366
.unwrap ( )
2367
.with_home ( root.join ( " home-c" ) ) ;
83e0677b Andy McCall 1 month ago ↶
2368
assert_eq! ( c.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 3 ) ;
75167672 Andy McCall 1 month ago
2369
assert! ( c
2370
.current_policy ( )
2371
.unwrap ( )
2372
.unwrap ( )
2373
.1
2374
.policy
2375
.roots
2376
.contains ( & key_a) ) ;
83e0677b Andy McCall 1 month ago ↶
2377
assert! ( ! c.snapshot ( " status" ) .unwrap ( ) .changed) ;
2378
}
d02e545d Andy McCall 1 month ago ↶
2379
9ae4d15a Andy McCall 1 month ago ↶
2380
d02e545d Andy McCall 1 month ago ↶
2381
# [ test ]
9ae4d15a Andy McCall 1 month ago ↶
2382
fn gc_reports_its_phases ( ) {
2383
hermetic ( ) ;
2384
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2385
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
2386
for i in 0 .. 3 {
2387
write (
2388
dir.path ( ) ,
2389
& format! ( " f{i} .txt" ) ,
2390
format! ( " {i} \n " ) .as_bytes ( ) ,
2391
) ;
2392
repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ;
2393
}
2394
let mut phases = Vec :: new( ) ;
2395
repo.gc_with ( true , & mut | p| phases.push ( p.to_string ( ) ) )
2396
.unwrap ( ) ;
2397
assert_eq! ( phases[ 0 ] , " finding what is reachable" , " {phases:?}" ) ;
2398
assert! (
2399
phases.iter ( ) .any ( | p | p.starts_with ( " pruning" ) ) ,
2400
" {phases:?}"
2401
) ;
2402
assert! (
2403
phases.iter ( ) .any ( | p | p.starts_with ( " packing 0 of " ) ) ,
2404
" {phases:?}"
2405
) ;
2406
assert! (
2407
phases
2408
.iter ( )
2409
.any ( | p | p.starts_with ( " writing the index of " ) ) ,
2410
" {phases:?}"
2411
) ;
2412
}
2413
2414
# [ test ]
d02e545d Andy McCall 1 month ago ↶
2415
fn gc_packs_objects_and_history_still_works ( ) {
2416
hermetic ( ) ;
2417
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2418
let root = dir.path ( ) ;
2419
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
2420
repo.create_identity ( " Ada" , " a@b" , IdentityScope:: User)
2421
.unwrap ( ) ;
d02e545d Andy McCall 1 month ago ↶
2422
repo.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
2423
write ( root, " a.txt" , b " one\n " ) ;
2424
let h1 = repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
2425
write ( root, " a.txt" , b " two\n " ) ;
2426
write ( root, " big.bin" , & vec! [ 0 u8 ; 100_000 ] ) ;
2427
let h2 = repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
ba160f60 Andy McCall 1 month ago ↶
2428
2429
for i in 0 .. 5 {
2430
write ( root, " a.txt" , format! ( " two\n {i} \n " ) .as_bytes ( ) ) ;
2431
repo.snapshot ( " status" ) .unwrap ( ) ;
2432
}
2433
write ( root, " a.txt" , b " two\n " ) ;
2434
repo.snapshot ( " status" ) .unwrap ( ) ;
d02e545d Andy McCall 1 month ago ↶
2435
let before = repo.store ( ) .list ( ) .unwrap ( ) ;
2436
assert! ( repo.store ( ) .loose_count ( ) .unwrap ( ) > 0 ) ;
ba160f60 Andy McCall 1 month ago ↶
2437
2438
2439
let r = repo.gc ( true ) .unwrap ( ) ;
2440
assert_eq! ( r.packed.objects, before.len ( ) ) ;
2441
assert_eq! ( r.pruned.objects, 0 ) ;
d02e545d Andy McCall 1 month ago ↶
2442
assert_eq! ( repo.store ( ) .loose_count ( ) .unwrap ( ) , 0 ) ;
2443
assert_eq! ( repo.store ( ) .list ( ) .unwrap ( ) , before) ;
ba160f60 Andy McCall 1 month ago ↶
2444
2445
2446
2447
repo.config.set ( " core.op-retention" , " 0" ) .unwrap ( ) ;
2448
repo.save_config ( ) .unwrap ( ) ;
21b2aec2 Andy McCall 1 month ago ↶
2449
let mut repo = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
ba160f60 Andy McCall 1 month ago ↶
2450
let r = repo.gc ( true ) .unwrap ( ) ;
2451
assert! ( r.pruned.objects > = 5 , " {r:?}" ) ;
2452
let after = repo.store ( ) .list ( ) .unwrap ( ) ;
2453
assert! ( after.len ( ) < before.len ( ) ) ;
2454
assert! ( after.contains ( & h1) & & after.contains ( & h2) ) ;
2455
for ( _ , op) in repo.op_log ( ) .unwrap ( ) {
2456
assert! ( ! op.command.is_empty ( ) , " operation objects are all kept" ) ;
2457
}
2458
d02e545d Andy McCall 1 month ago ↶
2459
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
75167672 Andy McCall 1 month ago
2460
assert! ( matches! (
2461
repo.verify_change ( & h2) .unwrap ( ) ,
2462
SignatureStatus:: Valid { .. }
2463
) ) ;
d02e545d Andy McCall 1 month ago ↶
2464
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 3 ) ;
2465
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
2466
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n " ) ;
2467
repo.checkout ( & [ h2] , false , " checkout" ) .unwrap ( ) ;
2468
assert_eq! ( fs:: read( root.join ( " big.bin" ) ) .unwrap ( ) .len ( ) , 100_000 ) ;
2469
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
ba160f60 Andy McCall 1 month ago ↶
2470
d02e545d Andy McCall 1 month ago ↶
2471
write ( root, " a.txt" , b " three\n " ) ;
2472
repo.commit ( " three" , & [ ] ) .unwrap ( ) ;
ba160f60 Andy McCall 1 month ago ↶
2473
let r = repo.gc ( false ) .unwrap ( ) ;
2474
assert_eq! ( r.pruned.objects, 0 ) ;
2475
assert! ( r.pruned.deferred > 0 ) ;
2476
let r = repo.gc ( true ) .unwrap ( ) ;
2477
assert! ( r.pruned.objects > 0 ) ;
2478
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 ) ;
21b2aec2 Andy McCall 1 month ago ↶
2479
2480
2481
repo.config.set ( " core.op-retention" , " 90" ) .unwrap ( ) ;
2482
repo.save_config ( ) .unwrap ( ) ;
2483
let repo = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2484
let r = repo.gc ( false ) .unwrap ( ) ;
2485
assert_eq! ( r.pruned.objects, 0 ) ;
2486
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 ) ;
d02e545d Andy McCall 1 month ago ↶
2487
}
614541c3 Andy McCall 1 month ago ↶
2488
2489
# [ test ]
2490
fn describe_amend_abandon_heads ( ) {
2491
hermetic ( ) ;
2492
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2493
let root = dir.path ( ) ;
2494
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
2495
repo.create_identity ( " Ada" , " a@b" , IdentityScope:: User)
2496
.unwrap ( ) ;
614541c3 Andy McCall 1 month ago ↶
2497
repo.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
2498
write ( root, " a.txt" , b " one\n " ) ;
2499
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
2500
write ( root, " a.txt" , b " one\n two\n " ) ;
2501
let h2 = repo.commit ( " secnod" , & [ ] ) .unwrap ( ) ;
2502
let id2 = repo.store ( ) .get_change ( & h2) .unwrap ( ) .change_id;
2503
2504
2505
let h2b = repo.describe ( & h2, " second" ) .unwrap ( ) ;
2506
assert_ne! ( h2b, h2) ;
2507
let c = repo.store ( ) .get_change ( & h2b) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2508
assert_eq! (
2509
( c.change_id, c.message.as_str ( ) , c.deps.clone ( ) ) ,
2510
( id2, " second" , vec! [ h1] )
2511
) ;
614541c3 Andy McCall 1 month ago ↶
2512
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2b] ) ;
75167672 Andy McCall 1 month ago
2513
assert! ( matches! (
2514
repo.verify_change ( & h2b) .unwrap ( ) ,
2515
SignatureStatus:: Valid { .. }
2516
) ) ;
614541c3 Andy McCall 1 month ago ↶
2517
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
2518
assert! ( s.statuses.is_empty ( ) , " {:?}" , s.statuses) ;
75167672 Andy McCall 1 month ago
2519
assert_eq! (
2520
repo.resolve_change ( & id2.short ( 8 ) ) .unwrap ( ) ,
2521
h2b,
2522
" the old object is no longer reachable from the log"
2523
) ;
c07e0e7d Andy McCall 1 month ago ↶
2524
2525
let h1b = repo.describe ( & h1, " first, described" ) .unwrap ( ) ;
2526
assert_ne! ( h1b, h1) ;
2527
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
2528
assert_eq! ( heads.len ( ) , 1 ) ;
2529
let top = repo.store ( ) .get_change ( & heads[ 0 ] ) .unwrap ( ) ;
2530
assert_eq! ( ( top.change_id, top.deps.clone ( ) ) , ( id2, vec! [ h1b] ) ) ;
75167672 Andy McCall 1 month ago
2531
assert! (
2532
matches! (
2533
repo.verify_change ( & heads[ 0 ] ) .unwrap ( ) ,
2534
SignatureStatus:: Valid { .. }
2535
) ,
2536
" rewritten own change is re-signed"
2537
) ;
c07e0e7d Andy McCall 1 month ago ↶
2538
let ( _ , wc) = repo.working_copy ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2539
assert_eq! (
2540
wc.deps,
2541
vec! [ heads[ 0 ] ] ,
2542
" the working copy follows the rewrite"
2543
) ;
c07e0e7d Andy McCall 1 month ago ↶
2544
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
2545
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
2546
assert! (
2547
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n " )
2548
) ;
614541c3 Andy McCall 1 month ago ↶
2549
2550
2551
write ( root, " a.txt" , b " one\n two\n three\n " ) ;
2552
write ( root, " b.txt" , b " b\n " ) ;
2553
let h2c = repo.amend ( None ) .unwrap ( ) ;
2554
let c = repo.store ( ) .get_change ( & h2c) .unwrap ( ) ;
2555
assert_eq! ( c.change_id, id2) ;
2556
assert_eq! ( c.message, " second" ) ;
2557
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2c] ) ;
75167672 Andy McCall 1 month ago
2558
assert_eq! (
2559
repo.log ( ) .unwrap ( ) .len ( ) ,
2560
3 ,
2561
" working copy, amended head, first"
2562
) ;
614541c3 Andy McCall 1 month ago ↶
2563
let snap = repo.load_state ( & [ h2c] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
2564
assert! (
2565
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n three\n " )
2566
) ;
614541c3 Andy McCall 1 month ago ↶
2567
assert! ( snap.files.contains_key ( " b.txt" ) ) ;
2568
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
75167672 Andy McCall 1 month ago
2569
assert! (
2570
repo.amend ( None ) .is_err ( ) ,
2571
" clean working copy and no message"
2572
) ;
614541c3 Andy McCall 1 month ago ↶
2573
let h2d = repo.amend ( Some ( " second, amended" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2574
assert_eq! (
2575
repo.store ( ) .get_change ( & h2d) .unwrap ( ) .message,
2576
" second, amended"
2577
) ;
614541c3 Andy McCall 1 month ago ↶
2578
2579
2580
repo.abandon ( & h2d) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2581
assert_eq! (
2582
repo.bookmark_heads ( ) .unwrap ( ) ,
2583
vec! [ h1b] ,
2584
" the described first change is the head again"
2585
) ;
614541c3 Andy McCall 1 month ago ↶
2586
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2587
let paths: Vec< _ > = s
2588
.statuses
2589
.iter ( )
2590
.map ( | p | ( p.path.as_str ( ) , p.status) )
2591
.collect ( ) ;
2592
assert_eq! (
2593
paths,
2594
vec! [
2595
( " a.txt" , ChangeStatus:: Modified) ,
2596
( " b.txt" , ChangeStatus:: Added)
2597
]
2598
) ;
614541c3 Andy McCall 1 month ago ↶
2599
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n two\n three\n " ) ;
2600
let h3 = repo.commit ( " recommitted" , & [ ] ) .unwrap ( ) ;
2601
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h3] ) ;
2602
2603
repo.undo ( ) .unwrap ( ) ;
2604
repo.undo ( ) .unwrap ( ) ;
2605
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2d] ) ;
c07e0e7d Andy McCall 1 month ago ↶
2606
6706bdcf Andy McCall 1 month ago ↶
2607
2608
2609
c07e0e7d Andy McCall 1 month ago ↶
2610
let base = repo.bookmark_heads ( ) .unwrap ( ) [ 0 ] ;
2611
let base_change = repo.store ( ) .get_change ( & base) .unwrap ( ) ;
2612
let h1_now = base_change.deps[ 0 ] ;
6706bdcf Andy McCall 1 month ago ↶
2613
let notes = repo.abandon ( & h1_now) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2614
assert! (
2615
! notes.is_empty ( ) ,
2616
" the dependent's edit of a.txt was reified"
2617
) ;
6706bdcf Andy McCall 1 month ago ↶
2618
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
2619
let after = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
2620
assert! (
2621
! after.files.contains_key ( " a.txt" ) & & after.files.contains_key ( " b.txt" ) ,
2622
" {:?}" ,
2623
after.files.keys ( )
2624
) ;
6706bdcf Andy McCall 1 month ago ↶
2625
repo.undo ( ) .unwrap ( ) ;
2626
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ base] ) ;
c07e0e7d Andy McCall 1 month ago ↶
2627
write ( root, " c.txt" , b " c\n " ) ;
2628
let hc = repo.commit ( " c" , & [ ] ) .unwrap ( ) ;
2629
write ( root, " d.txt" , b " d\n " ) ;
2630
let hd = repo.commit ( " d" , & [ hc] ) .unwrap ( ) ;
2631
assert_eq! ( repo.store ( ) .get_change ( & hd) .unwrap ( ) .deps, vec! [ hc] ) ;
2632
repo.abandon ( & hc) .unwrap ( ) ;
2633
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
2634
assert! ( ! heads.contains ( & hc) ) ;
75167672 Andy McCall 1 month ago
2635
let d_now = heads
2636
.iter ( )
2637
.find ( | h | repo.store ( ) .get_change ( h) .unwrap ( ) .message = = " d" )
2638
.copied ( )
2639
.unwrap ( ) ;
2640
assert! (
2641
repo.store ( ) .get_change ( & d_now) .unwrap ( ) .deps.is_empty ( ) ,
2642
" d inherited c's (empty) dependencies"
2643
) ;
c07e0e7d Andy McCall 1 month ago ↶
2644
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2645
assert_eq! (
2646
s.statuses
2647
.iter ( )
2648
.map ( | p | p.path.as_str ( ) )
2649
.collect:: < Vec< _ > > ( ) ,
2650
vec! [ " c.txt" ] ,
2651
" c's content is now uncommitted work"
2652
) ;
614541c3 Andy McCall 1 month ago ↶
2653
}
79ab8528 Andy McCall 1 month ago ↶
2654
2655
# [ test ]
2656
fn git_export_mirrors_history ( ) {
2657
hermetic ( ) ;
75167672 Andy McCall 1 month ago
2658
if std:: process:: Command:: new( " git" )
2659
.arg ( " --version" )
2660
.output ( )
2661
.is_err ( )
2662
{
79ab8528 Andy McCall 1 month ago ↶
2663
eprintln! ( " git not installed; skipping" ) ;
2664
return ;
2665
}
2666
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2667
let root = dir.path ( ) ;
75167672 Andy McCall 1 month ago
2668
let mut repo = Repo:: init( & root.join ( " r" ) )
2669
.unwrap ( )
2670
.with_home ( root.join ( " home" ) ) ;
2671
repo.create_identity ( " Ada Lovelace" , " [email protected] " , IdentityScope:: User)
2672
.unwrap ( ) ;
79ab8528 Andy McCall 1 month ago ↶
2673
let r = root.join ( " r" ) ;
2674
write ( & r, " README.md" , b " hello\n " ) ;
2675
write ( & r, " src/main.rs" , b " fn main() {}\n " ) ;
2676
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
2677
write ( & r, " src/main.rs" , b " fn main() { run(); }\n " ) ;
2678
write ( & r, " logo.png" , b " \x89 PNG\0 \0 " ) ;
2679
let h2 = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
2680
2681
let git_dir = root.join ( " mirror.git" ) ;
2682
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
2683
assert_eq! ( ( rep.exported, rep.skipped, rep.heads) , ( 2 , 0 , 1 ) ) ;
2684
let git = | args : & [& str ]| -> String {
75167672 Andy McCall 1 month ago
2685
let out = std:: process:: Command:: new( " git" )
2686
.arg ( " -C" )
2687
.arg ( & git_dir)
2688
.args ( args)
2689
.output ( )
2690
.unwrap ( ) ;
2691
assert! (
2692
out.status.success ( ) ,
2693
" {}" ,
2694
String :: from_utf8_lossy( & out.stderr)
2695
) ;
79ab8528 Andy McCall 1 month ago ↶
2696
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
2697
} ;
2698
assert_eq! ( git ( & [ " log" , " --format=%s" , " main" ] ) , " second\n first" ) ;
2699
assert_eq! ( git ( & [ " show" , " main:src/main.rs" ] ) , " fn main() { run(); }" ) ;
2700
assert_eq! ( git ( & [ " show" , " main:README.md" ] ) , " hello" ) ;
2701
let body = git ( & [ " log" , " -1" , " --format=%B" , " main" ] ) ;
75167672 Andy McCall 1 month ago
2702
assert! (
2703
body.contains ( & format! ( " Oxy-Hash: {} " , h2.to_hex ( ) ) ) ,
2704
" {body}"
2705
) ;
79ab8528 Andy McCall 1 month ago ↶
2706
assert! ( body.contains ( " Change-Id: " ) , " {body}" ) ;
75167672 Andy McCall 1 month ago
2707
assert_eq! (
2708
git ( & [ " log" , " -1" , " --format=%an <%ae>" , " main" ] ) ,
2709
" Ada Lovelace <[email protected] >"
2710
) ;
2711
assert_eq! (
2712
git ( & [ " ls-tree" , " --name-only" , " main" ] ) ,
2713
" README.md\n logo.png\n src"
2714
) ;
79ab8528 Andy McCall 1 month ago ↶
2715
2716
2717
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
2718
assert_eq! ( ( rep.exported, rep.skipped) , ( 0 , 2 ) ) ;
2719
2720
2721
2722
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
2723
write ( & r, " other.txt" , b " o\n " ) ;
2724
let h3 = repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
2725
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
2726
assert_eq! ( ( rep.exported, rep.heads) , ( 1 , 2 ) ) ;
2727
assert_eq! ( git ( & [ " log" , " --format=%s" , " main" ] ) , " third\n second\n first" ) ;
2728
let parents = git ( & [ " rev-list" , " --parents" , " -1" , " main" ] ) ;
2729
assert_eq! ( parents.split ( ' ' ) .count ( ) , 2 , " one parent: {parents}" ) ;
2730
assert_eq! ( git ( & [ " show" , " main:other.txt" ] ) , " o" ) ;
2731
assert_eq! ( git ( & [ " show" , " main:src/main.rs" ] ) , " fn main() { run(); }" ) ;
75167672 Andy McCall 1 month ago
2732
assert_eq! (
2733
git ( & [ " ls-tree" , " --name-only" , " main" ] ) ,
2734
" README.md\n logo.png\n other.txt\n src"
2735
) ;
79ab8528 Andy McCall 1 month ago ↶
2736
let body = git ( & [ " log" , " -1" , " --format=%B" , " main" ] ) ;
75167672 Andy McCall 1 month ago
2737
assert! (
2738
body.contains ( & format! ( " Oxy-Hash: {} " , h3.to_hex ( ) ) ) & & body.contains ( " Oxy-State: " ) ,
2739
" {body}"
2740
) ;
79ab8528 Andy McCall 1 month ago ↶
2741
2742
repo.describe ( & h3, " third, described" ) .unwrap ( ) ;
2743
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
2744
assert_eq! ( rep.exported, 1 ) ;
75167672 Andy McCall 1 month ago
2745
assert_eq! (
2746
git ( & [ " log" , " -1" , " --format=%s" , " main" ] ) ,
2747
" third, described"
2748
) ;
79ab8528 Andy McCall 1 month ago ↶
2749
assert_eq! ( git ( & [ " log" , " --format=%s" , " main" ] ) .lines ( ) .count ( ) , 4 ) ;
2750
let _ = h2;
2751
}
c3069537 Andy McCall 1 month ago ↶
2752
2753
# [ test ]
2754
fn git_import_reproduces_trees ( ) {
c3d2ef61 Andy McCall 1 month ago ↶
2755
git_import_reproduces_trees_with ( None ) ;
2756
}
2757
2758
2759
2760
# [ test ]
2761
fn git_import_reproduces_trees_in_batches ( ) {
2762
git_import_reproduces_trees_with ( Some ( 1 ) ) ;
2763
}
2764
2765
fn git_import_reproduces_trees_with ( batch : Option< usize > ) {
2766
crate :: repo:: git:: set_merge_batch_for_tests( batch) ;
c3069537 Andy McCall 1 month ago ↶
2767
hermetic ( ) ;
75167672 Andy McCall 1 month ago
2768
if std:: process:: Command:: new( " git" )
2769
.arg ( " --version" )
2770
.output ( )
2771
.is_err ( )
2772
{
c3069537 Andy McCall 1 month ago ↶
2773
eprintln! ( " git not installed; skipping" ) ;
2774
return ;
2775
}
2776
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2777
let root = dir.path ( ) ;
2778
let g = root.join ( " src.git" ) ;
2779
fs:: create_dir_all( & g) .unwrap ( ) ;
2780
let git = | args : & [& str ]| -> String {
2781
let out = std:: process:: Command:: new( " git" )
2782
.arg ( " -C" )
2783
.arg ( & g)
75167672 Andy McCall 1 month ago
2784
.args ( [
2785
" -c" ,
2786
" user.name=Grace" ,
2787
" -c" ,
2788
" [email protected] " ,
2789
" -c" ,
2790
" commit.gpgsign=false" ,
2791
] )
c3069537 Andy McCall 1 month ago ↶
2792
.args ( args)
2793
.output ( )
2794
.unwrap ( ) ;
75167672 Andy McCall 1 month ago
2795
assert! (
2796
out.status.success ( ) ,
2797
" git {args:?}: {}" ,
2798
String :: from_utf8_lossy( & out.stderr)
2799
) ;
c3069537 Andy McCall 1 month ago ↶
2800
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
2801
} ;
2802
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
2803
write ( & g, " README.md" , b " # Hi\n " ) ;
2804
write ( & g, " src/lib.rs" , b " pub fn a() {}\n " ) ;
2805
git ( & [ " add" , " ." ] ) ;
2806
git ( & [ " commit" , " -q" , " -m" , " initial" ] ) ;
2807
write ( & g, " src/lib.rs" , b " pub fn a() {}\n pub fn b() {}\n " ) ;
2808
write ( & g, " run.sh" , b " #!/bin/sh\n " ) ;
2809
# [ cfg ( unix ) ]
2810
{
2811
use std:: os:: unix:: fs:: PermissionsExt;
2812
fs:: set_permissions( g.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
2813
}
2814
write ( & g, " logo.png" , b " \x89 PNG\0 \0 \x01 " ) ;
2815
git ( & [ " add" , " ." ] ) ;
75167672 Andy McCall 1 month ago
2816
git ( & [
2817
" commit" ,
2818
" -q" ,
2819
" -m" ,
2820
" second\n \n Change-Id: 0123456789abcdef0123456789abcdef" ,
2821
] ) ;
c3069537 Andy McCall 1 month ago ↶
2822
2823
git ( & [ " checkout" , " -q" , " -b" , " topic" , " HEAD~1" ] ) ;
2824
write ( & g, " README.md" , b " # Hi\n \n More.\n " ) ;
2825
git ( & [ " commit" , " -q" , " -am" , " readme" ] ) ;
2826
git ( & [ " checkout" , " -q" , " main" ] ) ;
2827
git ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge topic" , " topic" ] ) ;
2828
2829
2830
let initial = git ( & [ " rev-list" , " --max-parents=0" , " HEAD" ] ) ;
2831
git ( & [ " checkout" , " -q" , " -b" , " clash" , & initial] ) ;
2832
write ( & g, " src/lib.rs" , b " pub fn a() {}\n pub fn c() {}\n " ) ;
2833
git ( & [ " commit" , " -q" , " -am" , " clash" ] ) ;
2834
git ( & [ " checkout" , " -q" , " main" ] ) ;
75167672 Andy McCall 1 month ago
2835
let _ = std:: process:: Command:: new( " git" )
2836
.arg ( " -C" )
2837
.arg ( & g)
2838
.args ( [ " merge" , " -q" , " clash" ] )
2839
.output ( ) ;
2840
write (
2841
& g,
2842
" src/lib.rs" ,
2843
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n " ,
2844
) ;
c3069537 Andy McCall 1 month ago ↶
2845
git ( & [ " add" , " ." ] ) ;
2846
git ( & [ " commit" , " -q" , " -m" , " resolve clash" ] ) ;
2847
git ( & [ " rm" , " -q" , " logo.png" ] ) ;
2848
git ( & [ " commit" , " -q" , " -m" , " drop logo" ] ) ;
6e723b81 Andy McCall 1 month ago ↶
2849
2850
git ( & [ " mv" , " src/lib.rs" , " src/lib2.rs" ] ) ;
75167672 Andy McCall 1 month ago
2851
write (
2852
& g,
2853
" src/lib2.rs" ,
2854
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n pub fn d() {}\n " ,
2855
) ;
6e723b81 Andy McCall 1 month ago ↶
2856
git ( & [ " add" , " ." ] ) ;
2857
git ( & [ " commit" , " -q" , " -m" , " rename lib" ] ) ;
958fb27d Andy McCall 1 month ago ↶
2858
2859
2860
2861
git ( & [ " checkout" , " -q" , " -b" , " discarded" ] ) ;
2862
write ( & g, " run.sh" , b " #!/bin/sh\n echo discarded\n " ) ;
2863
git ( & [ " commit" , " -q" , " -am" , " discarded edit" ] ) ;
2864
git ( & [ " checkout" , " -q" , " main" ] ) ;
314db9b4 Andy McCall 1 month ago ↶
2865
git ( & [
2866
" merge" ,
2867
" -q" ,
2868
" -s" ,
2869
" ours" ,
2870
" -m" ,
2871
" merge discarded, keeping ours" ,
2872
" discarded" ,
2873
] ) ;
c3069537 Andy McCall 1 month ago ↶
2874
let expected_count: usize = git ( & [ " rev-list" , " --count" , " main" ] ) .parse ( ) .unwrap ( ) ;
2875
2876
let dest = root.join ( " oxy" ) ;
2877
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2878
let rep = repo.import_git ( & g, " main" ) .unwrap ( ) ;
2879
assert_eq! ( rep.commits, expected_count) ;
958fb27d Andy McCall 1 month ago ↶
2880
assert_eq! ( rep.merges, 3 ) ;
314db9b4 Andy McCall 1 month ago ↶
2881
assert_eq! (
2882
fs:: read( dest.join ( " run.sh" ) ) .unwrap ( ) ,
2883
b " #!/bin/sh\n " ,
2884
" the ours merge kept main's run.sh"
2885
) ;
958fb27d Andy McCall 1 month ago ↶
2886
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
2887
assert_eq! ( v.commits, expected_count, " every commit's tree reproduced" ) ;
2888
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
c3069537 Andy McCall 1 month ago ↶
2889
75167672 Andy McCall 1 month ago
2890
assert_eq! (
2891
fs:: read( dest.join ( " src/lib2.rs" ) ) .unwrap ( ) ,
2892
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n pub fn d() {}\n "
2893
) ;
6e723b81 Andy McCall 1 month ago ↶
2894
assert! ( ! dest.join ( " src/lib.rs" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
2895
assert_eq! (
2896
fs:: read( dest.join ( " README.md" ) ) .unwrap ( ) ,
2897
b " # Hi\n \n More.\n "
2898
) ;
c3069537 Andy McCall 1 month ago ↶
2899
assert! ( ! dest.join ( " logo.png" ) .exists ( ) ) ;
2900
# [ cfg ( unix ) ]
2901
{
2902
use std:: os:: unix:: fs:: PermissionsExt;
75167672 Andy McCall 1 month ago
2903
assert! (
2904
fs:: metadata( dest.join ( " run.sh" ) )
2905
.unwrap ( )
2906
.permissions ( )
2907
.mode ( )
2908
& 0o111
2909
! = 0
2910
) ;
c3069537 Andy McCall 1 month ago ↶
2911
}
75167672 Andy McCall 1 month ago
2912
assert! (
2913
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
2914
" clean after import"
2915
) ;
c3069537 Andy McCall 1 month ago ↶
2916
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
2917
let st = repo.load_state ( & heads) .unwrap ( ) ;
2918
assert! ( ! st.materialize ( ) .is_conflicted ( ) ) ;
2919
assert_eq! ( st.len ( ) , expected_count) ;
75167672 Andy McCall 1 month ago
2920
let tip = repo
2921
.log ( )
2922
.unwrap ( )
2923
.into_iter ( )
2924
.find ( | e | e.change.message.starts_with ( " rename lib" ) )
2925
.unwrap ( ) ;
6e723b81 Andy McCall 1 month ago ↶
2926
let c = repo.store ( ) .get_change ( & tip.hash) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2927
assert! (
2928
c.tree_ops
2929
.iter ( )
2930
.any ( | op | matches! ( op, TreeOp:: Move { name, .. } if name = = " lib2.rs" ) ) ,
2931
" {:?}" ,
2932
c.tree_ops
2933
) ;
2934
assert! ( ! c
2935
.tree_ops
2936
.iter ( )
2937
.any ( | op | matches! ( op, TreeOp:: AddNode { .. } ) ) ) ;
c3069537 Andy McCall 1 month ago ↶
2938
2939
let log = repo.log ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2940
let second = log
2941
.iter ( )
2942
.find ( | e | e.change.message.starts_with ( " second" ) )
2943
.unwrap ( ) ;
2944
assert_eq! (
2945
second.change.change_id,
2946
ChangeId:: from_hex( " 0123456789abcdef0123456789abcdef" ) .unwrap ( )
2947
) ;
2948
assert! ( second
2949
.change
2950
.message
2951
.contains ( " Git-Author: Grace <[email protected] >" ) ) ;
2952
let resolve = log
2953
.iter ( )
2954
.find ( | e | e.change.message.starts_with ( " resolve clash" ) )
2955
.unwrap ( ) ;
2956
assert_eq! (
2957
resolve.change.kind,
2958
ChangeKind:: Resolution,
2959
" a conflicting Git merge becomes a resolution"
2960
) ;
c3069537 Andy McCall 1 month ago ↶
2961
75167672 Andy McCall 1 month ago
2962
let repo2 = Repo:: init( & root.join ( " oxy2" ) )
2963
.unwrap ( )
2964
.with_home ( root.join ( " home" ) ) ;
c3069537 Andy McCall 1 month ago ↶
2965
repo2.import_git ( & g, " main" ) .unwrap ( ) ;
dfb23a3a Andy McCall 1 month ago ↶
2966
2967
2968
assert_eq! (
2969
repo2.bookmark_heads ( ) .unwrap ( ) ,
2970
repo.bookmark_heads ( ) .unwrap ( )
2971
) ;
75167672 Andy McCall 1 month ago
2972
let ids: BTreeSet< ChangeId> = repo
2973
.log ( )
2974
.unwrap ( )
2975
.iter ( )
2976
.filter ( | e | ! e.is_working_copy)
2977
.map ( | e | e.change.change_id )
2978
.collect ( ) ;
2979
let ids2: BTreeSet< ChangeId> = repo2
2980
.log ( )
2981
.unwrap ( )
2982
.iter ( )
2983
.filter ( | e | ! e.is_working_copy)
2984
.map ( | e | e.change.change_id )
2985
.collect ( ) ;
c3069537 Andy McCall 1 month ago ↶
2986
assert_eq! ( ids, ids2) ;
2987
2988
let mirror = root.join ( " mirror.git" ) ;
2989
repo.git_export ( & mirror) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2990
let out = std:: process:: Command:: new( " git" )
2991
.arg ( " -C" )
2992
.arg ( & mirror)
2993
.args ( [ " show" , " main:src/lib2.rs" ] )
2994
.output ( )
2995
.unwrap ( ) ;
2996
assert_eq! (
2997
String :: from_utf8_lossy( & out.stdout) ,
2998
" pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n pub fn d() {}\n "
2999
) ;
3000
let out = std:: process:: Command:: new( " git" )
3001
.arg ( " -C" )
3002
.arg ( & mirror)
3003
.args ( [ " show" , " main:src/lib.rs" ] )
3004
.output ( )
3005
.unwrap ( ) ;
3006
assert! (
3007
! out.status.success ( ) ,
3008
" the old path is gone from the export"
3009
) ;
c3069537 Andy McCall 1 month ago ↶
3010
}
252e8cb2 Andy McCall 1 month ago ↶
3011
3012
# [ test ]
3013
fn checkpoints_accelerate_loading_and_stay_correct ( ) {
3014
hermetic ( ) ;
3015
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3016
let root = dir.path ( ) ;
3017
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
3018
repo.config.set ( " core.checkpoint-interval" , " 5" ) .unwrap ( ) ;
3019
repo.save_config ( ) .unwrap ( ) ;
3020
let repo = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
3021
let mut hashes = Vec :: new( ) ;
3022
for i in 0 .. 12 {
75167672 Andy McCall 1 month ago
3023
write (
3024
root,
3025
" a.txt" ,
3026
format! (
3027
" {} \n " ,
3028
( 0 .. = i)
3029
.map ( | k | k.to_string ( ) )
3030
.collect:: < Vec< _ > > ( )
3031
.join ( " \n " )
3032
)
3033
.as_bytes ( ) ,
3034
) ;
252e8cb2 Andy McCall 1 month ago ↶
3035
write ( root, & format! ( " f{i} .txt" ) , b " x\n " ) ;
3036
hashes.push ( repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ) ;
3037
}
75167672 Andy McCall 1 month ago
3038
let ( _ , cp) = repo
3039
.latest_checkpoint ( " main" )
3040
.unwrap ( )
3041
.expect ( " automatic checkpoint" ) ;
252e8cb2 Andy McCall 1 month ago ↶
3042
assert! ( cp.count > = 5 & & cp.count < = 12 , " {}" , cp.count) ;
3043
3044
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
3045
let fast = repo.load_state ( & heads) .unwrap ( ) ;
3046
let full = repo.load_state_full ( & heads) .unwrap ( ) ;
3047
assert_eq! ( fast.len ( ) , full.len ( ) ) ;
3048
assert_eq! ( fast.heads ( ) , full.heads ( ) ) ;
3049
let ( a, b) = ( fast.materialize ( ) , full.materialize ( ) ) ;
75167672 Andy McCall 1 month ago
3050
assert_eq! (
3051
a.files.keys ( ) .collect:: < Vec< _ > > ( ) ,
3052
b.files.keys ( ) .collect:: < Vec< _ > > ( )
3053
) ;
252e8cb2 Andy McCall 1 month ago ↶
3054
for ( p, f) in & a.files {
3055
assert_eq! ( f.content, b.files[ p] .content, " {p}" ) ;
3056
}
3057
assert! ( repo.load_state_checkpointed ( & heads) .unwrap ( ) .is_some ( ) ) ;
3058
75167672 Andy McCall 1 month ago
3059
assert! ( repo
3060
.load_state_checkpointed ( & [ hashes[ 0 ] ] )
3061
.unwrap ( )
3062
.is_none ( ) ) ;
252e8cb2 Andy McCall 1 month ago ↶
3063
assert_eq! ( repo.load_state ( & [ hashes[ 0 ] ] ) .unwrap ( ) .len ( ) , 1 ) ;
3064
3065
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
3066
let h = repo.describe ( & hashes[ 3 ] , " c3 described" ) .unwrap ( ) ;
3067
assert_eq! ( repo.store ( ) .get_change ( & h) .unwrap ( ) .message, " c3 described" ) ;
3068
let ( r, n, bytes) = repo.checkpoint ( ) .unwrap ( ) ;
3069
assert! ( n > = 12 & & bytes > 0 ) ;
3070
assert_eq! ( repo.latest_checkpoint ( " main" ) .unwrap ( ) .unwrap ( ) .0 , r) ;
3071
write ( root, " a.txt" , b " after\n " ) ;
3072
let h_after = repo.commit ( " after" , & [ ] ) .unwrap ( ) ;
3073
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
3074
assert! ( st.contains ( & h_after) ) ;
75167672 Andy McCall 1 month ago
3075
assert! (
3076
matches! ( & st.materialize ( ) .files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " after\n " )
3077
) ;
252e8cb2 Andy McCall 1 month ago ↶
3078
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 14 ) ;
3079
3080
repo.gc ( true ) .unwrap ( ) ;
3081
assert! ( repo.latest_checkpoint ( " main" ) .unwrap ( ) .is_some ( ) ) ;
75167672 Andy McCall 1 month ago
3082
assert! ( repo
3083
.load_state_checkpointed ( & repo.bookmark_heads ( ) .unwrap ( ) )
3084
.unwrap ( )
3085
.is_some ( ) ) ;
252e8cb2 Andy McCall 1 month ago ↶
3086
}
135212de Andy McCall 1 month ago ↶
3087
3088
# [ test ]
3089
fn quick_status_for_prompts ( ) {
3090
hermetic ( ) ;
3091
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3092
let root = dir.path ( ) ;
3093
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
3094
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
3095
let a = Repo:: clone( & url, & root.join ( " a" ) )
3096
.unwrap ( )
3097
.with_home ( root.join ( " home" ) ) ;
135212de Andy McCall 1 month ago ↶
3098
let ops_before = a.op_log ( ) .unwrap ( ) .len ( ) ;
3099
let info = a.quick_status ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3100
assert_eq! (
3101
(
3102
info.bookmark.as_str ( ) ,
3103
info.heads,
3104
info.dirty,
3105
info.conflicted,
3106
info.ahead,
3107
info.behind
3108
) ,
3109
( " main" , 0 , false , false , Some ( 0 ) , Some ( 0 ) )
3110
) ;
135212de Andy McCall 1 month ago ↶
3111
write ( & root.join ( " a" ) , " f.txt" , b " f\n " ) ;
3112
assert! ( a.quick_status ( ) .unwrap ( ) .dirty, " a new file is dirty" ) ;
75167672 Andy McCall 1 month ago
3113
assert_eq! (
3114
a.op_log ( ) .unwrap ( ) .len ( ) ,
3115
ops_before,
3116
" quick_status records nothing"
3117
) ;
135212de Andy McCall 1 month ago ↶
3118
3119
a.snapshot ( " status" ) .unwrap ( ) ;
3120
assert! ( a.quick_status ( ) .unwrap ( ) .dirty) ;
3121
a.commit ( " f" , & [ ] ) .unwrap ( ) ;
3122
let info = a.quick_status ( ) .unwrap ( ) ;
3123
assert! ( ! info.dirty, " clean right after a commit" ) ;
3124
assert_eq! ( ( info.ahead, info.behind) , ( Some ( 1 ) , Some ( 0 ) ) ) ;
75167672 Andy McCall 1 month ago
3125
assert_eq! (
3126
info.format ( " %b %c%d%x%A%D" ) ,
3127
format! ( " main {} +1" , info.change_id)
3128
) ;
135212de Andy McCall 1 month ago ↶
3129
assert_eq! ( info.format ( " [%h heads] %a/%B %%" ) , " [1 heads] 1/0 %" ) ;
3130
3131
3132
let p = root.join ( " a/f.txt" ) ;
3133
let content = fs:: read( & p) .unwrap ( ) ;
3134
fs:: write( & p, & content) .unwrap ( ) ;
3135
assert! ( ! a.quick_status ( ) .unwrap ( ) .dirty) ;
3136
fs:: write( & p, b " g\n " ) .unwrap ( ) ;
3137
assert! ( a.quick_status ( ) .unwrap ( ) .dirty) ;
3138
fs:: write( & p, b " f\n " ) .unwrap ( ) ;
3139
3140
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
3141
assert_eq! ( a.quick_status ( ) .unwrap ( ) .ahead, Some ( 0 ) ) ;
75167672 Andy McCall 1 month ago
3142
let b = Repo:: clone( & url, & root.join ( " b" ) )
3143
.unwrap ( )
3144
.with_home ( root.join ( " home-b" ) ) ;
135212de Andy McCall 1 month ago ↶
3145
write ( & root.join ( " b" ) , " g.txt" , b " g\n " ) ;
3146
b.commit ( " g" , & [ ] ) .unwrap ( ) ;
3147
b.push ( " origin" , false ) .unwrap ( ) ;
3148
a.fetch ( " origin" ) .unwrap ( ) ;
3149
let info = a.quick_status ( ) .unwrap ( ) ;
3150
assert_eq! ( ( info.ahead, info.behind) , ( Some ( 0 ) , Some ( 1 ) ) ) ;
3151
assert_eq! ( info.format ( " %A%D" ) , " -1" ) ;
3152
let _ = origin;
3153
}
b3c5a59a Andy McCall 1 month ago ↶
3154
3155
# [ test ]
3156
fn lazy_blobs_are_fetched_on_first_read ( ) {
3157
hermetic ( ) ;
3158
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3159
let root = dir.path ( ) ;
3160
let origin_dir = root.join ( " origin" ) ;
75167672 Andy McCall 1 month ago
3161
let origin = Repo:: init( & origin_dir)
3162
.unwrap ( )
3163
.with_home ( root.join ( " home" ) ) ;
b3c5a59a Andy McCall 1 month ago ↶
3164
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
3165
let big: Vec< u8 > = ( 0 .. 200_000 u32 ) .map ( | i | ( i % 251 ) as u8 ) .collect ( ) ;
3166
write ( & origin_dir, " big.bin" , & big) ;
3167
write ( & origin_dir, " small.bin" , & [ 0 u8 , 1 , 2 ] ) ;
3168
write ( & origin_dir, " t.txt" , b " t\n " ) ;
3169
let h = origin.commit ( " blobs" , & [ ] ) .unwrap ( ) ;
3170
let big_ref = Repo:: blob_ref( & big) ;
3171
3172
75167672 Andy McCall 1 month ago
3173
let a = Repo:: init_with_id( & root.join ( " a" ) , origin.id ( ) .unwrap ( ) )
3174
.unwrap ( )
3175
.with_home ( root.join ( " home" ) ) ;
b3c5a59a Andy McCall 1 month ago ↶
3176
let a = {
3177
let mut a = a;
3178
a.config.set ( " remote.origin.url" , & url) .unwrap ( ) ;
3179
a.config.set ( " filter.max-blob" , " 100K" ) .unwrap ( ) ;
3180
a.save_config ( ) .unwrap ( ) ;
3181
a
3182
} ;
3183
assert_eq! ( a.config.max_blob, Some ( 100 * 1024 ) ) ;
3184
a.fetch ( " origin" ) .unwrap ( ) ;
3185
assert! ( a.store ( ) .has ( & h) ) ;
3186
assert! ( ! a.store ( ) .has ( & big_ref) , " withheld" ) ;
75167672 Andy McCall 1 month ago
3187
assert! (
3188
a.store ( ) .has ( & Repo:: blob_ref( & [ 0 u8 , 1 , 2 ] ) ) ,
3189
" small blob came along"
3190
) ;
b3c5a59a Andy McCall 1 month ago ↶
3191
assert_eq! ( a.lazy_remote ( & big_ref) .as_deref ( ) , Some ( " origin" ) ) ;
3192
3193
3194
let ( op_ref, op) = a.head_op ( ) .unwrap ( ) ;
3195
let mut view = op.view.clone ( ) ;
75167672 Andy McCall 1 month ago
3196
view.bookmarks.insert (
3197
" main" .into ( ) ,
3198
a.store ( ) .put_state ( & StateObject:: new( vec! [ h] ) ) .unwrap ( ) ,
3199
) ;
b3c5a59a Andy McCall 1 month ago ↶
3200
a.record_op ( & [ op_ref] , " adopt" , view) .unwrap ( ) ;
3201
3202
a.checkout ( & [ h] , true , " checkout" ) .unwrap ( ) ;
3203
assert! ( a.store ( ) .has ( & big_ref) , " fetched on first read" ) ;
3204
assert_eq! ( fs:: read( root.join ( " a/big.bin" ) ) .unwrap ( ) , big) ;
3205
assert! ( a.lazy_remote ( & big_ref) .is_none ( ) , " no longer registered" ) ;
3206
write ( & root.join ( " a" ) , " t.txt" , b " t2\n " ) ;
3207
a.commit ( " text edit" , & [ ] ) .unwrap ( ) ;
3208
3209
7421309f Andy McCall 1 month ago ↶
3210
let b = Repo:: clone_with( & url, & root.join ( " b" ) , Some ( 1024 ) , & [ ] ) .unwrap ( ) ;
b3c5a59a Andy McCall 1 month ago ↶
3211
assert_eq! ( b.config.max_blob, Some ( 1024 ) ) ;
3212
assert_eq! ( fs:: read( root.join ( " b/big.bin" ) ) .unwrap ( ) , big) ;
3213
assert_eq! ( parse_size ( " 2M" ) .unwrap ( ) , 2 < < 20 ) ;
3214
assert! ( parse_size ( " lots" ) .is_err ( ) ) ;
3215
}
7421309f Andy McCall 1 month ago ↶
3216
3217
# [ test ]
3218
fn sparse_checkout_materializes_only_chosen_paths ( ) {
3219
hermetic ( ) ;
3220
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3221
let root = dir.path ( ) ;
3222
let origin_dir = root.join ( " origin" ) ;
75167672 Andy McCall 1 month ago
3223
let origin = Repo:: init( & origin_dir)
3224
.unwrap ( )
3225
.with_home ( root.join ( " home" ) ) ;
7421309f Andy McCall 1 month ago ↶
3226
write ( & origin_dir, " src/main.rs" , b " fn main() {}\n " ) ;
3227
write ( & origin_dir, " src/lib.rs" , b " pub fn a() {}\n " ) ;
3228
write ( & origin_dir, " docs/guide.md" , b " # guide\n " ) ;
3229
let logo: Vec< u8 > = ( 0 .. 50_000 u32 ) .map ( | i | ( i % 7 ) as u8 ) .collect ( ) ;
3230
write ( & origin_dir, " docs/logo.png" , & logo) ;
3231
write ( & origin_dir, " README.md" , b " # top\n " ) ;
3232
let h1 = origin.commit ( " tree" , & [ ] ) .unwrap ( ) ;
3233
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
3234
75167672 Andy McCall 1 month ago
3235
let a = Repo:: clone_with( & url, & root.join ( " a" ) , None , & [ " src" .into ( ) ] )
3236
.unwrap ( )
3237
.with_home ( root.join ( " home" ) ) ;
7421309f Andy McCall 1 month ago ↶
3238
assert_eq! ( a.config.sparse_paths, vec! [ " src" ] ) ;
3239
assert_eq! ( a.config.max_blob, Some ( 0 ) , " sparse implies lazy blobs" ) ;
3240
assert! ( root.join ( " a/src/main.rs" ) .exists ( ) ) ;
3241
assert! ( ! root.join ( " a/docs" ) .exists ( ) & & ! root.join ( " a/README.md" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
3242
assert! (
3243
! a.store ( ) .has ( & Repo:: blob_ref( & logo) ) ,
3244
" out-of-scope blob never fetched"
3245
) ;
3246
assert! (
3247
! a.snapshot ( " status" ) .unwrap ( ) .changed,
3248
" out-of-scope files are not removals"
3249
) ;
05e71ed6 Andy McCall 1 month ago ↶
3250
assert! ( ! a.quick_status ( ) .unwrap ( ) .dirty) ;
7421309f Andy McCall 1 month ago ↶
3251
3252
75167672 Andy McCall 1 month ago
3253
write (
3254
& root.join ( " a" ) ,
3255
" src/lib.rs" ,
3256
b " pub fn a() {}\n pub fn b() {}\n " ,
3257
) ;
7421309f Andy McCall 1 month ago ↶
3258
let h2 = a.commit ( " edit lib" , & [ ] ) .unwrap ( ) ;
3259
let st = a.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
3260
assert! (
3261
st.files.contains_key ( " docs/guide.md" ) ,
3262
" the state still has everything"
3263
) ;
7421309f Andy McCall 1 month ago ↶
3264
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
3265
origin.refresh ( ) .unwrap ( ) ;
7421309f Andy McCall 1 month ago ↶
3266
3267
write ( & root.join ( " a" ) , " docs/new.md" , b " ignored\n " ) ;
3268
assert! ( a.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
3269
3270
3271
let mut a = a;
3272
a.config.set ( " filter.paths" , " src,docs" ) .unwrap ( ) ;
3273
a.save_config ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3274
let r = a
3275
.checkout ( & a.bookmark_heads ( ) .unwrap ( ) , true , " widen" )
3276
.unwrap ( ) ;
7421309f Andy McCall 1 month ago ↶
3277
assert! ( r.written > = 2 ) ;
75167672 Andy McCall 1 month ago
3278
assert_eq! (
3279
fs:: read( root.join ( " a/docs/logo.png" ) ) .unwrap ( ) ,
3280
logo,
3281
" now fetched lazily"
3282
) ;
7421309f Andy McCall 1 month ago ↶
3283
assert! ( ! root.join ( " a/README.md" ) .exists ( ) ) ;
3284
3285
origin.pull ( " origin" ) .err ( ) ;
75167672 Andy McCall 1 month ago
3286
let full = origin
3287
.load_state ( & origin.bookmark_heads ( ) .unwrap ( ) )
3288
.unwrap ( )
3289
.materialize ( ) ;
3290
assert! (
3291
matches! ( & full.files[ " src/lib.rs" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " pub fn a() {}\n pub fn b() {}\n " )
3292
) ;
7421309f Andy McCall 1 month ago ↶
3293
assert! ( full.files.contains_key ( " README.md" ) ) ;
3294
let _ = h1;
3295
}
2863456a Andy McCall 1 month ago ↶
3296
7bd805ff Andy McCall 13 days ago ↶
3297
3298
3299
3300
2863456a Andy McCall 1 month ago ↶
3301
# [ test ]
7bd805ff Andy McCall 13 days ago ↶
3302
fn a_depth_0_clone_brings_its_binary_files ( ) {
3303
hermetic ( ) ;
3304
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3305
let root = dir.path ( ) ;
3306
let origin_dir = root.join ( " origin" ) ;
3307
let origin = Repo:: init( & origin_dir)
3308
.unwrap ( )
3309
.with_home ( root.join ( " home" ) ) ;
3310
let png: Vec< u8 > = ( 0 .. 4000 u32 )
3311
.map ( | i | ( i * 7 % 251 ) as u8 )
3312
.chain ( [ 0 , 0xff ] )
3313
.collect ( ) ;
3314
3315
3316
write ( & origin_dir, " logo.png" , & png) ;
3317
write ( & origin_dir, " a.txt" , b " first\n " ) ;
3318
origin.commit ( " logo" , & [ ] ) .unwrap ( ) ;
3319
write ( & origin_dir, " a.txt" , b " later\n " ) ;
3320
origin.commit ( " later" , & [ ] ) .unwrap ( ) ;
3321
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) .len ( ) , 1 ) ;
3322
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
3323
Repo:: clone_shallow( & url, & root.join ( " a" ) , 0 , None , & [ ] ) .unwrap ( ) ;
3324
assert_eq! ( fs:: read( root.join ( " a/logo.png" ) ) .unwrap ( ) , png) ;
3325
let lazy = Repo:: clone_shallow( & url, & root.join ( " b" ) , 0 , Some ( 0 ) , & [ ] ) .unwrap ( ) ;
3326
assert_eq! ( fs:: read( root.join ( " b/a.txt" ) ) .unwrap ( ) , b " later\n " ) ;
3327
let blob = {
3328
let heads = lazy.bookmark_heads ( ) .unwrap ( ) ;
3329
let st = lazy.load_state_history ( & heads) .unwrap ( ) ;
3330
st.opaque_nodes ( )
3331
.iter ( )
3332
.flat_map ( | n | st.live_blobs ( n) )
3333
.next ( )
3334
.unwrap ( )
3335
} ;
3336
assert_eq! ( lazy.read_blob ( & blob) .unwrap ( ) , png, " fetched on read" ) ;
3337
}
3338
3339
# [ test ]
2863456a Andy McCall 1 month ago ↶
3340
fn shallow_clone_and_deepen ( ) {
3341
hermetic ( ) ;
3342
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3343
let root = dir.path ( ) ;
3344
let origin_dir = root.join ( " origin" ) ;
75167672 Andy McCall 1 month ago
3345
let origin = Repo:: init( & origin_dir)
3346
.unwrap ( )
3347
.with_home ( root.join ( " home" ) ) ;
2863456a Andy McCall 1 month ago ↶
3348
let mut hashes = Vec :: new( ) ;
3349
for i in 0 .. 8 {
75167672 Andy McCall 1 month ago
3350
write (
3351
& origin_dir,
3352
" a.txt" ,
3353
format! (
3354
" {} \n " ,
3355
( 0 .. = i)
3356
.map ( | k | k.to_string ( ) )
3357
.collect:: < Vec< _ > > ( )
3358
.join ( " \n " )
3359
)
3360
.as_bytes ( ) ,
3361
) ;
2863456a Andy McCall 1 month ago ↶
3362
hashes.push ( origin.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ) ;
3363
}
3364
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
3365
let a = Repo:: clone_shallow( & url, & root.join ( " a" ) , 3 , None , & [ ] )
3366
.unwrap ( )
3367
.with_home ( root.join ( " home" ) ) ;
2863456a Andy McCall 1 month ago ↶
3368
assert! ( a.is_shallow ( ) ) ;
75167672 Andy McCall 1 month ago
3369
assert_eq! (
3370
fs:: read_to_string( root.join ( " a/a.txt" ) ) .unwrap ( ) ,
3371
" 0\n 1\n 2\n 3\n 4\n 5\n 6\n 7\n "
3372
) ;
3373
assert! (
3374
a.store ( ) .has ( & hashes[ 7 ] ) & & a.store ( ) .has ( & hashes[ 5 ] ) ,
3375
" three levels present"
3376
) ;
2863456a Andy McCall 1 month ago ↶
3377
assert! ( ! a.store ( ) .has ( & hashes[ 4 ] ) , " beyond the depth is absent" ) ;
3378
assert_eq! ( a.boundary ( ) , [ hashes[ 4 ] ] .into_iter ( ) .collect ( ) ) ;
75167672 Andy McCall 1 month ago
3379
assert_eq! (
3380
a.log ( ) .unwrap ( ) .len ( ) ,
3381
9 ,
3382
" the log shows every change from the checkpoint's metadata"
3383
) ;
2863456a Andy McCall 1 month ago ↶
3384
assert! ( ! a.snapshot ( " status" ) .unwrap ( ) .changed) ;
3385
assert_eq! ( a.quick_status ( ) .unwrap ( ) .heads, 1 ) ;
3386
3387
3388
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 " ) ;
3389
let h_new = a.commit ( " c8" , & [ ] ) .unwrap ( ) ;
3390
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
3391
origin.refresh ( ) .unwrap ( ) ;
2863456a Andy McCall 1 month ago ↶
3392
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ h_new] ) ;
3393
3394
write ( & origin_dir, " b.txt" , b " b\n " ) ;
3395
origin.pull ( " origin" ) .err ( ) ;
3396
let ( _ , wc) = origin.working_copy ( ) .unwrap ( ) ;
3397
let _ = wc;
75167672 Andy McCall 1 month ago
3398
origin
3399
.checkout ( & origin.bookmark_heads ( ) .unwrap ( ) , true , " co" )
3400
.unwrap ( ) ;
2863456a Andy McCall 1 month ago ↶
3401
write ( & origin_dir, " b.txt" , b " b\n " ) ;
3402
let h_b = origin.commit ( " b" , & [ ] ) .unwrap ( ) ;
3403
let pr = a.pull ( " origin" ) .unwrap ( ) ;
3404
assert! ( pr.changed) ;
3405
assert! ( a.store ( ) .has ( & h_b) ) ;
3406
assert! ( root.join ( " a/b.txt" ) .exists ( ) ) ;
3407
3408
3409
3410
3411
assert! ( a.deepen ( " origin" , 2 ) .unwrap ( ) > = 2 ) ;
3412
assert! ( a.store ( ) .has ( & hashes[ 3 ] ) ) ;
3413
assert! ( a.is_shallow ( ) ) ;
3414
a.deepen ( " origin" , 100 ) .unwrap ( ) ;
3415
assert! ( ! a.is_shallow ( ) , " boundary reached the root" ) ;
3416
assert! ( a.store ( ) .has ( & hashes[ 0 ] ) ) ;
75167672 Andy McCall 1 month ago
3417
assert_eq! (
3418
a.load_state_full ( & a.bookmark_heads ( ) .unwrap ( ) )
3419
.unwrap ( )
3420
.len ( ) ,
3421
10
3422
) ;
2863456a Andy McCall 1 month ago ↶
3423
}
132b884b Andy McCall 1 month ago ↶
3424
3425
# [ test ]
3426
fn tags_are_immutable_named_states_and_travel ( ) {
3427
hermetic ( ) ;
3428
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3429
let root = dir.path ( ) ;
3430
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
3431
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
3432
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
3433
.unwrap ( )
3434
.with_home ( root.join ( " home-a" ) ) ;
3435
let ( key_a, _ ) = a
3436
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
3437
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3438
write ( & root.join ( " a" ) , " f.txt" , b " 1\n " ) ;
3439
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
3440
write ( & root.join ( " a" ) , " f.txt" , b " 1\n 2\n " ) ;
3441
let h2 = a.commit ( " two" , & [ ] ) .unwrap ( ) ;
3442
3443
let t = a.tag_create ( " v1" , & [ h1] , " first release" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3444
assert_eq! (
3445
( t.heads.clone ( ) , t.signer.as_deref ( ) ) ,
3446
( vec! [ h1] , Some ( " Ada" ) )
3447
) ;
132b884b Andy McCall 1 month ago ↶
3448
assert! ( a.tag_create ( " v1" , & [ h2] , " " ) .is_err ( ) , " immutable" ) ;
3449
assert! ( a.tag_create ( " bad/name" , & [ h2] , " " ) .is_err ( ) ) ;
3450
assert_eq! ( a.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
3451
assert_eq! ( a.resolve_heads ( " v1" ) .unwrap ( ) , vec! [ h1] ) ;
3452
assert_eq! ( a.resolve_heads ( " main" ) .unwrap ( ) , vec! [ h2] ) ;
3453
let ( _ , sa) = a.tag_attest ( " v1" ) .unwrap ( ) .unwrap ( ) ;
3454
assert_eq! ( sa.sigs[ 0 ] .key, key_a) ;
ee130dd9 Andy McCall 17 days ago ↶
3455
3456
3457
let mut refs = a.remote_refs ( ) .unwrap ( ) ;
3458
a.refs_for_client ( & mut refs, None ) ;
3459
assert_eq! ( refs.tags.len ( ) , 1 ) ;
132b884b Andy McCall 1 month ago ↶
3460
75167672 Andy McCall 1 month ago
3461
a.checkout ( & a.resolve_heads ( " v1" ) .unwrap ( ) , false , " co" )
3462
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3463
assert_eq! ( fs:: read( root.join ( " a/f.txt" ) ) .unwrap ( ) , b " 1\n " ) ;
75167672 Andy McCall 1 month ago
3464
a.checkout ( & a.bookmark_heads ( ) .unwrap ( ) , false , " co" )
3465
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3466
3467
3468
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3469
origin.refresh ( ) .unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3470
assert! ( r.iter ( ) .any ( | x | x.name = = " tags/v1" & & x.ok ) , " {r:?}" ) ;
3471
assert_eq! ( origin.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
75167672 Andy McCall 1 month ago
3472
let mut forged = a
3473
.tag_refs ( )
3474
.unwrap ( )
3475
.into_iter ( )
3476
.find ( | t | t.name = = " v1" )
3477
.unwrap ( ) ;
3478
forged.state = origin
3479
.store ( )
3480
.put_state ( & StateObject:: new( vec! [ h2] ) )
3481
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3482
forged.heads = vec! [ h2] ;
3483
forged.attestation = None ;
75167672 Andy McCall 1 month ago
3484
let r = origin
3485
.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ forged] )
3486
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3487
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " immutable" ) , " {r:?}" ) ;
3488
3489
75167672 Andy McCall 1 month ago
3490
let b = Repo:: clone( & url, & root.join ( " b" ) )
3491
.unwrap ( )
3492
.with_home ( root.join ( " home-b" ) ) ;
132b884b Andy McCall 1 month ago ↶
3493
assert_eq! ( b.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
3494
assert! ( b.tags ( ) .unwrap ( ) [ 0 ] .signer.as_deref ( ) = = Some ( " Ada" ) ) ;
3495
a.tag_create ( " v2" , & [ h2] , " second" ) .unwrap ( ) ;
3496
a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3497
origin.refresh ( ) .unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3498
let rep = b.fetch ( " origin" ) .unwrap ( ) ;
3499
assert_eq! ( rep.new_tags, 1 ) ;
3500
assert_eq! ( b.tag_heads ( " v2" ) .unwrap ( ) , Some ( vec! [ h2] ) ) ;
3501
3502
3503
a.policy_init ( ) .unwrap ( ) ;
3504
a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3505
origin.refresh ( ) .unwrap ( ) ;
1ec6fdbc Andy McCall 18 days ago ↶
3506
let unattested = crate :: protocol:: TagRef:: new(
3507
" v3" .into ( ) ,
3508
origin
75167672 Andy McCall 1 month ago
3509
.store ( )
3510
.put_state ( & StateObject:: new( vec! [ h2] ) )
3511
.unwrap ( ) ,
1ec6fdbc Andy McCall 18 days ago ↶
3512
vec! [ h2] ,
3513
None ,
3514
) ;
75167672 Andy McCall 1 month ago
3515
let r = origin
3516
.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ unattested] )
3517
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3518
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " attestation" ) , " {r:?}" ) ;
3519
a.tag_create ( " v3" , & [ h2] , " " ) .unwrap ( ) ;
3520
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3521
origin.refresh ( ) .unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3522
assert! ( r.iter ( ) .any ( | x | x.name = = " tags/v3" & & x.ok ) , " {r:?}" ) ;
3523
assert! ( a.gc ( true ) .is_ok ( ) ) ;
3524
assert_eq! ( a.tags ( ) .unwrap ( ) .len ( ) , 3 , " tags survive gc" ) ;
e5d9b051 Andy McCall 21 days ago ↶
3525
3526
3527
3528
3529
let rot = a.rotate_identity ( ) .unwrap ( ) ;
3530
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3531
origin.refresh ( ) .unwrap ( ) ;
e5d9b051 Andy McCall 21 days ago ↶
3532
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
3533
let c = Repo:: clone( & url, & root.join ( " c" ) )
3534
.unwrap ( )
3535
.with_home ( root.join ( " home-c" ) ) ;
3536
assert_eq! ( c.tag_heads ( " v3" ) .unwrap ( ) , Some ( vec! [ h2] ) ) ;
3537
let ( _ , sp) = c.current_policy ( ) .unwrap ( ) .unwrap ( ) ;
3538
assert! ( ! sp.policy.authorizes ( & key_a, " tags/v9" ) ) ;
3539
let made = c.identity ( & rot.new) .unwrap ( ) .created;
3540
assert! ( c.predecessor_authorized ( & sp.policy, & key_a, " tags/v9" , made - 1 ) ) ;
3541
assert! ( ! c.predecessor_authorized ( & sp.policy, & key_a, " tags/v9" , made) ) ;
3542
assert! ( ! c.predecessor_authorized ( & sp.policy, & rot.new, " tags/v9" , 0 ) ) ;
132b884b Andy McCall 1 month ago ↶
3543
}
4e88e774 Andy McCall 1 month ago ↶
3544
3545
# [ test ]
3546
fn multiple_workspaces_share_a_repository ( ) {
3547
hermetic ( ) ;
3548
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3549
let root = dir.path ( ) ;
3550
let main_dir = root.join ( " main" ) ;
3551
let repo = Repo:: init( & main_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
3552
write ( & main_dir, " a.txt" , b " a\n " ) ;
3553
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
3554
assert_eq! ( repo.workspaces ( ) .unwrap ( ) .len ( ) , 1 ) ;
3555
3556
3557
let ws_dir = root.join ( " ws2" ) ;
4476cdbb Andy McCall 12 days ago ↶
3558
let ops = repo.op_log ( ) .unwrap ( ) .len ( ) ;
4e88e774 Andy McCall 1 month ago ↶
3559
let ws = repo.workspace_add ( & ws_dir, None ) .unwrap ( ) ;
4476cdbb Andy McCall 12 days ago ↶
3560
assert_eq! (
3561
ws.op_log ( ) .unwrap ( ) .len ( ) ,
3562
ops + 2 ,
3563
" the add and its checkout, and no snapshot finding the tree deleted (oxygen#137)"
3564
) ;
4e88e774 Andy McCall 1 month ago ↶
3565
assert! ( ws_dir.join ( " .oxy" ) .is_file ( ) ) ;
3566
assert_eq! ( fs:: read( ws_dir.join ( " a.txt" ) ) .unwrap ( ) , b " a\n " ) ;
3567
assert_ne! ( ws.workspace ( ) , repo.workspace ( ) ) ;
3568
let list = repo.workspaces ( ) .unwrap ( ) ;
3569
assert_eq! ( list.len ( ) , 2 ) ;
75167672 Andy McCall 1 month ago
3570
assert! ( list
3571
.iter ( )
3572
.any ( | w | w.id = = ws.workspace ( ) & & w.path = = ws_dir.canonicalize ( ) .unwrap ( ) & & ! w.current ) ) ;
4e88e774 Andy McCall 1 month ago ↶
3573
assert! ( ! ws.snapshot ( " status" ) .unwrap ( ) .changed) ;
3574
3575
3576
write ( & ws_dir, " b.txt" , b " b\n " ) ;
3577
let s = ws.snapshot ( " status" ) .unwrap ( ) ;
3578
assert_eq! ( s.statuses.len ( ) , 1 ) ;
75167672 Andy McCall 1 month ago
3579
assert! (
3580
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
3581
" the first workspace is untouched"
3582
) ;
4e88e774 Andy McCall 1 month ago ↶
3583
let h2 = ws.commit ( " b from ws2" , & [ ] ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3584
repo.refresh ( ) .unwrap ( ) ;
4e88e774 Andy McCall 1 month ago ↶
3585
let mut heads = repo.bookmark_heads ( ) .unwrap ( ) ;
3586
heads.sort ( ) ;
3587
let mut want = vec! [ h1, h2] ;
3588
want.sort ( ) ;
3589
assert_eq! ( heads, want, " both workspaces see the bookmark move" ) ;
3590
assert! ( ! main_dir.join ( " b.txt" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
3591
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " co" )
3592
.unwrap ( ) ;
4e88e774 Andy McCall 1 month ago ↶
3593
assert! ( main_dir.join ( " b.txt" ) .exists ( ) ) ;
3594
3595
3596
let reopened = Repo:: open( & ws_dir) .unwrap ( ) ;
3597
assert_eq! ( reopened.workspace ( ) , ws.workspace ( ) ) ;
3598
assert_eq! ( reopened.id ( ) .unwrap ( ) , repo.id ( ) .unwrap ( ) ) ;
3599
assert_eq! ( reopened.quick_status ( ) .unwrap ( ) .heads, 2 ) ;
3600
3601
3602
assert! ( repo.workspace_add ( & main_dir.join ( " inner" ) , None ) .is_err ( ) ) ;
3603
assert! ( repo.workspace_forget ( & repo.workspace ( ) ) .is_err ( ) ) ;
3604
repo.workspace_forget ( & ws.workspace ( ) ) .unwrap ( ) ;
3605
assert_eq! ( repo.workspaces ( ) .unwrap ( ) .len ( ) , 1 ) ;
3606
assert! ( ws_dir.join ( " b.txt" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
3607
assert! (
3608
ws.snapshot ( " status" ) .is_err ( ) ,
3609
" a forgotten workspace is no longer in the view"
3610
) ;
4e88e774 Andy McCall 1 month ago ↶
3611
6caacfff Andy McCall 1 month ago ↶
3612
3613
4e88e774 Andy McCall 1 month ago ↶
3614
let lock = repo.lock_snapshot ( ) .unwrap ( ) ;
6caacfff Andy McCall 1 month ago ↶
3615
let again = repo.lock_working_copy ( ) .unwrap ( ) ;
3616
drop ( again) ;
4e88e774 Andy McCall 1 month ago ↶
3617
drop ( lock) ;
75167672 Andy McCall 1 month ago
3618
let lock_path = main_dir
3619
.join ( " .oxy/workspaces" )
3620
.join ( repo.workspace ( ) .to_hex ( ) )
3621
.join ( " lock" ) ;
4e88e774 Andy McCall 1 month ago ↶
3622
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 120 ) ;
6caacfff Andy McCall 1 month ago ↶
3623
let age = | p : & Path| fs:: File:: open( p) .unwrap ( ) .set_modified ( old) .unwrap ( ) ;
3624
fs:: write( & lock_path, std:: process:: id( ) .to_string ( ) ) .unwrap ( ) ;
3625
age ( & lock_path) ;
3626
assert! (
3627
! super :: workspace:: lock_is_stale( & lock_path) ,
3628
" a live process keeps its lock"
3629
) ;
3630
fs:: write( & lock_path, " 4294967295" ) .unwrap ( ) ;
3631
age ( & lock_path) ;
4e88e774 Andy McCall 1 month ago ↶
3632
assert! ( repo.lock_snapshot ( ) .is_ok ( ) , " stale lock taken over" ) ;
3633
}
cdb5beba Andy McCall 1 month ago ↶
3634
3635
# [ test ]
3636
fn attributes_binary_eol_nocompress_lockable ( ) {
3637
hermetic ( ) ;
3638
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3639
let root = dir.path ( ) ;
3640
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
3641
write (
3642
root,
3643
" .oxyattributes" ,
3644
b " *.dat binary nocompress\n *.txt eol=crlf\n assets/** lockable\n " ,
3645
) ;
cdb5beba Andy McCall 1 month ago ↶
3646
write ( root, " plain.dat" , b " no nul bytes here\n " ) ;
3647
write ( root, " notes.txt" , b " one\r \n two\r \n " ) ;
3648
write ( root, " assets/model.bin" , b " \0 model" ) ;
3649
let h = repo.commit ( " attrs" , & [ ] ) .unwrap ( ) ;
3650
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
3651
assert! (
3652
snap.files[ " plain.dat" ] .mode.opaque,
3653
" binary attribute forces opaque without NUL bytes"
3654
) ;
3655
assert! (
3656
matches! ( & snap.files[ " notes.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n " ) ,
3657
" stored with LF"
3658
) ;
3659
assert! (
3660
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
3661
" CRLF on disk equals LF in the state"
3662
) ;
cdb5beba Andy McCall 1 month ago ↶
3663
3664
let r = repo.gc ( true ) .unwrap ( ) ;
3665
assert! ( r.packed.objects > 0 ) ;
3666
3667
fs:: remove_file( root.join ( " notes.txt" ) ) .unwrap ( ) ;
3668
repo.checkout ( & [ h] , true , " co" ) .unwrap ( ) ;
3669
assert_eq! ( fs:: read( root.join ( " notes.txt" ) ) .unwrap ( ) , b " one\r \n two\r \n " ) ;
75167672 Andy McCall 1 month ago
3670
assert! ( fs:: metadata( root.join ( " assets/model.bin" ) )
3671
.unwrap ( )
3672
.permissions ( )
3673
.readonly ( ) ) ;
cdb5beba Andy McCall 1 month ago ↶
3674
assert! ( repo.lock_path ( " plain.dat" ) .is_err ( ) , " not lockable" ) ;
3675
repo.lock_path ( " assets/model.bin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3676
assert! ( ! fs:: metadata( root.join ( " assets/model.bin" ) )
3677
.unwrap ( )
3678
.permissions ( )
3679
.readonly ( ) ) ;
cdb5beba Andy McCall 1 month ago ↶
3680
assert! ( repo.is_locked ( " assets/model.bin" ) ) ;
3681
write ( root, " assets/model.bin" , b " \0 model2" ) ;
3682
repo.commit ( " edit model" , & [ ] ) .unwrap ( ) ;
3683
repo.unlock_path ( " assets/model.bin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3684
assert! ( fs:: metadata( root.join ( " assets/model.bin" ) )
3685
.unwrap ( )
3686
.permissions ( )
3687
.readonly ( ) ) ;
cdb5beba Andy McCall 1 month ago ↶
3688
3689
repo.checkout ( & [ h] , true , " co" ) .unwrap ( ) ;
3690
assert_eq! ( fs:: read( root.join ( " assets/model.bin" ) ) .unwrap ( ) , b " \0 model" ) ;
3691
}
6d3eefbc Andy McCall 1 month ago ↶
3692
3693
# [ cfg ( unix ) ]
3694
# [ test ]
3695
fn hooks_can_veto_observe_and_add_dependencies ( ) {
3696
use std:: os:: unix:: fs:: PermissionsExt;
3697
hermetic ( ) ;
3698
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3699
let root = dir.path ( ) ;
3700
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
3701
let hooks = root.join ( " .oxy/hooks" ) ;
3702
fs:: create_dir_all( & hooks) .unwrap ( ) ;
3703
let hook = | name : & str , body : & str | {
3704
let p = hooks.join ( name) ;
3705
fs:: write( & p, format! ( " #!/bin/sh\n {body} \n " ) ) .unwrap ( ) ;
3706
fs:: set_permissions( & p, fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
3707
} ;
3708
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" ) ;
3709
hook ( " post-commit" , " cat > \" $OXY_ROOT/.last-post-commit\" " ) ;
3710
hook ( " post-checkout" , " cat > \" $OXY_ROOT/.last-post-checkout\" " ) ;
3711
hook ( " pre-push" , " echo pushing blocked >&2; exit 3" ) ;
3712
write ( root, " .oxyignore" , b " .last-*\n " ) ;
3713
write ( root, " a.txt" , b " a\n " ) ;
3714
let err = repo.commit ( " WIP: nope" , & [ ] ) .unwrap_err ( ) .to_string ( ) ;
3715
assert! ( err.contains ( " pre-commit refused: no WIP commits" ) , " {err}" ) ;
3716
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
3717
let pre = fs:: read_to_string( root.join ( " .last-pre-commit" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3718
assert! (
3719
pre.contains ( " \" event\" :\" pre-commit\" " )
3720
& & pre.contains ( " \" path\" :\" a.txt\" " )
3721
& & pre.contains ( " \" message\" :\" first\" " ) ,
3722
" {pre}"
3723
) ;
6d3eefbc Andy McCall 1 month ago ↶
3724
let post = fs:: read_to_string( root.join ( " .last-post-commit" ) ) .unwrap ( ) ;
3725
assert! ( post.contains ( & h1.to_hex ( ) ) , " {post}" ) ;
3726
repo.checkout ( & [ h1] , false , " co" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3727
assert! ( fs:: read_to_string( root.join ( " .last-post-checkout" ) )
3728
.unwrap ( )
3729
.contains ( " post-checkout" ) ) ;
6d3eefbc Andy McCall 1 month ago ↶
3730
repo.config.set ( " remote.origin.url" , " /nowhere" ) .unwrap ( ) ;
3731
repo.save_config ( ) .unwrap ( ) ;
3732
let err = repo.push ( " origin" , false ) .unwrap_err ( ) .to_string ( ) ;
3733
assert! ( err.contains ( " pre-push refused: pushing blocked" ) , " {err}" ) ;
3734
3735
3736
write ( root, " b.txt" , b " b\n " ) ;
75167672 Andy McCall 1 month ago
3737
hook (
3738
" semantic-deps" ,
3739
& format! (
3740
" cat > \" $OXY_ROOT/.last-semantic\" ; echo '{{ \" deps\" : [\" {} \" ]}} '" ,
3741
h1.to_hex ( )
3742
) ,
3743
) ;
6d3eefbc Andy McCall 1 month ago ↶
3744
let h2 = repo.commit ( " b without hook" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3745
assert! (
3746
repo.store ( ) .get_change ( & h2) .unwrap ( ) .deps.is_empty ( ) ,
3747
" hook disabled by default"
3748
) ;
6d3eefbc Andy McCall 1 month ago ↶
3749
repo.config.set ( " hooks.semantic-deps" , " true" ) .unwrap ( ) ;
3750
repo.save_config ( ) .unwrap ( ) ;
3751
write ( root, " c.txt" , b " c\n " ) ;
3752
let h3 = repo.commit ( " c with hook" , & [ ] ) .unwrap ( ) ;
3753
assert_eq! ( repo.store ( ) .get_change ( & h3) .unwrap ( ) .deps, vec! [ h1] ) ;
3754
let sem = fs:: read_to_string( root.join ( " .last-semantic" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3755
assert! (
3756
sem.contains ( " \" touched_paths\" :[\" c.txt\" ]" ) & & sem.contains ( " candidate_deps" ) ,
3757
" {sem}"
3758
) ;
6d3eefbc Andy McCall 1 month ago ↶
3759
assert_eq! ( json_str ( " a\" b\\ c\n " ) , " \" a\\ \" b\\ \\ c\\ n\" " ) ;
3760
}
23b217d7 Andy McCall 1 month ago ↶
3761
3762
# [ test ]
3763
fn amend_into_older_change_and_split ( ) {
3764
hermetic ( ) ;
3765
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3766
let root = dir.path ( ) ;
3767
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
3768
repo.create_identity ( " Ada" , " a@b" , IdentityScope:: User)
3769
.unwrap ( ) ;
23b217d7 Andy McCall 1 month ago ↶
3770
repo.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
3771
write ( root, " a.txt" , b " one\n two\n " ) ;
3772
let h0 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
3773
write ( root, " a.txt" , b " one\n TWO\n " ) ;
3774
let h1 = repo.commit ( " edit two" , & [ ] ) .unwrap ( ) ;
3775
write ( root, " a.txt" , b " one\n TWO\n three\n " ) ;
3776
let h2 = repo.commit ( " add three" , & [ ] ) .unwrap ( ) ;
3777
let id1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) .change_id;
3778
3779
3780
write ( root, " a.txt" , b " one\n TWO!\n three\n " ) ;
3781
let h1b = repo.amend_into ( & h1, None ) .unwrap ( ) ;
3782
assert_ne! ( h1b, h1) ;
3783
let c1b = repo.store ( ) .get_change ( & h1b) .unwrap ( ) ;
3784
assert_eq! ( c1b.change_id, id1) ;
3785
assert_eq! ( c1b.deps, vec! [ h0] ) ;
75167672 Andy McCall 1 month ago
3786
assert! ( matches! (
3787
repo.verify_change ( & h1b) .unwrap ( ) ,
3788
SignatureStatus:: Valid { .. }
3789
) ) ;
23b217d7 Andy McCall 1 month ago ↶
3790
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
3791
assert_eq! ( heads.len ( ) , 1 ) ;
3792
let top = repo.store ( ) .get_change ( & heads[ 0 ] ) .unwrap ( ) ;
3793
assert_eq! ( top.message, " add three" ) ;
75167672 Andy McCall 1 month ago
3794
assert_eq! (
3795
top.deps.iter ( ) .copied ( ) .collect:: < BTreeSet< _ > > ( ) ,
3796
[ h0, h1b] .into_iter ( ) .collect ( ) ,
3797
" depends on the file's creator and the rewritten line"
3798
) ;
23b217d7 Andy McCall 1 month ago ↶
3799
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
3800
assert! (
3801
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n TWO!\n three\n " )
3802
) ;
3803
assert! (
3804
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
3805
" the working copy is clean after folding"
3806
) ;
23b217d7 Andy McCall 1 month ago ↶
3807
3808
let only = repo.load_state ( & [ h1b] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
3809
assert! (
3810
matches! ( & only.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n TWO!\n " )
3811
) ;
23b217d7 Andy McCall 1 month ago ↶
3812
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 ) ;
3813
3814
3815
write ( root, " a.txt" , b " one\n TWO!\n three!\n " ) ;
3816
let err = repo.amend_into ( & h1b, None ) .unwrap_err ( ) .to_string ( ) ;
3817
assert! ( err.contains ( " introduced after" ) , " {err}" ) ;
75167672 Andy McCall 1 month ago
3818
assert! (
3819
repo.snapshot ( " status" ) .unwrap ( ) .statuses.len ( ) = = 1 ,
3820
" nothing was changed by the refusal"
3821
) ;
23b217d7 Andy McCall 1 month ago ↶
3822
write ( root, " a.txt" , b " one\n TWO!\n three\n " ) ;
3823
3824
3825
write ( root, " b.txt" , b " b\n " ) ;
3826
write ( root, " c.txt" , b " c\n " ) ;
3827
let h3 = repo.commit ( " b and c" , & [ ] ) .unwrap ( ) ;
3828
let id3 = repo.store ( ) .get_change ( & h3) .unwrap ( ) .change_id;
3829
let ( p1, p2) = repo.split ( & h3, & [ " b.txt" .into ( ) ] ) .unwrap ( ) ;
3830
let c_1 = repo.store ( ) .get_change ( & p1) .unwrap ( ) ;
3831
let c_2 = repo.store ( ) .get_change ( & p2) .unwrap ( ) ;
3832
assert! ( c_1 .message.contains ( " part 1: b.txt" ) ) ;
3833
assert_eq! ( c_2 .change_id, id3) ;
3834
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
75167672 Andy McCall 1 month ago
3835
assert! (
3836
heads.contains ( & p1) & & heads.contains ( & p2) ,
3837
" independent parts are both heads"
3838
) ;
3839
let snap = repo
3840
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
3841
.unwrap ( )
3842
.materialize ( ) ;
23b217d7 Andy McCall 1 month ago ↶
3843
assert! ( snap.files.contains_key ( " b.txt" ) & & snap.files.contains_key ( " c.txt" ) ) ;
3844
let only1 = repo.load_state ( & [ p1] ) .unwrap ( ) .materialize ( ) ;
3845
assert! ( only1.files.contains_key ( " b.txt" ) & & ! only1.files.contains_key ( " c.txt" ) ) ;
3846
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
3847
assert! ( repo.split ( & p2, & [ " nope.txt" .into ( ) ] ) .is_err ( ) ) ;
3848
let _ = h2;
3849
}
28fc23d7 Andy McCall 1 month ago ↶
3850
3851
# [ test ]
3852
fn identity_lifecycle ( ) {
3853
hermetic ( ) ;
3854
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3855
let root = dir.path ( ) ;
3856
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
3857
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
3858
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
3859
.unwrap ( )
3860
.with_home ( root.join ( " home-a" ) ) ;
28fc23d7 Andy McCall 1 month ago ↶
3861
a.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3862
let ( key_a, _ ) = a
3863
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
3864
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3865
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
3866
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
3867
assert! ( a.policy_init ( ) .unwrap ( ) .applied) ;
3868
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
767ac6cd Andy McCall 19 days ago
3869
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3870
3871
75167672 Andy McCall 1 month ago
3872
let mut e = Repo:: init( & root.join ( " e" ) )
3873
.unwrap ( )
3874
.with_home ( root.join ( " home-e" ) ) ;
28fc23d7 Andy McCall 1 month ago ↶
3875
e.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3876
e.create_identity_with (
3877
" Eve" ,
3878
" [email protected] " ,
3879
IdentityScope:: User,
3880
Some ( Hlc:: now( ) .wall_ms - 1 ) ,
3881
)
3882
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3883
write ( & root.join ( " e" ) , " e.txt" , b " e\n " ) ;
3884
let he = e.commit ( " late" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3885
assert! (
3886
matches! ( e.verify_change ( & he) .unwrap ( ) , SignatureStatus:: Invalid( m) if m.contains ( " expired" ) )
3887
) ;
28fc23d7 Andy McCall 1 month ago ↶
3888
75167672 Andy McCall 1 month ago
3889
let r = e
3890
.renew_identity ( Some ( Hlc:: now( ) .wall_ms + 86_400_000 ) )
3891
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3892
assert_eq! ( e.identity ( & r.new) .unwrap ( ) .previous, Some ( r.old) ) ;
3893
write ( & root.join ( " e" ) , " e.txt" , b " e2\n " ) ;
3894
let he2 = e.commit ( " in time" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3895
assert! (
3896
matches! ( e.verify_change ( & he2) .unwrap ( ) , SignatureStatus:: Valid { key, .. } if key = = r.new)
3897
) ;
28fc23d7 Andy McCall 1 month ago ↶
3898
3899
3900
let r = a.rotate_identity ( ) .unwrap ( ) ;
3901
assert_eq! ( r.old, key_a) ;
3902
assert_eq! ( a.config.author_key, Some ( r.new) ) ;
3903
assert_eq! ( a.identity ( & r.new) .unwrap ( ) .previous, Some ( key_a) ) ;
3904
assert! ( r.policy.as_ref ( ) .unwrap ( ) .applied) ;
3905
let ( _ , sp) = a.current_policy ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3906
assert_eq! (
3907
( sp.policy.serial, sp.policy.roots.clone ( ) ) ,
3908
( 2 , vec! [ r.new] )
3909
) ;
3910
assert! (
3911
! sp.policy.revoked.contains ( & key_a) & & sp.policy.authorizes ( & r.new, " main" ) ,
3912
" superseded, not revoked"
3913
) ;
28fc23d7 Andy McCall 1 month ago ↶
3914
assert! ( sp.policy.succeeds ( & r.new, & key_a) & & ! sp.policy.succeeds ( & key_a, & r.new) ) ;
75167672 Andy McCall 1 month ago
3915
assert! (
3916
matches! ( a.verify_change ( & h1) .unwrap ( ) , SignatureStatus:: Valid { key, revoked: false , .. } if key = = key_a)
3917
) ;
28fc23d7 Andy McCall 1 month ago ↶
3918
write ( & root.join ( " a" ) , " a.txt" , b " a2\n " ) ;
3919
let h2 = a.commit ( " two" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3920
assert! (
3921
matches! ( a.verify_change ( & h2) .unwrap ( ) , SignatureStatus:: Valid { key, revoked: false , .. } if key = = r.new)
3922
) ;
28fc23d7 Andy McCall 1 month ago ↶
3923
3924
let res = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3925
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3926
assert! ( res.iter ( ) .all ( | x | x.ok ) , " {res:?}" ) ;
3927
assert_eq! ( origin.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 2 ) ;
3928
let key_a = r.new;
3929
3930
3931
75167672 Andy McCall 1 month ago
3932
let mut b = Repo:: clone( & url, & root.join ( " b" ) )
3933
.unwrap ( )
3934
.with_home ( root.join ( " home-b" ) ) ;
3935
let ( key_b, id_b) = b
3936
.create_identity ( " Bob" , " [email protected] " , IdentityScope:: User)
3937
.unwrap ( ) ;
7ba155f2 Andy McCall 1 month ago ↶
3938
let r = a.delegate_add ( id_b.clone ( ) , Scope:: ALL , None ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3939
assert_eq! ( r.old, key_a) ;
3940
let key_a2 = r.new;
75167672 Andy McCall 1 month ago
3941
assert_eq! (
7ba155f2 Andy McCall 1 month ago ↶
3942
a.identity ( & key_a2) .unwrap ( ) .delegates,
3943
vec! [ Delegate:: full( key_b) ]
3944
) ;
3945
assert_eq! (
75167672 Andy McCall 1 month ago
3946
a.identity ( & key_a2) .unwrap ( ) .ed25519_pk,
3947
a.identity ( & key_a) .unwrap ( ) .ed25519_pk
7ba155f2 Andy McCall 1 month ago ↶
3948
) ;
3949
assert! (
3950
a.delegate_add ( id_b.clone ( ) , Scope:: ALL , None ) .is_err ( ) ,
3951
" already a delegate"
75167672 Andy McCall 1 month ago
3952
) ;
28fc23d7 Andy McCall 1 month ago ↶
3953
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | x | x.ok ) ) ;
767ac6cd Andy McCall 19 days ago
3954
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3955
write ( & root.join ( " a" ) , " a.txt" , b " a3\n " ) ;
3956
let h3 = a.commit ( " three" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3957
assert! (
3958
matches! ( a.verify_change ( & h3) .unwrap ( ) , SignatureStatus:: Valid { key, .. } if key = = key_a2) ,
3959
" the same secret signs under the new KeyId"
3960
) ;
28fc23d7 Andy McCall 1 month ago ↶
3961
3962
a.set_user_config ( " core.sign" , " seal" ) .unwrap ( ) ;
3963
write ( & root.join ( " a" ) , " a.txt" , b " a4\n " ) ;
3964
let h4 = a.commit ( " four" , & [ ] ) .unwrap ( ) ;
3965
assert_eq! ( a.verify_change ( & h4) .unwrap ( ) , SignatureStatus:: Unsigned) ;
3966
let ( _ , sk_b) = b.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3967
a.store ( )
3968
.put_signature ( & h4, & sk_b.sign ( Domain:: Change, & key_b, & h4) )
3969
.unwrap ( ) ;
3970
assert! (
3971
matches! ( a.verify_change ( & h4) .unwrap ( ) , SignatureStatus:: Valid { key, .. } if key = = key_b)
3972
) ;
28fc23d7 Andy McCall 1 month ago ↶
3973
a.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
3974
3975
let r = a.delegate_remove ( & key_b) .unwrap ( ) ;
3976
assert! ( a.identity ( & r.new) .unwrap ( ) .delegates.is_empty ( ) ) ;
3977
assert! ( a.delegate_remove ( & key_b) .is_err ( ) ) ;
3978
let key_a3 = r.new;
3979
let res = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3980
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3981
assert! ( res.iter ( ) .all ( | x | x.ok ) , " {res:?}" ) ;
3982
3983
3984
3985
assert! ( a.policy_root_add ( id_b.clone ( ) ) .unwrap ( ) .applied) ;
3986
assert! ( a.policy_root_add ( id_b.clone ( ) ) .is_err ( ) ) ;
3987
assert! ( a.policy_set_threshold ( 3 ) .is_err ( ) ) ;
3988
assert! ( a.policy_set_threshold ( 2 ) .unwrap ( ) .applied) ;
3989
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | x | x.ok ) ) ;
767ac6cd Andy McCall 19 days ago
3990
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3991
b.pull ( " origin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3992
assert_eq! (
3993
b.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.root_threshold,
3994
2
3995
) ;
3996
let mut c = Repo:: init( & root.join ( " c" ) )
3997
.unwrap ( )
3998
.with_home ( root.join ( " home-c" ) ) ;
3999
let ( key_c, id_c) = c
4000
.create_identity ( " Cy" , " [email protected] " , IdentityScope:: User)
4001
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4002
let serial_before = a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial;
4003
let proposal = a.policy_grant ( id_c, None ) .unwrap ( ) ;
4004
assert! ( ! proposal.applied & & proposal.sigs.len ( ) = = 1 ) ;
4005
assert_eq! ( proposal.policy.serial, serial_before + 1 ) ;
75167672 Andy McCall 1 month ago
4006
assert_eq! (
4007
a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial,
4008
serial_before,
4009
" a proposal changes nothing yet"
4010
) ;
28fc23d7 Andy McCall 1 month ago ↶
4011
4012
let bytes = proposal.signed ( ) .encode ( ) ;
75167672 Andy McCall 1 month ago
4013
let signed = b
4014
.policy_sign ( SignedPolicy:: decode( & bytes) .unwrap ( ) )
4015
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4016
assert! ( signed.applied & & signed.sigs.len ( ) = = 2 ) ;
75167672 Andy McCall 1 month ago
4017
assert! ( b
4018
.current_policy ( )
4019
.unwrap ( )
4020
.unwrap ( )
4021
.1
4022
.policy
4023
.authorizes ( & key_c, " main" ) ) ;
4024
assert! (
4025
b.policy_sign ( signed.signed ( ) ) .is_err ( ) ,
4026
" a stale proposal is refused"
4027
) ;
28fc23d7 Andy McCall 1 month ago ↶
4028
75167672 Andy McCall 1 month ago
4029
assert! ( b
4030
.push ( " origin" , false )
4031
.unwrap ( )
4032
.iter ( )
4033
.any ( | x | x.name = = " policy" & & x.ok ) ) ;
767ac6cd Andy McCall 19 days ago
4034
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4035
a.pull ( " origin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4036
assert_eq! (
4037
a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial,
4038
serial_before + 1
4039
) ;
28fc23d7 Andy McCall 1 month ago ↶
4040
4041
assert! ( a.policy_revoke ( & key_a3) .is_err ( ) ) ;
75167672 Andy McCall 1 month ago
4042
let u = a
4043
.revoke_identity ( & key_c, " left the project" )
4044
.unwrap ( )
4045
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4046
assert! ( ! u.applied) ;
4047
assert! ( a.is_revoked ( & key_c) .unwrap ( ) ) ;
4048
assert! ( ! b.is_revoked ( & key_c) .unwrap ( ) ) ;
4049
let u = b.policy_sign ( u.signed ( ) ) .unwrap ( ) ;
4050
assert! ( u.applied & & u.policy.is_revoked ( & key_c) ) ;
4051
assert! ( b.is_revoked ( & key_c) .unwrap ( ) ) ;
4052
75167672 Andy McCall 1 month ago
4053
assert! (
4054
b.policy_revoke ( & key_a3) .is_err ( ) ,
4055
" would leave one root for a threshold of 2"
4056
) ;
28fc23d7 Andy McCall 1 month ago ↶
4057
4058
let key_c_own = c.config.author_key.unwrap ( ) ;
75167672 Andy McCall 1 month ago
4059
assert! ( c
4060
.revoke_identity ( & key_c_own, " lost laptop" )
4061
.unwrap ( )
4062
.is_none ( ) ) ;
28fc23d7 Andy McCall 1 month ago ↶
4063
assert! ( c.is_revoked ( & key_c_own) .unwrap ( ) ) ;
4064
assert_eq! ( key_c_own, key_c) ;
4065
}
4066
6e723b81 Andy McCall 1 month ago ↶
4067
# [ test ]
4068
fn renames_are_detected_and_tracked_as_moves ( ) {
4069
hermetic ( ) ;
4070
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4071
let root = dir.path ( ) ;
4072
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
4073
write (
4074
root,
4075
" src/old.rs" ,
4076
b " fn a() {}\n fn b() {}\n fn c() {}\n fn d() {}\n " ,
4077
) ;
6e723b81 Andy McCall 1 month ago ↶
4078
write ( root, " img.bin" , b " \x89 PNG\0 \0 \x01 \x02 " ) ;
4079
write ( root, " tiny.txt" , b " x\n " ) ;
4080
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4081
let node_old = repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) .files[ " src/old.rs" ] .node;
4082
let node_img = repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) .files[ " img.bin" ] .node;
4083
4084
4085
4086
fs:: remove_file( root.join ( " src/old.rs" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4087
write (
4088
root,
4089
" lib/new.rs" ,
4090
b " fn a() {}\n fn b() {}\n fn c() {}\n fn e() {}\n " ,
4091
) ;
6e723b81 Andy McCall 1 month ago ↶
4092
fs:: remove_file( root.join ( " img.bin" ) ) .unwrap ( ) ;
4093
write ( root, " assets/img.bin" , b " \x89 PNG\0 \0 \x01 \x02 " ) ;
4094
fs:: remove_file( root.join ( " tiny.txt" ) ) .unwrap ( ) ;
4095
write ( root, " other.txt" , b " completely different\n " ) ;
4096
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4097
let statuses: Vec< ( & str , ChangeStatus) > = snap
4098
.statuses
4099
.iter ( )
4100
.map ( | s | ( s.path.as_str ( ) , s.status) )
4101
.collect ( ) ;
6e723b81 Andy McCall 1 month ago ↶
4102
assert_eq! (
4103
statuses,
75167672 Andy McCall 1 month ago
4104
vec! [
4105
( " assets/img.bin" , ChangeStatus:: Renamed) ,
4106
( " lib/new.rs" , ChangeStatus:: Renamed) ,
4107
( " other.txt" , ChangeStatus:: Added) ,
4108
( " src" , ChangeStatus:: Removed) ,
4109
( " tiny.txt" , ChangeStatus:: Removed)
4110
]
6e723b81 Andy McCall 1 month ago ↶
4111
) ;
4112
let h2 = repo.commit ( " move things" , & [ ] ) .unwrap ( ) ;
4113
let c = repo.store ( ) .get_change ( & h2) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4114
let moves: Vec< _ > = c
4115
.tree_ops
4116
.iter ( )
4117
.filter ( | op | matches! ( op, TreeOp:: Move { .. } ) )
4118
.collect ( ) ;
6e723b81 Andy McCall 1 month ago ↶
4119
assert_eq! ( moves.len ( ) , 2 , " {:?}" , c.tree_ops) ;
75167672 Andy McCall 1 month ago
4120
assert! ( c.tree_ops.iter ( ) .any (
4121
| op | matches! ( op, TreeOp:: Move { node, name, .. } if * node = = node_old & & name = = " new.rs" )
4122
) ) ;
4123
assert! ( c.tree_ops.iter ( ) .any (
4124
| op | matches! ( op, TreeOp:: Move { node, name, .. } if * node = = node_img & & name = = " img.bin" )
4125
) ) ;
6e723b81 Andy McCall 1 month ago ↶
4126
let snap = repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
4127
assert_eq! (
4128
snap.files[ " lib/new.rs" ] .node, node_old,
4129
" the node survives the rename"
4130
) ;
4131
assert! (
4132
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 " )
4133
) ;
6e723b81 Andy McCall 1 month ago ↶
4134
assert! ( ! snap.files.contains_key ( " src/old.rs" ) & & ! snap.files.contains_key ( " src" ) ) ;
75167672 Andy McCall 1 month ago
4135
assert! ( render_change (
4136
& c,
4137
& repo.load_state ( & [ h1] ) .unwrap ( ) ,
4138
& repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( )
4139
)
4140
.contains ( " R " ) ) ;
6e723b81 Andy McCall 1 month ago ↶
4141
4142
let other = tempfile:: tempdir( ) .unwrap ( ) ;
4143
let clone = Repo:: clone( & root.to_string_lossy ( ) , other.path ( ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4144
assert_eq! (
4145
fs:: read( other.path ( ) .join ( " lib/new.rs" ) ) .unwrap ( ) ,
4146
b " fn a() {}\n fn b() {}\n fn c() {}\n fn e() {}\n "
4147
) ;
6e723b81 Andy McCall 1 month ago ↶
4148
assert! ( ! other.path ( ) .join ( " src" ) .exists ( ) ) ;
4149
assert! ( ! clone.snapshot ( " status" ) .unwrap ( ) .changed) ;
4150
4151
assert_eq! ( similarity ( b " a\n b\n " , b " a\n b\n " ) , 100 ) ;
4152
assert_eq! ( similarity ( b " a\n b\n c\n d\n " , b " a\n b\n x\n y\n " ) , 50 ) ;
4153
assert_eq! ( similarity ( b " hello\n " , b " world\n " ) , 0 ) ;
4154
assert_eq! ( similarity ( b " " , b " x\n " ) , 0 ) ;
4155
}
4156
30ea9c1e Andy McCall 1 month ago ↶
4157
# [ test ]
4158
fn git_fetch_and_push_round_trip ( ) {
4159
hermetic ( ) ;
75167672 Andy McCall 1 month ago
4160
if std:: process:: Command:: new( " git" )
4161
.arg ( " --version" )
4162
.output ( )
4163
.is_err ( )
4164
{
30ea9c1e Andy McCall 1 month ago ↶
4165
eprintln! ( " git not installed; skipping" ) ;
4166
return ;
4167
}
4168
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4169
let root = dir.path ( ) ;
4170
let git_in = | d : & Path, args : & [& str ]| -> String {
4171
let out = std:: process:: Command:: new( " git" )
4172
.arg ( " -C" )
4173
.arg ( d)
75167672 Andy McCall 1 month ago
4174
.args ( [
4175
" -c" ,
4176
" user.name=Grace" ,
4177
" -c" ,
4178
" [email protected] " ,
4179
" -c" ,
4180
" commit.gpgsign=false" ,
4181
] )
30ea9c1e Andy McCall 1 month ago ↶
4182
.args ( args)
4183
.output ( )
4184
.unwrap ( ) ;
75167672 Andy McCall 1 month ago
4185
assert! (
4186
out.status.success ( ) ,
4187
" git {args:?}: {}" ,
4188
String :: from_utf8_lossy( & out.stderr)
4189
) ;
30ea9c1e Andy McCall 1 month ago ↶
4190
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
4191
} ;
4192
4193
let remote = root.join ( " remote.git" ) ;
4194
fs:: create_dir_all( & remote) .unwrap ( ) ;
4195
git_in ( & remote, & [ " init" , " -q" , " --bare" , " -b" , " main" ] ) ;
4196
let work = root.join ( " work" ) ;
75167672 Andy McCall 1 month ago
4197
git_in (
4198
root,
4199
& [
4200
" clone" ,
4201
" -q" ,
4202
remote.to_str ( ) .unwrap ( ) ,
4203
work.to_str ( ) .unwrap ( ) ,
4204
] ,
4205
) ;
30ea9c1e Andy McCall 1 month ago ↶
4206
git_in ( & work, & [ " checkout" , " -q" , " -b" , " main" ] ) ;
4207
write ( & work, " README.md" , b " # Project\n " ) ;
4208
write ( & work, " src/lib.rs" , b " pub fn a() {}\n " ) ;
4209
git_in ( & work, & [ " add" , " ." ] ) ;
4210
git_in ( & work, & [ " commit" , " -q" , " -m" , " initial" ] ) ;
4211
write ( & work, " src/lib.rs" , b " pub fn a() {}\n pub fn b() {}\n " ) ;
4212
git_in ( & work, & [ " commit" , " -q" , " -am" , " second" ] ) ;
4213
git_in ( & work, & [ " push" , " -q" , " origin" , " main" ] ) ;
4214
let tip1 = git_in ( & work, & [ " rev-parse" , " HEAD" ] ) ;
4215
4216
4217
let o = root.join ( " oxy" ) ;
4218
let mut repo = Repo:: init( & o) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
4219
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
4220
.unwrap ( ) ;
30ea9c1e Andy McCall 1 month ago ↶
4221
let url = remote.to_string_lossy ( ) .to_string ( ) ;
4222
let r = repo.git_fetch ( & url, " main" ) .unwrap ( ) ;
4223
assert_eq! ( ( r.commits, r.heads) , ( 2 , 1 ) ) ;
75167672 Andy McCall 1 month ago
4224
assert_eq! (
4225
fs:: read( o.join ( " src/lib.rs" ) ) .unwrap ( ) ,
4226
b " pub fn a() {}\n pub fn b() {}\n "
4227
) ;
30ea9c1e Andy McCall 1 month ago ↶
4228
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
75167672 Andy McCall 1 month ago
4229
assert_eq! (
4230
repo.git_fetch ( & url, " main" ) .unwrap ( ) .commits,
4231
0 ,
4232
" nothing new"
4233
) ;
30ea9c1e Andy McCall 1 month ago ↶
4234
4235
75167672 Andy McCall 1 month ago
4236
write (
4237
& o,
4238
" src/lib.rs" ,
4239
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n " ,
4240
) ;
30ea9c1e Andy McCall 1 month ago ↶
4241
write ( & o, " NOTES.md" , b " from oxy\n " ) ;
4242
let h_oxy = repo.commit ( " add c and notes" , & [ ] ) .unwrap ( ) ;
4243
let r = repo.git_push ( & url, " main" ) .unwrap ( ) ;
4244
assert_eq! ( r.exported, 1 , " {r:?}" ) ;
4245
git_in ( & work, & [ " pull" , " -q" , " --ff-only" , " origin" , " main" ] ) ;
75167672 Andy McCall 1 month ago
4246
assert_eq! (
4247
git_in ( & work, & [ " log" , " --format=%s" , " main" ] ) ,
4248
" add c and notes\n second\n initial"
4249
) ;
30ea9c1e Andy McCall 1 month ago ↶
4250
assert_eq! ( git_in ( & work, & [ " rev-parse" , " HEAD~1" ] ) , tip1) ;
4251
assert_eq! ( fs:: read( work.join ( " NOTES.md" ) ) .unwrap ( ) , b " from oxy\n " ) ;
4252
assert_eq! ( git_in ( & work, & [ " log" , " -1" , " --format=%an" , " main" ] ) , " Ada" ) ;
75167672 Andy McCall 1 month ago
4253
assert! ( git_in ( & work, & [ " log" , " -1" , " --format=%B" , " main" ] )
4254
.contains ( & format! ( " Oxy-Hash: {} " , h_oxy.to_hex ( ) ) ) ) ;
4255
assert_eq! (
4256
repo.git_push ( & url, " main" ) .unwrap ( ) .exported,
4257
0 ,
4258
" already pushed"
4259
) ;
30ea9c1e Andy McCall 1 month ago ↶
4260
4261
4262
4263
write ( & work, " README.md" , b " # Project\n \n More.\n " ) ;
4264
git_in ( & work, & [ " commit" , " -q" , " -am" , " readme from git" ] ) ;
4265
git_in ( & work, & [ " push" , " -q" , " origin" , " main" ] ) ;
4266
let tip2 = git_in ( & work, & [ " rev-parse" , " HEAD" ] ) ;
75167672 Andy McCall 1 month ago
4267
assert! ( repo
4268
.git_push ( & url, " main" )
4269
.unwrap_err ( )
4270
.to_string ( )
4271
.contains ( " fetch" ) ) ;
30ea9c1e Andy McCall 1 month ago ↶
4272
let r = repo.git_fetch ( & url, " main" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4273
assert_eq! (
4274
( r.commits, r.heads) ,
4275
( 1 , 2 ) ,
4276
" the Git change and the oxy change commute"
4277
) ;
4278
assert_eq! (
4279
fs:: read( o.join ( " README.md" ) ) .unwrap ( ) ,
4280
b " # Project\n \n More.\n "
4281
) ;
30ea9c1e Andy McCall 1 month ago ↶
4282
assert_eq! ( fs:: read( o.join ( " NOTES.md" ) ) .unwrap ( ) , b " from oxy\n " ) ;
4283
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4284
4285
write ( & o, " NOTES.md" , b " from oxy\n again\n " ) ;
4286
repo.commit ( " notes again" , & [ ] ) .unwrap ( ) ;
4287
assert_eq! ( repo.git_push ( & url, " main" ) .unwrap ( ) .exported, 1 ) ;
4288
git_in ( & work, & [ " pull" , " -q" , " --ff-only" , " origin" , " main" ] ) ;
4289
assert_eq! ( git_in ( & work, & [ " rev-parse" , " HEAD~1" ] ) , tip2) ;
75167672 Andy McCall 1 month ago
4290
assert_eq! (
4291
fs:: read( work.join ( " NOTES.md" ) ) .unwrap ( ) ,
4292
b " from oxy\n again\n "
4293
) ;
4294
assert_eq! (
4295
git_in ( & work, & [ " log" , " --format=%s" , " main" ] )
4296
.lines ( )
4297
.count ( ) ,
4298
5
4299
) ;
30ea9c1e Andy McCall 1 month ago ↶
4300
}
4301
2b872d07 Andy McCall 1 month ago ↶
4302
4303
4304
# [ test ]
4305
# [ ignore ]
4306
fn bench_one_gibibyte_file ( ) {
4307
hermetic ( ) ;
4308
4309
let dir = match std:: env:: var_os( " OXY_BENCH_DIR" ) {
4310
Some ( d) => tempfile:: tempdir_in( d) .unwrap ( ) ,
4311
None => tempfile:: tempdir( ) .unwrap ( ) ,
4312
} ;
4313
let root = dir.path ( ) ;
75167672 Andy McCall 1 month ago
4314
let repo = Repo:: init( & root.join ( " r" ) )
4315
.unwrap ( )
4316
.with_home ( root.join ( " home" ) ) ;
2b872d07 Andy McCall 1 month ago ↶
4317
let size = 1 usize < < 30 ;
4318
let mut data = vec! [ 0 u8 ; size] ;
4319
let mut x = 0x9e3779b97f4a7c15 u64 ;
4320
for chunk in data.chunks_mut ( 8 ) {
4321
x ^ = x < < 13 ;
4322
x ^ = x > > 7 ;
4323
x ^ = x < < 17 ;
4324
let b = x.to_le_bytes ( ) ;
4325
chunk.copy_from_slice ( & b[ .. chunk.len ( ) ] ) ;
4326
}
4327
let t = std:: time:: Instant:: now( ) ;
4328
write ( & root.join ( " r" ) , " big.bin" , & data) ;
4329
eprintln! ( " write 1 GiB to disk: {:.2?}" , t.elapsed ( ) ) ;
4330
let t = std:: time:: Instant:: now( ) ;
4331
let h = repo.commit ( " big" , & [ ] ) .unwrap ( ) ;
4332
eprintln! ( " commit (chunk + hash + store): {:.2?}" , t.elapsed ( ) ) ;
4333
let t = std:: time:: Instant:: now( ) ;
4334
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4335
eprintln! ( " status on a clean 1 GiB working copy: {:.2?}" , t.elapsed ( ) ) ;
4336
let t = std:: time:: Instant:: now( ) ;
4337
let objects = repo.store ( ) .list ( ) .unwrap ( ) .len ( ) ;
4338
eprintln! ( " objects stored: {objects} ({:.2?} to list)" , t.elapsed ( ) ) ;
4339
let t = std:: time:: Instant:: now( ) ;
4340
let g = repo.gc ( true ) .unwrap ( ) ;
4341
eprintln! ( " gc (pack): {:.2?} {g:?}" , t.elapsed ( ) ) ;
4342
let t = std:: time:: Instant:: now( ) ;
4343
let clone = Repo:: clone( & root.join ( " r" ) .to_string_lossy ( ) , & root.join ( " c" ) ) .unwrap ( ) ;
4344
eprintln! ( " clone (fetch + checkout): {:.2?}" , t.elapsed ( ) ) ;
4345
let t = std:: time:: Instant:: now( ) ;
4346
let back = fs:: read( root.join ( " c/big.bin" ) ) .unwrap ( ) ;
4347
assert_eq! ( blake3:: hash( & back) , blake3:: hash( & data) ) ;
4348
eprintln! ( " read back and verify: {:.2?}" , t.elapsed ( ) ) ;
4349
assert_eq! ( clone.bookmark_heads ( ) .unwrap ( ) , vec! [ h] ) ;
4350
let store_bytes: u64 = walkdir_size ( & root.join ( " r/.oxy/objects" ) ) ;
75167672 Andy McCall 1 month ago
4351
eprintln! (
4352
" store size: {:.1} MiB for 1 GiB of data" ,
4353
store_bytes as f64 / ( 1024. 0 * 1024. 0 )
4354
) ;
2b872d07 Andy McCall 1 month ago ↶
4355
}
4356
4357
fn walkdir_size ( p : & Path) -> u64 {
4358
let mut total = 0 ;
4359
if let Ok ( rd) = fs:: read_dir( p) {
4360
for e in rd.flatten ( ) {
4361
let path = e.path ( ) ;
4362
if path.is_dir ( ) {
4363
total + = walkdir_size ( & path) ;
4364
} else if let Ok ( m) = fs:: metadata( & path) {
4365
total + = m.len ( ) ;
4366
}
4367
}
4368
}
4369
total
4370
}
4371
b7f102fc Andy McCall 1 month ago ↶
4372
4373
4374
# [ cfg ( unix ) ]
4375
# [ test ]
4376
fn stat_cache_skips_reading_unchanged_opaque_files ( ) {
4377
use std:: os:: unix:: fs:: PermissionsExt;
4378
hermetic ( ) ;
4379
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4380
let root = dir.path ( ) ;
4381
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4382
let mut big = vec! [ 0 u8 ; 300 * 1024 ] ;
4383
for ( i, b) in big.iter_mut ( ) .enumerate ( ) {
4384
* b = ( i as u32 ) .wrapping_mul ( 2654435761 ) .to_le_bytes ( ) [ 1 ] ;
4385
}
4386
big[ 0 ] = 0 ;
4387
write ( root, " big.bin" , & big) ;
4388
write ( root, " a.txt" , b " text\n " ) ;
4389
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4390
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4391
assert! (
4392
m.entries[ " big.bin" ] .blob.is_some ( ) ,
4393
" opaque files record their blob"
4394
) ;
b7f102fc Andy McCall 1 month ago ↶
4395
assert! ( m.entries[ " a.txt" ] .blob.is_none ( ) ) ;
4396
let blob = m.entries[ " big.bin" ] .blob.unwrap ( ) ;
4397
assert! ( repo.store ( ) .has ( & blob) ) ;
4398
4399
4400
4401
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
75167672 Andy McCall 1 month ago
4402
fs:: File:: options( )
4403
.write ( true )
4404
.open ( root.join ( " big.bin" ) )
4405
.unwrap ( )
4406
.set_modified ( old)
4407
.unwrap ( ) ;
b7f102fc Andy McCall 1 month ago ↶
4408
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4409
4410
fs:: set_permissions( root.join ( " big.bin" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
4411
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4412
assert! (
4413
! snap.changed & & snap.statuses.is_empty ( ) ,
4414
" {:?}" ,
4415
snap.statuses
4416
) ;
b7f102fc Andy McCall 1 month ago ↶
4417
fs:: set_permissions( root.join ( " big.bin" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
4418
4419
let mut big2 = big.clone ( ) ;
4420
big2[ 10 ] ^ = 0xff ;
4421
write ( root, " big.bin" , & big2) ;
4422
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4423
assert_eq! (
4424
snap.statuses
4425
.iter ( )
4426
.map ( | s | ( s.path.as_str ( ) , s.status) )
4427
.collect:: < Vec< _ > > ( ) ,
4428
vec! [ ( " big.bin" , ChangeStatus:: Modified) ]
4429
) ;
b7f102fc Andy McCall 1 month ago ↶
4430
let h2 = repo.commit ( " edit" , & [ ] ) .unwrap ( ) ;
4431
let files = repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
4432
assert_eq! (
4433
files[ " big.bin" ] .content,
4434
FileContent:: Opaque {
4435
blobs: vec! [ Repo:: blob_ref( & big2) ]
4436
}
4437
) ;
b7f102fc Andy McCall 1 month ago ↶
4438
4439
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
4440
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
4441
assert_eq! ( m.entries[ " big.bin" ] .blob, Some ( Repo:: blob_ref( & big) ) ) ;
4442
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4443
}
4444
50a046e4 Andy McCall 1 month ago ↶
4445
# [ test ]
4446
fn status_json_lists_conflicts_with_contributors ( ) {
4447
hermetic ( ) ;
4448
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4449
let root = dir.path ( ) ;
4450
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4451
write ( root, " a.txt" , b " one\n " ) ;
4452
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4453
write ( root, " a.txt" , b " one\n left\n " ) ;
4454
let ha = repo.commit ( " left" , & [ ] ) .unwrap ( ) ;
4455
repo.checkout ( & [ h1] , true , " checkout" ) .unwrap ( ) ;
4456
write ( root, " a.txt" , b " one\n right\n " ) ;
4457
let hb = repo.commit ( " right" , & [ ] ) .unwrap ( ) ;
4458
repo.checkout ( & [ ha, hb] , true , " checkout" ) .unwrap ( ) ;
4459
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4460
assert! ( snap.base_snapshot.is_conflicted ( ) ) ;
4461
let json = repo.status_json ( & snap) .unwrap ( ) ;
4462
let ida = repo.store ( ) .get_change ( & ha) .unwrap ( ) .change_id.to_hex ( ) ;
4463
let idb = repo.store ( ) .get_change ( & hb) .unwrap ( ) .change_id.to_hex ( ) ;
4464
assert! ( json.contains ( " \" conflicted\" :true" ) , " {json}" ) ;
75167672 Andy McCall 1 month ago
4465
assert! (
4466
json.contains ( " \" kind\" :\" text\" ,\" path\" :\" a.txt\" " ) ,
4467
" {json}"
4468
) ;
50a046e4 Andy McCall 1 month ago ↶
4469
assert! ( json.contains ( & ida) & & json.contains ( & idb) , " {json}" ) ;
4470
assert! ( json.contains ( " \" resolved\" :false" ) , " {json}" ) ;
4471
assert! ( json.starts_with ( ' {' ) & & json.ends_with ( ' }' ) ) ;
4472
4473
write ( root, " a.txt" , b " one\n left\n right\n " ) ;
4474
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4475
let json = repo.status_json ( & snap) .unwrap ( ) ;
4476
assert! ( json.contains ( " \" resolved\" :true" ) , " {json}" ) ;
4477
repo.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
4478
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4479
let json = repo.status_json ( & snap) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4480
assert! (
4481
json.contains ( " \" clean\" :true,\" conflicted\" :false,\" paths\" :[],\" conflicts\" :[]" ) ,
4482
" {json}"
4483
) ;
50a046e4 Andy McCall 1 month ago ↶
4484
}
4485
9ac1fbe2 Andy McCall 1 month ago ↶
4486
# [ test ]
4487
fn ignore_rules_do_not_untrack_files ( ) {
4488
hermetic ( ) ;
4489
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4490
let root = dir.path ( ) ;
4491
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4492
write ( root, " src.c" , b " int x;\n " ) ;
4493
write ( root, " build/out.o" , b " \0 obj" ) ;
4494
write ( root, " sub/keep.log" , b " keep\n " ) ;
4495
write ( root, " sub/junk.log" , b " junk\n " ) ;
4496
write ( root, " .oxyignore" , b " /build\n *.log\n !sub/keep.log\n " ) ;
4497
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4498
let added: Vec< & str > = snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect ( ) ;
75167672 Andy McCall 1 month ago
4499
assert_eq! (
4500
added,
4501
vec! [ " .oxyignore" , " src.c" , " sub/keep.log" ] ,
4502
" anchored, glob, and negated rules"
4503
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4504
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4505
4506
write ( root, " .oxyignore" , b " /build\n *.log\n !sub/keep.log\n src.c\n " ) ;
4507
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4508
assert_eq! (
4509
snap.statuses
4510
.iter ( )
4511
.map ( | s | s.path.as_str ( ) )
4512
.collect:: < Vec< _ > > ( ) ,
4513
vec! [ " .oxyignore" ]
4514
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4515
let h2 = repo.commit ( " ignore src.c" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4516
assert! ( repo
4517
.load_state ( & [ h2] )
4518
.unwrap ( )
4519
.materialize ( )
4520
.files
4521
.contains_key ( " src.c" ) ) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4522
4523
assert_eq! ( repo.quick_dirty ( ) .unwrap ( ) , Some ( false ) ) ;
4524
write ( root, " src.c" , b " int x, y;\n " ) ;
4525
assert_eq! ( repo.quick_dirty ( ) .unwrap ( ) , Some ( true ) ) ;
4526
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4527
assert_eq! (
4528
snap.statuses
4529
.iter ( )
4530
.map ( | s | ( s.path.as_str ( ) , s.status) )
4531
.collect:: < Vec< _ > > ( ) ,
4532
vec! [ ( " src.c" , ChangeStatus:: Modified) ]
4533
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4534
let h3 = repo.commit ( " edit src.c" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4535
assert! (
4536
matches! ( & repo.load_state ( & [ h3] ) .unwrap ( ) .materialize ( ) .files[ " src.c" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " int x, y;\n " )
4537
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4538
4539
fs:: remove_file( root.join ( " src.c" ) ) .unwrap ( ) ;
4540
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4541
assert_eq! (
4542
snap.statuses
4543
.iter ( )
4544
.map ( | s | ( s.path.as_str ( ) , s.status) )
4545
.collect:: < Vec< _ > > ( ) ,
4546
vec! [ ( " src.c" , ChangeStatus:: Removed) ]
4547
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4548
repo.checkout ( & [ h3] , true , " checkout" ) .unwrap ( ) ;
4549
assert_eq! ( fs:: read( root.join ( " src.c" ) ) .unwrap ( ) , b " int x, y;\n " ) ;
4550
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4551
let _ = h1;
4552
}
4553
b837d2b5 Andy McCall 1 month ago ↶
4554
# [ test ]
4555
fn split_works_on_non_heads_by_node ( ) {
4556
hermetic ( ) ;
4557
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4558
let root = dir.path ( ) ;
4559
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4560
write ( root, " a.txt" , b " one\n " ) ;
4561
write ( root, " junk/x.lock" , b " lock\n " ) ;
4562
let h1 = repo.commit ( " work plus junk" , & [ ] ) .unwrap ( ) ;
4563
write ( root, " a.txt" , b " one\n two\n " ) ;
4564
let h2 = repo.commit ( " more work" , & [ ] ) .unwrap ( ) ;
4565
assert_eq! ( repo.store ( ) .get_change ( & h2) .unwrap ( ) .deps, vec! [ h1] ) ;
4566
let id1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) .change_id;
4567
4568
let ( hj, h1b) = repo.split ( & h1, & [ " junk" .into ( ) ] ) .unwrap ( ) ;
4569
assert_ne! ( h1b, h1) ;
75167672 Andy McCall 1 month ago
4570
assert_eq! (
4571
repo.store ( ) .get_change ( & h1b) .unwrap ( ) .change_id,
4572
id1,
4573
" the rest keeps the ChangeId"
4574
) ;
b837d2b5 Andy McCall 1 month ago ↶
4575
let junk = repo.store ( ) .get_change ( & hj) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4576
assert! ( junk.tree_ops.iter ( ) .all (
4577
| op | matches! ( op, TreeOp:: AddNode { name, .. } if name = = " junk" | | name = = " x.lock" )
4578
) ) ;
b837d2b5 Andy McCall 1 month ago ↶
4579
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4580
assert! (
4581
heads.contains ( & hj) ,
4582
" the split-off part is a head of its own"
4583
) ;
b837d2b5 Andy McCall 1 month ago ↶
4584
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
4585
assert! (
4586
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n " ) ,
4587
" the dependent was rewritten onto the new hash"
4588
) ;
b837d2b5 Andy McCall 1 month ago ↶
4589
assert! ( snap.files.contains_key ( " junk/x.lock" ) ) ;
75167672 Andy McCall 1 month ago
4590
let h2b = heads
4591
.iter ( )
4592
.find ( | h | repo.store ( ) .get_change ( h) .unwrap ( ) .message = = " more work" )
4593
.copied ( )
4594
.unwrap ( ) ;
b837d2b5 Andy McCall 1 month ago ↶
4595
assert_eq! ( repo.store ( ) .get_change ( & h2b) .unwrap ( ) .deps, vec! [ h1b] ) ;
4596
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4597
4598
repo.abandon ( & hj) .unwrap ( ) ;
4599
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
4600
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
4601
assert! ( ! snap.files.contains_key ( " junk/x.lock" ) & & ! snap.files.contains_key ( " junk" ) ) ;
4602
assert! ( snap.files.contains_key ( " a.txt" ) ) ;
4603
4604
write ( root, " junk/y.lock" , b " y\n " ) ;
4605
let hb = repo.commit ( " junk base" , & [ ] ) .unwrap ( ) ;
4606
write ( root, " junk/y.lock" , b " yy\n " ) ;
4607
write ( root, " b.txt" , b " b\n " ) ;
4608
repo.commit ( " touches junk" , & [ ] ) .unwrap ( ) ;
4609
let err = repo.split ( & hb, & [ " junk" .into ( ) ] ) .unwrap_err ( ) .to_string ( ) ;
4610
assert! ( err.contains ( " depends on the split-off paths" ) , " {err}" ) ;
4611
}
4612
7b9809e6 Andy McCall 1 month ago ↶
4613
# [ test ]
4614
fn bookmarks_switch_merge_and_push ( ) {
4615
hermetic ( ) ;
4616
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4617
let root = dir.path ( ) ;
4618
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
4619
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
4620
let mut repo = Repo:: clone( & url, & root.join ( " r" ) )
4621
.unwrap ( )
4622
.with_home ( root.join ( " home" ) ) ;
7b9809e6 Andy McCall 1 month ago ↶
4623
let r = root.join ( " r" ) ;
4624
write ( & r, " a.txt" , b " a\n " ) ;
4625
let h_a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
4626
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
4627
origin.refresh ( ) .unwrap ( ) ;
7b9809e6 Andy McCall 1 month ago ↶
4628
4629
4630
assert! ( repo.bookmark_create ( " bad name.." , None ) .is_err ( ) ) ;
4631
assert! ( repo.bookmark_create ( " -x" , None ) .is_err ( ) ) ;
4632
assert_eq! ( repo.bookmark_create ( " feature" , None ) .unwrap ( ) , vec! [ h_a] ) ;
4633
assert! ( repo.bookmark_create ( " feature" , None ) .is_err ( ) , " exists" ) ;
4634
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
4635
assert_eq! ( repo.config.bookmark, " feature" ) ;
4636
write ( & r, " a.txt" , b " a\n b\n " ) ;
4637
write ( & r, " b.txt" , b " b\n " ) ;
4638
let h_b = repo.commit ( " b on feature" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4639
assert_eq! (
4640
repo.bookmark_heads ( ) .unwrap ( ) ,
4641
vec! [ h_b] ,
4642
" b edits a.txt, so it depends on a"
4643
) ;
4644
assert_eq! (
4645
repo.resolve_heads ( " main" ) .unwrap ( ) ,
4646
vec! [ h_a] ,
4647
" main is untouched"
4648
) ;
4649
assert_eq! (
4650
repo.resolve_change ( " feature" ) .unwrap ( ) ,
4651
h_b,
4652
" a one-head bookmark names its change"
4653
) ;
7b9809e6 Andy McCall 1 month ago ↶
4654
4655
let reopened = Repo:: open( & r) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4656
assert_eq! ( reopened.config.bookmark, " feature" ) ;
4657
4658
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
4659
assert! ( ! r.join ( " b.txt" ) .exists ( ) ) ;
4660
assert_eq! ( fs:: read( r.join ( " a.txt" ) ) .unwrap ( ) , b " a\n " ) ;
4661
write ( & r, " c.txt" , b " c\n " ) ;
4662
let h_c = repo.commit ( " c on main" , & [ ] ) .unwrap ( ) ;
4663
let list = repo.bookmarks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4664
assert_eq! (
4665
list.iter ( )
4666
.map ( | b | ( b.name.as_str ( ) , b.current) )
4667
.collect:: < Vec< _ > > ( ) ,
4668
vec! [ ( " feature" , false ) , ( " main" , true ) ]
4669
) ;
4670
assert_eq! (
4671
list.iter ( ) .find ( | b | b.name = = " main" ) .unwrap ( ) .ahead,
4672
Some ( 1 ) ,
4673
" c is not on origin yet"
4674
) ;
7b9809e6 Andy McCall 1 month ago ↶
4675
4676
let m = repo.merge ( " feature" ) .unwrap ( ) ;
4677
assert! ( m.changed & & m.checked_out) ;
4678
let mut want = vec! [ h_b, h_c] ;
4679
want.sort ( ) ;
4680
assert_eq! ( m.heads, want) ;
4681
assert_eq! ( fs:: read( r.join ( " b.txt" ) ) .unwrap ( ) , b " b\n " ) ;
4682
assert! ( ! repo.merge ( " feature" ) .unwrap ( ) .changed, " already merged" ) ;
4683
assert! ( repo.merge ( " main" ) .is_err ( ) ) ;
4684
4685
assert! ( repo.push_bookmark ( " origin" , " feature" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
4686
origin.refresh ( ) .unwrap ( ) ;
7b9809e6 Andy McCall 1 month ago ↶
4687
assert_eq! ( origin.resolve_heads ( " feature" ) .unwrap ( ) , vec! [ h_b] ) ;
4688
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
4689
origin.refresh ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4690
assert_eq! (
4691
repo.bookmarks ( )
4692
.unwrap ( )
4693
.iter ( )
4694
.find ( | b | b.name = = " main" )
4695
.unwrap ( )
4696
.ahead,
4697
Some ( 0 )
4698
) ;
7b9809e6 Andy McCall 1 month ago ↶
4699
4700
let ws = repo.workspace_add ( & root.join ( " ws" ) , None ) .unwrap ( ) ;
4701
assert_eq! ( ws.config.bookmark, " main" ) ;
4702
let mut ws = ws;
4703
ws.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4704
assert_eq! (
4705
Repo:: open( & r) .unwrap ( ) .config.bookmark,
4706
" main" ,
4707
" the first workspace is unaffected"
4708
) ;
4709
assert! (
4710
repo.bookmark_delete ( " feature" ) .is_err ( ) ,
4711
" current bookmark of another workspace"
4712
) ;
7b9809e6 Andy McCall 1 month ago ↶
4713
ws.bookmark_switch ( " main" , false ) .unwrap ( ) ;
4714
4715
assert_eq! ( repo.bookmark_set ( " feature" , & [ h_a] ) .unwrap ( ) , vec! [ h_a] ) ;
4716
assert! ( repo.bookmark_delete ( " main" ) .is_err ( ) , " current here" ) ;
4717
repo.bookmark_delete ( " feature" ) .unwrap ( ) ;
4718
assert! ( repo.resolve_heads ( " feature" ) .is_err ( ) ) ;
4719
assert! ( repo.bookmark_set ( " feature" , & [ h_a] ) .is_err ( ) ) ;
75167672 Andy McCall 1 month ago
4720
assert! (
4721
repo.checkout ( & [ h_a] , false , " checkout" ) .is_ok ( ) ,
4722
" changes of a deleted bookmark remain"
4723
) ;
7b9809e6 Andy McCall 1 month ago ↶
4724
}
4725
4dee277c Andy McCall 1 month ago ↶
4726
# [ test ]
4727
fn oxygen_subrepos_pin_follow_and_update ( ) {
4728
hermetic ( ) ;
4729
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4730
let root = dir.path ( ) ;
4731
4732
let lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
4733
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
4734
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
4735
.unwrap ( )
4736
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4737
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
4738
let l1 = lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
4739
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
4740
let _ = lib_origin;
4741
4742
4743
let app_origin = Repo:: init( & root.join ( " app-origin" ) ) .unwrap ( ) ;
4744
let app_url = root.join ( " app-origin" ) .to_string_lossy ( ) .to_string ( ) ;
4745
let _ = app_origin;
4746
let app_dir = root.join ( " app" ) ;
75167672 Andy McCall 1 month ago
4747
let app = Repo:: clone( & app_url, & app_dir)
4748
.unwrap ( )
4749
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4750
write ( & app_dir, " main.txt" , b " app\n " ) ;
4751
let pin = app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
4752
assert! ( matches! ( & pin, crate :: subrepo:: Pin:: Oxy { heads, .. } if heads = = & vec! [ l1] ) ) ;
4753
assert! ( app_dir.join ( " .oxymodules" ) .exists ( ) & & app_dir.join ( " libs/lib/.oxy" ) .is_dir ( ) ) ;
4754
let snap = app.snapshot ( " status" ) .unwrap ( ) ;
4755
let paths: Vec< & str > = snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect ( ) ;
75167672 Andy McCall 1 month ago
4756
assert_eq! (
4757
paths,
4758
vec! [ " .oxymodules" , " libs/lib" , " main.txt" ] ,
4759
" the nested files are not walked"
4760
) ;
4dee277c Andy McCall 1 month ago ↶
4761
let a1 = app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
4762
let files = app.load_state ( & [ a1] ) .unwrap ( ) .materialize ( ) .files;
4763
assert_eq! ( files[ " libs/lib" ] .kind, NodeKind:: Subrepo) ;
4764
assert! ( ! files.contains_key ( " libs/lib/lib.txt" ) ) ;
4765
assert! ( ! app.snapshot ( " status" ) .unwrap ( ) .changed) ;
4766
let subs = app.subrepos ( ) .unwrap ( ) ;
4767
assert_eq! ( subs.len ( ) , 1 ) ;
4768
assert_eq! ( subs[ 0 ] .actual.as_ref ( ) , Some ( & subs[ 0 ] .pin) ) ;
4769
4770
75167672 Andy McCall 1 month ago
4771
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) )
4772
.unwrap ( )
4773
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4774
write ( & app_dir.join ( " libs/lib" ) , " lib.txt" , b " v2\n " ) ;
4775
let l2 = nested.commit ( " lib v2" , & [ ] ) .unwrap ( ) ;
4776
assert! ( nested.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
4777
let snap = app.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4778
assert_eq! (
4779
snap.statuses
4780
.iter ( )
4781
.map ( | s | ( s.path.as_str ( ) , s.status) )
4782
.collect:: < Vec< _ > > ( ) ,
4783
vec! [ ( " libs/lib" , ChangeStatus:: Modified) ]
4784
) ;
4dee277c Andy McCall 1 month ago ↶
4785
let a2 = app.commit ( " bump lib" , & [ ] ) .unwrap ( ) ;
1a3b6203 Andy McCall 1 month ago ↶
4786
4787
4dee277c Andy McCall 1 month ago ↶
4788
let r = app.checkout ( & [ a1] , false , " checkout" ) .unwrap ( ) ;
1a3b6203 Andy McCall 1 month ago ↶
4789
assert! (
4790
r.notes.len ( ) = = 1 & & r.notes[ 0 ] .contains ( " keeps 1 newer change" ) ,
4791
" {:?}" ,
4792
r.notes
4793
) ;
4dee277c Andy McCall 1 month ago ↶
4794
assert_eq! ( fs:: read( app_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v1\n " ) ;
1a3b6203 Andy McCall 1 month ago ↶
4795
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) ) .unwrap ( ) ;
4796
assert_eq! ( nested.bookmark_heads ( ) .unwrap ( ) , vec! [ l2] ) ;
4797
assert_eq! ( nested.working_copy ( ) .unwrap ( ) .1. deps, vec! [ l1] ) ;
4dee277c Andy McCall 1 month ago ↶
4798
assert! ( ! app.snapshot ( " status" ) .unwrap ( ) .changed) ;
4799
4800
write ( & app_dir.join ( " libs/lib" ) , " scratch.txt" , b " wip\n " ) ;
4801
let r = app.checkout ( & [ a2] , false , " checkout" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4802
assert! (
4803
r.notes.iter ( ) .any ( | n | n.contains ( " uncommitted" ) ) ,
4804
" {:?}" ,
4805
r.notes
4806
) ;
4dee277c Andy McCall 1 month ago ↶
4807
assert_eq! ( fs:: read( app_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v1\n " ) ;
4808
fs:: remove_file( app_dir.join ( " libs/lib/scratch.txt" ) ) .unwrap ( ) ;
4809
let r = app.submodule_update ( false , & [ ] ) .unwrap ( ) ;
4810
assert_eq! ( r, vec! [ ( " libs/lib" .to_string ( ) , SubrepoSync:: Updated) ] ) ;
4811
assert_eq! ( fs:: read( app_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v2\n " ) ;
4812
let _ = l2;
4813
4814
4815
assert! ( app.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
4816
let clone_dir = root.join ( " app-clone" ) ;
75167672 Andy McCall 1 month ago
4817
let clone = Repo:: clone( & app_url, & clone_dir)
4818
.unwrap ( )
4819
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4820
assert! ( ! clone_dir.join ( " libs/lib/.oxy" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
4821
assert_eq! (
4822
clone.submodule_update ( false , & [ ] ) .unwrap ( ) ,
4823
vec! [ ( " libs/lib" .to_string ( ) , SubrepoSync:: Missing) ]
4824
) ;
4825
assert_eq! (
4826
clone.submodule_update ( true , & [ ] ) .unwrap ( ) ,
4827
vec! [ ( " libs/lib" .to_string ( ) , SubrepoSync:: Updated) ]
4828
) ;
4829
assert_eq! (
4830
fs:: read( clone_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) ,
4831
b " v2\n "
4832
) ;
4dee277c Andy McCall 1 month ago ↶
4833
assert! ( ! clone.snapshot ( " status" ) .unwrap ( ) .changed) ;
4834
4835
clone.submodule_remove ( " libs/lib" ) .unwrap ( ) ;
4836
let snap = clone.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4837
assert! (
4838
snap.statuses
4839
.iter ( )
4840
.any ( | s | s.path = = " libs/lib" & & s.status = = ChangeStatus:: Removed ) ,
4841
" {:?}" ,
4842
snap.statuses
4843
) ;
4dee277c Andy McCall 1 month ago ↶
4844
let c = clone.commit ( " drop lib" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4845
assert! ( ! clone
4846
.load_state ( & [ c] )
4847
.unwrap ( )
4848
.materialize ( )
4849
.files
4850
.contains_key ( " libs/lib" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4851
assert! ( clone_dir.join ( " libs/lib/lib.txt" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
4852
assert! (
4853
! clone.snapshot ( " status" ) .unwrap ( ) .changed,
4854
" an unregistered nested repository is ignored"
4855
) ;
4dee277c Andy McCall 1 month ago ↶
4856
}
4857
4858
# [ test ]
4859
fn git_submodules_import_and_export_as_gitlinks ( ) {
4860
hermetic ( ) ;
75167672 Andy McCall 1 month ago
4861
if std:: process:: Command:: new( " git" )
4862
.arg ( " --version" )
4863
.output ( )
4864
.is_err ( )
4865
{
4dee277c Andy McCall 1 month ago ↶
4866
eprintln! ( " git not installed; skipping" ) ;
4867
return ;
4868
}
4869
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4870
let root = dir.path ( ) ;
4871
let git_in = | d : & Path, args : & [& str ]| -> String {
4872
let out = std:: process:: Command:: new( " git" )
4873
.arg ( " -C" )
4874
.arg ( d)
75167672 Andy McCall 1 month ago
4875
.args ( [
4876
" -c" ,
4877
" user.name=Grace" ,
4878
" -c" ,
4879
" [email protected] " ,
4880
" -c" ,
4881
" commit.gpgsign=false" ,
4882
" -c" ,
4883
" protocol.file.allow=always" ,
4884
] )
4dee277c Andy McCall 1 month ago ↶
4885
.args ( args)
4886
.output ( )
4887
.unwrap ( ) ;
75167672 Andy McCall 1 month ago
4888
assert! (
4889
out.status.success ( ) ,
4890
" git {args:?}: {}" ,
4891
String :: from_utf8_lossy( & out.stderr)
4892
) ;
4dee277c Andy McCall 1 month ago ↶
4893
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
4894
} ;
4895
let sub = root.join ( " sub" ) ;
4896
fs:: create_dir_all( & sub) .unwrap ( ) ;
4897
git_in ( & sub, & [ " init" , " -q" , " -b" , " main" ] ) ;
4898
write ( & sub, " s.txt" , b " sub\n " ) ;
4899
git_in ( & sub, & [ " add" , " ." ] ) ;
4900
git_in ( & sub, & [ " commit" , " -q" , " -m" , " sub initial" ] ) ;
4901
let sub_sha = git_in ( & sub, & [ " rev-parse" , " HEAD" ] ) ;
4902
let g = root.join ( " main.git" ) ;
4903
fs:: create_dir_all( & g) .unwrap ( ) ;
4904
git_in ( & g, & [ " init" , " -q" , " -b" , " main" ] ) ;
4905
write ( & g, " README.md" , b " # app\n " ) ;
4906
git_in ( & g, & [ " add" , " ." ] ) ;
4907
git_in ( & g, & [ " commit" , " -q" , " -m" , " initial" ] ) ;
75167672 Andy McCall 1 month ago
4908
git_in (
4909
& g,
4910
& [
4911
" submodule" ,
4912
" add" ,
4913
" -q" ,
4914
sub.to_str ( ) .unwrap ( ) ,
4915
" vendor/sub" ,
4916
] ,
4917
) ;
4dee277c Andy McCall 1 month ago ↶
4918
git_in ( & g, & [ " commit" , " -q" , " -m" , " add submodule" ] ) ;
4919
4920
let dest = root.join ( " oxy" ) ;
4921
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4922
let rep = repo.import_git ( & g, " main" ) .unwrap ( ) ;
4923
assert_eq! ( ( rep.commits, rep.submodules) , ( 2 , 1 ) ) ;
75167672 Andy McCall 1 month ago
4924
let files = repo
4925
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
4926
.unwrap ( )
4927
.materialize ( )
4928
.files;
4dee277c Andy McCall 1 month ago ↶
4929
assert_eq! ( files[ " vendor/sub" ] .kind, NodeKind:: Subrepo) ;
75167672 Andy McCall 1 month ago
4930
let FileContent:: Opaque { blobs } = & files[ " vendor/sub" ] .content else {
4931
panic! ( )
4932
} ;
4933
assert_eq! (
4934
crate :: subrepo:: Pin:: decode( & repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) ) .unwrap ( ) ,
4935
crate :: subrepo:: Pin:: Git {
4936
sha: sub_sha.clone ( ) ,
4937
branch: " HEAD" .into ( )
4938
}
4939
) ;
4940
let modules =
4941
crate :: subrepo:: Modules:: parse( & fs:: read_to_string( dest.join ( " .oxymodules" ) ) .unwrap ( ) ) ;
4dee277c Andy McCall 1 month ago ↶
4942
assert_eq! ( modules.entries[ " vendor/sub" ] .url, sub.to_string_lossy ( ) ) ;
4943
assert! ( modules.entries[ " vendor/sub" ] .git) ;
75167672 Andy McCall 1 month ago
4944
assert! (
4945
files.contains_key ( " .gitmodules" ) ,
4946
" the Git file is kept for round trips"
4947
) ;
4948
assert! (
4949
! dest.join ( " vendor/sub/.git" ) .exists ( ) ,
4950
" not cloned until update --init"
4951
) ;
4dee277c Andy McCall 1 month ago ↶
4952
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4953
75167672 Andy McCall 1 month ago
4954
assert_eq! (
4955
repo.submodule_update ( true , & [ ] ) .unwrap ( ) ,
4956
vec! [ ( " vendor/sub" .to_string ( ) , SubrepoSync:: Updated) ]
4957
) ;
4958
assert_eq! (
4959
git_in ( & dest.join ( " vendor/sub" ) , & [ " rev-parse" , " HEAD" ] ) ,
4960
sub_sha
4961
) ;
4dee277c Andy McCall 1 month ago ↶
4962
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4963
4964
let mirror = root.join ( " mirror.git" ) ;
4965
let rep = repo.git_export ( & mirror) .unwrap ( ) ;
4966
assert_eq! ( rep.skipped_subrepos, 0 ) ;
4967
let ls = git_in ( & mirror, & [ " ls-tree" , " main" , " vendor/sub" ] ) ;
75167672 Andy McCall 1 month ago
4968
assert! (
4969
ls.starts_with ( " 160000 commit " ) & & ls.contains ( & sub_sha) ,
4970
" {ls}"
4971
) ;
4972
assert_eq! (
4973
git_in ( & mirror, & [ " ls-tree" , " --name-only" , " main" ] ) ,
4974
" .gitmodules\n README.md\n vendor"
4975
) ;
4dee277c Andy McCall 1 month ago ↶
4976
let orig_tree = git_in ( & g, & [ " rev-parse" , " main^{tree}" ] ) ;
75167672 Andy McCall 1 month ago
4977
assert_eq! (
4978
git_in ( & mirror, & [ " rev-parse" , " main^{tree}" ] ) ,
4979
orig_tree,
4980
" tree round trip"
4981
) ;
4dee277c Andy McCall 1 month ago ↶
4982
}
4983
957fee30 Andy McCall 1 month ago ↶
4984
# [ test ]
4985
fn partial_commits_by_path_and_by_hunk ( ) {
4986
hermetic ( ) ;
4987
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4988
let root = dir.path ( ) ;
4989
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4990
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
4991
write ( root, " b.txt" , b " b\n " ) ;
4992
let h0 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4993
write ( root, " a.txt" , b " 0\n 1\n 2\n 3\n 4\n " ) ;
4994
write ( root, " b.txt" , b " b\n bb\n " ) ;
4995
write ( root, " c.txt" , b " c\n " ) ;
4996
75167672 Andy McCall 1 month ago
4997
let h1 = repo
4998
.commit_selection ( " a only" , & [ ] , & Selection:: Paths( vec! [ " a.txt" .into ( ) ] ) )
4999
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5000
let c1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5001
assert! (
5002
c1.tree_ops.is_empty ( ) & & c1.edits.iter ( ) .all ( | e | e.node ( ) = = c1.edits[ 0 ] .node ( ) ) ,
5003
" {:?}" ,
5004
c1.tree_ops
5005
) ;
957fee30 Andy McCall 1 month ago ↶
5006
assert_eq! ( c1.deps, vec! [ h0] ) ;
5007
let files = repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
5008
assert! (
5009
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 0\n 1\n 2\n 3\n 4\n " )
5010
) ;
957fee30 Andy McCall 1 month ago ↶
5011
assert! ( matches! ( & files[ " b.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " b\n " ) ) ;
5012
assert! ( ! files.contains_key ( " c.txt" ) ) ;
5013
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5014
assert_eq! (
5015
snap.statuses
5016
.iter ( )
5017
.map ( | s | ( s.path.as_str ( ) , s.status) )
5018
.collect:: < Vec< _ > > ( ) ,
5019
vec! [
5020
( " b.txt" , ChangeStatus:: Modified) ,
5021
( " c.txt" , ChangeStatus:: Added)
5022
]
5023
) ;
5024
assert_eq! (
5025
fs:: read( root.join ( " b.txt" ) ) .unwrap ( ) ,
5026
b " b\n bb\n " ,
5027
" disk untouched"
5028
) ;
957fee30 Andy McCall 1 month ago ↶
5029
5030
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5031
let b_hunk = hunks
5032
.iter ( )
5033
.find ( | h | h.path = = " b.txt" & & h.text.contains ( " +bb" ) )
5034
.unwrap ( )
5035
.index;
5036
let h2 = repo
5037
.commit_selection ( " b only" , & [ ] , & Selection:: Hunks( vec! [ b_hunk] ) )
5038
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5039
let files = repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
5040
assert! (
5041
matches! ( & files[ " b.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " b\n bb\n " )
5042
) ;
957fee30 Andy McCall 1 month ago ↶
5043
assert! ( ! files.contains_key ( " c.txt" ) ) ;
5044
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5045
assert_eq! (
5046
snap.statuses
5047
.iter ( )
5048
.map ( | s | s.path.as_str ( ) )
5049
.collect:: < Vec< _ > > ( ) ,
5050
vec! [ " c.txt" ]
5051
) ;
957fee30 Andy McCall 1 month ago ↶
5052
5053
5054
write ( root, " d/e.txt" , b " e\n " ) ;
5055
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5056
let e_insert = hunks
5057
.iter ( )
5058
.find ( | h | h.path = = " d/e.txt" & & h.text.contains ( " +e" ) )
5059
.unwrap ( )
5060
.index;
5061
let h3 = repo
5062
.commit_selection ( " e only" , & [ ] , & Selection:: Hunks( vec! [ e_insert] ) )
5063
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5064
let c3 = repo.store ( ) .get_change ( & h3) .unwrap ( ) ;
5065
assert_eq! ( c3.tree_ops.len ( ) , 2 , " {:?}" , c3.tree_ops) ;
5066
let files = repo.load_state ( & [ h3] ) .unwrap ( ) .materialize ( ) .files;
5067
assert! ( files.contains_key ( " d/e.txt" ) & & ! files.contains_key ( " c.txt" ) ) ;
d8c086ca Andy McCall 1 month ago ↶
5068
5069
write ( root, " f/g.txt" , b " g\n " ) ;
5070
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5071
let g_add = hunks
5072
.iter ( )
5073
.find ( | h | h.path = = " f/g.txt" & & h.text.starts_with ( " A " ) )
5074
.unwrap ( )
5075
.index;
5076
let h4 = repo
5077
.commit_selection ( " g by addition" , & [ ] , & Selection:: Hunks( vec! [ g_add] ) )
5078
.unwrap ( ) ;
d8c086ca Andy McCall 1 month ago ↶
5079
let files = repo.load_state ( & [ h4] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
5080
assert! (
5081
matches! ( & files[ " f/g.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " g\n " )
5082
) ;
5083
assert! ( ! repo
5084
.snapshot ( " status" )
5085
.unwrap ( )
5086
.statuses
5087
.iter ( )
5088
.any ( | s | s.path = = " f/g.txt" ) ) ;
957fee30 Andy McCall 1 month ago ↶
5089
5090
repo.commit ( " the rest" , & [ ] ) .unwrap ( ) ;
5091
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
75167672 Andy McCall 1 month ago
5092
let files = repo
5093
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
5094
.unwrap ( )
5095
.materialize ( )
5096
.files;
957fee30 Andy McCall 1 month ago ↶
5097
assert! ( files.contains_key ( " c.txt" ) ) ;
5098
}
5099
5100
# [ test ]
5101
fn blame_attributes_lines_to_introducing_changes_across_renames ( ) {
5102
hermetic ( ) ;
5103
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5104
let root = dir.path ( ) ;
5105
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
5106
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
5107
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5108
write ( root, " a.txt" , b " one\n " ) ;
5109
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
5110
write ( root, " a.txt" , b " one\n two\n " ) ;
5111
let h2 = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
5112
let b = repo.blame ( " a.txt" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5113
assert_eq! (
5114
b.iter ( )
5115
.map ( | l | ( l.line, l.hash, l.text.as_slice ( ) ) )
5116
.collect:: < Vec< _ > > ( ) ,
5117
vec! [ ( 1 , h1, b " one\n " .as_slice ( ) ) , ( 2 , h2, b " two\n " .as_slice ( ) ) ]
5118
) ;
957fee30 Andy McCall 1 month ago ↶
5119
assert! ( b.iter ( ) .all ( | l | l.author = = " Ada" ) ) ;
75167672 Andy McCall 1 month ago
5120
assert_eq! (
5121
b[ 0 ] .change_id,
5122
repo.store ( ) .get_change ( & h1) .unwrap ( ) .change_id
5123
) ;
957fee30 Andy McCall 1 month ago ↶
5124
fs:: rename( root.join ( " a.txt" ) , root.join ( " b.txt" ) ) .unwrap ( ) ;
5125
repo.commit ( " rename" , & [ ] ) .unwrap ( ) ;
5126
let b = repo.blame ( " b.txt" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5127
assert_eq! (
5128
b.iter ( ) .map ( | l | l.hash ) .collect:: < Vec< _ > > ( ) ,
5129
vec! [ h1, h2] ,
5130
" the node survives the rename"
5131
) ;
957fee30 Andy McCall 1 month ago ↶
5132
assert! ( repo.blame ( " a.txt" ) .is_err ( ) ) ;
5133
}
5134
5135
# [ test ]
5136
fn bisect_finds_the_first_bad_change ( ) {
5137
hermetic ( ) ;
5138
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5139
let root = dir.path ( ) ;
5140
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5141
let mut content = String :: new( ) ;
5142
let mut hashes = Vec :: new( ) ;
5143
for i in 1 .. = 8 {
5144
content.push_str ( & format! ( " line {i} {} \n " , if i = = 5 { " bug" } else { " " } ) ) ;
5145
write ( root, " f.txt" , content.as_bytes ( ) ) ;
5146
hashes.push ( repo.commit ( & format! ( " step {i} " ) , & [ ] ) .unwrap ( ) ) ;
5147
}
5148
5149
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ hashes[ 7 ] ] ) ;
5150
repo.bisect_start ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5151
assert! ( matches! (
5152
repo.bisect_mark ( Some ( true ) , & [ hashes[ 0 ] ] ) .unwrap ( ) ,
5153
BisectStep:: NeedMarks { good: 1 , bad: 0 }
5154
) ) ;
957fee30 Andy McCall 1 month ago ↶
5155
let step = repo.bisect_mark ( Some ( false ) , & [ hashes[ 7 ] ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5156
assert! (
5157
matches! ( step, BisectStep:: Testing { remaining: 7 , .. } ) ,
5158
" {step:?}"
5159
) ;
957fee30 Andy McCall 1 month ago ↶
5160
75167672 Andy McCall 1 month ago
5161
let step = repo
5162
.bisect_run ( & [ " sh" .into ( ) , " -c" .into ( ) , " ! grep -q bug f.txt" .into ( ) ] )
5163
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5164
assert_eq! ( step, BisectStep:: Found { culprit: hashes[ 4 ] } ) ;
75167672 Andy McCall 1 month ago
5165
assert_eq! (
5166
repo.bisect_status ( ) .unwrap ( ) ,
5167
Some ( BisectStep:: Found { culprit: hashes[ 4 ] } )
5168
) ;
957fee30 Andy McCall 1 month ago ↶
5169
repo.bisect_reset ( ) .unwrap ( ) ;
5170
assert_eq! ( fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) , content) ;
5171
assert! ( repo.bisect_status ( ) .unwrap ( ) .is_none ( ) ) ;
5172
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
5173
5174
repo.bisect_start ( ) .unwrap ( ) ;
5175
repo.bisect_mark ( Some ( true ) , & [ hashes[ 0 ] ] ) .unwrap ( ) ;
5176
let mut step = repo.bisect_mark ( Some ( false ) , & [ hashes[ 7 ] ] ) .unwrap ( ) ;
5177
let mut skipped_once = false ;
5178
let mut guard = 0 ;
5179
while let BisectStep:: Testing { .. } = step {
5180
guard + = 1 ;
5181
assert! ( guard < 20 ) ;
5182
let text = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
5183
if ! skipped_once & & text.contains ( " line 6" ) {
5184
5185
skipped_once = true ;
5186
step = repo.bisect_mark ( None , & [ ] ) .unwrap ( ) ;
5187
continue ;
5188
}
5189
step = repo.bisect_mark ( Some ( ! text.contains ( " bug" ) ) , & [ ] ) .unwrap ( ) ;
5190
}
5191
assert! ( skipped_once) ;
5192
assert_eq! ( step, BisectStep:: Found { culprit: hashes[ 4 ] } ) ;
5193
repo.bisect_reset ( ) .unwrap ( ) ;
5194
5195
repo.bisect_start ( ) .unwrap ( ) ;
5196
repo.bisect_mark ( Some ( true ) , & [ hashes[ 0 ] ] ) .unwrap ( ) ;
5197
let mut step = repo.bisect_mark ( Some ( false ) , & [ hashes[ 7 ] ] ) .unwrap ( ) ;
5198
let mut guard = 0 ;
5199
while let BisectStep:: Testing { heads, .. } = & step {
5200
guard + = 1 ;
5201
assert! ( guard < 20 ) ;
5202
let text = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5203
let is_culprit_state =
5204
heads.contains ( & hashes[ 4 ] ) & & ! heads.iter ( ) .any ( | h | hashes[ 5 .. ] .contains ( h) ) ;
5205
step = if is_culprit_state {
5206
repo.bisect_mark ( None , & [ ] ) .unwrap ( )
5207
} else {
5208
repo.bisect_mark ( Some ( ! text.contains ( " bug" ) ) , & [ ] ) .unwrap ( )
5209
} ;
957fee30 Andy McCall 1 month ago ↶
5210
}
5211
match step {
75167672 Andy McCall 1 month ago
5212
BisectStep:: Inconclusive { candidates } => assert! (
5213
candidates.contains ( & hashes[ 4 ] ) & & candidates.len ( ) < = 2 ,
5214
" {candidates:?}"
5215
) ,
957fee30 Andy McCall 1 month ago ↶
5216
other => panic! ( " {other:?}" ) ,
5217
}
5218
repo.bisect_reset ( ) .unwrap ( ) ;
5219
}
5220
728da0f9 Andy McCall 1 month ago ↶
5221
# [ test ]
5222
fn squash_folds_a_change_into_its_dependency ( ) {
5223
hermetic ( ) ;
5224
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5225
let root = dir.path ( ) ;
5226
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5227
write ( root, " a.txt" , b " 1\n " ) ;
5228
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
5229
write ( root, " a.txt" , b " 1\n 2\n " ) ;
5230
let c = repo.commit ( " C" , & [ ] ) .unwrap ( ) ;
5231
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
5232
let e = repo.commit ( " E" , & [ ] ) .unwrap ( ) ;
5233
write ( root, " b.txt" , b " b\n " ) ;
5234
let b = repo.commit ( " B" , & [ ] ) .unwrap ( ) ;
5235
let id_a = repo.store ( ) .get_change ( & a) .unwrap ( ) .change_id;
5236
let id_e = repo.store ( ) .get_change ( & e) .unwrap ( ) .change_id;
5237
5238
let err = repo.squash ( & e, & a) .unwrap_err ( ) .to_string ( ) ;
5239
assert! ( err.contains ( & c.short ( 12 ) ) , " {err}" ) ;
5240
5241
let a2 = repo.squash ( & c, & a) .unwrap ( ) ;
5242
let a2c = repo.store ( ) .get_change ( & a2) .unwrap ( ) ;
5243
assert_eq! ( a2c.change_id, id_a) ;
5244
assert! ( a2c.deps.is_empty ( ) ) ;
5245
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5246
let st = repo.load_state ( & heads) .unwrap ( ) ;
5247
assert! ( ! st.contains ( & c) & & ! st.contains ( & a) & & ! st.contains ( & e) ) ;
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 ( ) ;
5253
assert_eq! (
5254
repo.store ( ) .get_change ( & e2) .unwrap ( ) .deps,
5255
vec! [ a2] ,
5256
" E now depends on A' alone"
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 " 1\n 2\n 3\n " )
5261
) ;
728da0f9 Andy McCall 1 month ago ↶
5262
assert! ( files.contains_key ( " b.txt" ) ) ;
5263
assert! ( st.contains ( & b) ) ;
5264
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
5265
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 , " working copy, B, E', A'" ) ;
5266
5267
repo.undo ( ) .unwrap ( ) ;
5268
repo.undo ( ) .unwrap ( ) ;
5269
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
5270
assert! ( st.contains ( & c) & & st.contains ( & a) & & st.contains ( & e) ) ;
5271
}
5272
5273
# [ test ]
5274
fn rebase_re_expresses_a_change_against_another_base ( ) {
5275
hermetic ( ) ;
5276
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5277
let root = dir.path ( ) ;
5278
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5279
write ( root, " a.txt" , b " 1\n 2\n " ) ;
5280
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
5281
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
5282
let c = repo.commit ( " C: append 3" , & [ ] ) .unwrap ( ) ;
5283
write ( root, " a.txt" , b " 1\n 2\n 3\n 4\n " ) ;
5284
let e = repo.commit ( " E: append 4" , & [ ] ) .unwrap ( ) ;
5285
5286
repo.checkout ( & [ a] , true , " checkout" ) .unwrap ( ) ;
5287
write ( root, " a.txt" , b " 0\n 1\n 2\n " ) ;
5288
let d = repo.commit ( " D: prepend 0" , & [ ] ) .unwrap ( ) ;
5289
let id_c = repo.store ( ) .get_change ( & c) .unwrap ( ) .change_id;
5290
let id_e = repo.store ( ) .get_change ( & e) .unwrap ( ) .change_id;
5291
5292
let c2 = repo.rebase ( & c, & [ d] ) .unwrap ( ) ;
5293
let c2c = repo.store ( ) .get_change ( & c2) .unwrap ( ) ;
5294
assert_eq! ( c2c.change_id, id_c) ;
5295
assert! ( c2c.deps.contains ( & d) , " {:?}" , c2c.deps) ;
5296
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5297
let st = repo.load_state ( & heads) .unwrap ( ) ;
5298
assert! ( ! st.contains ( & c) & & st.contains ( & d) ) ;
75167672 Andy McCall 1 month ago
5299
let e2 = st
5300
.changes ( )
5301
.find ( | ( _, m ) | m.change_id = = id_e )
5302
.map ( | ( h , _) | * h)
5303
.unwrap ( ) ;
728da0f9 Andy McCall 1 month ago ↶
5304
let e2deps = repo.store ( ) .get_change ( & e2) .unwrap ( ) .deps;
75167672 Andy McCall 1 month ago
5305
assert! (
5306
e2deps.contains ( & c2) & & ! e2deps.contains ( & c) & & e2deps.contains ( & a) ,
5307
" E follows C by content and still depends on A, which introduced the file: {e2deps:?}"
5308
) ;
728da0f9 Andy McCall 1 month ago ↶
5309
let files = st.materialize ( ) .files;
75167672 Andy McCall 1 month ago
5310
assert! (
5311
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 0\n 1\n 2\n 3\n 4\n " ) ,
5312
" {:?}" ,
5313
files[ " a.txt" ] .content
5314
) ;
728da0f9 Andy McCall 1 month ago ↶
5315
assert! ( ! files[ " a.txt" ] .is_conflicted ( ) ) ;
5316
5317
assert! ( repo.rebase ( & c2, & [ c2] ) .is_err ( ) ) ;
5318
let err = repo.rebase ( & e2, & [ d] ) .unwrap_err ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
5319
assert! (
5320
err.contains ( " lacks dependencies" ) ,
5321
" E needs C, which D's state does not have: {err}"
5322
) ;
728da0f9 Andy McCall 1 month ago ↶
5323
}
5324
c792033a Andy McCall 1 month ago ↶
5325
# [ test ]
5326
fn unsigned_tags_keep_their_message ( ) {
5327
hermetic ( ) ;
5328
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5329
let root = dir.path ( ) ;
5330
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5331
write ( root, " a.txt" , b " a\n " ) ;
5332
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
5333
assert! ( repo.secret_key ( ) .unwrap ( ) .is_none ( ) ) ;
75167672 Andy McCall 1 month ago
5334
repo.tag_create ( " v1" , & repo.bookmark_heads ( ) .unwrap ( ) , " first release" )
5335
.unwrap ( ) ;
c792033a Andy McCall 1 month ago ↶
5336
let tags = repo.tags ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5337
assert_eq! (
5338
(
5339
tags[ 0 ] .name.as_str ( ) ,
5340
tags[ 0 ] .message.as_str ( ) ,
5341
tags[ 0 ] .signer.as_deref ( )
5342
) ,
5343
( " v1" , " first release" , None )
5344
) ;
5345
assert! (
5346
repo.tag_refs ( ) .unwrap ( ) [ 0 ] .attestation.is_none ( ) ,
5347
" an unsigned attestation is not advertised"
5348
) ;
c792033a Andy McCall 1 month ago ↶
5349
}
387335ff Andy McCall 1 month ago ↶
5350
5351
# [ test ]
5352
fn fsck_passes_a_sound_repository_and_names_each_fault ( ) {
64818938 Andy McCall 1 month ago ↶
5353
5354
5355
if std:: env:: var_os( " OXY_STORE_BACKEND" ) .is_some ( ) {
5356
return ;
5357
}
387335ff Andy McCall 1 month ago ↶
5358
hermetic ( ) ;
5359
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5360
let root = dir.path ( ) ;
5361
let origin_path = root.join ( " origin" ) ;
5362
let origin = Repo:: init( & origin_path) .unwrap ( ) ;
5363
let mut a = Repo:: clone( & origin_path.to_string_lossy ( ) , & root.join ( " a" ) )
5364
.unwrap ( )
5365
.with_home ( root.join ( " home-a" ) ) ;
5366
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
5367
.unwrap ( ) ;
5368
write ( & root.join ( " a" ) , " f.txt" , b " one\n " ) ;
5369
write ( & root.join ( " a" ) , " bin.dat" , b " \0 \x01 \x02 binary" ) ;
5370
let h1 = a.commit ( " first" , & [ ] ) .unwrap ( ) ;
5371
write ( & root.join ( " a" ) , " f.txt" , b " one\n two\n " ) ;
5372
let h2 = a.commit ( " second" , & [ ] ) .unwrap ( ) ;
5373
a.tag_create ( " v1" , & [ h2] , " first release" ) .unwrap ( ) ;
5374
a.checkpoint ( ) .unwrap ( ) ;
5375
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
5376
origin.refresh ( ) .unwrap ( ) ;
387335ff Andy McCall 1 month ago ↶
5377
5378
5379
for repo in [ & a, & origin] {
5380
let rep = repo.fsck ( ) .unwrap ( ) ;
5381
assert_eq! ( rep.problems, vec! [ ] , " {rep:?}" ) ;
5382
5383
assert! ( rep.changes > = 2 & & rep.signed > = 2 , " {rep:?}" ) ;
5384
assert! ( rep.ops > 1 & & rep.objects > 5 ) ;
5385
}
5386
assert! ( origin.fsck ( ) .unwrap ( ) .attestations > = 1 ) ;
5387
5388
5389
let c1 = a.store ( ) .path_for ( & h1) ;
5390
let pristine = fs:: read( & c1) .unwrap ( ) ;
5391
let mut bytes = pristine.clone ( ) ;
5392
* bytes.last_mut ( ) .unwrap ( ) ^ = 1 ;
5393
fs:: write( & c1, & bytes) .unwrap ( ) ;
5394
let rep = a.fsck ( ) .unwrap ( ) ;
5395
assert! ( rep.errors ( ) > = 1 ) ;
5396
assert! (
5397
rep.problems
5398
.iter ( )
5399
.any ( | p | p.object = = Some ( h1) & & p.what.contains ( " unreadable" ) ) ,
5400
" {rep:?}"
5401
) ;
5402
fs:: write( & c1, & pristine) .unwrap ( ) ;
5403
assert_eq! ( a.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
5404
5405
5406
fs:: remove_file( & c1) .unwrap ( ) ;
5407
let rep = a.fsck ( ) .unwrap ( ) ;
5408
assert! (
5409
rep.problems.iter ( ) .any ( | p | p.severity = = Severity:: Error
5410
& & p.object = = Some ( h1)
5411
& & p.what.contains ( " change missing, needed by change" ) ) ,
5412
" {rep:?}"
5413
) ;
5414
fs:: write( & c1, & pristine) .unwrap ( ) ;
5415
5416
5417
let c2 = a.store ( ) .get_change ( & h1) .unwrap ( ) ;
5418
let blob = c2
5419
.edits
5420
.iter ( )
5421
.find_map ( | e | match e {
5422
Edit:: Replace { new: Some ( b) , .. } => Some ( * b) ,
5423
_ => None ,
5424
} )
5425
.expect ( " the binary file is a blob" ) ;
5426
let bp = a.store ( ) .path_for ( & blob) ;
5427
let blob_bytes = fs:: read( & bp) .unwrap ( ) ;
5428
fs:: remove_file( & bp) .unwrap ( ) ;
5429
let rep = a.fsck ( ) .unwrap ( ) ;
5430
assert! (
5431
rep.problems
5432
.iter ( )
5433
.any ( | p | p.object = = Some ( blob) & & p.what.contains ( " blob missing" ) ) ,
5434
" {rep:?}"
5435
) ;
5436
fs:: write( & bp, & blob_bytes) .unwrap ( ) ;
5437
5438
5439
let head = root.join ( " a/.oxy/ops/head" ) ;
5440
let saved = fs:: read( & head) .unwrap ( ) ;
5441
fs:: write( & head, b " not a hash" ) .unwrap ( ) ;
5442
let rep = a.fsck ( ) .unwrap ( ) ;
5443
assert! (
5444
rep.problems.iter ( ) .any ( | p | p.what.contains ( " ops/head" ) ) ,
5445
" {rep:?}"
5446
) ;
5447
fs:: write( & head, saved) .unwrap ( ) ;
5448
assert_eq! ( a.fsck ( ) .unwrap ( ) .problems, vec! [ ] ) ;
5449
}
4dcb8c09 Andy McCall 1 month ago ↶
5450
5451
5452
fn copy_tree ( from : & Path, to : & Path) {
5453
fs:: create_dir_all( to) .unwrap ( ) ;
5454
for e in fs:: read_dir( from) .unwrap ( ) {
5455
let e = e.unwrap ( ) ;
5456
let target = to.join ( e.file_name ( ) ) ;
5457
if e.file_type ( ) .unwrap ( ) .is_dir ( ) {
5458
copy_tree ( & e.path ( ) , & target) ;
5459
} else {
5460
fs:: copy( e.path ( ) , & target) .unwrap ( ) ;
5461
}
5462
}
5463
}
5464
5465
5466
5467
5468
# [ test ]
5469
fn crash_at_every_step_of_commit_and_push_leaves_sound_repositories ( ) {
64818938 Andy McCall 1 month ago ↶
5470
5471
5472
if std:: env:: var_os( " OXY_STORE_BACKEND" ) .is_some ( ) {
5473
return ;
5474
}
4dcb8c09 Andy McCall 1 month ago ↶
5475
use crate :: durable:: failpoint;
5476
hermetic ( ) ;
5477
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5478
let pristine = dir.path ( ) .join ( " pristine" ) ;
5479
let origin_path = pristine.join ( " origin" ) ;
5480
Repo:: init( & origin_path) .unwrap ( ) ;
5481
let mut a = Repo:: clone( & origin_path.to_string_lossy ( ) , & pristine.join ( " a" ) )
5482
.unwrap ( )
5483
.with_home ( dir.path ( ) .join ( " home" ) ) ;
5484
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
5485
.unwrap ( ) ;
5486
write ( & pristine.join ( " a" ) , " f.txt" , b " one\n " ) ;
5487
a.commit ( " first" , & [ ] ) .unwrap ( ) ;
5488
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
5489
drop ( a) ;
5490
5491
type Op = fn ( & Repo) -> Result< ( ) > ;
5492
let edit_and_commit: Op = | r | {
5493
write ( r.root ( ) , " f.txt" , b " one\n two\n " ) ;
5494
write ( r.root ( ) , " big.bin" , & vec! [ 7 u8 ; 300_000 ] ) ;
5495
r.commit ( " second" , & [ ] ) .map ( | _| ( ) )
5496
} ;
5497
let push: Op = | r | {
5498
let res = r.push ( " origin" , false ) ? ;
5499
if res.iter ( ) .all ( | x | x.ok ) {
5500
Ok ( ( ) )
5501
} else {
5502
Err ( RepoError:: Msg( format! ( " {res:?} " ) ) )
5503
}
5504
} ;
5505
5506
for ( name, prepare, op) in [
5507
( " commit" , None , edit_and_commit) ,
5508
( " push" , Some ( edit_and_commit) , push) ,
5509
] {
5510
let mut steps = 0 ;
5511
for n in 1 .. {
5512
let run = dir.path ( ) .join ( format! ( " {name} -{n} " ) ) ;
5513
copy_tree ( & pristine, & run) ;
5514
5515
let open = | p : & Path| {
5516
let mut r = Repo:: open( p) .unwrap ( ) .with_home ( dir.path ( ) .join ( " home" ) ) ;
5517
r.config
5518
.set ( " remote.origin.url" , & run.join ( " origin" ) .to_string_lossy ( ) )
5519
.unwrap ( ) ;
5520
r.save_config ( ) .unwrap ( ) ;
5521
r
5522
} ;
5523
let a = open ( & run.join ( " a" ) ) ;
5524
if let Some ( prep) = prepare {
5525
prep ( & a) .unwrap ( ) ;
5526
}
5527
failpoint:: crash_after( n) ;
5528
let outcome = op ( & a) ;
5529
let crashed = failpoint:: disarm( ) ;
5530
drop ( a) ;
5531
if ! crashed {
5532
outcome.unwrap_or_else ( | e | panic! ( " {name} failed without a crash: {e}" ) ) ;
5533
steps = n - 1 ;
5534
break ;
5535
}
5536
assert! (
5537
outcome.is_err ( ) ,
5538
" {name}: a crash at step {n} was swallowed"
5539
) ;
5540
for repo in [ " a" , " origin" ] {
5541
let r = Repo:: open( & run.join ( repo) ) .unwrap_or_else ( | e | {
5542
panic! ( " {name} crash at step {n}: {repo} no longer opens: {e}" )
5543
} ) ;
5544
let rep = r.fsck ( ) .unwrap ( ) ;
5545
assert_eq! (
5546
rep.errors ( ) ,
5547
0 ,
5548
" {name} crash at step {n}: {repo} fails fsck: {:?}" ,
5549
rep.problems
5550
) ;
5551
}
5552
5553
let a = open ( & run.join ( " a" ) ) ;
5554
5555
let landed = a
5556
.bookmark_heads ( )
5557
.unwrap ( )
5558
.iter ( )
5559
.any ( | h | a.store ( ) .get_change ( h) .unwrap ( ) .message = = " second" ) ;
5560
let again = match ( name, landed) {
5561
( " commit" , true ) => Ok ( ( ) ) ,
5562
( " commit" , false ) => a.commit ( " second" , & [ ] ) .map ( | _| ( ) ) ,
5563
_ => op ( & a) ,
5564
} ;
5565
again.unwrap_or_else ( | e | panic! ( " {name} crash at step {n}: retry failed: {e}" ) ) ;
5566
assert_eq! ( a.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
5567
fs:: remove_dir_all( & run) .unwrap ( ) ;
5568
}
5569
assert! ( steps > = 3 , " {name} has only {steps} durable steps?" ) ;
5570
}
5571
}
19555025 Andy McCall 1 month ago ↶
5572
5573
5574
5575
5576
# [ test ]
5577
fn concurrent_commits_in_two_workspaces_lose_nothing ( ) {
5578
hermetic ( ) ;
5579
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5580
let root = dir.path ( ) ;
5581
let origin = root.join ( " origin" ) ;
5582
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
5583
let main = root.join ( " main" ) ;
5584
let mut repo = Repo:: init( & main) .unwrap ( ) ;
5585
repo.config
5586
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
5587
.unwrap ( ) ;
5588
repo.save_config ( ) .unwrap ( ) ;
5589
write ( & main, " seed.txt" , b " seed\n " ) ;
5590
repo.commit ( " seed" , & [ ] ) .unwrap ( ) ;
5591
let second = root.join ( " second" ) ;
5592
repo.workspace_add ( & second, None ) .unwrap ( ) ;
5593
drop ( repo) ;
5594
5595
const EACH : usize = 15 ;
5596
std:: thread:: scope( | s | {
5597
for ( tag, dir, push) in [ ( " a" , main.clone ( ) , true ) , ( " b" , second.clone ( ) , false ) ] {
5598
s.spawn ( move | | {
5599
let r = Repo:: open( & dir) .unwrap ( ) ;
5600
for i in 0 .. EACH {
5601
write (
5602
& dir,
5603
& format! ( " {tag} -{i} .txt" ) ,
5604
format! ( " {tag} {i} \n " ) .as_bytes ( ) ,
5605
) ;
5606
r.commit ( & format! ( " {tag} {i} " ) , & [ ] ) .unwrap ( ) ;
5607
if push & & i % 5 = = 4 {
5608
5609
let _ = r.push ( " origin" , false ) ;
5610
}
5611
}
5612
} ) ;
5613
}
5614
} ) ;
5615
5616
let repo = Repo:: open( & main) .unwrap ( ) ;
5617
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5618
let state = repo.load_state ( & heads) .unwrap ( ) ;
5619
let messages: BTreeSet< String> = state.changes ( ) .map ( | ( _, m ) | m.message.clone ( ) ) .collect ( ) ;
5620
for tag in [ " a" , " b" ] {
5621
for i in 0 .. EACH {
5622
assert! (
5623
messages.contains ( & format! ( " {tag} {i} " ) ) ,
5624
" lost commit {tag} {i}; have {messages:?}"
5625
) ;
5626
}
5627
}
5628
let rep = repo.fsck ( ) .unwrap ( ) ;
5629
assert_eq! ( rep.errors ( ) , 0 , " {:?}" , rep.problems) ;
5630
assert! ( repo.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
5631
let hosted = Repo:: open( & origin) .unwrap ( ) ;
5632
let hosted_heads = hosted.bookmark_heads ( ) .unwrap ( ) ;
5633
assert_eq! (
5634
hosted.load_state ( & hosted_heads) .unwrap ( ) .len ( ) ,
5635
state.len ( ) ,
5636
" the remote has every commit"
5637
) ;
5638
}
5639
5640
5641
5642
5643
# [ test ]
5644
fn operations_from_the_same_parent_are_merged ( ) {
5645
hermetic ( ) ;
5646
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5647
let root = dir.path ( ) ;
5648
let repo = Repo:: init( root) .unwrap ( ) ;
5649
write ( root, " a.txt" , b " a\n " ) ;
5650
let a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
5651
write ( root, " b.txt" , b " b\n " ) ;
5652
let b = repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
5653
let ( x, op) = repo.head_op ( ) .unwrap ( ) ;
5654
let state = | heads : Vec< ChangeHash> | repo.store ( ) .put_state ( & StateObject:: new( heads) ) .unwrap ( ) ;
5655
5656
5657
let mut one = op.view.clone ( ) ;
5658
one.bookmarks.insert ( " main" .into ( ) , state ( vec! [ a] ) ) ;
5659
one.bookmarks.insert ( " topic" .into ( ) , state ( vec! [ a] ) ) ;
5660
let mut two = op.view.clone ( ) ;
5661
two.bookmarks.insert ( " main" .into ( ) , state ( vec! [ b] ) ) ;
5662
repo.record_op ( & [ x] , " one" , one) .unwrap ( ) ;
5663
let merged = repo.record_op ( & [ x] , " two" , two) .unwrap ( ) ;
5664
5665
let ( head, op) = repo.head_op ( ) .unwrap ( ) ;
5666
assert_eq! ( head, merged) ;
5667
assert_eq! ( op.parents.len ( ) , 2 , " a merge of the two operations" ) ;
5668
assert! ( op.command.contains ( " merged with a concurrent operation" ) ) ;
5669
let mut main = repo
5670
.store ( )
5671
.get_state ( & op.view.bookmarks[ " main" ] )
5672
.unwrap ( )
5673
.heads;
5674
main.sort ( ) ;
5675
let mut want = vec! [ a, b] ;
5676
want.sort ( ) ;
5677
assert_eq! ( main, want, " both sides' heads are kept" ) ;
5678
assert! (
5679
op.view.bookmarks.contains_key ( " topic" ) ,
5680
" a one-sided change survives"
5681
) ;
5682
assert_eq! ( repo.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
5683
}
3e5772cc Andy McCall 1 month ago ↶
5684
5685
# [ test ]
5686
fn failed_requests_come_with_a_next_step ( ) {
5687
use crate :: protocol:: hint_for;
5688
assert! ( hint_for ( " https://h/x/oxy/v1/push: HTTP 401: bad token" )
5689
.unwrap ( )
5690
.contains ( " token" ) ) ;
5691
assert! ( hint_for ( " …: HTTP 403: forbidden" )
5692
.unwrap ( )
5693
.contains ( " repo:write" ) ) ;
5694
assert! ( hint_for ( " …: HTTP 404: no such repository" )
5695
.unwrap ( )
5696
.contains ( " owner and repository name" ) ) ;
5697
assert! ( hint_for ( " invalid peer certificate: UnknownIssuer" )
5698
.unwrap ( )
5699
.contains ( " oxyhub-ca.pem" ) ) ;
5700
assert! ( hint_for ( " Connect error: Connection refused (os error 111)" )
5701
.unwrap ( )
5702
.contains ( " running" ) ) ;
5703
assert! ( hint_for ( " Dns Failed: failed to lookup address information" )
5704
.unwrap ( )
5705
.contains ( " resolve" ) ) ;
5706
assert_eq! ( hint_for ( " something else entirely" ) , None ) ;
5707
}
5708
5709
# [ test ]
5710
fn doctor_names_problems_and_what_to_do ( ) {
5711
hermetic ( ) ;
5712
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5713
let root = dir.path ( ) ;
5714
let origin = root.join ( " origin" ) ;
5715
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
5716
let mut repo = Repo:: init( & root.join ( " r" ) )
5717
.unwrap ( )
5718
.with_home ( root.join ( " home" ) ) ;
5719
let find = | r : & DoctorReport, area : & str , h : Health| {
5720
r.checks
5721
.iter ( )
5722
.find ( | c | c.area = = area & & c.health = = h )
5723
.cloned ( )
5724
} ;
5725
5726
let r = repo.doctor ( true ) ;
5727
assert! ( find ( & r, " identity" , Health:: Warn)
5728
.unwrap ( )
5729
.hint
5730
.unwrap ( )
5731
.contains ( " oxy identity create" ) ) ;
5732
assert! ( find ( & r, " repository" , Health:: Ok) .is_some ( ) ) ;
5733
5734
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
5735
.unwrap ( ) ;
5736
repo.config.set ( " author.name" , " Ada" ) .unwrap ( ) ;
5737
repo.config.set ( " author.email" , " [email protected] " ) .unwrap ( ) ;
5738
repo.config
5739
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
5740
.unwrap ( ) ;
5741
repo.config
5742
.set ( " remote.down.url" , " http://127.0.0.1:9/a/b" )
5743
.unwrap ( ) ;
5744
repo.config.set ( " remote.down.token" , " t" ) .unwrap ( ) ;
5745
repo.config
5746
.set ( " remote.badca.url" , " https://example.invalid/a/b" )
5747
.unwrap ( ) ;
5748
repo.config
5749
.set (
5750
" remote.badca.ca" ,
5751
& root.join ( " missing.pem" ) .to_string_lossy ( ) ,
5752
)
5753
.unwrap ( ) ;
5754
repo.save_config ( ) .unwrap ( ) ;
5755
5756
let r = repo.doctor ( true ) ;
5757
assert! ( find ( & r, " identity" , Health:: Ok) .is_some ( ) , " {r:?}" ) ;
5758
assert! ( find ( & r, " author" , Health:: Ok) .is_some ( ) , " {r:?}" ) ;
5759
assert! ( find ( & r, " remote origin" , Health:: Ok)
5760
.unwrap ( )
5761
.message
5762
.contains ( " first push binds it" ) ) ;
5763
let down = find ( & r, " remote down" , Health:: Fail) .unwrap ( ) ;
5764
assert! (
5765
down.hint.as_deref ( ) .unwrap_or ( " " ) .contains ( " running" ) ,
5766
" {down:?}"
5767
) ;
5768
let badca = find ( & r, " remote badca" , Health:: Fail) .unwrap ( ) ;
5769
assert! (
5770
badca.hint.unwrap ( ) .contains ( " remote.badca.ca" ) ,
5771
" the remote's own name is in the hint"
5772
) ;
5773
assert_eq! ( r.worst ( ) , Health:: Fail) ;
5774
5775
5776
let r = repo.doctor ( false ) ;
5777
assert! ( find ( & r, " remote down" , Health:: Fail) .is_none ( ) ) ;
5778
assert! ( find ( & r, " remote down" , Health:: Ok)
5779
.unwrap ( )
5780
.message
5781
.contains ( " not contacted" ) ) ;
5782
}
62794ecc Andy McCall 1 month ago ↶
5783
5784
5785
5786
5787
# [ test ]
5788
fn deleting_a_line_after_an_insertion_below_it_is_not_a_conflict ( ) {
5789
hermetic ( ) ;
5790
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5791
let root = dir.path ( ) ;
5792
let repo = Repo:: init( root) .unwrap ( ) ;
5793
write ( root, " f" , b " a\n b\n c\n " ) ;
5794
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
5795
write ( root, " f" , b " a\n b\n x\n c\n " ) ;
5796
let insert = repo.commit ( " insert x after b" , & [ ] ) .unwrap ( ) ;
5797
write ( root, " f" , b " a\n x\n c\n " ) ;
5798
let delete = repo.commit ( " delete b" , & [ ] ) .unwrap ( ) ;
5799
assert! (
5800
repo.store ( )
5801
.get_change ( & delete)
5802
.unwrap ( )
5803
.deps
5804
.contains ( & insert) ,
5805
" the deletion depends on the insertion below the deleted line"
5806
) ;
5807
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5808
assert_eq! ( heads, vec! [ delete] ) ;
5809
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
5810
assert! ( ! snap.is_conflicted ( ) ) ;
5811
assert! (
5812
matches! ( & snap.files[ " f" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n x\n c\n " )
5813
) ;
5814
}
5815
5816
5817
5818
# [ test ]
5819
fn a_binary_file_reverted_to_an_earlier_blob_keeps_it ( ) {
5820
hermetic ( ) ;
5821
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5822
let root = dir.path ( ) ;
5823
let repo = Repo:: init( root) .unwrap ( ) ;
5824
write ( root, " bin" , b " \0 A" ) ;
5825
let v1 = repo.commit ( " v1" , & [ ] ) .unwrap ( ) ;
5826
write ( root, " bin" , b " \0 B" ) ;
5827
let v2 = repo.commit ( " v2" , & [ ] ) .unwrap ( ) ;
5828
assert! ( repo.store ( ) .get_change ( & v2) .unwrap ( ) .deps.contains ( & v1) ) ;
5829
write ( root, " bin" , b " \0 A" ) ;
5830
let v3 = repo.commit ( " back to A" , & [ ] ) .unwrap ( ) ;
5831
assert! ( repo.store ( ) .get_change ( & v3) .unwrap ( ) .deps.contains ( & v2) ) ;
5832
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ v3] ) ;
5833
let state = repo.load_state ( & [ v3] ) .unwrap ( ) ;
5834
let snap = state.materialize ( ) ;
5835
let FileContent:: Opaque { blobs } = & snap.files[ " bin" ] .content else {
5836
panic! ( " opaque" )
5837
} ;
5838
assert_eq! ( blobs.len ( ) , 1 , " one live blob, not none and not two" ) ;
5839
assert_eq! ( repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) , b " \0 A" ) ;
5840
}
5841
5842
5843
5844
5845
# [ test ]
5846
fn git_import_resolves_a_binary_merged_in_git_and_verifies ( ) {
5847
hermetic ( ) ;
5848
if std:: process:: Command:: new( " git" )
5849
.arg ( " --version" )
5850
.output ( )
5851
.is_err ( )
5852
{
5853
eprintln! ( " git not installed; skipping" ) ;
5854
return ;
5855
}
5856
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5857
let root = dir.path ( ) ;
5858
let g = root.join ( " src.git" ) ;
5859
fs:: create_dir_all( & g) .unwrap ( ) ;
5860
let git = | args : & [& str ]| -> String {
5861
let out = std:: process:: Command:: new( " git" )
5862
.arg ( " -C" )
5863
.arg ( & g)
5864
.args ( [
5865
" -c" ,
5866
" user.name=Grace" ,
5867
" -c" ,
5868
" [email protected] " ,
5869
" -c" ,
5870
" commit.gpgsign=false" ,
5871
] )
5872
.args ( args)
5873
.output ( )
5874
.unwrap ( ) ;
5875
assert! (
5876
out.status.success ( ) ,
5877
" git {args:?}: {}" ,
5878
String :: from_utf8_lossy( & out.stderr)
5879
) ;
5880
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
5881
} ;
5882
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
5883
write ( & g, " logo.bin" , b " \0 base" ) ;
5884
write ( & g, " notes.txt" , b " a\n b\n c\n " ) ;
5885
git ( & [ " add" , " ." ] ) ;
5886
git ( & [ " commit" , " -qm" , " base" ] ) ;
5887
git ( & [ " checkout" , " -qb" , " side" ] ) ;
5888
write ( & g, " logo.bin" , b " \0 side" ) ;
5889
write ( & g, " notes.txt" , b " a\n b\n x\n c\n " ) ;
5890
git ( & [ " commit" , " -qam" , " side" ] ) ;
5891
git ( & [ " checkout" , " -q" , " main" ] ) ;
5892
write ( & g, " logo.bin" , b " \0 main" ) ;
5893
git ( & [ " commit" , " -qam" , " main" ] ) ;
5894
5895
let _ = std:: process:: Command:: new( " git" )
5896
.arg ( " -C" )
5897
.arg ( & g)
5898
.args ( [
5899
" -c" ,
5900
" user.name=G" ,
5901
" -c" ,
5902
" user.email=g@e" ,
5903
" merge" ,
5904
" -q" ,
5905
" side" ,
5906
] )
5907
.output ( ) ;
5908
write ( & g, " logo.bin" , b " \0 main" ) ;
5909
git ( & [ " add" , " ." ] ) ;
5910
git ( & [ " commit" , " -qm" , " merge side" ] ) ;
5911
5912
write ( & g, " logo.bin" , b " \0 base" ) ;
5913
git ( & [ " commit" , " -qam" , " back to base" ] ) ;
5914
5915
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
5916
let rep = repo.import_git ( & g, " main" ) .unwrap ( ) ;
5917
assert_eq! ( rep.merges, 1 ) ;
5918
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
5919
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
5920
assert_eq! ( v.commits, 5 ) ;
5921
let snap = repo
5922
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
5923
.unwrap ( )
5924
.materialize ( ) ;
5925
assert! ( ! snap.is_conflicted ( ) ) ;
5926
let FileContent:: Opaque { blobs } = & snap.files[ " logo.bin" ] .content else {
5927
panic! ( " opaque" )
5928
} ;
5929
assert_eq! ( repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) , b " \0 base" ) ;
5930
}
c863c7a7 Andy McCall 1 month ago ↶
5931
5932
5933
5934
5935
# [ cfg ( unix ) ]
5936
# [ test ]
5937
fn windows_file_systems_keep_exec_bits_and_symlinks ( ) {
5938
use crate :: portable:: Platform;
5939
use std:: os:: unix:: fs:: PermissionsExt;
5940
hermetic ( ) ;
5941
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5942
let root = dir.path ( ) ;
5943
let repo = Repo:: init( root) .unwrap ( ) ;
5944
write ( root, " run.sh" , b " #!/bin/sh\n " ) ;
5945
fs:: set_permissions( root.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
5946
std:: os:: unix:: fs:: symlink( " run.sh" , root.join ( " link" ) ) .unwrap ( ) ;
5947
repo.commit ( " script and link" , & [ ] ) .unwrap ( ) ;
5948
drop ( repo) ;
5949
5950
5951
fs:: set_permissions( root.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
5952
fs:: remove_file( root.join ( " link" ) ) .unwrap ( ) ;
5953
write ( root, " link" , b " run.sh" ) ;
5954
let win = Repo:: open( root) .unwrap ( ) .with_platform ( Platform:: windows( ) ) ;
5955
let s = win.snapshot ( " oxy status" ) .unwrap ( ) ;
5956
assert! (
5957
s.statuses.is_empty ( ) ,
5958
" nothing changed on Windows: {:?}" ,
5959
statuses ( & s)
5960
) ;
5961
5962
write ( root, " run.sh" , b " #!/bin/sh\n echo hi\n " ) ;
5963
write ( root, " link" , b " other" ) ;
5964
let h = win.commit ( " edit on Windows" , & [ ] ) .unwrap ( ) ;
5965
let snap = win.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
5966
assert! ( snap.files[ " run.sh" ] .mode.exec, " the exec bit survives" ) ;
5967
assert_eq! ( snap.files[ " link" ] .kind, NodeKind:: Symlink) ;
5968
5969
let linux = Repo:: open( root) .unwrap ( ) ;
5970
assert! ( ! linux.snapshot ( " oxy status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
5971
}
5972
5973
5974
5975
# [ test ]
5976
fn paths_other_platforms_cannot_hold_are_reported ( ) {
5977
use crate :: portable:: Platform;
5978
hermetic ( ) ;
5979
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5980
let root = dir.path ( ) ;
5981
let repo = Repo:: init( root) .unwrap ( ) ;
5982
write ( root, " README.md" , b " a\n " ) ;
5983
write ( root, " readme.md" , b " b\n " ) ;
5984
write ( root, " src/aux.c" , b " int x;\n " ) ;
5985
let h = repo.commit ( " unportable" , & [ ] ) .unwrap ( ) ;
5986
let r = repo.doctor ( false ) ;
5987
let port = r.checks.iter ( ) .find ( | c | c.area = = " portability" ) .unwrap ( ) ;
5988
assert_eq! ( port.health, Health:: Warn) ;
5989
assert! (
5990
port.message.contains ( " differ only in case" ) & & port.message.contains ( " aux.c" ) ,
5991
" {}" ,
5992
port.message
5993
) ;
5994
let mac = Repo:: open( root) .unwrap ( ) .with_platform ( Platform:: macos( ) ) ;
5995
let rep = mac.checkout ( & [ h] , true , " oxy checkout" ) .unwrap ( ) ;
5996
assert! (
5997
rep.notes
5998
.iter ( )
5999
.any ( | n | n.contains ( " README.md and readme.md" ) ) ,
6000
" {:?}" ,
6001
rep.notes
6002
) ;
6003
assert! (
6004
! rep.notes.iter ( ) .any ( | n | n.contains ( " aux.c" ) ) ,
6005
" macOS allows aux.c"
6006
) ;
6007
}
bc489b6c Andy McCall 1 month ago ↶
6008
6009
# [ test ]
6010
fn history_and_blame_of_a_loaded_state_match_the_full_walk ( ) {
6011
hermetic ( ) ;
6012
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6013
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
6014
write ( dir.path ( ) , " a.txt" , b " one\n " ) ;
6015
write ( dir.path ( ) , " other.txt" , b " x\n " ) ;
6016
write ( dir.path ( ) , " bin.dat" , & [ 0 u8 , 1 , 2 , 0 , 255 ] ) ;
6017
repo.snapshot ( " t" ) .unwrap ( ) ;
6018
let c1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
6019
write ( dir.path ( ) , " other.txt" , b " y\n " ) ;
6020
repo.snapshot ( " t" ) .unwrap ( ) ;
6021
repo.commit ( " other only" , & [ ] ) .unwrap ( ) ;
6022
write ( dir.path ( ) , " a.txt" , b " one\n two\n " ) ;
6023
repo.snapshot ( " t" ) .unwrap ( ) ;
6024
let c3 = repo.commit ( " second line" , & [ ] ) .unwrap ( ) ;
6025
write ( dir.path ( ) , " other.txt" , b " z\n " ) ;
6026
repo.snapshot ( " t" ) .unwrap ( ) ;
6027
repo.commit ( " other again" , & [ ] ) .unwrap ( ) ;
6028
6029
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6030
let state = repo.load_state ( & heads) .unwrap ( ) ;
6031
let full: Vec< ChangeHash> = repo
6032
.log_for_path_in ( " a.txt" , & heads)
6033
.unwrap ( )
6034
.iter ( )
6035
.map ( | e | e.hash )
6036
.collect ( ) ;
6037
assert_eq! (
6038
full,
6039
vec! [ c3, c1] ,
6040
" newest first, only changes touching the path"
6041
) ;
6042
let newest: Vec< ChangeHash> = repo
6043
.log_for_path_of ( & state, " a.txt" , Some ( 1 ) )
6044
.unwrap ( )
6045
.iter ( )
6046
.map ( | e | e.hash )
6047
.collect ( ) ;
6048
assert_eq! ( newest, vec! [ c3] , " stops at the first" ) ;
6049
assert_eq! (
6050
repo.log_for_path_of ( & state, " a.txt" , None ) .unwrap ( ) .len ( ) ,
6051
2
6052
) ;
6053
assert! ( repo
6054
.log_for_path_of ( & state, " missing.txt" , Some ( 1 ) )
6055
.is_err ( ) ) ;
6056
assert_eq! (
6057
repo.log_state ( & heads) .unwrap ( ) .len ( ) ,
6058
repo.log_of ( & state) .len ( )
6059
) ;
6060
6061
let tree = state.tree ( ) .view ( ) ;
6062
let via_state = repo.blame_of ( & state, & tree, " a.txt" ) .unwrap ( ) ;
6063
let via_heads = repo.blame_in ( " a.txt" , & heads) .unwrap ( ) ;
6064
assert_eq! (
6065
via_state
6066
.iter ( )
6067
.map ( | l | ( l.hash, l.text.clone ( ) ) )
6068
.collect:: < Vec< _ > > ( ) ,
6069
via_heads
6070
.iter ( )
6071
.map ( | l | ( l.hash, l.text.clone ( ) ) )
6072
.collect:: < Vec< _ > > ( )
6073
) ;
6074
assert_eq! (
6075
via_state.iter ( ) .map ( | l | l.hash ) .collect:: < Vec< _ > > ( ) ,
6076
vec! [ c1, c3]
6077
) ;
6078
assert! ( repo.blame_of ( & state, & tree, " missing.txt" ) .is_err ( ) ) ;
6079
}
696bbf29 Andy McCall 1 month ago ↶
6080
6081
6082
6083
6084
# [ cfg ( unix ) ]
6085
# [ test ]
6086
fn stat_cache_takes_unchanged_text_from_the_base_and_misses_no_edit ( ) {
6087
use std:: os:: unix:: fs:: PermissionsExt;
6088
hermetic ( ) ;
6089
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6090
let root = dir.path ( ) ;
6091
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6092
write ( root, " a.txt" , b " one\n two\n " ) ;
6093
write ( root, " b.txt" , b " bee\n " ) ;
6094
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6095
let age = | p : & str | {
6096
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6097
fs:: File:: options( )
6098
.write ( true )
6099
.open ( root.join ( p) )
6100
.unwrap ( )
6101
.set_modified ( old)
6102
.unwrap ( ) ;
6103
} ;
6104
age ( " a.txt" ) ;
6105
age ( " b.txt" ) ;
6106
assert! (
6107
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
6108
" records the aged stat"
6109
) ;
6110
6111
6112
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6113
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
6114
assert! (
6115
! snap.changed & & snap.statuses.is_empty ( ) ,
6116
" {:?}" ,
6117
snap.statuses
6118
) ;
6119
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6120
6121
6122
6123
write ( root, " b.txt" , b " bee edited\n " ) ;
6124
age ( " b.txt" ) ;
6125
let first = repo.snapshot ( " status" ) .unwrap ( ) ;
6126
assert_eq! (
6127
statuses ( & first) ,
6128
vec! [ ( " b.txt" .to_string ( ) , ChangeStatus:: Modified) ]
6129
) ;
6130
let again = repo.snapshot ( " status" ) .unwrap ( ) ;
6131
assert_eq! (
6132
statuses ( & again) ,
6133
vec! [ ( " b.txt" .to_string ( ) , ChangeStatus:: Modified) ] ,
6134
" still seen once stat-clean"
6135
) ;
6136
assert! ( ! again.changed, " and the working copy hashes the same" ) ;
6137
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6138
assert! (
6139
repo.snapshot ( " status" ) .is_err ( ) ,
6140
" an edited file must be read, never taken from the base"
6141
) ;
6142
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6143
6144
6145
write ( root, " a.txt" , b " one\n TWO\n " ) ;
6146
assert_eq! (
6147
statuses ( & repo.snapshot ( " status" ) .unwrap ( ) ) ,
6148
vec! [
6149
( " a.txt" .to_string ( ) , ChangeStatus:: Modified) ,
6150
( " b.txt" .to_string ( ) , ChangeStatus:: Modified)
6151
]
6152
) ;
6153
6154
repo.commit ( " edits" , & [ ] ) .unwrap ( ) ;
6155
age ( " a.txt" ) ;
6156
age ( " b.txt" ) ;
6157
assert! ( repo.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6158
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6159
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6160
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
6161
assert! ( ! snap.changed & & snap.statuses.is_empty ( ) ) ;
6162
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6163
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6164
}
afaba5f1 Andy McCall 1 month ago ↶
6165
6166
6167
6168
6169
# [ test ]
6170
fn checkpoints_keep_large_graphs_as_blobs_of_their_own ( ) {
6171
hermetic ( ) ;
6172
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6173
let root = dir.path ( ) ;
6174
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6175
let long: String = ( 0 .. 300 )
6176
.map ( | i | format! ( " line {i} of a long file\n " ) )
6177
.collect ( ) ;
6178
write ( root, " long.txt" , long.as_bytes ( ) ) ;
6179
write ( root, " short.txt" , b " short\n " ) ;
6180
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6181
let ( r1, _ , _ ) = repo.checkpoint ( ) .unwrap ( ) ;
6182
let cp1 = crate :: objects:: Checkpoint:: decode(
6183
& repo.store ( ) .get_typed ( & r1, ObjectType:: Checkpoint) .unwrap ( ) ,
6184
)
6185
.unwrap ( ) ;
6186
let graphs1 = repo.checkpoint_graph_blobs ( & cp1) .unwrap ( ) ;
6187
assert_eq! ( graphs1.len ( ) , 1 , " only the long file's graph is a blob" ) ;
6188
6189
6190
repo.gc ( true ) .unwrap ( ) ;
6191
assert! ( repo.store ( ) .has ( & graphs1[ 0 ] ) ) ;
6192
let report = repo.fsck ( ) .unwrap ( ) ;
6193
assert_eq! ( report.errors ( ) , 0 , " {:?}" , report.problems) ;
6194
6195
6196
write ( root, " short.txt" , b " short, edited\n " ) ;
6197
repo.commit ( " edit the short file" , & [ ] ) .unwrap ( ) ;
6198
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6199
let fast = repo.load_state ( & heads) .unwrap ( ) ;
6200
let full = repo.load_state_full ( & heads) .unwrap ( ) ;
6201
assert_eq! ( fast.checkpoint_payload ( ) , full.checkpoint_payload ( ) ) ;
6202
6203
6204
let ( r2, _ , _ ) = repo.checkpoint ( ) .unwrap ( ) ;
6205
let cp2 = crate :: objects:: Checkpoint:: decode(
6206
& repo.store ( ) .get_typed ( & r2, ObjectType:: Checkpoint) .unwrap ( ) ,
6207
)
6208
.unwrap ( ) ;
6209
assert_eq! ( repo.checkpoint_graph_blobs ( & cp2) .unwrap ( ) , graphs1) ;
6210
6211
6212
let bundle = repo.checkpoint_bundle_for ( & heads) .unwrap ( ) ;
6213
assert! ( bundle.objects.iter ( ) .any ( | ( t , body ) | * t = = ObjectType:: Blob
6214
& & crate :: hash:: hash_object( ObjectType:: Blob, body) = = graphs1[ 0 ] ) ) ;
6215
6216
6217
let state = repo.load_state_full ( & heads) .unwrap ( ) ;
6218
let old = repo.store_blob ( & state.checkpoint_payload ( ) ) .unwrap ( ) ;
6219
let cp_old = crate :: objects:: Checkpoint {
6220
heads: cp2.heads.clone ( ) ,
6221
count: cp2.count,
6222
payload: old,
6223
} ;
6224
let r_old = repo
6225
.store ( )
6226
.put ( ObjectType:: Checkpoint, & cp_old.encode ( ) )
6227
.unwrap ( ) ;
6228
repo.install_checkpoint ( " main" , & r_old) .unwrap ( ) ;
6229
assert_eq! (
6230
repo.load_state ( & heads) .unwrap ( ) .checkpoint_payload ( ) ,
6231
full.checkpoint_payload ( )
6232
) ;
6233
assert_eq! ( repo.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
6234
6235
6236
6237
let dir2 = tempfile:: tempdir( ) .unwrap ( ) ;
6238
let root2 = dir2.path ( ) ;
6239
let repo2 = Repo:: init( root2) .unwrap ( ) .with_home ( root2.join ( " home" ) ) ;
6240
write ( root2, " long.txt" , long.as_bytes ( ) ) ;
6241
repo2.commit ( " base" , & [ ] ) .unwrap ( ) ;
6242
let ( r, _ , _ ) = repo2.checkpoint ( ) .unwrap ( ) ;
6243
let cp = crate :: objects:: Checkpoint:: decode(
6244
& repo2.store ( ) .get_typed ( & r, ObjectType:: Checkpoint) .unwrap ( ) ,
6245
)
6246
.unwrap ( ) ;
6247
let graph = repo2.checkpoint_graph_blobs ( & cp) .unwrap ( ) [ 0 ] ;
64818938 Andy McCall 1 month ago ↶
6248
repo2.store ( ) .remove_loose_for_test ( & graph) .unwrap ( ) ;
afaba5f1 Andy McCall 1 month ago ↶
6249
assert! ( repo2.load_state ( & repo2.bookmark_heads ( ) .unwrap ( ) ) .is_err ( ) ) ;
6250
assert! ( repo2.fsck ( ) .unwrap ( ) .errors ( ) > 0 , " fsck reports it" ) ;
6251
}
e3d071ce Andy McCall 1 month ago ↶
6252
6253
6254
6255
6256
# [ test ]
6257
fn identity_is_found_in_the_key_directory_for_a_new_repository ( ) {
6258
hermetic ( ) ;
6259
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6260
let home = dir.path ( ) .join ( " home" ) ;
6261
let mut first = Repo:: init( & dir.path ( ) .join ( " a" ) )
6262
.unwrap ( )
6263
.with_home ( home.clone ( ) ) ;
6264
let ( key, _ ) = first
6265
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
6266
.unwrap ( ) ;
6267
let fresh = Repo:: init( & dir.path ( ) .join ( " b" ) )
6268
.unwrap ( )
6269
.with_home ( home.clone ( ) ) ;
6270
assert! ( ! fresh.store ( ) .has ( & key) , " nothing stored yet" ) ;
6271
let id = fresh.identity ( & key) .unwrap ( ) ;
6272
assert_eq! ( ( id.name.as_str ( ) , id.hash ( ) ) , ( " Ada" , key) ) ;
6273
6274
let keys = fresh.keys_dir.clone ( ) ;
6275
let ( other, _ ) = first
6276
.create_identity ( " Bob" , " [email protected] " , IdentityScope:: User)
6277
.unwrap ( ) ;
6278
fs:: copy(
6279
keys.join ( format! ( " {} .identity" , other.to_hex ( ) ) ) ,
6280
keys.join ( format! ( " {} .identity" , key.to_hex ( ) ) ) ,
6281
)
6282
.unwrap ( ) ;
6283
assert! ( fresh
6284
.identity ( & key)
6285
.unwrap_err ( )
6286
.to_string ( )
6287
.contains ( " does not hold identity" ) ) ;
6288
}
cc7a0056 Andy McCall 1 month ago ↶
6289
6290
6291
6292
6293
6294
# [ cfg ( unix ) ]
6295
# [ test ]
6296
fn status_snapshots_skip_files_unchanged_since_the_manifest ( ) {
6297
use std:: os:: unix:: fs:: PermissionsExt;
6298
hermetic ( ) ;
6299
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6300
let root = dir.path ( ) ;
6301
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6302
for ( p, c) in [ ( " a.txt" , " one\n " ) , ( " b.txt" , " bee\n " ) , ( " d/c.txt" , " sea\n " ) ] {
6303
write ( root, p, c.as_bytes ( ) ) ;
6304
}
6305
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6306
let age = | p : & str | {
6307
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6308
fs:: File:: options( )
6309
.write ( true )
6310
.open ( root.join ( p) )
6311
.unwrap ( )
6312
.set_modified ( old)
6313
.unwrap ( ) ;
6314
} ;
6315
let lock = | p : & str , mode : u32 | {
6316
fs:: set_permissions( root.join ( p) , fs:: Permissions:: from_mode( mode) ) .unwrap ( )
6317
} ;
6318
for p in [ " a.txt" , " b.txt" , " d/c.txt" ] {
6319
age ( p) ;
6320
}
6321
assert! ( repo.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6322
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
6323
assert! (
6324
m.entries.values ( ) .all ( | e | e.graph.is_some ( ) ) ,
6325
" keys recorded"
6326
) ;
6327
6328
6329
6330
lock ( " a.txt" , 0o000 ) ;
6331
lock ( " b.txt" , 0o000 ) ;
6332
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6333
assert! ( ! snap.changed & & snap.statuses.is_empty ( ) ) ;
6334
assert_eq! (
6335
snap.base_snapshot
6336
.unrendered
6337
.iter ( )
6338
.cloned ( )
6339
.collect:: < Vec< _ > > ( ) ,
6340
vec! [ " a.txt" , " b.txt" , " d/c.txt" ]
6341
) ;
6342
assert! ( ! snap.base.is_full ( ) ) ;
6343
assert! ( ! snap.base_snapshot.is_conflicted ( ) ) ;
6344
6345
let full = repo.snapshot ( " status" ) .unwrap ( ) ;
6346
assert! ( full.statuses.is_empty ( ) & & full.base.is_full ( ) ) ;
6347
assert! ( full.base_snapshot.unrendered.is_empty ( ) ) ;
6348
assert! ( matches! ( & full.base_snapshot.files[ " a.txt" ] .content,
6349
FileContent:: Text { bytes, .. } if bytes = = b " one\n " ) ) ;
6350
6351
6352
write ( root, " d/c.txt" , b " sea edited\n " ) ;
6353
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6354
assert_eq! (
6355
statuses ( & snap) ,
6356
vec! [ ( " d/c.txt" .to_string ( ) , ChangeStatus:: Modified) ]
6357
) ;
6358
assert! ( ! snap.base_snapshot.unrendered.contains ( " d/c.txt" ) ) ;
6359
6360
lock ( " b.txt" , 0o100 ) ;
6361
assert_eq! (
6362
statuses ( & repo.snapshot_status ( " status" ) .unwrap ( ) ) ,
6363
vec! [
6364
( " b.txt" .to_string ( ) , ChangeStatus:: ModeChanged) ,
6365
( " d/c.txt" .to_string ( ) , ChangeStatus:: Modified)
6366
]
6367
) ;
6368
lock ( " b.txt" , 0o000 ) ;
6369
6370
6371
6372
repo.commit_selection ( " edit c" , & [ ] , & Selection:: Paths( vec! [ " d/c.txt" .into ( ) ] ) )
6373
.unwrap ( ) ;
6374
age ( " d/c.txt" ) ;
6375
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6376
assert! ( snap.statuses.is_empty ( ) , " {:?}" , snap.statuses) ;
6377
assert! ( snap.base_snapshot.unrendered.contains ( " a.txt" ) ) ;
6378
lock ( " d/c.txt" , 0o000 ) ;
6379
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6380
assert! ( snap.statuses.is_empty ( ) ) ;
6381
assert! ( snap.base_snapshot.unrendered.contains ( " d/c.txt" ) ) ;
6382
for p in [ " a.txt" , " b.txt" , " d/c.txt" ] {
6383
lock ( p, 0o644 ) ;
6384
}
6385
}
6386
6387
6388
6389
# [ cfg ( unix ) ]
6390
# [ test ]
6391
fn a_changed_attributes_file_disables_skipping_unchanged_files ( ) {
6392
hermetic ( ) ;
6393
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6394
let root = dir.path ( ) ;
6395
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6396
write ( root, " a.txt" , b " crlf\r \n " ) ;
6397
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6398
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6399
fs:: File:: options( )
6400
.write ( true )
6401
.open ( root.join ( " a.txt" ) )
6402
.unwrap ( )
6403
.set_modified ( old)
6404
.unwrap ( ) ;
6405
assert! ( repo.snapshot_status ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6406
assert! ( repo
6407
.snapshot_status ( " status" )
6408
.unwrap ( )
6409
.base_snapshot
6410
.unrendered
6411
.contains ( " a.txt" ) ) ;
6412
6413
write ( root, " .oxyattributes" , b " *.txt eol=lf\n " ) ;
6414
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6415
assert_eq! (
6416
statuses ( & snap) ,
6417
vec! [
6418
( " .oxyattributes" .to_string ( ) , ChangeStatus:: Added) ,
6419
( " a.txt" .to_string ( ) , ChangeStatus:: Modified)
6420
]
6421
) ;
6422
}
54cd08a1 Andy McCall 1 month ago ↶
6423
6424
6425
6426
6427
# [ test ]
6428
fn bookmarks_with_slashes_push_and_checkpoint ( ) {
6429
hermetic ( ) ;
6430
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6431
let root = dir.path ( ) ;
6432
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
6433
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
6434
let mut repo = Repo:: clone( & url, & root.join ( " r" ) )
6435
.unwrap ( )
6436
.with_home ( root.join ( " home" ) ) ;
6437
6438
6439
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
6440
.unwrap ( ) ;
6441
let r = root.join ( " r" ) ;
6442
write ( & r, " a.txt" , b " a\n " ) ;
6443
let h_a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
6444
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
6445
origin.refresh ( ) .unwrap ( ) ;
54cd08a1 Andy McCall 1 month ago ↶
6446
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
6447
repo.bookmark_create ( " feature/x" , None ) .unwrap ( ) ;
6448
repo.bookmark_switch ( " feature/x" , false ) .unwrap ( ) ;
6449
write ( & r, " b.txt" , b " b\n " ) ;
6450
let h_b = repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
6451
for name in [ " feature" , " feature/x" ] {
6452
let res = repo.push_bookmark ( " origin" , name, false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
6453
origin.refresh ( ) .unwrap ( ) ;
54cd08a1 Andy McCall 1 month ago ↶
6454
assert! ( res[ 0 ] .ok, " {name}: {res:?}" ) ;
6455
}
6456
assert_eq! ( origin.resolve_heads ( " feature" ) .unwrap ( ) , vec! [ h_a] ) ;
6457
assert_eq! (
6458
origin.resolve_heads ( " feature/x" ) .unwrap ( ) ,
6459
repo.resolve_heads ( " feature/x" ) .unwrap ( )
6460
) ;
6461
assert! ( origin.resolve_heads ( " feature/x" ) .unwrap ( ) .contains ( & h_b) ) ;
6462
assert! ( origin.latest_ref_attest ( " feature/x" ) .unwrap ( ) .is_some ( ) ) ;
6463
assert! ( origin.latest_ref_attest ( " feature" ) .unwrap ( ) .is_some ( ) ) ;
6464
6465
6466
let ( cp, _ , _ ) = repo.checkpoint ( ) .unwrap ( ) ;
6467
assert_eq! ( repo.latest_checkpoint ( " feature/x" ) .unwrap ( ) .unwrap ( ) .0 , cp) ;
6468
assert! ( repo.latest_checkpoint ( " feature" ) .unwrap ( ) .is_none ( ) ) ;
6469
let heads = repo.resolve_heads ( " feature/x" ) .unwrap ( ) ;
6470
assert! ( repo.load_state_checkpointed ( & heads) .unwrap ( ) .is_some ( ) ) ;
f8c69576 Andy McCall 1 month ago ↶
6471
}
6472
6473
6474
6475
6476
# [ test ]
6477
fn bookmarks_are_deleted_on_a_remote ( ) {
6478
hermetic ( ) ;
6479
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6480
let root = dir.path ( ) ;
6481
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
6482
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
6483
let mut repo = Repo:: clone( & url, & root.join ( " r" ) )
6484
.unwrap ( )
6485
.with_home ( root.join ( " home" ) ) ;
6486
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
6487
.unwrap ( ) ;
6488
let r = root.join ( " r" ) ;
6489
write ( & r, " a.txt" , b " a\n " ) ;
6490
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
6491
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
6492
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6493
repo.bookmark_create ( " feature/x" , None ) .unwrap ( ) ;
6494
assert! ( repo.push_bookmark ( " origin" , " feature/x" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
6495
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6496
assert! ( origin.resolve_heads ( " feature/x" ) .is_ok ( ) ) ;
6497
6498
let res = repo.push_delete ( " origin" , " feature/x" ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
6499
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6500
assert! ( res[ 0 ] .ok, " {res:?}" ) ;
6501
assert! ( ! origin.view ( ) .unwrap ( ) .bookmarks.contains_key ( " feature/x" ) ) ;
6502
let ( _ , att) = origin.latest_ref_attest ( " feature/x" ) .unwrap ( ) .unwrap ( ) ;
6503
assert! (
6504
matches! ( att.attest.body, AttestBody:: Ref { new_state, .. } if new_state.is_zero ( ) ) ,
6505
" the deletion is attested"
6506
) ;
6507
assert! (
6508
repo.view ( ) .unwrap ( ) .bookmarks.contains_key ( " feature/x" ) ,
6509
" the local bookmark stays"
6510
) ;
6511
assert! ( ! repo
6512
.view ( )
6513
.unwrap ( )
6514
.remotes
6515
.get ( " origin" )
6516
.is_some_and ( | m | m.contains_key ( " feature/x" ) ) ) ;
6517
6518
6519
let res = repo.push_bookmark ( " origin" , " feature/x" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
6520
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6521
assert! ( res[ 0 ] .ok, " {res:?}" ) ;
6522
assert! ( origin.view ( ) .unwrap ( ) .bookmarks.contains_key ( " feature/x" ) ) ;
6523
6524
6525
assert! ( repo.push_delete ( " origin" , " nope" ) .is_err ( ) ) ;
6526
let res = repo.push_delete ( " origin" , " main" ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
6527
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6528
assert! (
6529
! res[ 0 ] .ok & & res[ 0 ] .reason.contains ( " default bookmark" ) ,
6530
" {res:?}"
6531
) ;
6532
assert! ( origin.view ( ) .unwrap ( ) .bookmarks.contains_key ( " main" ) ) ;
54cd08a1 Andy McCall 1 month ago ↶
6533
}
61e3bce7 Andy McCall 1 month ago ↶
6534
6535
6536
6537
6538
# [ test ]
6539
fn switching_bookmarks_carries_uncommitted_work ( ) {
6540
hermetic ( ) ;
6541
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6542
let root = dir.path ( ) ;
6543
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6544
write ( root, " a.txt" , b " a\n " ) ;
6545
write ( root, " b.txt" , b " b\n " ) ;
6546
write ( root, " gone.txt" , b " gone\n " ) ;
6547
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6548
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
6549
6550
write ( root, " a.txt" , b " a\n more\n " ) ;
6551
write ( root, " new.txt" , b " new\n " ) ;
6552
fs:: remove_file( root.join ( " gone.txt" ) ) .unwrap ( ) ;
44f1ebe7 Andy McCall 19 days ago ↶
6553
let ( report, carried) = repo.bookmark_switch_carry ( " feature" ) .unwrap ( ) ;
61e3bce7 Andy McCall 1 month ago ↶
6554
assert_eq! ( carried, vec! [ " a.txt" , " gone.txt" , " new.txt" ] ) ;
44f1ebe7 Andy McCall 19 days ago ↶
6555
6556
assert_eq! (
6557
( report.removed, report.removed_paths.len ( ) ) ,
6558
( 0 , 0 ) ,
6559
" {report:?}"
6560
) ;
61e3bce7 Andy McCall 1 month ago ↶
6561
assert_eq! ( repo.config.bookmark, " feature" ) ;
6562
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " a\n more\n " ) ;
6563
assert_eq! ( fs:: read( root.join ( " new.txt" ) ) .unwrap ( ) , b " new\n " ) ;
6564
assert! ( ! root.join ( " gone.txt" ) .exists ( ) ) ;
6565
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
6566
assert_eq! (
6567
statuses ( & snap) ,
6568
vec! [
6569
( " a.txt" .to_string ( ) , ChangeStatus:: Modified) ,
6570
( " gone.txt" .to_string ( ) , ChangeStatus:: Removed) ,
6571
( " new.txt" .to_string ( ) , ChangeStatus:: Added)
6572
]
6573
) ;
6574
repo.commit ( " the work" , & [ ] ) .unwrap ( ) ;
6575
assert! ( repo.resolve_heads ( " main" ) .unwrap ( ) .len ( ) = = 1 ) ;
6576
6577
6578
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
6579
write ( root, " b.txt" , b " b on main\n " ) ;
6580
repo.commit ( " b on main" , & [ ] ) .unwrap ( ) ;
6581
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
6582
write ( root, " b.txt" , b " b edited\n " ) ;
6583
let err = repo.bookmark_switch_carry ( " main" ) .unwrap_err ( ) .to_string ( ) ;
6584
assert! ( err.contains ( " b.txt" ) & & err.contains ( " --discard" ) , " {err}" ) ;
6585
assert_eq! ( repo.config.bookmark, " feature" , " nothing happened" ) ;
6586
assert_eq! ( fs:: read( root.join ( " b.txt" ) ) .unwrap ( ) , b " b edited\n " ) ;
6587
}
1c2bd9ab Andy McCall 1 month ago ↶
6588
6589
6590
6591
6592
# [ test ]
6593
fn merges_move_clean_working_copies_and_checkouts_say_what_blocks_them ( ) {
6594
hermetic ( ) ;
6595
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6596
let root = dir.path ( ) ;
6597
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6598
write ( root, " a.txt" , b " a\n " ) ;
6599
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
6600
write ( root, " b.txt" , b " b\n " ) ;
6601
repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
6602
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
6603
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
6604
write ( root, " c.txt" , b " c\n " ) ;
6605
repo.commit ( " c on feature" , & [ ] ) .unwrap ( ) ;
6606
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
6607
6608
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
6609
assert! ( ! root.join ( " b.txt" ) .exists ( ) ) ;
6610
let m = repo.merge ( " feature" ) .unwrap ( ) ;
6611
assert! ( m.checked_out, " a clean working copy follows the merge" ) ;
6612
for f in [ " a.txt" , " b.txt" , " c.txt" ] {
6613
assert! ( root.join ( f) .exists ( ) , " {f}" ) ;
6614
}
6615
6616
6617
fs:: remove_file( root.join ( " b.txt" ) ) .unwrap ( ) ;
6618
write ( root, " a.txt" , b " a edited\n " ) ;
6619
let err = repo
6620
.checkout ( & [ h1] , false , " checkout" )
6621
.unwrap_err ( )
6622
.to_string ( ) ;
6623
assert! (
6624
err.contains ( " modified a.txt" ) & & err.contains ( " removed b.txt" ) & & err.contains ( " --carry" ) ,
6625
" {err}"
6626
) ;
fe9586d9 Andy McCall 26 days ago ↶
6627
6628
6629
1c2bd9ab Andy McCall 1 month ago ↶
6630
let ( _ , carried) = repo
6631
.checkout_carry ( & [ h1] , " the first change" , " checkout" )
6632
.unwrap ( ) ;
6633
assert_eq! ( carried, vec! [ " a.txt" ] ) ;
6634
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " a edited\n " ) ;
6635
assert! ( ! root.join ( " b.txt" ) .exists ( ) , " h1 has no b.txt" ) ;
6636
}
e7ac6c2e Andy McCall 1 month ago ↶
6637
6638
6639
6640
6641
# [ test ]
6642
fn a_new_repository_is_healthy_before_its_first_commit ( ) {
6643
hermetic ( ) ;
6644
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6645
let root = dir.path ( ) ;
6646
let home = root.join ( " home" ) ;
6647
let mut first = Repo:: init( & root.join ( " first" ) )
6648
.unwrap ( )
6649
.with_home ( home.clone ( ) ) ;
6650
let ( key, _ ) = first
6651
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
6652
.unwrap ( ) ;
6653
let repo = Repo:: init( & root.join ( " second" ) )
6654
.unwrap ( )
6655
.with_home ( home.clone ( ) ) ;
6656
6657
let keys = repo.keys_dir.clone ( ) ;
6658
let _ = fs:: remove_file( keys.join ( format! ( " {} .identity" , key.to_hex ( ) ) ) ) ;
6659
assert! ( keys.join ( format! ( " {} .secret" , key.to_hex ( ) ) ) .is_file ( ) ) ;
6660
let r = repo.doctor ( true ) ;
6661
let id = r.checks.iter ( ) .find ( | c | c.area = = " identity" ) .unwrap ( ) ;
6662
assert_eq! ( id.health, Health:: Ok, " {}" , id.message) ;
6663
assert! ( id.message.contains ( " first signed change" ) , " {}" , id.message) ;
6664
6665
6666
let lib = root.join ( " lib" ) ;
6667
let l = Repo:: init( & lib) .unwrap ( ) .with_home ( home.clone ( ) ) ;
6668
write ( & lib, " x.txt" , b " x\n " ) ;
6669
l.commit ( " x" , & [ ] ) .unwrap ( ) ;
6670
let second = root.join ( " second" ) ;
6671
write ( & second, " a.txt" , b " a\n " ) ;
6672
repo.submodule_add ( & lib.to_string_lossy ( ) , " deps/lib" , None )
6673
.unwrap ( ) ;
6674
let subs = repo.subrepos ( ) .unwrap ( ) ;
6675
assert_eq! ( subs.len ( ) , 1 ) ;
6676
assert_eq! (
6677
( subs[ 0 ] .path.as_str ( ) , subs[ 0 ] .committed) ,
6678
( " deps/lib" , false )
6679
) ;
6680
assert_eq! ( subs[ 0 ] .actual.as_ref ( ) , Some ( & subs[ 0 ] .pin) ) ;
6681
repo.commit ( " add lib" , & [ ] ) .unwrap ( ) ;
6682
let subs = repo.subrepos ( ) .unwrap ( ) ;
6683
assert! ( subs[ 0 ] .committed, " committed now" ) ;
6684
}
8ab265dc Andy McCall 1 month ago ↶
6685
6686
6687
6688
6689
# [ cfg ( unix ) ]
6690
# [ test ]
6691
fn commits_and_diffs_leave_unchanged_files_alone ( ) {
6692
use std:: os:: unix:: fs:: PermissionsExt;
6693
hermetic ( ) ;
6694
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6695
let root = dir.path ( ) ;
6696
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6697
write ( root, " big.txt" , b " lots\n of\n lines\n " ) ;
6698
write ( root, " a.txt" , b " one\n two\n " ) ;
6699
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6700
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6701
for f in [ " big.txt" , " a.txt" ] {
6702
fs:: File:: options( )
6703
.write ( true )
6704
.open ( root.join ( f) )
6705
.unwrap ( )
6706
.set_modified ( old)
6707
.unwrap ( ) ;
6708
}
6709
assert! ( repo.snapshot_status ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6710
6711
fs:: set_permissions( root.join ( " big.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6712
6713
write ( root, " a.txt" , b " one\n TWO\n three\n " ) ;
6714
let diff = repo.diff_files ( None ) .unwrap ( ) ;
6715
assert_eq! (
6716
diff.iter ( ) .map ( | f | f.path.as_str ( ) ) .collect:: < Vec< _ > > ( ) ,
6717
[ " a.txt" ]
6718
) ;
6719
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
6720
assert! ( ! hunks.is_empty ( ) & & hunks.iter ( ) .all ( | h | h.path = = " a.txt" ) ) ;
6721
let h = repo.commit ( " edit a" , & [ ] ) .unwrap ( ) ;
6722
6723
6724
let c = repo.store ( ) .get_change ( & h) .unwrap ( ) ;
6725
let full = repo.load_state ( & c.deps) .unwrap ( ) ;
6726
let want = render_change ( & c, & full, & full.materialize ( ) ) ;
6727
assert_eq! ( repo.describe_change ( & h) .unwrap ( ) , want) ;
6728
let ( base, snap) = repo.change_base ( & c) .unwrap ( ) ;
6729
assert! ( ! base.is_full ( ) & & snap.unrendered.contains ( " big.txt" ) ) ;
6730
6731
6732
write ( root, " a.txt" , b " scratch\n " ) ;
6733
assert_eq! ( repo.discard ( & [ " a.txt" .into ( ) ] ) .unwrap ( ) , 1 ) ;
6734
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n TWO\n three\n " ) ;
6735
fs:: set_permissions( root.join ( " big.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6736
}
f7510b63 Andy McCall 1 month ago ↶
6737
6738
6739
6740
6741
# [ test ]
6742
fn replace_conflicts_are_settled_when_asked ( ) {
6743
hermetic ( ) ;
6744
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6745
let root = dir.path ( ) ;
6746
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6747
let blob = | n : u8 | {
6748
let mut v = vec! [ 0 u8 ; 64 ] ;
6749
v[ 1 ] = n;
6750
v
6751
} ;
6752
write ( root, " img.bin" , & blob ( 1 ) ) ;
6753
repo.commit ( " add img" , & [ ] ) .unwrap ( ) ;
6754
repo.bookmark_create ( " other" , None ) .unwrap ( ) ;
6755
write ( root, " img.bin" , & blob ( 2 ) ) ;
6756
repo.commit ( " img two" , & [ ] ) .unwrap ( ) ;
6757
repo.bookmark_switch ( " other" , false ) .unwrap ( ) ;
6758
write ( root, " img.bin" , & blob ( 3 ) ) ;
6759
repo.commit ( " img three" , & [ ] ) .unwrap ( ) ;
6760
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
6761
repo.merge ( " other" ) .unwrap ( ) ;
6762
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6763
let merged = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
6764
assert! (
6765
matches! ( & merged.files[ " img.bin" ] .content, FileContent:: Opaque { blobs } if blobs.len ( ) = = 2 ) ,
6766
" a replace conflict"
6767
) ;
6768
6769
assert! ( repo
6770
.snapshot ( " status" )
6771
.unwrap ( )
6772
.statuses
6773
.iter ( )
6774
.all ( | s | s.path ! = " img.bin" ) ) ;
6775
6776
6777
let author = repo.author ( ) ;
6778
let edits = [ FileEdit {
6779
path: " img.bin" .into ( ) ,
6780
content: Some ( blob ( 3 ) ) ,
6781
} ] ;
6782
let c = repo
6783
.build_change ( & heads, & edits, " keep three" , author)
6784
.unwrap ( ) ;
6785
assert_eq! ( c.kind, ChangeKind:: Resolution) ;
6786
let mut st = repo.load_state ( & heads) .unwrap ( ) ;
6787
for d in & c.deps {
6788
assert! ( st.contains ( d) ) ;
6789
}
6790
assert! (
6791
heads.iter ( ) .all ( | h | c.deps.contains ( h) ) ,
6792
" depends on both contributors"
6793
) ;
6794
st.add ( c) .unwrap ( ) ;
6795
let after = st.materialize ( ) ;
6796
assert! ( ! after.is_conflicted ( ) ) ;
6797
assert_eq! (
6798
after.files[ " img.bin" ] .content,
6799
FileContent:: Opaque {
6800
blobs: vec! [ Repo:: blob_ref( & blob ( 3 ) ) ]
6801
}
6802
) ;
6803
6804
6805
let edits = [ FileEdit {
6806
path: " img.bin" .into ( ) ,
6807
content: Some ( blob ( 9 ) ) ,
6808
} ] ;
6809
let c = repo.build_change ( & heads, & edits, " new" , author) .unwrap ( ) ;
6810
let mut st = repo.load_state ( & heads) .unwrap ( ) ;
6811
st.add ( c) .unwrap ( ) ;
6812
assert_eq! (
6813
st.materialize ( ) .files[ " img.bin" ] .content,
6814
FileContent:: Opaque {
6815
blobs: vec! [ Repo:: blob_ref( & blob ( 9 ) ) ]
6816
}
6817
) ;
6818
}
83d61e80 Andy McCall 1 month ago ↶
6819
6820
6821
6822
6823
# [ test ]
6824
fn line_ranges_have_exact_histories ( ) {
6825
hermetic ( ) ;
6826
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6827
let root = dir.path ( ) ;
6828
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6829
write ( root, " f.rs" , b " use x;\n fn a() {\n one();\n two();\n }\n " ) ;
6830
let base = repo.commit ( " add f" , & [ ] ) .unwrap ( ) ;
6831
write ( root, " f.rs" , b " use y;\n fn a() {\n one();\n two();\n }\n " ) ;
6832
let outside = repo.commit ( " change the import" , & [ ] ) .unwrap ( ) ;
6833
write ( root, " f.rs" , b " use y;\n fn a() {\n ONE();\n two();\n }\n " ) ;
6834
let inside = repo.commit ( " edit a line of a" , & [ ] ) .unwrap ( ) ;
6835
write ( root, " f.rs" , b " use y;\n fn a() {\n ONE();\n }\n " ) ;
6836
let deleted = repo.commit ( " delete a line of a" , & [ ] ) .unwrap ( ) ;
6837
write ( root, " f.rs" , b " use y;\n fn a() {\n ONE();\n }\n fn b() {}\n " ) ;
6838
let after = repo.commit ( " add b after a" , & [ ] ) .unwrap ( ) ;
6839
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6840
6841
6842
let hist: Vec< ChangeHash> = repo
6843
.line_history ( & heads, " f.rs" , 2 , 4 )
6844
.unwrap ( )
6845
.iter ( )
6846
.map ( | e | e.hash )
6847
.collect ( ) ;
6848
for ( h, what) in [
6849
( base, " wrote it" ) ,
6850
( inside, " edited a line" ) ,
6851
( deleted, " deleted a line from inside" ) ,
6852
] {
6853
assert! ( hist.contains ( & h) , " {what}" ) ;
6854
}
6855
for ( h, what) in [ ( outside, " an edit above" ) , ( after, " an addition below" ) ] {
6856
assert! ( ! hist.contains ( & h) , " {what}" ) ;
6857
}
6858
assert_eq! ( hist[ 0 ] , deleted, " newest first" ) ;
6859
6860
let one: Vec< ChangeHash> = repo
6861
.line_history ( & heads, " f.rs" , 1 , 1 )
6862
.unwrap ( )
6863
.iter ( )
6864
.map ( | e | e.hash )
6865
.collect ( ) ;
6866
assert! ( one.contains ( & outside) & & ! one.contains ( & inside) ) ;
6867
assert! ( repo.line_history ( & heads, " f.rs" , 3 , 9 ) .is_err ( ) ) ;
6868
assert! ( repo.line_history ( & heads, " f.rs" , 0 , 1 ) .is_err ( ) ) ;
6869
assert! ( repo.line_history ( & heads, " nope.rs" , 1 , 1 ) .is_err ( ) ) ;
6870
}
fc1a466a Andy McCall 1 month ago ↶
6871
6872
# [ test ]
6873
fn receipts_are_kept_with_their_state_and_verified ( ) {
6874
use crate :: receipt:: { Receipt, SignedReceipt} ;
6875
hermetic ( ) ;
6876
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6877
let root = dir.path ( ) ;
6878
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6879
write ( root, " a.txt" , b " 1\n " ) ;
6880
repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
6881
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6882
let state = repo
6883
.store ( )
6884
.put_state ( & StateObject:: new( heads.clone ( ) ) )
6885
.unwrap ( ) ;
6886
let out = repo.store_blob ( b " built\n " ) .unwrap ( ) ;
6887
let receipt = Receipt {
6888
job_name: " build" .into ( ) ,
6889
pipeline_hash: ObjectRef( [ 1 ; 32 ] ) ,
6890
scope_tree_hash: ObjectRef( [ 2 ; 32 ] ) ,
6891
inputs: vec! [ ] ,
6892
environment: ObjectRef( [ 3 ; 32 ] ) ,
6893
outputs: vec! [ out] ,
6894
exit_code: 0 ,
6895
duration_ms: 42 ,
6896
} ;
6897
let sk = SecretKey:: generate( ) ;
6898
let runner = sk.identity ( " earth-1" , " runner@earth" , 1 ) ;
6899
let sr = SignedReceipt:: sign(
6900
receipt.clone ( ) ,
6901
repo.id ( ) .unwrap ( ) ,
6902
state,
6903
" demo#1" ,
6904
& runner,
6905
& sk,
6906
) ;
6907
6908
let r = repo.add_receipt ( & sr) .unwrap ( ) ;
6909
let found = repo.receipts ( & heads) .unwrap ( ) ;
6910
assert_eq! ( found.len ( ) , 1 ) ;
6911
assert_eq! ( found[ 0 ] .attest, r) ;
6912
assert_eq! ( found[ 0 ] .signed, sr) ;
6913
assert! ( found[ 0 ] .problem.is_none ( ) & & ! found[ 0 ] .revoked) ;
6914
assert! ( repo.receipts ( & [ ObjectRef( [ 5 ; 32 ] ) ] ) .unwrap ( ) .is_empty ( ) ) ;
6915
6916
6917
let elsewhere = SignedReceipt:: sign( receipt.clone ( ) , [ 0 ; 16 ] , state, " x" , & runner, & sk) ;
6918
assert! ( repo
6919
.add_receipt ( & elsewhere)
6920
.unwrap_err ( )
6921
.to_string ( )
6922
.contains ( " another repository" ) ) ;
6923
let mut forged = sr.clone ( ) ;
6924
forged.receipt.exit_code = 1 ;
6925
assert! ( repo
6926
.add_receipt ( & forged)
6927
.unwrap_err ( )
6928
.to_string ( )
6929
.contains ( " does not verify" ) ) ;
6930
c63fed2e Andy McCall 20 days ago ↶
6931
6932
6933
let hub_sk = SecretKey:: generate( ) ;
6934
let hub = hub_sk.identity ( " oxygenhub.io" , " [email protected] " , 1 ) ;
6935
let job_sk = SecretKey:: generate( ) ;
6936
let job = job_sk.identity ( " job 2 test" , " [email protected] " , 1 ) ;
6937
let mut test_receipt = receipt.clone ( ) ;
6938
test_receipt.job_name = " test" .into ( ) ;
6939
let plain = SignedReceipt:: sign(
6940
test_receipt,
6941
repo.id ( ) .unwrap ( ) ,
6942
state,
6943
" demo#2" ,
6944
& job,
6945
& job_sk,
6946
) ;
6947
let now = plain.attest.hlc.wall_ms;
6948
let cert = crate :: cert:: SignedCertificate:: sign(
6949
crate :: cert:: Certificate {
6950
key: job.clone ( ) ,
6951
issuer: hub.hash ( ) ,
6952
subject: " repo:andy/demo:bookmark:main" .into ( ) ,
6953
claims: vec! [ ] ,
6954
scope: crate :: identity:: Scope:: SEAL ,
6955
not_before: now - 1_000 ,
6956
expires: now + 600_000 ,
a2387d54 Andy McCall 20 days ago ↶
6957
grant: None ,
c63fed2e Andy McCall 20 days ago ↶
6958
} ,
6959
& hub,
6960
& hub_sk,
6961
) ;
6962
let certified = plain.with_certificate ( cert, hub.clone ( ) ) ;
6963
repo.add_receipt ( & certified) .unwrap ( ) ;
6964
let found = repo.receipts ( & heads) .unwrap ( ) ;
6965
let got = found
6966
.iter ( )
6967
.find ( | i | i.signed.receipt.job_name = = " test" )
6968
.unwrap ( ) ;
6969
assert_eq! ( got.signed, certified) ;
6970
assert! ( got.problem.is_none ( ) , " {:?}" , got.problem) ;
6971
6972
6973
fc1a466a Andy McCall 1 month ago ↶
6974
repo.gc ( true ) .unwrap ( ) ;
c63fed2e Andy McCall 20 days ago ↶
6975
let found = repo.receipts ( & heads) .unwrap ( ) ;
6976
assert_eq! ( found.len ( ) , 2 ) ;
6977
let got = found
6978
.iter ( )
6979
.find ( | i | i.signed.receipt.job_name = = " test" )
6980
.unwrap ( ) ;
6981
assert_eq! ( got.signed, certified, " the certificate survives gc" ) ;
6982
assert! ( got.problem.is_none ( ) ) ;
fc1a466a Andy McCall 1 month ago ↶
6983
assert_eq! ( repo.read_blob ( & out) .unwrap ( ) , b " built\n " ) ;
6984
assert! ( repo.fsck ( ) .unwrap ( ) .problems.is_empty ( ) ) ;
dfb23a3a Andy McCall 1 month ago ↶
6985
}
6986
6987
# [ test ]
3a4b3e01 Andy McCall 21 days ago ↶
6988
fn approvals_are_kept_with_their_state_and_survive_gc ( ) {
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
repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
6995
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6996
let state = repo.store ( ) .put_state ( & StateObject:: new( heads) ) .unwrap ( ) ;
6997
let plan = repo.store_blob ( b " the reviewed plan\n " ) .unwrap ( ) ;
6998
let sk = SecretKey:: generate( ) ;
6999
let andy = sk.identity ( " Andy" , " [email protected] " , 1 ) ;
7000
let statement = crate :: approval:: Statement {
7001
repo: repo.id ( ) .unwrap ( ) ,
7002
run: " https://hub.example/andy/infra/flow/8" .into ( ) ,
7003
job: " apply" .into ( ) ,
7004
job_hash: ObjectRef( [ 1 ; 32 ] ) ,
7005
state,
7006
inputs: vec! [ plan] ,
7007
environment: " prod" .into ( ) ,
7008
approve: true ,
7009
} ;
7010
let sa = crate :: approval:: SignedApproval:: sign( & statement, & andy, & sk) ;
7011
let r = repo.add_approval ( & sa) .unwrap ( ) ;
7012
assert_eq! ( repo.store ( ) .approvals_for ( & state) .unwrap ( ) , vec! [ r] ) ;
7013
7014
let elsewhere = crate :: approval:: SignedApproval:: sign(
7015
& crate :: approval:: Statement {
7016
repo: [ 0 ; 16 ] ,
7017
.. statement.clone ( )
7018
} ,
7019
& andy,
7020
& sk,
7021
) ;
7022
assert! ( repo
7023
.add_approval ( & elsewhere)
7024
.unwrap_err ( )
7025
.to_string ( )
7026
.contains ( " another repository" ) ) ;
7027
let mut forged = sa.clone ( ) ;
7028
if let AttestBody:: Approval { environment, .. } = & mut forged.attest.body {
7029
* environment = " dev" .into ( ) ;
7030
}
7031
assert! ( repo
7032
.add_approval ( & forged)
7033
.unwrap_err ( )
7034
.to_string ( )
7035
.contains ( " does not verify" ) ) ;
7036
7037
repo.gc ( true ) .unwrap ( ) ;
7038
assert_eq! ( repo.store ( ) .approvals_for ( & state) .unwrap ( ) , vec! [ r] ) ;
7039
assert_eq! ( repo.store ( ) .get_attest ( & r) .unwrap ( ) , sa.attest) ;
7040
assert_eq! ( repo.store ( ) .signatures ( & r) .unwrap ( ) , sa.sigs) ;
7041
assert! ( repo.fsck ( ) .unwrap ( ) .problems.is_empty ( ) ) ;
7042
}
7043
7044
# [ test ]
dfb23a3a Andy McCall 1 month ago ↶
7045
fn status_leaves_an_unchanged_manifest_alone ( ) {
7046
hermetic ( ) ;
7047
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7048
let root = dir.path ( ) ;
7049
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
7050
write ( root, " a.txt" , b " 1\n " ) ;
7051
write ( root, " b.txt" , b " 2\n " ) ;
7052
repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
7053
7054
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 60 ) ;
7055
for f in [ " a.txt" , " b.txt" ] {
7056
fs:: File:: options( )
7057
.write ( true )
7058
.open ( root.join ( f) )
7059
.unwrap ( )
7060
.set_modified ( old)
7061
.unwrap ( ) ;
7062
}
7063
repo.snapshot_status ( " status" ) .unwrap ( ) ;
7064
let path = repo.manifest_path ( ) ;
7065
let first = fs:: read( & path) .unwrap ( ) ;
7066
repo.snapshot_status ( " status" ) .unwrap ( ) ;
7067
assert_eq! (
7068
fs:: read( & path) .unwrap ( ) ,
7069
first,
7070
" an unchanged tree does not rewrite the manifest"
7071
) ;
7072
7073
write ( root, " a.txt" , b " 1\n 2\n " ) ;
7074
repo.snapshot_status ( " status" ) .unwrap ( ) ;
7075
assert_ne! ( fs:: read( & path) .unwrap ( ) , first) ;
fc1a466a Andy McCall 1 month ago ↶
7076
}
6f8aeb99 Andy McCall 1 month ago ↶
7077
7078
# [ test ]
7079
fn large_text_files_travel_and_survive_gc_as_text_blobs ( ) {
7080
hermetic ( ) ;
7081
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7082
let root = dir.path ( ) ;
7083
let origin_path = root.join ( " origin" ) ;
7084
Repo:: init( & origin_path) .unwrap ( ) ;
7085
let origin_url = origin_path.to_string_lossy ( ) .to_string ( ) ;
7086
let a = Repo:: clone( & origin_url, & root.join ( " a" ) ) .unwrap ( ) ;
7087
let big: String = ( 0 .. 5000 ) .map ( | i | format! ( " line {i} \n " ) ) .collect ( ) ;
7088
write ( & root.join ( " a" ) , " big.txt" , big.as_bytes ( ) ) ;
7089
write ( & root.join ( " a" ) , " small.txt" , b " small\n " ) ;
7090
let h1 = a.commit ( " large" , & [ ] ) .unwrap ( ) ;
7091
let ( _ , body) = a.store ( ) .get ( & h1) .unwrap ( ) ;
7092
assert! ( body.len ( ) < 2048 , " the change is {} bytes" , body.len ( ) ) ;
7093
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7094
7095
7096
let b = Repo:: clone( & origin_url, & root.join ( " b" ) ) .unwrap ( ) ;
7097
assert_eq! ( fs:: read( root.join ( " b/big.txt" ) ) .unwrap ( ) , big.as_bytes ( ) ) ;
7098
7099
let edited = big.replace ( " line 2500\n " , " line 2500, edited\n " ) ;
7100
write ( & root.join ( " b" ) , " big.txt" , edited.as_bytes ( ) ) ;
7101
let h2 = b.commit ( " edit" , & [ ] ) .unwrap ( ) ;
7102
assert! ( b.store ( ) .get_change ( & h2) .unwrap ( ) .edits.len ( ) < = 2 ) ;
7103
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7104
assert! ( a.pull ( " origin" ) .unwrap ( ) .checked_out) ;
7105
assert_eq! ( fs:: read( root.join ( " a/big.txt" ) ) .unwrap ( ) , edited.as_bytes ( ) ) ;
7106
assert_eq! (
7107
a.fsck ( ) .unwrap ( ) .errors ( ) ,
7108
0 ,
7109
" {:?}" ,
7110
a.fsck ( ) .unwrap ( ) .problems
7111
) ;
7112
7113
7114
let mut a = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) ;
7115
a.config.set ( " core.op-retention" , " 0" ) .unwrap ( ) ;
7116
a.save_config ( ) .unwrap ( ) ;
7117
let a = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) ;
7118
a.gc ( true ) .unwrap ( ) ;
7119
let a = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) ;
7120
assert_eq! ( a.store ( ) .get_change ( & h1) .unwrap ( ) .message, " large" ) ;
7121
assert_eq! (
7122
a.fsck ( ) .unwrap ( ) .errors ( ) ,
7123
0 ,
7124
" {:?}" ,
7125
a.fsck ( ) .unwrap ( ) .problems
7126
) ;
7127
7128
let c = Repo:: clone( & root.join ( " a" ) .to_string_lossy ( ) , & root.join ( " c" ) ) .unwrap ( ) ;
7129
assert_eq! ( fs:: read( root.join ( " c/big.txt" ) ) .unwrap ( ) , edited.as_bytes ( ) ) ;
7130
drop ( c) ;
6caacfff Andy McCall 1 month ago ↶
7131
}
7132
7133
# [ test ]
7134
fn operations_that_both_replace_a_working_copy_are_not_merged ( ) {
7135
hermetic ( ) ;
7136
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7137
let root = dir.path ( ) ;
7138
let repo = Repo:: init( root) .unwrap ( ) ;
7139
write ( root, " a.txt" , b " a\n " ) ;
7140
let a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
7141
write ( root, " b.txt" , b " b\n " ) ;
7142
repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
7143
let ( x, op) = repo.head_op ( ) .unwrap ( ) ;
7144
let ws = * op.view.workspaces.keys ( ) .next ( ) .unwrap ( ) ;
7145
let mut one = op.view.clone ( ) ;
7146
one.workspaces.insert ( ws, a) ;
7147
let mut two = op.view.clone ( ) ;
7148
two.workspaces.insert ( ws, ObjectRef( [ 7 ; 32 ] ) ) ;
7149
let first = repo.record_op ( & [ x] , " one" , one) .unwrap ( ) ;
7150
let e = repo.record_op ( & [ x] , " two" , two) .unwrap_err ( ) ;
7151
assert! (
7152
e.to_string ( )
7153
.contains ( " changed this working copy at the same time" ) ,
7154
" {e}"
7155
) ;
7156
assert_eq! (
7157
repo.head_op ( ) .unwrap ( ) .0 ,
7158
first,
7159
" the second is not recorded"
7160
) ;
7161
}
7162
7163
7164
7165
7166
# [ test ]
7167
fn a_snapshot_during_a_switch_never_records_the_switch_as_edits ( ) {
7168
hermetic ( ) ;
7169
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7170
let root = dir.path ( ) .to_path_buf ( ) ;
7171
let mut repo = Repo:: init( & root) .unwrap ( ) ;
7172
write ( & root, " keep.txt" , b " keep\n " ) ;
7173
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7174
repo.bookmark_create ( " small" , None ) .unwrap ( ) ;
7175
for i in 0 .. 300 {
7176
write (
7177
& root,
7178
& format! ( " many/f{i} .txt" ) ,
7179
format! ( " file {i} \n " ) .as_bytes ( ) ,
7180
) ;
7181
}
7182
repo.commit ( " many" , & [ ] ) .unwrap ( ) ;
7183
let stop = std:: sync:: Arc:: new( std:: sync:: atomic:: AtomicBool:: new( false ) ) ;
7184
let watcher = {
7185
let ( root, stop) = ( root.clone ( ) , stop.clone ( ) ) ;
7186
std:: thread:: spawn( move | | {
7187
let r = Repo:: open( & root) .unwrap ( ) ;
7188
let mut ran = 0 ;
7189
while ! stop.load ( std:: sync:: atomic:: Ordering:: Relaxed) {
7190
if r.snapshot_status ( " oxy status" ) .is_ok ( ) {
7191
ran + = 1 ;
7192
}
7193
}
7194
ran
7195
} )
7196
} ;
7197
for i in 0 .. 20 {
7198
let to = if i % 2 = = 0 { " small" } else { " main" } ;
7199
repo.bookmark_switch ( to, false ) .unwrap ( ) ;
7200
}
7201
stop.store ( true , std:: sync:: atomic:: Ordering:: Relaxed) ;
7202
assert! ( watcher.join ( ) .unwrap ( ) > 0 , " the watcher snapshotted" ) ;
7203
7204
let repo = Repo:: open( & root) .unwrap ( ) ;
7205
let s = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7206
assert! ( s.statuses.is_empty ( ) , " {:?}" , statuses ( & s) ) ;
7207
assert! ( s.wc.edits.is_empty ( ) & & s.wc.tree_ops.is_empty ( ) ) ;
7208
assert_eq! ( s.wc.deps, repo.bookmark_heads ( ) .unwrap ( ) ) ;
7209
assert! ( root.join ( " many/f299.txt" ) .exists ( ) ) ;
1a3b6203 Andy McCall 1 month ago ↶
7210
}
7211
7212
7213
7214
7215
# [ test ]
7216
fn a_parent_switch_with_carry_leaves_a_subrepo_ahead_of_its_pin_alone ( ) {
7217
hermetic ( ) ;
7218
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7219
let root = dir.path ( ) ;
7220
Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
7221
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
7222
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) ) .unwrap ( ) ;
7223
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
7224
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
7225
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7226
7227
let app_dir = root.join ( " app" ) ;
7228
let mut app = Repo:: init( & app_dir) .unwrap ( ) ;
7229
write ( & app_dir, " main.txt" , b " app\n " ) ;
7230
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
7231
app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
7232
app.bookmark_create ( " feature" , None ) .unwrap ( ) ;
7233
7234
7235
let nested_dir = app_dir.join ( " libs/lib" ) ;
7236
let nested = Repo:: open( & nested_dir) .unwrap ( ) ;
7237
write ( & nested_dir, " new.txt" , b " new\n " ) ;
7238
let l2 = nested.commit ( " lib: new file" , & [ ] ) .unwrap ( ) ;
7239
drop ( nested) ;
7240
7241
let ( _ , carried) = app.bookmark_switch_carry ( " feature" ) .unwrap ( ) ;
7242
assert! ( carried.contains ( & " libs/lib" .to_string ( ) ) , " {carried:?}" ) ;
7243
let nested = Repo:: open( & nested_dir) .unwrap ( ) ;
7244
assert! (
7245
nested.bookmark_heads ( ) .unwrap ( ) .contains ( & l2) ,
7246
" its bookmark kept its work"
7247
) ;
7248
assert! ( nested_dir.join ( " new.txt" ) .exists ( ) , " and its files" ) ;
7249
let s = app.snapshot ( " status" ) .unwrap ( ) ;
7250
assert_eq! (
7251
statuses ( & s) ,
7252
vec! [ ( " libs/lib" .to_string ( ) , ChangeStatus:: Modified) ]
7253
) ;
6f8aeb99 Andy McCall 1 month ago ↶
7254
}
7d9dab13 Andy McCall 1 month ago ↶
7255
7256
7257
7258
7259
# [ test ]
7260
fn pull_takes_a_remote_move_to_changes_already_here ( ) {
7261
hermetic ( ) ;
7262
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7263
let root = dir.path ( ) ;
7264
let origin_path = root.join ( " origin" ) ;
7265
let origin = Repo:: init( & origin_path) .unwrap ( ) ;
7266
let url = origin_path.to_string_lossy ( ) .to_string ( ) ;
7267
let mut b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) ;
7268
write ( & root.join ( " b" ) , " base.txt" , b " base\n " ) ;
7269
b.commit ( " base" , & [ ] ) .unwrap ( ) ;
7270
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
7271
origin.refresh ( ) .unwrap ( ) ;
7d9dab13 Andy McCall 1 month ago ↶
7272
7273
7274
b.bookmark_create ( " feat" , None ) .unwrap ( ) ;
7275
b.bookmark_switch ( " feat" , false ) .unwrap ( ) ;
7276
write ( & root.join ( " b" ) , " x.txt" , b " x\n " ) ;
7277
let x = b.commit ( " x" , & [ ] ) .unwrap ( ) ;
7278
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
7279
origin.refresh ( ) .unwrap ( ) ;
7d9dab13 Andy McCall 1 month ago ↶
7280
b.bookmark_switch ( " main" , false ) .unwrap ( ) ;
7281
7282
7283
let feat = origin
7284
.store ( )
7285
.get_state ( & origin.view ( ) .unwrap ( ) .bookmarks[ " feat" ] )
7286
.unwrap ( )
7287
.heads;
7288
origin.bookmark_set ( " main" , & feat) .unwrap ( ) ;
7289
7290
let r = b.pull ( " origin" ) .unwrap ( ) ;
7291
assert! ( r.changed, " {r:?}" ) ;
7292
assert! ( r.heads.contains ( & x) | | b.load_state ( & r.heads) .unwrap ( ) .contains ( & x) ) ;
7293
let main = b
7294
.bookmarks ( )
7295
.unwrap ( )
7296
.into_iter ( )
7297
.find ( | m | m.name = = " main" )
7298
.unwrap ( ) ;
7299
assert_eq! ( main.behind, Some ( 0 ) , " {main:?}" ) ;
7300
}
86e1824f Andy McCall 1 month ago ↶
7301
14edbaaf Andy McCall 1 month ago ↶
7302
7303
7304
7305
7306
# [ test ]
7307
fn remote_bookmarks_are_named_switched_to_pulled_and_cloned ( ) {
7308
hermetic ( ) ;
7309
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7310
let root = dir.path ( ) ;
7311
let origin_path = root.join ( " origin" ) ;
7312
let mut origin = Repo:: init( & origin_path) .unwrap ( ) ;
7313
let url = origin_path.to_string_lossy ( ) .to_string ( ) ;
7314
let mut a = Repo:: clone( & url, & root.join ( " a" ) ) .unwrap ( ) ;
7315
write ( & root.join ( " a" ) , " base.txt" , b " base\n " ) ;
7316
a.commit ( " base" , & [ ] ) .unwrap ( ) ;
7317
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7318
a.bookmark_create ( " develop" , None ) .unwrap ( ) ;
7319
a.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
7320
write ( & root.join ( " a" ) , " base.txt" , b " base\n d1\n " ) ;
7321
let d1 = a.commit ( " d1" , & [ ] ) .unwrap ( ) ;
7322
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7323
7324
7325
let c_root = root.join ( " c" ) ;
7326
let mut c = Repo:: clone( & url, & c_root ) .unwrap ( ) ;
7327
assert_eq! ( c.config.bookmark, " main" ) ;
7328
assert! ( ! c.view ( ) .unwrap ( ) .bookmarks.contains_key ( " develop" ) ) ;
7329
assert_eq! ( c.resolve_heads ( " origin/develop" ) .unwrap ( ) , vec! [ d1] ) ;
7330
assert_eq! ( c.resolve_change ( " origin/develop" ) .unwrap ( ) , d1) ;
7331
assert_eq! ( c.remote_only_bookmarks ( ) .unwrap ( ) .len ( ) , 1 ) ;
7332
7333
7334
c.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
7335
assert_eq! ( c.config.bookmark, " develop" ) ;
7336
assert_eq! ( c.bookmark_heads ( ) .unwrap ( ) , vec! [ d1] ) ;
7337
assert_eq! ( fs:: read( c_root .join ( " base.txt" ) ) .unwrap ( ) , b " base\n d1\n " ) ;
7338
assert! ( c.remote_only_bookmarks ( ) .unwrap ( ) .is_empty ( ) ) ;
7339
assert! ( c.bookmark_switch ( " nowhere" , false ) .is_err ( ) ) ;
7340
7341
7342
write ( & root.join ( " a" ) , " base.txt" , b " base\n d1\n d2\n " ) ;
7343
let d2 = a.commit ( " d2" , & [ ] ) .unwrap ( ) ;
7344
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7345
a.bookmark_switch ( " main" , false ) .unwrap ( ) ;
7346
write ( & root.join ( " a" ) , " m.txt" , b " m\n " ) ;
7347
let m = a.commit ( " m" , & [ ] ) .unwrap ( ) ;
7348
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7349
let r = c.pull ( " origin" ) .unwrap ( ) ;
7350
assert! ( r.changed & & r.checked_out, " {r:?}" ) ;
7351
assert_eq! ( r.heads, vec! [ d2] ) ;
7352
assert_eq! ( r.others, vec! [ " main" .to_string ( ) ] ) ;
7353
assert! ( c.resolve_heads ( " main" ) .unwrap ( ) .contains ( & m) ) ;
7354
assert_eq! (
7355
fs:: read( c_root .join ( " base.txt" ) ) .unwrap ( ) ,
7356
b " base\n d1\n d2\n "
7357
) ;
7358
let again = c.pull ( " origin" ) .unwrap ( ) ;
7359
assert! ( ! again.changed & & again.others.is_empty ( ) , " {again:?}" ) ;
7360
7361
7362
origin.config.set ( " workspace.bookmark" , " develop" ) .unwrap ( ) ;
7363
origin.save_config ( ) .unwrap ( ) ;
7364
let e = Repo:: clone( & url, & root.join ( " e" ) ) .unwrap ( ) ;
7365
assert_eq! ( e.config.bookmark, " develop" ) ;
7366
assert_eq! ( e.bookmark_heads ( ) .unwrap ( ) , vec! [ d2] ) ;
7367
let reopened = Repo:: open( & root.join ( " e" ) ) .unwrap ( ) ;
7368
assert_eq! ( reopened.config.bookmark, " develop" ) ;
7369
7370
let mut e = reopened;
7371
e.bookmark_switch ( " origin/main" , false ) .unwrap ( ) ;
7372
assert_eq! ( e.config.bookmark, " main" ) ;
7373
assert! ( e.bookmark_heads ( ) .unwrap ( ) .contains ( & m) ) ;
7374
}
7375
98a1620a Andy McCall 1 month ago ↶
7376
7377
7378
7379
7380
# [ test ]
7381
fn operations_can_be_read_restored_and_reverted ( ) {
7382
hermetic ( ) ;
7383
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7384
let root = dir.path ( ) ;
7385
let repo = Repo:: init( root) .unwrap ( ) ;
7386
write ( root, " a.txt" , b " a\n " ) ;
7387
let first = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
7388
write ( root, " a.txt" , b " a\n b\n " ) ;
7389
let second = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
7390
let at_second = repo.head_op ( ) .unwrap ( ) .0. to_hex ( ) ;
7391
repo.bookmark_create ( " keep" , None ) .unwrap ( ) ;
7392
let created = repo.head_op ( ) .unwrap ( ) .0. to_hex ( ) ;
7393
write ( root, " n.txt" , b " n\n " ) ;
7394
let third = repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
7395
7396
7397
let ops = repo.op_log ( ) .unwrap ( ) ;
7398
let changes = repo.view_changes ( & ops[ 1 ] .1. view, & ops[ 0 ] .1. view) .unwrap ( ) ;
7399
assert! (
7400
changes
7401
.iter ( )
7402
.any ( | c | matches! ( c, ViewChange:: BookmarkMoved { name, .. } if name = = " main" ) ) ,
7403
" {changes:?}"
7404
) ;
7405
assert! ( changes
7406
.iter ( )
7407
.any ( | c | matches! ( c, ViewChange:: WorkingCopy { .. } ) ) ) ;
7408
7409
7410
let old = Repo:: open( root) .unwrap ( ) .at_op ( & at_second[ .. 10 ] ) .unwrap ( ) ;
7411
assert_eq! ( old.bookmark_heads ( ) .unwrap ( ) , vec! [ second] ) ;
7412
assert! ( ! old.view ( ) .unwrap ( ) .bookmarks.contains_key ( " keep" ) ) ;
7413
write ( root, " z.txt" , b " z\n " ) ;
7414
assert! ( old.commit ( " refused" , & [ ] ) .is_err ( ) , " --at-op is read-only" ) ;
7415
fs:: remove_file( root.join ( " z.txt" ) ) .unwrap ( ) ;
7416
assert! ( repo.resolve_op ( " zz" ) .is_err ( ) ) ;
7417
assert_eq! ( repo.resolve_op ( " @" ) .unwrap ( ) .0 , repo.head_op ( ) .unwrap ( ) .0 ) ;
7418
7419
7420
repo.op_restore ( & at_second, false ) .unwrap ( ) ;
7421
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ second] ) ;
7422
assert! ( ! root.join ( " n.txt" ) .exists ( ) ) ;
7423
repo.undo ( ) .unwrap ( ) ;
7424
let mut heads = repo.bookmark_heads ( ) .unwrap ( ) ;
7425
heads.sort ( ) ;
7426
let mut want = vec! [ second, third] ;
7427
want.sort ( ) ;
7428
assert_eq! ( heads, want) ;
7429
assert! (
7430
root.join ( " n.txt" ) .exists ( ) ,
7431
" the files came back with the undo"
7432
) ;
7433
7434
7435
let r = repo.op_revert ( & created) .unwrap ( ) ;
7436
assert_eq! ( r.reverted, vec! [ " keep" .to_string ( ) ] ) ;
7437
assert! ( ! repo.view ( ) .unwrap ( ) .bookmarks.contains_key ( " keep" ) ) ;
7438
assert! ( repo
7439
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
7440
.unwrap ( )
7441
.contains ( & third) ) ;
7442
assert! ( repo.op_revert ( & created) .is_err ( ) , " nothing left to revert" ) ;
7443
let _ = first;
7444
}
7445
36041796 Andy McCall 1 month ago ↶
7446
7447
7448
7449
# [ test ]
7450
fn status_with_uncommitted_work_comes_from_the_cache_until_a_file_changes ( ) {
7451
hermetic ( ) ;
7452
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7453
let root = dir.path ( ) ;
7454
let repo = Repo:: init( root) .unwrap ( ) ;
7455
write ( root, " a.txt" , b " a\n " ) ;
7456
write ( root, " b.txt" , b " b\n " ) ;
7457
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7458
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , Some ( vec! [ ] ) , " clean" ) ;
7459
7460
write ( root, " a.txt" , b " a\n more\n " ) ;
7461
assert_eq! (
7462
repo.status_by_manifest ( ) .unwrap ( ) ,
7463
None ,
7464
" no snapshot of the edit yet"
7465
) ;
7466
let snap = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7467
assert_eq! ( snap.statuses.len ( ) , 1 ) ;
7468
let cached = repo
7469
.status_by_manifest ( )
7470
.unwrap ( )
7471
.expect ( " answered from the cache" ) ;
7472
assert_eq! ( cached.len ( ) , 1 ) ;
7473
assert_eq! (
7474
( cached[ 0 ] .path.as_str ( ) , cached[ 0 ] .status) ,
7475
( " a.txt" , ChangeStatus:: Modified)
7476
) ;
7477
7478
7479
write ( root, " a.txt" , b " a\n more\n and more\n " ) ;
7480
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None , " edited again" ) ;
7481
repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7482
assert! ( repo.status_by_manifest ( ) .unwrap ( ) .is_some ( ) ) ;
7483
write ( root, " new.txt" , b " n\n " ) ;
7484
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None , " a file added" ) ;
7485
fs:: remove_file( root.join ( " new.txt" ) ) .unwrap ( ) ;
7486
assert! (
7487
repo.status_by_manifest ( ) .unwrap ( ) .is_some ( ) ,
7488
" back as it was"
7489
) ;
7490
fs:: remove_file( root.join ( " b.txt" ) ) .unwrap ( ) ;
7491
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None , " a file removed" ) ;
7492
write ( root, " b.txt" , b " b\n " ) ;
7493
7494
7495
repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7496
repo.commit ( " more" , & [ ] ) .unwrap ( ) ;
7497
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , Some ( vec! [ ] ) ) ;
7498
}
7499
77445673 Andy McCall 1 month ago ↶
7500
7501
7502
7503
# [ test ]
7504
fn the_working_copy_diff_from_the_cache_matches_a_snapshot ( ) {
7505
hermetic ( ) ;
7506
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7507
let root = dir.path ( ) ;
7508
let repo = Repo:: init( root) .unwrap ( ) ;
7509
write ( root, " a.txt" , b " one\n two\n three\n " ) ;
7510
write ( root, " gone.txt" , b " bye\n " ) ;
7511
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7512
write ( root, " a.txt" , b " one\n 2\n three\n four\n " ) ;
7513
write ( root, " new.txt" , b " hello\n " ) ;
7514
fs:: remove_file( root.join ( " gone.txt" ) ) .unwrap ( ) ;
7515
let snap = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7516
let slow = render_change ( & snap.wc, & snap.base, & snap.base_snapshot) ;
7517
assert! (
7518
repo.status_by_manifest ( ) .unwrap ( ) .is_some ( ) ,
7519
" the cache holds"
7520
) ;
7521
let ( wc, base, snapshot) = repo.working_copy_with_base ( ) .unwrap ( ) ;
7522
assert_eq! ( render_change ( & wc, & base, & snapshot) , slow) ;
7523
assert_eq! ( repo.diff_files ( None ) .unwrap ( ) .len ( ) , 3 ) ;
7524
}
7525
1ec3f008 Andy McCall 1 month ago ↶
7526
7527
7528
7529
7530
# [ test ]
7531
fn the_manifest_check_skips_listing_while_no_directory_changed ( ) {
7532
hermetic ( ) ;
7533
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7534
let root = dir.path ( ) ;
7535
let repo = Repo:: init( root) .unwrap ( ) ;
7536
write ( root, " a.txt" , b " a\n " ) ;
7537
write ( root, " sub/deep/b.txt" , b " b\n " ) ;
7538
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7539
7540
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 60 ) ;
7541
for p in [ " a.txt" , " sub/deep/b.txt" , " sub/deep" , " sub" , " " ] {
7542
fs:: File:: open( root.join ( p) )
7543
.unwrap ( )
7544
.set_modified ( old)
7545
.unwrap ( ) ;
7546
}
7547
repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7548
repo.write_manifest ( false , false ) .unwrap ( ) ;
7549
let m = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) .2 ;
7550
assert! ( m.dirs.iter ( ) .any ( | ( d , _) | d = = " sub/deep" ) , " {:?}" , m.dirs) ;
7551
assert_eq! (
7552
repo.disk_differs_by_dirs ( & m) ,
7553
Some ( false ) ,
7554
" no listing needed"
7555
) ;
7556
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , Some ( vec! [ ] ) ) ;
7557
7558
7559
write ( root, " sub/deep/b.txt" , b " c\n " ) ;
7560
assert_eq! ( repo.disk_differs_by_dirs ( & m) , Some ( true ) ) ;
7561
write ( root, " sub/deep/b.txt" , b " b\n " ) ;
7562
fs:: File:: open( root.join ( " sub/deep/b.txt" ) )
7563
.unwrap ( )
7564
.set_modified ( old)
7565
.unwrap ( ) ;
7566
assert_eq! ( repo.disk_differs_by_dirs ( & m) , Some ( false ) ) ;
7567
7568
7569
write ( root, " sub/deep/new.txt" , b " n\n " ) ;
7570
assert_eq! ( repo.disk_differs_by_dirs ( & m) , None ) ;
7571
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None ) ;
7572
fs:: remove_file( root.join ( " sub/deep/new.txt" ) ) .unwrap ( ) ;
7573
7574
fs:: remove_file( root.join ( " a.txt" ) ) .unwrap ( ) ;
7575
assert_eq! ( repo.disk_differs_by_dirs ( & m) , None ) ;
7576
}
7577
e226a6cc Andy McCall 1 month ago ↶
7578
7579
7580
7581
# [ test ]
7582
fn a_commit_after_a_status_uses_the_cached_snapshot ( ) {
7583
hermetic ( ) ;
7584
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7585
let root = dir.path ( ) ;
7586
let repo = Repo:: init( root) .unwrap ( ) ;
7587
write ( root, " a.txt" , b " a\n b\n " ) ;
7588
write ( root, " .oxyattributes" , b " *.bin binary\n " ) ;
7589
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7590
write ( root, " a.txt" , b " a\n B\n " ) ;
7591
write ( root, " new.txt" , b " new\n " ) ;
7592
let snap = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7593
let cached = repo
7594
.snapshot_from_cache ( )
7595
.unwrap ( )
7596
.expect ( " the cache holds" ) ;
7597
assert_eq! ( cached.wc_hash, snap.wc_hash) ;
7598
assert_eq! ( cached.statuses, snap.statuses) ;
7599
assert! (
7600
! cached.attributes.is_empty ( ) ,
7601
" the .oxyattributes file was read"
7602
) ;
7603
let h = repo.commit ( " edit" , & [ ] ) .unwrap ( ) ;
7604
let c = repo.store ( ) .get_change ( & h) .unwrap ( ) ;
7605
assert_eq! (
7606
c.tree_ops.len ( ) + c.edits.len ( ) ,
7607
snap.wc.tree_ops.len ( ) + snap.wc.edits.len ( )
7608
) ;
7609
assert_eq! (
7610
repo.status_by_manifest ( ) .unwrap ( ) ,
7611
Some ( vec! [ ] ) ,
7612
" clean after the commit"
7613
) ;
7614
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " a\n B\n " ) ;
7615
}
7616
0389c3e8 Andy McCall 1 month ago ↶
7617
7618
7619
7620
7621
# [ test ]
7622
fn a_verified_clean_check_restamps_the_manifest_safely ( ) {
7623
hermetic ( ) ;
7624
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7625
let root = dir.path ( ) ;
7626
let repo = Repo:: init( root) .unwrap ( ) ;
7627
write ( root, " a.txt" , b " one\n " ) ;
7628
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
7629
let before = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) .2. written_ns;
7630
assert! ( repo.clean_by_manifest ( ) .unwrap ( ) ) ;
7631
let after = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) .2. written_ns;
7632
assert! ( after > before, " restamped" ) ;
7633
write ( root, " a.txt" , b " two\n " ) ;
7634
assert! (
7635
! repo.clean_by_manifest ( ) .unwrap ( ) ,
7636
" a same-size rewrite is still seen"
7637
) ;
7638
}
7639
86e1824f Andy McCall 1 month ago ↶
7640
7641
7642
7643
# [ test ]
7644
fn history_only_states_read_text_only_for_the_graphs_they_build ( ) {
7645
hermetic ( ) ;
7646
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7647
let root = dir.path ( ) ;
7648
let repo = Repo:: init( root) .unwrap ( ) ;
7649
let big: String = ( 0 .. 3000 ) .map ( | i | format! ( " big line {i} \n " ) ) .collect ( ) ;
7650
let other: String = ( 0 .. 3000 ) .map ( | i | format! ( " other line {i} \n " ) ) .collect ( ) ;
7651
write ( root, " big.txt" , big.as_bytes ( ) ) ;
7652
write ( root, " other.txt" , other.as_bytes ( ) ) ;
7653
let h = repo.commit ( " two large files" , & [ ] ) .unwrap ( ) ;
7654
write ( root, " big.txt" , format! ( " {big} one more\n " ) .as_bytes ( ) ) ;
7655
let h2 = repo.commit ( " append" , & [ ] ) .unwrap ( ) ;
7656
7657
let full = repo.load_state_full ( & [ h2] ) .unwrap ( ) .materialize ( ) ;
7658
let text = | snap : & crate:: state:: Snapshot, p : & str | match & snap.files[ p] .content {
7659
FileContent:: Text { bytes, .. } => bytes.clone ( ) ,
7660
other => panic! ( " {other:?}" ) ,
7661
} ;
7662
7663
let mut hist = repo.load_state_history ( & [ h2] ) .unwrap ( ) ;
7664
let big_node = full.files[ " big.txt" ] .node;
7665
assert! ( hist.graph_key ( & big_node) .is_some ( ) , " not built yet" ) ;
7666
repo.load_all_graphs ( & mut hist) .unwrap ( ) ;
7667
let rendered = hist.materialize ( ) ;
7668
for p in [ " big.txt" , " other.txt" ] {
7669
assert_eq! ( text ( & rendered, p) , text ( & full, p) , " {p}" ) ;
7670
}
7671
7672
7673
7674
let ( _ , texts) = repo.store ( ) .get_change_lazy ( & h) .unwrap ( ) ;
7675
assert_eq! ( texts.len ( ) , 2 ) ;
7676
let content = | r : & ObjectRef| -> Vec< u8 > {
7677
let blob = crate :: objects:: Blob:: decode( & repo.store ( ) .get ( r) .unwrap ( ) .1 ) .unwrap ( ) ;
7678
blob.chunks
7679
.iter ( )
7680
.flat_map ( | c | repo.store ( ) .get ( c) .unwrap ( ) .1 )
7681
.collect ( )
7682
} ;
7683
let other_text = texts
7684
.iter ( )
a4fad5b0 Andy McCall 1 month ago ↶
7685
.filter_map ( | t | match t {
0f93f49c Andy McCall 1 month ago ↶
7686
crate :: objects:: TextRef:: Blob( r, _ , _ ) => Some ( * r) ,
a4fad5b0 Andy McCall 1 month ago ↶
7687
_ => None ,
7688
} )
86e1824f Andy McCall 1 month ago ↶
7689
.find ( | r | content ( r) .windows ( 12 ) .any ( | w | w = = b " other line 1" ) )
7690
.unwrap ( ) ;
64818938 Andy McCall 1 month ago ↶
7691
repo.store ( ) .remove_loose_for_test ( & other_text) .unwrap ( ) ;
86e1824f Andy McCall 1 month ago ↶
7692
let mut hist = repo.load_state_history ( & [ h2] ) .unwrap ( ) ;
7693
repo.load_graphs_of ( & mut hist, & [ big_node] ) .unwrap ( ) ;
7694
assert! ( repo
7695
.load_graphs_of ( & mut hist, & [ full.files[ " other.txt" ] .node] )
7696
.is_err ( ) ) ;
7697
}
7698
7699
7700
7701
7702
# [ test ]
7703
fn a_commit_leaves_a_manifest_the_next_status_trusts ( ) {
7704
use std:: os:: unix:: fs:: PermissionsExt;
7705
hermetic ( ) ;
7706
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7707
let root = dir.path ( ) ;
7708
let repo = Repo:: init( root) .unwrap ( ) ;
7709
let files = [ " a.txt" , " b.txt" , " d/c.txt" ] ;
7710
for p in files {
7711
write ( root, p, format! ( " {p} \n " ) .as_bytes ( ) ) ;
7712
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
7713
fs:: File:: options( )
7714
.write ( true )
7715
.open ( root.join ( p) )
7716
.unwrap ( )
7717
.set_modified ( old)
7718
.unwrap ( ) ;
7719
}
7720
repo.commit ( " added" , & [ ] ) .unwrap ( ) ;
7721
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
7722
assert! (
7723
m.entries.values ( ) .all ( | e | e.graph.is_some ( ) ) ,
7724
" keys recorded at commit"
7725
) ;
7726
7727
for p in files {
7728
fs:: set_permissions( root.join ( p) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
7729
}
7730
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
7731
for p in files {
7732
fs:: set_permissions( root.join ( p) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
7733
}
7734
assert! ( ! snap.changed & & snap.statuses.is_empty ( ) ) ;
7735
assert_eq! ( snap.base_snapshot.unrendered.len ( ) , files.len ( ) ) ;
7736
}
c5ce0367 Andy McCall 1 month ago ↶
7737
7738
7739
7740
# [ test ]
7741
fn checkout_touches_only_what_differs_and_renders_in_batches ( ) {
7742
use std:: os:: unix:: fs:: PermissionsExt;
7743
hermetic ( ) ;
7744
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7745
let root = dir.path ( ) ;
7746
let origin = root.join ( " origin" ) ;
7747
let mut repo = Repo:: init( & origin) .unwrap ( ) ;
7748
7749
let n = 2100 ;
7750
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
7751
for i in 0 .. n {
7752
let p = format! ( " f/{i:04} .txt" ) ;
7753
write ( & origin, & p, format! ( " file {i} \n " ) .as_bytes ( ) ) ;
7754
fs:: File:: options( )
7755
.write ( true )
7756
.open ( origin.join ( & p) )
7757
.unwrap ( )
7758
.set_modified ( old)
7759
.unwrap ( ) ;
7760
}
7761
repo.commit ( " many" , & [ ] ) .unwrap ( ) ;
7762
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
7763
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
7764
write ( & origin, " f/0007.txt" , b " changed\n " ) ;
7765
repo.commit ( " one change" , & [ ] ) .unwrap ( ) ;
7766
7767
7768
for i in 0 .. n {
7769
if i ! = 7 {
7770
let p = origin.join ( format! ( " f/{i:04} .txt" ) ) ;
7771
fs:: set_permissions( & p, fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
7772
}
7773
}
7774
let r = repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
7775
for i in 0 .. n {
7776
let p = origin.join ( format! ( " f/{i:04} .txt" ) ) ;
7777
fs:: set_permissions( & p, fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
7778
}
7779
assert_eq! ( ( r.written, r.removed) , ( 1 , 0 ) , " {r:?}" ) ;
7780
assert_eq! ( fs:: read( origin.join ( " f/0007.txt" ) ) .unwrap ( ) , b " file 7\n " ) ;
7781
assert! ( repo.snapshot_status ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
7782
7783
7784
let c = root.join ( " clone" ) ;
7785
Repo:: clone( & origin.to_string_lossy ( ) , & c) .unwrap ( ) ;
7786
for i in [ 0 , 2047 , 2048 , n - 1 ] {
7787
assert_eq! (
7788
fs:: read( c.join ( format! ( " f/{i:04} .txt" ) ) ) .unwrap ( ) ,
7789
format! ( " file {i} \n " ) .as_bytes ( )
7790
) ;
7791
}
7792
let cloned = Repo:: open( & c) .unwrap ( ) ;
7793
assert! ( cloned
7794
.snapshot_status ( " status" )
7795
.unwrap ( )
7796
.statuses
7797
.is_empty ( ) ) ;
d3cd7ef9 Andy McCall 1 month ago ↶
7798
}
7799
7800
7801
7802
7803
# [ test ]
7804
fn a_racy_manifest_entry_is_checked_by_hash ( ) {
7805
hermetic ( ) ;
7806
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7807
let root = dir.path ( ) ;
7808
let repo = Repo:: init( root) .unwrap ( ) ;
7809
write ( root, " a.txt" , b " one\n " ) ;
7810
repo.commit ( " fresh" , & [ ] ) .unwrap ( ) ;
7811
assert! ( repo.clean_by_manifest ( ) .unwrap ( ) , " fresh but equal" ) ;
7812
let t = fs:: metadata( root.join ( " a.txt" ) )
7813
.unwrap ( )
7814
.modified ( )
7815
.unwrap ( ) ;
7816
write ( root, " a.txt" , b " two\n " ) ;
7817
fs:: File:: options( )
7818
.write ( true )
7819
.open ( root.join ( " a.txt" ) )
7820
.unwrap ( )
7821
.set_modified ( t)
7822
.unwrap ( ) ;
7823
assert! (
7824
! repo.clean_by_manifest ( ) .unwrap ( ) ,
7825
" same size and time, other bytes"
7826
) ;
7827
assert_eq! (
7828
statuses ( & repo.snapshot_status ( " status" ) .unwrap ( ) ) ,
7829
vec! [ ( " a.txt" .to_string ( ) , ChangeStatus:: Modified) ]
7830
) ;
c5ce0367 Andy McCall 1 month ago ↶
7831
}
2c75672f Andy McCall 1 month ago ↶
7832
7833
7834
7835
# [ test ]
7836
fn a_raw_bundle_without_a_changes_text_is_refused ( ) {
7837
hermetic ( ) ;
7838
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7839
let root = dir.path ( ) ;
7840
let a = Repo:: init( & root.join ( " a" ) ) .unwrap ( ) ;
7841
let big: String = ( 0 .. 3000 ) .map ( | i | format! ( " line {i} \n " ) ) .collect ( ) ;
7842
write ( & root.join ( " a" ) , " big.txt" , big.as_bytes ( ) ) ;
7843
let h = a.commit ( " big" , & [ ] ) .unwrap ( ) ;
7844
let bundle = a.bundle_raw_for ( & [ h] , None ) .unwrap ( ) ;
7845
assert_eq! ( bundle.raw_changes.len ( ) , 1 ) ;
7846
assert! ( bundle.changes.is_empty ( ) ) ;
7847
let b = Repo:: init( & root.join ( " b" ) ) .unwrap ( ) ;
7848
let mut without = bundle.clone ( ) ;
7849
without.objects.retain ( | ( ty , _) | * ty ! = ObjectType:: Blob) ;
7850
let e = b.import_bundle ( & without, false ) .unwrap_err ( ) ;
7851
assert! ( e.to_string ( ) .contains ( " came without its text blob" ) , " {e}" ) ;
7852
assert! ( ! b.store ( ) .has ( & h) ) ;
7853
assert_eq! ( b.import_bundle ( & bundle, false ) .unwrap ( ) , vec! [ h] ) ;
7854
assert_eq! ( b.store ( ) .get_change ( & h) .unwrap ( ) .message, " big" ) ;
7855
}
a87c7b9d Andy McCall 1 month ago ↶
7856
7857
7858
7859
7860
7861
# [ test ]
7862
fn git_import_brings_a_directory_back_rather_than_making_a_second ( ) {
7863
hermetic ( ) ;
7864
if std:: process:: Command:: new( " git" )
7865
.arg ( " --version" )
7866
.output ( )
7867
.is_err ( )
7868
{
7869
eprintln! ( " git not installed; skipping" ) ;
7870
return ;
7871
}
7872
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7873
let root = dir.path ( ) ;
7874
let g = root.join ( " src.git" ) ;
7875
fs:: create_dir_all( & g) .unwrap ( ) ;
7876
let git = | args : & [& str ]| -> String {
7877
let out = std:: process:: Command:: new( " git" )
7878
.arg ( " -C" )
7879
.arg ( & g)
7880
.args ( [
7881
" -c" ,
7882
" user.name=Grace" ,
7883
" -c" ,
7884
" [email protected] " ,
7885
" -c" ,
7886
" commit.gpgsign=false" ,
7887
] )
7888
.args ( args)
7889
.output ( )
7890
.unwrap ( ) ;
7891
assert! (
7892
out.status.success ( ) ,
7893
" git {args:?}: {}" ,
7894
String :: from_utf8_lossy( & out.stderr)
7895
) ;
7896
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
7897
} ;
7898
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
7899
write ( & g, " d/a.txt" , b " a\n " ) ;
7900
write ( & g, " c/x.txt" , b " x\n " ) ;
7901
git ( & [ " add" , " ." ] ) ;
7902
git ( & [ " commit" , " -qm" , " base" ] ) ;
7903
7904
git ( & [ " checkout" , " -qb" , " side" ] ) ;
7905
git ( & [ " mv" , " c/x.txt" , " d/x.txt" ] ) ;
7906
git ( & [ " commit" , " -qm" , " x into d" ] ) ;
7907
7908
git ( & [ " checkout" , " -q" , " main" ] ) ;
7909
git ( & [ " rm" , " -q" , " d/a.txt" ] ) ;
7910
git ( & [ " commit" , " -qm" , " d goes" ] ) ;
7911
write ( & g, " d/b.txt" , b " b\n " ) ;
7912
git ( & [ " add" , " ." ] ) ;
7913
git ( & [ " commit" , " -qm" , " d comes back" ] ) ;
7914
git ( & [ " mv" , " c/x.txt" , " d/x.txt" ] ) ;
7915
git ( & [ " commit" , " -qm" , " x into d again" ] ) ;
7916
git ( & [ " merge" , " -q" , " --no-edit" , " side" ] ) ;
7917
7918
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
7919
repo.import_git ( & g, " main" ) .unwrap ( ) ;
7920
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
7921
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
7922
let st = repo
7923
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
7924
.unwrap ( ) ;
7925
let view = st.tree ( ) .view ( ) ;
7926
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
7927
7928
7929
let first = repo
7930
.log ( )
7931
.unwrap ( )
7932
.into_iter ( )
7933
.find ( | e | e.change.message.starts_with ( " base" ) )
7934
.unwrap ( ) ;
7935
let base = repo
7936
.load_state_history ( & [ first.hash] )
7937
.unwrap ( )
7938
.tree ( )
7939
.view ( ) ;
7940
assert_eq! ( view.entries[ " d" ] .node, base.entries[ " d" ] .node) ;
7941
}
7942
7943
7944
7945
7946
# [ test ]
7947
fn git_import_merge_retires_a_placement_under_a_removed_directory ( ) {
7948
hermetic ( ) ;
7949
if std:: process:: Command:: new( " git" )
7950
.arg ( " --version" )
7951
.output ( )
7952
.is_err ( )
7953
{
7954
eprintln! ( " git not installed; skipping" ) ;
7955
return ;
7956
}
7957
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7958
let root = dir.path ( ) ;
7959
let g = root.join ( " src.git" ) ;
7960
fs:: create_dir_all( & g) .unwrap ( ) ;
7961
let run = | args : & [& str ]| {
7962
std:: process:: Command:: new( " git" )
7963
.arg ( " -C" )
7964
.arg ( & g)
7965
.args ( [
7966
" -c" ,
7967
" user.name=Grace" ,
7968
" -c" ,
7969
" [email protected] " ,
7970
" -c" ,
7971
" commit.gpgsign=false" ,
7972
] )
7973
.args ( args)
7974
.output ( )
7975
.unwrap ( )
7976
} ;
7977
let git = | args : & [& str ]| {
7978
let out = run ( args) ;
7979
assert! (
7980
out.status.success ( ) ,
7981
" git {args:?}: {}" ,
7982
String :: from_utf8_lossy( & out.stderr)
7983
) ;
7984
} ;
7985
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
7986
write ( & g, " c/x.txt" , b " x\n " ) ;
7987
write ( & g, " keep.txt" , b " k\n " ) ;
7988
git ( & [ " add" , " ." ] ) ;
7989
git ( & [ " commit" , " -qm" , " base" ] ) ;
7990
7991
7992
git ( & [ " checkout" , " -qb" , " side" ] ) ;
7993
git ( & [ " mv" , " c/x.txt" , " c/y.txt" ] ) ;
7994
git ( & [ " commit" , " -qm" , " rename within c" ] ) ;
7995
git ( & [ " checkout" , " -q" , " main" ] ) ;
7996
fs:: create_dir_all( g.join ( " n" ) ) .unwrap ( ) ;
7997
git ( & [ " mv" , " c/x.txt" , " n/x.txt" ] ) ;
7998
git ( & [ " commit" , " -qm" , " move out of c" ] ) ;
7999
8000
let _ = run ( & [ " merge" , " -q" , " side" ] ) ;
8001
let _ = run ( & [
8002
" rm" ,
8003
" -q" ,
8004
" --cached" ,
8005
" --ignore-unmatch" ,
8006
" c/y.txt" ,
8007
" c/x.txt" ,
8008
] ) ;
8009
let _ = std:: fs:: remove_dir_all( g.join ( " c" ) ) ;
8010
write ( & g, " n/x.txt" , b " x\n " ) ;
8011
git ( & [ " add" , " -A" ] ) ;
8012
git ( & [ " commit" , " -qm" , " merge: keep n/x.txt" ] ) ;
8013
8014
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
8015
repo.import_git ( & g, " main" ) .unwrap ( ) ;
8016
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
8017
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
8018
let st = repo
8019
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
8020
.unwrap ( ) ;
8021
let view = st.tree ( ) .view ( ) ;
8022
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
8023
assert! ( view.entries.contains_key ( " n/x.txt" ) ) ;
8024
}
8025
8026
8027
8028
8029
# [ test ]
8030
fn git_import_merge_leaves_no_orphan_behind ( ) {
8031
hermetic ( ) ;
8032
if std:: process:: Command:: new( " git" )
8033
.arg ( " --version" )
8034
.output ( )
8035
.is_err ( )
8036
{
8037
eprintln! ( " git not installed; skipping" ) ;
8038
return ;
8039
}
8040
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8041
let root = dir.path ( ) ;
8042
let g = root.join ( " src.git" ) ;
8043
fs:: create_dir_all( & g) .unwrap ( ) ;
8044
let git = | args : & [& str ]| {
8045
let out = std:: process:: Command:: new( " git" )
8046
.arg ( " -C" )
8047
.arg ( & g)
8048
.args ( [
8049
" -c" ,
8050
" user.name=Grace" ,
8051
" -c" ,
8052
" [email protected] " ,
8053
" -c" ,
8054
" commit.gpgsign=false" ,
8055
] )
8056
.args ( args)
8057
.output ( )
8058
.unwrap ( ) ;
8059
assert! (
8060
out.status.success ( ) ,
8061
" git {args:?}: {}" ,
8062
String :: from_utf8_lossy( & out.stderr)
8063
) ;
8064
} ;
8065
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
8066
write ( & g, " d/f.txt" , b " f\n " ) ;
8067
write ( & g, " keep.txt" , b " k\n " ) ;
8068
git ( & [ " add" , " ." ] ) ;
8069
git ( & [ " commit" , " -qm" , " base" ] ) ;
8070
git ( & [ " checkout" , " -qb" , " side" ] ) ;
8071
write ( & g, " d/new.txt" , b " new\n " ) ;
8072
git ( & [ " add" , " ." ] ) ;
8073
git ( & [ " commit" , " -qm" , " add into d" ] ) ;
8074
git ( & [ " checkout" , " -q" , " main" ] ) ;
8075
git ( & [ " rm" , " -q" , " d/f.txt" ] ) ;
8076
git ( & [ " commit" , " -qm" , " d goes" ] ) ;
8077
git ( & [ " merge" , " -q" , " --no-edit" , " side" ] ) ;
8078
8079
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
8080
repo.import_git ( & g, " main" ) .unwrap ( ) ;
8081
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
8082
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
8083
let st = repo
8084
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
8085
.unwrap ( ) ;
8086
let view = st.tree ( ) .view ( ) ;
8087
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
8088
assert! ( view.entries.contains_key ( " d/new.txt" ) ) ;
8089
}
d77e1702 Andy McCall 1 month ago ↶
8090
8091
8092
8093
# [ test ]
8094
fn fsck_names_a_missing_text_blob ( ) {
8095
hermetic ( ) ;
8096
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8097
let root = dir.path ( ) ;
8098
let repo = Repo:: init( root) .unwrap ( ) ;
8099
let big: String = ( 0 .. 3000 ) .map ( | i | format! ( " line {i} \n " ) ) .collect ( ) ;
8100
write ( root, " big.txt" , big.as_bytes ( ) ) ;
8101
let h = repo.commit ( " big" , & [ ] ) .unwrap ( ) ;
8102
assert_eq! ( repo.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
8103
let ( _ , texts) = repo.store ( ) .get_change_lazy ( & h) .unwrap ( ) ;
8104
let Some ( crate :: objects:: TextRef:: Blob( b, _ , _ ) ) = texts.first ( ) .copied ( ) else {
8105
panic! ( " a stored text" )
8106
} ;
64818938 Andy McCall 1 month ago ↶
8107
repo.store ( ) .remove_loose_for_test ( & b) .unwrap ( ) ;
d77e1702 Andy McCall 1 month ago ↶
8108
let rep = repo.fsck ( ) .unwrap ( ) ;
8109
assert! ( rep.errors ( ) > 0 ) ;
8110
assert! (
8111
rep.problems.iter ( ) .any ( | p | p.what.contains ( & h.short ( 12 ) ) ) ,
8112
" {:?}" ,
8113
rep.problems
8114
) ;
8115
}
a07f60a3 Andy McCall 1 month ago ↶
8116
8117
8118
8119
8120
# [ test ]
8121
fn a_subrepo_on_the_parents_hub_borrows_its_credentials ( ) {
8122
hermetic ( ) ;
8123
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8124
let mut repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
8125
repo.config
8126
.set (
8127
" remote.origin.url" ,
8128
" https://Hub.example:7343/doomsdayone/metalgear" ,
8129
)
8130
.unwrap ( ) ;
8131
repo.config.set ( " remote.origin.token" , " secret" ) .unwrap ( ) ;
8132
repo.config
8133
.set ( " remote.origin.ca" , " /tmp/hub-ca.pem" )
8134
.unwrap ( ) ;
8135
let same = repo.credentials_for ( " https://hub.example:7343/doomsdayone/rstar-api" ) ;
8136
assert_eq! ( same.token.as_deref ( ) , Some ( " secret" ) ) ;
8137
assert_eq! ( same.ca.as_deref ( ) , Some ( Path:: new( " /tmp/hub-ca.pem" ) ) ) ;
8138
for other in [
8139
" https://elsewhere.example:7343/doomsdayone/rstar-api" ,
8140
" https://hub.example/doomsdayone/rstar-api" ,
8141
" http://hub.example:7343/doomsdayone/rstar-api" ,
8142
] {
8143
let o = repo.credentials_for ( other) ;
8144
assert_ne! ( o.token.as_deref ( ) , Some ( " secret" ) , " {other}" ) ;
8145
}
8146
}
ce14f0d6 Andy McCall 1 month ago ↶
8147
ff1f62fe Andy McCall 1 month ago ↶
8148
8149
8150
ce14f0d6 Andy McCall 1 month ago ↶
8151
# [ test ]
ff1f62fe Andy McCall 1 month ago ↶
8152
fn remotes_use_the_credential_store_and_the_enclosing_checkout ( ) {
8153
hermetic ( ) ;
8154
let home = tempfile:: tempdir( ) .unwrap ( ) ;
8155
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8156
let mut parent = Repo:: init( dir.path ( ) ) .unwrap ( ) .with_home ( home.path ( ) ) ;
8157
parent
8158
.config
8159
.set ( " remote.origin.url" , " https://hub.example/o/parent" )
8160
.unwrap ( ) ;
8161
parent.save_config ( ) .unwrap ( ) ;
8162
assert_eq! ( parent.remote_options ( " origin" ) .token, None ) ;
8163
crate :: credentials:: Credentials:: at( home.path ( ) )
8164
.save ( " https://hub.example" , " stored" , Some ( " andy" ) , None )
8165
.unwrap ( ) ;
8166
assert_eq! (
8167
parent.remote_options ( " origin" ) .token.as_deref ( ) ,
8168
Some ( " stored" )
8169
) ;
8170
8171
let empty_home = tempfile:: tempdir( ) .unwrap ( ) ;
8172
let nested = dir.path ( ) .join ( " deps" ) .join ( " child" ) ;
8173
let mut child = Repo:: init( & nested) .unwrap ( ) .with_home ( empty_home.path ( ) ) ;
8174
child
8175
.config
8176
.set ( " remote.origin.url" , " https://hub.example/o/child" )
8177
.unwrap ( ) ;
8178
child.save_config ( ) .unwrap ( ) ;
8179
assert_eq! ( child.remote_options ( " origin" ) .token, None ) ;
8180
parent
8181
.config
8182
.set ( " remote.origin.token" , " checkout" )
8183
.unwrap ( ) ;
8184
parent.save_config ( ) .unwrap ( ) ;
8185
assert_eq! (
8186
child.remote_options ( " origin" ) .token.as_deref ( ) ,
8187
Some ( " checkout" )
8188
) ;
8189
8190
child
8191
.config
8192
.set ( " remote.origin.url" , " https://other.example/o/child" )
8193
.unwrap ( ) ;
8194
assert_eq! ( child.remote_options ( " origin" ) .token, None ) ;
8195
}
8196
8197
# [ test ]
ce14f0d6 Andy McCall 1 month ago ↶
8198
fn the_metadata_cache_survives_a_torn_tail ( ) {
8199
hermetic ( ) ;
8200
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8201
let root = dir.path ( ) ;
8202
let repo = Repo:: init( root) .unwrap ( ) ;
8203
write ( root, " a.txt" , b " 1\n " ) ;
8204
let h = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8205
let meta = repo.store ( ) .get_change_meta ( & h) .unwrap ( ) ;
8206
let cache = repo.store ( ) .dir ( ) .join ( " metas" ) ;
8207
let len = fs:: metadata( & cache) .unwrap ( ) .len ( ) ;
8208
assert! ( len > 0 , " a change read is cached" ) ;
8209
8210
fs:: OpenOptions:: new( )
8211
.append ( true )
8212
.open ( & cache)
8213
.unwrap ( )
8214
.write_all ( & [ 7 ; 20 ] )
8215
.unwrap ( ) ;
8216
let again = Repo:: open( root) .unwrap ( ) ;
8217
assert_eq! ( again.store ( ) .get_change_meta ( & h) .unwrap ( ) , meta) ;
8218
assert_eq! ( fs:: metadata( & cache) .unwrap ( ) .len ( ) , len) ;
8219
}
f9466021 Andy McCall 1 month ago ↶
8220
8221
# [ test ]
8222
fn a_manifest_that_does_not_check_reads_as_none ( ) {
8223
hermetic ( ) ;
8224
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8225
let root = dir.path ( ) ;
8226
let repo = Repo:: init( root) .unwrap ( ) ;
8227
write ( root, " a.txt" , b " 1\n " ) ;
8228
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8229
let path = repo.manifest_path ( ) ;
8230
let mut bytes = fs:: read( & path) .unwrap ( ) ;
8231
assert! ( Repo:: open( root) .unwrap ( ) .read_manifest ( ) .unwrap ( ) .is_some ( ) ) ;
8232
let last = bytes.len ( ) - 1 ;
8233
bytes[ last] ^ = 1 ;
8234
fs:: write( & path, & bytes) .unwrap ( ) ;
8235
assert! ( Repo:: open( root) .unwrap ( ) .read_manifest ( ) .unwrap ( ) .is_none ( ) ) ;
8236
8237
let again = Repo:: open( root) .unwrap ( ) ;
8238
assert! ( ! again.snapshot ( " status" ) .unwrap ( ) .changed) ;
8239
assert! ( again.read_manifest ( ) .unwrap ( ) .is_some ( ) ) ;
8240
}
8241
8242
# [ test ]
8243
fn a_commit_leaves_a_manifest_that_proves_the_working_copy_clean ( ) {
8244
hermetic ( ) ;
8245
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8246
let root = dir.path ( ) ;
8247
let repo = Repo:: init( root) .unwrap ( ) ;
8248
write ( root, " a.txt" , b " 1\n " ) ;
8249
write ( root, " b.txt" , b " 2\n " ) ;
8250
8251
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 60 ) ;
8252
fs:: File:: options( )
8253
.write ( true )
8254
.open ( root.join ( " b.txt" ) )
8255
.unwrap ( )
8256
.set_modified ( old)
8257
.unwrap ( ) ;
8258
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8259
assert! ( repo.clean_by_manifest ( ) .unwrap ( ) , " after the first commit" ) ;
8260
repo.snapshot_status ( " status" ) .unwrap ( ) ;
8261
write ( root, " a.txt" , b " 1\n 2\n " ) ;
8262
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
8263
assert! (
8264
repo.clean_by_manifest ( ) .unwrap ( ) ,
8265
" after a commit of one file"
8266
) ;
8267
8268
8269
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
8270
repo.snapshot_status ( " status" ) .unwrap ( ) ;
8271
write ( root, " a.txt" , b " 1\n 2\n 3\n 4\n 5\n " ) ;
8272
repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
8273
assert! (
8274
repo.clean_by_manifest ( ) .unwrap ( ) ,
8275
" after an edit a status saw"
8276
) ;
8277
}
e266b7d4 Andy McCall 1 month ago ↶
8278
8279
# [ test ]
8280
fn a_conflicted_rebase_can_be_resolved_in_the_working_copy ( ) {
8281
hermetic ( ) ;
8282
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8283
let root = dir.path ( ) ;
8284
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8285
write ( root, " a.txt" , b " 1\n 2\n " ) ;
8286
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
8287
repo.bookmark_create ( " develop" , Some ( & [ a] ) ) .unwrap ( ) ;
8288
write ( root, " a.txt" , b " 1\n X\n " ) ;
8289
let c = repo.commit ( " C: 2 to X" , & [ ] ) .unwrap ( ) ;
8290
repo.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
8291
write ( root, " a.txt" , b " 1\n Y\n " ) ;
8292
let d = repo.commit ( " D: 2 to Y" , & [ ] ) .unwrap ( ) ;
8293
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
8294
8295
let err = repo.rebase ( & c, & [ d] ) .unwrap_err ( ) ;
8296
assert! ( matches! ( err, RepoError:: Conflicted( _ ) ) , " {err}" ) ;
8297
let r = repo.rebase_into_working_copy ( & c, & [ d] ) .unwrap ( ) ;
8298
assert! ( r.changed & & r.checked_out) ;
8299
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8300
assert! (
8301
snap.base_snapshot.is_conflicted ( ) ,
8302
" the conflict is in the working copy"
8303
) ;
8304
let text = fs:: read_to_string( root.join ( " a.txt" ) ) .unwrap ( ) ;
8305
assert! ( text.contains ( ' X' ) & & text.contains ( ' Y' ) , " {text}" ) ;
8306
8307
write ( root, " a.txt" , b " 1\n XY\n " ) ;
8308
repo.commit ( " Resolve" , & [ ] ) .unwrap ( ) ;
8309
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
8310
assert! ( st.contains ( & c) & & st.contains ( & d) ) ;
8311
let files = st.materialize ( ) .files;
8312
assert! ( ! files[ " a.txt" ] .is_conflicted ( ) ) ;
8313
assert! (
8314
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 1\n XY\n " )
8315
) ;
8316
8317
8318
repo.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
8319
write ( root, " b.txt" , b " b\n " ) ;
8320
let e = repo.commit ( " E" , & [ ] ) .unwrap ( ) ;
8321
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
8322
let err = repo.rebase_into_working_copy ( & e, & [ a] ) .unwrap_err ( ) ;
8323
assert! (
8324
err.to_string ( ) .contains ( " not on the current bookmark" ) ,
8325
" {err}"
8326
) ;
8327
}
d4dbab97 Andy McCall 1 month ago ↶
8328
8329
# [ test ]
8330
fn an_ignore_rule_drops_an_added_file_but_not_a_tracked_one ( ) {
8331
hermetic ( ) ;
8332
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8333
let root = dir.path ( ) ;
8334
let repo = Repo:: init( root) .unwrap ( ) ;
8335
write ( root, " a.txt" , b " a\n " ) ;
8336
write ( root, " kept.log" , b " tracked\n " ) ;
8337
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8338
write ( root, " build.log" , b " made\n " ) ;
8339
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8340
assert! ( snap.statuses.iter ( ) .any ( | s | s.path = = " build.log" ) ) ;
8341
8342
8343
write ( root, " .gitignore" , b " *.log\n " ) ;
8344
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8345
let paths: Vec< & str > = snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect ( ) ;
8346
assert_eq! ( paths, vec! [ " .gitignore" ] , " {paths:?}" ) ;
8347
write ( root, " kept.log" , b " tracked, edited\n " ) ;
8348
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8349
assert! ( snap.statuses.iter ( ) .any ( | s | s.path = = " kept.log" ) ) ;
8350
}
fd280228 Andy McCall 1 month ago ↶
8351
8352
# [ test ]
8353
fn shed_graphs_come_back_the_same ( ) {
8354
hermetic ( ) ;
8355
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8356
let root = dir.path ( ) ;
8357
let repo = Repo:: init( root) .unwrap ( ) ;
8358
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
8359
write ( root, " b.txt" , b " x\n " ) ;
8360
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8361
write ( root, " a.txt" , b " 1\n two\n 3\n 4\n " ) ;
8362
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
8363
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
8364
let mut st = repo.load_state_history ( & heads) .unwrap ( ) ;
8365
let view = st.tree ( ) .view ( ) ;
8366
let nodes: Vec< NodeId> = [ " a.txt" , " b.txt" ]
8367
.iter ( )
8368
.map ( | p | view.entries[ * p] .node )
8369
.collect ( ) ;
8370
repo.load_graphs_of ( & mut st, & nodes) .unwrap ( ) ;
8371
let before: Vec< Vec< u8 > > = nodes.iter ( ) .map ( | n | st.render_file ( n) .0 ) .collect ( ) ;
8372
assert_eq! ( st.built_count ( ) , 2 ) ;
8373
let shed = st.shed_graphs ( | b | repo.store_blob ( b) ) .unwrap ( ) ;
8374
assert_eq! ( ( shed, st.built_count ( ) ) , ( 2 , 0 ) ) ;
8375
8376
write ( root, " a.txt" , b " 1\n two\n 3\n 4\n 5\n " ) ;
8377
let h3 = repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
8378
let ( skeleton, texts) = repo.store ( ) .get_change_lazy ( & h3) .unwrap ( ) ;
8379
st.add_known ( h3, skeleton, texts) ;
8380
repo.load_graphs_of ( & mut st, & nodes) .unwrap ( ) ;
8381
let after: Vec< Vec< u8 > > = nodes.iter ( ) .map ( | n | st.render_file ( n) .0 ) .collect ( ) ;
8382
assert_eq! ( after[ 1 ] , before[ 1 ] ) ;
8383
assert_eq! ( after[ 0 ] , b " 1\n two\n 3\n 4\n 5\n " ) ;
8384
}
360aa74a Andy McCall 1 month ago ↶
8385
8386
# [ test ]
8387
fn many_edited_files_diff_in_parallel ( ) {
8388
8389
8390
8391
hermetic ( ) ;
8392
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8393
let root = dir.path ( ) ;
8394
let repo = Repo:: init( root) .unwrap ( ) ;
8395
let text = | i : usize , round : usize | {
8396
let mut s = String :: new( ) ;
8397
for line in 0 .. 50 {
8398
if round > 0 & & line % 7 = = i % 7 {
8399
s.push_str ( & format! ( " edited {round} {i} {line} \n " ) ) ;
8400
} else {
8401
s.push_str ( & format! ( " file {i} line {line} \n " ) ) ;
8402
}
8403
}
8404
if round > 0 & & i.is_multiple_of ( 3 ) {
8405
s.push_str ( " appended\n " ) ;
8406
}
8407
s
8408
} ;
8409
for i in 0 .. 40 {
8410
write ( root, & format! ( " d/f{i} .txt" ) , text ( i, 0 ) .as_bytes ( ) ) ;
8411
}
8412
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8413
for i in 0 .. 40 {
8414
write ( root, & format! ( " d/f{i} .txt" ) , text ( i, 1 ) .as_bytes ( ) ) ;
8415
}
8416
let h = repo.commit ( " edit every file" , & [ ] ) .unwrap ( ) ;
8417
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
8418
for i in 0 .. 40 {
8419
let want = text ( i, 1 ) ;
8420
assert! (
8421
matches! ( & snap.files[ & format! ( " d/f{i} .txt" ) ] .content, FileContent:: Text { bytes, .. } if bytes = = want.as_bytes ( ) ) ,
8422
" d/f{i}.txt as committed"
8423
) ;
8424
}
8425
assert! (
8426
repo.commit ( " nothing" , & [ ] ) .is_err ( ) ,
8427
" nothing left to commit"
8428
) ;
8429
}
b35df36b Andy McCall 1 month ago ↶
8430
8431
8432
8433
8434
8435
# [ test ]
8436
fn a_fetched_change_shows_as_behind_and_resolves ( ) {
8437
hermetic ( ) ;
8438
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8439
let root = dir.path ( ) ;
8440
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
8441
drop ( origin) ;
8442
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
8443
let a = Repo:: clone( & url, & root.join ( " a" ) )
8444
.unwrap ( )
8445
.with_home ( root.join ( " home-a" ) ) ;
8446
write ( & root.join ( " a" ) , " f.txt" , b " one\n " ) ;
8447
a.commit ( " one" , & [ ] ) .unwrap ( ) ;
8448
a.push ( " origin" , false ) .unwrap ( ) ;
8449
let b = Repo:: clone( & url, & root.join ( " b" ) )
8450
.unwrap ( )
8451
.with_home ( root.join ( " home-b" ) ) ;
8452
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 0 ) ) ) ;
8453
8454
write ( & root.join ( " a" ) , " f.txt" , b " one\n two\n " ) ;
8455
let h2 = a.commit ( " two" , & [ ] ) .unwrap ( ) ;
8456
a.push ( " origin" , false ) .unwrap ( ) ;
8457
b.fetch ( " origin" ) .unwrap ( ) ;
8458
8459
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 1 ) ) ) ;
8460
assert_eq! ( b.tracking ( " origin" , " nope" ) .unwrap ( ) , None ) ;
8461
assert_eq! (
8462
b.resolve_change ( & format! ( " #{} " , & h2.to_hex ( ) [ .. 12 ] ) )
8463
.unwrap ( ) ,
8464
h2
8465
) ;
8466
assert_eq! (
8467
b.remote_only_location ( & h2) .unwrap ( ) .as_deref ( ) ,
8468
Some ( " origin/main" )
8469
) ;
8470
8471
8472
b.fetch ( " origin" ) .unwrap ( ) ;
8473
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 1 ) ) ) ;
8474
b.pull ( " origin" ) .unwrap ( ) ;
8475
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 0 ) ) ) ;
8476
assert_eq! ( b.remote_only_location ( & h2) .unwrap ( ) , None ) ;
8477
8478
8479
let mut c = Repo:: init( & root.join ( " c" ) ) .unwrap ( ) ;
8480
assert_eq! ( c.default_remote ( ) , " origin" ) ;
8481
c.config.set ( " remote.earth.url" , & url) .unwrap ( ) ;
8482
assert_eq! ( c.default_remote ( ) , " earth" ) ;
8483
c.config.set ( " remote.origin.url" , & url) .unwrap ( ) ;
8484
assert_eq! ( c.default_remote ( ) , " origin" ) ;
8485
}
827281ee Andy McCall 1 month ago ↶
8486
8487
8488
8489
# [ test ]
8490
fn a_clone_from_a_relative_path_records_it_absolute ( ) {
8491
hermetic ( ) ;
8492
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8493
let root = dir.path ( ) ;
8494
Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
8495
let rel = crate :: protocol:: stored_url( " relative/repo" ) ;
8496
assert! ( std:: path:: Path:: new( & rel) .is_absolute ( ) , " {rel}" ) ;
8497
assert_eq! (
8498
crate :: protocol:: stored_url( " https://hub.example/o/n" ) ,
8499
" https://hub.example/o/n"
8500
) ;
8501
assert! ( crate :: protocol:: stored_url( " file://x" ) .starts_with ( " file:///" ) ) ;
8d803563 Andy McCall 1 month ago ↶
8502
assert! ( crate :: protocol:: stored_url( " dir://x" ) .starts_with ( " dir:///" ) ) ;
8503
8504
assert_eq! (
8505
crate :: protocol:: stored_url( " s3://bucket/prefix?profile=p" ) ,
8506
" s3://bucket/prefix?profile=p"
8507
) ;
827281ee Andy McCall 1 month ago ↶
8508
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
8509
let b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) ;
8510
assert_eq! ( b.config.remotes[ " origin" ] , url) ;
8511
b.fetch ( " origin" ) .unwrap ( ) ;
8512
}
a46f3e4f Andy McCall 1 month ago ↶
8513
8514
8515
8516
8517
# [ test ]
8518
fn a_workspace_added_on_a_bookmark_is_on_it_with_its_subrepos ( ) {
8519
hermetic ( ) ;
8520
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8521
let root = dir.path ( ) ;
8522
Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
8523
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
8524
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
8525
.unwrap ( )
8526
.with_home ( root.join ( " home" ) ) ;
8527
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
8528
let l1 = lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
8529
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
8530
8531
let app_dir = root.join ( " app" ) ;
8532
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8533
write ( & app_dir, " main.txt" , b " app\n " ) ;
8534
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
8535
app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
8536
8537
app.bookmark_create ( " develop" , None ) .unwrap ( ) ;
8538
app.bookmark_create ( " feature" , None ) .unwrap ( ) ;
8539
let mut app = app;
8540
app.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
8541
assert_eq! ( app.config.bookmark, " feature" ) ;
8542
8543
8544
std:: fs:: rename( root.join ( " lib-origin" ) , root.join ( " lib-origin-gone" ) ) .unwrap ( ) ;
8545
let heads = app.resolve_heads ( " develop" ) .unwrap ( ) ;
8546
let ws = app
8547
.workspace_add_on ( & root.join ( " ws" ) , Some ( & heads) , Some ( " develop" ) )
8548
.unwrap ( ) ;
8549
assert_eq! (
8550
ws.config.bookmark, " develop" ,
8551
" the new workspace is on develop"
8552
) ;
8553
assert_eq! (
8554
app.config.bookmark, " feature" ,
8555
" and this one stays on its own"
8556
) ;
8557
let sub = Repo:: open( & root.join ( " ws/libs/lib" ) ) .unwrap ( ) ;
8558
assert_eq! (
8559
sub.config.remotes[ " origin" ] , lib_url,
8560
" its origin is the subrepo's URL"
8561
) ;
8562
assert_eq! ( sub.bookmark_heads ( ) .unwrap ( ) , vec! [ l1] , " at its pin" ) ;
8563
assert_eq! (
8564
std:: fs:: read( root.join ( " ws/libs/lib/lib.txt" ) ) .unwrap ( ) ,
8565
b " v1\n "
8566
) ;
8567
}
47ce8b8e Andy McCall 1 month ago ↶
8568
8569
8570
8571
8572
# [ test ]
8573
fn revsets_select_changes_by_relation_author_text_date_and_path ( ) {
8574
hermetic ( ) ;
8575
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8576
let root = dir.path ( ) ;
8577
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8578
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
8579
.unwrap ( ) ;
8580
write ( root, " src/a.rs" , b " one\n " ) ;
8581
let a = repo.commit ( " first: add a" , & [ ] ) .unwrap ( ) ;
8582
write ( root, " src/a.rs" , b " one\n two\n " ) ;
8583
let b = repo.commit ( " second: grow a" , & [ ] ) .unwrap ( ) ;
8584
8585
repo.checkout ( & [ a] , false , " checkout a" ) .unwrap ( ) ;
8586
write ( root, " docs/notes.md" , b " notes\n " ) ;
8587
let c = repo.commit ( " third: notes" , & [ ] ) .unwrap ( ) ;
8588
8589
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
8590
.unwrap ( ) ;
8591
fs:: remove_file( root.join ( " src/a.rs" ) ) .unwrap ( ) ;
8592
write ( root, " src/z.rs" , b " one\n two\n " ) ;
8593
let d = repo.commit ( " fourth: rename a to z" , & [ ] ) .unwrap ( ) ;
8594
let wc = repo.working_copy ( ) .unwrap ( ) .0 ;
8595
8596
repo.set_user_config ( " revset.mine" , " author(me) & ::main" )
8597
.unwrap ( ) ;
8598
assert! ( repo.set_user_config ( " revset.bad" , " a &" ) .is_err ( ) ) ;
8599
let set = | src : & str | -> Vec< ChangeHash> {
8600
let mut v = repo.revset ( src) .unwrap ( ) .hashes;
8601
v.sort ( ) ;
8602
v
8603
} ;
8604
let sorted = | hs : & [ChangeHash]| -> Vec< ChangeHash> {
8605
let mut v = hs.to_vec ( ) ;
8606
v.sort ( ) ;
8607
v
8608
} ;
8609
8610
8611
8612
8613
let heads = sorted ( & repo.bookmark_heads ( ) .unwrap ( ) ) ;
8614
let meta = | h : & ChangeHash| repo.store ( ) .get_change_meta ( h) .unwrap ( ) ;
8615
assert! ( heads.contains ( & d) & & heads.contains ( & c) ) ;
8616
8617
assert_eq! ( set ( " main" ) , heads) ;
8618
assert_eq! ( set ( " ::main" ) , sorted ( & [ a, b, c, d] ) ) ;
8619
assert_eq! ( set ( " @" ) , vec! [ wc] ) ;
8620
assert_eq! ( set ( " ::@" ) , sorted ( & [ a, b, c, d, wc] ) ) ;
8621
assert_eq! ( set ( & format! ( " ::#{} " , b.short ( 12 ) ) ) , sorted ( & [ a, b] ) ) ;
8622
assert_eq! ( set ( & format! ( " #{} ::" , a.short ( 12 ) ) ) , sorted ( & [ a, b, d, wc] ) ) ;
8623
assert_eq! (
8624
set ( & format! ( " #{} ::#{} " , a.short ( 12 ) , b.short ( 12 ) ) ) ,
8625
sorted ( & [ a, b] )
8626
) ;
8627
8628
assert_eq! ( set ( & format! ( " #{} ..main" , b.short ( 12 ) ) ) , sorted ( & [ c, d] ) ) ;
8629
assert_eq! ( set ( & format! ( " #{} .." , c.short ( 12 ) ) ) , sorted ( & [ a, b, d, wc] ) ) ;
8630
assert_eq! ( set ( " ..main" ) , sorted ( & [ a, b, c, d] ) ) ;
8631
8632
assert_eq! (
8633
set ( & format! ( " ::main ~ #{} " , d.short ( 12 ) ) ) ,
8634
sorted ( & [ a, b, c] )
8635
) ;
8636
assert_eq! (
8637
set ( & format! ( " ::main & #{} | #{} " , b.short ( 12 ) , c.short ( 12 ) ) ) ,
8638
sorted ( & [ b, c] )
8639
) ;
8640
assert_eq! ( set ( " all() ~ ::main" ) , vec! [ wc] ) ;
8641
8642
let mut head_deps: Vec< ChangeHash> = heads.iter ( ) .flat_map ( | h | meta ( h) .deps) .collect ( ) ;
8643
head_deps.sort ( ) ;
8644
head_deps.dedup ( ) ;
8645
assert_eq! ( set ( " deps(main)" ) , head_deps) ;
8646
let on_a: Vec< ChangeHash> = [ b, c, d]
8647
.into_iter ( )
8648
.filter ( | h | meta ( h) .deps.contains ( & a) )
8649
.collect ( ) ;
8650
assert_eq! ( set ( & format! ( " dependents(#{} )" , a.short ( 12 ) ) ) , sorted ( & on_a) ) ;
8651
assert_eq! ( set ( " heads(::main)" ) , heads) ;
8652
assert_eq! ( set ( " roots(::main)" ) , sorted ( & [ a, c] ) ) ;
8653
assert_eq! (
8654
set ( & format! (
8655
" heads(#{} | #{} | #{} )" ,
8656
a.short ( 12 ) ,
8657
b.short ( 12 ) ,
8658
c.short ( 12 )
8659
) ) ,
8660
sorted ( & [ b, c] )
8661
) ;
8662
assert_eq! ( set ( " latest(::main, 1)" ) , vec! [ d] ) ;
8663
assert_eq! ( set ( " kind(working-copy)" ) , vec! [ wc] ) ;
8664
assert_eq! ( set ( " none()" ) , vec! [ ] ) ;
8665
8666
let key = repo.config.author_key.unwrap ( ) ;
8667
assert_eq! ( set ( " author(ada) & ::main" ) , sorted ( & [ a, b, c, d] ) ) ;
8668
assert_eq! ( set ( " author(\" [email protected] \" ) & ::main" ) .len ( ) , 4 ) ;
8669
assert_eq! ( set ( & format! ( " author({} ) & ::main" , key.short ( 12 ) ) ) .len ( ) , 4 ) ;
8670
assert_eq! ( set ( " author(me) & ::main" ) .len ( ) , 4 ) ;
8671
assert_eq! ( set ( " author(nobody)" ) , vec! [ ] ) ;
8672
assert_eq! ( set ( " description(notes)" ) , vec! [ c] ) ;
8673
assert_eq! ( set ( " description(\" rename a\" )" ) , vec! [ d] ) ;
8674
assert_eq! ( set ( " date(-1h..) & ::main" ) .len ( ) , 4 ) ;
8675
assert_eq! ( set ( " date(..2000-01-01)" ) , vec! [ ] ) ;
8676
8677
assert_eq! ( set ( " touches(\" src/z.rs\" )" ) , sorted ( & [ a, b, d] ) ) ;
8678
assert_eq! ( set ( " touches(\" src/**\" ) & ::main" ) , sorted ( & [ a, b, d] ) ) ;
8679
assert_eq! ( set ( " touches(\" docs/*\" )" ) , vec! [ c] ) ;
8680
assert_eq! ( set ( " touches(\" nothing/*\" )" ) , vec! [ ] ) ;
8681
8682
assert_eq! ( repo.revset ( " ::main" ) .unwrap ( ) .hashes[ 0 ] , d) ;
8683
8684
assert_eq! ( set ( " mine()" ) .len ( ) , 4 ) ;
8685
let e = repo.revset ( " nosuch()" ) .unwrap_err ( ) .to_string ( ) ;
8686
assert! ( e.contains ( " unknown function nosuch()" ) , " {e}" ) ;
8687
let e = repo.revset ( " main &" ) .unwrap_err ( ) .to_string ( ) ;
8688
assert! ( e.contains ( " at character" ) , " {e}" ) ;
8689
let e = repo.revset ( " zzzz" ) .unwrap_err ( ) .to_string ( ) ;
8690
assert! ( e.contains ( " no change matches" ) , " {e}" ) ;
8691
let e = repo
8692
.revset_with_budget ( " ::main" , std:: time:: Duration:: ZERO )
8693
.unwrap_err ( )
8694
.to_string ( ) ;
8695
assert! ( e.contains ( " budget" ) , " {e}" ) ;
8696
8697
let entries = repo.log_revset ( " ::@" ) .unwrap ( ) ;
8698
assert! ( entries[ 0 ] .is_working_copy & & entries.iter ( ) .any ( | e | e.is_head & & e.hash = = d ) ) ;
2c9ddac3 Andy McCall 1 month ago ↶
8699
8700
8701
assert_eq! ( set ( " unsigned() & ::main" ) , sorted ( & [ a, b, c, d] ) ) ;
8702
assert_eq! ( set ( " signed_by(me)" ) , vec! [ ] ) ;
8703
repo.sign_change ( & d) .unwrap ( ) ;
8704
assert_eq! ( set ( " signed_by(me)" ) , vec! [ d] ) ;
8705
assert_eq! ( set ( " signed_by([email protected] )" ) , vec! [ d] ) ;
8706
assert_eq! ( set ( & format! ( " signed_by({} )" , key.short ( 12 ) ) ) , vec! [ d] ) ;
8707
assert_eq! ( set ( " unsigned() & ::main" ) , sorted ( & [ a, b, c] ) ) ;
8708
assert_eq! ( set ( " delegated()" ) , vec! [ ] ) ;
8709
assert_eq! ( set ( " revoked()" ) , vec! [ ] ) ;
8710
assert_eq! ( set ( " attested(tag)" ) , vec! [ ] ) ;
8711
repo.tag_create ( " v1" , & [ b] , " first" ) .unwrap ( ) ;
8712
assert_eq! ( set ( " attested(tag)" ) , sorted ( & [ a, b] ) ) ;
8713
assert_eq! ( set ( " attested(tag) & unsigned()" ) , sorted ( & [ a, b] ) ) ;
8714
assert_eq! ( set ( " attested(receipt)" ) , vec! [ ] ) ;
8715
assert_eq! ( set ( " built(test)" ) , vec! [ ] ) ;
8716
let e = repo.revset ( " attested(seal)" ) .unwrap_err ( ) .to_string ( ) ;
8717
assert! ( e.contains ( " tag, receipt or ref" ) , " {e}" ) ;
8718
repo.revoke_identity ( & key, " test" ) .unwrap ( ) ;
8719
assert_eq! ( set ( " revoked()" ) , vec! [ d] ) ;
8720
assert_eq! ( set ( " signed_by(me) ~ revoked()" ) , vec! [ ] ) ;
47ce8b8e Andy McCall 1 month ago ↶
8721
}
614f55e1 Andy McCall 1 month ago ↶
8722
8723
# [ test ]
8724
fn a_rotation_is_recorded_wherever_the_old_key_was_named ( ) {
8725
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8726
let root = dir.path ( ) ;
8727
let home = root.join ( " home" ) ;
8728
8729
8730
let mut a = Repo:: init( & root.join ( " a" ) ) .unwrap ( ) .with_home ( & home) ;
8731
let ( key, _ ) = a
8732
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
8733
.unwrap ( ) ;
8734
a.config.set ( " author.key" , & key.to_hex ( ) ) .unwrap ( ) ;
8735
a.save_config ( ) .unwrap ( ) ;
8736
let agent = SecretKey:: generate( ) .identity ( " Agent" , " [email protected] " , 1 ) ;
8737
let r = a
8738
.delegate_add ( agent.clone ( ) , crate :: identity:: Scope:: CHANGES , None )
8739
.unwrap ( ) ;
8740
assert_eq! (
8741
read_config_file ( & home.join ( " config.toml" ) ) .unwrap ( ) [ " author.key" ] ,
8742
r.new.to_hex ( ) ,
8743
" the user file follows"
8744
) ;
8745
assert_eq! (
8746
read_config_file ( & root.join ( " a/.oxy/config" ) ) .unwrap ( ) [ " author.key" ] ,
8747
r.new.to_hex ( ) ,
8748
" the pinning repository follows"
8749
) ;
8750
let b = Repo:: init( & root.join ( " b" ) ) .unwrap ( ) .with_home ( & home) ;
8751
assert_eq! (
8752
b.config.author_key,
8753
Some ( r.new) ,
8754
" a checkout without a pin follows the user file"
8755
) ;
8756
8757
let other_home = root.join ( " home2" ) ;
8758
let mut c = Repo:: init( & root.join ( " c" ) ) .unwrap ( ) .with_home ( & other_home) ;
8759
let ( ck, _ ) = c
8760
.create_identity ( " Cy" , " [email protected] " , IdentityScope:: Repo)
8761
.unwrap ( ) ;
8762
assert! ( ! other_home.join ( " config.toml" ) .exists ( ) ) ;
8763
let r2 = c.rotate_identity ( ) .unwrap ( ) ;
8764
assert_ne! ( r2.new, ck) ;
8765
assert_eq! (
8766
read_config_file ( & root.join ( " c/.oxy/config" ) ) .unwrap ( ) [ " author.key" ] ,
8767
r2.new.to_hex ( )
8768
) ;
8769
assert! (
8770
! other_home.join ( " config.toml" ) .exists ( ) ,
8771
" a repository-scoped identity stays repository-scoped"
8772
) ;
8773
8774
let ( d1, d2) = (
8775
SecretKey:: generate( ) .identity ( " D1" , " d1@x" , 1 ) ,
8776
SecretKey:: generate( ) .identity ( " D2" , " d2@x" , 1 ) ,
8777
) ;
8778
let r3 = c
8779
.delegate_add_many ( vec! [ d1.clone ( ) , d2] , crate :: identity:: Scope:: CHANGES , None )
8780
.unwrap ( ) ;
8781
assert_eq! ( r3.identity.delegates.len ( ) , 2 ) ;
8782
assert! ( c
8783
.delegate_add ( d1, crate :: identity:: Scope:: CHANGES , None )
8784
.is_err ( ) ) ;
8785
}
33be2409 Andy McCall 1 month ago ↶
8786
8787
8788
8789
8790
8791
# [ test ]
8792
fn git_import_joins_unrelated_histories_that_add_the_same_file ( ) {
8793
8794
8795
crate :: repo:: git:: set_merge_batch_for_tests( Some ( 1 ) ) ;
8796
hermetic ( ) ;
8797
if std:: process:: Command:: new( " git" )
8798
.arg ( " --version" )
8799
.output ( )
8800
.is_err ( )
8801
{
8802
eprintln! ( " git not installed; skipping" ) ;
8803
return ;
8804
}
8805
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8806
let root = dir.path ( ) ;
8807
let g = root.join ( " src.git" ) ;
8808
fs:: create_dir_all( & g) .unwrap ( ) ;
8809
let git = | args : & [& str ]| -> String {
8810
let out = std:: process:: Command:: new( " git" )
8811
.arg ( " -C" )
8812
.arg ( & g)
8813
.args ( [
8814
" -c" ,
8815
" user.name=Grace" ,
8816
" -c" ,
8817
" [email protected] " ,
8818
" -c" ,
8819
" commit.gpgsign=false" ,
8820
] )
8821
.args ( args)
8822
.output ( )
8823
.unwrap ( ) ;
8824
assert! (
8825
out.status.success ( ) ,
8826
" git {args:?}: {}" ,
8827
String :: from_utf8_lossy( & out.stderr)
8828
) ;
8829
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
8830
} ;
8831
let text = b " /* header */\n const a = 1;\n const b = 2;\n " ;
8832
8833
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
8834
write ( & g, " gulpfile.js" , text) ;
8835
write ( & g, " a.txt" , b " a\n " ) ;
8836
git ( & [ " add" , " ." ] ) ;
8837
git ( & [ " commit" , " -q" , " -m" , " A root" ] ) ;
8838
8839
git ( & [ " checkout" , " -q" , " --orphan" , " fork" ] ) ;
8840
git ( & [ " rm" , " -q" , " -r" , " --cached" , " ." ] ) ;
8841
for f in [ " gulpfile.js" , " a.txt" ] {
8842
let _ = fs:: remove_file( g.join ( f) ) ;
8843
}
8844
write ( & g, " gulpfile.js" , text) ;
8845
write ( & g, " b.txt" , b " b\n " ) ;
8846
git ( & [ " add" , " ." ] ) ;
8847
git ( & [ " commit" , " -q" , " -m" , " B root" ] ) ;
8848
8849
git ( & [ " checkout" , " -q" , " -b" , " one" , " main" ] ) ;
8850
git ( & [
8851
" merge" ,
8852
" -q" ,
8853
" --no-ff" ,
8854
" --allow-unrelated-histories" ,
8855
" -m" ,
8856
" join on one" ,
8857
" fork" ,
8858
] ) ;
8859
git ( & [ " checkout" , " -q" , " -b" , " two" , " main" ] ) ;
8860
write ( & g, " a.txt" , b " a\n two\n " ) ;
8861
git ( & [ " commit" , " -q" , " -am" , " work on two" ] ) ;
8862
git ( & [
8863
" merge" ,
8864
" -q" ,
8865
" --no-ff" ,
8866
" --allow-unrelated-histories" ,
8867
" -m" ,
8868
" join on two" ,
8869
" fork" ,
8870
] ) ;
8871
8872
git ( & [ " checkout" , " -q" , " main" ] ) ;
8873
git ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge one" , " one" ] ) ;
8874
git ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge two" , " two" ] ) ;
8875
write (
8876
& g,
8877
" gulpfile.js" ,
8878
b " /* header */\n const a = 1;\n const b = 3;\n " ,
8879
) ;
8880
git ( & [ " commit" , " -q" , " -am" , " after" ] ) ;
8881
8882
let dest = root.join ( " dest" ) ;
8883
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8884
repo.import_git ( & g, " main" ) .unwrap ( ) ;
8885
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
8886
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
8887
assert_eq! (
8888
fs:: read( dest.join ( " gulpfile.js" ) ) .unwrap ( ) ,
8889
b " /* header */\n const a = 1;\n const b = 3;\n "
8890
) ;
8891
}
8892
8893
8894
8895
8896
# [ test ]
8897
fn git_import_joins_where_each_join_kept_a_different_copy ( ) {
8898
8899
8900
crate :: repo:: git:: set_merge_batch_for_tests( Some ( 1 ) ) ;
8901
hermetic ( ) ;
8902
if std:: process:: Command:: new( " git" )
8903
.arg ( " --version" )
8904
.output ( )
8905
.is_err ( )
8906
{
8907
eprintln! ( " git not installed; skipping" ) ;
8908
return ;
8909
}
8910
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8911
let root = dir.path ( ) ;
8912
let g = root.join ( " src.git" ) ;
8913
fs:: create_dir_all( & g) .unwrap ( ) ;
8914
let run = | args : & [& str ]| {
8915
std:: process:: Command:: new( " git" )
8916
.arg ( " -C" )
8917
.arg ( & g)
8918
.args ( [
8919
" -c" ,
8920
" user.name=Grace" ,
8921
" -c" ,
8922
" [email protected] " ,
8923
" -c" ,
8924
" commit.gpgsign=false" ,
8925
] )
8926
.args ( args)
8927
.output ( )
8928
.unwrap ( )
8929
} ;
8930
let git = | args : & [& str ]| -> String {
8931
let out = run ( args) ;
8932
assert! (
8933
out.status.success ( ) ,
8934
" git {args:?}: {}" ,
8935
String :: from_utf8_lossy( & out.stderr)
8936
) ;
8937
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
8938
} ;
8939
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
8940
write (
8941
& g,
8942
" gulpfile.js" ,
8943
b " /* header */\n const a = 1;\n const b = 2;\n " ,
8944
) ;
8945
git ( & [ " add" , " ." ] ) ;
8946
git ( & [ " commit" , " -q" , " -m" , " A root" ] ) ;
8947
write (
8948
& g,
8949
" gulpfile.js" ,
8950
b " /* header */\n const a = 1;\n \n const b = 2;\n " ,
8951
) ;
8952
git ( & [ " commit" , " -q" , " -am" , " whitespace" ] ) ;
8953
git ( & [ " checkout" , " -q" , " --orphan" , " fork" ] ) ;
8954
git ( & [ " rm" , " -q" , " -r" , " --cached" , " ." ] ) ;
8955
let _ = fs:: remove_file( g.join ( " gulpfile.js" ) ) ;
8956
write (
8957
& g,
8958
" gulpfile.js" ,
8959
b " /* header */\n const a = 1;\n const b = 2;\n const c = 3;\n " ,
8960
) ;
8961
git ( & [ " add" , " ." ] ) ;
8962
git ( & [ " commit" , " -q" , " -m" , " B root" ] ) ;
8963
8964
for ( branch, keep) in [ ( " one" , " ours" ) , ( " two" , " theirs" ) ] {
8965
git ( & [ " checkout" , " -q" , " -b" , branch, " main" ] ) ;
8966
let _ = run ( & [
8967
" merge" ,
8968
" -q" ,
8969
" --no-ff" ,
8970
" --allow-unrelated-histories" ,
8971
" -m" ,
8972
" join" ,
8973
" fork" ,
8974
] ) ;
8975
git ( & [ " checkout" , & format! ( " --{keep} " ) , " gulpfile.js" ] ) ;
8976
git ( & [ " add" , " gulpfile.js" ] ) ;
8977
git ( & [ " commit" , " -q" , " -m" , & format! ( " join on {branch} " ) ] ) ;
8978
}
8979
8980
git ( & [ " checkout" , " -q" , " one" ] ) ;
8981
write (
8982
& g,
8983
" gulpfile.js" ,
8984
b " /* header */\n const a = 1;\n \n const b = 2;\n // tslint\n " ,
8985
) ;
8986
git ( & [ " commit" , " -q" , " -am" , " tslint on one" ] ) ;
8987
git ( & [ " checkout" , " -q" , " two" ] ) ;
8988
write (
8989
& g,
8990
" gulpfile.js" ,
8991
b " /* header */\n const a = 1;\n const b = 2;\n const c = 3;\n // fmt\n " ,
8992
) ;
8993
write ( & g, " extra.txt" , b " x\n " ) ;
8994
write ( & g, " more.txt" , b " m\n " ) ;
8995
git ( & [ " add" , " ." ] ) ;
8996
git ( & [ " commit" , " -q" , " -am" , " fmt on two" ] ) ;
8997
8998
git ( & [ " checkout" , " -q" , " one" ] ) ;
8999
let _ = run ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge two" , " two" ] ) ;
9000
write (
9001
& g,
9002
" gulpfile.js" ,
9003
b " /* header */\n const a = 1;\n const b = 2;\n const c = 3;\n // tslint\n // fmt\n " ,
9004
) ;
9005
git ( & [ " add" , " gulpfile.js" ] ) ;
9006
git ( & [ " commit" , " -q" , " -m" , " merge two into one" ] ) ;
9007
9008
let dest = root.join ( " dest" ) ;
9009
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9010
repo.import_git ( & g, " one" ) .unwrap ( ) ;
9011
let v = repo.verify_git_import ( & g, " one" ) .unwrap ( ) ;
9012
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
9013
}
dd21fd82 Andy McCall 1 month ago ↶
9014
9015
9016
9017
9018
9019
9020
# [ test ]
9021
fn over_a_store_the_operation_head_moves_by_compare_and_swap ( ) {
9022
use crate :: strata:: { MemoryStore, Store as _ } ;
9023
let backend = std:: sync:: Arc:: new( MemoryStore:: new( ) ) ;
9024
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = Some ( backend.clone ( ) ) ) ;
9025
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9026
let a = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9027
assert! (
9028
! dir.path ( ) .join ( " .oxy/ops/head" ) .exists ( ) ,
9029
" the head is not a file"
9030
) ;
9031
let ( h0, op0) = a.head_op ( ) .unwrap ( ) ;
9032
let record = backend
9033
.read_ref ( HEAD_RECORD )
9034
.unwrap ( )
9035
.expect ( " the head is the store's record" ) ;
9036
assert_eq! ( String :: from_utf8( record.0 ) .unwrap ( ) , h0.to_hex ( ) ) ;
9037
9038
let b = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
9039
let state = a.store.put_state ( & StateObject:: new( vec! [ ] ) ) .unwrap ( ) ;
9040
let mut va = op0.view.clone ( ) ;
9041
va.bookmarks.insert ( " from-a" .into ( ) , state) ;
9042
let mut vb = op0.view.clone ( ) ;
9043
vb.bookmarks.insert ( " from-b" .into ( ) , state) ;
9044
let rb = b.record_op ( & [ h0] , " bookmark from-b" , vb) .unwrap ( ) ;
9045
assert_eq! ( b.head_ref ( ) .unwrap ( ) , rb) ;
9046
9047
let ra = a.record_op ( & [ h0] , " bookmark from-a" , va) .unwrap ( ) ;
9048
let ( head, op) = Repo:: open( dir.path ( ) ) .unwrap ( ) .head_op ( ) .unwrap ( ) ;
9049
assert_eq! ( head, ra) ;
9050
assert! (
9051
op.command.ends_with ( " (merged with a concurrent operation)" ) ,
9052
" {}" ,
9053
op.command
9054
) ;
9055
assert_eq! ( op.parents, vec! [ rb, op.parents[ 1 ] ] ) ;
9056
assert! ( op.view.bookmarks.contains_key ( " from-a" ) & & op.view.bookmarks.contains_key ( " from-b" ) ) ;
9057
9058
9059
let ( h, v) = a.read_head ( ) .unwrap ( ) ;
9060
assert! ( b.swap_head ( & rb, v.as_ref ( ) ) .unwrap ( ) ) ;
9061
assert! ( ! a.swap_head ( & h, v.as_ref ( ) ) .unwrap ( ) , " stale" ) ;
767ac6cd Andy McCall 19 days ago
9062
a.refresh ( ) .unwrap ( ) ;
dd21fd82 Andy McCall 1 month ago ↶
9063
assert_eq! ( a.head_ref ( ) .unwrap ( ) , rb) ;
9064
9065
let other = tempfile:: tempdir( ) .unwrap ( ) ;
9066
let err = Repo:: init( other.path ( ) )
9067
.err ( )
9068
.expect ( " the store already holds a log" )
9069
.to_string ( ) ;
9070
assert! (
9071
err.contains ( " already holds a repository's operation log" ) ,
9072
" {err}"
9073
) ;
9074
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = None ) ;
9075
}
d069701a Andy McCall 1 month ago ↶
9076
9077
9078
9079
9080
# [ test ]
9081
fn a_repository_runs_on_a_store_with_a_cache_in_front ( ) {
9082
hermetic ( ) ;
9083
if std:: env:: var_os( " OXY_STORE_BACKEND" ) .is_some ( ) {
9084
return ;
9085
}
9086
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9087
let ( root, bucket) = ( dir.path ( ) .join ( " work" ) , dir.path ( ) .join ( " bucket" ) ) ;
9088
let url = format! ( " dir://{} " , bucket.display ( ) ) ;
9089
let repo = Repo:: init_on( & root, [ 7 ; 16 ] , Some ( & url) ) .unwrap ( ) ;
9090
assert_eq! ( repo.config.storage_url.as_deref ( ) , Some ( url.as_str ( ) ) ) ;
9091
write ( & root, " a.txt" , b " one\n " ) ;
9092
let h1 = repo.commit ( " First" , & [ ] ) .unwrap ( ) ;
9093
write ( & root, " a.txt" , b " one\n two\n " ) ;
9094
let h2 = repo.commit ( " Second" , & [ ] ) .unwrap ( ) ;
9095
assert! (
9096
! root.join ( " .oxy/ops/head" ) .exists ( ) ,
9097
" the head is in the store"
9098
) ;
9099
assert! ( bucket.join ( " ops/head" ) .is_file ( ) ) ;
9100
assert! ( root.join ( " .oxy/cache" ) .is_dir ( ) ) ;
9101
drop ( repo) ;
9102
9103
9104
for drop_cache in [ false , true ] {
9105
if drop_cache {
9106
std:: fs:: remove_dir_all( root.join ( " .oxy/cache" ) ) .unwrap ( ) ;
9107
}
9108
let repo = Repo:: open( & root) .unwrap ( ) ;
9109
let log: Vec< String> = repo
9110
.op_log ( )
9111
.unwrap ( )
9112
.into_iter ( )
9113
.map ( | ( _, op ) | op.command )
9114
.collect ( ) ;
9115
assert! ( log.len ( ) > = 3 , " {log:?}" ) ;
9116
assert_eq! ( repo.store ( ) .get_change ( & h1) .unwrap ( ) .message, " First" ) ;
9117
let heads = repo
9118
.store ( )
9119
.get_state ( & repo.view ( ) .unwrap ( ) .bookmarks[ " main" ] )
9120
.unwrap ( )
9121
.heads;
9122
assert_eq! ( heads, vec! [ h2] ) ;
9123
assert_eq! (
9124
repo.load_state ( & heads) .unwrap ( ) .materialize ( ) .files.len ( ) ,
9125
1
9126
) ;
9127
}
9128
9129
9130
let err = Repo:: init_on( & dir.path ( ) .join ( " other" ) , [ 8 ; 16 ] , Some ( & url) )
9131
.err ( )
9132
.unwrap ( )
9133
.to_string ( ) ;
9134
assert! (
9135
err.contains ( " already holds a repository's operation log" ) ,
9136
" {err}"
9137
) ;
9138
let err = Repo:: init_on( & dir.path ( ) .join ( " third" ) , [ 9 ; 16 ] , Some ( " nope://x" ) )
9139
.err ( )
9140
.unwrap ( )
9141
.to_string ( ) ;
9142
assert! ( err.contains ( " cannot open that kind of store" ) , " {err}" ) ;
9143
}
80677b7a Andy McCall 1 month ago ↶
9144
9145
9146
9147
9148
9149
# [ test ]
9150
fn the_quick_dirty_check_compares_a_subrepo_by_its_pin ( ) {
9151
hermetic ( ) ;
9152
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9153
let root = dir.path ( ) ;
9154
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9155
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9156
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9157
.unwrap ( )
9158
.with_home ( root.join ( " home" ) ) ;
9159
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9160
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9161
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9162
let app_dir = root.join ( " app" ) ;
9163
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9164
write ( & app_dir, " main.txt" , b " app\n " ) ;
9165
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9166
app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
9167
9168
std:: thread:: sleep( std:: time:: Duration:: from_millis( 2100 ) ) ;
9169
let reopen = | | Repo:: open( & app_dir) .unwrap ( ) ;
9170
assert_eq! (
9171
reopen ( ) .quick_dirty ( ) .unwrap ( ) ,
9172
Some ( false ) ,
9173
" clean, as status says"
9174
) ;
9175
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) ) .unwrap ( ) ;
9176
write ( & app_dir.join ( " libs/lib" ) , " lib.txt" , b " v2\n " ) ;
9177
nested.commit ( " lib v2" , & [ ] ) .unwrap ( ) ;
9178
assert_eq! (
9179
reopen ( ) .quick_dirty ( ) .unwrap ( ) ,
9180
Some ( true ) ,
9181
" the subrepo moved"
9182
) ;
9183
reopen ( ) .commit ( " pin lib v2" , & [ ] ) .unwrap ( ) ;
9184
std:: thread:: sleep( std:: time:: Duration:: from_millis( 2100 ) ) ;
9185
assert_eq! (
9186
reopen ( ) .quick_dirty ( ) .unwrap ( ) ,
9187
Some ( false ) ,
9188
" the new pin is committed"
9189
) ;
9190
}
7993d465 Andy McCall 1 month ago ↶
9191
9192
# [ test ]
9193
fn a_module_entry_that_was_never_added_is_reported_as_unpinned ( ) {
9194
9195
hermetic ( ) ;
9196
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9197
let root = dir.path ( ) ;
9198
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9199
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9200
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9201
.unwrap ( )
9202
.with_home ( root.join ( " home" ) ) ;
9203
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9204
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9205
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9206
let app_dir = root.join ( " app" ) ;
9207
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9208
write (
9209
& app_dir,
9210
" .oxymodules" ,
9211
format! ( " [libs/lib]\n url = {lib_url} \n " ) .as_bytes ( ) ,
9212
) ;
9213
let unpinned = app.unpinned_modules ( ) .unwrap ( ) ;
9214
assert_eq! ( unpinned.len ( ) , 1 ) ;
9215
assert_eq! ( unpinned[ 0 ] .0 , " libs/lib" ) ;
9216
assert_eq! ( unpinned[ 0 ] .1. url, lib_url) ;
9217
assert! ( app.submodule_update ( true , & [ ] ) .unwrap ( ) .is_empty ( ) ) ;
9218
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9219
assert! ( app.unpinned_modules ( ) .unwrap ( ) .is_empty ( ) , " added now" ) ;
9220
}
bf3a6781 Andy McCall 1 month ago ↶
9221
9222
# [ test ]
9223
fn the_bookmark_revset_function_names_only_bookmarks ( ) {
9224
9225
hermetic ( ) ;
9226
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9227
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9228
write ( dir.path ( ) , " a.txt" , b " a\n " ) ;
9229
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
9230
write ( dir.path ( ) , " b.txt" , b " b\n " ) ;
9231
let b = repo.commit ( " B" , & [ ] ) .unwrap ( ) ;
9232
let mut heads = repo.revset ( " bookmark(main)" ) .unwrap ( ) .hashes;
9233
heads.sort ( ) ;
9234
let mut want = repo.bookmark_heads ( ) .unwrap ( ) ;
9235
want.sort ( ) ;
9236
assert_eq! ( heads, want) ;
9237
assert! ( heads.contains ( & b) ) ;
9238
assert_eq! ( repo.revset ( " main" ) .unwrap ( ) .hashes.len ( ) , heads.len ( ) ) ;
9239
9240
let prefix = repo.store ( ) .get_change_meta ( & a) .unwrap ( ) .change_id.short ( 8 ) ;
9241
assert! ( repo.revset ( & prefix) .is_ok ( ) ) ;
9242
let e = repo
9243
.revset ( & format! ( " bookmark({prefix} )" ) )
9244
.unwrap_err ( )
9245
.to_string ( ) ;
9246
assert! ( e.contains ( " no such bookmark" ) , " {e}" ) ;
9247
}
72484195 Andy McCall 1 month ago ↶
9248
157d02e9 Andy McCall 12 days ago ↶
9249
9250
9251
72484195 Andy McCall 1 month ago ↶
9252
# [ test ]
157d02e9 Andy McCall 12 days ago ↶
9253
fn a_replace_conflict_is_settled_from_the_working_copy ( ) {
9254
hermetic ( ) ;
9255
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9256
let root = dir.path ( ) ;
9257
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9258
let blob = | n : u8 | {
9259
let mut v = vec! [ 0 u8 ; 64 ] ;
9260
v[ 1 ] = n;
9261
v
9262
} ;
9263
write ( root, " img.bin" , & blob ( 1 ) ) ;
9264
write ( root, " a.txt" , b " a\n " ) ;
9265
repo.commit ( " add img" , & [ ] ) .unwrap ( ) ;
9266
repo.bookmark_create ( " other" , None ) .unwrap ( ) ;
9267
write ( root, " img.bin" , & blob ( 2 ) ) ;
9268
repo.commit ( " img two" , & [ ] ) .unwrap ( ) ;
9269
repo.bookmark_switch ( " other" , false ) .unwrap ( ) ;
9270
write ( root, " img.bin" , & blob ( 3 ) ) ;
9271
repo.commit ( " img three" , & [ ] ) .unwrap ( ) ;
9272
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
9273
repo.merge ( " other" ) .unwrap ( ) ;
9274
9275
9276
write ( root, " img.bin" , & blob ( 9 ) ) ;
9277
write ( root, " a.txt" , b " a\n more\n " ) ;
9278
assert! (
9279
repo.commit ( " edit a" , & [ ] ) .is_err ( ) ,
9280
" refused while conflicted"
9281
) ;
9282
let snap = repo.snapshot ( " oxy resolve" ) .unwrap ( ) ;
9283
assert! (
9284
repo.choose_resolutions ( & snap.base_snapshot, & [ " a.txt" .to_string ( ) ] )
9285
.is_err ( ) ,
9286
" a.txt has no replace conflict"
9287
) ;
9288
repo.choose_resolutions ( & snap.base_snapshot, & [ " img.bin" .to_string ( ) ] )
9289
.unwrap ( ) ;
9290
let snap = repo.snapshot ( " oxy resolve" ) .unwrap ( ) ;
9291
assert! ( snap
9292
.statuses
9293
.iter ( )
9294
.any ( | s | s.path = = " img.bin" & & s.status = = ChangeStatus:: Resolved ) ) ;
9295
let c = repo.commit ( " settle img" , & [ ] ) .unwrap ( ) ;
9296
assert_eq! (
9297
repo.store ( ) .get_change ( & c) .unwrap ( ) .kind,
9298
ChangeKind:: Resolution
9299
) ;
9300
let after = repo
9301
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
9302
.unwrap ( )
9303
.materialize ( ) ;
9304
assert! ( ! after.is_conflicted ( ) ) ;
9305
assert_eq! (
9306
after.files[ " img.bin" ] .content,
9307
FileContent:: Opaque {
9308
blobs: vec! [ Repo:: blob_ref( & blob ( 9 ) ) ]
9309
}
9310
) ;
9311
assert! (
9312
matches! ( & after.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n more\n " )
9313
) ;
9314
9315
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
9316
assert! ( snap.statuses.is_empty ( ) , " {:?}" , snap.statuses) ;
9317
assert! ( repo.resolutions ( ) .unwrap ( ) .is_empty ( ) ) ;
9318
}
9319
9320
9321
# [ test ]
9322
fn a_pin_conflict_is_settled_from_the_working_copy ( ) {
9323
hermetic ( ) ;
9324
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9325
let root = dir.path ( ) ;
9326
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9327
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9328
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9329
.unwrap ( )
9330
.with_home ( root.join ( " home" ) ) ;
9331
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9332
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9333
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9334
let app_dir = root.join ( " app" ) ;
9335
let mut app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9336
write ( & app_dir, " main.txt" , b " app\n " ) ;
9337
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9338
app.commit ( " pin v1" , & [ ] ) .unwrap ( ) ;
9339
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) )
9340
.unwrap ( )
9341
.with_home ( root.join ( " home" ) ) ;
9342
fn pin ( app : & Repo, nested : & Repo, text : & [u8 ], message : & str ) {
9343
write ( nested.root ( ) , " lib.txt" , text) ;
9344
nested.commit ( message, & [ ] ) .unwrap ( ) ;
9345
app.commit ( & format! ( " pin {message} " ) , & [ ] ) .unwrap ( ) ;
9346
}
9347
app.bookmark_create ( " other" , None ) .unwrap ( ) ;
9348
pin ( & app, & nested, b " v2\n " , " v2" ) ;
9349
app.bookmark_switch ( " other" , false ) .unwrap ( ) ;
9350
pin ( & app, & nested, b " v3\n " , " v3" ) ;
9351
app.bookmark_switch ( " main" , false ) .unwrap ( ) ;
9352
app.merge ( " other" ) .unwrap ( ) ;
9353
let merged = app
9354
.load_state ( & app.bookmark_heads ( ) .unwrap ( ) )
9355
.unwrap ( )
9356
.materialize ( ) ;
9357
assert! (
9358
matches! ( & merged.files[ " libs/lib" ] .content, FileContent:: Opaque { blobs } if blobs.len ( ) = = 2 ) ,
9359
" a pin conflict"
9360
) ;
9361
9362
write ( & app_dir.join ( " libs/lib" ) , " lib.txt" , b " v4\n " ) ;
9363
nested.commit ( " v4" , & [ ] ) .unwrap ( ) ;
9364
let snap = app.snapshot ( " oxy resolve" ) .unwrap ( ) ;
9365
app.choose_resolutions ( & snap.base_snapshot, & [ " libs/lib" .to_string ( ) ] )
9366
.unwrap ( ) ;
9367
let c = app.commit ( " settle the pin" , & [ ] ) .unwrap ( ) ;
9368
assert_eq! (
9369
app.store ( ) .get_change ( & c) .unwrap ( ) .kind,
9370
ChangeKind:: Resolution
9371
) ;
9372
let after = app
9373
.load_state ( & app.bookmark_heads ( ) .unwrap ( ) )
9374
.unwrap ( )
9375
.materialize ( ) ;
9376
assert! ( ! after.is_conflicted ( ) ) ;
9377
assert! ( app.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
9378
}
9379
9380
# [ test ]
72484195 Andy McCall 1 month ago ↶
9381
fn carrying_work_over_a_subrepo_already_at_the_target_pin_is_no_conflict ( ) {
9382
9383
9384
hermetic ( ) ;
9385
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9386
let root = dir.path ( ) ;
9387
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9388
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9389
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9390
.unwrap ( )
9391
.with_home ( root.join ( " home" ) ) ;
9392
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9393
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9394
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9395
let app_dir = root.join ( " app" ) ;
9396
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9397
write ( & app_dir, " main.txt" , b " app\n " ) ;
9398
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9399
9400
fs:: rename( app_dir.join ( " libs/lib" ) , root.join ( " aside" ) ) .unwrap ( ) ;
9401
app.commit ( " initial" , & [ ] ) .unwrap ( ) ;
9402
let stale = app.bookmark_heads ( ) .unwrap ( ) ;
9403
fs:: rename( root.join ( " aside" ) , app_dir.join ( " libs/lib" ) ) .unwrap ( ) ;
9404
app.commit ( " pin lib" , & [ ] ) .unwrap ( ) ;
9405
let newer = app.bookmark_heads ( ) .unwrap ( ) ;
9406
app.checkout ( & stale, false , " test" ) .unwrap ( ) ;
9407
write ( & app_dir, " notes.txt" , b " mine\n " ) ;
9408
let ( _ , carried) = app.checkout_carry ( & newer, " main" , " test" ) .unwrap ( ) ;
9409
assert_eq! ( carried, vec! [ " notes.txt" .to_string ( ) ] ) ;
9410
assert! ( app_dir.join ( " notes.txt" ) .exists ( ) ) ;
9411
}
9412
c8d9059e Andy McCall 12 days ago ↶
9413
9414
72484195 Andy McCall 1 month ago ↶
9415
# [ test ]
c8d9059e Andy McCall 12 days ago ↶
9416
fn a_new_bookmark_sends_only_what_the_remote_lacks ( ) {
9417
hermetic ( ) ;
9418
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9419
let root = dir.path ( ) ;
9420
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
9421
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
9422
let mut one = Repo:: clone( & url, & root.join ( " one" ) )
9423
.unwrap ( )
9424
.with_home ( root.join ( " home" ) ) ;
9425
for n in 0 .. 5 {
9426
write ( & root.join ( " one" ) , " a.txt" , format! ( " {n} \n " ) .as_bytes ( ) ) ;
9427
one.commit ( & format! ( " main {n} " ) , & [ ] ) .unwrap ( ) ;
9428
}
9429
assert! ( one.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9430
one.bookmark_create ( " feature" , None ) .unwrap ( ) ;
9431
one.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
9432
write ( & root.join ( " one" ) , " b.txt" , b " feature\n " ) ;
9433
let added = one.commit ( " feature" , & [ ] ) .unwrap ( ) ;
9434
let heads = one.bookmark_heads ( ) .unwrap ( ) ;
9435
let refs = one
9436
.open_named_remote ( " origin" )
9437
.unwrap ( )
9438
.refs ( )
9439
.unwrap ( )
9440
.refs;
9441
assert! (
9442
refs.iter ( ) .all ( | r | r.name ! = " feature" ) ,
9443
" new to the remote"
9444
) ;
9445
let sent = one
9446
.negotiate ( & one.remote_holds ( & refs, & [ ] ) , & heads)
9447
.unwrap ( ) ;
9448
assert_eq! ( sent, vec! [ added] , " only the change main lacks" ) ;
9449
assert! ( one.push_bookmark ( " origin" , " feature" , false ) .unwrap ( ) [ 0 ] .ok) ;
9450
let two = Repo:: clone( & url, & root.join ( " two" ) ) .unwrap ( ) ;
9451
assert! ( two.store ( ) .has ( & added) ) ;
9452
}
9453
9454
# [ test ]
72484195 Andy McCall 1 month ago ↶
9455
fn a_bookmark_only_behind_its_remote_catches_up ( ) {
9456
9457
hermetic ( ) ;
9458
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9459
let root = dir.path ( ) ;
9460
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
9461
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
9462
let one = Repo:: clone( & url, & root.join ( " one" ) )
9463
.unwrap ( )
9464
.with_home ( root.join ( " home" ) ) ;
9465
write ( & root.join ( " one" ) , " a.txt" , b " a\n " ) ;
9466
one.commit ( " A" , & [ ] ) .unwrap ( ) ;
9467
assert! ( one.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9468
let two = Repo:: clone( & url, & root.join ( " two" ) )
9469
.unwrap ( )
9470
.with_home ( root.join ( " home" ) ) ;
9471
write ( & root.join ( " two" ) , " b.txt" , b " b\n " ) ;
9472
two.commit ( " B" , & [ ] ) .unwrap ( ) ;
9473
assert! ( two.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9474
assert_eq! ( one.catch_up_bookmark ( " main" ) .unwrap ( ) , None , " not fetched" ) ;
9475
one.fetch ( " origin" ) .unwrap ( ) ;
9476
assert_eq! ( one.catch_up_bookmark ( " main" ) .unwrap ( ) , Some ( 1 ) ) ;
9477
assert_eq! ( one.bookmarks ( ) .unwrap ( ) [ 0 ] .behind, Some ( 0 ) ) ;
9478
9479
write ( & root.join ( " one" ) , " c.txt" , b " c\n " ) ;
9480
one.commit ( " C" , & [ ] ) .unwrap ( ) ;
9481
write ( & root.join ( " two" ) , " d.txt" , b " d\n " ) ;
9482
two.commit ( " D" , & [ ] ) .unwrap ( ) ;
9483
assert! ( two.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9484
one.fetch ( " origin" ) .unwrap ( ) ;
9485
assert_eq! ( one.catch_up_bookmark ( " main" ) .unwrap ( ) , None ) ;
9486
}
42ccf504 Andy McCall 29 days ago ↶
9487
9488
# [ test ]
9489
fn a_bookmark_is_compared_with_a_remote_that_has_it_when_there_is_no_origin ( ) {
9490
9491
hermetic ( ) ;
9492
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9493
let root = dir.path ( ) ;
9494
let _up = Repo:: init( & root.join ( " up" ) ) .unwrap ( ) ;
9495
let url = root.join ( " up" ) .to_string_lossy ( ) .to_string ( ) ;
9496
let other = Repo:: clone( & url, & root.join ( " other" ) )
9497
.unwrap ( )
9498
.with_home ( root.join ( " home" ) ) ;
9499
write ( & root.join ( " other" ) , " a.txt" , b " a\n " ) ;
9500
other.commit ( " a" , & [ ] ) .unwrap ( ) ;
9501
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9502
let mut one = Repo:: clone( & url, & root.join ( " one" ) )
9503
.unwrap ( )
9504
.with_home ( root.join ( " home" ) ) ;
9505
one.config.set ( " remote.earth.url" , & url) .unwrap ( ) ;
9506
one.config.set ( " remote.gcs.url" , & url) .unwrap ( ) ;
9507
one.config.set ( " remote.origin.url" , " " ) .unwrap ( ) ;
9508
one.save_config ( ) .unwrap ( ) ;
9509
write ( & root.join ( " other" ) , " b.txt" , b " b\n " ) ;
9510
other.commit ( " b" , & [ ] ) .unwrap ( ) ;
9511
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9512
one.fetch ( " earth" ) .unwrap ( ) ;
9513
let b = one
9514
.bookmarks ( )
9515
.unwrap ( )
9516
.into_iter ( )
9517
.find ( | b | b.name = = " main" )
9518
.unwrap ( ) ;
9519
assert_eq! (
9520
( b.remote.as_deref ( ) , b.behind) ,
9521
( Some ( " earth" ) , Some ( 1 ) ) ,
9522
" a stale origin copy is not used"
9523
) ;
9524
assert_eq! (
9525
one.default_remote ( ) ,
9526
" origin" ,
9527
" no default chosen among several"
9528
) ;
9529
let e = one.pull ( & one.default_remote ( ) ) .unwrap_err ( ) .to_string ( ) ;
9530
assert! ( e.contains ( " earth, gcs" ) & & e.contains ( " core.remote" ) , " {e}" ) ;
9531
one.config.set ( " core.remote" , " earth" ) .unwrap ( ) ;
9532
assert_eq! ( one.default_remote ( ) , " earth" ) ;
9533
}
cbe8499a Andy McCall 29 days ago ↶
9534
9535
# [ test ]
9536
fn a_missing_author_identity_says_how_to_fix_it ( ) {
9537
9538
9539
hermetic ( ) ;
9540
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9541
let mut repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9542
let missing = KeyId:: from_hex( & " ab" .repeat ( 32 ) ) .unwrap ( ) ;
9543
repo.config.set ( " author.key" , & missing.to_hex ( ) ) .unwrap ( ) ;
9544
let e = repo.identity ( & missing) .unwrap_err ( ) .to_string ( ) ;
9545
assert! (
9546
e.contains ( " this repository has no identity" ) & & e.contains ( " oxy config author.key" ) ,
9547
" {e}"
9548
) ;
9549
assert! ( e.contains ( " this repository's config" ) , " {e}" ) ;
9550
}
f6dccc45 Andy McCall 28 days ago ↶
9551
9552
# [ test ]
9553
fn a_working_copy_behind_its_bookmark_is_flagged_and_pull_brings_it_up ( ) {
9554
9555
9556
9557
hermetic ( ) ;
9558
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9559
let root = dir.path ( ) ;
9560
let _up = Repo:: init( & root.join ( " up" ) ) .unwrap ( ) ;
9561
let url = root.join ( " up" ) .to_string_lossy ( ) .to_string ( ) ;
9562
let other = Repo:: clone( & url, & root.join ( " other" ) )
9563
.unwrap ( )
9564
.with_home ( root.join ( " home" ) ) ;
9565
write ( & root.join ( " other" ) , " a.txt" , b " a\n " ) ;
9566
other.commit ( " a" , & [ ] ) .unwrap ( ) ;
9567
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9568
let one = Repo:: clone( & url, & root.join ( " one" ) )
9569
.unwrap ( )
9570
.with_home ( root.join ( " home" ) ) ;
9571
write ( & root.join ( " other" ) , " b.txt" , b " b\n " ) ;
9572
other.commit ( " b" , & [ ] ) .unwrap ( ) ;
9573
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9574
one.fetch ( " origin" ) .unwrap ( ) ;
9575
9576
let theirs = one.resolve_heads ( " origin/main" ) .unwrap ( ) ;
9577
one.bookmark_set ( " main" , & theirs) .unwrap ( ) ;
9578
assert! ( ! root.join ( " one/b.txt" ) .exists ( ) ) ;
9579
assert_eq! ( one.working_copy_behind ( ) .unwrap ( ) , Some ( 1 ) ) ;
9580
let r = one.pull ( " origin" ) .unwrap ( ) ;
9581
assert! (
9582
! r.changed & & r.checked_out,
9583
" nothing new, but the working copy caught up"
9584
) ;
9585
assert! ( root.join ( " one/b.txt" ) .exists ( ) ) ;
9586
assert_eq! ( one.working_copy_behind ( ) .unwrap ( ) , None ) ;
9587
}
170bb436 Andy McCall 28 days ago ↶
9588
9589
# [ test ]
9590
fn touched_nodes_cache_answers_as_the_changes_do ( ) {
9591
hermetic ( ) ;
9592
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9593
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9594
write ( dir.path ( ) , " a.txt" , b " one\n " ) ;
9595
write ( dir.path ( ) , " b.txt" , b " x\n " ) ;
9596
repo.snapshot ( " t" ) .unwrap ( ) ;
9597
let c1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
9598
write ( dir.path ( ) , " b.txt" , b " y\n " ) ;
9599
repo.snapshot ( " t" ) .unwrap ( ) ;
9600
repo.commit ( " b only" , & [ ] ) .unwrap ( ) ;
9601
fs:: rename( dir.path ( ) .join ( " a.txt" ) , dir.path ( ) .join ( " c.txt" ) ) .unwrap ( ) ;
9602
write ( dir.path ( ) , " c.txt" , b " one\n two\n " ) ;
9603
repo.snapshot ( " t" ) .unwrap ( ) ;
9604
let c3 = repo.commit ( " rename and edit" , & [ ] ) .unwrap ( ) ;
9605
9606
write ( dir.path ( ) , " c.txt" , b " one\n two\n three\n " ) ;
9607
repo.snapshot ( " t" ) .unwrap ( ) ;
9608
9609
let path = dir.path ( ) .join ( " .oxy/index/touched" ) ;
9610
let hashes = | es : Vec< LogEntry> | es.iter ( ) .map ( | e | e.hash ) .collect:: < Vec< _ > > ( ) ;
9611
let cold = hashes ( repo.log_for_path ( " c.txt" ) .unwrap ( ) ) ;
9612
assert! ( path.exists ( ) , " the first query writes the cache" ) ;
9613
let warm = hashes ( repo.log_for_path ( " c.txt" ) .unwrap ( ) ) ;
9614
assert_eq! ( cold, warm, " the cache answers as the changes do" ) ;
9615
let ( wc, _ ) = repo.working_copy ( ) .unwrap ( ) ;
9616
assert_eq! (
9617
cold,
9618
vec! [ wc, c3, c1] ,
9619
" renames followed, the working copy included"
9620
) ;
9621
9622
9623
let cached = super :: touched:: read_touched_for_test( & fs:: read( & path) .unwrap ( ) ) .unwrap ( ) ;
9624
assert! ( ! cached.contains ( & wc) , " a working-copy change is not cached" ) ;
9625
assert! ( cached.contains ( & c1) & & cached.contains ( & c3) ) ;
9626
9627
9628
let mut bytes = fs:: read( & path) .unwrap ( ) ;
9629
let last = bytes.len ( ) - 40 ;
9630
bytes[ last] ^ = 1 ;
9631
fs:: write( & path, & bytes) .unwrap ( ) ;
9632
assert_eq! ( hashes ( repo.log_for_path ( " c.txt" ) .unwrap ( ) ) , cold) ;
9633
let touches = repo.log_revset ( " touches(\" c.txt\" ) ~ @" ) .unwrap ( ) ;
9634
assert_eq! ( hashes ( touches) , vec! [ c3, c1] ) ;
9635
}
e8e6e0ca Andy McCall 28 days ago ↶
9636
9637
9638
9639
9640
9641
# [ test ]
9642
fn git_import_removes_every_copy_of_a_file_at_one_place ( ) {
9643
hermetic ( ) ;
9644
if std:: process:: Command:: new( " git" )
9645
.arg ( " --version" )
9646
.output ( )
9647
.is_err ( )
9648
{
9649
eprintln! ( " git not installed; skipping" ) ;
9650
return ;
9651
}
9652
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9653
let root = dir.path ( ) ;
9654
let g = root.join ( " src.git" ) ;
9655
fs:: create_dir_all( & g) .unwrap ( ) ;
9656
let git = | args : & [& str ]| {
9657
let out = std:: process:: Command:: new( " git" )
9658
.arg ( " -C" )
9659
.arg ( & g)
9660
.args ( [
9661
" -c" ,
9662
" user.name=Grace" ,
9663
" -c" ,
9664
" [email protected] " ,
9665
" -c" ,
9666
" commit.gpgsign=false" ,
9667
] )
9668
.args ( args)
9669
.output ( )
9670
.unwrap ( ) ;
9671
assert! (
9672
out.status.success ( ) ,
9673
" git {args:?}: {}" ,
9674
String :: from_utf8_lossy( & out.stderr)
9675
) ;
9676
} ;
9677
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
9678
write ( & g, " d/list.txt" , b " one\n two\n three\n four\n " ) ;
9679
write ( & g, " keep.txt" , b " k\n " ) ;
9680
git ( & [ " add" , " ." ] ) ;
9681
git ( & [ " commit" , " -qm" , " base" ] ) ;
9682
fs:: create_dir_all( g.join ( " d/sub" ) ) .unwrap ( ) ;
9683
git ( & [ " mv" , " d/list.txt" , " d/sub/list.txt" ] ) ;
9684
git ( & [ " commit" , " -qm" , " into sub" ] ) ;
9685
git ( & [ " checkout" , " -qb" , " side" ] ) ;
9686
git ( & [ " mv" , " d/sub/list.txt" , " d/list.txt" ] ) ;
9687
git ( & [ " commit" , " -qm" , " back, on side" ] ) ;
9688
git ( & [ " checkout" , " -q" , " main" ] ) ;
9689
git ( & [ " mv" , " d/sub/list.txt" , " d/list.txt" ] ) ;
9690
git ( & [ " commit" , " -qm" , " back, on main" ] ) ;
9691
git ( & [ " merge" , " -q" , " --no-edit" , " side" ] ) ;
9692
git ( & [ " rm" , " -rq" , " d" ] ) ;
9693
git ( & [ " commit" , " -qm" , " d goes" ] ) ;
9694
9695
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
9696
repo.import_git ( & g, " main" ) .unwrap ( ) ;
9697
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
9698
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
9699
let st = repo
9700
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
9701
.unwrap ( ) ;
9702
let view = st.tree ( ) .view ( ) ;
9703
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
9704
assert_eq! ( view.entries.keys ( ) .collect:: < Vec< _ > > ( ) , vec! [ " keep.txt" ] ) ;
9705
}
5552e69c Andy McCall 28 days ago ↶
9706
9707
9708
9709
9710
# [ test ]
9711
fn a_damaged_file_history_in_the_state_index_is_rebuilt ( ) {
9712
hermetic ( ) ;
9713
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9714
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9715
for i in 0 .. 6 {
9716
write (
9717
dir.path ( ) ,
9718
& format! ( " f{i} .txt" ) ,
9719
format! ( " one {i} \n " ) .as_bytes ( ) ,
9720
) ;
9721
}
9722
repo.snapshot ( " t" ) .unwrap ( ) ;
9723
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
9724
for i in 0 .. 6 {
9725
write (
9726
dir.path ( ) ,
9727
& format! ( " f{i} .txt" ) ,
9728
format! ( " one {i} \n two\n " ) .as_bytes ( ) ,
9729
) ;
9730
}
9731
repo.snapshot ( " t" ) .unwrap ( ) ;
9732
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
9733
let want: Vec< Vec< u8 > > = ( 0 .. 6 )
9734
.map ( | i | format! ( " one {i} \n two\n " ) .into_bytes ( ) )
9735
.collect ( ) ;
9736
9737
9738
let ( _ , wc) = repo.working_copy ( ) .unwrap ( ) ;
9739
let base = repo.load_state_history ( & wc.deps) .unwrap ( ) ;
9740
repo.write_state_index ( & wc.deps, & base) ;
9741
for ( i, w) in want.iter ( ) .enumerate ( ) {
9742
assert_eq! ( & repo.file_at ( & format! ( " f{i} .txt" ) , None ) .unwrap ( ) , w) ;
9743
}
9744
let index = repo.state_indexes ( ) ;
9745
assert! ( ! index.is_empty ( ) , " a state index was written" ) ;
9746
9747
let mut bytes = fs:: read( & index[ 0 ] ) .unwrap ( ) ;
9748
let last = bytes.len ( ) - 1 ;
9749
bytes[ last] ^ = 0x40 ;
9750
fs:: write( & index[ 0 ] , & bytes) .unwrap ( ) ;
9751
9752
let reads: Vec< _ > = ( 0 .. 6 )
9753
.map ( | i | repo.file_at ( & format! ( " f{i} .txt" ) , None ) )
9754
.collect ( ) ;
9755
let damaged: Vec< String> = reads
9756
.iter ( )
9757
.filter_map ( | r | r.as_ref ( ) .err ( ) .map ( | e | e.to_string ( ) ) )
9758
.collect ( ) ;
9759
assert_eq! ( damaged.len ( ) , 1 , " {damaged:?}" ) ;
9760
assert! (
9761
damaged[ 0 ] .contains ( " state index was damaged" )
9762
& & damaged[ 0 ] .contains ( " run the command again" ) ,
9763
" {}" ,
9764
damaged[ 0 ]
9765
) ;
9766
9767
for ( i, w) in want.iter ( ) .enumerate ( ) {
9768
assert_eq! ( & repo.file_at ( & format! ( " f{i} .txt" ) , None ) .unwrap ( ) , w) ;
9769
}
9770
}
cb8b8082 Andy McCall 28 days ago ↶
9771
9772
9773
9774
9775
9776
# [ test ]
9777
fn a_long_push_goes_in_parts ( ) {
9778
hermetic ( ) ;
9779
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9780
let root = dir.path ( ) ;
9781
let origin = root.join ( " origin" ) ;
9782
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
9783
let main = root.join ( " main" ) ;
9784
let mut repo = Repo:: init( & main) .unwrap ( ) ;
9785
repo.config
9786
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
9787
.unwrap ( ) ;
9788
9789
repo.config.set ( " push.batch-bytes" , " 1" ) .unwrap ( ) ;
9790
repo.save_config ( ) .unwrap ( ) ;
9791
for i in 0 .. 6 {
9792
write (
9793
& main,
9794
& format! ( " f{i} .txt" ) ,
9795
format! ( " file {i} \n " ) .as_bytes ( ) ,
9796
) ;
9797
repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ;
9798
}
9799
let mut parts = Vec :: new( ) ;
9800
let results = repo
9801
.push_bookmark_with ( " origin" , " main" , false , & mut | m| parts.push ( m.to_string ( ) ) )
9802
.unwrap ( ) ;
9803
assert! (
9804
results.iter ( ) .any ( | r | r.ok & & r.name = = " main" ) ,
9805
" {results:?}"
9806
) ;
9807
assert_eq! ( parts.len ( ) , 6 , " {parts:?}" ) ;
9808
assert_eq! ( parts[ 0 ] , " part 1 of 6: 1 changes" ) ;
9809
9810
let remote = Repo:: open( & origin) .unwrap ( ) ;
9811
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
9812
assert_eq! ( remote.bookmark_heads ( ) .unwrap ( ) , heads) ;
9813
assert_eq! (
9814
remote.log_state ( & heads) .unwrap ( ) .len ( ) ,
9815
repo.log_state ( & heads) .unwrap ( ) .len ( )
9816
) ;
9817
9818
let got = remote
9819
.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ ] )
9820
.unwrap ( ) ;
9821
assert_eq! ( got.len ( ) , 1 ) ;
9822
assert! ( got[ 0 ] .ok & & got[ 0 ] .name = = OBJECTS_RESULT , " {got:?}" ) ;
9823
}
d6e0d5b8 Andy McCall 27 days ago ↶
9824
d3b27090 Andy McCall 13 days ago ↶
9825
9826
9827
9828
9829
# [ test ]
9830
fn a_heavy_change_sends_its_blobs_in_parts ( ) {
9831
hermetic ( ) ;
9832
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9833
let root = dir.path ( ) ;
9834
let origin = root.join ( " origin" ) ;
9835
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
9836
let main = root.join ( " main" ) ;
9837
let mut repo = Repo:: init( & main) .unwrap ( ) ;
9838
repo.config
9839
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
9840
.unwrap ( ) ;
9841
repo.config.set ( " push.batch-bytes" , " 50000" ) .unwrap ( ) ;
9842
repo.save_config ( ) .unwrap ( ) ;
9843
let mut x: u64 = 0x9e3779b97f4a7c15 ;
9844
let mut noise = | n : usize | -> Vec< u8 > {
9845
( 0 .. n)
9846
.map ( | _| {
9847
x ^ = x < < 13 ;
9848
x ^ = x > > 7 ;
9849
x ^ = x < < 17 ;
9850
( x > > 24 ) as u8
9851
} )
9852
.collect ( )
9853
} ;
9854
let files: Vec< Vec< u8 > > = ( 0 .. 4 ) .map ( | _| noise ( 40_000 ) ) .collect ( ) ;
9855
for ( i, f) in files.iter ( ) .enumerate ( ) {
9856
write ( & main, & format! ( " bin{i} .dat" ) , f) ;
9857
}
9858
repo.commit ( " four blobs" , & [ ] ) .unwrap ( ) ;
9859
let mut parts = Vec :: new( ) ;
9860
let results = repo
9861
.push_bookmark_with ( " origin" , " main" , false , & mut | m| parts.push ( m.to_string ( ) ) )
9862
.unwrap ( ) ;
9863
assert! (
9864
results.iter ( ) .any ( | r | r.ok & & r.name = = " main" ) ,
9865
" {results:?}"
9866
) ;
9867
assert! (
9868
parts.len ( ) > = 3 & & parts[ 0 ] .starts_with ( " objects 1 of" ) ,
9869
" {parts:?}"
9870
) ;
9871
let back = Repo:: clone( & origin.to_string_lossy ( ) , & root.join ( " back" ) ) .unwrap ( ) ;
9872
for ( i, f) in files.iter ( ) .enumerate ( ) {
9873
assert_eq! ( & fs:: read( root.join ( format! ( " back/bin{i} .dat" ) ) ) .unwrap ( ) , f) ;
9874
}
9875
drop ( back) ;
9876
}
9877
570e3531 Andy McCall 13 days ago ↶
9878
9879
9880
9881
9882
# [ test ]
9883
fn a_replacement_does_not_send_the_old_content_again ( ) {
9884
hermetic ( ) ;
9885
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9886
let root = dir.path ( ) ;
9887
let origin = root.join ( " origin" ) ;
9888
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
9889
let main = root.join ( " main" ) ;
9890
let mut repo = Repo:: init( & main) .unwrap ( ) ;
9891
repo.config
9892
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
9893
.unwrap ( ) ;
9894
repo.save_config ( ) .unwrap ( ) ;
9895
let blobs = | b : & Bundle| {
9896
b.objects
9897
.iter ( )
9898
.filter ( | ( ty , _) | * ty = = ObjectType:: Blob)
9899
.count ( )
9900
} ;
9901
write ( & main, " bin.dat" , b " \0 \x01 first binary" ) ;
9902
write ( & main, " gone.dat" , b " \0 \x02 to be removed" ) ;
9903
let added = repo.commit ( " add" , & [ ] ) .unwrap ( ) ;
9904
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9905
write ( & main, " bin.dat" , b " \0 \x01 second binary" ) ;
9906
fs:: remove_file( main.join ( " gone.dat" ) ) .unwrap ( ) ;
9907
let changed = repo.commit ( " replace and remove" , & [ ] ) .unwrap ( ) ;
9908
assert_eq! ( blobs ( & repo.bundle_for ( & [ changed] ) .unwrap ( ) ) , 1 ) ;
9909
assert_eq! ( blobs ( & repo.bundle_for ( & [ added, changed] ) .unwrap ( ) ) , 3 ) ;
9910
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9911
let origin = Repo:: open( & origin) .unwrap ( ) ;
9912
assert_eq! ( origin.fsck ( ) .unwrap ( ) .problems, vec! [ ] ) ;
9913
let back = Repo:: clone( & origin.root ( ) .to_string_lossy ( ) , & root.join ( " back" ) ) .unwrap ( ) ;
9914
assert_eq! ( back.fsck ( ) .unwrap ( ) .problems, vec! [ ] ) ;
9915
assert_eq! (
9916
fs:: read( root.join ( " back/bin.dat" ) ) .unwrap ( ) ,
9917
b " \0 \x01 second binary"
9918
) ;
9919
assert! ( ! root.join ( " back/gone.dat" ) .exists ( ) ) ;
9920
}
9921
d6e0d5b8 Andy McCall 27 days ago ↶
9922
9923
9924
9925
# [ test ]
9926
fn git_import_claims_your_own_commits ( ) {
9927
hermetic ( ) ;
9928
if std:: process:: Command:: new( " git" )
9929
.arg ( " --version" )
9930
.output ( )
9931
.is_err ( )
9932
{
9933
eprintln! ( " git not installed; skipping" ) ;
9934
return ;
9935
}
9936
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9937
let root = dir.path ( ) ;
9938
let g = root.join ( " src.git" ) ;
9939
fs:: create_dir_all( & g) .unwrap ( ) ;
9940
let git = | who : & str , args : & [& str ]| {
9941
let ( name, email) = who.split_once ( ' ' ) .unwrap ( ) ;
9942
let out = std:: process:: Command:: new( " git" )
9943
.arg ( " -C" )
9944
.arg ( & g)
9945
.args ( [
9946
" -c" ,
9947
& format! ( " user.name={name} " ) ,
9948
" -c" ,
9949
& format! ( " user.email={email} " ) ,
9950
" -c" ,
9951
" commit.gpgsign=false" ,
9952
] )
9953
.args ( args)
9954
.output ( )
9955
.unwrap ( ) ;
9956
assert! (
9957
out.status.success ( ) ,
9958
" git {args:?}: {}" ,
9959
String :: from_utf8_lossy( & out.stderr)
9960
) ;
9961
} ;
9962
git ( " Grace [email protected] " , & [ " init" , " -q" , " -b" , " main" ] ) ;
9963
write ( & g, " a.txt" , b " a\n " ) ;
9964
git ( " Grace [email protected] " , & [ " add" , " ." ] ) ;
9965
git ( " Grace [email protected] " , & [ " commit" , " -qm" , " mine" ] ) ;
9966
write ( & g, " b.txt" , b " b\n " ) ;
9967
git ( " Ada [email protected] " , & [ " add" , " ." ] ) ;
9968
git ( " Ada [email protected] " , & [ " commit" , " -qm" , " someone else's" ] ) ;
9969
9970
let mut repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
9971
let ( key, _ ) = repo
9972
.create_identity ( " Grace" , " [email protected] " , IdentityScope:: Repo)
9973
.unwrap ( ) ;
9974
repo.config.set ( " git.claim" , " true" ) .unwrap ( ) ;
9975
repo.save_config ( ) .unwrap ( ) ;
9976
let repo = Repo:: open( & root.join ( " o" ) ) .unwrap ( ) ;
9977
repo.import_git ( & g, " main" ) .unwrap ( ) ;
9978
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
9979
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
9980
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
9981
let by_message: std:: collections:: HashMap< String, KeyId> = repo
9982
.log_state ( & heads)
9983
.unwrap ( )
9984
.into_iter ( )
9985
.map ( | e | {
9986
(
9987
e.change.message.lines ( ) .next ( ) .unwrap_or ( " " ) .to_string ( ) ,
9988
e.change.author,
9989
)
9990
} )
9991
.collect ( ) ;
9992
assert_eq! ( by_message[ " mine" ] , key, " her own commit, any case of email" ) ;
9993
assert_eq! ( by_message[ " someone else's" ] , ObjectRef:: ZERO , " not hers" ) ;
9994
}
34fad4a9 Andy McCall 27 days ago ↶
9995
9996
9997
9998
# [ test ]
9999
fn removing_a_remote_leaves_nothing_of_it ( ) {
10000
hermetic ( ) ;
10001
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10002
let origin = dir.path ( ) .join ( " origin" ) ;
10003
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
10004
let main = dir.path ( ) .join ( " main" ) ;
10005
let mut repo = Repo:: init( & main) .unwrap ( ) ;
10006
repo.config
10007
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
10008
.unwrap ( ) ;
10009
repo.config
10010
.set ( " remote.origin.token" , " secret-ish" )
10011
.unwrap ( ) ;
10012
repo.config
10013
.set ( " remote.keep.url" , & origin.to_string_lossy ( ) )
10014
.unwrap ( ) ;
10015
repo.save_config ( ) .unwrap ( ) ;
10016
write ( & main, " a.txt" , b " a\n " ) ;
10017
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10018
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10019
assert! (
10020
repo.view ( ) .unwrap ( ) .remotes.contains_key ( " origin" ) ,
10021
" a tracking bookmark"
10022
) ;
10023
10024
repo.remove_remote ( " origin" ) .unwrap ( ) ;
10025
let again = Repo:: open( & main) .unwrap ( ) ;
10026
assert! (
10027
! again
10028
.config
10029
.repo_layer ( )
10030
.keys ( )
10031
.any ( | k | k.starts_with ( " remote.origin." ) ) ,
10032
" {:?}" ,
10033
again.config.repo_layer ( )
10034
) ;
10035
assert! ( ! again.config.remotes.contains_key ( " origin" ) ) ;
10036
assert! (
10037
! again.view ( ) .unwrap ( ) .remotes.contains_key ( " origin" ) ,
10038
" its tracking bookmarks go"
10039
) ;
10040
assert! (
10041
again.config.remotes.contains_key ( " keep" ) ,
10042
" another remote stays"
10043
) ;
10044
let mut again = again;
10045
assert! (
10046
again.remove_remote ( " origin" ) .is_err ( ) ,
10047
" a second removal is refused"
10048
) ;
10049
}
9d580f21 Andy McCall 27 days ago ↶
10050
10051
10052
10053
10054
# [ test ]
10055
fn gc_keeps_the_blobs_a_host_names ( ) {
10056
hermetic ( ) ;
10057
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10058
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
10059
write ( dir.path ( ) , " a.txt" , b " a\n " ) ;
10060
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10061
10062
let body: Vec< u8 > = ( 0 .. 400_000 u32 )
10063
.flat_map ( | i | ( i.wrapping_mul ( 2654435761 ) ) .to_le_bytes ( ) )
10064
.collect ( ) ;
10065
let kept = repo.store_blob ( & body) .unwrap ( ) ;
10066
let lost = repo.store_blob ( b " an artifact nobody names" ) .unwrap ( ) ;
10067
repo.gc_keeping ( true , & [ kept] , & mut | _ | { } ) .unwrap ( ) ;
10068
assert_eq! ( repo.read_blob ( & kept) .unwrap ( ) , body, " named: kept whole" ) ;
10069
assert! ( ! repo.store ( ) .has ( & lost) , " not named: pruned" ) ;
10070
repo.gc ( true ) .unwrap ( ) ;
10071
assert! ( ! repo.store ( ) .has ( & kept) , " a plain gc prunes it" ) ;
10072
}
530a0fc6 Andy McCall 26 days ago ↶
10073
10074
10075
10076
10077
# [ test ]
10078
fn undo_is_scoped_to_its_workspace ( ) {
10079
hermetic ( ) ;
10080
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10081
let root = dir.path ( ) .join ( " main" ) ;
10082
let a = Repo:: init( & root) .unwrap ( ) ;
10083
write ( & root, " base.txt" , b " base\n " ) ;
10084
let base = a.commit ( " base" , & [ ] ) .unwrap ( ) ;
10085
a.bookmark_create ( " feature" , None ) .unwrap ( ) ;
10086
let ws_dir = dir.path ( ) .join ( " ws" ) ;
10087
let heads = a.resolve_heads ( " feature" ) .unwrap ( ) ;
10088
let b = a
10089
.workspace_add_on ( & ws_dir, Some ( & heads) , Some ( " feature" ) )
10090
.unwrap ( ) ;
10091
10092
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
10098
10099
let u = a.undo ( ) .unwrap ( ) ;
10100
assert_eq! ( u.command, " oxy commit -m \" from a\" " ) ;
10101
assert_eq! ( u.others_kept, 1 , " B's newer commit is left" ) ;
10102
assert! ( ! u.changes.is_empty ( ) ) ;
10103
let view = a.view ( ) .unwrap ( ) ;
10104
10105
assert_eq! ( a.heads_of ( & view, " main" ) .unwrap ( ) , vec! [ base] ) ;
10106
assert! (
10107
a.heads_of ( & view, " feature" ) .unwrap ( ) .contains ( & cb) ,
10108
" B's commit stands"
10109
) ;
10110
assert! (
10111
a.store ( ) .get_change ( & ca) .is_ok ( ) ,
10112
" the change itself is still in the store"
10113
) ;
10114
10115
assert! ( ! root.join ( " a.txt" ) .exists ( ) ) ;
10116
assert! ( a.snapshot ( " oxy status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
10117
assert! ( ws_dir.join ( " b.txt" ) .exists ( ) ) ;
10118
10119
10120
let u = b.undo ( ) .unwrap ( ) ;
10121
assert_eq! ( u.command, " oxy commit -m \" from b\" " ) ;
10122
assert! ( ! b
10123
.heads_of ( & b.view ( ) .unwrap ( ) , " feature" )
10124
.unwrap ( )
10125
.contains ( & cb) ) ;
10126
assert_eq! (
10127
a.heads_of ( & a.view ( ) .unwrap ( ) , " main" ) .unwrap ( ) ,
10128
vec! [ base] ,
10129
" A's undo stands"
10130
) ;
10131
10132
10133
10134
let e = a.undo ( ) .unwrap_err ( ) .to_string ( ) ;
10135
assert! ( e.contains ( " the workspace at" ) & & e.contains ( " ws" ) , " {e}" ) ;
10136
}
10137
10138
10139
10140
# [ test ]
10141
fn undo_on_a_shared_bookmark_keeps_the_other_workspaces_head ( ) {
10142
hermetic ( ) ;
10143
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10144
let root = dir.path ( ) .join ( " main" ) ;
10145
let a = Repo:: init( & root) .unwrap ( ) ;
10146
write ( & root, " base.txt" , b " base\n " ) ;
10147
a.commit ( " base" , & [ ] ) .unwrap ( ) ;
10148
let ws_dir = dir.path ( ) .join ( " ws" ) ;
10149
let b = a.workspace_add ( & ws_dir, None ) .unwrap ( ) ;
10150
write ( & root, " a.txt" , b " a\n " ) ;
10151
let ca = a.commit ( " from a" , & [ ] ) .unwrap ( ) ;
10152
write ( & ws_dir, " b.txt" , b " b\n " ) ;
10153
let cb = b.commit ( " from b" , & [ ] ) .unwrap ( ) ;
10154
let u = a.undo ( ) .unwrap ( ) ;
10155
assert_eq! ( u.command, " oxy commit -m \" from a\" " ) ;
10156
let heads = a.heads_of ( & a.view ( ) .unwrap ( ) , " main" ) .unwrap ( ) ;
10157
assert! ( heads.contains ( & cb) & & ! heads.contains ( & ca) , " {heads:?}" ) ;
10158
}
10159
10160
10161
10162
# [ test ]
10163
fn undo_refuses_to_take_back_another_workspaces_work ( ) {
10164
hermetic ( ) ;
10165
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10166
let root = dir.path ( ) .join ( " main" ) ;
10167
let a = Repo:: init( & root) .unwrap ( ) ;
10168
write ( & root, " base.txt" , b " base\n " ) ;
10169
a.commit ( " base" , & [ ] ) .unwrap ( ) ;
10170
let ws_dir = dir.path ( ) .join ( " ws" ) ;
10171
let b = a.workspace_add ( & ws_dir, None ) .unwrap ( ) ;
10172
10173
write ( & root, " a.txt" , b " a\n " ) ;
10174
a.commit ( " from a" , & [ ] ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
10175
b.refresh ( ) .unwrap ( ) ;
530a0fc6 Andy McCall 26 days ago ↶
10176
let heads = b.resolve_heads ( " main" ) .unwrap ( ) ;
10177
b.checkout ( & heads, false , " oxy checkout" ) .unwrap ( ) ;
10178
write ( & ws_dir, " a.txt" , b " a, then b\n " ) ;
10179
b.commit ( " from b" , & [ ] ) .unwrap ( ) ;
10180
let e = a.undo ( ) .unwrap_err ( ) .to_string ( ) ;
10181
assert! (
10182
e.contains ( " bookmark main" ) & & e.contains ( " builds on" ) ,
10183
" {e}"
10184
) ;
10185
assert! ( e.contains ( " oxy op restore" ) , " {e}" ) ;
10186
}
10187
10188
10189
10190
# [ test ]
10191
fn undo_says_when_the_commit_was_pushed ( ) {
10192
hermetic ( ) ;
10193
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10194
let root = dir.path ( ) ;
10195
Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
10196
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
10197
let repo = Repo:: clone( & url, & root.join ( " work" ) )
10198
.unwrap ( )
10199
.with_home ( root.join ( " home" ) ) ;
10200
write ( & root.join ( " work" ) , " a.txt" , b " a\n " ) ;
10201
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10202
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10203
10204
let u = repo.undo ( ) .unwrap ( ) ;
10205
assert_eq! ( u.command, " oxy commit -m \" a\" " ) ;
10206
assert_eq! ( u.pushed, vec! [ ( " main" .to_string ( ) , " origin" .to_string ( ) ) ] ) ;
10207
}
b689779e Andy McCall 26 days ago ↶
10208
10209
10210
10211
10212
# [ test ]
10213
fn large_new_files_are_left_out_of_snapshots ( ) {
10214
hermetic ( ) ;
10215
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10216
let root = dir.path ( ) ;
10217
let mut repo = Repo:: init( root) .unwrap ( ) ;
10218
repo.config.max_new_file = 1024 ;
10219
write ( root, " small.txt" , b " small\n " ) ;
10220
write ( root, " big.bin" , & vec! [ 0 u8 ; 4096 ] ) ;
10221
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10222
let paths: Vec< _ > = s.statuses.iter ( ) .map ( | p | p.path.as_str ( ) ) .collect ( ) ;
10223
assert_eq! ( paths, [ " small.txt" ] ) ;
10224
assert_eq! ( repo.skipped_large ( ) , vec! [ ( " big.bin" .to_string ( ) , 4096 ) ] ) ;
10225
repo.commit ( " small" , & [ ] ) .unwrap ( ) ;
10226
10227
10228
write ( root, " small.txt" , & vec! [ b ' x' ; 4096 ] ) ;
10229
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10230
assert! ( s.statuses.iter ( ) .any ( | p | p.path = = " small.txt" ) ) ;
10231
10232
repo.config.max_new_file = 0 ;
10233
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10234
assert! ( s.statuses.iter ( ) .any ( | p | p.path = = " big.bin" ) ) ;
10235
repo.config.max_new_file = 1024 ;
10236
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10237
assert! (
10238
s.statuses.iter ( ) .any ( | p | p.path = = " big.bin" ) ,
10239
" already in the working copy"
10240
) ;
10241
assert! ( repo.skipped_large ( ) .is_empty ( ) ) ;
10242
}
10243
10244
10245
10246
# [ test ]
10247
fn gc_prunes_what_only_expired_snapshots_held ( ) {
10248
hermetic ( ) ;
10249
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10250
let root = dir.path ( ) ;
10251
let mut repo = Repo:: init( root) .unwrap ( ) ;
10252
repo.config.gc_grace_days = 0 ;
10253
write ( root, " a.txt" , b " a\n " ) ;
10254
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10255
let image: Vec< u8 > = ( 0 .. 200_000 u32 ) .map ( | i | ( i % 7 ) as u8 ) .collect ( ) ;
10256
write ( root, " image.tar" , & image) ;
10257
repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10258
std:: fs:: remove_file( root.join ( " image.tar" ) ) .unwrap ( ) ;
10259
repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10260
let blob = Repo:: blob_ref( & image) ;
10261
assert! ( repo.store ( ) .has ( & blob) ) ;
10262
repo.gc ( true ) .unwrap ( ) ;
10263
assert! ( repo.store ( ) .has ( & blob) , " a recent snapshot keeps it" ) ;
10264
repo.config.snapshot_retention_days = 0 ;
10265
repo.gc ( true ) .unwrap ( ) ;
10266
assert! ( ! repo.store ( ) .has ( & blob) , " an expired snapshot does not" ) ;
10267
assert_eq! (
10268
repo.file_at ( " a.txt" , None ) .unwrap ( ) ,
10269
b " a\n " ,
10270
" committed files stay"
10271
) ;
fe9586d9 Andy McCall 26 days ago ↶
10272
}
10273
10274
10275
10276
10277
# [ test ]
10278
fn carrying_work_the_target_already_has_is_no_conflict ( ) {
10279
hermetic ( ) ;
10280
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10281
let root = dir.path ( ) ;
10282
let mut repo = Repo:: init( root) .unwrap ( ) ;
10283
write ( root, " a.txt" , b " one\n " ) ;
10284
write ( root, " b.txt" , b " b\n " ) ;
10285
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
10286
repo.bookmark_create ( " landed" , None ) .unwrap ( ) ;
10287
10288
repo.bookmark_switch ( " landed" , false ) .unwrap ( ) ;
10289
write ( root, " a.txt" , b " two\n " ) ;
10290
repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
10291
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
10292
assert_eq! ( std:: fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n " ) ;
10293
10294
write ( root, " a.txt" , b " two\n " ) ;
10295
write ( root, " b.txt" , b " b, edited\n " ) ;
10296
let ( _ , carried) = repo.bookmark_switch_carry ( " landed" ) .unwrap ( ) ;
10297
assert_eq! (
10298
carried,
10299
vec! [ " b.txt" .to_string ( ) ] ,
10300
" only the work landed lacks is carried"
10301
) ;
10302
assert_eq! ( std:: fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " two\n " ) ;
10303
assert_eq! ( std:: fs:: read( root.join ( " b.txt" ) ) .unwrap ( ) , b " b, edited\n " ) ;
10304
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10305
let paths: Vec< _ > = s.statuses.iter ( ) .map ( | p | p.path.as_str ( ) ) .collect ( ) ;
10306
assert_eq! ( paths, [ " b.txt" ] ) ;
10307
10308
10309
repo.bookmark_switch ( " main" , true ) .unwrap ( ) ;
10310
write ( root, " a.txt" , b " three\n " ) ;
10311
let e = repo
10312
.bookmark_switch_carry ( " landed" )
10313
.unwrap_err ( )
10314
.to_string ( ) ;
10315
assert! ( e.contains ( " a.txt" ) & & e.contains ( " --discard" ) , " {e}" ) ;
b689779e Andy McCall 26 days ago ↶
10316
}
aa4939e0 Andy McCall 26 days ago ↶
10317
10318
10319
10320
# [ test ]
10321
fn a_full_hash_resolves_without_its_hash_sign ( ) {
10322
hermetic ( ) ;
10323
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10324
let root = dir.path ( ) ;
10325
let repo = Repo:: init( root) .unwrap ( ) ;
10326
write ( root, " a.txt" , b " a\n " ) ;
10327
let h = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10328
assert_eq! ( repo.resolve_change ( & h.to_hex ( ) ) .unwrap ( ) , h) ;
10329
assert_eq! ( repo.resolve_change ( & format! ( " #{} " , h.to_hex ( ) ) ) .unwrap ( ) , h) ;
10330
assert! ( repo
10331
.resolve_change ( & " 0" .repeat ( 64 ) )
10332
.unwrap_err ( )
10333
.to_string ( )
10334
.contains ( " no change matches" ) ) ;
10335
}
f2f91ceb Andy McCall 25 days ago ↶
10336
10337
# [ test ]
10338
fn a_pin_change_moves_only_the_pin ( ) {
10339
hermetic ( ) ;
10340
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10341
let root = dir.path ( ) ;
10342
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
10343
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
10344
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
10345
.unwrap ( )
10346
.with_home ( root.join ( " home" ) ) ;
10347
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
10348
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
10349
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10350
let _app_origin = Repo:: init( & root.join ( " app-origin" ) ) .unwrap ( ) ;
10351
let app_dir = root.join ( " app" ) ;
10352
let app = Repo:: clone( & root.join ( " app-origin" ) .to_string_lossy ( ) , & app_dir)
10353
.unwrap ( )
10354
.with_home ( root.join ( " home" ) ) ;
10355
write ( & app_dir, " main.txt" , b " app\n " ) ;
10356
let pin1 = app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
10357
let a1 = app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
10358
10359
write ( & root.join ( " lib" ) , " more.txt" , b " v2\n " ) ;
10360
let l2 = lib.commit ( " lib v2" , & [ ] ) .unwrap ( ) ;
10361
let crate :: subrepo:: Pin:: Oxy { repo, bookmark, .. } = pin1 else {
10362
panic! ( " an Oxygen pin" )
10363
} ;
10364
let pin2 = crate :: subrepo:: Pin:: Oxy {
10365
repo,
10366
heads: vec! [ l2] ,
10367
bookmark,
10368
} ;
10369
let author = app.store ( ) .get_change ( & a1) .unwrap ( ) .author;
10370
let change = app
10371
.build_pin_change ( & [ a1] , " libs/lib" , & pin2, " Move lib to v2" , author)
10372
.unwrap ( ) ;
10373
let mut state = app.load_state ( & [ a1] ) .unwrap ( ) ;
10374
state.add ( change) .unwrap ( ) ;
10375
let files = state.materialize ( ) .files;
10376
assert_eq! ( files[ " libs/lib" ] .kind, NodeKind:: Subrepo) ;
10377
let FileContent:: Opaque { blobs } = & files[ " libs/lib" ] .content else {
10378
panic! ( " a pin is opaque" )
10379
} ;
10380
let read = crate :: subrepo:: Pin:: decode( & app.read_blob ( & blobs[ 0 ] ) .unwrap ( ) ) .unwrap ( ) ;
10381
assert_eq! ( read, pin2, " the new pin" ) ;
10382
assert! (
10383
matches! ( & files[ " main.txt" ] .content, FileContent:: Text { bytes, .. } if bytes.as_slice ( ) = = b " app\n " ) ,
10384
" nothing else changes"
10385
) ;
10386
10387
let e = app.build_pin_change ( & [ a1] , " main.txt" , & pin2, " x" , author) ;
10388
assert! ( format! ( " {:?} " , e.unwrap_err ( ) ) .contains ( " not a subrepo" ) ) ;
10389
let e = app.build_pin_change ( & [ a1] , " libs/none" , & pin2, " x" , author) ;
10390
assert! ( format! ( " {:?} " , e.unwrap_err ( ) ) .contains ( " does not exist" ) ) ;
10391
}
767ac6cd Andy McCall 19 days ago
10392
10393
10394
10395
10396
10397
10398
# [ test ]
10399
fn one_handle_keeps_one_head_until_it_refreshes ( ) {
10400
hermetic ( ) ;
10401
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10402
let root = dir.path ( ) ;
10403
let writer = Repo:: init( root) .unwrap ( ) ;
10404
write ( root, " a.txt" , b " one\n " ) ;
10405
let h1 = writer.commit ( " one" , & [ ] ) .unwrap ( ) ;
10406
let reader = Repo:: open( root) .unwrap ( ) ;
10407
let before = reader.view ( ) .unwrap ( ) ;
10408
assert_eq! ( reader.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
10409
write ( root, " a.txt" , b " two\n " ) ;
10410
let h2 = writer.commit ( " two" , & [ ] ) .unwrap ( ) ;
10411
assert_eq! (
10412
writer.bookmark_heads ( ) .unwrap ( ) ,
10413
vec! [ h2] ,
10414
" its own operation"
10415
) ;
10416
assert_eq! ( reader.view ( ) .unwrap ( ) , before, " the head it read first" ) ;
10417
assert_eq! ( reader.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
10418
reader.refresh ( ) .unwrap ( ) ;
10419
assert_eq! ( reader.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
10420
10421
10422
let stale = Repo:: open( root) .unwrap ( ) ;
10423
assert_eq! ( stale.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
10424
write ( root, " a.txt" , b " three\n " ) ;
10425
let h3 = writer.commit ( " three" , & [ ] ) .unwrap ( ) ;
10426
stale.bookmark_create ( " feature" , Some ( & [ h1] ) ) .unwrap ( ) ;
10427
let view = stale.view ( ) .unwrap ( ) ;
10428
assert! ( view.bookmarks.contains_key ( " feature" ) ) ;
10429
assert_eq! (
10430
stale.bookmark_heads ( ) .unwrap ( ) ,
10431
vec! [ h3] ,
10432
" merged, not lost"
10433
) ;
10434
let fresh = Repo:: open( root) .unwrap ( ) ;
10435
assert_eq! ( fresh.view ( ) .unwrap ( ) , view) ;
10436
}
10437
10438
10439
# [ test ]
10440
fn over_a_store_one_handle_keeps_one_head ( ) {
10441
hermetic ( ) ;
10442
let backend = std:: sync:: Arc:: new( crate :: strata:: MemoryStore:: new( ) ) ;
10443
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = Some ( backend.clone ( ) ) ) ;
10444
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10445
let a = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
10446
let ( h0, _ ) = a.head_op ( ) .unwrap ( ) ;
10447
let b = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
10448
assert_eq! ( b.head_op ( ) .unwrap ( ) .0 , h0) ;
10449
let state = a.store.put_state ( & StateObject:: new( vec! [ ] ) ) .unwrap ( ) ;
10450
let mut view = a.view ( ) .unwrap ( ) ;
10451
view.bookmarks.insert ( " x" .into ( ) , state) ;
10452
let r = a.record_op ( & [ h0] , " bookmark x" , view) .unwrap ( ) ;
10453
assert_eq! ( a.head_op ( ) .unwrap ( ) .0 , r) ;
10454
assert_eq! ( b.head_op ( ) .unwrap ( ) .0 , h0, " b keeps its head" ) ;
10455
b.refresh ( ) .unwrap ( ) ;
10456
assert_eq! ( b.head_op ( ) .unwrap ( ) .0 , r) ;
10457
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = None ) ;
10458
}
10459
10460
10461
10462
10463
# [ test ]
10464
fn gc_retires_and_a_later_gc_deletes_after_the_hour ( ) {
10465
hermetic ( ) ;
10466
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10467
let root = dir.path ( ) ;
10468
let repo = Repo:: init( root) .unwrap ( ) ;
10469
let mut heads = Vec :: new( ) ;
10470
for i in 0 .. 4 {
10471
write ( root, & format! ( " f{i} .txt" ) , format! ( " {i} \n " ) .as_bytes ( ) ) ;
10472
heads.push ( repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ) ;
10473
}
10474
let reader = Repo:: open( root) .unwrap ( ) ;
10475
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 5 ) ;
10476
let r = repo.gc ( true ) .unwrap ( ) ;
10477
assert_eq! ( r.retired, crate :: store:: RetiredReport:: default( ) ) ;
10478
assert_eq! ( repo.store ( ) .loose_count ( ) .unwrap ( ) , 0 ) ;
10479
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 5 , " read on from what it had" ) ;
10480
10481
let r = repo.gc ( true ) .unwrap ( ) ;
10482
assert_eq! ( ( r.retired.packs, r.retired.loose) , ( 0 , 0 ) ) ;
10483
assert! ( r.retired.kept > 0 , " {r:?}" ) ;
10484
write ( root, " f9.txt" , b " 9\n " ) ;
10485
heads.push ( repo.commit ( " c9" , & [ ] ) .unwrap ( ) ) ;
10486
crate :: store:: reader_tests:: age_retired(
10487
repo.store ( ) ,
10488
crate :: store:: RETIRED_FOR + std:: time:: Duration:: from_secs( 60 ) ,
10489
) ;
10490
let r = repo.gc ( true ) .unwrap ( ) ;
10491
assert! ( r.retired.packs > = 1 & & r.retired.loose > 0 , " {r:?}" ) ;
10492
assert_eq! ( r.retired.kept, 0 , " {r:?}" ) ;
10493
let r = repo.gc ( true ) .unwrap ( ) ;
10494
assert_eq! ( ( r.retired.packs, r.retired.loose) , ( 0 , 0 ) ) ;
10495
assert! ( r.retired.kept > 0 , " what the last gc retired is young" ) ;
10496
10497
10498
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 5 ) ;
10499
for h in & heads[ .. 4 ] {
10500
reader.store ( ) .get_change ( h) .unwrap ( ) ;
10501
}
10502
reader.refresh ( ) .unwrap ( ) ;
10503
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 6 ) ;
10504
let fresh = Repo:: open( root) .unwrap ( ) ;
10505
assert_eq! (
10506
fresh.store ( ) .pack_count ( ) ,
10507
1 ,
10508
" retired packs are not loaded"
10509
) ;
10510
}
10511
10512
10513
10514
# [ test ]
10515
fn a_repository_opens_when_a_listed_index_is_gone ( ) {
10516
hermetic ( ) ;
10517
let backend = crate :: store:: reader_tests:: racing( true ) ;
10518
let dyn_backend: std:: sync:: Arc< crate:: store:: Backend> = backend.clone ( ) ;
10519
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = Some ( dyn_backend) ) ;
10520
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10521
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
10522
write ( dir.path ( ) , " a.txt" , b " a\n " ) ;
10523
let h = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10524
repo.gc ( true ) .unwrap ( ) ;
10525
let again = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
10526
assert_eq! ( again.store ( ) .pack_count ( ) , 1 ) ;
10527
assert_eq! ( again.bookmark_heads ( ) .unwrap ( ) , vec! [ h] ) ;
10528
10529
10530
use crate :: strata:: Store as _ ;
10531
let idx = backend
10532
.list ( " pack-" )
10533
.unwrap ( )
10534
.into_iter ( )
10535
.map ( | e | e.key )
10536
.find ( | k | k.ends_with ( " .idx" ) & & k ! = " pack-0000.idx" )
10537
.unwrap ( ) ;
10538
let stem = idx.trim_end_matches ( " .idx" ) ;
10539
for ext in [ " pack" , " idx" ] {
10540
let bytes = backend.get ( & format! ( " {stem} .{ext} " ) ) .unwrap ( ) ;
10541
backend
10542
.put_new ( & format! ( " pack-copy.{ext} " ) , & bytes)
10543
.unwrap ( ) ;
10544
}
10545
* backend.doomed.lock ( ) .unwrap ( ) = Some ( " pack-copy.idx" .into ( ) ) ;
10546
let again = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
10547
assert_eq! ( again.store ( ) .pack_count ( ) , 1 ) ;
10548
assert_eq! ( again.bookmark_heads ( ) .unwrap ( ) , vec! [ h] ) ;
10549
assert! ( ! backend.has ( " pack-copy.idx" ) .unwrap ( ) ) ;
10550
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = None ) ;
10551
}