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 17 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 17 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 17 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 17 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" ) ;
f3811eb2 Andy McCall 13 days ago ↶
3337
3338
3339
let mut a = Repo:: open( & root.join ( " a" ) )
3340
.unwrap ( )
3341
.with_home ( root.join ( " home" ) ) ;
3342
let head = origin.bookmark_heads ( ) .unwrap ( ) [ 0 ] ;
3343
assert! ( ! a.store ( ) .has ( & head) , " no change fetched" ) ;
3344
assert! ( a.boundary ( ) .contains ( & head) ) ;
3345
assert_eq! (
3346
a.log ( )
3347
.unwrap ( )
3348
.iter ( )
3349
.filter ( | e | ! e.is_working_copy)
3350
.count ( ) ,
3351
2 ,
3352
" the log from the checkpoint"
3353
) ;
3354
write ( & root.join ( " a" ) , " a.txt" , b " later\n mine\n " ) ;
3355
a.config.set ( " remote.origin.url" , & url) .unwrap ( ) ;
3356
a.save_config ( ) .unwrap ( ) ;
3357
let mine = a.commit ( " mine" , & [ ] ) .unwrap ( ) ;
3358
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
3359
origin.refresh ( ) .unwrap ( ) ;
3360
assert! ( origin.store ( ) .has ( & mine) ) ;
3361
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ mine] ) ;
3362
3363
origin
3364
.checkout ( & origin.bookmark_heads ( ) .unwrap ( ) , false , " co" )
3365
.unwrap ( ) ;
3366
write ( & origin_dir, " a.txt" , b " later\n mine\n theirs\n " ) ;
3367
origin.snapshot ( " t" ) .unwrap ( ) ;
3368
origin.commit ( " theirs" , & [ ] ) .unwrap ( ) ;
3369
a.pull ( " origin" ) .unwrap ( ) ;
3370
assert_eq! (
3371
fs:: read( root.join ( " a/a.txt" ) ) .unwrap ( ) ,
3372
b " later\n mine\n theirs\n "
3373
) ;
7bd805ff Andy McCall 13 days ago ↶
3374
}
3375
3376
# [ test ]
2863456a Andy McCall 1 month ago ↶
3377
fn shallow_clone_and_deepen ( ) {
3378
hermetic ( ) ;
3379
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3380
let root = dir.path ( ) ;
3381
let origin_dir = root.join ( " origin" ) ;
75167672 Andy McCall 1 month ago
3382
let origin = Repo:: init( & origin_dir)
3383
.unwrap ( )
3384
.with_home ( root.join ( " home" ) ) ;
2863456a Andy McCall 1 month ago ↶
3385
let mut hashes = Vec :: new( ) ;
3386
for i in 0 .. 8 {
75167672 Andy McCall 1 month ago
3387
write (
3388
& origin_dir,
3389
" a.txt" ,
3390
format! (
3391
" {} \n " ,
3392
( 0 .. = i)
3393
.map ( | k | k.to_string ( ) )
3394
.collect:: < Vec< _ > > ( )
3395
.join ( " \n " )
3396
)
3397
.as_bytes ( ) ,
3398
) ;
2863456a Andy McCall 1 month ago ↶
3399
hashes.push ( origin.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ) ;
3400
}
3401
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
3402
let a = Repo:: clone_shallow( & url, & root.join ( " a" ) , 3 , None , & [ ] )
3403
.unwrap ( )
3404
.with_home ( root.join ( " home" ) ) ;
2863456a Andy McCall 1 month ago ↶
3405
assert! ( a.is_shallow ( ) ) ;
75167672 Andy McCall 1 month ago
3406
assert_eq! (
3407
fs:: read_to_string( root.join ( " a/a.txt" ) ) .unwrap ( ) ,
3408
" 0\n 1\n 2\n 3\n 4\n 5\n 6\n 7\n "
3409
) ;
3410
assert! (
3411
a.store ( ) .has ( & hashes[ 7 ] ) & & a.store ( ) .has ( & hashes[ 5 ] ) ,
3412
" three levels present"
3413
) ;
2863456a Andy McCall 1 month ago ↶
3414
assert! ( ! a.store ( ) .has ( & hashes[ 4 ] ) , " beyond the depth is absent" ) ;
3415
assert_eq! ( a.boundary ( ) , [ hashes[ 4 ] ] .into_iter ( ) .collect ( ) ) ;
75167672 Andy McCall 1 month ago
3416
assert_eq! (
3417
a.log ( ) .unwrap ( ) .len ( ) ,
3418
9 ,
3419
" the log shows every change from the checkpoint's metadata"
3420
) ;
2863456a Andy McCall 1 month ago ↶
3421
assert! ( ! a.snapshot ( " status" ) .unwrap ( ) .changed) ;
3422
assert_eq! ( a.quick_status ( ) .unwrap ( ) .heads, 1 ) ;
3423
3424
3425
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 " ) ;
3426
let h_new = a.commit ( " c8" , & [ ] ) .unwrap ( ) ;
3427
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
3428
origin.refresh ( ) .unwrap ( ) ;
2863456a Andy McCall 1 month ago ↶
3429
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ h_new] ) ;
3430
3431
write ( & origin_dir, " b.txt" , b " b\n " ) ;
3432
origin.pull ( " origin" ) .err ( ) ;
3433
let ( _ , wc) = origin.working_copy ( ) .unwrap ( ) ;
3434
let _ = wc;
75167672 Andy McCall 1 month ago
3435
origin
3436
.checkout ( & origin.bookmark_heads ( ) .unwrap ( ) , true , " co" )
3437
.unwrap ( ) ;
2863456a Andy McCall 1 month ago ↶
3438
write ( & origin_dir, " b.txt" , b " b\n " ) ;
3439
let h_b = origin.commit ( " b" , & [ ] ) .unwrap ( ) ;
3440
let pr = a.pull ( " origin" ) .unwrap ( ) ;
3441
assert! ( pr.changed) ;
3442
assert! ( a.store ( ) .has ( & h_b) ) ;
3443
assert! ( root.join ( " a/b.txt" ) .exists ( ) ) ;
3444
3445
3446
3447
3448
assert! ( a.deepen ( " origin" , 2 ) .unwrap ( ) > = 2 ) ;
3449
assert! ( a.store ( ) .has ( & hashes[ 3 ] ) ) ;
3450
assert! ( a.is_shallow ( ) ) ;
3451
a.deepen ( " origin" , 100 ) .unwrap ( ) ;
3452
assert! ( ! a.is_shallow ( ) , " boundary reached the root" ) ;
3453
assert! ( a.store ( ) .has ( & hashes[ 0 ] ) ) ;
75167672 Andy McCall 1 month ago
3454
assert_eq! (
3455
a.load_state_full ( & a.bookmark_heads ( ) .unwrap ( ) )
3456
.unwrap ( )
3457
.len ( ) ,
3458
10
3459
) ;
2863456a Andy McCall 1 month ago ↶
3460
}
132b884b Andy McCall 1 month ago ↶
3461
3462
# [ test ]
3463
fn tags_are_immutable_named_states_and_travel ( ) {
3464
hermetic ( ) ;
3465
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3466
let root = dir.path ( ) ;
3467
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
3468
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
3469
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
3470
.unwrap ( )
3471
.with_home ( root.join ( " home-a" ) ) ;
3472
let ( key_a, _ ) = a
3473
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
3474
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3475
write ( & root.join ( " a" ) , " f.txt" , b " 1\n " ) ;
3476
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
3477
write ( & root.join ( " a" ) , " f.txt" , b " 1\n 2\n " ) ;
3478
let h2 = a.commit ( " two" , & [ ] ) .unwrap ( ) ;
3479
3480
let t = a.tag_create ( " v1" , & [ h1] , " first release" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3481
assert_eq! (
3482
( t.heads.clone ( ) , t.signer.as_deref ( ) ) ,
3483
( vec! [ h1] , Some ( " Ada" ) )
3484
) ;
132b884b Andy McCall 1 month ago ↶
3485
assert! ( a.tag_create ( " v1" , & [ h2] , " " ) .is_err ( ) , " immutable" ) ;
3486
assert! ( a.tag_create ( " bad/name" , & [ h2] , " " ) .is_err ( ) ) ;
3487
assert_eq! ( a.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
3488
assert_eq! ( a.resolve_heads ( " v1" ) .unwrap ( ) , vec! [ h1] ) ;
3489
assert_eq! ( a.resolve_heads ( " main" ) .unwrap ( ) , vec! [ h2] ) ;
3490
let ( _ , sa) = a.tag_attest ( " v1" ) .unwrap ( ) .unwrap ( ) ;
3491
assert_eq! ( sa.sigs[ 0 ] .key, key_a) ;
ee130dd9 Andy McCall 17 days ago ↶
3492
3493
3494
let mut refs = a.remote_refs ( ) .unwrap ( ) ;
3495
a.refs_for_client ( & mut refs, None ) ;
3496
assert_eq! ( refs.tags.len ( ) , 1 ) ;
132b884b Andy McCall 1 month ago ↶
3497
75167672 Andy McCall 1 month ago
3498
a.checkout ( & a.resolve_heads ( " v1" ) .unwrap ( ) , false , " co" )
3499
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3500
assert_eq! ( fs:: read( root.join ( " a/f.txt" ) ) .unwrap ( ) , b " 1\n " ) ;
75167672 Andy McCall 1 month ago
3501
a.checkout ( & a.bookmark_heads ( ) .unwrap ( ) , false , " co" )
3502
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3503
3504
3505
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3506
origin.refresh ( ) .unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3507
assert! ( r.iter ( ) .any ( | x | x.name = = " tags/v1" & & x.ok ) , " {r:?}" ) ;
3508
assert_eq! ( origin.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
75167672 Andy McCall 1 month ago
3509
let mut forged = a
3510
.tag_refs ( )
3511
.unwrap ( )
3512
.into_iter ( )
3513
.find ( | t | t.name = = " v1" )
3514
.unwrap ( ) ;
3515
forged.state = origin
3516
.store ( )
3517
.put_state ( & StateObject:: new( vec! [ h2] ) )
3518
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3519
forged.heads = vec! [ h2] ;
3520
forged.attestation = None ;
75167672 Andy McCall 1 month ago
3521
let r = origin
3522
.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ forged] )
3523
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3524
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " immutable" ) , " {r:?}" ) ;
3525
3526
75167672 Andy McCall 1 month ago
3527
let b = Repo:: clone( & url, & root.join ( " b" ) )
3528
.unwrap ( )
3529
.with_home ( root.join ( " home-b" ) ) ;
132b884b Andy McCall 1 month ago ↶
3530
assert_eq! ( b.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
3531
assert! ( b.tags ( ) .unwrap ( ) [ 0 ] .signer.as_deref ( ) = = Some ( " Ada" ) ) ;
3532
a.tag_create ( " v2" , & [ h2] , " second" ) .unwrap ( ) ;
3533
a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3534
origin.refresh ( ) .unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3535
let rep = b.fetch ( " origin" ) .unwrap ( ) ;
3536
assert_eq! ( rep.new_tags, 1 ) ;
3537
assert_eq! ( b.tag_heads ( " v2" ) .unwrap ( ) , Some ( vec! [ h2] ) ) ;
3538
3539
3540
a.policy_init ( ) .unwrap ( ) ;
3541
a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3542
origin.refresh ( ) .unwrap ( ) ;
1ec6fdbc Andy McCall 17 days ago ↶
3543
let unattested = crate :: protocol:: TagRef:: new(
3544
" v3" .into ( ) ,
3545
origin
75167672 Andy McCall 1 month ago
3546
.store ( )
3547
.put_state ( & StateObject:: new( vec! [ h2] ) )
3548
.unwrap ( ) ,
1ec6fdbc Andy McCall 17 days ago ↶
3549
vec! [ h2] ,
3550
None ,
3551
) ;
75167672 Andy McCall 1 month ago
3552
let r = origin
3553
.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ unattested] )
3554
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3555
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " attestation" ) , " {r:?}" ) ;
3556
a.tag_create ( " v3" , & [ h2] , " " ) .unwrap ( ) ;
3557
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3558
origin.refresh ( ) .unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3559
assert! ( r.iter ( ) .any ( | x | x.name = = " tags/v3" & & x.ok ) , " {r:?}" ) ;
3560
assert! ( a.gc ( true ) .is_ok ( ) ) ;
3561
assert_eq! ( a.tags ( ) .unwrap ( ) .len ( ) , 3 , " tags survive gc" ) ;
e5d9b051 Andy McCall 21 days ago ↶
3562
3563
3564
3565
3566
let rot = a.rotate_identity ( ) .unwrap ( ) ;
3567
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3568
origin.refresh ( ) .unwrap ( ) ;
e5d9b051 Andy McCall 21 days ago ↶
3569
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
3570
let c = Repo:: clone( & url, & root.join ( " c" ) )
3571
.unwrap ( )
3572
.with_home ( root.join ( " home-c" ) ) ;
3573
assert_eq! ( c.tag_heads ( " v3" ) .unwrap ( ) , Some ( vec! [ h2] ) ) ;
3574
let ( _ , sp) = c.current_policy ( ) .unwrap ( ) .unwrap ( ) ;
3575
assert! ( ! sp.policy.authorizes ( & key_a, " tags/v9" ) ) ;
3576
let made = c.identity ( & rot.new) .unwrap ( ) .created;
3577
assert! ( c.predecessor_authorized ( & sp.policy, & key_a, " tags/v9" , made - 1 ) ) ;
3578
assert! ( ! c.predecessor_authorized ( & sp.policy, & key_a, " tags/v9" , made) ) ;
3579
assert! ( ! c.predecessor_authorized ( & sp.policy, & rot.new, " tags/v9" , 0 ) ) ;
132b884b Andy McCall 1 month ago ↶
3580
}
4e88e774 Andy McCall 1 month ago ↶
3581
3582
# [ test ]
3583
fn multiple_workspaces_share_a_repository ( ) {
3584
hermetic ( ) ;
3585
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3586
let root = dir.path ( ) ;
3587
let main_dir = root.join ( " main" ) ;
3588
let repo = Repo:: init( & main_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
3589
write ( & main_dir, " a.txt" , b " a\n " ) ;
3590
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
3591
assert_eq! ( repo.workspaces ( ) .unwrap ( ) .len ( ) , 1 ) ;
3592
3593
3594
let ws_dir = root.join ( " ws2" ) ;
4476cdbb Andy McCall 12 days ago ↶
3595
let ops = repo.op_log ( ) .unwrap ( ) .len ( ) ;
4e88e774 Andy McCall 1 month ago ↶
3596
let ws = repo.workspace_add ( & ws_dir, None ) .unwrap ( ) ;
4476cdbb Andy McCall 12 days ago ↶
3597
assert_eq! (
3598
ws.op_log ( ) .unwrap ( ) .len ( ) ,
3599
ops + 2 ,
3600
" the add and its checkout, and no snapshot finding the tree deleted (oxygen#137)"
3601
) ;
4e88e774 Andy McCall 1 month ago ↶
3602
assert! ( ws_dir.join ( " .oxy" ) .is_file ( ) ) ;
3603
assert_eq! ( fs:: read( ws_dir.join ( " a.txt" ) ) .unwrap ( ) , b " a\n " ) ;
3604
assert_ne! ( ws.workspace ( ) , repo.workspace ( ) ) ;
3605
let list = repo.workspaces ( ) .unwrap ( ) ;
3606
assert_eq! ( list.len ( ) , 2 ) ;
75167672 Andy McCall 1 month ago
3607
assert! ( list
3608
.iter ( )
3609
.any ( | w | w.id = = ws.workspace ( ) & & w.path = = ws_dir.canonicalize ( ) .unwrap ( ) & & ! w.current ) ) ;
4e88e774 Andy McCall 1 month ago ↶
3610
assert! ( ! ws.snapshot ( " status" ) .unwrap ( ) .changed) ;
3611
3612
3613
write ( & ws_dir, " b.txt" , b " b\n " ) ;
3614
let s = ws.snapshot ( " status" ) .unwrap ( ) ;
3615
assert_eq! ( s.statuses.len ( ) , 1 ) ;
75167672 Andy McCall 1 month ago
3616
assert! (
3617
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
3618
" the first workspace is untouched"
3619
) ;
4e88e774 Andy McCall 1 month ago ↶
3620
let h2 = ws.commit ( " b from ws2" , & [ ] ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3621
repo.refresh ( ) .unwrap ( ) ;
4e88e774 Andy McCall 1 month ago ↶
3622
let mut heads = repo.bookmark_heads ( ) .unwrap ( ) ;
3623
heads.sort ( ) ;
3624
let mut want = vec! [ h1, h2] ;
3625
want.sort ( ) ;
3626
assert_eq! ( heads, want, " both workspaces see the bookmark move" ) ;
3627
assert! ( ! main_dir.join ( " b.txt" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
3628
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " co" )
3629
.unwrap ( ) ;
4e88e774 Andy McCall 1 month ago ↶
3630
assert! ( main_dir.join ( " b.txt" ) .exists ( ) ) ;
3631
3632
3633
let reopened = Repo:: open( & ws_dir) .unwrap ( ) ;
3634
assert_eq! ( reopened.workspace ( ) , ws.workspace ( ) ) ;
3635
assert_eq! ( reopened.id ( ) .unwrap ( ) , repo.id ( ) .unwrap ( ) ) ;
3636
assert_eq! ( reopened.quick_status ( ) .unwrap ( ) .heads, 2 ) ;
3637
3638
3639
assert! ( repo.workspace_add ( & main_dir.join ( " inner" ) , None ) .is_err ( ) ) ;
3640
assert! ( repo.workspace_forget ( & repo.workspace ( ) ) .is_err ( ) ) ;
3641
repo.workspace_forget ( & ws.workspace ( ) ) .unwrap ( ) ;
3642
assert_eq! ( repo.workspaces ( ) .unwrap ( ) .len ( ) , 1 ) ;
3643
assert! ( ws_dir.join ( " b.txt" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
3644
assert! (
3645
ws.snapshot ( " status" ) .is_err ( ) ,
3646
" a forgotten workspace is no longer in the view"
3647
) ;
4e88e774 Andy McCall 1 month ago ↶
3648
6caacfff Andy McCall 1 month ago ↶
3649
3650
4e88e774 Andy McCall 1 month ago ↶
3651
let lock = repo.lock_snapshot ( ) .unwrap ( ) ;
6caacfff Andy McCall 1 month ago ↶
3652
let again = repo.lock_working_copy ( ) .unwrap ( ) ;
3653
drop ( again) ;
4e88e774 Andy McCall 1 month ago ↶
3654
drop ( lock) ;
75167672 Andy McCall 1 month ago
3655
let lock_path = main_dir
3656
.join ( " .oxy/workspaces" )
3657
.join ( repo.workspace ( ) .to_hex ( ) )
3658
.join ( " lock" ) ;
4e88e774 Andy McCall 1 month ago ↶
3659
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 120 ) ;
6caacfff Andy McCall 1 month ago ↶
3660
let age = | p : & Path| fs:: File:: open( p) .unwrap ( ) .set_modified ( old) .unwrap ( ) ;
3661
fs:: write( & lock_path, std:: process:: id( ) .to_string ( ) ) .unwrap ( ) ;
3662
age ( & lock_path) ;
3663
assert! (
3664
! super :: workspace:: lock_is_stale( & lock_path) ,
3665
" a live process keeps its lock"
3666
) ;
3667
fs:: write( & lock_path, " 4294967295" ) .unwrap ( ) ;
3668
age ( & lock_path) ;
4e88e774 Andy McCall 1 month ago ↶
3669
assert! ( repo.lock_snapshot ( ) .is_ok ( ) , " stale lock taken over" ) ;
3670
}
cdb5beba Andy McCall 1 month ago ↶
3671
3672
# [ test ]
3673
fn attributes_binary_eol_nocompress_lockable ( ) {
3674
hermetic ( ) ;
3675
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3676
let root = dir.path ( ) ;
3677
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
3678
write (
3679
root,
3680
" .oxyattributes" ,
3681
b " *.dat binary nocompress\n *.txt eol=crlf\n assets/** lockable\n " ,
3682
) ;
cdb5beba Andy McCall 1 month ago ↶
3683
write ( root, " plain.dat" , b " no nul bytes here\n " ) ;
3684
write ( root, " notes.txt" , b " one\r \n two\r \n " ) ;
3685
write ( root, " assets/model.bin" , b " \0 model" ) ;
3686
let h = repo.commit ( " attrs" , & [ ] ) .unwrap ( ) ;
3687
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
3688
assert! (
3689
snap.files[ " plain.dat" ] .mode.opaque,
3690
" binary attribute forces opaque without NUL bytes"
3691
) ;
3692
assert! (
3693
matches! ( & snap.files[ " notes.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n " ) ,
3694
" stored with LF"
3695
) ;
3696
assert! (
3697
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
3698
" CRLF on disk equals LF in the state"
3699
) ;
cdb5beba Andy McCall 1 month ago ↶
3700
3701
let r = repo.gc ( true ) .unwrap ( ) ;
3702
assert! ( r.packed.objects > 0 ) ;
3703
3704
fs:: remove_file( root.join ( " notes.txt" ) ) .unwrap ( ) ;
3705
repo.checkout ( & [ h] , true , " co" ) .unwrap ( ) ;
3706
assert_eq! ( fs:: read( root.join ( " notes.txt" ) ) .unwrap ( ) , b " one\r \n two\r \n " ) ;
75167672 Andy McCall 1 month ago
3707
assert! ( fs:: metadata( root.join ( " assets/model.bin" ) )
3708
.unwrap ( )
3709
.permissions ( )
3710
.readonly ( ) ) ;
cdb5beba Andy McCall 1 month ago ↶
3711
assert! ( repo.lock_path ( " plain.dat" ) .is_err ( ) , " not lockable" ) ;
3712
repo.lock_path ( " assets/model.bin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3713
assert! ( ! fs:: metadata( root.join ( " assets/model.bin" ) )
3714
.unwrap ( )
3715
.permissions ( )
3716
.readonly ( ) ) ;
cdb5beba Andy McCall 1 month ago ↶
3717
assert! ( repo.is_locked ( " assets/model.bin" ) ) ;
3718
write ( root, " assets/model.bin" , b " \0 model2" ) ;
3719
repo.commit ( " edit model" , & [ ] ) .unwrap ( ) ;
3720
repo.unlock_path ( " assets/model.bin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3721
assert! ( fs:: metadata( root.join ( " assets/model.bin" ) )
3722
.unwrap ( )
3723
.permissions ( )
3724
.readonly ( ) ) ;
cdb5beba Andy McCall 1 month ago ↶
3725
3726
repo.checkout ( & [ h] , true , " co" ) .unwrap ( ) ;
3727
assert_eq! ( fs:: read( root.join ( " assets/model.bin" ) ) .unwrap ( ) , b " \0 model" ) ;
3728
}
6d3eefbc Andy McCall 1 month ago ↶
3729
3730
# [ cfg ( unix ) ]
3731
# [ test ]
3732
fn hooks_can_veto_observe_and_add_dependencies ( ) {
3733
use std:: os:: unix:: fs:: PermissionsExt;
3734
hermetic ( ) ;
3735
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3736
let root = dir.path ( ) ;
3737
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
3738
let hooks = root.join ( " .oxy/hooks" ) ;
3739
fs:: create_dir_all( & hooks) .unwrap ( ) ;
3740
let hook = | name : & str , body : & str | {
3741
let p = hooks.join ( name) ;
3742
fs:: write( & p, format! ( " #!/bin/sh\n {body} \n " ) ) .unwrap ( ) ;
3743
fs:: set_permissions( & p, fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
3744
} ;
3745
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" ) ;
3746
hook ( " post-commit" , " cat > \" $OXY_ROOT/.last-post-commit\" " ) ;
3747
hook ( " post-checkout" , " cat > \" $OXY_ROOT/.last-post-checkout\" " ) ;
3748
hook ( " pre-push" , " echo pushing blocked >&2; exit 3" ) ;
3749
write ( root, " .oxyignore" , b " .last-*\n " ) ;
3750
write ( root, " a.txt" , b " a\n " ) ;
3751
let err = repo.commit ( " WIP: nope" , & [ ] ) .unwrap_err ( ) .to_string ( ) ;
3752
assert! ( err.contains ( " pre-commit refused: no WIP commits" ) , " {err}" ) ;
3753
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
3754
let pre = fs:: read_to_string( root.join ( " .last-pre-commit" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3755
assert! (
3756
pre.contains ( " \" event\" :\" pre-commit\" " )
3757
& & pre.contains ( " \" path\" :\" a.txt\" " )
3758
& & pre.contains ( " \" message\" :\" first\" " ) ,
3759
" {pre}"
3760
) ;
6d3eefbc Andy McCall 1 month ago ↶
3761
let post = fs:: read_to_string( root.join ( " .last-post-commit" ) ) .unwrap ( ) ;
3762
assert! ( post.contains ( & h1.to_hex ( ) ) , " {post}" ) ;
3763
repo.checkout ( & [ h1] , false , " co" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3764
assert! ( fs:: read_to_string( root.join ( " .last-post-checkout" ) )
3765
.unwrap ( )
3766
.contains ( " post-checkout" ) ) ;
6d3eefbc Andy McCall 1 month ago ↶
3767
repo.config.set ( " remote.origin.url" , " /nowhere" ) .unwrap ( ) ;
3768
repo.save_config ( ) .unwrap ( ) ;
3769
let err = repo.push ( " origin" , false ) .unwrap_err ( ) .to_string ( ) ;
3770
assert! ( err.contains ( " pre-push refused: pushing blocked" ) , " {err}" ) ;
3771
3772
3773
write ( root, " b.txt" , b " b\n " ) ;
75167672 Andy McCall 1 month ago
3774
hook (
3775
" semantic-deps" ,
3776
& format! (
3777
" cat > \" $OXY_ROOT/.last-semantic\" ; echo '{{ \" deps\" : [\" {} \" ]}} '" ,
3778
h1.to_hex ( )
3779
) ,
3780
) ;
6d3eefbc Andy McCall 1 month ago ↶
3781
let h2 = repo.commit ( " b without hook" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3782
assert! (
3783
repo.store ( ) .get_change ( & h2) .unwrap ( ) .deps.is_empty ( ) ,
3784
" hook disabled by default"
3785
) ;
6d3eefbc Andy McCall 1 month ago ↶
3786
repo.config.set ( " hooks.semantic-deps" , " true" ) .unwrap ( ) ;
3787
repo.save_config ( ) .unwrap ( ) ;
3788
write ( root, " c.txt" , b " c\n " ) ;
3789
let h3 = repo.commit ( " c with hook" , & [ ] ) .unwrap ( ) ;
3790
assert_eq! ( repo.store ( ) .get_change ( & h3) .unwrap ( ) .deps, vec! [ h1] ) ;
3791
let sem = fs:: read_to_string( root.join ( " .last-semantic" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3792
assert! (
3793
sem.contains ( " \" touched_paths\" :[\" c.txt\" ]" ) & & sem.contains ( " candidate_deps" ) ,
3794
" {sem}"
3795
) ;
6d3eefbc Andy McCall 1 month ago ↶
3796
assert_eq! ( json_str ( " a\" b\\ c\n " ) , " \" a\\ \" b\\ \\ c\\ n\" " ) ;
3797
}
23b217d7 Andy McCall 1 month ago ↶
3798
3799
# [ test ]
3800
fn amend_into_older_change_and_split ( ) {
3801
hermetic ( ) ;
3802
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3803
let root = dir.path ( ) ;
3804
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
3805
repo.create_identity ( " Ada" , " a@b" , IdentityScope:: User)
3806
.unwrap ( ) ;
23b217d7 Andy McCall 1 month ago ↶
3807
repo.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
3808
write ( root, " a.txt" , b " one\n two\n " ) ;
3809
let h0 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
3810
write ( root, " a.txt" , b " one\n TWO\n " ) ;
3811
let h1 = repo.commit ( " edit two" , & [ ] ) .unwrap ( ) ;
3812
write ( root, " a.txt" , b " one\n TWO\n three\n " ) ;
3813
let h2 = repo.commit ( " add three" , & [ ] ) .unwrap ( ) ;
3814
let id1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) .change_id;
3815
3816
3817
write ( root, " a.txt" , b " one\n TWO!\n three\n " ) ;
3818
let h1b = repo.amend_into ( & h1, None ) .unwrap ( ) ;
3819
assert_ne! ( h1b, h1) ;
3820
let c1b = repo.store ( ) .get_change ( & h1b) .unwrap ( ) ;
3821
assert_eq! ( c1b.change_id, id1) ;
3822
assert_eq! ( c1b.deps, vec! [ h0] ) ;
75167672 Andy McCall 1 month ago
3823
assert! ( matches! (
3824
repo.verify_change ( & h1b) .unwrap ( ) ,
3825
SignatureStatus:: Valid { .. }
3826
) ) ;
23b217d7 Andy McCall 1 month ago ↶
3827
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
3828
assert_eq! ( heads.len ( ) , 1 ) ;
3829
let top = repo.store ( ) .get_change ( & heads[ 0 ] ) .unwrap ( ) ;
3830
assert_eq! ( top.message, " add three" ) ;
75167672 Andy McCall 1 month ago
3831
assert_eq! (
3832
top.deps.iter ( ) .copied ( ) .collect:: < BTreeSet< _ > > ( ) ,
3833
[ h0, h1b] .into_iter ( ) .collect ( ) ,
3834
" depends on the file's creator and the rewritten line"
3835
) ;
23b217d7 Andy McCall 1 month ago ↶
3836
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
3837
assert! (
3838
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n TWO!\n three\n " )
3839
) ;
3840
assert! (
3841
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
3842
" the working copy is clean after folding"
3843
) ;
23b217d7 Andy McCall 1 month ago ↶
3844
3845
let only = repo.load_state ( & [ h1b] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
3846
assert! (
3847
matches! ( & only.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n TWO!\n " )
3848
) ;
23b217d7 Andy McCall 1 month ago ↶
3849
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 ) ;
3850
3851
3852
write ( root, " a.txt" , b " one\n TWO!\n three!\n " ) ;
3853
let err = repo.amend_into ( & h1b, None ) .unwrap_err ( ) .to_string ( ) ;
3854
assert! ( err.contains ( " introduced after" ) , " {err}" ) ;
75167672 Andy McCall 1 month ago
3855
assert! (
3856
repo.snapshot ( " status" ) .unwrap ( ) .statuses.len ( ) = = 1 ,
3857
" nothing was changed by the refusal"
3858
) ;
23b217d7 Andy McCall 1 month ago ↶
3859
write ( root, " a.txt" , b " one\n TWO!\n three\n " ) ;
3860
3861
3862
write ( root, " b.txt" , b " b\n " ) ;
3863
write ( root, " c.txt" , b " c\n " ) ;
3864
let h3 = repo.commit ( " b and c" , & [ ] ) .unwrap ( ) ;
3865
let id3 = repo.store ( ) .get_change ( & h3) .unwrap ( ) .change_id;
3866
let ( p1, p2) = repo.split ( & h3, & [ " b.txt" .into ( ) ] ) .unwrap ( ) ;
3867
let c_1 = repo.store ( ) .get_change ( & p1) .unwrap ( ) ;
3868
let c_2 = repo.store ( ) .get_change ( & p2) .unwrap ( ) ;
3869
assert! ( c_1 .message.contains ( " part 1: b.txt" ) ) ;
3870
assert_eq! ( c_2 .change_id, id3) ;
3871
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
75167672 Andy McCall 1 month ago
3872
assert! (
3873
heads.contains ( & p1) & & heads.contains ( & p2) ,
3874
" independent parts are both heads"
3875
) ;
3876
let snap = repo
3877
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
3878
.unwrap ( )
3879
.materialize ( ) ;
23b217d7 Andy McCall 1 month ago ↶
3880
assert! ( snap.files.contains_key ( " b.txt" ) & & snap.files.contains_key ( " c.txt" ) ) ;
3881
let only1 = repo.load_state ( & [ p1] ) .unwrap ( ) .materialize ( ) ;
3882
assert! ( only1.files.contains_key ( " b.txt" ) & & ! only1.files.contains_key ( " c.txt" ) ) ;
3883
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
3884
assert! ( repo.split ( & p2, & [ " nope.txt" .into ( ) ] ) .is_err ( ) ) ;
3885
let _ = h2;
3886
}
28fc23d7 Andy McCall 1 month ago ↶
3887
3888
# [ test ]
3889
fn identity_lifecycle ( ) {
3890
hermetic ( ) ;
3891
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3892
let root = dir.path ( ) ;
3893
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
3894
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
3895
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
3896
.unwrap ( )
3897
.with_home ( root.join ( " home-a" ) ) ;
28fc23d7 Andy McCall 1 month ago ↶
3898
a.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3899
let ( key_a, _ ) = a
3900
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
3901
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3902
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
3903
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
3904
assert! ( a.policy_init ( ) .unwrap ( ) .applied) ;
3905
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
767ac6cd Andy McCall 19 days ago
3906
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3907
3908
75167672 Andy McCall 1 month ago
3909
let mut e = Repo:: init( & root.join ( " e" ) )
3910
.unwrap ( )
3911
.with_home ( root.join ( " home-e" ) ) ;
28fc23d7 Andy McCall 1 month ago ↶
3912
e.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3913
e.create_identity_with (
3914
" Eve" ,
3915
" [email protected] " ,
3916
IdentityScope:: User,
3917
Some ( Hlc:: now( ) .wall_ms - 1 ) ,
3918
)
3919
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3920
write ( & root.join ( " e" ) , " e.txt" , b " e\n " ) ;
3921
let he = e.commit ( " late" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3922
assert! (
3923
matches! ( e.verify_change ( & he) .unwrap ( ) , SignatureStatus:: Invalid( m) if m.contains ( " expired" ) )
3924
) ;
28fc23d7 Andy McCall 1 month ago ↶
3925
75167672 Andy McCall 1 month ago
3926
let r = e
3927
.renew_identity ( Some ( Hlc:: now( ) .wall_ms + 86_400_000 ) )
3928
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3929
assert_eq! ( e.identity ( & r.new) .unwrap ( ) .previous, Some ( r.old) ) ;
3930
write ( & root.join ( " e" ) , " e.txt" , b " e2\n " ) ;
3931
let he2 = e.commit ( " in time" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3932
assert! (
3933
matches! ( e.verify_change ( & he2) .unwrap ( ) , SignatureStatus:: Valid { key, .. } if key = = r.new)
3934
) ;
28fc23d7 Andy McCall 1 month ago ↶
3935
3936
3937
let r = a.rotate_identity ( ) .unwrap ( ) ;
3938
assert_eq! ( r.old, key_a) ;
3939
assert_eq! ( a.config.author_key, Some ( r.new) ) ;
3940
assert_eq! ( a.identity ( & r.new) .unwrap ( ) .previous, Some ( key_a) ) ;
3941
assert! ( r.policy.as_ref ( ) .unwrap ( ) .applied) ;
3942
let ( _ , sp) = a.current_policy ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3943
assert_eq! (
3944
( sp.policy.serial, sp.policy.roots.clone ( ) ) ,
3945
( 2 , vec! [ r.new] )
3946
) ;
3947
assert! (
3948
! sp.policy.revoked.contains ( & key_a) & & sp.policy.authorizes ( & r.new, " main" ) ,
3949
" superseded, not revoked"
3950
) ;
28fc23d7 Andy McCall 1 month ago ↶
3951
assert! ( sp.policy.succeeds ( & r.new, & key_a) & & ! sp.policy.succeeds ( & key_a, & r.new) ) ;
75167672 Andy McCall 1 month ago
3952
assert! (
3953
matches! ( a.verify_change ( & h1) .unwrap ( ) , SignatureStatus:: Valid { key, revoked: false , .. } if key = = key_a)
3954
) ;
28fc23d7 Andy McCall 1 month ago ↶
3955
write ( & root.join ( " a" ) , " a.txt" , b " a2\n " ) ;
3956
let h2 = a.commit ( " two" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3957
assert! (
3958
matches! ( a.verify_change ( & h2) .unwrap ( ) , SignatureStatus:: Valid { key, revoked: false , .. } if key = = r.new)
3959
) ;
28fc23d7 Andy McCall 1 month ago ↶
3960
3961
let res = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3962
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3963
assert! ( res.iter ( ) .all ( | x | x.ok ) , " {res:?}" ) ;
3964
assert_eq! ( origin.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 2 ) ;
3965
let key_a = r.new;
3966
3967
3968
75167672 Andy McCall 1 month ago
3969
let mut b = Repo:: clone( & url, & root.join ( " b" ) )
3970
.unwrap ( )
3971
.with_home ( root.join ( " home-b" ) ) ;
3972
let ( key_b, id_b) = b
3973
.create_identity ( " Bob" , " [email protected] " , IdentityScope:: User)
3974
.unwrap ( ) ;
7ba155f2 Andy McCall 1 month ago ↶
3975
let r = a.delegate_add ( id_b.clone ( ) , Scope:: ALL , None ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3976
assert_eq! ( r.old, key_a) ;
3977
let key_a2 = r.new;
75167672 Andy McCall 1 month ago
3978
assert_eq! (
7ba155f2 Andy McCall 1 month ago ↶
3979
a.identity ( & key_a2) .unwrap ( ) .delegates,
3980
vec! [ Delegate:: full( key_b) ]
3981
) ;
3982
assert_eq! (
75167672 Andy McCall 1 month ago
3983
a.identity ( & key_a2) .unwrap ( ) .ed25519_pk,
3984
a.identity ( & key_a) .unwrap ( ) .ed25519_pk
7ba155f2 Andy McCall 1 month ago ↶
3985
) ;
3986
assert! (
3987
a.delegate_add ( id_b.clone ( ) , Scope:: ALL , None ) .is_err ( ) ,
3988
" already a delegate"
75167672 Andy McCall 1 month ago
3989
) ;
28fc23d7 Andy McCall 1 month ago ↶
3990
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | x | x.ok ) ) ;
767ac6cd Andy McCall 19 days ago
3991
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3992
write ( & root.join ( " a" ) , " a.txt" , b " a3\n " ) ;
3993
let h3 = a.commit ( " three" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3994
assert! (
3995
matches! ( a.verify_change ( & h3) .unwrap ( ) , SignatureStatus:: Valid { key, .. } if key = = key_a2) ,
3996
" the same secret signs under the new KeyId"
3997
) ;
28fc23d7 Andy McCall 1 month ago ↶
3998
3999
a.set_user_config ( " core.sign" , " seal" ) .unwrap ( ) ;
4000
write ( & root.join ( " a" ) , " a.txt" , b " a4\n " ) ;
4001
let h4 = a.commit ( " four" , & [ ] ) .unwrap ( ) ;
4002
assert_eq! ( a.verify_change ( & h4) .unwrap ( ) , SignatureStatus:: Unsigned) ;
4003
let ( _ , sk_b) = b.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4004
a.store ( )
4005
.put_signature ( & h4, & sk_b.sign ( Domain:: Change, & key_b, & h4) )
4006
.unwrap ( ) ;
4007
assert! (
4008
matches! ( a.verify_change ( & h4) .unwrap ( ) , SignatureStatus:: Valid { key, .. } if key = = key_b)
4009
) ;
28fc23d7 Andy McCall 1 month ago ↶
4010
a.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
4011
4012
let r = a.delegate_remove ( & key_b) .unwrap ( ) ;
4013
assert! ( a.identity ( & r.new) .unwrap ( ) .delegates.is_empty ( ) ) ;
4014
assert! ( a.delegate_remove ( & key_b) .is_err ( ) ) ;
4015
let key_a3 = r.new;
4016
let res = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
4017
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4018
assert! ( res.iter ( ) .all ( | x | x.ok ) , " {res:?}" ) ;
4019
4020
4021
4022
assert! ( a.policy_root_add ( id_b.clone ( ) ) .unwrap ( ) .applied) ;
4023
assert! ( a.policy_root_add ( id_b.clone ( ) ) .is_err ( ) ) ;
4024
assert! ( a.policy_set_threshold ( 3 ) .is_err ( ) ) ;
4025
assert! ( a.policy_set_threshold ( 2 ) .unwrap ( ) .applied) ;
4026
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | x | x.ok ) ) ;
767ac6cd Andy McCall 19 days ago
4027
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4028
b.pull ( " origin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4029
assert_eq! (
4030
b.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.root_threshold,
4031
2
4032
) ;
4033
let mut c = Repo:: init( & root.join ( " c" ) )
4034
.unwrap ( )
4035
.with_home ( root.join ( " home-c" ) ) ;
4036
let ( key_c, id_c) = c
4037
.create_identity ( " Cy" , " [email protected] " , IdentityScope:: User)
4038
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4039
let serial_before = a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial;
4040
let proposal = a.policy_grant ( id_c, None ) .unwrap ( ) ;
4041
assert! ( ! proposal.applied & & proposal.sigs.len ( ) = = 1 ) ;
4042
assert_eq! ( proposal.policy.serial, serial_before + 1 ) ;
75167672 Andy McCall 1 month ago
4043
assert_eq! (
4044
a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial,
4045
serial_before,
4046
" a proposal changes nothing yet"
4047
) ;
28fc23d7 Andy McCall 1 month ago ↶
4048
4049
let bytes = proposal.signed ( ) .encode ( ) ;
75167672 Andy McCall 1 month ago
4050
let signed = b
4051
.policy_sign ( SignedPolicy:: decode( & bytes) .unwrap ( ) )
4052
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4053
assert! ( signed.applied & & signed.sigs.len ( ) = = 2 ) ;
75167672 Andy McCall 1 month ago
4054
assert! ( b
4055
.current_policy ( )
4056
.unwrap ( )
4057
.unwrap ( )
4058
.1
4059
.policy
4060
.authorizes ( & key_c, " main" ) ) ;
4061
assert! (
4062
b.policy_sign ( signed.signed ( ) ) .is_err ( ) ,
4063
" a stale proposal is refused"
4064
) ;
28fc23d7 Andy McCall 1 month ago ↶
4065
75167672 Andy McCall 1 month ago
4066
assert! ( b
4067
.push ( " origin" , false )
4068
.unwrap ( )
4069
.iter ( )
4070
.any ( | x | x.name = = " policy" & & x.ok ) ) ;
767ac6cd Andy McCall 19 days ago
4071
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4072
a.pull ( " origin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4073
assert_eq! (
4074
a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial,
4075
serial_before + 1
4076
) ;
28fc23d7 Andy McCall 1 month ago ↶
4077
4078
assert! ( a.policy_revoke ( & key_a3) .is_err ( ) ) ;
75167672 Andy McCall 1 month ago
4079
let u = a
4080
.revoke_identity ( & key_c, " left the project" )
4081
.unwrap ( )
4082
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4083
assert! ( ! u.applied) ;
4084
assert! ( a.is_revoked ( & key_c) .unwrap ( ) ) ;
4085
assert! ( ! b.is_revoked ( & key_c) .unwrap ( ) ) ;
4086
let u = b.policy_sign ( u.signed ( ) ) .unwrap ( ) ;
4087
assert! ( u.applied & & u.policy.is_revoked ( & key_c) ) ;
4088
assert! ( b.is_revoked ( & key_c) .unwrap ( ) ) ;
4089
75167672 Andy McCall 1 month ago
4090
assert! (
4091
b.policy_revoke ( & key_a3) .is_err ( ) ,
4092
" would leave one root for a threshold of 2"
4093
) ;
28fc23d7 Andy McCall 1 month ago ↶
4094
4095
let key_c_own = c.config.author_key.unwrap ( ) ;
75167672 Andy McCall 1 month ago
4096
assert! ( c
4097
.revoke_identity ( & key_c_own, " lost laptop" )
4098
.unwrap ( )
4099
.is_none ( ) ) ;
28fc23d7 Andy McCall 1 month ago ↶
4100
assert! ( c.is_revoked ( & key_c_own) .unwrap ( ) ) ;
4101
assert_eq! ( key_c_own, key_c) ;
4102
}
4103
6e723b81 Andy McCall 1 month ago ↶
4104
# [ test ]
4105
fn renames_are_detected_and_tracked_as_moves ( ) {
4106
hermetic ( ) ;
4107
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4108
let root = dir.path ( ) ;
4109
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
4110
write (
4111
root,
4112
" src/old.rs" ,
4113
b " fn a() {}\n fn b() {}\n fn c() {}\n fn d() {}\n " ,
4114
) ;
6e723b81 Andy McCall 1 month ago ↶
4115
write ( root, " img.bin" , b " \x89 PNG\0 \0 \x01 \x02 " ) ;
4116
write ( root, " tiny.txt" , b " x\n " ) ;
4117
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4118
let node_old = repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) .files[ " src/old.rs" ] .node;
4119
let node_img = repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) .files[ " img.bin" ] .node;
4120
4121
4122
4123
fs:: remove_file( root.join ( " src/old.rs" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4124
write (
4125
root,
4126
" lib/new.rs" ,
4127
b " fn a() {}\n fn b() {}\n fn c() {}\n fn e() {}\n " ,
4128
) ;
6e723b81 Andy McCall 1 month ago ↶
4129
fs:: remove_file( root.join ( " img.bin" ) ) .unwrap ( ) ;
4130
write ( root, " assets/img.bin" , b " \x89 PNG\0 \0 \x01 \x02 " ) ;
4131
fs:: remove_file( root.join ( " tiny.txt" ) ) .unwrap ( ) ;
4132
write ( root, " other.txt" , b " completely different\n " ) ;
4133
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4134
let statuses: Vec< ( & str , ChangeStatus) > = snap
4135
.statuses
4136
.iter ( )
4137
.map ( | s | ( s.path.as_str ( ) , s.status) )
4138
.collect ( ) ;
6e723b81 Andy McCall 1 month ago ↶
4139
assert_eq! (
4140
statuses,
75167672 Andy McCall 1 month ago
4141
vec! [
4142
( " assets/img.bin" , ChangeStatus:: Renamed) ,
4143
( " lib/new.rs" , ChangeStatus:: Renamed) ,
4144
( " other.txt" , ChangeStatus:: Added) ,
4145
( " src" , ChangeStatus:: Removed) ,
4146
( " tiny.txt" , ChangeStatus:: Removed)
4147
]
6e723b81 Andy McCall 1 month ago ↶
4148
) ;
4149
let h2 = repo.commit ( " move things" , & [ ] ) .unwrap ( ) ;
4150
let c = repo.store ( ) .get_change ( & h2) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4151
let moves: Vec< _ > = c
4152
.tree_ops
4153
.iter ( )
4154
.filter ( | op | matches! ( op, TreeOp:: Move { .. } ) )
4155
.collect ( ) ;
6e723b81 Andy McCall 1 month ago ↶
4156
assert_eq! ( moves.len ( ) , 2 , " {:?}" , c.tree_ops) ;
75167672 Andy McCall 1 month ago
4157
assert! ( c.tree_ops.iter ( ) .any (
4158
| op | matches! ( op, TreeOp:: Move { node, name, .. } if * node = = node_old & & name = = " new.rs" )
4159
) ) ;
4160
assert! ( c.tree_ops.iter ( ) .any (
4161
| op | matches! ( op, TreeOp:: Move { node, name, .. } if * node = = node_img & & name = = " img.bin" )
4162
) ) ;
6e723b81 Andy McCall 1 month ago ↶
4163
let snap = repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
4164
assert_eq! (
4165
snap.files[ " lib/new.rs" ] .node, node_old,
4166
" the node survives the rename"
4167
) ;
4168
assert! (
4169
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 " )
4170
) ;
6e723b81 Andy McCall 1 month ago ↶
4171
assert! ( ! snap.files.contains_key ( " src/old.rs" ) & & ! snap.files.contains_key ( " src" ) ) ;
75167672 Andy McCall 1 month ago
4172
assert! ( render_change (
4173
& c,
4174
& repo.load_state ( & [ h1] ) .unwrap ( ) ,
4175
& repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( )
4176
)
4177
.contains ( " R " ) ) ;
6e723b81 Andy McCall 1 month ago ↶
4178
4179
let other = tempfile:: tempdir( ) .unwrap ( ) ;
4180
let clone = Repo:: clone( & root.to_string_lossy ( ) , other.path ( ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4181
assert_eq! (
4182
fs:: read( other.path ( ) .join ( " lib/new.rs" ) ) .unwrap ( ) ,
4183
b " fn a() {}\n fn b() {}\n fn c() {}\n fn e() {}\n "
4184
) ;
6e723b81 Andy McCall 1 month ago ↶
4185
assert! ( ! other.path ( ) .join ( " src" ) .exists ( ) ) ;
4186
assert! ( ! clone.snapshot ( " status" ) .unwrap ( ) .changed) ;
4187
4188
assert_eq! ( similarity ( b " a\n b\n " , b " a\n b\n " ) , 100 ) ;
4189
assert_eq! ( similarity ( b " a\n b\n c\n d\n " , b " a\n b\n x\n y\n " ) , 50 ) ;
4190
assert_eq! ( similarity ( b " hello\n " , b " world\n " ) , 0 ) ;
4191
assert_eq! ( similarity ( b " " , b " x\n " ) , 0 ) ;
4192
}
4193
30ea9c1e Andy McCall 1 month ago ↶
4194
# [ test ]
4195
fn git_fetch_and_push_round_trip ( ) {
4196
hermetic ( ) ;
75167672 Andy McCall 1 month ago
4197
if std:: process:: Command:: new( " git" )
4198
.arg ( " --version" )
4199
.output ( )
4200
.is_err ( )
4201
{
30ea9c1e Andy McCall 1 month ago ↶
4202
eprintln! ( " git not installed; skipping" ) ;
4203
return ;
4204
}
4205
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4206
let root = dir.path ( ) ;
4207
let git_in = | d : & Path, args : & [& str ]| -> String {
4208
let out = std:: process:: Command:: new( " git" )
4209
.arg ( " -C" )
4210
.arg ( d)
75167672 Andy McCall 1 month ago
4211
.args ( [
4212
" -c" ,
4213
" user.name=Grace" ,
4214
" -c" ,
4215
" [email protected] " ,
4216
" -c" ,
4217
" commit.gpgsign=false" ,
4218
] )
30ea9c1e Andy McCall 1 month ago ↶
4219
.args ( args)
4220
.output ( )
4221
.unwrap ( ) ;
75167672 Andy McCall 1 month ago
4222
assert! (
4223
out.status.success ( ) ,
4224
" git {args:?}: {}" ,
4225
String :: from_utf8_lossy( & out.stderr)
4226
) ;
30ea9c1e Andy McCall 1 month ago ↶
4227
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
4228
} ;
4229
4230
let remote = root.join ( " remote.git" ) ;
4231
fs:: create_dir_all( & remote) .unwrap ( ) ;
4232
git_in ( & remote, & [ " init" , " -q" , " --bare" , " -b" , " main" ] ) ;
4233
let work = root.join ( " work" ) ;
75167672 Andy McCall 1 month ago
4234
git_in (
4235
root,
4236
& [
4237
" clone" ,
4238
" -q" ,
4239
remote.to_str ( ) .unwrap ( ) ,
4240
work.to_str ( ) .unwrap ( ) ,
4241
] ,
4242
) ;
30ea9c1e Andy McCall 1 month ago ↶
4243
git_in ( & work, & [ " checkout" , " -q" , " -b" , " main" ] ) ;
4244
write ( & work, " README.md" , b " # Project\n " ) ;
4245
write ( & work, " src/lib.rs" , b " pub fn a() {}\n " ) ;
4246
git_in ( & work, & [ " add" , " ." ] ) ;
4247
git_in ( & work, & [ " commit" , " -q" , " -m" , " initial" ] ) ;
4248
write ( & work, " src/lib.rs" , b " pub fn a() {}\n pub fn b() {}\n " ) ;
4249
git_in ( & work, & [ " commit" , " -q" , " -am" , " second" ] ) ;
4250
git_in ( & work, & [ " push" , " -q" , " origin" , " main" ] ) ;
4251
let tip1 = git_in ( & work, & [ " rev-parse" , " HEAD" ] ) ;
4252
4253
4254
let o = root.join ( " oxy" ) ;
4255
let mut repo = Repo:: init( & o) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
4256
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
4257
.unwrap ( ) ;
30ea9c1e Andy McCall 1 month ago ↶
4258
let url = remote.to_string_lossy ( ) .to_string ( ) ;
4259
let r = repo.git_fetch ( & url, " main" ) .unwrap ( ) ;
4260
assert_eq! ( ( r.commits, r.heads) , ( 2 , 1 ) ) ;
75167672 Andy McCall 1 month ago
4261
assert_eq! (
4262
fs:: read( o.join ( " src/lib.rs" ) ) .unwrap ( ) ,
4263
b " pub fn a() {}\n pub fn b() {}\n "
4264
) ;
30ea9c1e Andy McCall 1 month ago ↶
4265
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
75167672 Andy McCall 1 month ago
4266
assert_eq! (
4267
repo.git_fetch ( & url, " main" ) .unwrap ( ) .commits,
4268
0 ,
4269
" nothing new"
4270
) ;
30ea9c1e Andy McCall 1 month ago ↶
4271
4272
75167672 Andy McCall 1 month ago
4273
write (
4274
& o,
4275
" src/lib.rs" ,
4276
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n " ,
4277
) ;
30ea9c1e Andy McCall 1 month ago ↶
4278
write ( & o, " NOTES.md" , b " from oxy\n " ) ;
4279
let h_oxy = repo.commit ( " add c and notes" , & [ ] ) .unwrap ( ) ;
4280
let r = repo.git_push ( & url, " main" ) .unwrap ( ) ;
4281
assert_eq! ( r.exported, 1 , " {r:?}" ) ;
4282
git_in ( & work, & [ " pull" , " -q" , " --ff-only" , " origin" , " main" ] ) ;
75167672 Andy McCall 1 month ago
4283
assert_eq! (
4284
git_in ( & work, & [ " log" , " --format=%s" , " main" ] ) ,
4285
" add c and notes\n second\n initial"
4286
) ;
30ea9c1e Andy McCall 1 month ago ↶
4287
assert_eq! ( git_in ( & work, & [ " rev-parse" , " HEAD~1" ] ) , tip1) ;
4288
assert_eq! ( fs:: read( work.join ( " NOTES.md" ) ) .unwrap ( ) , b " from oxy\n " ) ;
4289
assert_eq! ( git_in ( & work, & [ " log" , " -1" , " --format=%an" , " main" ] ) , " Ada" ) ;
75167672 Andy McCall 1 month ago
4290
assert! ( git_in ( & work, & [ " log" , " -1" , " --format=%B" , " main" ] )
4291
.contains ( & format! ( " Oxy-Hash: {} " , h_oxy.to_hex ( ) ) ) ) ;
4292
assert_eq! (
4293
repo.git_push ( & url, " main" ) .unwrap ( ) .exported,
4294
0 ,
4295
" already pushed"
4296
) ;
30ea9c1e Andy McCall 1 month ago ↶
4297
4298
4299
4300
write ( & work, " README.md" , b " # Project\n \n More.\n " ) ;
4301
git_in ( & work, & [ " commit" , " -q" , " -am" , " readme from git" ] ) ;
4302
git_in ( & work, & [ " push" , " -q" , " origin" , " main" ] ) ;
4303
let tip2 = git_in ( & work, & [ " rev-parse" , " HEAD" ] ) ;
75167672 Andy McCall 1 month ago
4304
assert! ( repo
4305
.git_push ( & url, " main" )
4306
.unwrap_err ( )
4307
.to_string ( )
4308
.contains ( " fetch" ) ) ;
30ea9c1e Andy McCall 1 month ago ↶
4309
let r = repo.git_fetch ( & url, " main" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4310
assert_eq! (
4311
( r.commits, r.heads) ,
4312
( 1 , 2 ) ,
4313
" the Git change and the oxy change commute"
4314
) ;
4315
assert_eq! (
4316
fs:: read( o.join ( " README.md" ) ) .unwrap ( ) ,
4317
b " # Project\n \n More.\n "
4318
) ;
30ea9c1e Andy McCall 1 month ago ↶
4319
assert_eq! ( fs:: read( o.join ( " NOTES.md" ) ) .unwrap ( ) , b " from oxy\n " ) ;
4320
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4321
4322
write ( & o, " NOTES.md" , b " from oxy\n again\n " ) ;
4323
repo.commit ( " notes again" , & [ ] ) .unwrap ( ) ;
4324
assert_eq! ( repo.git_push ( & url, " main" ) .unwrap ( ) .exported, 1 ) ;
4325
git_in ( & work, & [ " pull" , " -q" , " --ff-only" , " origin" , " main" ] ) ;
4326
assert_eq! ( git_in ( & work, & [ " rev-parse" , " HEAD~1" ] ) , tip2) ;
75167672 Andy McCall 1 month ago
4327
assert_eq! (
4328
fs:: read( work.join ( " NOTES.md" ) ) .unwrap ( ) ,
4329
b " from oxy\n again\n "
4330
) ;
4331
assert_eq! (
4332
git_in ( & work, & [ " log" , " --format=%s" , " main" ] )
4333
.lines ( )
4334
.count ( ) ,
4335
5
4336
) ;
30ea9c1e Andy McCall 1 month ago ↶
4337
}
4338
2b872d07 Andy McCall 1 month ago ↶
4339
4340
4341
# [ test ]
4342
# [ ignore ]
4343
fn bench_one_gibibyte_file ( ) {
4344
hermetic ( ) ;
4345
4346
let dir = match std:: env:: var_os( " OXY_BENCH_DIR" ) {
4347
Some ( d) => tempfile:: tempdir_in( d) .unwrap ( ) ,
4348
None => tempfile:: tempdir( ) .unwrap ( ) ,
4349
} ;
4350
let root = dir.path ( ) ;
75167672 Andy McCall 1 month ago
4351
let repo = Repo:: init( & root.join ( " r" ) )
4352
.unwrap ( )
4353
.with_home ( root.join ( " home" ) ) ;
2b872d07 Andy McCall 1 month ago ↶
4354
let size = 1 usize < < 30 ;
4355
let mut data = vec! [ 0 u8 ; size] ;
4356
let mut x = 0x9e3779b97f4a7c15 u64 ;
4357
for chunk in data.chunks_mut ( 8 ) {
4358
x ^ = x < < 13 ;
4359
x ^ = x > > 7 ;
4360
x ^ = x < < 17 ;
4361
let b = x.to_le_bytes ( ) ;
4362
chunk.copy_from_slice ( & b[ .. chunk.len ( ) ] ) ;
4363
}
4364
let t = std:: time:: Instant:: now( ) ;
4365
write ( & root.join ( " r" ) , " big.bin" , & data) ;
4366
eprintln! ( " write 1 GiB to disk: {:.2?}" , t.elapsed ( ) ) ;
4367
let t = std:: time:: Instant:: now( ) ;
4368
let h = repo.commit ( " big" , & [ ] ) .unwrap ( ) ;
4369
eprintln! ( " commit (chunk + hash + store): {:.2?}" , t.elapsed ( ) ) ;
4370
let t = std:: time:: Instant:: now( ) ;
4371
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4372
eprintln! ( " status on a clean 1 GiB working copy: {:.2?}" , t.elapsed ( ) ) ;
4373
let t = std:: time:: Instant:: now( ) ;
4374
let objects = repo.store ( ) .list ( ) .unwrap ( ) .len ( ) ;
4375
eprintln! ( " objects stored: {objects} ({:.2?} to list)" , t.elapsed ( ) ) ;
4376
let t = std:: time:: Instant:: now( ) ;
4377
let g = repo.gc ( true ) .unwrap ( ) ;
4378
eprintln! ( " gc (pack): {:.2?} {g:?}" , t.elapsed ( ) ) ;
4379
let t = std:: time:: Instant:: now( ) ;
4380
let clone = Repo:: clone( & root.join ( " r" ) .to_string_lossy ( ) , & root.join ( " c" ) ) .unwrap ( ) ;
4381
eprintln! ( " clone (fetch + checkout): {:.2?}" , t.elapsed ( ) ) ;
4382
let t = std:: time:: Instant:: now( ) ;
4383
let back = fs:: read( root.join ( " c/big.bin" ) ) .unwrap ( ) ;
4384
assert_eq! ( blake3:: hash( & back) , blake3:: hash( & data) ) ;
4385
eprintln! ( " read back and verify: {:.2?}" , t.elapsed ( ) ) ;
4386
assert_eq! ( clone.bookmark_heads ( ) .unwrap ( ) , vec! [ h] ) ;
4387
let store_bytes: u64 = walkdir_size ( & root.join ( " r/.oxy/objects" ) ) ;
75167672 Andy McCall 1 month ago
4388
eprintln! (
4389
" store size: {:.1} MiB for 1 GiB of data" ,
4390
store_bytes as f64 / ( 1024. 0 * 1024. 0 )
4391
) ;
2b872d07 Andy McCall 1 month ago ↶
4392
}
4393
4394
fn walkdir_size ( p : & Path) -> u64 {
4395
let mut total = 0 ;
4396
if let Ok ( rd) = fs:: read_dir( p) {
4397
for e in rd.flatten ( ) {
4398
let path = e.path ( ) ;
4399
if path.is_dir ( ) {
4400
total + = walkdir_size ( & path) ;
4401
} else if let Ok ( m) = fs:: metadata( & path) {
4402
total + = m.len ( ) ;
4403
}
4404
}
4405
}
4406
total
4407
}
4408
b7f102fc Andy McCall 1 month ago ↶
4409
4410
4411
# [ cfg ( unix ) ]
4412
# [ test ]
4413
fn stat_cache_skips_reading_unchanged_opaque_files ( ) {
4414
use std:: os:: unix:: fs:: PermissionsExt;
4415
hermetic ( ) ;
4416
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4417
let root = dir.path ( ) ;
4418
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4419
let mut big = vec! [ 0 u8 ; 300 * 1024 ] ;
4420
for ( i, b) in big.iter_mut ( ) .enumerate ( ) {
4421
* b = ( i as u32 ) .wrapping_mul ( 2654435761 ) .to_le_bytes ( ) [ 1 ] ;
4422
}
4423
big[ 0 ] = 0 ;
4424
write ( root, " big.bin" , & big) ;
4425
write ( root, " a.txt" , b " text\n " ) ;
4426
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4427
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4428
assert! (
4429
m.entries[ " big.bin" ] .blob.is_some ( ) ,
4430
" opaque files record their blob"
4431
) ;
b7f102fc Andy McCall 1 month ago ↶
4432
assert! ( m.entries[ " a.txt" ] .blob.is_none ( ) ) ;
4433
let blob = m.entries[ " big.bin" ] .blob.unwrap ( ) ;
4434
assert! ( repo.store ( ) .has ( & blob) ) ;
4435
4436
4437
4438
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
75167672 Andy McCall 1 month ago
4439
fs:: File:: options( )
4440
.write ( true )
4441
.open ( root.join ( " big.bin" ) )
4442
.unwrap ( )
4443
.set_modified ( old)
4444
.unwrap ( ) ;
b7f102fc Andy McCall 1 month ago ↶
4445
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4446
4447
fs:: set_permissions( root.join ( " big.bin" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
4448
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4449
assert! (
4450
! snap.changed & & snap.statuses.is_empty ( ) ,
4451
" {:?}" ,
4452
snap.statuses
4453
) ;
b7f102fc Andy McCall 1 month ago ↶
4454
fs:: set_permissions( root.join ( " big.bin" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
4455
4456
let mut big2 = big.clone ( ) ;
4457
big2[ 10 ] ^ = 0xff ;
4458
write ( root, " big.bin" , & big2) ;
4459
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4460
assert_eq! (
4461
snap.statuses
4462
.iter ( )
4463
.map ( | s | ( s.path.as_str ( ) , s.status) )
4464
.collect:: < Vec< _ > > ( ) ,
4465
vec! [ ( " big.bin" , ChangeStatus:: Modified) ]
4466
) ;
b7f102fc Andy McCall 1 month ago ↶
4467
let h2 = repo.commit ( " edit" , & [ ] ) .unwrap ( ) ;
4468
let files = repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
4469
assert_eq! (
4470
files[ " big.bin" ] .content,
4471
FileContent:: Opaque {
4472
blobs: vec! [ Repo:: blob_ref( & big2) ]
4473
}
4474
) ;
b7f102fc Andy McCall 1 month ago ↶
4475
4476
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
4477
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
4478
assert_eq! ( m.entries[ " big.bin" ] .blob, Some ( Repo:: blob_ref( & big) ) ) ;
4479
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4480
}
4481
50a046e4 Andy McCall 1 month ago ↶
4482
# [ test ]
4483
fn status_json_lists_conflicts_with_contributors ( ) {
4484
hermetic ( ) ;
4485
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4486
let root = dir.path ( ) ;
4487
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4488
write ( root, " a.txt" , b " one\n " ) ;
4489
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4490
write ( root, " a.txt" , b " one\n left\n " ) ;
4491
let ha = repo.commit ( " left" , & [ ] ) .unwrap ( ) ;
4492
repo.checkout ( & [ h1] , true , " checkout" ) .unwrap ( ) ;
4493
write ( root, " a.txt" , b " one\n right\n " ) ;
4494
let hb = repo.commit ( " right" , & [ ] ) .unwrap ( ) ;
4495
repo.checkout ( & [ ha, hb] , true , " checkout" ) .unwrap ( ) ;
4496
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4497
assert! ( snap.base_snapshot.is_conflicted ( ) ) ;
4498
let json = repo.status_json ( & snap) .unwrap ( ) ;
4499
let ida = repo.store ( ) .get_change ( & ha) .unwrap ( ) .change_id.to_hex ( ) ;
4500
let idb = repo.store ( ) .get_change ( & hb) .unwrap ( ) .change_id.to_hex ( ) ;
4501
assert! ( json.contains ( " \" conflicted\" :true" ) , " {json}" ) ;
75167672 Andy McCall 1 month ago
4502
assert! (
4503
json.contains ( " \" kind\" :\" text\" ,\" path\" :\" a.txt\" " ) ,
4504
" {json}"
4505
) ;
50a046e4 Andy McCall 1 month ago ↶
4506
assert! ( json.contains ( & ida) & & json.contains ( & idb) , " {json}" ) ;
4507
assert! ( json.contains ( " \" resolved\" :false" ) , " {json}" ) ;
4508
assert! ( json.starts_with ( ' {' ) & & json.ends_with ( ' }' ) ) ;
4509
4510
write ( root, " a.txt" , b " one\n left\n right\n " ) ;
4511
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4512
let json = repo.status_json ( & snap) .unwrap ( ) ;
4513
assert! ( json.contains ( " \" resolved\" :true" ) , " {json}" ) ;
4514
repo.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
4515
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4516
let json = repo.status_json ( & snap) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4517
assert! (
4518
json.contains ( " \" clean\" :true,\" conflicted\" :false,\" paths\" :[],\" conflicts\" :[]" ) ,
4519
" {json}"
4520
) ;
50a046e4 Andy McCall 1 month ago ↶
4521
}
4522
9ac1fbe2 Andy McCall 1 month ago ↶
4523
# [ test ]
4524
fn ignore_rules_do_not_untrack_files ( ) {
4525
hermetic ( ) ;
4526
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4527
let root = dir.path ( ) ;
4528
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4529
write ( root, " src.c" , b " int x;\n " ) ;
4530
write ( root, " build/out.o" , b " \0 obj" ) ;
4531
write ( root, " sub/keep.log" , b " keep\n " ) ;
4532
write ( root, " sub/junk.log" , b " junk\n " ) ;
4533
write ( root, " .oxyignore" , b " /build\n *.log\n !sub/keep.log\n " ) ;
4534
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4535
let added: Vec< & str > = snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect ( ) ;
75167672 Andy McCall 1 month ago
4536
assert_eq! (
4537
added,
4538
vec! [ " .oxyignore" , " src.c" , " sub/keep.log" ] ,
4539
" anchored, glob, and negated rules"
4540
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4541
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4542
4543
write ( root, " .oxyignore" , b " /build\n *.log\n !sub/keep.log\n src.c\n " ) ;
4544
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4545
assert_eq! (
4546
snap.statuses
4547
.iter ( )
4548
.map ( | s | s.path.as_str ( ) )
4549
.collect:: < Vec< _ > > ( ) ,
4550
vec! [ " .oxyignore" ]
4551
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4552
let h2 = repo.commit ( " ignore src.c" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4553
assert! ( repo
4554
.load_state ( & [ h2] )
4555
.unwrap ( )
4556
.materialize ( )
4557
.files
4558
.contains_key ( " src.c" ) ) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4559
4560
assert_eq! ( repo.quick_dirty ( ) .unwrap ( ) , Some ( false ) ) ;
4561
write ( root, " src.c" , b " int x, y;\n " ) ;
4562
assert_eq! ( repo.quick_dirty ( ) .unwrap ( ) , Some ( true ) ) ;
4563
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4564
assert_eq! (
4565
snap.statuses
4566
.iter ( )
4567
.map ( | s | ( s.path.as_str ( ) , s.status) )
4568
.collect:: < Vec< _ > > ( ) ,
4569
vec! [ ( " src.c" , ChangeStatus:: Modified) ]
4570
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4571
let h3 = repo.commit ( " edit src.c" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4572
assert! (
4573
matches! ( & repo.load_state ( & [ h3] ) .unwrap ( ) .materialize ( ) .files[ " src.c" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " int x, y;\n " )
4574
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4575
4576
fs:: remove_file( root.join ( " src.c" ) ) .unwrap ( ) ;
4577
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4578
assert_eq! (
4579
snap.statuses
4580
.iter ( )
4581
.map ( | s | ( s.path.as_str ( ) , s.status) )
4582
.collect:: < Vec< _ > > ( ) ,
4583
vec! [ ( " src.c" , ChangeStatus:: Removed) ]
4584
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4585
repo.checkout ( & [ h3] , true , " checkout" ) .unwrap ( ) ;
4586
assert_eq! ( fs:: read( root.join ( " src.c" ) ) .unwrap ( ) , b " int x, y;\n " ) ;
4587
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4588
let _ = h1;
4589
}
4590
b837d2b5 Andy McCall 1 month ago ↶
4591
# [ test ]
4592
fn split_works_on_non_heads_by_node ( ) {
4593
hermetic ( ) ;
4594
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4595
let root = dir.path ( ) ;
4596
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4597
write ( root, " a.txt" , b " one\n " ) ;
4598
write ( root, " junk/x.lock" , b " lock\n " ) ;
4599
let h1 = repo.commit ( " work plus junk" , & [ ] ) .unwrap ( ) ;
4600
write ( root, " a.txt" , b " one\n two\n " ) ;
4601
let h2 = repo.commit ( " more work" , & [ ] ) .unwrap ( ) ;
4602
assert_eq! ( repo.store ( ) .get_change ( & h2) .unwrap ( ) .deps, vec! [ h1] ) ;
4603
let id1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) .change_id;
4604
4605
let ( hj, h1b) = repo.split ( & h1, & [ " junk" .into ( ) ] ) .unwrap ( ) ;
4606
assert_ne! ( h1b, h1) ;
75167672 Andy McCall 1 month ago
4607
assert_eq! (
4608
repo.store ( ) .get_change ( & h1b) .unwrap ( ) .change_id,
4609
id1,
4610
" the rest keeps the ChangeId"
4611
) ;
b837d2b5 Andy McCall 1 month ago ↶
4612
let junk = repo.store ( ) .get_change ( & hj) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4613
assert! ( junk.tree_ops.iter ( ) .all (
4614
| op | matches! ( op, TreeOp:: AddNode { name, .. } if name = = " junk" | | name = = " x.lock" )
4615
) ) ;
b837d2b5 Andy McCall 1 month ago ↶
4616
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4617
assert! (
4618
heads.contains ( & hj) ,
4619
" the split-off part is a head of its own"
4620
) ;
b837d2b5 Andy McCall 1 month ago ↶
4621
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
4622
assert! (
4623
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n " ) ,
4624
" the dependent was rewritten onto the new hash"
4625
) ;
b837d2b5 Andy McCall 1 month ago ↶
4626
assert! ( snap.files.contains_key ( " junk/x.lock" ) ) ;
75167672 Andy McCall 1 month ago
4627
let h2b = heads
4628
.iter ( )
4629
.find ( | h | repo.store ( ) .get_change ( h) .unwrap ( ) .message = = " more work" )
4630
.copied ( )
4631
.unwrap ( ) ;
b837d2b5 Andy McCall 1 month ago ↶
4632
assert_eq! ( repo.store ( ) .get_change ( & h2b) .unwrap ( ) .deps, vec! [ h1b] ) ;
4633
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4634
4635
repo.abandon ( & hj) .unwrap ( ) ;
4636
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
4637
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
4638
assert! ( ! snap.files.contains_key ( " junk/x.lock" ) & & ! snap.files.contains_key ( " junk" ) ) ;
4639
assert! ( snap.files.contains_key ( " a.txt" ) ) ;
4640
4641
write ( root, " junk/y.lock" , b " y\n " ) ;
4642
let hb = repo.commit ( " junk base" , & [ ] ) .unwrap ( ) ;
4643
write ( root, " junk/y.lock" , b " yy\n " ) ;
4644
write ( root, " b.txt" , b " b\n " ) ;
4645
repo.commit ( " touches junk" , & [ ] ) .unwrap ( ) ;
4646
let err = repo.split ( & hb, & [ " junk" .into ( ) ] ) .unwrap_err ( ) .to_string ( ) ;
4647
assert! ( err.contains ( " depends on the split-off paths" ) , " {err}" ) ;
4648
}
4649
7b9809e6 Andy McCall 1 month ago ↶
4650
# [ test ]
4651
fn bookmarks_switch_merge_and_push ( ) {
4652
hermetic ( ) ;
4653
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4654
let root = dir.path ( ) ;
4655
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
4656
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
4657
let mut repo = Repo:: clone( & url, & root.join ( " r" ) )
4658
.unwrap ( )
4659
.with_home ( root.join ( " home" ) ) ;
7b9809e6 Andy McCall 1 month ago ↶
4660
let r = root.join ( " r" ) ;
4661
write ( & r, " a.txt" , b " a\n " ) ;
4662
let h_a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
4663
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
4664
origin.refresh ( ) .unwrap ( ) ;
7b9809e6 Andy McCall 1 month ago ↶
4665
4666
4667
assert! ( repo.bookmark_create ( " bad name.." , None ) .is_err ( ) ) ;
4668
assert! ( repo.bookmark_create ( " -x" , None ) .is_err ( ) ) ;
4669
assert_eq! ( repo.bookmark_create ( " feature" , None ) .unwrap ( ) , vec! [ h_a] ) ;
4670
assert! ( repo.bookmark_create ( " feature" , None ) .is_err ( ) , " exists" ) ;
4671
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
4672
assert_eq! ( repo.config.bookmark, " feature" ) ;
4673
write ( & r, " a.txt" , b " a\n b\n " ) ;
4674
write ( & r, " b.txt" , b " b\n " ) ;
4675
let h_b = repo.commit ( " b on feature" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4676
assert_eq! (
4677
repo.bookmark_heads ( ) .unwrap ( ) ,
4678
vec! [ h_b] ,
4679
" b edits a.txt, so it depends on a"
4680
) ;
4681
assert_eq! (
4682
repo.resolve_heads ( " main" ) .unwrap ( ) ,
4683
vec! [ h_a] ,
4684
" main is untouched"
4685
) ;
4686
assert_eq! (
4687
repo.resolve_change ( " feature" ) .unwrap ( ) ,
4688
h_b,
4689
" a one-head bookmark names its change"
4690
) ;
7b9809e6 Andy McCall 1 month ago ↶
4691
4692
let reopened = Repo:: open( & r) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4693
assert_eq! ( reopened.config.bookmark, " feature" ) ;
4694
4695
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
4696
assert! ( ! r.join ( " b.txt" ) .exists ( ) ) ;
4697
assert_eq! ( fs:: read( r.join ( " a.txt" ) ) .unwrap ( ) , b " a\n " ) ;
4698
write ( & r, " c.txt" , b " c\n " ) ;
4699
let h_c = repo.commit ( " c on main" , & [ ] ) .unwrap ( ) ;
4700
let list = repo.bookmarks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4701
assert_eq! (
4702
list.iter ( )
4703
.map ( | b | ( b.name.as_str ( ) , b.current) )
4704
.collect:: < Vec< _ > > ( ) ,
4705
vec! [ ( " feature" , false ) , ( " main" , true ) ]
4706
) ;
4707
assert_eq! (
4708
list.iter ( ) .find ( | b | b.name = = " main" ) .unwrap ( ) .ahead,
4709
Some ( 1 ) ,
4710
" c is not on origin yet"
4711
) ;
7b9809e6 Andy McCall 1 month ago ↶
4712
4713
let m = repo.merge ( " feature" ) .unwrap ( ) ;
4714
assert! ( m.changed & & m.checked_out) ;
4715
let mut want = vec! [ h_b, h_c] ;
4716
want.sort ( ) ;
4717
assert_eq! ( m.heads, want) ;
4718
assert_eq! ( fs:: read( r.join ( " b.txt" ) ) .unwrap ( ) , b " b\n " ) ;
4719
assert! ( ! repo.merge ( " feature" ) .unwrap ( ) .changed, " already merged" ) ;
4720
assert! ( repo.merge ( " main" ) .is_err ( ) ) ;
4721
4722
assert! ( repo.push_bookmark ( " origin" , " feature" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
4723
origin.refresh ( ) .unwrap ( ) ;
7b9809e6 Andy McCall 1 month ago ↶
4724
assert_eq! ( origin.resolve_heads ( " feature" ) .unwrap ( ) , vec! [ h_b] ) ;
4725
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
4726
origin.refresh ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4727
assert_eq! (
4728
repo.bookmarks ( )
4729
.unwrap ( )
4730
.iter ( )
4731
.find ( | b | b.name = = " main" )
4732
.unwrap ( )
4733
.ahead,
4734
Some ( 0 )
4735
) ;
7b9809e6 Andy McCall 1 month ago ↶
4736
4737
let ws = repo.workspace_add ( & root.join ( " ws" ) , None ) .unwrap ( ) ;
4738
assert_eq! ( ws.config.bookmark, " main" ) ;
4739
let mut ws = ws;
4740
ws.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4741
assert_eq! (
4742
Repo:: open( & r) .unwrap ( ) .config.bookmark,
4743
" main" ,
4744
" the first workspace is unaffected"
4745
) ;
4746
assert! (
4747
repo.bookmark_delete ( " feature" ) .is_err ( ) ,
4748
" current bookmark of another workspace"
4749
) ;
7b9809e6 Andy McCall 1 month ago ↶
4750
ws.bookmark_switch ( " main" , false ) .unwrap ( ) ;
4751
4752
assert_eq! ( repo.bookmark_set ( " feature" , & [ h_a] ) .unwrap ( ) , vec! [ h_a] ) ;
4753
assert! ( repo.bookmark_delete ( " main" ) .is_err ( ) , " current here" ) ;
4754
repo.bookmark_delete ( " feature" ) .unwrap ( ) ;
4755
assert! ( repo.resolve_heads ( " feature" ) .is_err ( ) ) ;
4756
assert! ( repo.bookmark_set ( " feature" , & [ h_a] ) .is_err ( ) ) ;
75167672 Andy McCall 1 month ago
4757
assert! (
4758
repo.checkout ( & [ h_a] , false , " checkout" ) .is_ok ( ) ,
4759
" changes of a deleted bookmark remain"
4760
) ;
7b9809e6 Andy McCall 1 month ago ↶
4761
}
4762
4dee277c Andy McCall 1 month ago ↶
4763
# [ test ]
4764
fn oxygen_subrepos_pin_follow_and_update ( ) {
4765
hermetic ( ) ;
4766
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4767
let root = dir.path ( ) ;
4768
4769
let lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
4770
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
4771
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
4772
.unwrap ( )
4773
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4774
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
4775
let l1 = lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
4776
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
4777
let _ = lib_origin;
4778
4779
4780
let app_origin = Repo:: init( & root.join ( " app-origin" ) ) .unwrap ( ) ;
4781
let app_url = root.join ( " app-origin" ) .to_string_lossy ( ) .to_string ( ) ;
4782
let _ = app_origin;
4783
let app_dir = root.join ( " app" ) ;
75167672 Andy McCall 1 month ago
4784
let app = Repo:: clone( & app_url, & app_dir)
4785
.unwrap ( )
4786
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4787
write ( & app_dir, " main.txt" , b " app\n " ) ;
4788
let pin = app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
4789
assert! ( matches! ( & pin, crate :: subrepo:: Pin:: Oxy { heads, .. } if heads = = & vec! [ l1] ) ) ;
4790
assert! ( app_dir.join ( " .oxymodules" ) .exists ( ) & & app_dir.join ( " libs/lib/.oxy" ) .is_dir ( ) ) ;
4791
let snap = app.snapshot ( " status" ) .unwrap ( ) ;
4792
let paths: Vec< & str > = snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect ( ) ;
75167672 Andy McCall 1 month ago
4793
assert_eq! (
4794
paths,
4795
vec! [ " .oxymodules" , " libs/lib" , " main.txt" ] ,
4796
" the nested files are not walked"
4797
) ;
4dee277c Andy McCall 1 month ago ↶
4798
let a1 = app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
4799
let files = app.load_state ( & [ a1] ) .unwrap ( ) .materialize ( ) .files;
4800
assert_eq! ( files[ " libs/lib" ] .kind, NodeKind:: Subrepo) ;
4801
assert! ( ! files.contains_key ( " libs/lib/lib.txt" ) ) ;
4802
assert! ( ! app.snapshot ( " status" ) .unwrap ( ) .changed) ;
4803
let subs = app.subrepos ( ) .unwrap ( ) ;
4804
assert_eq! ( subs.len ( ) , 1 ) ;
4805
assert_eq! ( subs[ 0 ] .actual.as_ref ( ) , Some ( & subs[ 0 ] .pin) ) ;
4806
4807
75167672 Andy McCall 1 month ago
4808
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) )
4809
.unwrap ( )
4810
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4811
write ( & app_dir.join ( " libs/lib" ) , " lib.txt" , b " v2\n " ) ;
4812
let l2 = nested.commit ( " lib v2" , & [ ] ) .unwrap ( ) ;
4813
assert! ( nested.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
4814
let snap = app.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4815
assert_eq! (
4816
snap.statuses
4817
.iter ( )
4818
.map ( | s | ( s.path.as_str ( ) , s.status) )
4819
.collect:: < Vec< _ > > ( ) ,
4820
vec! [ ( " libs/lib" , ChangeStatus:: Modified) ]
4821
) ;
4dee277c Andy McCall 1 month ago ↶
4822
let a2 = app.commit ( " bump lib" , & [ ] ) .unwrap ( ) ;
1a3b6203 Andy McCall 1 month ago ↶
4823
4824
4dee277c Andy McCall 1 month ago ↶
4825
let r = app.checkout ( & [ a1] , false , " checkout" ) .unwrap ( ) ;
1a3b6203 Andy McCall 1 month ago ↶
4826
assert! (
4827
r.notes.len ( ) = = 1 & & r.notes[ 0 ] .contains ( " keeps 1 newer change" ) ,
4828
" {:?}" ,
4829
r.notes
4830
) ;
4dee277c Andy McCall 1 month ago ↶
4831
assert_eq! ( fs:: read( app_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v1\n " ) ;
1a3b6203 Andy McCall 1 month ago ↶
4832
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) ) .unwrap ( ) ;
4833
assert_eq! ( nested.bookmark_heads ( ) .unwrap ( ) , vec! [ l2] ) ;
4834
assert_eq! ( nested.working_copy ( ) .unwrap ( ) .1. deps, vec! [ l1] ) ;
4dee277c Andy McCall 1 month ago ↶
4835
assert! ( ! app.snapshot ( " status" ) .unwrap ( ) .changed) ;
4836
4837
write ( & app_dir.join ( " libs/lib" ) , " scratch.txt" , b " wip\n " ) ;
4838
let r = app.checkout ( & [ a2] , false , " checkout" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4839
assert! (
4840
r.notes.iter ( ) .any ( | n | n.contains ( " uncommitted" ) ) ,
4841
" {:?}" ,
4842
r.notes
4843
) ;
4dee277c Andy McCall 1 month ago ↶
4844
assert_eq! ( fs:: read( app_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v1\n " ) ;
4845
fs:: remove_file( app_dir.join ( " libs/lib/scratch.txt" ) ) .unwrap ( ) ;
4846
let r = app.submodule_update ( false , & [ ] ) .unwrap ( ) ;
4847
assert_eq! ( r, vec! [ ( " libs/lib" .to_string ( ) , SubrepoSync:: Updated) ] ) ;
4848
assert_eq! ( fs:: read( app_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v2\n " ) ;
4849
let _ = l2;
4850
4851
4852
assert! ( app.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
4853
let clone_dir = root.join ( " app-clone" ) ;
75167672 Andy McCall 1 month ago
4854
let clone = Repo:: clone( & app_url, & clone_dir)
4855
.unwrap ( )
4856
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4857
assert! ( ! clone_dir.join ( " libs/lib/.oxy" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
4858
assert_eq! (
4859
clone.submodule_update ( false , & [ ] ) .unwrap ( ) ,
4860
vec! [ ( " libs/lib" .to_string ( ) , SubrepoSync:: Missing) ]
4861
) ;
4862
assert_eq! (
4863
clone.submodule_update ( true , & [ ] ) .unwrap ( ) ,
4864
vec! [ ( " libs/lib" .to_string ( ) , SubrepoSync:: Updated) ]
4865
) ;
4866
assert_eq! (
4867
fs:: read( clone_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) ,
4868
b " v2\n "
4869
) ;
4dee277c Andy McCall 1 month ago ↶
4870
assert! ( ! clone.snapshot ( " status" ) .unwrap ( ) .changed) ;
4871
4872
clone.submodule_remove ( " libs/lib" ) .unwrap ( ) ;
4873
let snap = clone.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4874
assert! (
4875
snap.statuses
4876
.iter ( )
4877
.any ( | s | s.path = = " libs/lib" & & s.status = = ChangeStatus:: Removed ) ,
4878
" {:?}" ,
4879
snap.statuses
4880
) ;
4dee277c Andy McCall 1 month ago ↶
4881
let c = clone.commit ( " drop lib" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4882
assert! ( ! clone
4883
.load_state ( & [ c] )
4884
.unwrap ( )
4885
.materialize ( )
4886
.files
4887
.contains_key ( " libs/lib" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4888
assert! ( clone_dir.join ( " libs/lib/lib.txt" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
4889
assert! (
4890
! clone.snapshot ( " status" ) .unwrap ( ) .changed,
4891
" an unregistered nested repository is ignored"
4892
) ;
4dee277c Andy McCall 1 month ago ↶
4893
}
4894
4895
# [ test ]
4896
fn git_submodules_import_and_export_as_gitlinks ( ) {
4897
hermetic ( ) ;
75167672 Andy McCall 1 month ago
4898
if std:: process:: Command:: new( " git" )
4899
.arg ( " --version" )
4900
.output ( )
4901
.is_err ( )
4902
{
4dee277c Andy McCall 1 month ago ↶
4903
eprintln! ( " git not installed; skipping" ) ;
4904
return ;
4905
}
4906
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4907
let root = dir.path ( ) ;
4908
let git_in = | d : & Path, args : & [& str ]| -> String {
4909
let out = std:: process:: Command:: new( " git" )
4910
.arg ( " -C" )
4911
.arg ( d)
75167672 Andy McCall 1 month ago
4912
.args ( [
4913
" -c" ,
4914
" user.name=Grace" ,
4915
" -c" ,
4916
" [email protected] " ,
4917
" -c" ,
4918
" commit.gpgsign=false" ,
4919
" -c" ,
4920
" protocol.file.allow=always" ,
4921
] )
4dee277c Andy McCall 1 month ago ↶
4922
.args ( args)
4923
.output ( )
4924
.unwrap ( ) ;
75167672 Andy McCall 1 month ago
4925
assert! (
4926
out.status.success ( ) ,
4927
" git {args:?}: {}" ,
4928
String :: from_utf8_lossy( & out.stderr)
4929
) ;
4dee277c Andy McCall 1 month ago ↶
4930
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
4931
} ;
4932
let sub = root.join ( " sub" ) ;
4933
fs:: create_dir_all( & sub) .unwrap ( ) ;
4934
git_in ( & sub, & [ " init" , " -q" , " -b" , " main" ] ) ;
4935
write ( & sub, " s.txt" , b " sub\n " ) ;
4936
git_in ( & sub, & [ " add" , " ." ] ) ;
4937
git_in ( & sub, & [ " commit" , " -q" , " -m" , " sub initial" ] ) ;
4938
let sub_sha = git_in ( & sub, & [ " rev-parse" , " HEAD" ] ) ;
4939
let g = root.join ( " main.git" ) ;
4940
fs:: create_dir_all( & g) .unwrap ( ) ;
4941
git_in ( & g, & [ " init" , " -q" , " -b" , " main" ] ) ;
4942
write ( & g, " README.md" , b " # app\n " ) ;
4943
git_in ( & g, & [ " add" , " ." ] ) ;
4944
git_in ( & g, & [ " commit" , " -q" , " -m" , " initial" ] ) ;
75167672 Andy McCall 1 month ago
4945
git_in (
4946
& g,
4947
& [
4948
" submodule" ,
4949
" add" ,
4950
" -q" ,
4951
sub.to_str ( ) .unwrap ( ) ,
4952
" vendor/sub" ,
4953
] ,
4954
) ;
4dee277c Andy McCall 1 month ago ↶
4955
git_in ( & g, & [ " commit" , " -q" , " -m" , " add submodule" ] ) ;
4956
4957
let dest = root.join ( " oxy" ) ;
4958
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4959
let rep = repo.import_git ( & g, " main" ) .unwrap ( ) ;
4960
assert_eq! ( ( rep.commits, rep.submodules) , ( 2 , 1 ) ) ;
75167672 Andy McCall 1 month ago
4961
let files = repo
4962
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
4963
.unwrap ( )
4964
.materialize ( )
4965
.files;
4dee277c Andy McCall 1 month ago ↶
4966
assert_eq! ( files[ " vendor/sub" ] .kind, NodeKind:: Subrepo) ;
75167672 Andy McCall 1 month ago
4967
let FileContent:: Opaque { blobs } = & files[ " vendor/sub" ] .content else {
4968
panic! ( )
4969
} ;
4970
assert_eq! (
4971
crate :: subrepo:: Pin:: decode( & repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) ) .unwrap ( ) ,
4972
crate :: subrepo:: Pin:: Git {
4973
sha: sub_sha.clone ( ) ,
4974
branch: " HEAD" .into ( )
4975
}
4976
) ;
4977
let modules =
4978
crate :: subrepo:: Modules:: parse( & fs:: read_to_string( dest.join ( " .oxymodules" ) ) .unwrap ( ) ) ;
4dee277c Andy McCall 1 month ago ↶
4979
assert_eq! ( modules.entries[ " vendor/sub" ] .url, sub.to_string_lossy ( ) ) ;
4980
assert! ( modules.entries[ " vendor/sub" ] .git) ;
75167672 Andy McCall 1 month ago
4981
assert! (
4982
files.contains_key ( " .gitmodules" ) ,
4983
" the Git file is kept for round trips"
4984
) ;
4985
assert! (
4986
! dest.join ( " vendor/sub/.git" ) .exists ( ) ,
4987
" not cloned until update --init"
4988
) ;
4dee277c Andy McCall 1 month ago ↶
4989
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4990
75167672 Andy McCall 1 month ago
4991
assert_eq! (
4992
repo.submodule_update ( true , & [ ] ) .unwrap ( ) ,
4993
vec! [ ( " vendor/sub" .to_string ( ) , SubrepoSync:: Updated) ]
4994
) ;
4995
assert_eq! (
4996
git_in ( & dest.join ( " vendor/sub" ) , & [ " rev-parse" , " HEAD" ] ) ,
4997
sub_sha
4998
) ;
4dee277c Andy McCall 1 month ago ↶
4999
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
5000
5001
let mirror = root.join ( " mirror.git" ) ;
5002
let rep = repo.git_export ( & mirror) .unwrap ( ) ;
5003
assert_eq! ( rep.skipped_subrepos, 0 ) ;
5004
let ls = git_in ( & mirror, & [ " ls-tree" , " main" , " vendor/sub" ] ) ;
75167672 Andy McCall 1 month ago
5005
assert! (
5006
ls.starts_with ( " 160000 commit " ) & & ls.contains ( & sub_sha) ,
5007
" {ls}"
5008
) ;
5009
assert_eq! (
5010
git_in ( & mirror, & [ " ls-tree" , " --name-only" , " main" ] ) ,
5011
" .gitmodules\n README.md\n vendor"
5012
) ;
4dee277c Andy McCall 1 month ago ↶
5013
let orig_tree = git_in ( & g, & [ " rev-parse" , " main^{tree}" ] ) ;
75167672 Andy McCall 1 month ago
5014
assert_eq! (
5015
git_in ( & mirror, & [ " rev-parse" , " main^{tree}" ] ) ,
5016
orig_tree,
5017
" tree round trip"
5018
) ;
4dee277c Andy McCall 1 month ago ↶
5019
}
5020
957fee30 Andy McCall 1 month ago ↶
5021
# [ test ]
5022
fn partial_commits_by_path_and_by_hunk ( ) {
5023
hermetic ( ) ;
5024
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5025
let root = dir.path ( ) ;
5026
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5027
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
5028
write ( root, " b.txt" , b " b\n " ) ;
5029
let h0 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
5030
write ( root, " a.txt" , b " 0\n 1\n 2\n 3\n 4\n " ) ;
5031
write ( root, " b.txt" , b " b\n bb\n " ) ;
5032
write ( root, " c.txt" , b " c\n " ) ;
5033
75167672 Andy McCall 1 month ago
5034
let h1 = repo
5035
.commit_selection ( " a only" , & [ ] , & Selection:: Paths( vec! [ " a.txt" .into ( ) ] ) )
5036
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5037
let c1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5038
assert! (
5039
c1.tree_ops.is_empty ( ) & & c1.edits.iter ( ) .all ( | e | e.node ( ) = = c1.edits[ 0 ] .node ( ) ) ,
5040
" {:?}" ,
5041
c1.tree_ops
5042
) ;
957fee30 Andy McCall 1 month ago ↶
5043
assert_eq! ( c1.deps, vec! [ h0] ) ;
5044
let files = repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
5045
assert! (
5046
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 0\n 1\n 2\n 3\n 4\n " )
5047
) ;
957fee30 Andy McCall 1 month ago ↶
5048
assert! ( matches! ( & files[ " b.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " b\n " ) ) ;
5049
assert! ( ! files.contains_key ( " c.txt" ) ) ;
5050
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5051
assert_eq! (
5052
snap.statuses
5053
.iter ( )
5054
.map ( | s | ( s.path.as_str ( ) , s.status) )
5055
.collect:: < Vec< _ > > ( ) ,
5056
vec! [
5057
( " b.txt" , ChangeStatus:: Modified) ,
5058
( " c.txt" , ChangeStatus:: Added)
5059
]
5060
) ;
5061
assert_eq! (
5062
fs:: read( root.join ( " b.txt" ) ) .unwrap ( ) ,
5063
b " b\n bb\n " ,
5064
" disk untouched"
5065
) ;
957fee30 Andy McCall 1 month ago ↶
5066
5067
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5068
let b_hunk = hunks
5069
.iter ( )
5070
.find ( | h | h.path = = " b.txt" & & h.text.contains ( " +bb" ) )
5071
.unwrap ( )
5072
.index;
5073
let h2 = repo
5074
.commit_selection ( " b only" , & [ ] , & Selection:: Hunks( vec! [ b_hunk] ) )
5075
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5076
let files = repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
5077
assert! (
5078
matches! ( & files[ " b.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " b\n bb\n " )
5079
) ;
957fee30 Andy McCall 1 month ago ↶
5080
assert! ( ! files.contains_key ( " c.txt" ) ) ;
5081
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5082
assert_eq! (
5083
snap.statuses
5084
.iter ( )
5085
.map ( | s | s.path.as_str ( ) )
5086
.collect:: < Vec< _ > > ( ) ,
5087
vec! [ " c.txt" ]
5088
) ;
957fee30 Andy McCall 1 month ago ↶
5089
5090
5091
write ( root, " d/e.txt" , b " e\n " ) ;
5092
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5093
let e_insert = hunks
5094
.iter ( )
5095
.find ( | h | h.path = = " d/e.txt" & & h.text.contains ( " +e" ) )
5096
.unwrap ( )
5097
.index;
5098
let h3 = repo
5099
.commit_selection ( " e only" , & [ ] , & Selection:: Hunks( vec! [ e_insert] ) )
5100
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5101
let c3 = repo.store ( ) .get_change ( & h3) .unwrap ( ) ;
5102
assert_eq! ( c3.tree_ops.len ( ) , 2 , " {:?}" , c3.tree_ops) ;
5103
let files = repo.load_state ( & [ h3] ) .unwrap ( ) .materialize ( ) .files;
5104
assert! ( files.contains_key ( " d/e.txt" ) & & ! files.contains_key ( " c.txt" ) ) ;
d8c086ca Andy McCall 1 month ago ↶
5105
5106
write ( root, " f/g.txt" , b " g\n " ) ;
5107
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5108
let g_add = hunks
5109
.iter ( )
5110
.find ( | h | h.path = = " f/g.txt" & & h.text.starts_with ( " A " ) )
5111
.unwrap ( )
5112
.index;
5113
let h4 = repo
5114
.commit_selection ( " g by addition" , & [ ] , & Selection:: Hunks( vec! [ g_add] ) )
5115
.unwrap ( ) ;
d8c086ca Andy McCall 1 month ago ↶
5116
let files = repo.load_state ( & [ h4] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
5117
assert! (
5118
matches! ( & files[ " f/g.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " g\n " )
5119
) ;
5120
assert! ( ! repo
5121
.snapshot ( " status" )
5122
.unwrap ( )
5123
.statuses
5124
.iter ( )
5125
.any ( | s | s.path = = " f/g.txt" ) ) ;
957fee30 Andy McCall 1 month ago ↶
5126
5127
repo.commit ( " the rest" , & [ ] ) .unwrap ( ) ;
5128
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
75167672 Andy McCall 1 month ago
5129
let files = repo
5130
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
5131
.unwrap ( )
5132
.materialize ( )
5133
.files;
957fee30 Andy McCall 1 month ago ↶
5134
assert! ( files.contains_key ( " c.txt" ) ) ;
5135
}
5136
5137
# [ test ]
5138
fn blame_attributes_lines_to_introducing_changes_across_renames ( ) {
5139
hermetic ( ) ;
5140
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5141
let root = dir.path ( ) ;
5142
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
5143
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
5144
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5145
write ( root, " a.txt" , b " one\n " ) ;
5146
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
5147
write ( root, " a.txt" , b " one\n two\n " ) ;
5148
let h2 = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
5149
let b = repo.blame ( " a.txt" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5150
assert_eq! (
5151
b.iter ( )
5152
.map ( | l | ( l.line, l.hash, l.text.as_slice ( ) ) )
5153
.collect:: < Vec< _ > > ( ) ,
5154
vec! [ ( 1 , h1, b " one\n " .as_slice ( ) ) , ( 2 , h2, b " two\n " .as_slice ( ) ) ]
5155
) ;
957fee30 Andy McCall 1 month ago ↶
5156
assert! ( b.iter ( ) .all ( | l | l.author = = " Ada" ) ) ;
75167672 Andy McCall 1 month ago
5157
assert_eq! (
5158
b[ 0 ] .change_id,
5159
repo.store ( ) .get_change ( & h1) .unwrap ( ) .change_id
5160
) ;
957fee30 Andy McCall 1 month ago ↶
5161
fs:: rename( root.join ( " a.txt" ) , root.join ( " b.txt" ) ) .unwrap ( ) ;
5162
repo.commit ( " rename" , & [ ] ) .unwrap ( ) ;
5163
let b = repo.blame ( " b.txt" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5164
assert_eq! (
5165
b.iter ( ) .map ( | l | l.hash ) .collect:: < Vec< _ > > ( ) ,
5166
vec! [ h1, h2] ,
5167
" the node survives the rename"
5168
) ;
957fee30 Andy McCall 1 month ago ↶
5169
assert! ( repo.blame ( " a.txt" ) .is_err ( ) ) ;
5170
}
5171
5172
# [ test ]
5173
fn bisect_finds_the_first_bad_change ( ) {
5174
hermetic ( ) ;
5175
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5176
let root = dir.path ( ) ;
5177
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5178
let mut content = String :: new( ) ;
5179
let mut hashes = Vec :: new( ) ;
5180
for i in 1 .. = 8 {
5181
content.push_str ( & format! ( " line {i} {} \n " , if i = = 5 { " bug" } else { " " } ) ) ;
5182
write ( root, " f.txt" , content.as_bytes ( ) ) ;
5183
hashes.push ( repo.commit ( & format! ( " step {i} " ) , & [ ] ) .unwrap ( ) ) ;
5184
}
5185
5186
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ hashes[ 7 ] ] ) ;
5187
repo.bisect_start ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5188
assert! ( matches! (
5189
repo.bisect_mark ( Some ( true ) , & [ hashes[ 0 ] ] ) .unwrap ( ) ,
5190
BisectStep:: NeedMarks { good: 1 , bad: 0 }
5191
) ) ;
957fee30 Andy McCall 1 month ago ↶
5192
let step = repo.bisect_mark ( Some ( false ) , & [ hashes[ 7 ] ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5193
assert! (
5194
matches! ( step, BisectStep:: Testing { remaining: 7 , .. } ) ,
5195
" {step:?}"
5196
) ;
957fee30 Andy McCall 1 month ago ↶
5197
75167672 Andy McCall 1 month ago
5198
let step = repo
5199
.bisect_run ( & [ " sh" .into ( ) , " -c" .into ( ) , " ! grep -q bug f.txt" .into ( ) ] )
5200
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5201
assert_eq! ( step, BisectStep:: Found { culprit: hashes[ 4 ] } ) ;
75167672 Andy McCall 1 month ago
5202
assert_eq! (
5203
repo.bisect_status ( ) .unwrap ( ) ,
5204
Some ( BisectStep:: Found { culprit: hashes[ 4 ] } )
5205
) ;
957fee30 Andy McCall 1 month ago ↶
5206
repo.bisect_reset ( ) .unwrap ( ) ;
5207
assert_eq! ( fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) , content) ;
5208
assert! ( repo.bisect_status ( ) .unwrap ( ) .is_none ( ) ) ;
5209
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
5210
5211
repo.bisect_start ( ) .unwrap ( ) ;
5212
repo.bisect_mark ( Some ( true ) , & [ hashes[ 0 ] ] ) .unwrap ( ) ;
5213
let mut step = repo.bisect_mark ( Some ( false ) , & [ hashes[ 7 ] ] ) .unwrap ( ) ;
5214
let mut skipped_once = false ;
5215
let mut guard = 0 ;
5216
while let BisectStep:: Testing { .. } = step {
5217
guard + = 1 ;
5218
assert! ( guard < 20 ) ;
5219
let text = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
5220
if ! skipped_once & & text.contains ( " line 6" ) {
5221
5222
skipped_once = true ;
5223
step = repo.bisect_mark ( None , & [ ] ) .unwrap ( ) ;
5224
continue ;
5225
}
5226
step = repo.bisect_mark ( Some ( ! text.contains ( " bug" ) ) , & [ ] ) .unwrap ( ) ;
5227
}
5228
assert! ( skipped_once) ;
5229
assert_eq! ( step, BisectStep:: Found { culprit: hashes[ 4 ] } ) ;
5230
repo.bisect_reset ( ) .unwrap ( ) ;
5231
5232
repo.bisect_start ( ) .unwrap ( ) ;
5233
repo.bisect_mark ( Some ( true ) , & [ hashes[ 0 ] ] ) .unwrap ( ) ;
5234
let mut step = repo.bisect_mark ( Some ( false ) , & [ hashes[ 7 ] ] ) .unwrap ( ) ;
5235
let mut guard = 0 ;
5236
while let BisectStep:: Testing { heads, .. } = & step {
5237
guard + = 1 ;
5238
assert! ( guard < 20 ) ;
5239
let text = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5240
let is_culprit_state =
5241
heads.contains ( & hashes[ 4 ] ) & & ! heads.iter ( ) .any ( | h | hashes[ 5 .. ] .contains ( h) ) ;
5242
step = if is_culprit_state {
5243
repo.bisect_mark ( None , & [ ] ) .unwrap ( )
5244
} else {
5245
repo.bisect_mark ( Some ( ! text.contains ( " bug" ) ) , & [ ] ) .unwrap ( )
5246
} ;
957fee30 Andy McCall 1 month ago ↶
5247
}
5248
match step {
75167672 Andy McCall 1 month ago
5249
BisectStep:: Inconclusive { candidates } => assert! (
5250
candidates.contains ( & hashes[ 4 ] ) & & candidates.len ( ) < = 2 ,
5251
" {candidates:?}"
5252
) ,
957fee30 Andy McCall 1 month ago ↶
5253
other => panic! ( " {other:?}" ) ,
5254
}
5255
repo.bisect_reset ( ) .unwrap ( ) ;
5256
}
5257
728da0f9 Andy McCall 1 month ago ↶
5258
# [ test ]
5259
fn squash_folds_a_change_into_its_dependency ( ) {
5260
hermetic ( ) ;
5261
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5262
let root = dir.path ( ) ;
5263
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5264
write ( root, " a.txt" , b " 1\n " ) ;
5265
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
5266
write ( root, " a.txt" , b " 1\n 2\n " ) ;
5267
let c = repo.commit ( " C" , & [ ] ) .unwrap ( ) ;
5268
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
5269
let e = repo.commit ( " E" , & [ ] ) .unwrap ( ) ;
5270
write ( root, " b.txt" , b " b\n " ) ;
5271
let b = repo.commit ( " B" , & [ ] ) .unwrap ( ) ;
5272
let id_a = repo.store ( ) .get_change ( & a) .unwrap ( ) .change_id;
5273
let id_e = repo.store ( ) .get_change ( & e) .unwrap ( ) .change_id;
5274
5275
let err = repo.squash ( & e, & a) .unwrap_err ( ) .to_string ( ) ;
5276
assert! ( err.contains ( & c.short ( 12 ) ) , " {err}" ) ;
5277
5278
let a2 = repo.squash ( & c, & a) .unwrap ( ) ;
5279
let a2c = repo.store ( ) .get_change ( & a2) .unwrap ( ) ;
5280
assert_eq! ( a2c.change_id, id_a) ;
5281
assert! ( a2c.deps.is_empty ( ) ) ;
5282
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5283
let st = repo.load_state ( & heads) .unwrap ( ) ;
5284
assert! ( ! st.contains ( & c) & & ! st.contains ( & a) & & ! st.contains ( & e) ) ;
75167672 Andy McCall 1 month ago
5285
let e2 = st
5286
.changes ( )
5287
.find ( | ( _, m ) | m.change_id = = id_e )
5288
.map ( | ( h , _) | * h)
5289
.unwrap ( ) ;
5290
assert_eq! (
5291
repo.store ( ) .get_change ( & e2) .unwrap ( ) .deps,
5292
vec! [ a2] ,
5293
" E now depends on A' alone"
5294
) ;
728da0f9 Andy McCall 1 month ago ↶
5295
let files = st.materialize ( ) .files;
75167672 Andy McCall 1 month ago
5296
assert! (
5297
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 1\n 2\n 3\n " )
5298
) ;
728da0f9 Andy McCall 1 month ago ↶
5299
assert! ( files.contains_key ( " b.txt" ) ) ;
5300
assert! ( st.contains ( & b) ) ;
5301
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
5302
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 , " working copy, B, E', A'" ) ;
5303
5304
repo.undo ( ) .unwrap ( ) ;
5305
repo.undo ( ) .unwrap ( ) ;
5306
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
5307
assert! ( st.contains ( & c) & & st.contains ( & a) & & st.contains ( & e) ) ;
5308
}
5309
5310
# [ test ]
5311
fn rebase_re_expresses_a_change_against_another_base ( ) {
5312
hermetic ( ) ;
5313
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5314
let root = dir.path ( ) ;
5315
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5316
write ( root, " a.txt" , b " 1\n 2\n " ) ;
5317
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
5318
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
5319
let c = repo.commit ( " C: append 3" , & [ ] ) .unwrap ( ) ;
5320
write ( root, " a.txt" , b " 1\n 2\n 3\n 4\n " ) ;
5321
let e = repo.commit ( " E: append 4" , & [ ] ) .unwrap ( ) ;
5322
5323
repo.checkout ( & [ a] , true , " checkout" ) .unwrap ( ) ;
5324
write ( root, " a.txt" , b " 0\n 1\n 2\n " ) ;
5325
let d = repo.commit ( " D: prepend 0" , & [ ] ) .unwrap ( ) ;
5326
let id_c = repo.store ( ) .get_change ( & c) .unwrap ( ) .change_id;
5327
let id_e = repo.store ( ) .get_change ( & e) .unwrap ( ) .change_id;
5328
5329
let c2 = repo.rebase ( & c, & [ d] ) .unwrap ( ) ;
5330
let c2c = repo.store ( ) .get_change ( & c2) .unwrap ( ) ;
5331
assert_eq! ( c2c.change_id, id_c) ;
5332
assert! ( c2c.deps.contains ( & d) , " {:?}" , c2c.deps) ;
5333
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5334
let st = repo.load_state ( & heads) .unwrap ( ) ;
5335
assert! ( ! st.contains ( & c) & & st.contains ( & d) ) ;
75167672 Andy McCall 1 month ago
5336
let e2 = st
5337
.changes ( )
5338
.find ( | ( _, m ) | m.change_id = = id_e )
5339
.map ( | ( h , _) | * h)
5340
.unwrap ( ) ;
728da0f9 Andy McCall 1 month ago ↶
5341
let e2deps = repo.store ( ) .get_change ( & e2) .unwrap ( ) .deps;
75167672 Andy McCall 1 month ago
5342
assert! (
5343
e2deps.contains ( & c2) & & ! e2deps.contains ( & c) & & e2deps.contains ( & a) ,
5344
" E follows C by content and still depends on A, which introduced the file: {e2deps:?}"
5345
) ;
728da0f9 Andy McCall 1 month ago ↶
5346
let files = st.materialize ( ) .files;
75167672 Andy McCall 1 month ago
5347
assert! (
5348
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 0\n 1\n 2\n 3\n 4\n " ) ,
5349
" {:?}" ,
5350
files[ " a.txt" ] .content
5351
) ;
728da0f9 Andy McCall 1 month ago ↶
5352
assert! ( ! files[ " a.txt" ] .is_conflicted ( ) ) ;
5353
5354
assert! ( repo.rebase ( & c2, & [ c2] ) .is_err ( ) ) ;
5355
let err = repo.rebase ( & e2, & [ d] ) .unwrap_err ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
5356
assert! (
5357
err.contains ( " lacks dependencies" ) ,
5358
" E needs C, which D's state does not have: {err}"
5359
) ;
728da0f9 Andy McCall 1 month ago ↶
5360
}
5361
c792033a Andy McCall 1 month ago ↶
5362
# [ test ]
5363
fn unsigned_tags_keep_their_message ( ) {
5364
hermetic ( ) ;
5365
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5366
let root = dir.path ( ) ;
5367
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5368
write ( root, " a.txt" , b " a\n " ) ;
5369
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
5370
assert! ( repo.secret_key ( ) .unwrap ( ) .is_none ( ) ) ;
75167672 Andy McCall 1 month ago
5371
repo.tag_create ( " v1" , & repo.bookmark_heads ( ) .unwrap ( ) , " first release" )
5372
.unwrap ( ) ;
c792033a Andy McCall 1 month ago ↶
5373
let tags = repo.tags ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5374
assert_eq! (
5375
(
5376
tags[ 0 ] .name.as_str ( ) ,
5377
tags[ 0 ] .message.as_str ( ) ,
5378
tags[ 0 ] .signer.as_deref ( )
5379
) ,
5380
( " v1" , " first release" , None )
5381
) ;
5382
assert! (
5383
repo.tag_refs ( ) .unwrap ( ) [ 0 ] .attestation.is_none ( ) ,
5384
" an unsigned attestation is not advertised"
5385
) ;
c792033a Andy McCall 1 month ago ↶
5386
}
387335ff Andy McCall 1 month ago ↶
5387
5388
# [ test ]
5389
fn fsck_passes_a_sound_repository_and_names_each_fault ( ) {
64818938 Andy McCall 1 month ago ↶
5390
5391
5392
if std:: env:: var_os( " OXY_STORE_BACKEND" ) .is_some ( ) {
5393
return ;
5394
}
387335ff Andy McCall 1 month ago ↶
5395
hermetic ( ) ;
5396
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5397
let root = dir.path ( ) ;
5398
let origin_path = root.join ( " origin" ) ;
5399
let origin = Repo:: init( & origin_path) .unwrap ( ) ;
5400
let mut a = Repo:: clone( & origin_path.to_string_lossy ( ) , & root.join ( " a" ) )
5401
.unwrap ( )
5402
.with_home ( root.join ( " home-a" ) ) ;
5403
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
5404
.unwrap ( ) ;
5405
write ( & root.join ( " a" ) , " f.txt" , b " one\n " ) ;
5406
write ( & root.join ( " a" ) , " bin.dat" , b " \0 \x01 \x02 binary" ) ;
5407
let h1 = a.commit ( " first" , & [ ] ) .unwrap ( ) ;
5408
write ( & root.join ( " a" ) , " f.txt" , b " one\n two\n " ) ;
5409
let h2 = a.commit ( " second" , & [ ] ) .unwrap ( ) ;
5410
a.tag_create ( " v1" , & [ h2] , " first release" ) .unwrap ( ) ;
5411
a.checkpoint ( ) .unwrap ( ) ;
5412
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
5413
origin.refresh ( ) .unwrap ( ) ;
387335ff Andy McCall 1 month ago ↶
5414
5415
5416
for repo in [ & a, & origin] {
5417
let rep = repo.fsck ( ) .unwrap ( ) ;
5418
assert_eq! ( rep.problems, vec! [ ] , " {rep:?}" ) ;
5419
5420
assert! ( rep.changes > = 2 & & rep.signed > = 2 , " {rep:?}" ) ;
5421
assert! ( rep.ops > 1 & & rep.objects > 5 ) ;
5422
}
5423
assert! ( origin.fsck ( ) .unwrap ( ) .attestations > = 1 ) ;
5424
5425
5426
let c1 = a.store ( ) .path_for ( & h1) ;
5427
let pristine = fs:: read( & c1) .unwrap ( ) ;
5428
let mut bytes = pristine.clone ( ) ;
5429
* bytes.last_mut ( ) .unwrap ( ) ^ = 1 ;
5430
fs:: write( & c1, & bytes) .unwrap ( ) ;
5431
let rep = a.fsck ( ) .unwrap ( ) ;
5432
assert! ( rep.errors ( ) > = 1 ) ;
5433
assert! (
5434
rep.problems
5435
.iter ( )
5436
.any ( | p | p.object = = Some ( h1) & & p.what.contains ( " unreadable" ) ) ,
5437
" {rep:?}"
5438
) ;
5439
fs:: write( & c1, & pristine) .unwrap ( ) ;
5440
assert_eq! ( a.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
5441
5442
5443
fs:: remove_file( & c1) .unwrap ( ) ;
5444
let rep = a.fsck ( ) .unwrap ( ) ;
5445
assert! (
5446
rep.problems.iter ( ) .any ( | p | p.severity = = Severity:: Error
5447
& & p.object = = Some ( h1)
5448
& & p.what.contains ( " change missing, needed by change" ) ) ,
5449
" {rep:?}"
5450
) ;
5451
fs:: write( & c1, & pristine) .unwrap ( ) ;
5452
5453
5454
let c2 = a.store ( ) .get_change ( & h1) .unwrap ( ) ;
5455
let blob = c2
5456
.edits
5457
.iter ( )
5458
.find_map ( | e | match e {
5459
Edit:: Replace { new: Some ( b) , .. } => Some ( * b) ,
5460
_ => None ,
5461
} )
5462
.expect ( " the binary file is a blob" ) ;
5463
let bp = a.store ( ) .path_for ( & blob) ;
5464
let blob_bytes = fs:: read( & bp) .unwrap ( ) ;
5465
fs:: remove_file( & bp) .unwrap ( ) ;
5466
let rep = a.fsck ( ) .unwrap ( ) ;
5467
assert! (
5468
rep.problems
5469
.iter ( )
5470
.any ( | p | p.object = = Some ( blob) & & p.what.contains ( " blob missing" ) ) ,
5471
" {rep:?}"
5472
) ;
5473
fs:: write( & bp, & blob_bytes) .unwrap ( ) ;
5474
5475
5476
let head = root.join ( " a/.oxy/ops/head" ) ;
5477
let saved = fs:: read( & head) .unwrap ( ) ;
5478
fs:: write( & head, b " not a hash" ) .unwrap ( ) ;
5479
let rep = a.fsck ( ) .unwrap ( ) ;
5480
assert! (
5481
rep.problems.iter ( ) .any ( | p | p.what.contains ( " ops/head" ) ) ,
5482
" {rep:?}"
5483
) ;
5484
fs:: write( & head, saved) .unwrap ( ) ;
5485
assert_eq! ( a.fsck ( ) .unwrap ( ) .problems, vec! [ ] ) ;
5486
}
4dcb8c09 Andy McCall 1 month ago ↶
5487
5488
5489
fn copy_tree ( from : & Path, to : & Path) {
5490
fs:: create_dir_all( to) .unwrap ( ) ;
5491
for e in fs:: read_dir( from) .unwrap ( ) {
5492
let e = e.unwrap ( ) ;
5493
let target = to.join ( e.file_name ( ) ) ;
5494
if e.file_type ( ) .unwrap ( ) .is_dir ( ) {
5495
copy_tree ( & e.path ( ) , & target) ;
5496
} else {
5497
fs:: copy( e.path ( ) , & target) .unwrap ( ) ;
5498
}
5499
}
5500
}
5501
5502
5503
5504
5505
# [ test ]
5506
fn crash_at_every_step_of_commit_and_push_leaves_sound_repositories ( ) {
64818938 Andy McCall 1 month ago ↶
5507
5508
5509
if std:: env:: var_os( " OXY_STORE_BACKEND" ) .is_some ( ) {
5510
return ;
5511
}
4dcb8c09 Andy McCall 1 month ago ↶
5512
use crate :: durable:: failpoint;
5513
hermetic ( ) ;
5514
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5515
let pristine = dir.path ( ) .join ( " pristine" ) ;
5516
let origin_path = pristine.join ( " origin" ) ;
5517
Repo:: init( & origin_path) .unwrap ( ) ;
5518
let mut a = Repo:: clone( & origin_path.to_string_lossy ( ) , & pristine.join ( " a" ) )
5519
.unwrap ( )
5520
.with_home ( dir.path ( ) .join ( " home" ) ) ;
5521
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
5522
.unwrap ( ) ;
5523
write ( & pristine.join ( " a" ) , " f.txt" , b " one\n " ) ;
5524
a.commit ( " first" , & [ ] ) .unwrap ( ) ;
5525
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
5526
drop ( a) ;
5527
5528
type Op = fn ( & Repo) -> Result< ( ) > ;
5529
let edit_and_commit: Op = | r | {
5530
write ( r.root ( ) , " f.txt" , b " one\n two\n " ) ;
5531
write ( r.root ( ) , " big.bin" , & vec! [ 7 u8 ; 300_000 ] ) ;
5532
r.commit ( " second" , & [ ] ) .map ( | _| ( ) )
5533
} ;
5534
let push: Op = | r | {
5535
let res = r.push ( " origin" , false ) ? ;
5536
if res.iter ( ) .all ( | x | x.ok ) {
5537
Ok ( ( ) )
5538
} else {
5539
Err ( RepoError:: Msg( format! ( " {res:?} " ) ) )
5540
}
5541
} ;
5542
5543
for ( name, prepare, op) in [
5544
( " commit" , None , edit_and_commit) ,
5545
( " push" , Some ( edit_and_commit) , push) ,
5546
] {
5547
let mut steps = 0 ;
5548
for n in 1 .. {
5549
let run = dir.path ( ) .join ( format! ( " {name} -{n} " ) ) ;
5550
copy_tree ( & pristine, & run) ;
5551
5552
let open = | p : & Path| {
5553
let mut r = Repo:: open( p) .unwrap ( ) .with_home ( dir.path ( ) .join ( " home" ) ) ;
5554
r.config
5555
.set ( " remote.origin.url" , & run.join ( " origin" ) .to_string_lossy ( ) )
5556
.unwrap ( ) ;
5557
r.save_config ( ) .unwrap ( ) ;
5558
r
5559
} ;
5560
let a = open ( & run.join ( " a" ) ) ;
5561
if let Some ( prep) = prepare {
5562
prep ( & a) .unwrap ( ) ;
5563
}
5564
failpoint:: crash_after( n) ;
5565
let outcome = op ( & a) ;
5566
let crashed = failpoint:: disarm( ) ;
5567
drop ( a) ;
5568
if ! crashed {
5569
outcome.unwrap_or_else ( | e | panic! ( " {name} failed without a crash: {e}" ) ) ;
5570
steps = n - 1 ;
5571
break ;
5572
}
5573
assert! (
5574
outcome.is_err ( ) ,
5575
" {name}: a crash at step {n} was swallowed"
5576
) ;
5577
for repo in [ " a" , " origin" ] {
5578
let r = Repo:: open( & run.join ( repo) ) .unwrap_or_else ( | e | {
5579
panic! ( " {name} crash at step {n}: {repo} no longer opens: {e}" )
5580
} ) ;
5581
let rep = r.fsck ( ) .unwrap ( ) ;
5582
assert_eq! (
5583
rep.errors ( ) ,
5584
0 ,
5585
" {name} crash at step {n}: {repo} fails fsck: {:?}" ,
5586
rep.problems
5587
) ;
5588
}
5589
5590
let a = open ( & run.join ( " a" ) ) ;
5591
5592
let landed = a
5593
.bookmark_heads ( )
5594
.unwrap ( )
5595
.iter ( )
5596
.any ( | h | a.store ( ) .get_change ( h) .unwrap ( ) .message = = " second" ) ;
5597
let again = match ( name, landed) {
5598
( " commit" , true ) => Ok ( ( ) ) ,
5599
( " commit" , false ) => a.commit ( " second" , & [ ] ) .map ( | _| ( ) ) ,
5600
_ => op ( & a) ,
5601
} ;
5602
again.unwrap_or_else ( | e | panic! ( " {name} crash at step {n}: retry failed: {e}" ) ) ;
5603
assert_eq! ( a.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
5604
fs:: remove_dir_all( & run) .unwrap ( ) ;
5605
}
5606
assert! ( steps > = 3 , " {name} has only {steps} durable steps?" ) ;
5607
}
5608
}
19555025 Andy McCall 1 month ago ↶
5609
5610
5611
5612
5613
# [ test ]
5614
fn concurrent_commits_in_two_workspaces_lose_nothing ( ) {
5615
hermetic ( ) ;
5616
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5617
let root = dir.path ( ) ;
5618
let origin = root.join ( " origin" ) ;
5619
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
5620
let main = root.join ( " main" ) ;
5621
let mut repo = Repo:: init( & main) .unwrap ( ) ;
5622
repo.config
5623
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
5624
.unwrap ( ) ;
5625
repo.save_config ( ) .unwrap ( ) ;
5626
write ( & main, " seed.txt" , b " seed\n " ) ;
5627
repo.commit ( " seed" , & [ ] ) .unwrap ( ) ;
5628
let second = root.join ( " second" ) ;
5629
repo.workspace_add ( & second, None ) .unwrap ( ) ;
5630
drop ( repo) ;
5631
5632
const EACH : usize = 15 ;
5633
std:: thread:: scope( | s | {
5634
for ( tag, dir, push) in [ ( " a" , main.clone ( ) , true ) , ( " b" , second.clone ( ) , false ) ] {
5635
s.spawn ( move | | {
5636
let r = Repo:: open( & dir) .unwrap ( ) ;
5637
for i in 0 .. EACH {
5638
write (
5639
& dir,
5640
& format! ( " {tag} -{i} .txt" ) ,
5641
format! ( " {tag} {i} \n " ) .as_bytes ( ) ,
5642
) ;
5643
r.commit ( & format! ( " {tag} {i} " ) , & [ ] ) .unwrap ( ) ;
5644
if push & & i % 5 = = 4 {
5645
5646
let _ = r.push ( " origin" , false ) ;
5647
}
5648
}
5649
} ) ;
5650
}
5651
} ) ;
5652
5653
let repo = Repo:: open( & main) .unwrap ( ) ;
5654
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5655
let state = repo.load_state ( & heads) .unwrap ( ) ;
5656
let messages: BTreeSet< String> = state.changes ( ) .map ( | ( _, m ) | m.message.clone ( ) ) .collect ( ) ;
5657
for tag in [ " a" , " b" ] {
5658
for i in 0 .. EACH {
5659
assert! (
5660
messages.contains ( & format! ( " {tag} {i} " ) ) ,
5661
" lost commit {tag} {i}; have {messages:?}"
5662
) ;
5663
}
5664
}
5665
let rep = repo.fsck ( ) .unwrap ( ) ;
5666
assert_eq! ( rep.errors ( ) , 0 , " {:?}" , rep.problems) ;
5667
assert! ( repo.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
5668
let hosted = Repo:: open( & origin) .unwrap ( ) ;
5669
let hosted_heads = hosted.bookmark_heads ( ) .unwrap ( ) ;
5670
assert_eq! (
5671
hosted.load_state ( & hosted_heads) .unwrap ( ) .len ( ) ,
5672
state.len ( ) ,
5673
" the remote has every commit"
5674
) ;
5675
}
5676
5677
5678
5679
5680
# [ test ]
5681
fn operations_from_the_same_parent_are_merged ( ) {
5682
hermetic ( ) ;
5683
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5684
let root = dir.path ( ) ;
5685
let repo = Repo:: init( root) .unwrap ( ) ;
5686
write ( root, " a.txt" , b " a\n " ) ;
5687
let a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
5688
write ( root, " b.txt" , b " b\n " ) ;
5689
let b = repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
5690
let ( x, op) = repo.head_op ( ) .unwrap ( ) ;
5691
let state = | heads : Vec< ChangeHash> | repo.store ( ) .put_state ( & StateObject:: new( heads) ) .unwrap ( ) ;
5692
5693
5694
let mut one = op.view.clone ( ) ;
5695
one.bookmarks.insert ( " main" .into ( ) , state ( vec! [ a] ) ) ;
5696
one.bookmarks.insert ( " topic" .into ( ) , state ( vec! [ a] ) ) ;
5697
let mut two = op.view.clone ( ) ;
5698
two.bookmarks.insert ( " main" .into ( ) , state ( vec! [ b] ) ) ;
5699
repo.record_op ( & [ x] , " one" , one) .unwrap ( ) ;
5700
let merged = repo.record_op ( & [ x] , " two" , two) .unwrap ( ) ;
5701
5702
let ( head, op) = repo.head_op ( ) .unwrap ( ) ;
5703
assert_eq! ( head, merged) ;
5704
assert_eq! ( op.parents.len ( ) , 2 , " a merge of the two operations" ) ;
5705
assert! ( op.command.contains ( " merged with a concurrent operation" ) ) ;
5706
let mut main = repo
5707
.store ( )
5708
.get_state ( & op.view.bookmarks[ " main" ] )
5709
.unwrap ( )
5710
.heads;
5711
main.sort ( ) ;
5712
let mut want = vec! [ a, b] ;
5713
want.sort ( ) ;
5714
assert_eq! ( main, want, " both sides' heads are kept" ) ;
5715
assert! (
5716
op.view.bookmarks.contains_key ( " topic" ) ,
5717
" a one-sided change survives"
5718
) ;
5719
assert_eq! ( repo.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
5720
}
3e5772cc Andy McCall 1 month ago ↶
5721
5722
# [ test ]
5723
fn failed_requests_come_with_a_next_step ( ) {
5724
use crate :: protocol:: hint_for;
5725
assert! ( hint_for ( " https://h/x/oxy/v1/push: HTTP 401: bad token" )
5726
.unwrap ( )
5727
.contains ( " token" ) ) ;
5728
assert! ( hint_for ( " …: HTTP 403: forbidden" )
5729
.unwrap ( )
5730
.contains ( " repo:write" ) ) ;
5731
assert! ( hint_for ( " …: HTTP 404: no such repository" )
5732
.unwrap ( )
5733
.contains ( " owner and repository name" ) ) ;
5734
assert! ( hint_for ( " invalid peer certificate: UnknownIssuer" )
5735
.unwrap ( )
5736
.contains ( " oxyhub-ca.pem" ) ) ;
5737
assert! ( hint_for ( " Connect error: Connection refused (os error 111)" )
5738
.unwrap ( )
5739
.contains ( " running" ) ) ;
5740
assert! ( hint_for ( " Dns Failed: failed to lookup address information" )
5741
.unwrap ( )
5742
.contains ( " resolve" ) ) ;
5743
assert_eq! ( hint_for ( " something else entirely" ) , None ) ;
5744
}
5745
5746
# [ test ]
5747
fn doctor_names_problems_and_what_to_do ( ) {
5748
hermetic ( ) ;
5749
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5750
let root = dir.path ( ) ;
5751
let origin = root.join ( " origin" ) ;
5752
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
5753
let mut repo = Repo:: init( & root.join ( " r" ) )
5754
.unwrap ( )
5755
.with_home ( root.join ( " home" ) ) ;
5756
let find = | r : & DoctorReport, area : & str , h : Health| {
5757
r.checks
5758
.iter ( )
5759
.find ( | c | c.area = = area & & c.health = = h )
5760
.cloned ( )
5761
} ;
5762
5763
let r = repo.doctor ( true ) ;
5764
assert! ( find ( & r, " identity" , Health:: Warn)
5765
.unwrap ( )
5766
.hint
5767
.unwrap ( )
5768
.contains ( " oxy identity create" ) ) ;
5769
assert! ( find ( & r, " repository" , Health:: Ok) .is_some ( ) ) ;
5770
5771
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
5772
.unwrap ( ) ;
5773
repo.config.set ( " author.name" , " Ada" ) .unwrap ( ) ;
5774
repo.config.set ( " author.email" , " [email protected] " ) .unwrap ( ) ;
5775
repo.config
5776
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
5777
.unwrap ( ) ;
5778
repo.config
5779
.set ( " remote.down.url" , " http://127.0.0.1:9/a/b" )
5780
.unwrap ( ) ;
5781
repo.config.set ( " remote.down.token" , " t" ) .unwrap ( ) ;
5782
repo.config
5783
.set ( " remote.badca.url" , " https://example.invalid/a/b" )
5784
.unwrap ( ) ;
5785
repo.config
5786
.set (
5787
" remote.badca.ca" ,
5788
& root.join ( " missing.pem" ) .to_string_lossy ( ) ,
5789
)
5790
.unwrap ( ) ;
5791
repo.save_config ( ) .unwrap ( ) ;
5792
5793
let r = repo.doctor ( true ) ;
5794
assert! ( find ( & r, " identity" , Health:: Ok) .is_some ( ) , " {r:?}" ) ;
5795
assert! ( find ( & r, " author" , Health:: Ok) .is_some ( ) , " {r:?}" ) ;
5796
assert! ( find ( & r, " remote origin" , Health:: Ok)
5797
.unwrap ( )
5798
.message
5799
.contains ( " first push binds it" ) ) ;
5800
let down = find ( & r, " remote down" , Health:: Fail) .unwrap ( ) ;
5801
assert! (
5802
down.hint.as_deref ( ) .unwrap_or ( " " ) .contains ( " running" ) ,
5803
" {down:?}"
5804
) ;
5805
let badca = find ( & r, " remote badca" , Health:: Fail) .unwrap ( ) ;
5806
assert! (
5807
badca.hint.unwrap ( ) .contains ( " remote.badca.ca" ) ,
5808
" the remote's own name is in the hint"
5809
) ;
5810
assert_eq! ( r.worst ( ) , Health:: Fail) ;
5811
5812
5813
let r = repo.doctor ( false ) ;
5814
assert! ( find ( & r, " remote down" , Health:: Fail) .is_none ( ) ) ;
5815
assert! ( find ( & r, " remote down" , Health:: Ok)
5816
.unwrap ( )
5817
.message
5818
.contains ( " not contacted" ) ) ;
5819
}
62794ecc Andy McCall 1 month ago ↶
5820
5821
5822
5823
5824
# [ test ]
5825
fn deleting_a_line_after_an_insertion_below_it_is_not_a_conflict ( ) {
5826
hermetic ( ) ;
5827
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5828
let root = dir.path ( ) ;
5829
let repo = Repo:: init( root) .unwrap ( ) ;
5830
write ( root, " f" , b " a\n b\n c\n " ) ;
5831
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
5832
write ( root, " f" , b " a\n b\n x\n c\n " ) ;
5833
let insert = repo.commit ( " insert x after b" , & [ ] ) .unwrap ( ) ;
5834
write ( root, " f" , b " a\n x\n c\n " ) ;
5835
let delete = repo.commit ( " delete b" , & [ ] ) .unwrap ( ) ;
5836
assert! (
5837
repo.store ( )
5838
.get_change ( & delete)
5839
.unwrap ( )
5840
.deps
5841
.contains ( & insert) ,
5842
" the deletion depends on the insertion below the deleted line"
5843
) ;
5844
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5845
assert_eq! ( heads, vec! [ delete] ) ;
5846
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
5847
assert! ( ! snap.is_conflicted ( ) ) ;
5848
assert! (
5849
matches! ( & snap.files[ " f" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n x\n c\n " )
5850
) ;
5851
}
5852
5853
5854
5855
# [ test ]
5856
fn a_binary_file_reverted_to_an_earlier_blob_keeps_it ( ) {
5857
hermetic ( ) ;
5858
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5859
let root = dir.path ( ) ;
5860
let repo = Repo:: init( root) .unwrap ( ) ;
5861
write ( root, " bin" , b " \0 A" ) ;
5862
let v1 = repo.commit ( " v1" , & [ ] ) .unwrap ( ) ;
5863
write ( root, " bin" , b " \0 B" ) ;
5864
let v2 = repo.commit ( " v2" , & [ ] ) .unwrap ( ) ;
5865
assert! ( repo.store ( ) .get_change ( & v2) .unwrap ( ) .deps.contains ( & v1) ) ;
5866
write ( root, " bin" , b " \0 A" ) ;
5867
let v3 = repo.commit ( " back to A" , & [ ] ) .unwrap ( ) ;
5868
assert! ( repo.store ( ) .get_change ( & v3) .unwrap ( ) .deps.contains ( & v2) ) ;
5869
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ v3] ) ;
5870
let state = repo.load_state ( & [ v3] ) .unwrap ( ) ;
5871
let snap = state.materialize ( ) ;
5872
let FileContent:: Opaque { blobs } = & snap.files[ " bin" ] .content else {
5873
panic! ( " opaque" )
5874
} ;
5875
assert_eq! ( blobs.len ( ) , 1 , " one live blob, not none and not two" ) ;
5876
assert_eq! ( repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) , b " \0 A" ) ;
5877
}
5878
5879
5880
5881
5882
# [ test ]
5883
fn git_import_resolves_a_binary_merged_in_git_and_verifies ( ) {
5884
hermetic ( ) ;
5885
if std:: process:: Command:: new( " git" )
5886
.arg ( " --version" )
5887
.output ( )
5888
.is_err ( )
5889
{
5890
eprintln! ( " git not installed; skipping" ) ;
5891
return ;
5892
}
5893
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5894
let root = dir.path ( ) ;
5895
let g = root.join ( " src.git" ) ;
5896
fs:: create_dir_all( & g) .unwrap ( ) ;
5897
let git = | args : & [& str ]| -> String {
5898
let out = std:: process:: Command:: new( " git" )
5899
.arg ( " -C" )
5900
.arg ( & g)
5901
.args ( [
5902
" -c" ,
5903
" user.name=Grace" ,
5904
" -c" ,
5905
" [email protected] " ,
5906
" -c" ,
5907
" commit.gpgsign=false" ,
5908
] )
5909
.args ( args)
5910
.output ( )
5911
.unwrap ( ) ;
5912
assert! (
5913
out.status.success ( ) ,
5914
" git {args:?}: {}" ,
5915
String :: from_utf8_lossy( & out.stderr)
5916
) ;
5917
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
5918
} ;
5919
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
5920
write ( & g, " logo.bin" , b " \0 base" ) ;
5921
write ( & g, " notes.txt" , b " a\n b\n c\n " ) ;
5922
git ( & [ " add" , " ." ] ) ;
5923
git ( & [ " commit" , " -qm" , " base" ] ) ;
5924
git ( & [ " checkout" , " -qb" , " side" ] ) ;
5925
write ( & g, " logo.bin" , b " \0 side" ) ;
5926
write ( & g, " notes.txt" , b " a\n b\n x\n c\n " ) ;
5927
git ( & [ " commit" , " -qam" , " side" ] ) ;
5928
git ( & [ " checkout" , " -q" , " main" ] ) ;
5929
write ( & g, " logo.bin" , b " \0 main" ) ;
5930
git ( & [ " commit" , " -qam" , " main" ] ) ;
5931
5932
let _ = std:: process:: Command:: new( " git" )
5933
.arg ( " -C" )
5934
.arg ( & g)
5935
.args ( [
5936
" -c" ,
5937
" user.name=G" ,
5938
" -c" ,
5939
" user.email=g@e" ,
5940
" merge" ,
5941
" -q" ,
5942
" side" ,
5943
] )
5944
.output ( ) ;
5945
write ( & g, " logo.bin" , b " \0 main" ) ;
5946
git ( & [ " add" , " ." ] ) ;
5947
git ( & [ " commit" , " -qm" , " merge side" ] ) ;
5948
5949
write ( & g, " logo.bin" , b " \0 base" ) ;
5950
git ( & [ " commit" , " -qam" , " back to base" ] ) ;
5951
5952
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
5953
let rep = repo.import_git ( & g, " main" ) .unwrap ( ) ;
5954
assert_eq! ( rep.merges, 1 ) ;
5955
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
5956
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
5957
assert_eq! ( v.commits, 5 ) ;
5958
let snap = repo
5959
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
5960
.unwrap ( )
5961
.materialize ( ) ;
5962
assert! ( ! snap.is_conflicted ( ) ) ;
5963
let FileContent:: Opaque { blobs } = & snap.files[ " logo.bin" ] .content else {
5964
panic! ( " opaque" )
5965
} ;
5966
assert_eq! ( repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) , b " \0 base" ) ;
5967
}
c863c7a7 Andy McCall 1 month ago ↶
5968
5969
5970
5971
5972
# [ cfg ( unix ) ]
5973
# [ test ]
5974
fn windows_file_systems_keep_exec_bits_and_symlinks ( ) {
5975
use crate :: portable:: Platform;
5976
use std:: os:: unix:: fs:: PermissionsExt;
5977
hermetic ( ) ;
5978
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5979
let root = dir.path ( ) ;
5980
let repo = Repo:: init( root) .unwrap ( ) ;
5981
write ( root, " run.sh" , b " #!/bin/sh\n " ) ;
5982
fs:: set_permissions( root.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
5983
std:: os:: unix:: fs:: symlink( " run.sh" , root.join ( " link" ) ) .unwrap ( ) ;
5984
repo.commit ( " script and link" , & [ ] ) .unwrap ( ) ;
5985
drop ( repo) ;
5986
5987
5988
fs:: set_permissions( root.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
5989
fs:: remove_file( root.join ( " link" ) ) .unwrap ( ) ;
5990
write ( root, " link" , b " run.sh" ) ;
5991
let win = Repo:: open( root) .unwrap ( ) .with_platform ( Platform:: windows( ) ) ;
5992
let s = win.snapshot ( " oxy status" ) .unwrap ( ) ;
5993
assert! (
5994
s.statuses.is_empty ( ) ,
5995
" nothing changed on Windows: {:?}" ,
5996
statuses ( & s)
5997
) ;
5998
5999
write ( root, " run.sh" , b " #!/bin/sh\n echo hi\n " ) ;
6000
write ( root, " link" , b " other" ) ;
6001
let h = win.commit ( " edit on Windows" , & [ ] ) .unwrap ( ) ;
6002
let snap = win.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
6003
assert! ( snap.files[ " run.sh" ] .mode.exec, " the exec bit survives" ) ;
6004
assert_eq! ( snap.files[ " link" ] .kind, NodeKind:: Symlink) ;
6005
6006
let linux = Repo:: open( root) .unwrap ( ) ;
6007
assert! ( ! linux.snapshot ( " oxy status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6008
}
6009
6010
6011
6012
# [ test ]
6013
fn paths_other_platforms_cannot_hold_are_reported ( ) {
6014
use crate :: portable:: Platform;
6015
hermetic ( ) ;
6016
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6017
let root = dir.path ( ) ;
6018
let repo = Repo:: init( root) .unwrap ( ) ;
6019
write ( root, " README.md" , b " a\n " ) ;
6020
write ( root, " readme.md" , b " b\n " ) ;
6021
write ( root, " src/aux.c" , b " int x;\n " ) ;
6022
let h = repo.commit ( " unportable" , & [ ] ) .unwrap ( ) ;
6023
let r = repo.doctor ( false ) ;
6024
let port = r.checks.iter ( ) .find ( | c | c.area = = " portability" ) .unwrap ( ) ;
6025
assert_eq! ( port.health, Health:: Warn) ;
6026
assert! (
6027
port.message.contains ( " differ only in case" ) & & port.message.contains ( " aux.c" ) ,
6028
" {}" ,
6029
port.message
6030
) ;
6031
let mac = Repo:: open( root) .unwrap ( ) .with_platform ( Platform:: macos( ) ) ;
6032
let rep = mac.checkout ( & [ h] , true , " oxy checkout" ) .unwrap ( ) ;
6033
assert! (
6034
rep.notes
6035
.iter ( )
6036
.any ( | n | n.contains ( " README.md and readme.md" ) ) ,
6037
" {:?}" ,
6038
rep.notes
6039
) ;
6040
assert! (
6041
! rep.notes.iter ( ) .any ( | n | n.contains ( " aux.c" ) ) ,
6042
" macOS allows aux.c"
6043
) ;
6044
}
bc489b6c Andy McCall 1 month ago ↶
6045
6046
# [ test ]
6047
fn history_and_blame_of_a_loaded_state_match_the_full_walk ( ) {
6048
hermetic ( ) ;
6049
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6050
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
6051
write ( dir.path ( ) , " a.txt" , b " one\n " ) ;
6052
write ( dir.path ( ) , " other.txt" , b " x\n " ) ;
6053
write ( dir.path ( ) , " bin.dat" , & [ 0 u8 , 1 , 2 , 0 , 255 ] ) ;
6054
repo.snapshot ( " t" ) .unwrap ( ) ;
6055
let c1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
6056
write ( dir.path ( ) , " other.txt" , b " y\n " ) ;
6057
repo.snapshot ( " t" ) .unwrap ( ) ;
6058
repo.commit ( " other only" , & [ ] ) .unwrap ( ) ;
6059
write ( dir.path ( ) , " a.txt" , b " one\n two\n " ) ;
6060
repo.snapshot ( " t" ) .unwrap ( ) ;
6061
let c3 = repo.commit ( " second line" , & [ ] ) .unwrap ( ) ;
6062
write ( dir.path ( ) , " other.txt" , b " z\n " ) ;
6063
repo.snapshot ( " t" ) .unwrap ( ) ;
6064
repo.commit ( " other again" , & [ ] ) .unwrap ( ) ;
6065
6066
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6067
let state = repo.load_state ( & heads) .unwrap ( ) ;
6068
let full: Vec< ChangeHash> = repo
6069
.log_for_path_in ( " a.txt" , & heads)
6070
.unwrap ( )
6071
.iter ( )
6072
.map ( | e | e.hash )
6073
.collect ( ) ;
6074
assert_eq! (
6075
full,
6076
vec! [ c3, c1] ,
6077
" newest first, only changes touching the path"
6078
) ;
6079
let newest: Vec< ChangeHash> = repo
6080
.log_for_path_of ( & state, " a.txt" , Some ( 1 ) )
6081
.unwrap ( )
6082
.iter ( )
6083
.map ( | e | e.hash )
6084
.collect ( ) ;
6085
assert_eq! ( newest, vec! [ c3] , " stops at the first" ) ;
6086
assert_eq! (
6087
repo.log_for_path_of ( & state, " a.txt" , None ) .unwrap ( ) .len ( ) ,
6088
2
6089
) ;
6090
assert! ( repo
6091
.log_for_path_of ( & state, " missing.txt" , Some ( 1 ) )
6092
.is_err ( ) ) ;
6093
assert_eq! (
6094
repo.log_state ( & heads) .unwrap ( ) .len ( ) ,
6095
repo.log_of ( & state) .len ( )
6096
) ;
6097
6098
let tree = state.tree ( ) .view ( ) ;
6099
let via_state = repo.blame_of ( & state, & tree, " a.txt" ) .unwrap ( ) ;
6100
let via_heads = repo.blame_in ( " a.txt" , & heads) .unwrap ( ) ;
6101
assert_eq! (
6102
via_state
6103
.iter ( )
6104
.map ( | l | ( l.hash, l.text.clone ( ) ) )
6105
.collect:: < Vec< _ > > ( ) ,
6106
via_heads
6107
.iter ( )
6108
.map ( | l | ( l.hash, l.text.clone ( ) ) )
6109
.collect:: < Vec< _ > > ( )
6110
) ;
6111
assert_eq! (
6112
via_state.iter ( ) .map ( | l | l.hash ) .collect:: < Vec< _ > > ( ) ,
6113
vec! [ c1, c3]
6114
) ;
6115
assert! ( repo.blame_of ( & state, & tree, " missing.txt" ) .is_err ( ) ) ;
6116
}
696bbf29 Andy McCall 1 month ago ↶
6117
6118
6119
6120
6121
# [ cfg ( unix ) ]
6122
# [ test ]
6123
fn stat_cache_takes_unchanged_text_from_the_base_and_misses_no_edit ( ) {
6124
use std:: os:: unix:: fs:: PermissionsExt;
6125
hermetic ( ) ;
6126
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6127
let root = dir.path ( ) ;
6128
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6129
write ( root, " a.txt" , b " one\n two\n " ) ;
6130
write ( root, " b.txt" , b " bee\n " ) ;
6131
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6132
let age = | p : & str | {
6133
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6134
fs:: File:: options( )
6135
.write ( true )
6136
.open ( root.join ( p) )
6137
.unwrap ( )
6138
.set_modified ( old)
6139
.unwrap ( ) ;
6140
} ;
6141
age ( " a.txt" ) ;
6142
age ( " b.txt" ) ;
6143
assert! (
6144
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
6145
" records the aged stat"
6146
) ;
6147
6148
6149
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6150
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
6151
assert! (
6152
! snap.changed & & snap.statuses.is_empty ( ) ,
6153
" {:?}" ,
6154
snap.statuses
6155
) ;
6156
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6157
6158
6159
6160
write ( root, " b.txt" , b " bee edited\n " ) ;
6161
age ( " b.txt" ) ;
6162
let first = repo.snapshot ( " status" ) .unwrap ( ) ;
6163
assert_eq! (
6164
statuses ( & first) ,
6165
vec! [ ( " b.txt" .to_string ( ) , ChangeStatus:: Modified) ]
6166
) ;
6167
let again = repo.snapshot ( " status" ) .unwrap ( ) ;
6168
assert_eq! (
6169
statuses ( & again) ,
6170
vec! [ ( " b.txt" .to_string ( ) , ChangeStatus:: Modified) ] ,
6171
" still seen once stat-clean"
6172
) ;
6173
assert! ( ! again.changed, " and the working copy hashes the same" ) ;
6174
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6175
assert! (
6176
repo.snapshot ( " status" ) .is_err ( ) ,
6177
" an edited file must be read, never taken from the base"
6178
) ;
6179
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6180
6181
6182
write ( root, " a.txt" , b " one\n TWO\n " ) ;
6183
assert_eq! (
6184
statuses ( & repo.snapshot ( " status" ) .unwrap ( ) ) ,
6185
vec! [
6186
( " a.txt" .to_string ( ) , ChangeStatus:: Modified) ,
6187
( " b.txt" .to_string ( ) , ChangeStatus:: Modified)
6188
]
6189
) ;
6190
6191
repo.commit ( " edits" , & [ ] ) .unwrap ( ) ;
6192
age ( " a.txt" ) ;
6193
age ( " b.txt" ) ;
6194
assert! ( repo.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6195
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6196
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6197
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
6198
assert! ( ! snap.changed & & snap.statuses.is_empty ( ) ) ;
6199
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6200
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6201
}
afaba5f1 Andy McCall 1 month ago ↶
6202
6203
6204
6205
6206
# [ test ]
6207
fn checkpoints_keep_large_graphs_as_blobs_of_their_own ( ) {
6208
hermetic ( ) ;
6209
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6210
let root = dir.path ( ) ;
6211
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6212
let long: String = ( 0 .. 300 )
6213
.map ( | i | format! ( " line {i} of a long file\n " ) )
6214
.collect ( ) ;
6215
write ( root, " long.txt" , long.as_bytes ( ) ) ;
6216
write ( root, " short.txt" , b " short\n " ) ;
6217
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6218
let ( r1, _ , _ ) = repo.checkpoint ( ) .unwrap ( ) ;
6219
let cp1 = crate :: objects:: Checkpoint:: decode(
6220
& repo.store ( ) .get_typed ( & r1, ObjectType:: Checkpoint) .unwrap ( ) ,
6221
)
6222
.unwrap ( ) ;
6223
let graphs1 = repo.checkpoint_graph_blobs ( & cp1) .unwrap ( ) ;
6224
assert_eq! ( graphs1.len ( ) , 1 , " only the long file's graph is a blob" ) ;
6225
6226
6227
repo.gc ( true ) .unwrap ( ) ;
6228
assert! ( repo.store ( ) .has ( & graphs1[ 0 ] ) ) ;
6229
let report = repo.fsck ( ) .unwrap ( ) ;
6230
assert_eq! ( report.errors ( ) , 0 , " {:?}" , report.problems) ;
6231
6232
6233
write ( root, " short.txt" , b " short, edited\n " ) ;
6234
repo.commit ( " edit the short file" , & [ ] ) .unwrap ( ) ;
6235
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6236
let fast = repo.load_state ( & heads) .unwrap ( ) ;
6237
let full = repo.load_state_full ( & heads) .unwrap ( ) ;
6238
assert_eq! ( fast.checkpoint_payload ( ) , full.checkpoint_payload ( ) ) ;
6239
6240
6241
let ( r2, _ , _ ) = repo.checkpoint ( ) .unwrap ( ) ;
6242
let cp2 = crate :: objects:: Checkpoint:: decode(
6243
& repo.store ( ) .get_typed ( & r2, ObjectType:: Checkpoint) .unwrap ( ) ,
6244
)
6245
.unwrap ( ) ;
6246
assert_eq! ( repo.checkpoint_graph_blobs ( & cp2) .unwrap ( ) , graphs1) ;
6247
6248
6249
let bundle = repo.checkpoint_bundle_for ( & heads) .unwrap ( ) ;
6250
assert! ( bundle.objects.iter ( ) .any ( | ( t , body ) | * t = = ObjectType:: Blob
6251
& & crate :: hash:: hash_object( ObjectType:: Blob, body) = = graphs1[ 0 ] ) ) ;
6252
6253
6254
let state = repo.load_state_full ( & heads) .unwrap ( ) ;
6255
let old = repo.store_blob ( & state.checkpoint_payload ( ) ) .unwrap ( ) ;
6256
let cp_old = crate :: objects:: Checkpoint {
6257
heads: cp2.heads.clone ( ) ,
6258
count: cp2.count,
6259
payload: old,
6260
} ;
6261
let r_old = repo
6262
.store ( )
6263
.put ( ObjectType:: Checkpoint, & cp_old.encode ( ) )
6264
.unwrap ( ) ;
6265
repo.install_checkpoint ( " main" , & r_old) .unwrap ( ) ;
6266
assert_eq! (
6267
repo.load_state ( & heads) .unwrap ( ) .checkpoint_payload ( ) ,
6268
full.checkpoint_payload ( )
6269
) ;
6270
assert_eq! ( repo.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
6271
6272
6273
6274
let dir2 = tempfile:: tempdir( ) .unwrap ( ) ;
6275
let root2 = dir2.path ( ) ;
6276
let repo2 = Repo:: init( root2) .unwrap ( ) .with_home ( root2.join ( " home" ) ) ;
6277
write ( root2, " long.txt" , long.as_bytes ( ) ) ;
6278
repo2.commit ( " base" , & [ ] ) .unwrap ( ) ;
6279
let ( r, _ , _ ) = repo2.checkpoint ( ) .unwrap ( ) ;
6280
let cp = crate :: objects:: Checkpoint:: decode(
6281
& repo2.store ( ) .get_typed ( & r, ObjectType:: Checkpoint) .unwrap ( ) ,
6282
)
6283
.unwrap ( ) ;
6284
let graph = repo2.checkpoint_graph_blobs ( & cp) .unwrap ( ) [ 0 ] ;
64818938 Andy McCall 1 month ago ↶
6285
repo2.store ( ) .remove_loose_for_test ( & graph) .unwrap ( ) ;
afaba5f1 Andy McCall 1 month ago ↶
6286
assert! ( repo2.load_state ( & repo2.bookmark_heads ( ) .unwrap ( ) ) .is_err ( ) ) ;
6287
assert! ( repo2.fsck ( ) .unwrap ( ) .errors ( ) > 0 , " fsck reports it" ) ;
6288
}
e3d071ce Andy McCall 1 month ago ↶
6289
6290
6291
6292
6293
# [ test ]
6294
fn identity_is_found_in_the_key_directory_for_a_new_repository ( ) {
6295
hermetic ( ) ;
6296
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6297
let home = dir.path ( ) .join ( " home" ) ;
6298
let mut first = Repo:: init( & dir.path ( ) .join ( " a" ) )
6299
.unwrap ( )
6300
.with_home ( home.clone ( ) ) ;
6301
let ( key, _ ) = first
6302
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
6303
.unwrap ( ) ;
6304
let fresh = Repo:: init( & dir.path ( ) .join ( " b" ) )
6305
.unwrap ( )
6306
.with_home ( home.clone ( ) ) ;
6307
assert! ( ! fresh.store ( ) .has ( & key) , " nothing stored yet" ) ;
6308
let id = fresh.identity ( & key) .unwrap ( ) ;
6309
assert_eq! ( ( id.name.as_str ( ) , id.hash ( ) ) , ( " Ada" , key) ) ;
6310
6311
let keys = fresh.keys_dir.clone ( ) ;
6312
let ( other, _ ) = first
6313
.create_identity ( " Bob" , " [email protected] " , IdentityScope:: User)
6314
.unwrap ( ) ;
6315
fs:: copy(
6316
keys.join ( format! ( " {} .identity" , other.to_hex ( ) ) ) ,
6317
keys.join ( format! ( " {} .identity" , key.to_hex ( ) ) ) ,
6318
)
6319
.unwrap ( ) ;
6320
assert! ( fresh
6321
.identity ( & key)
6322
.unwrap_err ( )
6323
.to_string ( )
6324
.contains ( " does not hold identity" ) ) ;
6325
}
cc7a0056 Andy McCall 1 month ago ↶
6326
6327
6328
6329
6330
6331
# [ cfg ( unix ) ]
6332
# [ test ]
6333
fn status_snapshots_skip_files_unchanged_since_the_manifest ( ) {
6334
use std:: os:: unix:: fs:: PermissionsExt;
6335
hermetic ( ) ;
6336
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6337
let root = dir.path ( ) ;
6338
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6339
for ( p, c) in [ ( " a.txt" , " one\n " ) , ( " b.txt" , " bee\n " ) , ( " d/c.txt" , " sea\n " ) ] {
6340
write ( root, p, c.as_bytes ( ) ) ;
6341
}
6342
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6343
let age = | p : & str | {
6344
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6345
fs:: File:: options( )
6346
.write ( true )
6347
.open ( root.join ( p) )
6348
.unwrap ( )
6349
.set_modified ( old)
6350
.unwrap ( ) ;
6351
} ;
6352
let lock = | p : & str , mode : u32 | {
6353
fs:: set_permissions( root.join ( p) , fs:: Permissions:: from_mode( mode) ) .unwrap ( )
6354
} ;
6355
for p in [ " a.txt" , " b.txt" , " d/c.txt" ] {
6356
age ( p) ;
6357
}
6358
assert! ( repo.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6359
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
6360
assert! (
6361
m.entries.values ( ) .all ( | e | e.graph.is_some ( ) ) ,
6362
" keys recorded"
6363
) ;
6364
6365
6366
6367
lock ( " a.txt" , 0o000 ) ;
6368
lock ( " b.txt" , 0o000 ) ;
6369
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6370
assert! ( ! snap.changed & & snap.statuses.is_empty ( ) ) ;
6371
assert_eq! (
6372
snap.base_snapshot
6373
.unrendered
6374
.iter ( )
6375
.cloned ( )
6376
.collect:: < Vec< _ > > ( ) ,
6377
vec! [ " a.txt" , " b.txt" , " d/c.txt" ]
6378
) ;
6379
assert! ( ! snap.base.is_full ( ) ) ;
6380
assert! ( ! snap.base_snapshot.is_conflicted ( ) ) ;
6381
6382
let full = repo.snapshot ( " status" ) .unwrap ( ) ;
6383
assert! ( full.statuses.is_empty ( ) & & full.base.is_full ( ) ) ;
6384
assert! ( full.base_snapshot.unrendered.is_empty ( ) ) ;
6385
assert! ( matches! ( & full.base_snapshot.files[ " a.txt" ] .content,
6386
FileContent:: Text { bytes, .. } if bytes = = b " one\n " ) ) ;
6387
6388
6389
write ( root, " d/c.txt" , b " sea edited\n " ) ;
6390
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6391
assert_eq! (
6392
statuses ( & snap) ,
6393
vec! [ ( " d/c.txt" .to_string ( ) , ChangeStatus:: Modified) ]
6394
) ;
6395
assert! ( ! snap.base_snapshot.unrendered.contains ( " d/c.txt" ) ) ;
6396
6397
lock ( " b.txt" , 0o100 ) ;
6398
assert_eq! (
6399
statuses ( & repo.snapshot_status ( " status" ) .unwrap ( ) ) ,
6400
vec! [
6401
( " b.txt" .to_string ( ) , ChangeStatus:: ModeChanged) ,
6402
( " d/c.txt" .to_string ( ) , ChangeStatus:: Modified)
6403
]
6404
) ;
6405
lock ( " b.txt" , 0o000 ) ;
6406
6407
6408
6409
repo.commit_selection ( " edit c" , & [ ] , & Selection:: Paths( vec! [ " d/c.txt" .into ( ) ] ) )
6410
.unwrap ( ) ;
6411
age ( " d/c.txt" ) ;
6412
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6413
assert! ( snap.statuses.is_empty ( ) , " {:?}" , snap.statuses) ;
6414
assert! ( snap.base_snapshot.unrendered.contains ( " a.txt" ) ) ;
6415
lock ( " d/c.txt" , 0o000 ) ;
6416
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6417
assert! ( snap.statuses.is_empty ( ) ) ;
6418
assert! ( snap.base_snapshot.unrendered.contains ( " d/c.txt" ) ) ;
6419
for p in [ " a.txt" , " b.txt" , " d/c.txt" ] {
6420
lock ( p, 0o644 ) ;
6421
}
6422
}
6423
6424
6425
6426
# [ cfg ( unix ) ]
6427
# [ test ]
6428
fn a_changed_attributes_file_disables_skipping_unchanged_files ( ) {
6429
hermetic ( ) ;
6430
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6431
let root = dir.path ( ) ;
6432
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6433
write ( root, " a.txt" , b " crlf\r \n " ) ;
6434
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6435
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6436
fs:: File:: options( )
6437
.write ( true )
6438
.open ( root.join ( " a.txt" ) )
6439
.unwrap ( )
6440
.set_modified ( old)
6441
.unwrap ( ) ;
6442
assert! ( repo.snapshot_status ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6443
assert! ( repo
6444
.snapshot_status ( " status" )
6445
.unwrap ( )
6446
.base_snapshot
6447
.unrendered
6448
.contains ( " a.txt" ) ) ;
6449
6450
write ( root, " .oxyattributes" , b " *.txt eol=lf\n " ) ;
6451
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6452
assert_eq! (
6453
statuses ( & snap) ,
6454
vec! [
6455
( " .oxyattributes" .to_string ( ) , ChangeStatus:: Added) ,
6456
( " a.txt" .to_string ( ) , ChangeStatus:: Modified)
6457
]
6458
) ;
6459
}
54cd08a1 Andy McCall 1 month ago ↶
6460
6461
6462
6463
6464
# [ test ]
6465
fn bookmarks_with_slashes_push_and_checkpoint ( ) {
6466
hermetic ( ) ;
6467
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6468
let root = dir.path ( ) ;
6469
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
6470
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
6471
let mut repo = Repo:: clone( & url, & root.join ( " r" ) )
6472
.unwrap ( )
6473
.with_home ( root.join ( " home" ) ) ;
6474
6475
6476
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
6477
.unwrap ( ) ;
6478
let r = root.join ( " r" ) ;
6479
write ( & r, " a.txt" , b " a\n " ) ;
6480
let h_a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
6481
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
6482
origin.refresh ( ) .unwrap ( ) ;
54cd08a1 Andy McCall 1 month ago ↶
6483
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
6484
repo.bookmark_create ( " feature/x" , None ) .unwrap ( ) ;
6485
repo.bookmark_switch ( " feature/x" , false ) .unwrap ( ) ;
6486
write ( & r, " b.txt" , b " b\n " ) ;
6487
let h_b = repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
6488
for name in [ " feature" , " feature/x" ] {
6489
let res = repo.push_bookmark ( " origin" , name, false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
6490
origin.refresh ( ) .unwrap ( ) ;
54cd08a1 Andy McCall 1 month ago ↶
6491
assert! ( res[ 0 ] .ok, " {name}: {res:?}" ) ;
6492
}
6493
assert_eq! ( origin.resolve_heads ( " feature" ) .unwrap ( ) , vec! [ h_a] ) ;
6494
assert_eq! (
6495
origin.resolve_heads ( " feature/x" ) .unwrap ( ) ,
6496
repo.resolve_heads ( " feature/x" ) .unwrap ( )
6497
) ;
6498
assert! ( origin.resolve_heads ( " feature/x" ) .unwrap ( ) .contains ( & h_b) ) ;
6499
assert! ( origin.latest_ref_attest ( " feature/x" ) .unwrap ( ) .is_some ( ) ) ;
6500
assert! ( origin.latest_ref_attest ( " feature" ) .unwrap ( ) .is_some ( ) ) ;
6501
6502
6503
let ( cp, _ , _ ) = repo.checkpoint ( ) .unwrap ( ) ;
6504
assert_eq! ( repo.latest_checkpoint ( " feature/x" ) .unwrap ( ) .unwrap ( ) .0 , cp) ;
6505
assert! ( repo.latest_checkpoint ( " feature" ) .unwrap ( ) .is_none ( ) ) ;
6506
let heads = repo.resolve_heads ( " feature/x" ) .unwrap ( ) ;
6507
assert! ( repo.load_state_checkpointed ( & heads) .unwrap ( ) .is_some ( ) ) ;
f8c69576 Andy McCall 1 month ago ↶
6508
}
6509
6510
6511
6512
6513
# [ test ]
6514
fn bookmarks_are_deleted_on_a_remote ( ) {
6515
hermetic ( ) ;
6516
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6517
let root = dir.path ( ) ;
6518
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
6519
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
6520
let mut repo = Repo:: clone( & url, & root.join ( " r" ) )
6521
.unwrap ( )
6522
.with_home ( root.join ( " home" ) ) ;
6523
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
6524
.unwrap ( ) ;
6525
let r = root.join ( " r" ) ;
6526
write ( & r, " a.txt" , b " a\n " ) ;
6527
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
6528
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
6529
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6530
repo.bookmark_create ( " feature/x" , None ) .unwrap ( ) ;
6531
assert! ( repo.push_bookmark ( " origin" , " feature/x" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
6532
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6533
assert! ( origin.resolve_heads ( " feature/x" ) .is_ok ( ) ) ;
6534
6535
let res = repo.push_delete ( " origin" , " feature/x" ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
6536
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6537
assert! ( res[ 0 ] .ok, " {res:?}" ) ;
6538
assert! ( ! origin.view ( ) .unwrap ( ) .bookmarks.contains_key ( " feature/x" ) ) ;
6539
let ( _ , att) = origin.latest_ref_attest ( " feature/x" ) .unwrap ( ) .unwrap ( ) ;
6540
assert! (
6541
matches! ( att.attest.body, AttestBody:: Ref { new_state, .. } if new_state.is_zero ( ) ) ,
6542
" the deletion is attested"
6543
) ;
6544
assert! (
6545
repo.view ( ) .unwrap ( ) .bookmarks.contains_key ( " feature/x" ) ,
6546
" the local bookmark stays"
6547
) ;
6548
assert! ( ! repo
6549
.view ( )
6550
.unwrap ( )
6551
.remotes
6552
.get ( " origin" )
6553
.is_some_and ( | m | m.contains_key ( " feature/x" ) ) ) ;
6554
6555
6556
let res = repo.push_bookmark ( " origin" , " feature/x" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
6557
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6558
assert! ( res[ 0 ] .ok, " {res:?}" ) ;
6559
assert! ( origin.view ( ) .unwrap ( ) .bookmarks.contains_key ( " feature/x" ) ) ;
6560
6561
6562
assert! ( repo.push_delete ( " origin" , " nope" ) .is_err ( ) ) ;
6563
let res = repo.push_delete ( " origin" , " main" ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
6564
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6565
assert! (
6566
! res[ 0 ] .ok & & res[ 0 ] .reason.contains ( " default bookmark" ) ,
6567
" {res:?}"
6568
) ;
6569
assert! ( origin.view ( ) .unwrap ( ) .bookmarks.contains_key ( " main" ) ) ;
54cd08a1 Andy McCall 1 month ago ↶
6570
}
61e3bce7 Andy McCall 1 month ago ↶
6571
6572
6573
6574
6575
# [ test ]
6576
fn switching_bookmarks_carries_uncommitted_work ( ) {
6577
hermetic ( ) ;
6578
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6579
let root = dir.path ( ) ;
6580
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6581
write ( root, " a.txt" , b " a\n " ) ;
6582
write ( root, " b.txt" , b " b\n " ) ;
6583
write ( root, " gone.txt" , b " gone\n " ) ;
6584
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6585
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
6586
6587
write ( root, " a.txt" , b " a\n more\n " ) ;
6588
write ( root, " new.txt" , b " new\n " ) ;
6589
fs:: remove_file( root.join ( " gone.txt" ) ) .unwrap ( ) ;
44f1ebe7 Andy McCall 19 days ago ↶
6590
let ( report, carried) = repo.bookmark_switch_carry ( " feature" ) .unwrap ( ) ;
61e3bce7 Andy McCall 1 month ago ↶
6591
assert_eq! ( carried, vec! [ " a.txt" , " gone.txt" , " new.txt" ] ) ;
44f1ebe7 Andy McCall 19 days ago ↶
6592
6593
assert_eq! (
6594
( report.removed, report.removed_paths.len ( ) ) ,
6595
( 0 , 0 ) ,
6596
" {report:?}"
6597
) ;
61e3bce7 Andy McCall 1 month ago ↶
6598
assert_eq! ( repo.config.bookmark, " feature" ) ;
6599
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " a\n more\n " ) ;
6600
assert_eq! ( fs:: read( root.join ( " new.txt" ) ) .unwrap ( ) , b " new\n " ) ;
6601
assert! ( ! root.join ( " gone.txt" ) .exists ( ) ) ;
6602
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
6603
assert_eq! (
6604
statuses ( & snap) ,
6605
vec! [
6606
( " a.txt" .to_string ( ) , ChangeStatus:: Modified) ,
6607
( " gone.txt" .to_string ( ) , ChangeStatus:: Removed) ,
6608
( " new.txt" .to_string ( ) , ChangeStatus:: Added)
6609
]
6610
) ;
6611
repo.commit ( " the work" , & [ ] ) .unwrap ( ) ;
6612
assert! ( repo.resolve_heads ( " main" ) .unwrap ( ) .len ( ) = = 1 ) ;
6613
6614
6615
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
6616
write ( root, " b.txt" , b " b on main\n " ) ;
6617
repo.commit ( " b on main" , & [ ] ) .unwrap ( ) ;
6618
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
6619
write ( root, " b.txt" , b " b edited\n " ) ;
6620
let err = repo.bookmark_switch_carry ( " main" ) .unwrap_err ( ) .to_string ( ) ;
6621
assert! ( err.contains ( " b.txt" ) & & err.contains ( " --discard" ) , " {err}" ) ;
6622
assert_eq! ( repo.config.bookmark, " feature" , " nothing happened" ) ;
6623
assert_eq! ( fs:: read( root.join ( " b.txt" ) ) .unwrap ( ) , b " b edited\n " ) ;
6624
}
1c2bd9ab Andy McCall 1 month ago ↶
6625
6626
6627
6628
6629
# [ test ]
6630
fn merges_move_clean_working_copies_and_checkouts_say_what_blocks_them ( ) {
6631
hermetic ( ) ;
6632
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6633
let root = dir.path ( ) ;
6634
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6635
write ( root, " a.txt" , b " a\n " ) ;
6636
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
6637
write ( root, " b.txt" , b " b\n " ) ;
6638
repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
6639
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
6640
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
6641
write ( root, " c.txt" , b " c\n " ) ;
6642
repo.commit ( " c on feature" , & [ ] ) .unwrap ( ) ;
6643
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
6644
6645
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
6646
assert! ( ! root.join ( " b.txt" ) .exists ( ) ) ;
6647
let m = repo.merge ( " feature" ) .unwrap ( ) ;
6648
assert! ( m.checked_out, " a clean working copy follows the merge" ) ;
6649
for f in [ " a.txt" , " b.txt" , " c.txt" ] {
6650
assert! ( root.join ( f) .exists ( ) , " {f}" ) ;
6651
}
6652
6653
6654
fs:: remove_file( root.join ( " b.txt" ) ) .unwrap ( ) ;
6655
write ( root, " a.txt" , b " a edited\n " ) ;
6656
let err = repo
6657
.checkout ( & [ h1] , false , " checkout" )
6658
.unwrap_err ( )
6659
.to_string ( ) ;
6660
assert! (
6661
err.contains ( " modified a.txt" ) & & err.contains ( " removed b.txt" ) & & err.contains ( " --carry" ) ,
6662
" {err}"
6663
) ;
fe9586d9 Andy McCall 26 days ago ↶
6664
6665
6666
1c2bd9ab Andy McCall 1 month ago ↶
6667
let ( _ , carried) = repo
6668
.checkout_carry ( & [ h1] , " the first change" , " checkout" )
6669
.unwrap ( ) ;
6670
assert_eq! ( carried, vec! [ " a.txt" ] ) ;
6671
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " a edited\n " ) ;
6672
assert! ( ! root.join ( " b.txt" ) .exists ( ) , " h1 has no b.txt" ) ;
6673
}
e7ac6c2e Andy McCall 1 month ago ↶
6674
6675
6676
6677
6678
# [ test ]
6679
fn a_new_repository_is_healthy_before_its_first_commit ( ) {
6680
hermetic ( ) ;
6681
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6682
let root = dir.path ( ) ;
6683
let home = root.join ( " home" ) ;
6684
let mut first = Repo:: init( & root.join ( " first" ) )
6685
.unwrap ( )
6686
.with_home ( home.clone ( ) ) ;
6687
let ( key, _ ) = first
6688
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
6689
.unwrap ( ) ;
6690
let repo = Repo:: init( & root.join ( " second" ) )
6691
.unwrap ( )
6692
.with_home ( home.clone ( ) ) ;
6693
6694
let keys = repo.keys_dir.clone ( ) ;
6695
let _ = fs:: remove_file( keys.join ( format! ( " {} .identity" , key.to_hex ( ) ) ) ) ;
6696
assert! ( keys.join ( format! ( " {} .secret" , key.to_hex ( ) ) ) .is_file ( ) ) ;
6697
let r = repo.doctor ( true ) ;
6698
let id = r.checks.iter ( ) .find ( | c | c.area = = " identity" ) .unwrap ( ) ;
6699
assert_eq! ( id.health, Health:: Ok, " {}" , id.message) ;
6700
assert! ( id.message.contains ( " first signed change" ) , " {}" , id.message) ;
6701
6702
6703
let lib = root.join ( " lib" ) ;
6704
let l = Repo:: init( & lib) .unwrap ( ) .with_home ( home.clone ( ) ) ;
6705
write ( & lib, " x.txt" , b " x\n " ) ;
6706
l.commit ( " x" , & [ ] ) .unwrap ( ) ;
6707
let second = root.join ( " second" ) ;
6708
write ( & second, " a.txt" , b " a\n " ) ;
6709
repo.submodule_add ( & lib.to_string_lossy ( ) , " deps/lib" , None )
6710
.unwrap ( ) ;
6711
let subs = repo.subrepos ( ) .unwrap ( ) ;
6712
assert_eq! ( subs.len ( ) , 1 ) ;
6713
assert_eq! (
6714
( subs[ 0 ] .path.as_str ( ) , subs[ 0 ] .committed) ,
6715
( " deps/lib" , false )
6716
) ;
6717
assert_eq! ( subs[ 0 ] .actual.as_ref ( ) , Some ( & subs[ 0 ] .pin) ) ;
6718
repo.commit ( " add lib" , & [ ] ) .unwrap ( ) ;
6719
let subs = repo.subrepos ( ) .unwrap ( ) ;
6720
assert! ( subs[ 0 ] .committed, " committed now" ) ;
6721
}
8ab265dc Andy McCall 1 month ago ↶
6722
6723
6724
6725
6726
# [ cfg ( unix ) ]
6727
# [ test ]
6728
fn commits_and_diffs_leave_unchanged_files_alone ( ) {
6729
use std:: os:: unix:: fs:: PermissionsExt;
6730
hermetic ( ) ;
6731
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6732
let root = dir.path ( ) ;
6733
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6734
write ( root, " big.txt" , b " lots\n of\n lines\n " ) ;
6735
write ( root, " a.txt" , b " one\n two\n " ) ;
6736
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6737
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6738
for f in [ " big.txt" , " a.txt" ] {
6739
fs:: File:: options( )
6740
.write ( true )
6741
.open ( root.join ( f) )
6742
.unwrap ( )
6743
.set_modified ( old)
6744
.unwrap ( ) ;
6745
}
6746
assert! ( repo.snapshot_status ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6747
6748
fs:: set_permissions( root.join ( " big.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6749
6750
write ( root, " a.txt" , b " one\n TWO\n three\n " ) ;
6751
let diff = repo.diff_files ( None ) .unwrap ( ) ;
6752
assert_eq! (
6753
diff.iter ( ) .map ( | f | f.path.as_str ( ) ) .collect:: < Vec< _ > > ( ) ,
6754
[ " a.txt" ]
6755
) ;
6756
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
6757
assert! ( ! hunks.is_empty ( ) & & hunks.iter ( ) .all ( | h | h.path = = " a.txt" ) ) ;
6758
let h = repo.commit ( " edit a" , & [ ] ) .unwrap ( ) ;
6759
6760
6761
let c = repo.store ( ) .get_change ( & h) .unwrap ( ) ;
6762
let full = repo.load_state ( & c.deps) .unwrap ( ) ;
6763
let want = render_change ( & c, & full, & full.materialize ( ) ) ;
6764
assert_eq! ( repo.describe_change ( & h) .unwrap ( ) , want) ;
6765
let ( base, snap) = repo.change_base ( & c) .unwrap ( ) ;
6766
assert! ( ! base.is_full ( ) & & snap.unrendered.contains ( " big.txt" ) ) ;
6767
6768
6769
write ( root, " a.txt" , b " scratch\n " ) ;
6770
assert_eq! ( repo.discard ( & [ " a.txt" .into ( ) ] ) .unwrap ( ) , 1 ) ;
6771
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n TWO\n three\n " ) ;
6772
fs:: set_permissions( root.join ( " big.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6773
}
f7510b63 Andy McCall 1 month ago ↶
6774
6775
6776
6777
6778
# [ test ]
6779
fn replace_conflicts_are_settled_when_asked ( ) {
6780
hermetic ( ) ;
6781
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6782
let root = dir.path ( ) ;
6783
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6784
let blob = | n : u8 | {
6785
let mut v = vec! [ 0 u8 ; 64 ] ;
6786
v[ 1 ] = n;
6787
v
6788
} ;
6789
write ( root, " img.bin" , & blob ( 1 ) ) ;
6790
repo.commit ( " add img" , & [ ] ) .unwrap ( ) ;
6791
repo.bookmark_create ( " other" , None ) .unwrap ( ) ;
6792
write ( root, " img.bin" , & blob ( 2 ) ) ;
6793
repo.commit ( " img two" , & [ ] ) .unwrap ( ) ;
6794
repo.bookmark_switch ( " other" , false ) .unwrap ( ) ;
6795
write ( root, " img.bin" , & blob ( 3 ) ) ;
6796
repo.commit ( " img three" , & [ ] ) .unwrap ( ) ;
6797
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
6798
repo.merge ( " other" ) .unwrap ( ) ;
6799
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6800
let merged = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
6801
assert! (
6802
matches! ( & merged.files[ " img.bin" ] .content, FileContent:: Opaque { blobs } if blobs.len ( ) = = 2 ) ,
6803
" a replace conflict"
6804
) ;
6805
6806
assert! ( repo
6807
.snapshot ( " status" )
6808
.unwrap ( )
6809
.statuses
6810
.iter ( )
6811
.all ( | s | s.path ! = " img.bin" ) ) ;
6812
6813
6814
let author = repo.author ( ) ;
6815
let edits = [ FileEdit {
6816
path: " img.bin" .into ( ) ,
6817
content: Some ( blob ( 3 ) ) ,
6818
} ] ;
6819
let c = repo
6820
.build_change ( & heads, & edits, " keep three" , author)
6821
.unwrap ( ) ;
6822
assert_eq! ( c.kind, ChangeKind:: Resolution) ;
6823
let mut st = repo.load_state ( & heads) .unwrap ( ) ;
6824
for d in & c.deps {
6825
assert! ( st.contains ( d) ) ;
6826
}
6827
assert! (
6828
heads.iter ( ) .all ( | h | c.deps.contains ( h) ) ,
6829
" depends on both contributors"
6830
) ;
6831
st.add ( c) .unwrap ( ) ;
6832
let after = st.materialize ( ) ;
6833
assert! ( ! after.is_conflicted ( ) ) ;
6834
assert_eq! (
6835
after.files[ " img.bin" ] .content,
6836
FileContent:: Opaque {
6837
blobs: vec! [ Repo:: blob_ref( & blob ( 3 ) ) ]
6838
}
6839
) ;
6840
6841
6842
let edits = [ FileEdit {
6843
path: " img.bin" .into ( ) ,
6844
content: Some ( blob ( 9 ) ) ,
6845
} ] ;
6846
let c = repo.build_change ( & heads, & edits, " new" , author) .unwrap ( ) ;
6847
let mut st = repo.load_state ( & heads) .unwrap ( ) ;
6848
st.add ( c) .unwrap ( ) ;
6849
assert_eq! (
6850
st.materialize ( ) .files[ " img.bin" ] .content,
6851
FileContent:: Opaque {
6852
blobs: vec! [ Repo:: blob_ref( & blob ( 9 ) ) ]
6853
}
6854
) ;
6855
}
83d61e80 Andy McCall 1 month ago ↶
6856
6857
6858
6859
6860
# [ test ]
6861
fn line_ranges_have_exact_histories ( ) {
6862
hermetic ( ) ;
6863
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6864
let root = dir.path ( ) ;
6865
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6866
write ( root, " f.rs" , b " use x;\n fn a() {\n one();\n two();\n }\n " ) ;
6867
let base = repo.commit ( " add f" , & [ ] ) .unwrap ( ) ;
6868
write ( root, " f.rs" , b " use y;\n fn a() {\n one();\n two();\n }\n " ) ;
6869
let outside = repo.commit ( " change the import" , & [ ] ) .unwrap ( ) ;
6870
write ( root, " f.rs" , b " use y;\n fn a() {\n ONE();\n two();\n }\n " ) ;
6871
let inside = repo.commit ( " edit a line of a" , & [ ] ) .unwrap ( ) ;
6872
write ( root, " f.rs" , b " use y;\n fn a() {\n ONE();\n }\n " ) ;
6873
let deleted = repo.commit ( " delete a line of a" , & [ ] ) .unwrap ( ) ;
6874
write ( root, " f.rs" , b " use y;\n fn a() {\n ONE();\n }\n fn b() {}\n " ) ;
6875
let after = repo.commit ( " add b after a" , & [ ] ) .unwrap ( ) ;
6876
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6877
6878
6879
let hist: Vec< ChangeHash> = repo
6880
.line_history ( & heads, " f.rs" , 2 , 4 )
6881
.unwrap ( )
6882
.iter ( )
6883
.map ( | e | e.hash )
6884
.collect ( ) ;
6885
for ( h, what) in [
6886
( base, " wrote it" ) ,
6887
( inside, " edited a line" ) ,
6888
( deleted, " deleted a line from inside" ) ,
6889
] {
6890
assert! ( hist.contains ( & h) , " {what}" ) ;
6891
}
6892
for ( h, what) in [ ( outside, " an edit above" ) , ( after, " an addition below" ) ] {
6893
assert! ( ! hist.contains ( & h) , " {what}" ) ;
6894
}
6895
assert_eq! ( hist[ 0 ] , deleted, " newest first" ) ;
6896
6897
let one: Vec< ChangeHash> = repo
6898
.line_history ( & heads, " f.rs" , 1 , 1 )
6899
.unwrap ( )
6900
.iter ( )
6901
.map ( | e | e.hash )
6902
.collect ( ) ;
6903
assert! ( one.contains ( & outside) & & ! one.contains ( & inside) ) ;
6904
assert! ( repo.line_history ( & heads, " f.rs" , 3 , 9 ) .is_err ( ) ) ;
6905
assert! ( repo.line_history ( & heads, " f.rs" , 0 , 1 ) .is_err ( ) ) ;
6906
assert! ( repo.line_history ( & heads, " nope.rs" , 1 , 1 ) .is_err ( ) ) ;
6907
}
fc1a466a Andy McCall 1 month ago ↶
6908
6909
# [ test ]
6910
fn receipts_are_kept_with_their_state_and_verified ( ) {
6911
use crate :: receipt:: { Receipt, SignedReceipt} ;
6912
hermetic ( ) ;
6913
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6914
let root = dir.path ( ) ;
6915
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6916
write ( root, " a.txt" , b " 1\n " ) ;
6917
repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
6918
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6919
let state = repo
6920
.store ( )
6921
.put_state ( & StateObject:: new( heads.clone ( ) ) )
6922
.unwrap ( ) ;
6923
let out = repo.store_blob ( b " built\n " ) .unwrap ( ) ;
6924
let receipt = Receipt {
6925
job_name: " build" .into ( ) ,
6926
pipeline_hash: ObjectRef( [ 1 ; 32 ] ) ,
6927
scope_tree_hash: ObjectRef( [ 2 ; 32 ] ) ,
6928
inputs: vec! [ ] ,
6929
environment: ObjectRef( [ 3 ; 32 ] ) ,
6930
outputs: vec! [ out] ,
6931
exit_code: 0 ,
6932
duration_ms: 42 ,
6933
} ;
6934
let sk = SecretKey:: generate( ) ;
6935
let runner = sk.identity ( " earth-1" , " runner@earth" , 1 ) ;
6936
let sr = SignedReceipt:: sign(
6937
receipt.clone ( ) ,
6938
repo.id ( ) .unwrap ( ) ,
6939
state,
6940
" demo#1" ,
6941
& runner,
6942
& sk,
6943
) ;
6944
6945
let r = repo.add_receipt ( & sr) .unwrap ( ) ;
6946
let found = repo.receipts ( & heads) .unwrap ( ) ;
6947
assert_eq! ( found.len ( ) , 1 ) ;
6948
assert_eq! ( found[ 0 ] .attest, r) ;
6949
assert_eq! ( found[ 0 ] .signed, sr) ;
6950
assert! ( found[ 0 ] .problem.is_none ( ) & & ! found[ 0 ] .revoked) ;
6951
assert! ( repo.receipts ( & [ ObjectRef( [ 5 ; 32 ] ) ] ) .unwrap ( ) .is_empty ( ) ) ;
6952
6953
6954
let elsewhere = SignedReceipt:: sign( receipt.clone ( ) , [ 0 ; 16 ] , state, " x" , & runner, & sk) ;
6955
assert! ( repo
6956
.add_receipt ( & elsewhere)
6957
.unwrap_err ( )
6958
.to_string ( )
6959
.contains ( " another repository" ) ) ;
6960
let mut forged = sr.clone ( ) ;
6961
forged.receipt.exit_code = 1 ;
6962
assert! ( repo
6963
.add_receipt ( & forged)
6964
.unwrap_err ( )
6965
.to_string ( )
6966
.contains ( " does not verify" ) ) ;
6967
c63fed2e Andy McCall 20 days ago ↶
6968
6969
6970
let hub_sk = SecretKey:: generate( ) ;
6971
let hub = hub_sk.identity ( " oxygenhub.io" , " [email protected] " , 1 ) ;
6972
let job_sk = SecretKey:: generate( ) ;
6973
let job = job_sk.identity ( " job 2 test" , " [email protected] " , 1 ) ;
6974
let mut test_receipt = receipt.clone ( ) ;
6975
test_receipt.job_name = " test" .into ( ) ;
6976
let plain = SignedReceipt:: sign(
6977
test_receipt,
6978
repo.id ( ) .unwrap ( ) ,
6979
state,
6980
" demo#2" ,
6981
& job,
6982
& job_sk,
6983
) ;
6984
let now = plain.attest.hlc.wall_ms;
6985
let cert = crate :: cert:: SignedCertificate:: sign(
6986
crate :: cert:: Certificate {
6987
key: job.clone ( ) ,
6988
issuer: hub.hash ( ) ,
6989
subject: " repo:andy/demo:bookmark:main" .into ( ) ,
6990
claims: vec! [ ] ,
6991
scope: crate :: identity:: Scope:: SEAL ,
6992
not_before: now - 1_000 ,
6993
expires: now + 600_000 ,
a2387d54 Andy McCall 20 days ago ↶
6994
grant: None ,
c63fed2e Andy McCall 20 days ago ↶
6995
} ,
6996
& hub,
6997
& hub_sk,
6998
) ;
6999
let certified = plain.with_certificate ( cert, hub.clone ( ) ) ;
7000
repo.add_receipt ( & certified) .unwrap ( ) ;
7001
let found = repo.receipts ( & heads) .unwrap ( ) ;
7002
let got = found
7003
.iter ( )
7004
.find ( | i | i.signed.receipt.job_name = = " test" )
7005
.unwrap ( ) ;
7006
assert_eq! ( got.signed, certified) ;
7007
assert! ( got.problem.is_none ( ) , " {:?}" , got.problem) ;
7008
7009
7010
fc1a466a Andy McCall 1 month ago ↶
7011
repo.gc ( true ) .unwrap ( ) ;
c63fed2e Andy McCall 20 days ago ↶
7012
let found = repo.receipts ( & heads) .unwrap ( ) ;
7013
assert_eq! ( found.len ( ) , 2 ) ;
7014
let got = found
7015
.iter ( )
7016
.find ( | i | i.signed.receipt.job_name = = " test" )
7017
.unwrap ( ) ;
7018
assert_eq! ( got.signed, certified, " the certificate survives gc" ) ;
7019
assert! ( got.problem.is_none ( ) ) ;
fc1a466a Andy McCall 1 month ago ↶
7020
assert_eq! ( repo.read_blob ( & out) .unwrap ( ) , b " built\n " ) ;
7021
assert! ( repo.fsck ( ) .unwrap ( ) .problems.is_empty ( ) ) ;
dfb23a3a Andy McCall 1 month ago ↶
7022
}
7023
7024
# [ test ]
3a4b3e01 Andy McCall 21 days ago ↶
7025
fn approvals_are_kept_with_their_state_and_survive_gc ( ) {
7026
hermetic ( ) ;
7027
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7028
let root = dir.path ( ) ;
7029
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
7030
write ( root, " a.txt" , b " 1\n " ) ;
7031
repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
7032
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
7033
let state = repo.store ( ) .put_state ( & StateObject:: new( heads) ) .unwrap ( ) ;
7034
let plan = repo.store_blob ( b " the reviewed plan\n " ) .unwrap ( ) ;
7035
let sk = SecretKey:: generate( ) ;
7036
let andy = sk.identity ( " Andy" , " [email protected] " , 1 ) ;
7037
let statement = crate :: approval:: Statement {
7038
repo: repo.id ( ) .unwrap ( ) ,
7039
run: " https://hub.example/andy/infra/flow/8" .into ( ) ,
7040
job: " apply" .into ( ) ,
7041
job_hash: ObjectRef( [ 1 ; 32 ] ) ,
7042
state,
7043
inputs: vec! [ plan] ,
7044
environment: " prod" .into ( ) ,
7045
approve: true ,
7046
} ;
7047
let sa = crate :: approval:: SignedApproval:: sign( & statement, & andy, & sk) ;
7048
let r = repo.add_approval ( & sa) .unwrap ( ) ;
7049
assert_eq! ( repo.store ( ) .approvals_for ( & state) .unwrap ( ) , vec! [ r] ) ;
7050
7051
let elsewhere = crate :: approval:: SignedApproval:: sign(
7052
& crate :: approval:: Statement {
7053
repo: [ 0 ; 16 ] ,
7054
.. statement.clone ( )
7055
} ,
7056
& andy,
7057
& sk,
7058
) ;
7059
assert! ( repo
7060
.add_approval ( & elsewhere)
7061
.unwrap_err ( )
7062
.to_string ( )
7063
.contains ( " another repository" ) ) ;
7064
let mut forged = sa.clone ( ) ;
7065
if let AttestBody:: Approval { environment, .. } = & mut forged.attest.body {
7066
* environment = " dev" .into ( ) ;
7067
}
7068
assert! ( repo
7069
.add_approval ( & forged)
7070
.unwrap_err ( )
7071
.to_string ( )
7072
.contains ( " does not verify" ) ) ;
7073
7074
repo.gc ( true ) .unwrap ( ) ;
7075
assert_eq! ( repo.store ( ) .approvals_for ( & state) .unwrap ( ) , vec! [ r] ) ;
7076
assert_eq! ( repo.store ( ) .get_attest ( & r) .unwrap ( ) , sa.attest) ;
7077
assert_eq! ( repo.store ( ) .signatures ( & r) .unwrap ( ) , sa.sigs) ;
7078
assert! ( repo.fsck ( ) .unwrap ( ) .problems.is_empty ( ) ) ;
7079
}
7080
7081
# [ test ]
dfb23a3a Andy McCall 1 month ago ↶
7082
fn status_leaves_an_unchanged_manifest_alone ( ) {
7083
hermetic ( ) ;
7084
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7085
let root = dir.path ( ) ;
7086
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
7087
write ( root, " a.txt" , b " 1\n " ) ;
7088
write ( root, " b.txt" , b " 2\n " ) ;
7089
repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
7090
7091
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 60 ) ;
7092
for f in [ " a.txt" , " b.txt" ] {
7093
fs:: File:: options( )
7094
.write ( true )
7095
.open ( root.join ( f) )
7096
.unwrap ( )
7097
.set_modified ( old)
7098
.unwrap ( ) ;
7099
}
7100
repo.snapshot_status ( " status" ) .unwrap ( ) ;
7101
let path = repo.manifest_path ( ) ;
7102
let first = fs:: read( & path) .unwrap ( ) ;
7103
repo.snapshot_status ( " status" ) .unwrap ( ) ;
7104
assert_eq! (
7105
fs:: read( & path) .unwrap ( ) ,
7106
first,
7107
" an unchanged tree does not rewrite the manifest"
7108
) ;
7109
7110
write ( root, " a.txt" , b " 1\n 2\n " ) ;
7111
repo.snapshot_status ( " status" ) .unwrap ( ) ;
7112
assert_ne! ( fs:: read( & path) .unwrap ( ) , first) ;
fc1a466a Andy McCall 1 month ago ↶
7113
}
6f8aeb99 Andy McCall 1 month ago ↶
7114
7115
# [ test ]
7116
fn large_text_files_travel_and_survive_gc_as_text_blobs ( ) {
7117
hermetic ( ) ;
7118
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7119
let root = dir.path ( ) ;
7120
let origin_path = root.join ( " origin" ) ;
7121
Repo:: init( & origin_path) .unwrap ( ) ;
7122
let origin_url = origin_path.to_string_lossy ( ) .to_string ( ) ;
7123
let a = Repo:: clone( & origin_url, & root.join ( " a" ) ) .unwrap ( ) ;
7124
let big: String = ( 0 .. 5000 ) .map ( | i | format! ( " line {i} \n " ) ) .collect ( ) ;
7125
write ( & root.join ( " a" ) , " big.txt" , big.as_bytes ( ) ) ;
7126
write ( & root.join ( " a" ) , " small.txt" , b " small\n " ) ;
7127
let h1 = a.commit ( " large" , & [ ] ) .unwrap ( ) ;
7128
let ( _ , body) = a.store ( ) .get ( & h1) .unwrap ( ) ;
7129
assert! ( body.len ( ) < 2048 , " the change is {} bytes" , body.len ( ) ) ;
7130
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7131
7132
7133
let b = Repo:: clone( & origin_url, & root.join ( " b" ) ) .unwrap ( ) ;
7134
assert_eq! ( fs:: read( root.join ( " b/big.txt" ) ) .unwrap ( ) , big.as_bytes ( ) ) ;
7135
7136
let edited = big.replace ( " line 2500\n " , " line 2500, edited\n " ) ;
7137
write ( & root.join ( " b" ) , " big.txt" , edited.as_bytes ( ) ) ;
7138
let h2 = b.commit ( " edit" , & [ ] ) .unwrap ( ) ;
7139
assert! ( b.store ( ) .get_change ( & h2) .unwrap ( ) .edits.len ( ) < = 2 ) ;
7140
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7141
assert! ( a.pull ( " origin" ) .unwrap ( ) .checked_out) ;
7142
assert_eq! ( fs:: read( root.join ( " a/big.txt" ) ) .unwrap ( ) , edited.as_bytes ( ) ) ;
7143
assert_eq! (
7144
a.fsck ( ) .unwrap ( ) .errors ( ) ,
7145
0 ,
7146
" {:?}" ,
7147
a.fsck ( ) .unwrap ( ) .problems
7148
) ;
7149
7150
7151
let mut a = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) ;
7152
a.config.set ( " core.op-retention" , " 0" ) .unwrap ( ) ;
7153
a.save_config ( ) .unwrap ( ) ;
7154
let a = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) ;
7155
a.gc ( true ) .unwrap ( ) ;
7156
let a = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) ;
7157
assert_eq! ( a.store ( ) .get_change ( & h1) .unwrap ( ) .message, " large" ) ;
7158
assert_eq! (
7159
a.fsck ( ) .unwrap ( ) .errors ( ) ,
7160
0 ,
7161
" {:?}" ,
7162
a.fsck ( ) .unwrap ( ) .problems
7163
) ;
7164
7165
let c = Repo:: clone( & root.join ( " a" ) .to_string_lossy ( ) , & root.join ( " c" ) ) .unwrap ( ) ;
7166
assert_eq! ( fs:: read( root.join ( " c/big.txt" ) ) .unwrap ( ) , edited.as_bytes ( ) ) ;
7167
drop ( c) ;
6caacfff Andy McCall 1 month ago ↶
7168
}
7169
7170
# [ test ]
7171
fn operations_that_both_replace_a_working_copy_are_not_merged ( ) {
7172
hermetic ( ) ;
7173
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7174
let root = dir.path ( ) ;
7175
let repo = Repo:: init( root) .unwrap ( ) ;
7176
write ( root, " a.txt" , b " a\n " ) ;
7177
let a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
7178
write ( root, " b.txt" , b " b\n " ) ;
7179
repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
7180
let ( x, op) = repo.head_op ( ) .unwrap ( ) ;
7181
let ws = * op.view.workspaces.keys ( ) .next ( ) .unwrap ( ) ;
7182
let mut one = op.view.clone ( ) ;
7183
one.workspaces.insert ( ws, a) ;
7184
let mut two = op.view.clone ( ) ;
7185
two.workspaces.insert ( ws, ObjectRef( [ 7 ; 32 ] ) ) ;
7186
let first = repo.record_op ( & [ x] , " one" , one) .unwrap ( ) ;
7187
let e = repo.record_op ( & [ x] , " two" , two) .unwrap_err ( ) ;
7188
assert! (
7189
e.to_string ( )
7190
.contains ( " changed this working copy at the same time" ) ,
7191
" {e}"
7192
) ;
7193
assert_eq! (
7194
repo.head_op ( ) .unwrap ( ) .0 ,
7195
first,
7196
" the second is not recorded"
7197
) ;
7198
}
7199
7200
7201
7202
7203
# [ test ]
7204
fn a_snapshot_during_a_switch_never_records_the_switch_as_edits ( ) {
7205
hermetic ( ) ;
7206
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7207
let root = dir.path ( ) .to_path_buf ( ) ;
7208
let mut repo = Repo:: init( & root) .unwrap ( ) ;
7209
write ( & root, " keep.txt" , b " keep\n " ) ;
7210
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7211
repo.bookmark_create ( " small" , None ) .unwrap ( ) ;
7212
for i in 0 .. 300 {
7213
write (
7214
& root,
7215
& format! ( " many/f{i} .txt" ) ,
7216
format! ( " file {i} \n " ) .as_bytes ( ) ,
7217
) ;
7218
}
7219
repo.commit ( " many" , & [ ] ) .unwrap ( ) ;
7220
let stop = std:: sync:: Arc:: new( std:: sync:: atomic:: AtomicBool:: new( false ) ) ;
7221
let watcher = {
7222
let ( root, stop) = ( root.clone ( ) , stop.clone ( ) ) ;
7223
std:: thread:: spawn( move | | {
7224
let r = Repo:: open( & root) .unwrap ( ) ;
7225
let mut ran = 0 ;
7226
while ! stop.load ( std:: sync:: atomic:: Ordering:: Relaxed) {
7227
if r.snapshot_status ( " oxy status" ) .is_ok ( ) {
7228
ran + = 1 ;
7229
}
7230
}
7231
ran
7232
} )
7233
} ;
7234
for i in 0 .. 20 {
7235
let to = if i % 2 = = 0 { " small" } else { " main" } ;
7236
repo.bookmark_switch ( to, false ) .unwrap ( ) ;
7237
}
7238
stop.store ( true , std:: sync:: atomic:: Ordering:: Relaxed) ;
7239
assert! ( watcher.join ( ) .unwrap ( ) > 0 , " the watcher snapshotted" ) ;
7240
7241
let repo = Repo:: open( & root) .unwrap ( ) ;
7242
let s = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7243
assert! ( s.statuses.is_empty ( ) , " {:?}" , statuses ( & s) ) ;
7244
assert! ( s.wc.edits.is_empty ( ) & & s.wc.tree_ops.is_empty ( ) ) ;
7245
assert_eq! ( s.wc.deps, repo.bookmark_heads ( ) .unwrap ( ) ) ;
7246
assert! ( root.join ( " many/f299.txt" ) .exists ( ) ) ;
1a3b6203 Andy McCall 1 month ago ↶
7247
}
7248
7249
7250
7251
7252
# [ test ]
7253
fn a_parent_switch_with_carry_leaves_a_subrepo_ahead_of_its_pin_alone ( ) {
7254
hermetic ( ) ;
7255
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7256
let root = dir.path ( ) ;
7257
Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
7258
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
7259
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) ) .unwrap ( ) ;
7260
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
7261
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
7262
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7263
7264
let app_dir = root.join ( " app" ) ;
7265
let mut app = Repo:: init( & app_dir) .unwrap ( ) ;
7266
write ( & app_dir, " main.txt" , b " app\n " ) ;
7267
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
7268
app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
7269
app.bookmark_create ( " feature" , None ) .unwrap ( ) ;
7270
7271
7272
let nested_dir = app_dir.join ( " libs/lib" ) ;
7273
let nested = Repo:: open( & nested_dir) .unwrap ( ) ;
7274
write ( & nested_dir, " new.txt" , b " new\n " ) ;
7275
let l2 = nested.commit ( " lib: new file" , & [ ] ) .unwrap ( ) ;
7276
drop ( nested) ;
7277
7278
let ( _ , carried) = app.bookmark_switch_carry ( " feature" ) .unwrap ( ) ;
7279
assert! ( carried.contains ( & " libs/lib" .to_string ( ) ) , " {carried:?}" ) ;
7280
let nested = Repo:: open( & nested_dir) .unwrap ( ) ;
7281
assert! (
7282
nested.bookmark_heads ( ) .unwrap ( ) .contains ( & l2) ,
7283
" its bookmark kept its work"
7284
) ;
7285
assert! ( nested_dir.join ( " new.txt" ) .exists ( ) , " and its files" ) ;
7286
let s = app.snapshot ( " status" ) .unwrap ( ) ;
7287
assert_eq! (
7288
statuses ( & s) ,
7289
vec! [ ( " libs/lib" .to_string ( ) , ChangeStatus:: Modified) ]
7290
) ;
6f8aeb99 Andy McCall 1 month ago ↶
7291
}
7d9dab13 Andy McCall 1 month ago ↶
7292
7293
7294
7295
7296
# [ test ]
7297
fn pull_takes_a_remote_move_to_changes_already_here ( ) {
7298
hermetic ( ) ;
7299
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7300
let root = dir.path ( ) ;
7301
let origin_path = root.join ( " origin" ) ;
7302
let origin = Repo:: init( & origin_path) .unwrap ( ) ;
7303
let url = origin_path.to_string_lossy ( ) .to_string ( ) ;
7304
let mut b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) ;
7305
write ( & root.join ( " b" ) , " base.txt" , b " base\n " ) ;
7306
b.commit ( " base" , & [ ] ) .unwrap ( ) ;
7307
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
7308
origin.refresh ( ) .unwrap ( ) ;
7d9dab13 Andy McCall 1 month ago ↶
7309
7310
7311
b.bookmark_create ( " feat" , None ) .unwrap ( ) ;
7312
b.bookmark_switch ( " feat" , false ) .unwrap ( ) ;
7313
write ( & root.join ( " b" ) , " x.txt" , b " x\n " ) ;
7314
let x = b.commit ( " x" , & [ ] ) .unwrap ( ) ;
7315
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
7316
origin.refresh ( ) .unwrap ( ) ;
7d9dab13 Andy McCall 1 month ago ↶
7317
b.bookmark_switch ( " main" , false ) .unwrap ( ) ;
7318
7319
7320
let feat = origin
7321
.store ( )
7322
.get_state ( & origin.view ( ) .unwrap ( ) .bookmarks[ " feat" ] )
7323
.unwrap ( )
7324
.heads;
7325
origin.bookmark_set ( " main" , & feat) .unwrap ( ) ;
7326
7327
let r = b.pull ( " origin" ) .unwrap ( ) ;
7328
assert! ( r.changed, " {r:?}" ) ;
7329
assert! ( r.heads.contains ( & x) | | b.load_state ( & r.heads) .unwrap ( ) .contains ( & x) ) ;
7330
let main = b
7331
.bookmarks ( )
7332
.unwrap ( )
7333
.into_iter ( )
7334
.find ( | m | m.name = = " main" )
7335
.unwrap ( ) ;
7336
assert_eq! ( main.behind, Some ( 0 ) , " {main:?}" ) ;
7337
}
86e1824f Andy McCall 1 month ago ↶
7338
14edbaaf Andy McCall 1 month ago ↶
7339
7340
7341
7342
7343
# [ test ]
7344
fn remote_bookmarks_are_named_switched_to_pulled_and_cloned ( ) {
7345
hermetic ( ) ;
7346
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7347
let root = dir.path ( ) ;
7348
let origin_path = root.join ( " origin" ) ;
7349
let mut origin = Repo:: init( & origin_path) .unwrap ( ) ;
7350
let url = origin_path.to_string_lossy ( ) .to_string ( ) ;
7351
let mut a = Repo:: clone( & url, & root.join ( " a" ) ) .unwrap ( ) ;
7352
write ( & root.join ( " a" ) , " base.txt" , b " base\n " ) ;
7353
a.commit ( " base" , & [ ] ) .unwrap ( ) ;
7354
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7355
a.bookmark_create ( " develop" , None ) .unwrap ( ) ;
7356
a.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
7357
write ( & root.join ( " a" ) , " base.txt" , b " base\n d1\n " ) ;
7358
let d1 = a.commit ( " d1" , & [ ] ) .unwrap ( ) ;
7359
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7360
7361
7362
let c_root = root.join ( " c" ) ;
7363
let mut c = Repo:: clone( & url, & c_root ) .unwrap ( ) ;
7364
assert_eq! ( c.config.bookmark, " main" ) ;
7365
assert! ( ! c.view ( ) .unwrap ( ) .bookmarks.contains_key ( " develop" ) ) ;
7366
assert_eq! ( c.resolve_heads ( " origin/develop" ) .unwrap ( ) , vec! [ d1] ) ;
7367
assert_eq! ( c.resolve_change ( " origin/develop" ) .unwrap ( ) , d1) ;
7368
assert_eq! ( c.remote_only_bookmarks ( ) .unwrap ( ) .len ( ) , 1 ) ;
7369
7370
7371
c.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
7372
assert_eq! ( c.config.bookmark, " develop" ) ;
7373
assert_eq! ( c.bookmark_heads ( ) .unwrap ( ) , vec! [ d1] ) ;
7374
assert_eq! ( fs:: read( c_root .join ( " base.txt" ) ) .unwrap ( ) , b " base\n d1\n " ) ;
7375
assert! ( c.remote_only_bookmarks ( ) .unwrap ( ) .is_empty ( ) ) ;
7376
assert! ( c.bookmark_switch ( " nowhere" , false ) .is_err ( ) ) ;
7377
7378
7379
write ( & root.join ( " a" ) , " base.txt" , b " base\n d1\n d2\n " ) ;
7380
let d2 = a.commit ( " d2" , & [ ] ) .unwrap ( ) ;
7381
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7382
a.bookmark_switch ( " main" , false ) .unwrap ( ) ;
7383
write ( & root.join ( " a" ) , " m.txt" , b " m\n " ) ;
7384
let m = a.commit ( " m" , & [ ] ) .unwrap ( ) ;
7385
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7386
let r = c.pull ( " origin" ) .unwrap ( ) ;
7387
assert! ( r.changed & & r.checked_out, " {r:?}" ) ;
7388
assert_eq! ( r.heads, vec! [ d2] ) ;
7389
assert_eq! ( r.others, vec! [ " main" .to_string ( ) ] ) ;
7390
assert! ( c.resolve_heads ( " main" ) .unwrap ( ) .contains ( & m) ) ;
7391
assert_eq! (
7392
fs:: read( c_root .join ( " base.txt" ) ) .unwrap ( ) ,
7393
b " base\n d1\n d2\n "
7394
) ;
7395
let again = c.pull ( " origin" ) .unwrap ( ) ;
7396
assert! ( ! again.changed & & again.others.is_empty ( ) , " {again:?}" ) ;
7397
7398
7399
origin.config.set ( " workspace.bookmark" , " develop" ) .unwrap ( ) ;
7400
origin.save_config ( ) .unwrap ( ) ;
7401
let e = Repo:: clone( & url, & root.join ( " e" ) ) .unwrap ( ) ;
7402
assert_eq! ( e.config.bookmark, " develop" ) ;
7403
assert_eq! ( e.bookmark_heads ( ) .unwrap ( ) , vec! [ d2] ) ;
7404
let reopened = Repo:: open( & root.join ( " e" ) ) .unwrap ( ) ;
7405
assert_eq! ( reopened.config.bookmark, " develop" ) ;
7406
7407
let mut e = reopened;
7408
e.bookmark_switch ( " origin/main" , false ) .unwrap ( ) ;
7409
assert_eq! ( e.config.bookmark, " main" ) ;
7410
assert! ( e.bookmark_heads ( ) .unwrap ( ) .contains ( & m) ) ;
7411
}
7412
98a1620a Andy McCall 1 month ago ↶
7413
7414
7415
7416
7417
# [ test ]
7418
fn operations_can_be_read_restored_and_reverted ( ) {
7419
hermetic ( ) ;
7420
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7421
let root = dir.path ( ) ;
7422
let repo = Repo:: init( root) .unwrap ( ) ;
7423
write ( root, " a.txt" , b " a\n " ) ;
7424
let first = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
7425
write ( root, " a.txt" , b " a\n b\n " ) ;
7426
let second = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
7427
let at_second = repo.head_op ( ) .unwrap ( ) .0. to_hex ( ) ;
7428
repo.bookmark_create ( " keep" , None ) .unwrap ( ) ;
7429
let created = repo.head_op ( ) .unwrap ( ) .0. to_hex ( ) ;
7430
write ( root, " n.txt" , b " n\n " ) ;
7431
let third = repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
7432
7433
7434
let ops = repo.op_log ( ) .unwrap ( ) ;
7435
let changes = repo.view_changes ( & ops[ 1 ] .1. view, & ops[ 0 ] .1. view) .unwrap ( ) ;
7436
assert! (
7437
changes
7438
.iter ( )
7439
.any ( | c | matches! ( c, ViewChange:: BookmarkMoved { name, .. } if name = = " main" ) ) ,
7440
" {changes:?}"
7441
) ;
7442
assert! ( changes
7443
.iter ( )
7444
.any ( | c | matches! ( c, ViewChange:: WorkingCopy { .. } ) ) ) ;
7445
7446
7447
let old = Repo:: open( root) .unwrap ( ) .at_op ( & at_second[ .. 10 ] ) .unwrap ( ) ;
7448
assert_eq! ( old.bookmark_heads ( ) .unwrap ( ) , vec! [ second] ) ;
7449
assert! ( ! old.view ( ) .unwrap ( ) .bookmarks.contains_key ( " keep" ) ) ;
7450
write ( root, " z.txt" , b " z\n " ) ;
7451
assert! ( old.commit ( " refused" , & [ ] ) .is_err ( ) , " --at-op is read-only" ) ;
7452
fs:: remove_file( root.join ( " z.txt" ) ) .unwrap ( ) ;
7453
assert! ( repo.resolve_op ( " zz" ) .is_err ( ) ) ;
7454
assert_eq! ( repo.resolve_op ( " @" ) .unwrap ( ) .0 , repo.head_op ( ) .unwrap ( ) .0 ) ;
7455
7456
7457
repo.op_restore ( & at_second, false ) .unwrap ( ) ;
7458
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ second] ) ;
7459
assert! ( ! root.join ( " n.txt" ) .exists ( ) ) ;
7460
repo.undo ( ) .unwrap ( ) ;
7461
let mut heads = repo.bookmark_heads ( ) .unwrap ( ) ;
7462
heads.sort ( ) ;
7463
let mut want = vec! [ second, third] ;
7464
want.sort ( ) ;
7465
assert_eq! ( heads, want) ;
7466
assert! (
7467
root.join ( " n.txt" ) .exists ( ) ,
7468
" the files came back with the undo"
7469
) ;
7470
7471
7472
let r = repo.op_revert ( & created) .unwrap ( ) ;
7473
assert_eq! ( r.reverted, vec! [ " keep" .to_string ( ) ] ) ;
7474
assert! ( ! repo.view ( ) .unwrap ( ) .bookmarks.contains_key ( " keep" ) ) ;
7475
assert! ( repo
7476
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
7477
.unwrap ( )
7478
.contains ( & third) ) ;
7479
assert! ( repo.op_revert ( & created) .is_err ( ) , " nothing left to revert" ) ;
7480
let _ = first;
7481
}
7482
36041796 Andy McCall 1 month ago ↶
7483
7484
7485
7486
# [ test ]
7487
fn status_with_uncommitted_work_comes_from_the_cache_until_a_file_changes ( ) {
7488
hermetic ( ) ;
7489
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7490
let root = dir.path ( ) ;
7491
let repo = Repo:: init( root) .unwrap ( ) ;
7492
write ( root, " a.txt" , b " a\n " ) ;
7493
write ( root, " b.txt" , b " b\n " ) ;
7494
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7495
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , Some ( vec! [ ] ) , " clean" ) ;
7496
7497
write ( root, " a.txt" , b " a\n more\n " ) ;
7498
assert_eq! (
7499
repo.status_by_manifest ( ) .unwrap ( ) ,
7500
None ,
7501
" no snapshot of the edit yet"
7502
) ;
7503
let snap = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7504
assert_eq! ( snap.statuses.len ( ) , 1 ) ;
7505
let cached = repo
7506
.status_by_manifest ( )
7507
.unwrap ( )
7508
.expect ( " answered from the cache" ) ;
7509
assert_eq! ( cached.len ( ) , 1 ) ;
7510
assert_eq! (
7511
( cached[ 0 ] .path.as_str ( ) , cached[ 0 ] .status) ,
7512
( " a.txt" , ChangeStatus:: Modified)
7513
) ;
7514
7515
7516
write ( root, " a.txt" , b " a\n more\n and more\n " ) ;
7517
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None , " edited again" ) ;
7518
repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7519
assert! ( repo.status_by_manifest ( ) .unwrap ( ) .is_some ( ) ) ;
7520
write ( root, " new.txt" , b " n\n " ) ;
7521
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None , " a file added" ) ;
7522
fs:: remove_file( root.join ( " new.txt" ) ) .unwrap ( ) ;
7523
assert! (
7524
repo.status_by_manifest ( ) .unwrap ( ) .is_some ( ) ,
7525
" back as it was"
7526
) ;
7527
fs:: remove_file( root.join ( " b.txt" ) ) .unwrap ( ) ;
7528
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None , " a file removed" ) ;
7529
write ( root, " b.txt" , b " b\n " ) ;
7530
7531
7532
repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7533
repo.commit ( " more" , & [ ] ) .unwrap ( ) ;
7534
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , Some ( vec! [ ] ) ) ;
7535
}
7536
77445673 Andy McCall 1 month ago ↶
7537
7538
7539
7540
# [ test ]
7541
fn the_working_copy_diff_from_the_cache_matches_a_snapshot ( ) {
7542
hermetic ( ) ;
7543
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7544
let root = dir.path ( ) ;
7545
let repo = Repo:: init( root) .unwrap ( ) ;
7546
write ( root, " a.txt" , b " one\n two\n three\n " ) ;
7547
write ( root, " gone.txt" , b " bye\n " ) ;
7548
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7549
write ( root, " a.txt" , b " one\n 2\n three\n four\n " ) ;
7550
write ( root, " new.txt" , b " hello\n " ) ;
7551
fs:: remove_file( root.join ( " gone.txt" ) ) .unwrap ( ) ;
7552
let snap = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7553
let slow = render_change ( & snap.wc, & snap.base, & snap.base_snapshot) ;
7554
assert! (
7555
repo.status_by_manifest ( ) .unwrap ( ) .is_some ( ) ,
7556
" the cache holds"
7557
) ;
7558
let ( wc, base, snapshot) = repo.working_copy_with_base ( ) .unwrap ( ) ;
7559
assert_eq! ( render_change ( & wc, & base, & snapshot) , slow) ;
7560
assert_eq! ( repo.diff_files ( None ) .unwrap ( ) .len ( ) , 3 ) ;
7561
}
7562
1ec3f008 Andy McCall 1 month ago ↶
7563
7564
7565
7566
7567
# [ test ]
7568
fn the_manifest_check_skips_listing_while_no_directory_changed ( ) {
7569
hermetic ( ) ;
7570
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7571
let root = dir.path ( ) ;
7572
let repo = Repo:: init( root) .unwrap ( ) ;
7573
write ( root, " a.txt" , b " a\n " ) ;
7574
write ( root, " sub/deep/b.txt" , b " b\n " ) ;
7575
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7576
7577
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 60 ) ;
7578
for p in [ " a.txt" , " sub/deep/b.txt" , " sub/deep" , " sub" , " " ] {
7579
fs:: File:: open( root.join ( p) )
7580
.unwrap ( )
7581
.set_modified ( old)
7582
.unwrap ( ) ;
7583
}
7584
repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7585
repo.write_manifest ( false , false ) .unwrap ( ) ;
7586
let m = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) .2 ;
7587
assert! ( m.dirs.iter ( ) .any ( | ( d , _) | d = = " sub/deep" ) , " {:?}" , m.dirs) ;
7588
assert_eq! (
7589
repo.disk_differs_by_dirs ( & m) ,
7590
Some ( false ) ,
7591
" no listing needed"
7592
) ;
7593
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , Some ( vec! [ ] ) ) ;
7594
7595
7596
write ( root, " sub/deep/b.txt" , b " c\n " ) ;
7597
assert_eq! ( repo.disk_differs_by_dirs ( & m) , Some ( true ) ) ;
7598
write ( root, " sub/deep/b.txt" , b " b\n " ) ;
7599
fs:: File:: open( root.join ( " sub/deep/b.txt" ) )
7600
.unwrap ( )
7601
.set_modified ( old)
7602
.unwrap ( ) ;
7603
assert_eq! ( repo.disk_differs_by_dirs ( & m) , Some ( false ) ) ;
7604
7605
7606
write ( root, " sub/deep/new.txt" , b " n\n " ) ;
7607
assert_eq! ( repo.disk_differs_by_dirs ( & m) , None ) ;
7608
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None ) ;
7609
fs:: remove_file( root.join ( " sub/deep/new.txt" ) ) .unwrap ( ) ;
7610
7611
fs:: remove_file( root.join ( " a.txt" ) ) .unwrap ( ) ;
7612
assert_eq! ( repo.disk_differs_by_dirs ( & m) , None ) ;
7613
}
7614
e226a6cc Andy McCall 1 month ago ↶
7615
7616
7617
7618
# [ test ]
7619
fn a_commit_after_a_status_uses_the_cached_snapshot ( ) {
7620
hermetic ( ) ;
7621
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7622
let root = dir.path ( ) ;
7623
let repo = Repo:: init( root) .unwrap ( ) ;
7624
write ( root, " a.txt" , b " a\n b\n " ) ;
7625
write ( root, " .oxyattributes" , b " *.bin binary\n " ) ;
7626
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7627
write ( root, " a.txt" , b " a\n B\n " ) ;
7628
write ( root, " new.txt" , b " new\n " ) ;
7629
let snap = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7630
let cached = repo
7631
.snapshot_from_cache ( )
7632
.unwrap ( )
7633
.expect ( " the cache holds" ) ;
7634
assert_eq! ( cached.wc_hash, snap.wc_hash) ;
7635
assert_eq! ( cached.statuses, snap.statuses) ;
7636
assert! (
7637
! cached.attributes.is_empty ( ) ,
7638
" the .oxyattributes file was read"
7639
) ;
7640
let h = repo.commit ( " edit" , & [ ] ) .unwrap ( ) ;
7641
let c = repo.store ( ) .get_change ( & h) .unwrap ( ) ;
7642
assert_eq! (
7643
c.tree_ops.len ( ) + c.edits.len ( ) ,
7644
snap.wc.tree_ops.len ( ) + snap.wc.edits.len ( )
7645
) ;
7646
assert_eq! (
7647
repo.status_by_manifest ( ) .unwrap ( ) ,
7648
Some ( vec! [ ] ) ,
7649
" clean after the commit"
7650
) ;
7651
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " a\n B\n " ) ;
7652
}
7653
0389c3e8 Andy McCall 1 month ago ↶
7654
7655
7656
7657
7658
# [ test ]
7659
fn a_verified_clean_check_restamps_the_manifest_safely ( ) {
7660
hermetic ( ) ;
7661
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7662
let root = dir.path ( ) ;
7663
let repo = Repo:: init( root) .unwrap ( ) ;
7664
write ( root, " a.txt" , b " one\n " ) ;
7665
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
7666
let before = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) .2. written_ns;
7667
assert! ( repo.clean_by_manifest ( ) .unwrap ( ) ) ;
7668
let after = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) .2. written_ns;
7669
assert! ( after > before, " restamped" ) ;
7670
write ( root, " a.txt" , b " two\n " ) ;
7671
assert! (
7672
! repo.clean_by_manifest ( ) .unwrap ( ) ,
7673
" a same-size rewrite is still seen"
7674
) ;
7675
}
7676
86e1824f Andy McCall 1 month ago ↶
7677
7678
7679
7680
# [ test ]
7681
fn history_only_states_read_text_only_for_the_graphs_they_build ( ) {
7682
hermetic ( ) ;
7683
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7684
let root = dir.path ( ) ;
7685
let repo = Repo:: init( root) .unwrap ( ) ;
7686
let big: String = ( 0 .. 3000 ) .map ( | i | format! ( " big line {i} \n " ) ) .collect ( ) ;
7687
let other: String = ( 0 .. 3000 ) .map ( | i | format! ( " other line {i} \n " ) ) .collect ( ) ;
7688
write ( root, " big.txt" , big.as_bytes ( ) ) ;
7689
write ( root, " other.txt" , other.as_bytes ( ) ) ;
7690
let h = repo.commit ( " two large files" , & [ ] ) .unwrap ( ) ;
7691
write ( root, " big.txt" , format! ( " {big} one more\n " ) .as_bytes ( ) ) ;
7692
let h2 = repo.commit ( " append" , & [ ] ) .unwrap ( ) ;
7693
7694
let full = repo.load_state_full ( & [ h2] ) .unwrap ( ) .materialize ( ) ;
7695
let text = | snap : & crate:: state:: Snapshot, p : & str | match & snap.files[ p] .content {
7696
FileContent:: Text { bytes, .. } => bytes.clone ( ) ,
7697
other => panic! ( " {other:?}" ) ,
7698
} ;
7699
7700
let mut hist = repo.load_state_history ( & [ h2] ) .unwrap ( ) ;
7701
let big_node = full.files[ " big.txt" ] .node;
7702
assert! ( hist.graph_key ( & big_node) .is_some ( ) , " not built yet" ) ;
7703
repo.load_all_graphs ( & mut hist) .unwrap ( ) ;
7704
let rendered = hist.materialize ( ) ;
7705
for p in [ " big.txt" , " other.txt" ] {
7706
assert_eq! ( text ( & rendered, p) , text ( & full, p) , " {p}" ) ;
7707
}
7708
7709
7710
7711
let ( _ , texts) = repo.store ( ) .get_change_lazy ( & h) .unwrap ( ) ;
7712
assert_eq! ( texts.len ( ) , 2 ) ;
7713
let content = | r : & ObjectRef| -> Vec< u8 > {
7714
let blob = crate :: objects:: Blob:: decode( & repo.store ( ) .get ( r) .unwrap ( ) .1 ) .unwrap ( ) ;
7715
blob.chunks
7716
.iter ( )
7717
.flat_map ( | c | repo.store ( ) .get ( c) .unwrap ( ) .1 )
7718
.collect ( )
7719
} ;
7720
let other_text = texts
7721
.iter ( )
a4fad5b0 Andy McCall 1 month ago ↶
7722
.filter_map ( | t | match t {
0f93f49c Andy McCall 1 month ago ↶
7723
crate :: objects:: TextRef:: Blob( r, _ , _ ) => Some ( * r) ,
a4fad5b0 Andy McCall 1 month ago ↶
7724
_ => None ,
7725
} )
86e1824f Andy McCall 1 month ago ↶
7726
.find ( | r | content ( r) .windows ( 12 ) .any ( | w | w = = b " other line 1" ) )
7727
.unwrap ( ) ;
64818938 Andy McCall 1 month ago ↶
7728
repo.store ( ) .remove_loose_for_test ( & other_text) .unwrap ( ) ;
86e1824f Andy McCall 1 month ago ↶
7729
let mut hist = repo.load_state_history ( & [ h2] ) .unwrap ( ) ;
7730
repo.load_graphs_of ( & mut hist, & [ big_node] ) .unwrap ( ) ;
7731
assert! ( repo
7732
.load_graphs_of ( & mut hist, & [ full.files[ " other.txt" ] .node] )
7733
.is_err ( ) ) ;
7734
}
7735
7736
7737
7738
7739
# [ test ]
7740
fn a_commit_leaves_a_manifest_the_next_status_trusts ( ) {
7741
use std:: os:: unix:: fs:: PermissionsExt;
7742
hermetic ( ) ;
7743
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7744
let root = dir.path ( ) ;
7745
let repo = Repo:: init( root) .unwrap ( ) ;
7746
let files = [ " a.txt" , " b.txt" , " d/c.txt" ] ;
7747
for p in files {
7748
write ( root, p, format! ( " {p} \n " ) .as_bytes ( ) ) ;
7749
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
7750
fs:: File:: options( )
7751
.write ( true )
7752
.open ( root.join ( p) )
7753
.unwrap ( )
7754
.set_modified ( old)
7755
.unwrap ( ) ;
7756
}
7757
repo.commit ( " added" , & [ ] ) .unwrap ( ) ;
7758
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
7759
assert! (
7760
m.entries.values ( ) .all ( | e | e.graph.is_some ( ) ) ,
7761
" keys recorded at commit"
7762
) ;
7763
7764
for p in files {
7765
fs:: set_permissions( root.join ( p) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
7766
}
7767
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
7768
for p in files {
7769
fs:: set_permissions( root.join ( p) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
7770
}
7771
assert! ( ! snap.changed & & snap.statuses.is_empty ( ) ) ;
7772
assert_eq! ( snap.base_snapshot.unrendered.len ( ) , files.len ( ) ) ;
7773
}
c5ce0367 Andy McCall 1 month ago ↶
7774
7775
7776
7777
# [ test ]
7778
fn checkout_touches_only_what_differs_and_renders_in_batches ( ) {
7779
use std:: os:: unix:: fs:: PermissionsExt;
7780
hermetic ( ) ;
7781
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7782
let root = dir.path ( ) ;
7783
let origin = root.join ( " origin" ) ;
7784
let mut repo = Repo:: init( & origin) .unwrap ( ) ;
7785
7786
let n = 2100 ;
7787
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
7788
for i in 0 .. n {
7789
let p = format! ( " f/{i:04} .txt" ) ;
7790
write ( & origin, & p, format! ( " file {i} \n " ) .as_bytes ( ) ) ;
7791
fs:: File:: options( )
7792
.write ( true )
7793
.open ( origin.join ( & p) )
7794
.unwrap ( )
7795
.set_modified ( old)
7796
.unwrap ( ) ;
7797
}
7798
repo.commit ( " many" , & [ ] ) .unwrap ( ) ;
7799
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
7800
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
7801
write ( & origin, " f/0007.txt" , b " changed\n " ) ;
7802
repo.commit ( " one change" , & [ ] ) .unwrap ( ) ;
7803
7804
7805
for i in 0 .. n {
7806
if i ! = 7 {
7807
let p = origin.join ( format! ( " f/{i:04} .txt" ) ) ;
7808
fs:: set_permissions( & p, fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
7809
}
7810
}
7811
let r = repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
7812
for i in 0 .. n {
7813
let p = origin.join ( format! ( " f/{i:04} .txt" ) ) ;
7814
fs:: set_permissions( & p, fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
7815
}
7816
assert_eq! ( ( r.written, r.removed) , ( 1 , 0 ) , " {r:?}" ) ;
7817
assert_eq! ( fs:: read( origin.join ( " f/0007.txt" ) ) .unwrap ( ) , b " file 7\n " ) ;
7818
assert! ( repo.snapshot_status ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
7819
7820
7821
let c = root.join ( " clone" ) ;
7822
Repo:: clone( & origin.to_string_lossy ( ) , & c) .unwrap ( ) ;
7823
for i in [ 0 , 2047 , 2048 , n - 1 ] {
7824
assert_eq! (
7825
fs:: read( c.join ( format! ( " f/{i:04} .txt" ) ) ) .unwrap ( ) ,
7826
format! ( " file {i} \n " ) .as_bytes ( )
7827
) ;
7828
}
7829
let cloned = Repo:: open( & c) .unwrap ( ) ;
7830
assert! ( cloned
7831
.snapshot_status ( " status" )
7832
.unwrap ( )
7833
.statuses
7834
.is_empty ( ) ) ;
d3cd7ef9 Andy McCall 1 month ago ↶
7835
}
7836
7837
7838
7839
7840
# [ test ]
7841
fn a_racy_manifest_entry_is_checked_by_hash ( ) {
7842
hermetic ( ) ;
7843
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7844
let root = dir.path ( ) ;
7845
let repo = Repo:: init( root) .unwrap ( ) ;
7846
write ( root, " a.txt" , b " one\n " ) ;
7847
repo.commit ( " fresh" , & [ ] ) .unwrap ( ) ;
7848
assert! ( repo.clean_by_manifest ( ) .unwrap ( ) , " fresh but equal" ) ;
7849
let t = fs:: metadata( root.join ( " a.txt" ) )
7850
.unwrap ( )
7851
.modified ( )
7852
.unwrap ( ) ;
7853
write ( root, " a.txt" , b " two\n " ) ;
7854
fs:: File:: options( )
7855
.write ( true )
7856
.open ( root.join ( " a.txt" ) )
7857
.unwrap ( )
7858
.set_modified ( t)
7859
.unwrap ( ) ;
7860
assert! (
7861
! repo.clean_by_manifest ( ) .unwrap ( ) ,
7862
" same size and time, other bytes"
7863
) ;
7864
assert_eq! (
7865
statuses ( & repo.snapshot_status ( " status" ) .unwrap ( ) ) ,
7866
vec! [ ( " a.txt" .to_string ( ) , ChangeStatus:: Modified) ]
7867
) ;
c5ce0367 Andy McCall 1 month ago ↶
7868
}
2c75672f Andy McCall 1 month ago ↶
7869
7870
7871
7872
# [ test ]
7873
fn a_raw_bundle_without_a_changes_text_is_refused ( ) {
7874
hermetic ( ) ;
7875
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7876
let root = dir.path ( ) ;
7877
let a = Repo:: init( & root.join ( " a" ) ) .unwrap ( ) ;
7878
let big: String = ( 0 .. 3000 ) .map ( | i | format! ( " line {i} \n " ) ) .collect ( ) ;
7879
write ( & root.join ( " a" ) , " big.txt" , big.as_bytes ( ) ) ;
7880
let h = a.commit ( " big" , & [ ] ) .unwrap ( ) ;
7881
let bundle = a.bundle_raw_for ( & [ h] , None ) .unwrap ( ) ;
7882
assert_eq! ( bundle.raw_changes.len ( ) , 1 ) ;
7883
assert! ( bundle.changes.is_empty ( ) ) ;
7884
let b = Repo:: init( & root.join ( " b" ) ) .unwrap ( ) ;
7885
let mut without = bundle.clone ( ) ;
7886
without.objects.retain ( | ( ty , _) | * ty ! = ObjectType:: Blob) ;
7887
let e = b.import_bundle ( & without, false ) .unwrap_err ( ) ;
7888
assert! ( e.to_string ( ) .contains ( " came without its text blob" ) , " {e}" ) ;
7889
assert! ( ! b.store ( ) .has ( & h) ) ;
7890
assert_eq! ( b.import_bundle ( & bundle, false ) .unwrap ( ) , vec! [ h] ) ;
7891
assert_eq! ( b.store ( ) .get_change ( & h) .unwrap ( ) .message, " big" ) ;
7892
}
a87c7b9d Andy McCall 1 month ago ↶
7893
7894
7895
7896
7897
7898
# [ test ]
7899
fn git_import_brings_a_directory_back_rather_than_making_a_second ( ) {
7900
hermetic ( ) ;
7901
if std:: process:: Command:: new( " git" )
7902
.arg ( " --version" )
7903
.output ( )
7904
.is_err ( )
7905
{
7906
eprintln! ( " git not installed; skipping" ) ;
7907
return ;
7908
}
7909
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7910
let root = dir.path ( ) ;
7911
let g = root.join ( " src.git" ) ;
7912
fs:: create_dir_all( & g) .unwrap ( ) ;
7913
let git = | args : & [& str ]| -> String {
7914
let out = std:: process:: Command:: new( " git" )
7915
.arg ( " -C" )
7916
.arg ( & g)
7917
.args ( [
7918
" -c" ,
7919
" user.name=Grace" ,
7920
" -c" ,
7921
" [email protected] " ,
7922
" -c" ,
7923
" commit.gpgsign=false" ,
7924
] )
7925
.args ( args)
7926
.output ( )
7927
.unwrap ( ) ;
7928
assert! (
7929
out.status.success ( ) ,
7930
" git {args:?}: {}" ,
7931
String :: from_utf8_lossy( & out.stderr)
7932
) ;
7933
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
7934
} ;
7935
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
7936
write ( & g, " d/a.txt" , b " a\n " ) ;
7937
write ( & g, " c/x.txt" , b " x\n " ) ;
7938
git ( & [ " add" , " ." ] ) ;
7939
git ( & [ " commit" , " -qm" , " base" ] ) ;
7940
7941
git ( & [ " checkout" , " -qb" , " side" ] ) ;
7942
git ( & [ " mv" , " c/x.txt" , " d/x.txt" ] ) ;
7943
git ( & [ " commit" , " -qm" , " x into d" ] ) ;
7944
7945
git ( & [ " checkout" , " -q" , " main" ] ) ;
7946
git ( & [ " rm" , " -q" , " d/a.txt" ] ) ;
7947
git ( & [ " commit" , " -qm" , " d goes" ] ) ;
7948
write ( & g, " d/b.txt" , b " b\n " ) ;
7949
git ( & [ " add" , " ." ] ) ;
7950
git ( & [ " commit" , " -qm" , " d comes back" ] ) ;
7951
git ( & [ " mv" , " c/x.txt" , " d/x.txt" ] ) ;
7952
git ( & [ " commit" , " -qm" , " x into d again" ] ) ;
7953
git ( & [ " merge" , " -q" , " --no-edit" , " side" ] ) ;
7954
7955
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
7956
repo.import_git ( & g, " main" ) .unwrap ( ) ;
7957
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
7958
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
7959
let st = repo
7960
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
7961
.unwrap ( ) ;
7962
let view = st.tree ( ) .view ( ) ;
7963
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
7964
7965
7966
let first = repo
7967
.log ( )
7968
.unwrap ( )
7969
.into_iter ( )
7970
.find ( | e | e.change.message.starts_with ( " base" ) )
7971
.unwrap ( ) ;
7972
let base = repo
7973
.load_state_history ( & [ first.hash] )
7974
.unwrap ( )
7975
.tree ( )
7976
.view ( ) ;
7977
assert_eq! ( view.entries[ " d" ] .node, base.entries[ " d" ] .node) ;
7978
}
7979
7980
7981
7982
7983
# [ test ]
7984
fn git_import_merge_retires_a_placement_under_a_removed_directory ( ) {
7985
hermetic ( ) ;
7986
if std:: process:: Command:: new( " git" )
7987
.arg ( " --version" )
7988
.output ( )
7989
.is_err ( )
7990
{
7991
eprintln! ( " git not installed; skipping" ) ;
7992
return ;
7993
}
7994
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7995
let root = dir.path ( ) ;
7996
let g = root.join ( " src.git" ) ;
7997
fs:: create_dir_all( & g) .unwrap ( ) ;
7998
let run = | args : & [& str ]| {
7999
std:: process:: Command:: new( " git" )
8000
.arg ( " -C" )
8001
.arg ( & g)
8002
.args ( [
8003
" -c" ,
8004
" user.name=Grace" ,
8005
" -c" ,
8006
" [email protected] " ,
8007
" -c" ,
8008
" commit.gpgsign=false" ,
8009
] )
8010
.args ( args)
8011
.output ( )
8012
.unwrap ( )
8013
} ;
8014
let git = | args : & [& str ]| {
8015
let out = run ( args) ;
8016
assert! (
8017
out.status.success ( ) ,
8018
" git {args:?}: {}" ,
8019
String :: from_utf8_lossy( & out.stderr)
8020
) ;
8021
} ;
8022
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
8023
write ( & g, " c/x.txt" , b " x\n " ) ;
8024
write ( & g, " keep.txt" , b " k\n " ) ;
8025
git ( & [ " add" , " ." ] ) ;
8026
git ( & [ " commit" , " -qm" , " base" ] ) ;
8027
8028
8029
git ( & [ " checkout" , " -qb" , " side" ] ) ;
8030
git ( & [ " mv" , " c/x.txt" , " c/y.txt" ] ) ;
8031
git ( & [ " commit" , " -qm" , " rename within c" ] ) ;
8032
git ( & [ " checkout" , " -q" , " main" ] ) ;
8033
fs:: create_dir_all( g.join ( " n" ) ) .unwrap ( ) ;
8034
git ( & [ " mv" , " c/x.txt" , " n/x.txt" ] ) ;
8035
git ( & [ " commit" , " -qm" , " move out of c" ] ) ;
8036
8037
let _ = run ( & [ " merge" , " -q" , " side" ] ) ;
8038
let _ = run ( & [
8039
" rm" ,
8040
" -q" ,
8041
" --cached" ,
8042
" --ignore-unmatch" ,
8043
" c/y.txt" ,
8044
" c/x.txt" ,
8045
] ) ;
8046
let _ = std:: fs:: remove_dir_all( g.join ( " c" ) ) ;
8047
write ( & g, " n/x.txt" , b " x\n " ) ;
8048
git ( & [ " add" , " -A" ] ) ;
8049
git ( & [ " commit" , " -qm" , " merge: keep n/x.txt" ] ) ;
8050
8051
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
8052
repo.import_git ( & g, " main" ) .unwrap ( ) ;
8053
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
8054
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
8055
let st = repo
8056
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
8057
.unwrap ( ) ;
8058
let view = st.tree ( ) .view ( ) ;
8059
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
8060
assert! ( view.entries.contains_key ( " n/x.txt" ) ) ;
8061
}
8062
8063
8064
8065
8066
# [ test ]
8067
fn git_import_merge_leaves_no_orphan_behind ( ) {
8068
hermetic ( ) ;
8069
if std:: process:: Command:: new( " git" )
8070
.arg ( " --version" )
8071
.output ( )
8072
.is_err ( )
8073
{
8074
eprintln! ( " git not installed; skipping" ) ;
8075
return ;
8076
}
8077
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8078
let root = dir.path ( ) ;
8079
let g = root.join ( " src.git" ) ;
8080
fs:: create_dir_all( & g) .unwrap ( ) ;
8081
let git = | args : & [& str ]| {
8082
let out = std:: process:: Command:: new( " git" )
8083
.arg ( " -C" )
8084
.arg ( & g)
8085
.args ( [
8086
" -c" ,
8087
" user.name=Grace" ,
8088
" -c" ,
8089
" [email protected] " ,
8090
" -c" ,
8091
" commit.gpgsign=false" ,
8092
] )
8093
.args ( args)
8094
.output ( )
8095
.unwrap ( ) ;
8096
assert! (
8097
out.status.success ( ) ,
8098
" git {args:?}: {}" ,
8099
String :: from_utf8_lossy( & out.stderr)
8100
) ;
8101
} ;
8102
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
8103
write ( & g, " d/f.txt" , b " f\n " ) ;
8104
write ( & g, " keep.txt" , b " k\n " ) ;
8105
git ( & [ " add" , " ." ] ) ;
8106
git ( & [ " commit" , " -qm" , " base" ] ) ;
8107
git ( & [ " checkout" , " -qb" , " side" ] ) ;
8108
write ( & g, " d/new.txt" , b " new\n " ) ;
8109
git ( & [ " add" , " ." ] ) ;
8110
git ( & [ " commit" , " -qm" , " add into d" ] ) ;
8111
git ( & [ " checkout" , " -q" , " main" ] ) ;
8112
git ( & [ " rm" , " -q" , " d/f.txt" ] ) ;
8113
git ( & [ " commit" , " -qm" , " d goes" ] ) ;
8114
git ( & [ " merge" , " -q" , " --no-edit" , " side" ] ) ;
8115
8116
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
8117
repo.import_git ( & g, " main" ) .unwrap ( ) ;
8118
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
8119
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
8120
let st = repo
8121
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
8122
.unwrap ( ) ;
8123
let view = st.tree ( ) .view ( ) ;
8124
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
8125
assert! ( view.entries.contains_key ( " d/new.txt" ) ) ;
8126
}
d77e1702 Andy McCall 1 month ago ↶
8127
8128
8129
8130
# [ test ]
8131
fn fsck_names_a_missing_text_blob ( ) {
8132
hermetic ( ) ;
8133
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8134
let root = dir.path ( ) ;
8135
let repo = Repo:: init( root) .unwrap ( ) ;
8136
let big: String = ( 0 .. 3000 ) .map ( | i | format! ( " line {i} \n " ) ) .collect ( ) ;
8137
write ( root, " big.txt" , big.as_bytes ( ) ) ;
8138
let h = repo.commit ( " big" , & [ ] ) .unwrap ( ) ;
8139
assert_eq! ( repo.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
8140
let ( _ , texts) = repo.store ( ) .get_change_lazy ( & h) .unwrap ( ) ;
8141
let Some ( crate :: objects:: TextRef:: Blob( b, _ , _ ) ) = texts.first ( ) .copied ( ) else {
8142
panic! ( " a stored text" )
8143
} ;
64818938 Andy McCall 1 month ago ↶
8144
repo.store ( ) .remove_loose_for_test ( & b) .unwrap ( ) ;
d77e1702 Andy McCall 1 month ago ↶
8145
let rep = repo.fsck ( ) .unwrap ( ) ;
8146
assert! ( rep.errors ( ) > 0 ) ;
8147
assert! (
8148
rep.problems.iter ( ) .any ( | p | p.what.contains ( & h.short ( 12 ) ) ) ,
8149
" {:?}" ,
8150
rep.problems
8151
) ;
8152
}
a07f60a3 Andy McCall 1 month ago ↶
8153
8154
8155
8156
8157
# [ test ]
8158
fn a_subrepo_on_the_parents_hub_borrows_its_credentials ( ) {
8159
hermetic ( ) ;
8160
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8161
let mut repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
8162
repo.config
8163
.set (
8164
" remote.origin.url" ,
8165
" https://Hub.example:7343/doomsdayone/metalgear" ,
8166
)
8167
.unwrap ( ) ;
8168
repo.config.set ( " remote.origin.token" , " secret" ) .unwrap ( ) ;
8169
repo.config
8170
.set ( " remote.origin.ca" , " /tmp/hub-ca.pem" )
8171
.unwrap ( ) ;
8172
let same = repo.credentials_for ( " https://hub.example:7343/doomsdayone/rstar-api" ) ;
8173
assert_eq! ( same.token.as_deref ( ) , Some ( " secret" ) ) ;
8174
assert_eq! ( same.ca.as_deref ( ) , Some ( Path:: new( " /tmp/hub-ca.pem" ) ) ) ;
8175
for other in [
8176
" https://elsewhere.example:7343/doomsdayone/rstar-api" ,
8177
" https://hub.example/doomsdayone/rstar-api" ,
8178
" http://hub.example:7343/doomsdayone/rstar-api" ,
8179
] {
8180
let o = repo.credentials_for ( other) ;
8181
assert_ne! ( o.token.as_deref ( ) , Some ( " secret" ) , " {other}" ) ;
8182
}
8183
}
ce14f0d6 Andy McCall 1 month ago ↶
8184
ff1f62fe Andy McCall 1 month ago ↶
8185
8186
8187
ce14f0d6 Andy McCall 1 month ago ↶
8188
# [ test ]
ff1f62fe Andy McCall 1 month ago ↶
8189
fn remotes_use_the_credential_store_and_the_enclosing_checkout ( ) {
8190
hermetic ( ) ;
8191
let home = tempfile:: tempdir( ) .unwrap ( ) ;
8192
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8193
let mut parent = Repo:: init( dir.path ( ) ) .unwrap ( ) .with_home ( home.path ( ) ) ;
8194
parent
8195
.config
8196
.set ( " remote.origin.url" , " https://hub.example/o/parent" )
8197
.unwrap ( ) ;
8198
parent.save_config ( ) .unwrap ( ) ;
8199
assert_eq! ( parent.remote_options ( " origin" ) .token, None ) ;
8200
crate :: credentials:: Credentials:: at( home.path ( ) )
8201
.save ( " https://hub.example" , " stored" , Some ( " andy" ) , None )
8202
.unwrap ( ) ;
8203
assert_eq! (
8204
parent.remote_options ( " origin" ) .token.as_deref ( ) ,
8205
Some ( " stored" )
8206
) ;
8207
8208
let empty_home = tempfile:: tempdir( ) .unwrap ( ) ;
8209
let nested = dir.path ( ) .join ( " deps" ) .join ( " child" ) ;
8210
let mut child = Repo:: init( & nested) .unwrap ( ) .with_home ( empty_home.path ( ) ) ;
8211
child
8212
.config
8213
.set ( " remote.origin.url" , " https://hub.example/o/child" )
8214
.unwrap ( ) ;
8215
child.save_config ( ) .unwrap ( ) ;
8216
assert_eq! ( child.remote_options ( " origin" ) .token, None ) ;
8217
parent
8218
.config
8219
.set ( " remote.origin.token" , " checkout" )
8220
.unwrap ( ) ;
8221
parent.save_config ( ) .unwrap ( ) ;
8222
assert_eq! (
8223
child.remote_options ( " origin" ) .token.as_deref ( ) ,
8224
Some ( " checkout" )
8225
) ;
8226
8227
child
8228
.config
8229
.set ( " remote.origin.url" , " https://other.example/o/child" )
8230
.unwrap ( ) ;
8231
assert_eq! ( child.remote_options ( " origin" ) .token, None ) ;
8232
}
8233
8234
# [ test ]
ce14f0d6 Andy McCall 1 month ago ↶
8235
fn the_metadata_cache_survives_a_torn_tail ( ) {
8236
hermetic ( ) ;
8237
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8238
let root = dir.path ( ) ;
8239
let repo = Repo:: init( root) .unwrap ( ) ;
8240
write ( root, " a.txt" , b " 1\n " ) ;
8241
let h = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8242
let meta = repo.store ( ) .get_change_meta ( & h) .unwrap ( ) ;
8243
let cache = repo.store ( ) .dir ( ) .join ( " metas" ) ;
8244
let len = fs:: metadata( & cache) .unwrap ( ) .len ( ) ;
8245
assert! ( len > 0 , " a change read is cached" ) ;
8246
8247
fs:: OpenOptions:: new( )
8248
.append ( true )
8249
.open ( & cache)
8250
.unwrap ( )
8251
.write_all ( & [ 7 ; 20 ] )
8252
.unwrap ( ) ;
8253
let again = Repo:: open( root) .unwrap ( ) ;
8254
assert_eq! ( again.store ( ) .get_change_meta ( & h) .unwrap ( ) , meta) ;
8255
assert_eq! ( fs:: metadata( & cache) .unwrap ( ) .len ( ) , len) ;
8256
}
f9466021 Andy McCall 1 month ago ↶
8257
8258
# [ test ]
8259
fn a_manifest_that_does_not_check_reads_as_none ( ) {
8260
hermetic ( ) ;
8261
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8262
let root = dir.path ( ) ;
8263
let repo = Repo:: init( root) .unwrap ( ) ;
8264
write ( root, " a.txt" , b " 1\n " ) ;
8265
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8266
let path = repo.manifest_path ( ) ;
8267
let mut bytes = fs:: read( & path) .unwrap ( ) ;
8268
assert! ( Repo:: open( root) .unwrap ( ) .read_manifest ( ) .unwrap ( ) .is_some ( ) ) ;
8269
let last = bytes.len ( ) - 1 ;
8270
bytes[ last] ^ = 1 ;
8271
fs:: write( & path, & bytes) .unwrap ( ) ;
8272
assert! ( Repo:: open( root) .unwrap ( ) .read_manifest ( ) .unwrap ( ) .is_none ( ) ) ;
8273
8274
let again = Repo:: open( root) .unwrap ( ) ;
8275
assert! ( ! again.snapshot ( " status" ) .unwrap ( ) .changed) ;
8276
assert! ( again.read_manifest ( ) .unwrap ( ) .is_some ( ) ) ;
8277
}
8278
8279
# [ test ]
8280
fn a_commit_leaves_a_manifest_that_proves_the_working_copy_clean ( ) {
8281
hermetic ( ) ;
8282
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8283
let root = dir.path ( ) ;
8284
let repo = Repo:: init( root) .unwrap ( ) ;
8285
write ( root, " a.txt" , b " 1\n " ) ;
8286
write ( root, " b.txt" , b " 2\n " ) ;
8287
8288
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 60 ) ;
8289
fs:: File:: options( )
8290
.write ( true )
8291
.open ( root.join ( " b.txt" ) )
8292
.unwrap ( )
8293
.set_modified ( old)
8294
.unwrap ( ) ;
8295
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8296
assert! ( repo.clean_by_manifest ( ) .unwrap ( ) , " after the first commit" ) ;
8297
repo.snapshot_status ( " status" ) .unwrap ( ) ;
8298
write ( root, " a.txt" , b " 1\n 2\n " ) ;
8299
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
8300
assert! (
8301
repo.clean_by_manifest ( ) .unwrap ( ) ,
8302
" after a commit of one file"
8303
) ;
8304
8305
8306
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
8307
repo.snapshot_status ( " status" ) .unwrap ( ) ;
8308
write ( root, " a.txt" , b " 1\n 2\n 3\n 4\n 5\n " ) ;
8309
repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
8310
assert! (
8311
repo.clean_by_manifest ( ) .unwrap ( ) ,
8312
" after an edit a status saw"
8313
) ;
8314
}
e266b7d4 Andy McCall 1 month ago ↶
8315
8316
# [ test ]
8317
fn a_conflicted_rebase_can_be_resolved_in_the_working_copy ( ) {
8318
hermetic ( ) ;
8319
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8320
let root = dir.path ( ) ;
8321
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8322
write ( root, " a.txt" , b " 1\n 2\n " ) ;
8323
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
8324
repo.bookmark_create ( " develop" , Some ( & [ a] ) ) .unwrap ( ) ;
8325
write ( root, " a.txt" , b " 1\n X\n " ) ;
8326
let c = repo.commit ( " C: 2 to X" , & [ ] ) .unwrap ( ) ;
8327
repo.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
8328
write ( root, " a.txt" , b " 1\n Y\n " ) ;
8329
let d = repo.commit ( " D: 2 to Y" , & [ ] ) .unwrap ( ) ;
8330
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
8331
8332
let err = repo.rebase ( & c, & [ d] ) .unwrap_err ( ) ;
8333
assert! ( matches! ( err, RepoError:: Conflicted( _ ) ) , " {err}" ) ;
8334
let r = repo.rebase_into_working_copy ( & c, & [ d] ) .unwrap ( ) ;
8335
assert! ( r.changed & & r.checked_out) ;
8336
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8337
assert! (
8338
snap.base_snapshot.is_conflicted ( ) ,
8339
" the conflict is in the working copy"
8340
) ;
8341
let text = fs:: read_to_string( root.join ( " a.txt" ) ) .unwrap ( ) ;
8342
assert! ( text.contains ( ' X' ) & & text.contains ( ' Y' ) , " {text}" ) ;
8343
8344
write ( root, " a.txt" , b " 1\n XY\n " ) ;
8345
repo.commit ( " Resolve" , & [ ] ) .unwrap ( ) ;
8346
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
8347
assert! ( st.contains ( & c) & & st.contains ( & d) ) ;
8348
let files = st.materialize ( ) .files;
8349
assert! ( ! files[ " a.txt" ] .is_conflicted ( ) ) ;
8350
assert! (
8351
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 1\n XY\n " )
8352
) ;
8353
8354
8355
repo.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
8356
write ( root, " b.txt" , b " b\n " ) ;
8357
let e = repo.commit ( " E" , & [ ] ) .unwrap ( ) ;
8358
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
8359
let err = repo.rebase_into_working_copy ( & e, & [ a] ) .unwrap_err ( ) ;
8360
assert! (
8361
err.to_string ( ) .contains ( " not on the current bookmark" ) ,
8362
" {err}"
8363
) ;
8364
}
d4dbab97 Andy McCall 1 month ago ↶
8365
8366
# [ test ]
8367
fn an_ignore_rule_drops_an_added_file_but_not_a_tracked_one ( ) {
8368
hermetic ( ) ;
8369
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8370
let root = dir.path ( ) ;
8371
let repo = Repo:: init( root) .unwrap ( ) ;
8372
write ( root, " a.txt" , b " a\n " ) ;
8373
write ( root, " kept.log" , b " tracked\n " ) ;
8374
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8375
write ( root, " build.log" , b " made\n " ) ;
8376
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8377
assert! ( snap.statuses.iter ( ) .any ( | s | s.path = = " build.log" ) ) ;
8378
8379
8380
write ( root, " .gitignore" , b " *.log\n " ) ;
8381
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8382
let paths: Vec< & str > = snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect ( ) ;
8383
assert_eq! ( paths, vec! [ " .gitignore" ] , " {paths:?}" ) ;
8384
write ( root, " kept.log" , b " tracked, edited\n " ) ;
8385
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8386
assert! ( snap.statuses.iter ( ) .any ( | s | s.path = = " kept.log" ) ) ;
8387
}
fd280228 Andy McCall 1 month ago ↶
8388
8389
# [ test ]
8390
fn shed_graphs_come_back_the_same ( ) {
8391
hermetic ( ) ;
8392
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8393
let root = dir.path ( ) ;
8394
let repo = Repo:: init( root) .unwrap ( ) ;
8395
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
8396
write ( root, " b.txt" , b " x\n " ) ;
8397
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8398
write ( root, " a.txt" , b " 1\n two\n 3\n 4\n " ) ;
8399
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
8400
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
8401
let mut st = repo.load_state_history ( & heads) .unwrap ( ) ;
8402
let view = st.tree ( ) .view ( ) ;
8403
let nodes: Vec< NodeId> = [ " a.txt" , " b.txt" ]
8404
.iter ( )
8405
.map ( | p | view.entries[ * p] .node )
8406
.collect ( ) ;
8407
repo.load_graphs_of ( & mut st, & nodes) .unwrap ( ) ;
8408
let before: Vec< Vec< u8 > > = nodes.iter ( ) .map ( | n | st.render_file ( n) .0 ) .collect ( ) ;
8409
assert_eq! ( st.built_count ( ) , 2 ) ;
8410
let shed = st.shed_graphs ( | b | repo.store_blob ( b) ) .unwrap ( ) ;
8411
assert_eq! ( ( shed, st.built_count ( ) ) , ( 2 , 0 ) ) ;
8412
8413
write ( root, " a.txt" , b " 1\n two\n 3\n 4\n 5\n " ) ;
8414
let h3 = repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
8415
let ( skeleton, texts) = repo.store ( ) .get_change_lazy ( & h3) .unwrap ( ) ;
8416
st.add_known ( h3, skeleton, texts) ;
8417
repo.load_graphs_of ( & mut st, & nodes) .unwrap ( ) ;
8418
let after: Vec< Vec< u8 > > = nodes.iter ( ) .map ( | n | st.render_file ( n) .0 ) .collect ( ) ;
8419
assert_eq! ( after[ 1 ] , before[ 1 ] ) ;
8420
assert_eq! ( after[ 0 ] , b " 1\n two\n 3\n 4\n 5\n " ) ;
8421
}
360aa74a Andy McCall 1 month ago ↶
8422
8423
# [ test ]
8424
fn many_edited_files_diff_in_parallel ( ) {
8425
8426
8427
8428
hermetic ( ) ;
8429
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8430
let root = dir.path ( ) ;
8431
let repo = Repo:: init( root) .unwrap ( ) ;
8432
let text = | i : usize , round : usize | {
8433
let mut s = String :: new( ) ;
8434
for line in 0 .. 50 {
8435
if round > 0 & & line % 7 = = i % 7 {
8436
s.push_str ( & format! ( " edited {round} {i} {line} \n " ) ) ;
8437
} else {
8438
s.push_str ( & format! ( " file {i} line {line} \n " ) ) ;
8439
}
8440
}
8441
if round > 0 & & i.is_multiple_of ( 3 ) {
8442
s.push_str ( " appended\n " ) ;
8443
}
8444
s
8445
} ;
8446
for i in 0 .. 40 {
8447
write ( root, & format! ( " d/f{i} .txt" ) , text ( i, 0 ) .as_bytes ( ) ) ;
8448
}
8449
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8450
for i in 0 .. 40 {
8451
write ( root, & format! ( " d/f{i} .txt" ) , text ( i, 1 ) .as_bytes ( ) ) ;
8452
}
8453
let h = repo.commit ( " edit every file" , & [ ] ) .unwrap ( ) ;
8454
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
8455
for i in 0 .. 40 {
8456
let want = text ( i, 1 ) ;
8457
assert! (
8458
matches! ( & snap.files[ & format! ( " d/f{i} .txt" ) ] .content, FileContent:: Text { bytes, .. } if bytes = = want.as_bytes ( ) ) ,
8459
" d/f{i}.txt as committed"
8460
) ;
8461
}
8462
assert! (
8463
repo.commit ( " nothing" , & [ ] ) .is_err ( ) ,
8464
" nothing left to commit"
8465
) ;
8466
}
b35df36b Andy McCall 1 month ago ↶
8467
8468
8469
8470
8471
8472
# [ test ]
8473
fn a_fetched_change_shows_as_behind_and_resolves ( ) {
8474
hermetic ( ) ;
8475
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8476
let root = dir.path ( ) ;
8477
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
8478
drop ( origin) ;
8479
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
8480
let a = Repo:: clone( & url, & root.join ( " a" ) )
8481
.unwrap ( )
8482
.with_home ( root.join ( " home-a" ) ) ;
8483
write ( & root.join ( " a" ) , " f.txt" , b " one\n " ) ;
8484
a.commit ( " one" , & [ ] ) .unwrap ( ) ;
8485
a.push ( " origin" , false ) .unwrap ( ) ;
8486
let b = Repo:: clone( & url, & root.join ( " b" ) )
8487
.unwrap ( )
8488
.with_home ( root.join ( " home-b" ) ) ;
8489
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 0 ) ) ) ;
8490
8491
write ( & root.join ( " a" ) , " f.txt" , b " one\n two\n " ) ;
8492
let h2 = a.commit ( " two" , & [ ] ) .unwrap ( ) ;
8493
a.push ( " origin" , false ) .unwrap ( ) ;
8494
b.fetch ( " origin" ) .unwrap ( ) ;
8495
8496
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 1 ) ) ) ;
8497
assert_eq! ( b.tracking ( " origin" , " nope" ) .unwrap ( ) , None ) ;
8498
assert_eq! (
8499
b.resolve_change ( & format! ( " #{} " , & h2.to_hex ( ) [ .. 12 ] ) )
8500
.unwrap ( ) ,
8501
h2
8502
) ;
8503
assert_eq! (
8504
b.remote_only_location ( & h2) .unwrap ( ) .as_deref ( ) ,
8505
Some ( " origin/main" )
8506
) ;
8507
8508
8509
b.fetch ( " origin" ) .unwrap ( ) ;
8510
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 1 ) ) ) ;
8511
b.pull ( " origin" ) .unwrap ( ) ;
8512
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 0 ) ) ) ;
8513
assert_eq! ( b.remote_only_location ( & h2) .unwrap ( ) , None ) ;
8514
8515
8516
let mut c = Repo:: init( & root.join ( " c" ) ) .unwrap ( ) ;
8517
assert_eq! ( c.default_remote ( ) , " origin" ) ;
8518
c.config.set ( " remote.earth.url" , & url) .unwrap ( ) ;
8519
assert_eq! ( c.default_remote ( ) , " earth" ) ;
8520
c.config.set ( " remote.origin.url" , & url) .unwrap ( ) ;
8521
assert_eq! ( c.default_remote ( ) , " origin" ) ;
8522
}
827281ee Andy McCall 1 month ago ↶
8523
8524
8525
8526
# [ test ]
8527
fn a_clone_from_a_relative_path_records_it_absolute ( ) {
8528
hermetic ( ) ;
8529
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8530
let root = dir.path ( ) ;
8531
Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
8532
let rel = crate :: protocol:: stored_url( " relative/repo" ) ;
8533
assert! ( std:: path:: Path:: new( & rel) .is_absolute ( ) , " {rel}" ) ;
8534
assert_eq! (
8535
crate :: protocol:: stored_url( " https://hub.example/o/n" ) ,
8536
" https://hub.example/o/n"
8537
) ;
8538
assert! ( crate :: protocol:: stored_url( " file://x" ) .starts_with ( " file:///" ) ) ;
8d803563 Andy McCall 1 month ago ↶
8539
assert! ( crate :: protocol:: stored_url( " dir://x" ) .starts_with ( " dir:///" ) ) ;
8540
8541
assert_eq! (
8542
crate :: protocol:: stored_url( " s3://bucket/prefix?profile=p" ) ,
8543
" s3://bucket/prefix?profile=p"
8544
) ;
827281ee Andy McCall 1 month ago ↶
8545
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
8546
let b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) ;
8547
assert_eq! ( b.config.remotes[ " origin" ] , url) ;
8548
b.fetch ( " origin" ) .unwrap ( ) ;
8549
}
a46f3e4f Andy McCall 1 month ago ↶
8550
8551
8552
8553
8554
# [ test ]
8555
fn a_workspace_added_on_a_bookmark_is_on_it_with_its_subrepos ( ) {
8556
hermetic ( ) ;
8557
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8558
let root = dir.path ( ) ;
8559
Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
8560
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
8561
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
8562
.unwrap ( )
8563
.with_home ( root.join ( " home" ) ) ;
8564
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
8565
let l1 = lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
8566
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
8567
8568
let app_dir = root.join ( " app" ) ;
8569
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8570
write ( & app_dir, " main.txt" , b " app\n " ) ;
8571
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
8572
app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
8573
8574
app.bookmark_create ( " develop" , None ) .unwrap ( ) ;
8575
app.bookmark_create ( " feature" , None ) .unwrap ( ) ;
8576
let mut app = app;
8577
app.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
8578
assert_eq! ( app.config.bookmark, " feature" ) ;
8579
8580
8581
std:: fs:: rename( root.join ( " lib-origin" ) , root.join ( " lib-origin-gone" ) ) .unwrap ( ) ;
8582
let heads = app.resolve_heads ( " develop" ) .unwrap ( ) ;
8583
let ws = app
8584
.workspace_add_on ( & root.join ( " ws" ) , Some ( & heads) , Some ( " develop" ) )
8585
.unwrap ( ) ;
8586
assert_eq! (
8587
ws.config.bookmark, " develop" ,
8588
" the new workspace is on develop"
8589
) ;
8590
assert_eq! (
8591
app.config.bookmark, " feature" ,
8592
" and this one stays on its own"
8593
) ;
8594
let sub = Repo:: open( & root.join ( " ws/libs/lib" ) ) .unwrap ( ) ;
8595
assert_eq! (
8596
sub.config.remotes[ " origin" ] , lib_url,
8597
" its origin is the subrepo's URL"
8598
) ;
8599
assert_eq! ( sub.bookmark_heads ( ) .unwrap ( ) , vec! [ l1] , " at its pin" ) ;
8600
assert_eq! (
8601
std:: fs:: read( root.join ( " ws/libs/lib/lib.txt" ) ) .unwrap ( ) ,
8602
b " v1\n "
8603
) ;
8604
}
47ce8b8e Andy McCall 1 month ago ↶
8605
8606
8607
8608
8609
# [ test ]
8610
fn revsets_select_changes_by_relation_author_text_date_and_path ( ) {
8611
hermetic ( ) ;
8612
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8613
let root = dir.path ( ) ;
8614
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8615
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
8616
.unwrap ( ) ;
8617
write ( root, " src/a.rs" , b " one\n " ) ;
8618
let a = repo.commit ( " first: add a" , & [ ] ) .unwrap ( ) ;
8619
write ( root, " src/a.rs" , b " one\n two\n " ) ;
8620
let b = repo.commit ( " second: grow a" , & [ ] ) .unwrap ( ) ;
8621
8622
repo.checkout ( & [ a] , false , " checkout a" ) .unwrap ( ) ;
8623
write ( root, " docs/notes.md" , b " notes\n " ) ;
8624
let c = repo.commit ( " third: notes" , & [ ] ) .unwrap ( ) ;
8625
8626
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
8627
.unwrap ( ) ;
8628
fs:: remove_file( root.join ( " src/a.rs" ) ) .unwrap ( ) ;
8629
write ( root, " src/z.rs" , b " one\n two\n " ) ;
8630
let d = repo.commit ( " fourth: rename a to z" , & [ ] ) .unwrap ( ) ;
8631
let wc = repo.working_copy ( ) .unwrap ( ) .0 ;
8632
8633
repo.set_user_config ( " revset.mine" , " author(me) & ::main" )
8634
.unwrap ( ) ;
8635
assert! ( repo.set_user_config ( " revset.bad" , " a &" ) .is_err ( ) ) ;
8636
let set = | src : & str | -> Vec< ChangeHash> {
8637
let mut v = repo.revset ( src) .unwrap ( ) .hashes;
8638
v.sort ( ) ;
8639
v
8640
} ;
8641
let sorted = | hs : & [ChangeHash]| -> Vec< ChangeHash> {
8642
let mut v = hs.to_vec ( ) ;
8643
v.sort ( ) ;
8644
v
8645
} ;
8646
8647
8648
8649
8650
let heads = sorted ( & repo.bookmark_heads ( ) .unwrap ( ) ) ;
8651
let meta = | h : & ChangeHash| repo.store ( ) .get_change_meta ( h) .unwrap ( ) ;
8652
assert! ( heads.contains ( & d) & & heads.contains ( & c) ) ;
8653
8654
assert_eq! ( set ( " main" ) , heads) ;
8655
assert_eq! ( set ( " ::main" ) , sorted ( & [ a, b, c, d] ) ) ;
8656
assert_eq! ( set ( " @" ) , vec! [ wc] ) ;
8657
assert_eq! ( set ( " ::@" ) , sorted ( & [ a, b, c, d, wc] ) ) ;
8658
assert_eq! ( set ( & format! ( " ::#{} " , b.short ( 12 ) ) ) , sorted ( & [ a, b] ) ) ;
8659
assert_eq! ( set ( & format! ( " #{} ::" , a.short ( 12 ) ) ) , sorted ( & [ a, b, d, wc] ) ) ;
8660
assert_eq! (
8661
set ( & format! ( " #{} ::#{} " , a.short ( 12 ) , b.short ( 12 ) ) ) ,
8662
sorted ( & [ a, b] )
8663
) ;
8664
8665
assert_eq! ( set ( & format! ( " #{} ..main" , b.short ( 12 ) ) ) , sorted ( & [ c, d] ) ) ;
8666
assert_eq! ( set ( & format! ( " #{} .." , c.short ( 12 ) ) ) , sorted ( & [ a, b, d, wc] ) ) ;
8667
assert_eq! ( set ( " ..main" ) , sorted ( & [ a, b, c, d] ) ) ;
8668
8669
assert_eq! (
8670
set ( & format! ( " ::main ~ #{} " , d.short ( 12 ) ) ) ,
8671
sorted ( & [ a, b, c] )
8672
) ;
8673
assert_eq! (
8674
set ( & format! ( " ::main & #{} | #{} " , b.short ( 12 ) , c.short ( 12 ) ) ) ,
8675
sorted ( & [ b, c] )
8676
) ;
8677
assert_eq! ( set ( " all() ~ ::main" ) , vec! [ wc] ) ;
8678
8679
let mut head_deps: Vec< ChangeHash> = heads.iter ( ) .flat_map ( | h | meta ( h) .deps) .collect ( ) ;
8680
head_deps.sort ( ) ;
8681
head_deps.dedup ( ) ;
8682
assert_eq! ( set ( " deps(main)" ) , head_deps) ;
8683
let on_a: Vec< ChangeHash> = [ b, c, d]
8684
.into_iter ( )
8685
.filter ( | h | meta ( h) .deps.contains ( & a) )
8686
.collect ( ) ;
8687
assert_eq! ( set ( & format! ( " dependents(#{} )" , a.short ( 12 ) ) ) , sorted ( & on_a) ) ;
8688
assert_eq! ( set ( " heads(::main)" ) , heads) ;
8689
assert_eq! ( set ( " roots(::main)" ) , sorted ( & [ a, c] ) ) ;
8690
assert_eq! (
8691
set ( & format! (
8692
" heads(#{} | #{} | #{} )" ,
8693
a.short ( 12 ) ,
8694
b.short ( 12 ) ,
8695
c.short ( 12 )
8696
) ) ,
8697
sorted ( & [ b, c] )
8698
) ;
8699
assert_eq! ( set ( " latest(::main, 1)" ) , vec! [ d] ) ;
8700
assert_eq! ( set ( " kind(working-copy)" ) , vec! [ wc] ) ;
8701
assert_eq! ( set ( " none()" ) , vec! [ ] ) ;
8702
8703
let key = repo.config.author_key.unwrap ( ) ;
8704
assert_eq! ( set ( " author(ada) & ::main" ) , sorted ( & [ a, b, c, d] ) ) ;
8705
assert_eq! ( set ( " author(\" [email protected] \" ) & ::main" ) .len ( ) , 4 ) ;
8706
assert_eq! ( set ( & format! ( " author({} ) & ::main" , key.short ( 12 ) ) ) .len ( ) , 4 ) ;
8707
assert_eq! ( set ( " author(me) & ::main" ) .len ( ) , 4 ) ;
8708
assert_eq! ( set ( " author(nobody)" ) , vec! [ ] ) ;
8709
assert_eq! ( set ( " description(notes)" ) , vec! [ c] ) ;
8710
assert_eq! ( set ( " description(\" rename a\" )" ) , vec! [ d] ) ;
8711
assert_eq! ( set ( " date(-1h..) & ::main" ) .len ( ) , 4 ) ;
8712
assert_eq! ( set ( " date(..2000-01-01)" ) , vec! [ ] ) ;
8713
8714
assert_eq! ( set ( " touches(\" src/z.rs\" )" ) , sorted ( & [ a, b, d] ) ) ;
8715
assert_eq! ( set ( " touches(\" src/**\" ) & ::main" ) , sorted ( & [ a, b, d] ) ) ;
8716
assert_eq! ( set ( " touches(\" docs/*\" )" ) , vec! [ c] ) ;
8717
assert_eq! ( set ( " touches(\" nothing/*\" )" ) , vec! [ ] ) ;
8718
8719
assert_eq! ( repo.revset ( " ::main" ) .unwrap ( ) .hashes[ 0 ] , d) ;
8720
8721
assert_eq! ( set ( " mine()" ) .len ( ) , 4 ) ;
8722
let e = repo.revset ( " nosuch()" ) .unwrap_err ( ) .to_string ( ) ;
8723
assert! ( e.contains ( " unknown function nosuch()" ) , " {e}" ) ;
8724
let e = repo.revset ( " main &" ) .unwrap_err ( ) .to_string ( ) ;
8725
assert! ( e.contains ( " at character" ) , " {e}" ) ;
8726
let e = repo.revset ( " zzzz" ) .unwrap_err ( ) .to_string ( ) ;
8727
assert! ( e.contains ( " no change matches" ) , " {e}" ) ;
8728
let e = repo
8729
.revset_with_budget ( " ::main" , std:: time:: Duration:: ZERO )
8730
.unwrap_err ( )
8731
.to_string ( ) ;
8732
assert! ( e.contains ( " budget" ) , " {e}" ) ;
8733
8734
let entries = repo.log_revset ( " ::@" ) .unwrap ( ) ;
8735
assert! ( entries[ 0 ] .is_working_copy & & entries.iter ( ) .any ( | e | e.is_head & & e.hash = = d ) ) ;
2c9ddac3 Andy McCall 1 month ago ↶
8736
8737
8738
assert_eq! ( set ( " unsigned() & ::main" ) , sorted ( & [ a, b, c, d] ) ) ;
8739
assert_eq! ( set ( " signed_by(me)" ) , vec! [ ] ) ;
8740
repo.sign_change ( & d) .unwrap ( ) ;
8741
assert_eq! ( set ( " signed_by(me)" ) , vec! [ d] ) ;
8742
assert_eq! ( set ( " signed_by([email protected] )" ) , vec! [ d] ) ;
8743
assert_eq! ( set ( & format! ( " signed_by({} )" , key.short ( 12 ) ) ) , vec! [ d] ) ;
8744
assert_eq! ( set ( " unsigned() & ::main" ) , sorted ( & [ a, b, c] ) ) ;
8745
assert_eq! ( set ( " delegated()" ) , vec! [ ] ) ;
8746
assert_eq! ( set ( " revoked()" ) , vec! [ ] ) ;
8747
assert_eq! ( set ( " attested(tag)" ) , vec! [ ] ) ;
8748
repo.tag_create ( " v1" , & [ b] , " first" ) .unwrap ( ) ;
8749
assert_eq! ( set ( " attested(tag)" ) , sorted ( & [ a, b] ) ) ;
8750
assert_eq! ( set ( " attested(tag) & unsigned()" ) , sorted ( & [ a, b] ) ) ;
8751
assert_eq! ( set ( " attested(receipt)" ) , vec! [ ] ) ;
8752
assert_eq! ( set ( " built(test)" ) , vec! [ ] ) ;
8753
let e = repo.revset ( " attested(seal)" ) .unwrap_err ( ) .to_string ( ) ;
8754
assert! ( e.contains ( " tag, receipt or ref" ) , " {e}" ) ;
8755
repo.revoke_identity ( & key, " test" ) .unwrap ( ) ;
8756
assert_eq! ( set ( " revoked()" ) , vec! [ d] ) ;
8757
assert_eq! ( set ( " signed_by(me) ~ revoked()" ) , vec! [ ] ) ;
47ce8b8e Andy McCall 1 month ago ↶
8758
}
614f55e1 Andy McCall 1 month ago ↶
8759
8760
# [ test ]
8761
fn a_rotation_is_recorded_wherever_the_old_key_was_named ( ) {
8762
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8763
let root = dir.path ( ) ;
8764
let home = root.join ( " home" ) ;
8765
8766
8767
let mut a = Repo:: init( & root.join ( " a" ) ) .unwrap ( ) .with_home ( & home) ;
8768
let ( key, _ ) = a
8769
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
8770
.unwrap ( ) ;
8771
a.config.set ( " author.key" , & key.to_hex ( ) ) .unwrap ( ) ;
8772
a.save_config ( ) .unwrap ( ) ;
8773
let agent = SecretKey:: generate( ) .identity ( " Agent" , " [email protected] " , 1 ) ;
8774
let r = a
8775
.delegate_add ( agent.clone ( ) , crate :: identity:: Scope:: CHANGES , None )
8776
.unwrap ( ) ;
8777
assert_eq! (
8778
read_config_file ( & home.join ( " config.toml" ) ) .unwrap ( ) [ " author.key" ] ,
8779
r.new.to_hex ( ) ,
8780
" the user file follows"
8781
) ;
8782
assert_eq! (
8783
read_config_file ( & root.join ( " a/.oxy/config" ) ) .unwrap ( ) [ " author.key" ] ,
8784
r.new.to_hex ( ) ,
8785
" the pinning repository follows"
8786
) ;
8787
let b = Repo:: init( & root.join ( " b" ) ) .unwrap ( ) .with_home ( & home) ;
8788
assert_eq! (
8789
b.config.author_key,
8790
Some ( r.new) ,
8791
" a checkout without a pin follows the user file"
8792
) ;
8793
8794
let other_home = root.join ( " home2" ) ;
8795
let mut c = Repo:: init( & root.join ( " c" ) ) .unwrap ( ) .with_home ( & other_home) ;
8796
let ( ck, _ ) = c
8797
.create_identity ( " Cy" , " [email protected] " , IdentityScope:: Repo)
8798
.unwrap ( ) ;
8799
assert! ( ! other_home.join ( " config.toml" ) .exists ( ) ) ;
8800
let r2 = c.rotate_identity ( ) .unwrap ( ) ;
8801
assert_ne! ( r2.new, ck) ;
8802
assert_eq! (
8803
read_config_file ( & root.join ( " c/.oxy/config" ) ) .unwrap ( ) [ " author.key" ] ,
8804
r2.new.to_hex ( )
8805
) ;
8806
assert! (
8807
! other_home.join ( " config.toml" ) .exists ( ) ,
8808
" a repository-scoped identity stays repository-scoped"
8809
) ;
8810
8811
let ( d1, d2) = (
8812
SecretKey:: generate( ) .identity ( " D1" , " d1@x" , 1 ) ,
8813
SecretKey:: generate( ) .identity ( " D2" , " d2@x" , 1 ) ,
8814
) ;
8815
let r3 = c
8816
.delegate_add_many ( vec! [ d1.clone ( ) , d2] , crate :: identity:: Scope:: CHANGES , None )
8817
.unwrap ( ) ;
8818
assert_eq! ( r3.identity.delegates.len ( ) , 2 ) ;
8819
assert! ( c
8820
.delegate_add ( d1, crate :: identity:: Scope:: CHANGES , None )
8821
.is_err ( ) ) ;
8822
}
33be2409 Andy McCall 1 month ago ↶
8823
8824
8825
8826
8827
8828
# [ test ]
8829
fn git_import_joins_unrelated_histories_that_add_the_same_file ( ) {
8830
8831
8832
crate :: repo:: git:: set_merge_batch_for_tests( Some ( 1 ) ) ;
8833
hermetic ( ) ;
8834
if std:: process:: Command:: new( " git" )
8835
.arg ( " --version" )
8836
.output ( )
8837
.is_err ( )
8838
{
8839
eprintln! ( " git not installed; skipping" ) ;
8840
return ;
8841
}
8842
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8843
let root = dir.path ( ) ;
8844
let g = root.join ( " src.git" ) ;
8845
fs:: create_dir_all( & g) .unwrap ( ) ;
8846
let git = | args : & [& str ]| -> String {
8847
let out = std:: process:: Command:: new( " git" )
8848
.arg ( " -C" )
8849
.arg ( & g)
8850
.args ( [
8851
" -c" ,
8852
" user.name=Grace" ,
8853
" -c" ,
8854
" [email protected] " ,
8855
" -c" ,
8856
" commit.gpgsign=false" ,
8857
] )
8858
.args ( args)
8859
.output ( )
8860
.unwrap ( ) ;
8861
assert! (
8862
out.status.success ( ) ,
8863
" git {args:?}: {}" ,
8864
String :: from_utf8_lossy( & out.stderr)
8865
) ;
8866
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
8867
} ;
8868
let text = b " /* header */\n const a = 1;\n const b = 2;\n " ;
8869
8870
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
8871
write ( & g, " gulpfile.js" , text) ;
8872
write ( & g, " a.txt" , b " a\n " ) ;
8873
git ( & [ " add" , " ." ] ) ;
8874
git ( & [ " commit" , " -q" , " -m" , " A root" ] ) ;
8875
8876
git ( & [ " checkout" , " -q" , " --orphan" , " fork" ] ) ;
8877
git ( & [ " rm" , " -q" , " -r" , " --cached" , " ." ] ) ;
8878
for f in [ " gulpfile.js" , " a.txt" ] {
8879
let _ = fs:: remove_file( g.join ( f) ) ;
8880
}
8881
write ( & g, " gulpfile.js" , text) ;
8882
write ( & g, " b.txt" , b " b\n " ) ;
8883
git ( & [ " add" , " ." ] ) ;
8884
git ( & [ " commit" , " -q" , " -m" , " B root" ] ) ;
8885
8886
git ( & [ " checkout" , " -q" , " -b" , " one" , " main" ] ) ;
8887
git ( & [
8888
" merge" ,
8889
" -q" ,
8890
" --no-ff" ,
8891
" --allow-unrelated-histories" ,
8892
" -m" ,
8893
" join on one" ,
8894
" fork" ,
8895
] ) ;
8896
git ( & [ " checkout" , " -q" , " -b" , " two" , " main" ] ) ;
8897
write ( & g, " a.txt" , b " a\n two\n " ) ;
8898
git ( & [ " commit" , " -q" , " -am" , " work on two" ] ) ;
8899
git ( & [
8900
" merge" ,
8901
" -q" ,
8902
" --no-ff" ,
8903
" --allow-unrelated-histories" ,
8904
" -m" ,
8905
" join on two" ,
8906
" fork" ,
8907
] ) ;
8908
8909
git ( & [ " checkout" , " -q" , " main" ] ) ;
8910
git ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge one" , " one" ] ) ;
8911
git ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge two" , " two" ] ) ;
8912
write (
8913
& g,
8914
" gulpfile.js" ,
8915
b " /* header */\n const a = 1;\n const b = 3;\n " ,
8916
) ;
8917
git ( & [ " commit" , " -q" , " -am" , " after" ] ) ;
8918
8919
let dest = root.join ( " dest" ) ;
8920
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8921
repo.import_git ( & g, " main" ) .unwrap ( ) ;
8922
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
8923
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
8924
assert_eq! (
8925
fs:: read( dest.join ( " gulpfile.js" ) ) .unwrap ( ) ,
8926
b " /* header */\n const a = 1;\n const b = 3;\n "
8927
) ;
8928
}
8929
8930
8931
8932
8933
# [ test ]
8934
fn git_import_joins_where_each_join_kept_a_different_copy ( ) {
8935
8936
8937
crate :: repo:: git:: set_merge_batch_for_tests( Some ( 1 ) ) ;
8938
hermetic ( ) ;
8939
if std:: process:: Command:: new( " git" )
8940
.arg ( " --version" )
8941
.output ( )
8942
.is_err ( )
8943
{
8944
eprintln! ( " git not installed; skipping" ) ;
8945
return ;
8946
}
8947
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8948
let root = dir.path ( ) ;
8949
let g = root.join ( " src.git" ) ;
8950
fs:: create_dir_all( & g) .unwrap ( ) ;
8951
let run = | args : & [& str ]| {
8952
std:: process:: Command:: new( " git" )
8953
.arg ( " -C" )
8954
.arg ( & g)
8955
.args ( [
8956
" -c" ,
8957
" user.name=Grace" ,
8958
" -c" ,
8959
" [email protected] " ,
8960
" -c" ,
8961
" commit.gpgsign=false" ,
8962
] )
8963
.args ( args)
8964
.output ( )
8965
.unwrap ( )
8966
} ;
8967
let git = | args : & [& str ]| -> String {
8968
let out = run ( args) ;
8969
assert! (
8970
out.status.success ( ) ,
8971
" git {args:?}: {}" ,
8972
String :: from_utf8_lossy( & out.stderr)
8973
) ;
8974
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
8975
} ;
8976
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
8977
write (
8978
& g,
8979
" gulpfile.js" ,
8980
b " /* header */\n const a = 1;\n const b = 2;\n " ,
8981
) ;
8982
git ( & [ " add" , " ." ] ) ;
8983
git ( & [ " commit" , " -q" , " -m" , " A root" ] ) ;
8984
write (
8985
& g,
8986
" gulpfile.js" ,
8987
b " /* header */\n const a = 1;\n \n const b = 2;\n " ,
8988
) ;
8989
git ( & [ " commit" , " -q" , " -am" , " whitespace" ] ) ;
8990
git ( & [ " checkout" , " -q" , " --orphan" , " fork" ] ) ;
8991
git ( & [ " rm" , " -q" , " -r" , " --cached" , " ." ] ) ;
8992
let _ = fs:: remove_file( g.join ( " gulpfile.js" ) ) ;
8993
write (
8994
& g,
8995
" gulpfile.js" ,
8996
b " /* header */\n const a = 1;\n const b = 2;\n const c = 3;\n " ,
8997
) ;
8998
git ( & [ " add" , " ." ] ) ;
8999
git ( & [ " commit" , " -q" , " -m" , " B root" ] ) ;
9000
9001
for ( branch, keep) in [ ( " one" , " ours" ) , ( " two" , " theirs" ) ] {
9002
git ( & [ " checkout" , " -q" , " -b" , branch, " main" ] ) ;
9003
let _ = run ( & [
9004
" merge" ,
9005
" -q" ,
9006
" --no-ff" ,
9007
" --allow-unrelated-histories" ,
9008
" -m" ,
9009
" join" ,
9010
" fork" ,
9011
] ) ;
9012
git ( & [ " checkout" , & format! ( " --{keep} " ) , " gulpfile.js" ] ) ;
9013
git ( & [ " add" , " gulpfile.js" ] ) ;
9014
git ( & [ " commit" , " -q" , " -m" , & format! ( " join on {branch} " ) ] ) ;
9015
}
9016
9017
git ( & [ " checkout" , " -q" , " one" ] ) ;
9018
write (
9019
& g,
9020
" gulpfile.js" ,
9021
b " /* header */\n const a = 1;\n \n const b = 2;\n // tslint\n " ,
9022
) ;
9023
git ( & [ " commit" , " -q" , " -am" , " tslint on one" ] ) ;
9024
git ( & [ " checkout" , " -q" , " two" ] ) ;
9025
write (
9026
& g,
9027
" gulpfile.js" ,
9028
b " /* header */\n const a = 1;\n const b = 2;\n const c = 3;\n // fmt\n " ,
9029
) ;
9030
write ( & g, " extra.txt" , b " x\n " ) ;
9031
write ( & g, " more.txt" , b " m\n " ) ;
9032
git ( & [ " add" , " ." ] ) ;
9033
git ( & [ " commit" , " -q" , " -am" , " fmt on two" ] ) ;
9034
9035
git ( & [ " checkout" , " -q" , " one" ] ) ;
9036
let _ = run ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge two" , " two" ] ) ;
9037
write (
9038
& g,
9039
" gulpfile.js" ,
9040
b " /* header */\n const a = 1;\n const b = 2;\n const c = 3;\n // tslint\n // fmt\n " ,
9041
) ;
9042
git ( & [ " add" , " gulpfile.js" ] ) ;
9043
git ( & [ " commit" , " -q" , " -m" , " merge two into one" ] ) ;
9044
9045
let dest = root.join ( " dest" ) ;
9046
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9047
repo.import_git ( & g, " one" ) .unwrap ( ) ;
9048
let v = repo.verify_git_import ( & g, " one" ) .unwrap ( ) ;
9049
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
9050
}
dd21fd82 Andy McCall 1 month ago ↶
9051
9052
9053
9054
9055
9056
9057
# [ test ]
9058
fn over_a_store_the_operation_head_moves_by_compare_and_swap ( ) {
9059
use crate :: strata:: { MemoryStore, Store as _ } ;
9060
let backend = std:: sync:: Arc:: new( MemoryStore:: new( ) ) ;
9061
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = Some ( backend.clone ( ) ) ) ;
9062
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9063
let a = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9064
assert! (
9065
! dir.path ( ) .join ( " .oxy/ops/head" ) .exists ( ) ,
9066
" the head is not a file"
9067
) ;
9068
let ( h0, op0) = a.head_op ( ) .unwrap ( ) ;
9069
let record = backend
9070
.read_ref ( HEAD_RECORD )
9071
.unwrap ( )
9072
.expect ( " the head is the store's record" ) ;
9073
assert_eq! ( String :: from_utf8( record.0 ) .unwrap ( ) , h0.to_hex ( ) ) ;
9074
9075
let b = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
9076
let state = a.store.put_state ( & StateObject:: new( vec! [ ] ) ) .unwrap ( ) ;
9077
let mut va = op0.view.clone ( ) ;
9078
va.bookmarks.insert ( " from-a" .into ( ) , state) ;
9079
let mut vb = op0.view.clone ( ) ;
9080
vb.bookmarks.insert ( " from-b" .into ( ) , state) ;
9081
let rb = b.record_op ( & [ h0] , " bookmark from-b" , vb) .unwrap ( ) ;
9082
assert_eq! ( b.head_ref ( ) .unwrap ( ) , rb) ;
9083
9084
let ra = a.record_op ( & [ h0] , " bookmark from-a" , va) .unwrap ( ) ;
9085
let ( head, op) = Repo:: open( dir.path ( ) ) .unwrap ( ) .head_op ( ) .unwrap ( ) ;
9086
assert_eq! ( head, ra) ;
9087
assert! (
9088
op.command.ends_with ( " (merged with a concurrent operation)" ) ,
9089
" {}" ,
9090
op.command
9091
) ;
9092
assert_eq! ( op.parents, vec! [ rb, op.parents[ 1 ] ] ) ;
9093
assert! ( op.view.bookmarks.contains_key ( " from-a" ) & & op.view.bookmarks.contains_key ( " from-b" ) ) ;
9094
9095
9096
let ( h, v) = a.read_head ( ) .unwrap ( ) ;
9097
assert! ( b.swap_head ( & rb, v.as_ref ( ) ) .unwrap ( ) ) ;
9098
assert! ( ! a.swap_head ( & h, v.as_ref ( ) ) .unwrap ( ) , " stale" ) ;
767ac6cd Andy McCall 19 days ago
9099
a.refresh ( ) .unwrap ( ) ;
dd21fd82 Andy McCall 1 month ago ↶
9100
assert_eq! ( a.head_ref ( ) .unwrap ( ) , rb) ;
9101
9102
let other = tempfile:: tempdir( ) .unwrap ( ) ;
9103
let err = Repo:: init( other.path ( ) )
9104
.err ( )
9105
.expect ( " the store already holds a log" )
9106
.to_string ( ) ;
9107
assert! (
9108
err.contains ( " already holds a repository's operation log" ) ,
9109
" {err}"
9110
) ;
9111
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = None ) ;
9112
}
d069701a Andy McCall 1 month ago ↶
9113
9114
9115
9116
9117
# [ test ]
9118
fn a_repository_runs_on_a_store_with_a_cache_in_front ( ) {
9119
hermetic ( ) ;
9120
if std:: env:: var_os( " OXY_STORE_BACKEND" ) .is_some ( ) {
9121
return ;
9122
}
9123
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9124
let ( root, bucket) = ( dir.path ( ) .join ( " work" ) , dir.path ( ) .join ( " bucket" ) ) ;
9125
let url = format! ( " dir://{} " , bucket.display ( ) ) ;
9126
let repo = Repo:: init_on( & root, [ 7 ; 16 ] , Some ( & url) ) .unwrap ( ) ;
9127
assert_eq! ( repo.config.storage_url.as_deref ( ) , Some ( url.as_str ( ) ) ) ;
9128
write ( & root, " a.txt" , b " one\n " ) ;
9129
let h1 = repo.commit ( " First" , & [ ] ) .unwrap ( ) ;
9130
write ( & root, " a.txt" , b " one\n two\n " ) ;
9131
let h2 = repo.commit ( " Second" , & [ ] ) .unwrap ( ) ;
9132
assert! (
9133
! root.join ( " .oxy/ops/head" ) .exists ( ) ,
9134
" the head is in the store"
9135
) ;
9136
assert! ( bucket.join ( " ops/head" ) .is_file ( ) ) ;
9137
assert! ( root.join ( " .oxy/cache" ) .is_dir ( ) ) ;
9138
drop ( repo) ;
9139
9140
9141
for drop_cache in [ false , true ] {
9142
if drop_cache {
9143
std:: fs:: remove_dir_all( root.join ( " .oxy/cache" ) ) .unwrap ( ) ;
9144
}
9145
let repo = Repo:: open( & root) .unwrap ( ) ;
9146
let log: Vec< String> = repo
9147
.op_log ( )
9148
.unwrap ( )
9149
.into_iter ( )
9150
.map ( | ( _, op ) | op.command )
9151
.collect ( ) ;
9152
assert! ( log.len ( ) > = 3 , " {log:?}" ) ;
9153
assert_eq! ( repo.store ( ) .get_change ( & h1) .unwrap ( ) .message, " First" ) ;
9154
let heads = repo
9155
.store ( )
9156
.get_state ( & repo.view ( ) .unwrap ( ) .bookmarks[ " main" ] )
9157
.unwrap ( )
9158
.heads;
9159
assert_eq! ( heads, vec! [ h2] ) ;
9160
assert_eq! (
9161
repo.load_state ( & heads) .unwrap ( ) .materialize ( ) .files.len ( ) ,
9162
1
9163
) ;
9164
}
9165
9166
9167
let err = Repo:: init_on( & dir.path ( ) .join ( " other" ) , [ 8 ; 16 ] , Some ( & url) )
9168
.err ( )
9169
.unwrap ( )
9170
.to_string ( ) ;
9171
assert! (
9172
err.contains ( " already holds a repository's operation log" ) ,
9173
" {err}"
9174
) ;
9175
let err = Repo:: init_on( & dir.path ( ) .join ( " third" ) , [ 9 ; 16 ] , Some ( " nope://x" ) )
9176
.err ( )
9177
.unwrap ( )
9178
.to_string ( ) ;
9179
assert! ( err.contains ( " cannot open that kind of store" ) , " {err}" ) ;
9180
}
80677b7a Andy McCall 1 month ago ↶
9181
9182
9183
9184
9185
9186
# [ test ]
9187
fn the_quick_dirty_check_compares_a_subrepo_by_its_pin ( ) {
9188
hermetic ( ) ;
9189
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9190
let root = dir.path ( ) ;
9191
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9192
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9193
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9194
.unwrap ( )
9195
.with_home ( root.join ( " home" ) ) ;
9196
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9197
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9198
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9199
let app_dir = root.join ( " app" ) ;
9200
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9201
write ( & app_dir, " main.txt" , b " app\n " ) ;
9202
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9203
app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
9204
9205
std:: thread:: sleep( std:: time:: Duration:: from_millis( 2100 ) ) ;
9206
let reopen = | | Repo:: open( & app_dir) .unwrap ( ) ;
9207
assert_eq! (
9208
reopen ( ) .quick_dirty ( ) .unwrap ( ) ,
9209
Some ( false ) ,
9210
" clean, as status says"
9211
) ;
9212
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) ) .unwrap ( ) ;
9213
write ( & app_dir.join ( " libs/lib" ) , " lib.txt" , b " v2\n " ) ;
9214
nested.commit ( " lib v2" , & [ ] ) .unwrap ( ) ;
9215
assert_eq! (
9216
reopen ( ) .quick_dirty ( ) .unwrap ( ) ,
9217
Some ( true ) ,
9218
" the subrepo moved"
9219
) ;
9220
reopen ( ) .commit ( " pin lib v2" , & [ ] ) .unwrap ( ) ;
9221
std:: thread:: sleep( std:: time:: Duration:: from_millis( 2100 ) ) ;
9222
assert_eq! (
9223
reopen ( ) .quick_dirty ( ) .unwrap ( ) ,
9224
Some ( false ) ,
9225
" the new pin is committed"
9226
) ;
9227
}
7993d465 Andy McCall 1 month ago ↶
9228
9229
# [ test ]
9230
fn a_module_entry_that_was_never_added_is_reported_as_unpinned ( ) {
9231
9232
hermetic ( ) ;
9233
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9234
let root = dir.path ( ) ;
9235
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9236
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9237
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9238
.unwrap ( )
9239
.with_home ( root.join ( " home" ) ) ;
9240
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9241
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9242
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9243
let app_dir = root.join ( " app" ) ;
9244
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9245
write (
9246
& app_dir,
9247
" .oxymodules" ,
9248
format! ( " [libs/lib]\n url = {lib_url} \n " ) .as_bytes ( ) ,
9249
) ;
9250
let unpinned = app.unpinned_modules ( ) .unwrap ( ) ;
9251
assert_eq! ( unpinned.len ( ) , 1 ) ;
9252
assert_eq! ( unpinned[ 0 ] .0 , " libs/lib" ) ;
9253
assert_eq! ( unpinned[ 0 ] .1. url, lib_url) ;
9254
assert! ( app.submodule_update ( true , & [ ] ) .unwrap ( ) .is_empty ( ) ) ;
9255
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9256
assert! ( app.unpinned_modules ( ) .unwrap ( ) .is_empty ( ) , " added now" ) ;
9257
}
bf3a6781 Andy McCall 1 month ago ↶
9258
9259
# [ test ]
9260
fn the_bookmark_revset_function_names_only_bookmarks ( ) {
9261
9262
hermetic ( ) ;
9263
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9264
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9265
write ( dir.path ( ) , " a.txt" , b " a\n " ) ;
9266
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
9267
write ( dir.path ( ) , " b.txt" , b " b\n " ) ;
9268
let b = repo.commit ( " B" , & [ ] ) .unwrap ( ) ;
9269
let mut heads = repo.revset ( " bookmark(main)" ) .unwrap ( ) .hashes;
9270
heads.sort ( ) ;
9271
let mut want = repo.bookmark_heads ( ) .unwrap ( ) ;
9272
want.sort ( ) ;
9273
assert_eq! ( heads, want) ;
9274
assert! ( heads.contains ( & b) ) ;
9275
assert_eq! ( repo.revset ( " main" ) .unwrap ( ) .hashes.len ( ) , heads.len ( ) ) ;
9276
9277
let prefix = repo.store ( ) .get_change_meta ( & a) .unwrap ( ) .change_id.short ( 8 ) ;
9278
assert! ( repo.revset ( & prefix) .is_ok ( ) ) ;
9279
let e = repo
9280
.revset ( & format! ( " bookmark({prefix} )" ) )
9281
.unwrap_err ( )
9282
.to_string ( ) ;
9283
assert! ( e.contains ( " no such bookmark" ) , " {e}" ) ;
9284
}
72484195 Andy McCall 1 month ago ↶
9285
157d02e9 Andy McCall 12 days ago ↶
9286
9287
9288
72484195 Andy McCall 1 month ago ↶
9289
# [ test ]
157d02e9 Andy McCall 12 days ago ↶
9290
fn a_replace_conflict_is_settled_from_the_working_copy ( ) {
9291
hermetic ( ) ;
9292
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9293
let root = dir.path ( ) ;
9294
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9295
let blob = | n : u8 | {
9296
let mut v = vec! [ 0 u8 ; 64 ] ;
9297
v[ 1 ] = n;
9298
v
9299
} ;
9300
write ( root, " img.bin" , & blob ( 1 ) ) ;
9301
write ( root, " a.txt" , b " a\n " ) ;
9302
repo.commit ( " add img" , & [ ] ) .unwrap ( ) ;
9303
repo.bookmark_create ( " other" , None ) .unwrap ( ) ;
9304
write ( root, " img.bin" , & blob ( 2 ) ) ;
9305
repo.commit ( " img two" , & [ ] ) .unwrap ( ) ;
9306
repo.bookmark_switch ( " other" , false ) .unwrap ( ) ;
9307
write ( root, " img.bin" , & blob ( 3 ) ) ;
9308
repo.commit ( " img three" , & [ ] ) .unwrap ( ) ;
9309
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
9310
repo.merge ( " other" ) .unwrap ( ) ;
9311
9312
9313
write ( root, " img.bin" , & blob ( 9 ) ) ;
9314
write ( root, " a.txt" , b " a\n more\n " ) ;
9315
assert! (
9316
repo.commit ( " edit a" , & [ ] ) .is_err ( ) ,
9317
" refused while conflicted"
9318
) ;
9319
let snap = repo.snapshot ( " oxy resolve" ) .unwrap ( ) ;
9320
assert! (
9321
repo.choose_resolutions ( & snap.base_snapshot, & [ " a.txt" .to_string ( ) ] )
9322
.is_err ( ) ,
9323
" a.txt has no replace conflict"
9324
) ;
9325
repo.choose_resolutions ( & snap.base_snapshot, & [ " img.bin" .to_string ( ) ] )
9326
.unwrap ( ) ;
9327
let snap = repo.snapshot ( " oxy resolve" ) .unwrap ( ) ;
9328
assert! ( snap
9329
.statuses
9330
.iter ( )
9331
.any ( | s | s.path = = " img.bin" & & s.status = = ChangeStatus:: Resolved ) ) ;
9332
let c = repo.commit ( " settle img" , & [ ] ) .unwrap ( ) ;
9333
assert_eq! (
9334
repo.store ( ) .get_change ( & c) .unwrap ( ) .kind,
9335
ChangeKind:: Resolution
9336
) ;
9337
let after = repo
9338
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
9339
.unwrap ( )
9340
.materialize ( ) ;
9341
assert! ( ! after.is_conflicted ( ) ) ;
9342
assert_eq! (
9343
after.files[ " img.bin" ] .content,
9344
FileContent:: Opaque {
9345
blobs: vec! [ Repo:: blob_ref( & blob ( 9 ) ) ]
9346
}
9347
) ;
9348
assert! (
9349
matches! ( & after.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n more\n " )
9350
) ;
9351
9352
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
9353
assert! ( snap.statuses.is_empty ( ) , " {:?}" , snap.statuses) ;
9354
assert! ( repo.resolutions ( ) .unwrap ( ) .is_empty ( ) ) ;
9355
}
9356
9357
9358
# [ test ]
9359
fn a_pin_conflict_is_settled_from_the_working_copy ( ) {
9360
hermetic ( ) ;
9361
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9362
let root = dir.path ( ) ;
9363
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9364
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9365
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9366
.unwrap ( )
9367
.with_home ( root.join ( " home" ) ) ;
9368
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9369
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9370
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9371
let app_dir = root.join ( " app" ) ;
9372
let mut app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9373
write ( & app_dir, " main.txt" , b " app\n " ) ;
9374
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9375
app.commit ( " pin v1" , & [ ] ) .unwrap ( ) ;
9376
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) )
9377
.unwrap ( )
9378
.with_home ( root.join ( " home" ) ) ;
9379
fn pin ( app : & Repo, nested : & Repo, text : & [u8 ], message : & str ) {
9380
write ( nested.root ( ) , " lib.txt" , text) ;
9381
nested.commit ( message, & [ ] ) .unwrap ( ) ;
9382
app.commit ( & format! ( " pin {message} " ) , & [ ] ) .unwrap ( ) ;
9383
}
9384
app.bookmark_create ( " other" , None ) .unwrap ( ) ;
9385
pin ( & app, & nested, b " v2\n " , " v2" ) ;
9386
app.bookmark_switch ( " other" , false ) .unwrap ( ) ;
9387
pin ( & app, & nested, b " v3\n " , " v3" ) ;
9388
app.bookmark_switch ( " main" , false ) .unwrap ( ) ;
9389
app.merge ( " other" ) .unwrap ( ) ;
9390
let merged = app
9391
.load_state ( & app.bookmark_heads ( ) .unwrap ( ) )
9392
.unwrap ( )
9393
.materialize ( ) ;
9394
assert! (
9395
matches! ( & merged.files[ " libs/lib" ] .content, FileContent:: Opaque { blobs } if blobs.len ( ) = = 2 ) ,
9396
" a pin conflict"
9397
) ;
9398
9399
write ( & app_dir.join ( " libs/lib" ) , " lib.txt" , b " v4\n " ) ;
9400
nested.commit ( " v4" , & [ ] ) .unwrap ( ) ;
9401
let snap = app.snapshot ( " oxy resolve" ) .unwrap ( ) ;
9402
app.choose_resolutions ( & snap.base_snapshot, & [ " libs/lib" .to_string ( ) ] )
9403
.unwrap ( ) ;
9404
let c = app.commit ( " settle the pin" , & [ ] ) .unwrap ( ) ;
9405
assert_eq! (
9406
app.store ( ) .get_change ( & c) .unwrap ( ) .kind,
9407
ChangeKind:: Resolution
9408
) ;
9409
let after = app
9410
.load_state ( & app.bookmark_heads ( ) .unwrap ( ) )
9411
.unwrap ( )
9412
.materialize ( ) ;
9413
assert! ( ! after.is_conflicted ( ) ) ;
9414
assert! ( app.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
9415
}
9416
9417
# [ test ]
72484195 Andy McCall 1 month ago ↶
9418
fn carrying_work_over_a_subrepo_already_at_the_target_pin_is_no_conflict ( ) {
9419
9420
9421
hermetic ( ) ;
9422
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9423
let root = dir.path ( ) ;
9424
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9425
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9426
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9427
.unwrap ( )
9428
.with_home ( root.join ( " home" ) ) ;
9429
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9430
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9431
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9432
let app_dir = root.join ( " app" ) ;
9433
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9434
write ( & app_dir, " main.txt" , b " app\n " ) ;
9435
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9436
9437
fs:: rename( app_dir.join ( " libs/lib" ) , root.join ( " aside" ) ) .unwrap ( ) ;
9438
app.commit ( " initial" , & [ ] ) .unwrap ( ) ;
9439
let stale = app.bookmark_heads ( ) .unwrap ( ) ;
9440
fs:: rename( root.join ( " aside" ) , app_dir.join ( " libs/lib" ) ) .unwrap ( ) ;
9441
app.commit ( " pin lib" , & [ ] ) .unwrap ( ) ;
9442
let newer = app.bookmark_heads ( ) .unwrap ( ) ;
9443
app.checkout ( & stale, false , " test" ) .unwrap ( ) ;
9444
write ( & app_dir, " notes.txt" , b " mine\n " ) ;
9445
let ( _ , carried) = app.checkout_carry ( & newer, " main" , " test" ) .unwrap ( ) ;
9446
assert_eq! ( carried, vec! [ " notes.txt" .to_string ( ) ] ) ;
9447
assert! ( app_dir.join ( " notes.txt" ) .exists ( ) ) ;
9448
}
9449
c8d9059e Andy McCall 12 days ago ↶
9450
9451
72484195 Andy McCall 1 month ago ↶
9452
# [ test ]
c8d9059e Andy McCall 12 days ago ↶
9453
fn a_new_bookmark_sends_only_what_the_remote_lacks ( ) {
9454
hermetic ( ) ;
9455
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9456
let root = dir.path ( ) ;
9457
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
9458
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
9459
let mut one = Repo:: clone( & url, & root.join ( " one" ) )
9460
.unwrap ( )
9461
.with_home ( root.join ( " home" ) ) ;
9462
for n in 0 .. 5 {
9463
write ( & root.join ( " one" ) , " a.txt" , format! ( " {n} \n " ) .as_bytes ( ) ) ;
9464
one.commit ( & format! ( " main {n} " ) , & [ ] ) .unwrap ( ) ;
9465
}
9466
assert! ( one.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9467
one.bookmark_create ( " feature" , None ) .unwrap ( ) ;
9468
one.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
9469
write ( & root.join ( " one" ) , " b.txt" , b " feature\n " ) ;
9470
let added = one.commit ( " feature" , & [ ] ) .unwrap ( ) ;
9471
let heads = one.bookmark_heads ( ) .unwrap ( ) ;
9472
let refs = one
9473
.open_named_remote ( " origin" )
9474
.unwrap ( )
9475
.refs ( )
9476
.unwrap ( )
9477
.refs;
9478
assert! (
9479
refs.iter ( ) .all ( | r | r.name ! = " feature" ) ,
9480
" new to the remote"
9481
) ;
9482
let sent = one
9483
.negotiate ( & one.remote_holds ( & refs, & [ ] ) , & heads)
9484
.unwrap ( ) ;
9485
assert_eq! ( sent, vec! [ added] , " only the change main lacks" ) ;
9486
assert! ( one.push_bookmark ( " origin" , " feature" , false ) .unwrap ( ) [ 0 ] .ok) ;
9487
let two = Repo:: clone( & url, & root.join ( " two" ) ) .unwrap ( ) ;
9488
assert! ( two.store ( ) .has ( & added) ) ;
9489
}
9490
9491
# [ test ]
72484195 Andy McCall 1 month ago ↶
9492
fn a_bookmark_only_behind_its_remote_catches_up ( ) {
9493
9494
hermetic ( ) ;
9495
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9496
let root = dir.path ( ) ;
9497
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
9498
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
9499
let one = Repo:: clone( & url, & root.join ( " one" ) )
9500
.unwrap ( )
9501
.with_home ( root.join ( " home" ) ) ;
9502
write ( & root.join ( " one" ) , " a.txt" , b " a\n " ) ;
9503
one.commit ( " A" , & [ ] ) .unwrap ( ) ;
9504
assert! ( one.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9505
let two = Repo:: clone( & url, & root.join ( " two" ) )
9506
.unwrap ( )
9507
.with_home ( root.join ( " home" ) ) ;
9508
write ( & root.join ( " two" ) , " b.txt" , b " b\n " ) ;
9509
two.commit ( " B" , & [ ] ) .unwrap ( ) ;
9510
assert! ( two.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9511
assert_eq! ( one.catch_up_bookmark ( " main" ) .unwrap ( ) , None , " not fetched" ) ;
9512
one.fetch ( " origin" ) .unwrap ( ) ;
9513
assert_eq! ( one.catch_up_bookmark ( " main" ) .unwrap ( ) , Some ( 1 ) ) ;
9514
assert_eq! ( one.bookmarks ( ) .unwrap ( ) [ 0 ] .behind, Some ( 0 ) ) ;
9515
9516
write ( & root.join ( " one" ) , " c.txt" , b " c\n " ) ;
9517
one.commit ( " C" , & [ ] ) .unwrap ( ) ;
9518
write ( & root.join ( " two" ) , " d.txt" , b " d\n " ) ;
9519
two.commit ( " D" , & [ ] ) .unwrap ( ) ;
9520
assert! ( two.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9521
one.fetch ( " origin" ) .unwrap ( ) ;
9522
assert_eq! ( one.catch_up_bookmark ( " main" ) .unwrap ( ) , None ) ;
9523
}
42ccf504 Andy McCall 29 days ago ↶
9524
9525
# [ test ]
9526
fn a_bookmark_is_compared_with_a_remote_that_has_it_when_there_is_no_origin ( ) {
9527
9528
hermetic ( ) ;
9529
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9530
let root = dir.path ( ) ;
9531
let _up = Repo:: init( & root.join ( " up" ) ) .unwrap ( ) ;
9532
let url = root.join ( " up" ) .to_string_lossy ( ) .to_string ( ) ;
9533
let other = Repo:: clone( & url, & root.join ( " other" ) )
9534
.unwrap ( )
9535
.with_home ( root.join ( " home" ) ) ;
9536
write ( & root.join ( " other" ) , " a.txt" , b " a\n " ) ;
9537
other.commit ( " a" , & [ ] ) .unwrap ( ) ;
9538
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9539
let mut one = Repo:: clone( & url, & root.join ( " one" ) )
9540
.unwrap ( )
9541
.with_home ( root.join ( " home" ) ) ;
9542
one.config.set ( " remote.earth.url" , & url) .unwrap ( ) ;
9543
one.config.set ( " remote.gcs.url" , & url) .unwrap ( ) ;
9544
one.config.set ( " remote.origin.url" , " " ) .unwrap ( ) ;
9545
one.save_config ( ) .unwrap ( ) ;
9546
write ( & root.join ( " other" ) , " b.txt" , b " b\n " ) ;
9547
other.commit ( " b" , & [ ] ) .unwrap ( ) ;
9548
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9549
one.fetch ( " earth" ) .unwrap ( ) ;
9550
let b = one
9551
.bookmarks ( )
9552
.unwrap ( )
9553
.into_iter ( )
9554
.find ( | b | b.name = = " main" )
9555
.unwrap ( ) ;
9556
assert_eq! (
9557
( b.remote.as_deref ( ) , b.behind) ,
9558
( Some ( " earth" ) , Some ( 1 ) ) ,
9559
" a stale origin copy is not used"
9560
) ;
9561
assert_eq! (
9562
one.default_remote ( ) ,
9563
" origin" ,
9564
" no default chosen among several"
9565
) ;
9566
let e = one.pull ( & one.default_remote ( ) ) .unwrap_err ( ) .to_string ( ) ;
9567
assert! ( e.contains ( " earth, gcs" ) & & e.contains ( " core.remote" ) , " {e}" ) ;
9568
one.config.set ( " core.remote" , " earth" ) .unwrap ( ) ;
9569
assert_eq! ( one.default_remote ( ) , " earth" ) ;
9570
}
cbe8499a Andy McCall 29 days ago ↶
9571
9572
# [ test ]
9573
fn a_missing_author_identity_says_how_to_fix_it ( ) {
9574
9575
9576
hermetic ( ) ;
9577
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9578
let mut repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9579
let missing = KeyId:: from_hex( & " ab" .repeat ( 32 ) ) .unwrap ( ) ;
9580
repo.config.set ( " author.key" , & missing.to_hex ( ) ) .unwrap ( ) ;
9581
let e = repo.identity ( & missing) .unwrap_err ( ) .to_string ( ) ;
9582
assert! (
9583
e.contains ( " this repository has no identity" ) & & e.contains ( " oxy config author.key" ) ,
9584
" {e}"
9585
) ;
9586
assert! ( e.contains ( " this repository's config" ) , " {e}" ) ;
9587
}
f6dccc45 Andy McCall 28 days ago ↶
9588
9589
# [ test ]
9590
fn a_working_copy_behind_its_bookmark_is_flagged_and_pull_brings_it_up ( ) {
9591
9592
9593
9594
hermetic ( ) ;
9595
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9596
let root = dir.path ( ) ;
9597
let _up = Repo:: init( & root.join ( " up" ) ) .unwrap ( ) ;
9598
let url = root.join ( " up" ) .to_string_lossy ( ) .to_string ( ) ;
9599
let other = Repo:: clone( & url, & root.join ( " other" ) )
9600
.unwrap ( )
9601
.with_home ( root.join ( " home" ) ) ;
9602
write ( & root.join ( " other" ) , " a.txt" , b " a\n " ) ;
9603
other.commit ( " a" , & [ ] ) .unwrap ( ) ;
9604
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9605
let one = Repo:: clone( & url, & root.join ( " one" ) )
9606
.unwrap ( )
9607
.with_home ( root.join ( " home" ) ) ;
9608
write ( & root.join ( " other" ) , " b.txt" , b " b\n " ) ;
9609
other.commit ( " b" , & [ ] ) .unwrap ( ) ;
9610
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9611
one.fetch ( " origin" ) .unwrap ( ) ;
9612
9613
let theirs = one.resolve_heads ( " origin/main" ) .unwrap ( ) ;
9614
one.bookmark_set ( " main" , & theirs) .unwrap ( ) ;
9615
assert! ( ! root.join ( " one/b.txt" ) .exists ( ) ) ;
9616
assert_eq! ( one.working_copy_behind ( ) .unwrap ( ) , Some ( 1 ) ) ;
9617
let r = one.pull ( " origin" ) .unwrap ( ) ;
9618
assert! (
9619
! r.changed & & r.checked_out,
9620
" nothing new, but the working copy caught up"
9621
) ;
9622
assert! ( root.join ( " one/b.txt" ) .exists ( ) ) ;
9623
assert_eq! ( one.working_copy_behind ( ) .unwrap ( ) , None ) ;
9624
}
170bb436 Andy McCall 28 days ago ↶
9625
9626
# [ test ]
9627
fn touched_nodes_cache_answers_as_the_changes_do ( ) {
9628
hermetic ( ) ;
9629
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9630
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9631
write ( dir.path ( ) , " a.txt" , b " one\n " ) ;
9632
write ( dir.path ( ) , " b.txt" , b " x\n " ) ;
9633
repo.snapshot ( " t" ) .unwrap ( ) ;
9634
let c1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
9635
write ( dir.path ( ) , " b.txt" , b " y\n " ) ;
9636
repo.snapshot ( " t" ) .unwrap ( ) ;
9637
repo.commit ( " b only" , & [ ] ) .unwrap ( ) ;
9638
fs:: rename( dir.path ( ) .join ( " a.txt" ) , dir.path ( ) .join ( " c.txt" ) ) .unwrap ( ) ;
9639
write ( dir.path ( ) , " c.txt" , b " one\n two\n " ) ;
9640
repo.snapshot ( " t" ) .unwrap ( ) ;
9641
let c3 = repo.commit ( " rename and edit" , & [ ] ) .unwrap ( ) ;
9642
9643
write ( dir.path ( ) , " c.txt" , b " one\n two\n three\n " ) ;
9644
repo.snapshot ( " t" ) .unwrap ( ) ;
9645
9646
let path = dir.path ( ) .join ( " .oxy/index/touched" ) ;
9647
let hashes = | es : Vec< LogEntry> | es.iter ( ) .map ( | e | e.hash ) .collect:: < Vec< _ > > ( ) ;
9648
let cold = hashes ( repo.log_for_path ( " c.txt" ) .unwrap ( ) ) ;
9649
assert! ( path.exists ( ) , " the first query writes the cache" ) ;
9650
let warm = hashes ( repo.log_for_path ( " c.txt" ) .unwrap ( ) ) ;
9651
assert_eq! ( cold, warm, " the cache answers as the changes do" ) ;
9652
let ( wc, _ ) = repo.working_copy ( ) .unwrap ( ) ;
9653
assert_eq! (
9654
cold,
9655
vec! [ wc, c3, c1] ,
9656
" renames followed, the working copy included"
9657
) ;
9658
9659
9660
let cached = super :: touched:: read_touched_for_test( & fs:: read( & path) .unwrap ( ) ) .unwrap ( ) ;
9661
assert! ( ! cached.contains ( & wc) , " a working-copy change is not cached" ) ;
9662
assert! ( cached.contains ( & c1) & & cached.contains ( & c3) ) ;
9663
9664
9665
let mut bytes = fs:: read( & path) .unwrap ( ) ;
9666
let last = bytes.len ( ) - 40 ;
9667
bytes[ last] ^ = 1 ;
9668
fs:: write( & path, & bytes) .unwrap ( ) ;
9669
assert_eq! ( hashes ( repo.log_for_path ( " c.txt" ) .unwrap ( ) ) , cold) ;
9670
let touches = repo.log_revset ( " touches(\" c.txt\" ) ~ @" ) .unwrap ( ) ;
9671
assert_eq! ( hashes ( touches) , vec! [ c3, c1] ) ;
9672
}
e8e6e0ca Andy McCall 28 days ago ↶
9673
9674
9675
9676
9677
9678
# [ test ]
9679
fn git_import_removes_every_copy_of_a_file_at_one_place ( ) {
9680
hermetic ( ) ;
9681
if std:: process:: Command:: new( " git" )
9682
.arg ( " --version" )
9683
.output ( )
9684
.is_err ( )
9685
{
9686
eprintln! ( " git not installed; skipping" ) ;
9687
return ;
9688
}
9689
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9690
let root = dir.path ( ) ;
9691
let g = root.join ( " src.git" ) ;
9692
fs:: create_dir_all( & g) .unwrap ( ) ;
9693
let git = | args : & [& str ]| {
9694
let out = std:: process:: Command:: new( " git" )
9695
.arg ( " -C" )
9696
.arg ( & g)
9697
.args ( [
9698
" -c" ,
9699
" user.name=Grace" ,
9700
" -c" ,
9701
" [email protected] " ,
9702
" -c" ,
9703
" commit.gpgsign=false" ,
9704
] )
9705
.args ( args)
9706
.output ( )
9707
.unwrap ( ) ;
9708
assert! (
9709
out.status.success ( ) ,
9710
" git {args:?}: {}" ,
9711
String :: from_utf8_lossy( & out.stderr)
9712
) ;
9713
} ;
9714
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
9715
write ( & g, " d/list.txt" , b " one\n two\n three\n four\n " ) ;
9716
write ( & g, " keep.txt" , b " k\n " ) ;
9717
git ( & [ " add" , " ." ] ) ;
9718
git ( & [ " commit" , " -qm" , " base" ] ) ;
9719
fs:: create_dir_all( g.join ( " d/sub" ) ) .unwrap ( ) ;
9720
git ( & [ " mv" , " d/list.txt" , " d/sub/list.txt" ] ) ;
9721
git ( & [ " commit" , " -qm" , " into sub" ] ) ;
9722
git ( & [ " checkout" , " -qb" , " side" ] ) ;
9723
git ( & [ " mv" , " d/sub/list.txt" , " d/list.txt" ] ) ;
9724
git ( & [ " commit" , " -qm" , " back, on side" ] ) ;
9725
git ( & [ " checkout" , " -q" , " main" ] ) ;
9726
git ( & [ " mv" , " d/sub/list.txt" , " d/list.txt" ] ) ;
9727
git ( & [ " commit" , " -qm" , " back, on main" ] ) ;
9728
git ( & [ " merge" , " -q" , " --no-edit" , " side" ] ) ;
9729
git ( & [ " rm" , " -rq" , " d" ] ) ;
9730
git ( & [ " commit" , " -qm" , " d goes" ] ) ;
9731
9732
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
9733
repo.import_git ( & g, " main" ) .unwrap ( ) ;
9734
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
9735
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
9736
let st = repo
9737
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
9738
.unwrap ( ) ;
9739
let view = st.tree ( ) .view ( ) ;
9740
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
9741
assert_eq! ( view.entries.keys ( ) .collect:: < Vec< _ > > ( ) , vec! [ " keep.txt" ] ) ;
9742
}
5552e69c Andy McCall 28 days ago ↶
9743
9744
9745
9746
9747
# [ test ]
9748
fn a_damaged_file_history_in_the_state_index_is_rebuilt ( ) {
9749
hermetic ( ) ;
9750
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9751
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9752
for i in 0 .. 6 {
9753
write (
9754
dir.path ( ) ,
9755
& format! ( " f{i} .txt" ) ,
9756
format! ( " one {i} \n " ) .as_bytes ( ) ,
9757
) ;
9758
}
9759
repo.snapshot ( " t" ) .unwrap ( ) ;
9760
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
9761
for i in 0 .. 6 {
9762
write (
9763
dir.path ( ) ,
9764
& format! ( " f{i} .txt" ) ,
9765
format! ( " one {i} \n two\n " ) .as_bytes ( ) ,
9766
) ;
9767
}
9768
repo.snapshot ( " t" ) .unwrap ( ) ;
9769
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
9770
let want: Vec< Vec< u8 > > = ( 0 .. 6 )
9771
.map ( | i | format! ( " one {i} \n two\n " ) .into_bytes ( ) )
9772
.collect ( ) ;
9773
9774
9775
let ( _ , wc) = repo.working_copy ( ) .unwrap ( ) ;
9776
let base = repo.load_state_history ( & wc.deps) .unwrap ( ) ;
9777
repo.write_state_index ( & wc.deps, & base) ;
9778
for ( i, w) in want.iter ( ) .enumerate ( ) {
9779
assert_eq! ( & repo.file_at ( & format! ( " f{i} .txt" ) , None ) .unwrap ( ) , w) ;
9780
}
9781
let index = repo.state_indexes ( ) ;
9782
assert! ( ! index.is_empty ( ) , " a state index was written" ) ;
9783
9784
let mut bytes = fs:: read( & index[ 0 ] ) .unwrap ( ) ;
9785
let last = bytes.len ( ) - 1 ;
9786
bytes[ last] ^ = 0x40 ;
9787
fs:: write( & index[ 0 ] , & bytes) .unwrap ( ) ;
9788
9789
let reads: Vec< _ > = ( 0 .. 6 )
9790
.map ( | i | repo.file_at ( & format! ( " f{i} .txt" ) , None ) )
9791
.collect ( ) ;
9792
let damaged: Vec< String> = reads
9793
.iter ( )
9794
.filter_map ( | r | r.as_ref ( ) .err ( ) .map ( | e | e.to_string ( ) ) )
9795
.collect ( ) ;
9796
assert_eq! ( damaged.len ( ) , 1 , " {damaged:?}" ) ;
9797
assert! (
9798
damaged[ 0 ] .contains ( " state index was damaged" )
9799
& & damaged[ 0 ] .contains ( " run the command again" ) ,
9800
" {}" ,
9801
damaged[ 0 ]
9802
) ;
9803
9804
for ( i, w) in want.iter ( ) .enumerate ( ) {
9805
assert_eq! ( & repo.file_at ( & format! ( " f{i} .txt" ) , None ) .unwrap ( ) , w) ;
9806
}
9807
}
cb8b8082 Andy McCall 28 days ago ↶
9808
9809
9810
9811
9812
9813
# [ test ]
9814
fn a_long_push_goes_in_parts ( ) {
9815
hermetic ( ) ;
9816
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9817
let root = dir.path ( ) ;
9818
let origin = root.join ( " origin" ) ;
9819
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
9820
let main = root.join ( " main" ) ;
9821
let mut repo = Repo:: init( & main) .unwrap ( ) ;
9822
repo.config
9823
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
9824
.unwrap ( ) ;
9825
9826
repo.config.set ( " push.batch-bytes" , " 1" ) .unwrap ( ) ;
9827
repo.save_config ( ) .unwrap ( ) ;
9828
for i in 0 .. 6 {
9829
write (
9830
& main,
9831
& format! ( " f{i} .txt" ) ,
9832
format! ( " file {i} \n " ) .as_bytes ( ) ,
9833
) ;
9834
repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ;
9835
}
9836
let mut parts = Vec :: new( ) ;
9837
let results = repo
9838
.push_bookmark_with ( " origin" , " main" , false , & mut | m| parts.push ( m.to_string ( ) ) )
9839
.unwrap ( ) ;
9840
assert! (
9841
results.iter ( ) .any ( | r | r.ok & & r.name = = " main" ) ,
9842
" {results:?}"
9843
) ;
9844
assert_eq! ( parts.len ( ) , 6 , " {parts:?}" ) ;
9845
assert_eq! ( parts[ 0 ] , " part 1 of 6: 1 changes" ) ;
9846
9847
let remote = Repo:: open( & origin) .unwrap ( ) ;
9848
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
9849
assert_eq! ( remote.bookmark_heads ( ) .unwrap ( ) , heads) ;
9850
assert_eq! (
9851
remote.log_state ( & heads) .unwrap ( ) .len ( ) ,
9852
repo.log_state ( & heads) .unwrap ( ) .len ( )
9853
) ;
9854
9855
let got = remote
9856
.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ ] )
9857
.unwrap ( ) ;
9858
assert_eq! ( got.len ( ) , 1 ) ;
9859
assert! ( got[ 0 ] .ok & & got[ 0 ] .name = = OBJECTS_RESULT , " {got:?}" ) ;
9860
}
d6e0d5b8 Andy McCall 27 days ago ↶
9861
d3b27090 Andy McCall 13 days ago ↶
9862
9863
9864
9865
9866
# [ test ]
9867
fn a_heavy_change_sends_its_blobs_in_parts ( ) {
9868
hermetic ( ) ;
9869
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9870
let root = dir.path ( ) ;
9871
let origin = root.join ( " origin" ) ;
9872
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
9873
let main = root.join ( " main" ) ;
9874
let mut repo = Repo:: init( & main) .unwrap ( ) ;
9875
repo.config
9876
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
9877
.unwrap ( ) ;
9878
repo.config.set ( " push.batch-bytes" , " 50000" ) .unwrap ( ) ;
9879
repo.save_config ( ) .unwrap ( ) ;
9880
let mut x: u64 = 0x9e3779b97f4a7c15 ;
9881
let mut noise = | n : usize | -> Vec< u8 > {
9882
( 0 .. n)
9883
.map ( | _| {
9884
x ^ = x < < 13 ;
9885
x ^ = x > > 7 ;
9886
x ^ = x < < 17 ;
9887
( x > > 24 ) as u8
9888
} )
9889
.collect ( )
9890
} ;
9891
let files: Vec< Vec< u8 > > = ( 0 .. 4 ) .map ( | _| noise ( 40_000 ) ) .collect ( ) ;
9892
for ( i, f) in files.iter ( ) .enumerate ( ) {
9893
write ( & main, & format! ( " bin{i} .dat" ) , f) ;
9894
}
9895
repo.commit ( " four blobs" , & [ ] ) .unwrap ( ) ;
9896
let mut parts = Vec :: new( ) ;
9897
let results = repo
9898
.push_bookmark_with ( " origin" , " main" , false , & mut | m| parts.push ( m.to_string ( ) ) )
9899
.unwrap ( ) ;
9900
assert! (
9901
results.iter ( ) .any ( | r | r.ok & & r.name = = " main" ) ,
9902
" {results:?}"
9903
) ;
9904
assert! (
9905
parts.len ( ) > = 3 & & parts[ 0 ] .starts_with ( " objects 1 of" ) ,
9906
" {parts:?}"
9907
) ;
9908
let back = Repo:: clone( & origin.to_string_lossy ( ) , & root.join ( " back" ) ) .unwrap ( ) ;
9909
for ( i, f) in files.iter ( ) .enumerate ( ) {
9910
assert_eq! ( & fs:: read( root.join ( format! ( " back/bin{i} .dat" ) ) ) .unwrap ( ) , f) ;
9911
}
9912
drop ( back) ;
9913
}
9914
570e3531 Andy McCall 12 days ago ↶
9915
9916
9917
9918
9919
# [ test ]
9920
fn a_replacement_does_not_send_the_old_content_again ( ) {
9921
hermetic ( ) ;
9922
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9923
let root = dir.path ( ) ;
9924
let origin = root.join ( " origin" ) ;
9925
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
9926
let main = root.join ( " main" ) ;
9927
let mut repo = Repo:: init( & main) .unwrap ( ) ;
9928
repo.config
9929
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
9930
.unwrap ( ) ;
9931
repo.save_config ( ) .unwrap ( ) ;
9932
let blobs = | b : & Bundle| {
9933
b.objects
9934
.iter ( )
9935
.filter ( | ( ty , _) | * ty = = ObjectType:: Blob)
9936
.count ( )
9937
} ;
9938
write ( & main, " bin.dat" , b " \0 \x01 first binary" ) ;
9939
write ( & main, " gone.dat" , b " \0 \x02 to be removed" ) ;
9940
let added = repo.commit ( " add" , & [ ] ) .unwrap ( ) ;
9941
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9942
write ( & main, " bin.dat" , b " \0 \x01 second binary" ) ;
9943
fs:: remove_file( main.join ( " gone.dat" ) ) .unwrap ( ) ;
9944
let changed = repo.commit ( " replace and remove" , & [ ] ) .unwrap ( ) ;
9945
assert_eq! ( blobs ( & repo.bundle_for ( & [ changed] ) .unwrap ( ) ) , 1 ) ;
9946
assert_eq! ( blobs ( & repo.bundle_for ( & [ added, changed] ) .unwrap ( ) ) , 3 ) ;
9947
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9948
let origin = Repo:: open( & origin) .unwrap ( ) ;
9949
assert_eq! ( origin.fsck ( ) .unwrap ( ) .problems, vec! [ ] ) ;
9950
let back = Repo:: clone( & origin.root ( ) .to_string_lossy ( ) , & root.join ( " back" ) ) .unwrap ( ) ;
9951
assert_eq! ( back.fsck ( ) .unwrap ( ) .problems, vec! [ ] ) ;
9952
assert_eq! (
9953
fs:: read( root.join ( " back/bin.dat" ) ) .unwrap ( ) ,
9954
b " \0 \x01 second binary"
9955
) ;
9956
assert! ( ! root.join ( " back/gone.dat" ) .exists ( ) ) ;
9957
}
9958
d6e0d5b8 Andy McCall 27 days ago ↶
9959
9960
9961
9962
# [ test ]
9963
fn git_import_claims_your_own_commits ( ) {
9964
hermetic ( ) ;
9965
if std:: process:: Command:: new( " git" )
9966
.arg ( " --version" )
9967
.output ( )
9968
.is_err ( )
9969
{
9970
eprintln! ( " git not installed; skipping" ) ;
9971
return ;
9972
}
9973
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9974
let root = dir.path ( ) ;
9975
let g = root.join ( " src.git" ) ;
9976
fs:: create_dir_all( & g) .unwrap ( ) ;
9977
let git = | who : & str , args : & [& str ]| {
9978
let ( name, email) = who.split_once ( ' ' ) .unwrap ( ) ;
9979
let out = std:: process:: Command:: new( " git" )
9980
.arg ( " -C" )
9981
.arg ( & g)
9982
.args ( [
9983
" -c" ,
9984
& format! ( " user.name={name} " ) ,
9985
" -c" ,
9986
& format! ( " user.email={email} " ) ,
9987
" -c" ,
9988
" commit.gpgsign=false" ,
9989
] )
9990
.args ( args)
9991
.output ( )
9992
.unwrap ( ) ;
9993
assert! (
9994
out.status.success ( ) ,
9995
" git {args:?}: {}" ,
9996
String :: from_utf8_lossy( & out.stderr)
9997
) ;
9998
} ;
9999
git ( " Grace [email protected] " , & [ " init" , " -q" , " -b" , " main" ] ) ;
10000
write ( & g, " a.txt" , b " a\n " ) ;
10001
git ( " Grace [email protected] " , & [ " add" , " ." ] ) ;
10002
git ( " Grace [email protected] " , & [ " commit" , " -qm" , " mine" ] ) ;
10003
write ( & g, " b.txt" , b " b\n " ) ;
10004
git ( " Ada [email protected] " , & [ " add" , " ." ] ) ;
10005
git ( " Ada [email protected] " , & [ " commit" , " -qm" , " someone else's" ] ) ;
10006
10007
let mut repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
10008
let ( key, _ ) = repo
10009
.create_identity ( " Grace" , " [email protected] " , IdentityScope:: Repo)
10010
.unwrap ( ) ;
10011
repo.config.set ( " git.claim" , " true" ) .unwrap ( ) ;
10012
repo.save_config ( ) .unwrap ( ) ;
10013
let repo = Repo:: open( & root.join ( " o" ) ) .unwrap ( ) ;
10014
repo.import_git ( & g, " main" ) .unwrap ( ) ;
10015
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
10016
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
10017
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
10018
let by_message: std:: collections:: HashMap< String, KeyId> = repo
10019
.log_state ( & heads)
10020
.unwrap ( )
10021
.into_iter ( )
10022
.map ( | e | {
10023
(
10024
e.change.message.lines ( ) .next ( ) .unwrap_or ( " " ) .to_string ( ) ,
10025
e.change.author,
10026
)
10027
} )
10028
.collect ( ) ;
10029
assert_eq! ( by_message[ " mine" ] , key, " her own commit, any case of email" ) ;
10030
assert_eq! ( by_message[ " someone else's" ] , ObjectRef:: ZERO , " not hers" ) ;
10031
}
34fad4a9 Andy McCall 27 days ago ↶
10032
10033
10034
10035
# [ test ]
10036
fn removing_a_remote_leaves_nothing_of_it ( ) {
10037
hermetic ( ) ;
10038
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10039
let origin = dir.path ( ) .join ( " origin" ) ;
10040
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
10041
let main = dir.path ( ) .join ( " main" ) ;
10042
let mut repo = Repo:: init( & main) .unwrap ( ) ;
10043
repo.config
10044
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
10045
.unwrap ( ) ;
10046
repo.config
10047
.set ( " remote.origin.token" , " secret-ish" )
10048
.unwrap ( ) ;
10049
repo.config
10050
.set ( " remote.keep.url" , & origin.to_string_lossy ( ) )
10051
.unwrap ( ) ;
10052
repo.save_config ( ) .unwrap ( ) ;
10053
write ( & main, " a.txt" , b " a\n " ) ;
10054
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10055
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10056
assert! (
10057
repo.view ( ) .unwrap ( ) .remotes.contains_key ( " origin" ) ,
10058
" a tracking bookmark"
10059
) ;
10060
10061
repo.remove_remote ( " origin" ) .unwrap ( ) ;
10062
let again = Repo:: open( & main) .unwrap ( ) ;
10063
assert! (
10064
! again
10065
.config
10066
.repo_layer ( )
10067
.keys ( )
10068
.any ( | k | k.starts_with ( " remote.origin." ) ) ,
10069
" {:?}" ,
10070
again.config.repo_layer ( )
10071
) ;
10072
assert! ( ! again.config.remotes.contains_key ( " origin" ) ) ;
10073
assert! (
10074
! again.view ( ) .unwrap ( ) .remotes.contains_key ( " origin" ) ,
10075
" its tracking bookmarks go"
10076
) ;
10077
assert! (
10078
again.config.remotes.contains_key ( " keep" ) ,
10079
" another remote stays"
10080
) ;
10081
let mut again = again;
10082
assert! (
10083
again.remove_remote ( " origin" ) .is_err ( ) ,
10084
" a second removal is refused"
10085
) ;
10086
}
9d580f21 Andy McCall 27 days ago ↶
10087
10088
10089
10090
10091
# [ test ]
10092
fn gc_keeps_the_blobs_a_host_names ( ) {
10093
hermetic ( ) ;
10094
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10095
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
10096
write ( dir.path ( ) , " a.txt" , b " a\n " ) ;
10097
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10098
10099
let body: Vec< u8 > = ( 0 .. 400_000 u32 )
10100
.flat_map ( | i | ( i.wrapping_mul ( 2654435761 ) ) .to_le_bytes ( ) )
10101
.collect ( ) ;
10102
let kept = repo.store_blob ( & body) .unwrap ( ) ;
10103
let lost = repo.store_blob ( b " an artifact nobody names" ) .unwrap ( ) ;
10104
repo.gc_keeping ( true , & [ kept] , & mut | _ | { } ) .unwrap ( ) ;
10105
assert_eq! ( repo.read_blob ( & kept) .unwrap ( ) , body, " named: kept whole" ) ;
10106
assert! ( ! repo.store ( ) .has ( & lost) , " not named: pruned" ) ;
10107
repo.gc ( true ) .unwrap ( ) ;
10108
assert! ( ! repo.store ( ) .has ( & kept) , " a plain gc prunes it" ) ;
10109
}
530a0fc6 Andy McCall 26 days ago ↶
10110
10111
10112
10113
10114
# [ test ]
10115
fn undo_is_scoped_to_its_workspace ( ) {
10116
hermetic ( ) ;
10117
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10118
let root = dir.path ( ) .join ( " main" ) ;
10119
let a = Repo:: init( & root) .unwrap ( ) ;
10120
write ( & root, " base.txt" , b " base\n " ) ;
10121
let base = a.commit ( " base" , & [ ] ) .unwrap ( ) ;
10122
a.bookmark_create ( " feature" , None ) .unwrap ( ) ;
10123
let ws_dir = dir.path ( ) .join ( " ws" ) ;
10124
let heads = a.resolve_heads ( " feature" ) .unwrap ( ) ;
10125
let b = a
10126
.workspace_add_on ( & ws_dir, Some ( & heads) , Some ( " feature" ) )
10127
.unwrap ( ) ;
10128
10129
10130
write ( & root, " a.txt" , b " a\n " ) ;
10131
let ca = a.commit ( " from a" , & [ ] ) .unwrap ( ) ;
10132
write ( & ws_dir, " b.txt" , b " b\n " ) ;
10133
let cb = b.commit ( " from b" , & [ ] ) .unwrap ( ) ;
10134
10135
10136
let u = a.undo ( ) .unwrap ( ) ;
10137
assert_eq! ( u.command, " oxy commit -m \" from a\" " ) ;
10138
assert_eq! ( u.others_kept, 1 , " B's newer commit is left" ) ;
10139
assert! ( ! u.changes.is_empty ( ) ) ;
10140
let view = a.view ( ) .unwrap ( ) ;
10141
10142
assert_eq! ( a.heads_of ( & view, " main" ) .unwrap ( ) , vec! [ base] ) ;
10143
assert! (
10144
a.heads_of ( & view, " feature" ) .unwrap ( ) .contains ( & cb) ,
10145
" B's commit stands"
10146
) ;
10147
assert! (
10148
a.store ( ) .get_change ( & ca) .is_ok ( ) ,
10149
" the change itself is still in the store"
10150
) ;
10151
10152
assert! ( ! root.join ( " a.txt" ) .exists ( ) ) ;
10153
assert! ( a.snapshot ( " oxy status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
10154
assert! ( ws_dir.join ( " b.txt" ) .exists ( ) ) ;
10155
10156
10157
let u = b.undo ( ) .unwrap ( ) ;
10158
assert_eq! ( u.command, " oxy commit -m \" from b\" " ) ;
10159
assert! ( ! b
10160
.heads_of ( & b.view ( ) .unwrap ( ) , " feature" )
10161
.unwrap ( )
10162
.contains ( & cb) ) ;
10163
assert_eq! (
10164
a.heads_of ( & a.view ( ) .unwrap ( ) , " main" ) .unwrap ( ) ,
10165
vec! [ base] ,
10166
" A's undo stands"
10167
) ;
10168
10169
10170
10171
let e = a.undo ( ) .unwrap_err ( ) .to_string ( ) ;
10172
assert! ( e.contains ( " the workspace at" ) & & e.contains ( " ws" ) , " {e}" ) ;
10173
}
10174
10175
10176
10177
# [ test ]
10178
fn undo_on_a_shared_bookmark_keeps_the_other_workspaces_head ( ) {
10179
hermetic ( ) ;
10180
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10181
let root = dir.path ( ) .join ( " main" ) ;
10182
let a = Repo:: init( & root) .unwrap ( ) ;
10183
write ( & root, " base.txt" , b " base\n " ) ;
10184
a.commit ( " base" , & [ ] ) .unwrap ( ) ;
10185
let ws_dir = dir.path ( ) .join ( " ws" ) ;
10186
let b = a.workspace_add ( & ws_dir, None ) .unwrap ( ) ;
10187
write ( & root, " a.txt" , b " a\n " ) ;
10188
let ca = a.commit ( " from a" , & [ ] ) .unwrap ( ) ;
10189
write ( & ws_dir, " b.txt" , b " b\n " ) ;
10190
let cb = b.commit ( " from b" , & [ ] ) .unwrap ( ) ;
10191
let u = a.undo ( ) .unwrap ( ) ;
10192
assert_eq! ( u.command, " oxy commit -m \" from a\" " ) ;
10193
let heads = a.heads_of ( & a.view ( ) .unwrap ( ) , " main" ) .unwrap ( ) ;
10194
assert! ( heads.contains ( & cb) & & ! heads.contains ( & ca) , " {heads:?}" ) ;
10195
}
10196
10197
10198
10199
# [ test ]
10200
fn undo_refuses_to_take_back_another_workspaces_work ( ) {
10201
hermetic ( ) ;
10202
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10203
let root = dir.path ( ) .join ( " main" ) ;
10204
let a = Repo:: init( & root) .unwrap ( ) ;
10205
write ( & root, " base.txt" , b " base\n " ) ;
10206
a.commit ( " base" , & [ ] ) .unwrap ( ) ;
10207
let ws_dir = dir.path ( ) .join ( " ws" ) ;
10208
let b = a.workspace_add ( & ws_dir, None ) .unwrap ( ) ;
10209
10210
write ( & root, " a.txt" , b " a\n " ) ;
10211
a.commit ( " from a" , & [ ] ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
10212
b.refresh ( ) .unwrap ( ) ;
530a0fc6 Andy McCall 26 days ago ↶
10213
let heads = b.resolve_heads ( " main" ) .unwrap ( ) ;
10214
b.checkout ( & heads, false , " oxy checkout" ) .unwrap ( ) ;
10215
write ( & ws_dir, " a.txt" , b " a, then b\n " ) ;
10216
b.commit ( " from b" , & [ ] ) .unwrap ( ) ;
10217
let e = a.undo ( ) .unwrap_err ( ) .to_string ( ) ;
10218
assert! (
10219
e.contains ( " bookmark main" ) & & e.contains ( " builds on" ) ,
10220
" {e}"
10221
) ;
10222
assert! ( e.contains ( " oxy op restore" ) , " {e}" ) ;
10223
}
10224
10225
10226
10227
# [ test ]
10228
fn undo_says_when_the_commit_was_pushed ( ) {
10229
hermetic ( ) ;
10230
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10231
let root = dir.path ( ) ;
10232
Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
10233
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
10234
let repo = Repo:: clone( & url, & root.join ( " work" ) )
10235
.unwrap ( )
10236
.with_home ( root.join ( " home" ) ) ;
10237
write ( & root.join ( " work" ) , " a.txt" , b " a\n " ) ;
10238
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10239
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10240
10241
let u = repo.undo ( ) .unwrap ( ) ;
10242
assert_eq! ( u.command, " oxy commit -m \" a\" " ) ;
10243
assert_eq! ( u.pushed, vec! [ ( " main" .to_string ( ) , " origin" .to_string ( ) ) ] ) ;
10244
}
b689779e Andy McCall 26 days ago ↶
10245
10246
10247
10248
10249
# [ test ]
10250
fn large_new_files_are_left_out_of_snapshots ( ) {
10251
hermetic ( ) ;
10252
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10253
let root = dir.path ( ) ;
10254
let mut repo = Repo:: init( root) .unwrap ( ) ;
10255
repo.config.max_new_file = 1024 ;
10256
write ( root, " small.txt" , b " small\n " ) ;
10257
write ( root, " big.bin" , & vec! [ 0 u8 ; 4096 ] ) ;
10258
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10259
let paths: Vec< _ > = s.statuses.iter ( ) .map ( | p | p.path.as_str ( ) ) .collect ( ) ;
10260
assert_eq! ( paths, [ " small.txt" ] ) ;
10261
assert_eq! ( repo.skipped_large ( ) , vec! [ ( " big.bin" .to_string ( ) , 4096 ) ] ) ;
10262
repo.commit ( " small" , & [ ] ) .unwrap ( ) ;
10263
10264
10265
write ( root, " small.txt" , & vec! [ b ' x' ; 4096 ] ) ;
10266
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10267
assert! ( s.statuses.iter ( ) .any ( | p | p.path = = " small.txt" ) ) ;
10268
10269
repo.config.max_new_file = 0 ;
10270
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10271
assert! ( s.statuses.iter ( ) .any ( | p | p.path = = " big.bin" ) ) ;
10272
repo.config.max_new_file = 1024 ;
10273
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10274
assert! (
10275
s.statuses.iter ( ) .any ( | p | p.path = = " big.bin" ) ,
10276
" already in the working copy"
10277
) ;
10278
assert! ( repo.skipped_large ( ) .is_empty ( ) ) ;
10279
}
10280
10281
10282
10283
# [ test ]
10284
fn gc_prunes_what_only_expired_snapshots_held ( ) {
10285
hermetic ( ) ;
10286
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10287
let root = dir.path ( ) ;
10288
let mut repo = Repo:: init( root) .unwrap ( ) ;
10289
repo.config.gc_grace_days = 0 ;
10290
write ( root, " a.txt" , b " a\n " ) ;
10291
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10292
let image: Vec< u8 > = ( 0 .. 200_000 u32 ) .map ( | i | ( i % 7 ) as u8 ) .collect ( ) ;
10293
write ( root, " image.tar" , & image) ;
10294
repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10295
std:: fs:: remove_file( root.join ( " image.tar" ) ) .unwrap ( ) ;
10296
repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10297
let blob = Repo:: blob_ref( & image) ;
10298
assert! ( repo.store ( ) .has ( & blob) ) ;
10299
repo.gc ( true ) .unwrap ( ) ;
10300
assert! ( repo.store ( ) .has ( & blob) , " a recent snapshot keeps it" ) ;
10301
repo.config.snapshot_retention_days = 0 ;
10302
repo.gc ( true ) .unwrap ( ) ;
10303
assert! ( ! repo.store ( ) .has ( & blob) , " an expired snapshot does not" ) ;
10304
assert_eq! (
10305
repo.file_at ( " a.txt" , None ) .unwrap ( ) ,
10306
b " a\n " ,
10307
" committed files stay"
10308
) ;
fe9586d9 Andy McCall 26 days ago ↶
10309
}
10310
10311
10312
10313
10314
# [ test ]
10315
fn carrying_work_the_target_already_has_is_no_conflict ( ) {
10316
hermetic ( ) ;
10317
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10318
let root = dir.path ( ) ;
10319
let mut repo = Repo:: init( root) .unwrap ( ) ;
10320
write ( root, " a.txt" , b " one\n " ) ;
10321
write ( root, " b.txt" , b " b\n " ) ;
10322
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
10323
repo.bookmark_create ( " landed" , None ) .unwrap ( ) ;
10324
10325
repo.bookmark_switch ( " landed" , false ) .unwrap ( ) ;
10326
write ( root, " a.txt" , b " two\n " ) ;
10327
repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
10328
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
10329
assert_eq! ( std:: fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n " ) ;
10330
10331
write ( root, " a.txt" , b " two\n " ) ;
10332
write ( root, " b.txt" , b " b, edited\n " ) ;
10333
let ( _ , carried) = repo.bookmark_switch_carry ( " landed" ) .unwrap ( ) ;
10334
assert_eq! (
10335
carried,
10336
vec! [ " b.txt" .to_string ( ) ] ,
10337
" only the work landed lacks is carried"
10338
) ;
10339
assert_eq! ( std:: fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " two\n " ) ;
10340
assert_eq! ( std:: fs:: read( root.join ( " b.txt" ) ) .unwrap ( ) , b " b, edited\n " ) ;
10341
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10342
let paths: Vec< _ > = s.statuses.iter ( ) .map ( | p | p.path.as_str ( ) ) .collect ( ) ;
10343
assert_eq! ( paths, [ " b.txt" ] ) ;
10344
10345
10346
repo.bookmark_switch ( " main" , true ) .unwrap ( ) ;
10347
write ( root, " a.txt" , b " three\n " ) ;
10348
let e = repo
10349
.bookmark_switch_carry ( " landed" )
10350
.unwrap_err ( )
10351
.to_string ( ) ;
10352
assert! ( e.contains ( " a.txt" ) & & e.contains ( " --discard" ) , " {e}" ) ;
b689779e Andy McCall 26 days ago ↶
10353
}
aa4939e0 Andy McCall 26 days ago ↶
10354
10355
10356
10357
# [ test ]
10358
fn a_full_hash_resolves_without_its_hash_sign ( ) {
10359
hermetic ( ) ;
10360
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10361
let root = dir.path ( ) ;
10362
let repo = Repo:: init( root) .unwrap ( ) ;
10363
write ( root, " a.txt" , b " a\n " ) ;
10364
let h = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10365
assert_eq! ( repo.resolve_change ( & h.to_hex ( ) ) .unwrap ( ) , h) ;
10366
assert_eq! ( repo.resolve_change ( & format! ( " #{} " , h.to_hex ( ) ) ) .unwrap ( ) , h) ;
10367
assert! ( repo
10368
.resolve_change ( & " 0" .repeat ( 64 ) )
10369
.unwrap_err ( )
10370
.to_string ( )
10371
.contains ( " no change matches" ) ) ;
10372
}
f2f91ceb Andy McCall 25 days ago ↶
10373
10374
# [ test ]
10375
fn a_pin_change_moves_only_the_pin ( ) {
10376
hermetic ( ) ;
10377
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10378
let root = dir.path ( ) ;
10379
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
10380
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
10381
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
10382
.unwrap ( )
10383
.with_home ( root.join ( " home" ) ) ;
10384
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
10385
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
10386
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10387
let _app_origin = Repo:: init( & root.join ( " app-origin" ) ) .unwrap ( ) ;
10388
let app_dir = root.join ( " app" ) ;
10389
let app = Repo:: clone( & root.join ( " app-origin" ) .to_string_lossy ( ) , & app_dir)
10390
.unwrap ( )
10391
.with_home ( root.join ( " home" ) ) ;
10392
write ( & app_dir, " main.txt" , b " app\n " ) ;
10393
let pin1 = app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
10394
let a1 = app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
10395
10396
write ( & root.join ( " lib" ) , " more.txt" , b " v2\n " ) ;
10397
let l2 = lib.commit ( " lib v2" , & [ ] ) .unwrap ( ) ;
10398
let crate :: subrepo:: Pin:: Oxy { repo, bookmark, .. } = pin1 else {
10399
panic! ( " an Oxygen pin" )
10400
} ;
10401
let pin2 = crate :: subrepo:: Pin:: Oxy {
10402
repo,
10403
heads: vec! [ l2] ,
10404
bookmark,
10405
} ;
10406
let author = app.store ( ) .get_change ( & a1) .unwrap ( ) .author;
10407
let change = app
10408
.build_pin_change ( & [ a1] , " libs/lib" , & pin2, " Move lib to v2" , author)
10409
.unwrap ( ) ;
10410
let mut state = app.load_state ( & [ a1] ) .unwrap ( ) ;
10411
state.add ( change) .unwrap ( ) ;
10412
let files = state.materialize ( ) .files;
10413
assert_eq! ( files[ " libs/lib" ] .kind, NodeKind:: Subrepo) ;
10414
let FileContent:: Opaque { blobs } = & files[ " libs/lib" ] .content else {
10415
panic! ( " a pin is opaque" )
10416
} ;
10417
let read = crate :: subrepo:: Pin:: decode( & app.read_blob ( & blobs[ 0 ] ) .unwrap ( ) ) .unwrap ( ) ;
10418
assert_eq! ( read, pin2, " the new pin" ) ;
10419
assert! (
10420
matches! ( & files[ " main.txt" ] .content, FileContent:: Text { bytes, .. } if bytes.as_slice ( ) = = b " app\n " ) ,
10421
" nothing else changes"
10422
) ;
10423
10424
let e = app.build_pin_change ( & [ a1] , " main.txt" , & pin2, " x" , author) ;
10425
assert! ( format! ( " {:?} " , e.unwrap_err ( ) ) .contains ( " not a subrepo" ) ) ;
10426
let e = app.build_pin_change ( & [ a1] , " libs/none" , & pin2, " x" , author) ;
10427
assert! ( format! ( " {:?} " , e.unwrap_err ( ) ) .contains ( " does not exist" ) ) ;
10428
}
767ac6cd Andy McCall 19 days ago
10429
10430
10431
10432
10433
10434
10435
# [ test ]
10436
fn one_handle_keeps_one_head_until_it_refreshes ( ) {
10437
hermetic ( ) ;
10438
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10439
let root = dir.path ( ) ;
10440
let writer = Repo:: init( root) .unwrap ( ) ;
10441
write ( root, " a.txt" , b " one\n " ) ;
10442
let h1 = writer.commit ( " one" , & [ ] ) .unwrap ( ) ;
10443
let reader = Repo:: open( root) .unwrap ( ) ;
10444
let before = reader.view ( ) .unwrap ( ) ;
10445
assert_eq! ( reader.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
10446
write ( root, " a.txt" , b " two\n " ) ;
10447
let h2 = writer.commit ( " two" , & [ ] ) .unwrap ( ) ;
10448
assert_eq! (
10449
writer.bookmark_heads ( ) .unwrap ( ) ,
10450
vec! [ h2] ,
10451
" its own operation"
10452
) ;
10453
assert_eq! ( reader.view ( ) .unwrap ( ) , before, " the head it read first" ) ;
10454
assert_eq! ( reader.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
10455
reader.refresh ( ) .unwrap ( ) ;
10456
assert_eq! ( reader.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
10457
10458
10459
let stale = Repo:: open( root) .unwrap ( ) ;
10460
assert_eq! ( stale.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
10461
write ( root, " a.txt" , b " three\n " ) ;
10462
let h3 = writer.commit ( " three" , & [ ] ) .unwrap ( ) ;
10463
stale.bookmark_create ( " feature" , Some ( & [ h1] ) ) .unwrap ( ) ;
10464
let view = stale.view ( ) .unwrap ( ) ;
10465
assert! ( view.bookmarks.contains_key ( " feature" ) ) ;
10466
assert_eq! (
10467
stale.bookmark_heads ( ) .unwrap ( ) ,
10468
vec! [ h3] ,
10469
" merged, not lost"
10470
) ;
10471
let fresh = Repo:: open( root) .unwrap ( ) ;
10472
assert_eq! ( fresh.view ( ) .unwrap ( ) , view) ;
10473
}
10474
10475
10476
# [ test ]
10477
fn over_a_store_one_handle_keeps_one_head ( ) {
10478
hermetic ( ) ;
10479
let backend = std:: sync:: Arc:: new( crate :: strata:: MemoryStore:: new( ) ) ;
10480
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = Some ( backend.clone ( ) ) ) ;
10481
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10482
let a = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
10483
let ( h0, _ ) = a.head_op ( ) .unwrap ( ) ;
10484
let b = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
10485
assert_eq! ( b.head_op ( ) .unwrap ( ) .0 , h0) ;
10486
let state = a.store.put_state ( & StateObject:: new( vec! [ ] ) ) .unwrap ( ) ;
10487
let mut view = a.view ( ) .unwrap ( ) ;
10488
view.bookmarks.insert ( " x" .into ( ) , state) ;
10489
let r = a.record_op ( & [ h0] , " bookmark x" , view) .unwrap ( ) ;
10490
assert_eq! ( a.head_op ( ) .unwrap ( ) .0 , r) ;
10491
assert_eq! ( b.head_op ( ) .unwrap ( ) .0 , h0, " b keeps its head" ) ;
10492
b.refresh ( ) .unwrap ( ) ;
10493
assert_eq! ( b.head_op ( ) .unwrap ( ) .0 , r) ;
10494
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = None ) ;
10495
}
10496
10497
10498
10499
10500
# [ test ]
10501
fn gc_retires_and_a_later_gc_deletes_after_the_hour ( ) {
10502
hermetic ( ) ;
10503
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10504
let root = dir.path ( ) ;
10505
let repo = Repo:: init( root) .unwrap ( ) ;
10506
let mut heads = Vec :: new( ) ;
10507
for i in 0 .. 4 {
10508
write ( root, & format! ( " f{i} .txt" ) , format! ( " {i} \n " ) .as_bytes ( ) ) ;
10509
heads.push ( repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ) ;
10510
}
10511
let reader = Repo:: open( root) .unwrap ( ) ;
10512
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 5 ) ;
10513
let r = repo.gc ( true ) .unwrap ( ) ;
10514
assert_eq! ( r.retired, crate :: store:: RetiredReport:: default( ) ) ;
10515
assert_eq! ( repo.store ( ) .loose_count ( ) .unwrap ( ) , 0 ) ;
10516
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 5 , " read on from what it had" ) ;
10517
10518
let r = repo.gc ( true ) .unwrap ( ) ;
10519
assert_eq! ( ( r.retired.packs, r.retired.loose) , ( 0 , 0 ) ) ;
10520
assert! ( r.retired.kept > 0 , " {r:?}" ) ;
10521
write ( root, " f9.txt" , b " 9\n " ) ;
10522
heads.push ( repo.commit ( " c9" , & [ ] ) .unwrap ( ) ) ;
10523
crate :: store:: reader_tests:: age_retired(
10524
repo.store ( ) ,
10525
crate :: store:: RETIRED_FOR + std:: time:: Duration:: from_secs( 60 ) ,
10526
) ;
10527
let r = repo.gc ( true ) .unwrap ( ) ;
10528
assert! ( r.retired.packs > = 1 & & r.retired.loose > 0 , " {r:?}" ) ;
10529
assert_eq! ( r.retired.kept, 0 , " {r:?}" ) ;
10530
let r = repo.gc ( true ) .unwrap ( ) ;
10531
assert_eq! ( ( r.retired.packs, r.retired.loose) , ( 0 , 0 ) ) ;
10532
assert! ( r.retired.kept > 0 , " what the last gc retired is young" ) ;
10533
10534
10535
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 5 ) ;
10536
for h in & heads[ .. 4 ] {
10537
reader.store ( ) .get_change ( h) .unwrap ( ) ;
10538
}
10539
reader.refresh ( ) .unwrap ( ) ;
10540
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 6 ) ;
10541
let fresh = Repo:: open( root) .unwrap ( ) ;
10542
assert_eq! (
10543
fresh.store ( ) .pack_count ( ) ,
10544
1 ,
10545
" retired packs are not loaded"
10546
) ;
10547
}
10548
10549
10550
10551
# [ test ]
10552
fn a_repository_opens_when_a_listed_index_is_gone ( ) {
10553
hermetic ( ) ;
10554
let backend = crate :: store:: reader_tests:: racing( true ) ;
10555
let dyn_backend: std:: sync:: Arc< crate:: store:: Backend> = backend.clone ( ) ;
10556
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = Some ( dyn_backend) ) ;
10557
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10558
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
10559
write ( dir.path ( ) , " a.txt" , b " a\n " ) ;
10560
let h = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10561
repo.gc ( true ) .unwrap ( ) ;
10562
let again = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
10563
assert_eq! ( again.store ( ) .pack_count ( ) , 1 ) ;
10564
assert_eq! ( again.bookmark_heads ( ) .unwrap ( ) , vec! [ h] ) ;
10565
10566
10567
use crate :: strata:: Store as _ ;
10568
let idx = backend
10569
.list ( " pack-" )
10570
.unwrap ( )
10571
.into_iter ( )
10572
.map ( | e | e.key )
10573
.find ( | k | k.ends_with ( " .idx" ) & & k ! = " pack-0000.idx" )
10574
.unwrap ( ) ;
10575
let stem = idx.trim_end_matches ( " .idx" ) ;
10576
for ext in [ " pack" , " idx" ] {
10577
let bytes = backend.get ( & format! ( " {stem} .{ext} " ) ) .unwrap ( ) ;
10578
backend
10579
.put_new ( & format! ( " pack-copy.{ext} " ) , & bytes)
10580
.unwrap ( ) ;
10581
}
10582
* backend.doomed.lock ( ) .unwrap ( ) = Some ( " pack-copy.idx" .into ( ) ) ;
10583
let again = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
10584
assert_eq! ( again.store ( ) .pack_count ( ) , 1 ) ;
10585
assert_eq! ( again.bookmark_heads ( ) .unwrap ( ) , vec! [ h] ) ;
10586
assert! ( ! backend.has ( " pack-copy.idx" ) .unwrap ( ) ) ;
10587
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = None ) ;
10588
}
2452b440 Andy McCall 11 days ago ↶
10589
10590
10591
10592
10593
10594
# [ test ]
10595
fn author_for_follows_the_key_the_hub_named ( ) {
10596
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10597
let home = dir.path ( ) .join ( " home" ) ;
10598
fs:: create_dir_all( home.join ( " keys" ) ) .unwrap ( ) ;
10599
let user = home.join ( " config.toml" ) ;
10600
let repo_cfg = dir.path ( ) .join ( " config" ) ;
10601
let ( old, new, pin) = (
10602
KeyId:: from_hex( & " a" .repeat ( 64 ) ) .unwrap ( ) ,
10603
KeyId:: from_hex( & " b" .repeat ( 64 ) ) .unwrap ( ) ,
10604
KeyId:: from_hex( & " c" .repeat ( 64 ) ) .unwrap ( ) ,
10605
) ;
10606
fs:: write( & user, format! ( " author.key = \" {} \" \n " , old.to_hex ( ) ) ) .unwrap ( ) ;
10607
fs:: write(
10608
home.join ( " keys" ) .join ( super :: config:: AUTHOR_FOR_FILE ) ,
10609
format! ( " {} \n " , new.to_hex ( ) ) ,
10610
)
10611
.unwrap ( ) ;
10612
fs:: write( & repo_cfg, " " ) .unwrap ( ) ;
10613
assert_eq! (
10614
Config:: load( Some ( & user) , & repo_cfg) .unwrap ( ) .author_key,
10615
Some ( old) ,
10616
" no author.for: the note is ignored"
10617
) ;
10618
fs:: write(
10619
& user,
10620
format! (
10621
" author.key = \" {} \" \n author.for = \" andymccall\" \n " ,
10622
old.to_hex ( )
10623
) ,
10624
)
10625
.unwrap ( ) ;
10626
assert_eq! (
10627
Config:: load( Some ( & user) , & repo_cfg) .unwrap ( ) .author_key,
10628
Some ( new)
10629
) ;
10630
fs:: write( & repo_cfg, format! ( " author.key = \" {} \" \n " , pin.to_hex ( ) ) ) .unwrap ( ) ;
10631
assert_eq! (
10632
Config:: load( Some ( & user) , & repo_cfg) .unwrap ( ) .author_key,
10633
Some ( pin) ,
10634
" a repository's pin wins"
10635
) ;
10636
}