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 ] ,
c0d4075c Andy McCall 11 days ago ↶
268
identical: vec! [ ] ,
9bd98cfb Andy McCall 11 days ago ↶
269
left: vec! [ ]
270
} ]
271
) ;
272
let want: & [ u8 ] = if reverse {
273
b " a\n second\n first\n b\n "
274
} else {
275
b " a\n first\n second\n b\n "
276
} ;
277
assert_eq! ( fs:: read( root.join ( " f.txt" ) ) .unwrap ( ) , want) ;
278
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
279
assert_eq! ( statuses ( & s) , vec! [ ( " f.txt" .into ( ) , ChangeStatus:: Resolved) ] ) ;
280
let h = repo.commit ( " both" , & [ ] ) .unwrap ( ) ;
281
assert_eq! (
282
repo.store ( ) .get_change ( & h) .unwrap ( ) .kind,
283
ChangeKind:: Resolution
284
) ;
285
assert! ( ! repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) .is_conflicted ( ) ) ;
286
}
287
}
288
4afe288b Andy McCall 11 days ago ↶
289
290
291
292
# [ test ]
293
fn union_files_keep_both_sides_on_checkout ( ) {
294
hermetic ( ) ;
295
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
296
let root = dir.path ( ) ;
297
let repo = Repo:: init( root) .unwrap ( ) ;
298
write ( root, " .oxyattributes" , b " CHANGELOG.md merge=union\n " ) ;
299
write (
300
root,
301
" CHANGELOG.md" ,
302
b " # Changelog\n ## Unreleased\n ## 1.0\n " ,
303
) ;
304
write ( root, " f.txt" , b " a\n b\n " ) ;
305
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
306
write (
307
root,
308
" CHANGELOG.md" ,
309
b " # Changelog\n ## Unreleased\n - one\n ## 1.0\n " ,
310
) ;
311
write ( root, " f.txt" , b " a\n one\n b\n " ) ;
312
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
313
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
314
write (
315
root,
316
" CHANGELOG.md" ,
317
b " # Changelog\n ## Unreleased\n - two\n ## 1.0\n " ,
318
) ;
319
write ( root, " f.txt" , b " a\n two\n b\n " ) ;
320
repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
321
let r = repo
322
.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
323
.unwrap ( ) ;
324
assert! ( r.conflicted) ;
325
assert_eq! ( r.kept_both.len ( ) , 1 , " {:?}" , r.kept_both) ;
326
assert_eq! ( r.kept_both[ 0 ] .path, " CHANGELOG.md" ) ;
327
assert_eq! (
328
fs:: read_to_string( root.join ( " CHANGELOG.md" ) ) .unwrap ( ) ,
329
" # Changelog\n ## Unreleased\n - one\n - two\n ## 1.0\n "
330
) ;
331
assert! ( has_conflict_markers ( & fs:: read( root.join ( " f.txt" ) ) .unwrap ( ) ) ) ;
332
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
333
assert_eq! (
334
statuses ( & s) ,
335
vec! [ ( " CHANGELOG.md" .into ( ) , ChangeStatus:: Resolved) ]
336
) ;
337
}
338
c0d4075c Andy McCall 11 days ago ↶
339
340
341
342
343
# [ test ]
344
fn identical_resolutions_on_two_bookmarks_settle_by_themselves ( ) {
345
hermetic ( ) ;
346
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
347
let root = dir.path ( ) ;
348
let repo = Repo:: init( root) .unwrap ( ) ;
349
write ( root, " f.txt" , b " a\n x\n b\n " ) ;
350
let base = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
351
write ( root, " f.txt" , b " a\n X1\n b\n " ) ;
352
let c1 = repo.commit ( " X1" , & [ ] ) .unwrap ( ) ;
353
repo.checkout ( & [ base] , false , " checkout" ) .unwrap ( ) ;
354
write ( root, " f.txt" , b " a\n X2\n b\n " ) ;
355
let c2 = repo.commit ( " X2" , & [ ] ) .unwrap ( ) ;
356
357
repo.checkout ( & [ c1, c2] , false , " checkout" ) .unwrap ( ) ;
358
assert! ( has_conflict_markers ( & fs:: read( root.join ( " f.txt" ) ) .unwrap ( ) ) ) ;
359
write ( root, " f.txt" , b " a\n X\n b\n " ) ;
360
let r1 = repo.commit ( " resolve on one" , & [ ] ) .unwrap ( ) ;
361
repo.checkout ( & [ c1, c2] , false , " checkout" ) .unwrap ( ) ;
362
write ( root, " f.txt" , b " a\n X\n b\n " ) ;
363
let r2 = repo.commit ( " resolve on two" , & [ ] ) .unwrap ( ) ;
364
assert_ne! ( r1, r2) ;
365
366
let st = repo.load_state ( & [ r1, r2] ) .unwrap ( ) ;
367
assert! (
368
st.materialize ( ) .is_conflicted ( ) ,
369
" two resolutions, one place"
370
) ;
371
assert_eq! (
372
Repo:: union_resolution( & st, & st.materialize ( ) ) ,
373
Some ( vec! [ ( " f.txt" .into ( ) , b " a\n X\n b\n " .to_vec ( ) ) ] ) ,
374
" a host settles it"
375
) ;
376
let r = repo.checkout ( & [ r1, r2] , false , " checkout" ) .unwrap ( ) ;
377
assert! ( r.conflicted) ;
378
assert_eq! ( r.kept_both.len ( ) , 1 , " {:?}" , r.kept_both) ;
379
assert_eq! (
380
( r.kept_both[ 0 ] .path.as_str ( ) , & r.kept_both[ 0 ] .identical) ,
381
( " f.txt" , & vec! [ 1 ] )
382
) ;
383
assert_eq! ( fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) , " a\n X\n b\n " ) ;
384
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
385
assert_eq! ( statuses ( & s) , vec! [ ( " f.txt" .into ( ) , ChangeStatus:: Resolved) ] ) ;
386
let merged = repo.commit ( " merge" , & [ ] ) .unwrap ( ) ;
387
let st = repo.load_state ( & [ merged] ) .unwrap ( ) ;
388
assert! ( ! st.materialize ( ) .is_conflicted ( ) ) ;
389
}
390
ac4ec124 Andy McCall 11 days ago ↶
391
392
393
394
# [ test ]
395
fn a_host_settles_a_state_whose_conflicts_are_all_union ( ) {
396
hermetic ( ) ;
397
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
398
let root = dir.path ( ) ;
399
let repo = Repo:: init( root) .unwrap ( ) ;
400
write ( root, " .oxyattributes" , b " *.md merge=union\n " ) ;
401
write ( root, " A.md" , b " a\n b\n " ) ;
402
write ( root, " B.md" , b " x\n y\n " ) ;
403
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
404
write ( root, " A.md" , b " a\n one\n b\n " ) ;
405
write ( root, " B.md" , b " x\n Y1\n " ) ;
406
let h2 = repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
407
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
408
write ( root, " A.md" , b " a\n two\n b\n " ) ;
409
let h3 = repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
410
let st = repo.load_state ( & [ h2, h3] ) .unwrap ( ) ;
411
assert_eq! (
412
Repo:: union_resolution( & st, & st.materialize ( ) ) ,
413
Some ( vec! [ ( " A.md" .into ( ) , b " a\n one\n two\n b\n " .to_vec ( ) ) ] )
414
) ;
415
416
repo.checkout ( & [ h3] , false , " checkout" ) .unwrap ( ) ;
417
write ( root, " B.md" , b " x\n Y2\n " ) ;
418
let h4 = repo.commit ( " Y2" , & [ ] ) .unwrap ( ) ;
419
let st = repo.load_state ( & [ h2, h4] ) .unwrap ( ) ;
420
assert! ( st.materialize ( ) .is_conflicted ( ) ) ;
421
assert_eq! ( Repo:: union_resolution( & st, & st.materialize ( ) ) , None ) ;
422
}
423
9bd98cfb Andy McCall 11 days ago ↶
424
425
426
427
# [ test ]
428
fn keeping_both_refuses_replaced_or_edited_lines ( ) {
429
hermetic ( ) ;
430
let ( dir, repo, _ , _ , _ ) = conflicted_repo ( ) ;
431
let root = dir.path ( ) ;
432
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
433
.unwrap ( ) ;
434
let marked = fs:: read( root.join ( " f.txt" ) ) .unwrap ( ) ;
435
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
436
let node = snap.base_snapshot.files[ " f.txt" ] .node;
437
assert_eq! ( snap.base.both_sides ( & node) , vec! [ None ] ) ;
438
let kept = repo.keep_both ( & snap, & [ " f.txt" .into ( ) ] , false ) .unwrap ( ) ;
439
assert! ( kept[ 0 ] .settled.is_empty ( ) ) ;
440
assert_eq! ( kept[ 0 ] .left[ 0 ] .0 , 1 ) ;
441
assert! ( kept[ 0 ] .left[ 0 ] .1. contains ( " both sides changed" ) , " {kept:?}" ) ;
442
assert_eq! ( fs:: read( root.join ( " f.txt" ) ) .unwrap ( ) , marked) ;
443
444
445
let sides = vec! [ Some ( vec! [ b " x\n " .to_vec ( ) , b " y\n " .to_vec ( ) ] ) ] ;
446
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 " ;
447
let ( out, settled, left) = super :: snapshot:: keep_both_text( text, & sides, false ) ;
448
assert_eq! ( out, text.to_vec ( ) ) ;
449
assert! ( settled.is_empty ( ) ) ;
450
assert! ( left[ 0 ] .1. contains ( " edited" ) , " {left:?}" ) ;
451
452
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 " ;
453
let ( out, settled, _ ) = super :: snapshot:: keep_both_text( text, & sides, false ) ;
454
assert_eq! ( out, b " a\n x\n y\n b\n " .to_vec ( ) ) ;
455
assert_eq! ( settled, vec! [ 1 ] ) ;
456
}
457
458
# [ test ]
98286406 Andy McCall 1 month ago ↶
459
fn concurrent_edits_conflict_and_can_be_resolved ( ) {
460
hermetic ( ) ;
461
let ( dir, repo, h1, h2, h3) = conflicted_repo ( ) ;
462
let root = dir.path ( ) ;
463
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
464
assert_eq! ( heads, [ h2, h3] .into_iter ( ) .collect ( ) ) ;
465
let st = repo.load_state ( & [ h2, h3] ) .unwrap ( ) ;
466
assert! ( st.materialize ( ) .is_conflicted ( ) ) ;
467
468
75167672 Andy McCall 1 month ago
469
let r = repo
470
.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
471
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
472
assert! ( r.conflicted) ;
473
let marked = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
474
assert! (
475
marked.starts_with ( " a\n <<<<<<< oxy conflict 1 of 1" ) ,
476
" {marked}"
477
) ;
478
assert! (
479
marked.contains ( " B\n " ) & & marked.contains ( " X\n " ) & & marked.ends_with ( " end\n c\n " ) ,
480
" {marked}"
481
) ;
98286406 Andy McCall 1 month ago ↶
482
assert! ( has_conflict_markers ( marked.as_bytes ( ) ) ) ;
483
484
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
485
assert! ( s.statuses.is_empty ( ) ) ;
486
assert! ( ! s.resolves) ;
487
488
489
write ( root, " f.txt" , marked.replace ( " B\n " , " BB\n " ) .as_bytes ( ) ) ;
490
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
491
assert_eq! (
492
statuses ( & s) ,
493
vec! [ ( " f.txt" .into ( ) , ChangeStatus:: ConflictEdited) ]
494
) ;
98286406 Andy McCall 1 month ago ↶
495
assert! ( repo.commit ( " no" , & [ ] ) .is_err ( ) ) ;
496
497
498
write ( root, " f.txt" , b " a\n X\n c\n d\n " ) ;
499
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
500
assert_eq! ( statuses ( & s) , vec! [ ( " f.txt" .into ( ) , ChangeStatus:: Resolved) ] ) ;
501
assert! ( s.resolves) ;
502
let h4 = repo.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
503
let c4 = repo.store ( ) .get_change ( & h4) .unwrap ( ) ;
504
assert_eq! ( c4.kind, ChangeKind:: Resolution) ;
505
let mut want = vec! [ h1, h2, h3] ;
506
want.sort ( ) ;
75167672 Andy McCall 1 month ago
507
assert_eq! (
508
c4.deps, want,
509
" a resolution depends on exactly the contributors and what it references"
510
) ;
98286406 Andy McCall 1 month ago ↶
511
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h4] ) ;
512
let snap = repo.load_state ( & [ h4] ) .unwrap ( ) .materialize ( ) ;
513
assert! ( ! snap.is_conflicted ( ) ) ;
75167672 Andy McCall 1 month ago
514
assert! (
515
matches! ( & snap.files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n X\n c\n d\n " )
516
) ;
98286406 Andy McCall 1 month ago ↶
517
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
518
519
520
let mut st = State:: new( ) ;
521
for h in [ h4, h3, h1, h2] {
522
st.add_unchecked ( repo.store ( ) .get_change ( & h) .unwrap ( ) ) ;
523
}
524
st.validate ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
525
assert! (
526
matches! ( & st.materialize ( ) .files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n X\n c\n d\n " )
527
) ;
98286406 Andy McCall 1 month ago ↶
528
}
529
69953b50 Andy McCall 19 days ago ↶
530
531
532
98286406 Andy McCall 1 month ago ↶
533
# [ test ]
69953b50 Andy McCall 19 days ago ↶
534
fn resolutions_and_amends_take_paths ( ) {
535
hermetic ( ) ;
536
let ( dir, repo, _h1, _h2, _h3) = conflicted_repo ( ) ;
537
let root = dir.path ( ) ;
538
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
539
.unwrap ( ) ;
540
541
write ( root, " f.txt" , b " a\n X\n c\n " ) ;
542
write ( root, " notes.txt" , b " pending\n " ) ;
543
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
544
assert! ( s.resolves) ;
545
let err = repo
546
.commit_selection_partial ( " resolve" , & [ ] , & Selection:: Paths( vec! [ " notes.txt" .into ( ) ] ) )
547
.unwrap_err ( )
548
.to_string ( ) ;
549
assert! (
550
err.contains ( " takes every file it resolves" ) & & err.contains ( " f.txt" ) ,
551
" {err}"
552
) ;
553
let ( h4, partial) = repo
554
.commit_selection_partial ( " resolve" , & [ ] , & Selection:: Paths( vec! [ " f.txt" .into ( ) ] ) )
555
.unwrap ( ) ;
556
assert! ( partial) ;
557
assert_eq! (
558
repo.store ( ) .get_change ( & h4) .unwrap ( ) .kind,
559
ChangeKind:: Resolution
560
) ;
561
assert! ( ! repo
562
.load_state ( & [ h4] )
563
.unwrap ( )
564
.materialize ( )
565
.is_conflicted ( ) ) ;
566
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
567
assert_eq! (
568
statuses ( & s) ,
569
vec! [ ( " notes.txt" .into ( ) , ChangeStatus:: Added) ] ,
570
" the other work waits"
571
) ;
572
573
574
575
let h5 = repo.commit ( " notes" , & [ h4] ) .unwrap ( ) ;
576
let id5 = repo.store ( ) .get_change ( & h5) .unwrap ( ) .change_id;
577
write ( root, " notes.txt" , b " pending\n more\n " ) ;
578
write ( root, " other.txt" , b " later\n " ) ;
579
let h6 = repo
580
.amend_paths ( Some ( " notes, amended" ) , & [ " notes.txt" .into ( ) ] )
581
.unwrap ( ) ;
582
let c6 = repo.store ( ) .get_change ( & h6) .unwrap ( ) ;
583
assert_eq! ( ( c6.change_id, c6.message.as_str ( ) ) , ( id5, " notes, amended" ) ) ;
584
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h6] ) ;
585
let snap6 = repo.load_state ( & [ h6] ) .unwrap ( ) .materialize ( ) ;
586
assert! (
587
matches! ( & snap6.files[ " notes.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " pending\n more\n " )
588
) ;
589
assert! (
590
! snap6.files.contains_key ( " other.txt" ) ,
591
" other.txt stays out"
592
) ;
593
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
594
assert_eq! (
595
statuses ( & s) ,
596
vec! [ ( " other.txt" .into ( ) , ChangeStatus:: Added) ]
597
) ;
598
}
599
600
# [ test ]
98286406 Andy McCall 1 month ago ↶
601
fn resolution_may_keep_both_sides_or_neither ( ) {
602
hermetic ( ) ;
603
let ( dir, repo, _h1, _h2, _h3) = conflicted_repo ( ) ;
604
let root = dir.path ( ) ;
75167672 Andy McCall 1 month ago
605
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
606
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
607
608
write ( root, " f.txt" , b " a\n X\n B\n c\n " ) ;
609
let h = repo.commit ( " both" , & [ ] ) .unwrap ( ) ;
610
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
611
assert! ( ! snap.is_conflicted ( ) ) ;
75167672 Andy McCall 1 month ago
612
assert! (
613
matches! ( & snap.files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n X\n B\n c\n " )
614
) ;
98286406 Andy McCall 1 month ago ↶
615
616
repo.undo ( ) .unwrap ( ) ;
617
write ( root, " f.txt" , b " a\n c\n " ) ;
618
let h = repo.commit ( " neither" , & [ ] ) .unwrap ( ) ;
619
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
620
assert! ( ! snap.is_conflicted ( ) ) ;
75167672 Andy McCall 1 month ago
621
assert! (
622
matches! ( & snap.files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n c\n " )
623
) ;
98286406 Andy McCall 1 month ago ↶
624
}
625
626
# [ test ]
627
fn name_conflict_resolved_by_removing_a_candidate ( ) {
628
hermetic ( ) ;
629
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
630
let root = dir.path ( ) ;
631
let repo = Repo:: init( root) .unwrap ( ) ;
632
write ( root, " keep.txt" , b " k\n " ) ;
633
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
634
write ( root, " new.txt" , b " one\n " ) ;
635
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
636
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
637
write ( root, " new.txt" , b " two\n " ) ;
638
repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
639
let r = repo
640
.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
641
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
642
assert! ( r.conflicted) ;
75167672 Andy McCall 1 month ago
643
assert! (
644
root.join ( " new.txt.oxy-conflict-1" ) .exists ( )
645
& & root.join ( " new.txt.oxy-conflict-2" ) .exists ( )
646
) ;
98286406 Andy McCall 1 month ago ↶
647
assert! ( ! root.join ( " new.txt" ) .exists ( ) ) ;
648
649
75167672 Andy McCall 1 month ago
650
let ( keep, drop) = if fs:: read( root.join ( " new.txt.oxy-conflict-1" ) ) .unwrap ( ) = = b " two\n " {
651
( 1 , 2 )
652
} else {
653
( 2 , 1 )
654
} ;
98286406 Andy McCall 1 month ago ↶
655
fs:: remove_file( root.join ( format! ( " new.txt.oxy-conflict-{drop} " ) ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
656
fs:: rename(
657
root.join ( format! ( " new.txt.oxy-conflict-{keep} " ) ) ,
658
root.join ( " new.txt" ) ,
659
)
660
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
661
let h = repo.commit ( " pick two" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
662
assert_eq! (
663
repo.store ( ) .get_change ( & h) .unwrap ( ) .kind,
664
ChangeKind:: Resolution
665
) ;
98286406 Andy McCall 1 month ago ↶
666
667
668
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
669
assert_eq! ( heads, [ h1, h] .into_iter ( ) .collect ( ) ) ;
75167672 Andy McCall 1 month ago
670
let snap = repo
671
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
672
.unwrap ( )
673
.materialize ( ) ;
98286406 Andy McCall 1 month ago ↶
674
assert! ( ! snap.is_conflicted ( ) , " {:?}" , snap.tree.conflicts) ;
75167672 Andy McCall 1 month ago
675
assert_eq! (
676
snap.files.keys ( ) .cloned ( ) .collect:: < Vec< _ > > ( ) ,
677
[ " keep.txt" , " new.txt" ]
678
) ;
679
assert! (
680
matches! ( & snap.files[ " new.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " two\n " )
681
) ;
98286406 Andy McCall 1 month ago ↶
682
}
683
684
# [ test ]
685
fn identities_and_signed_commits ( ) {
686
hermetic ( ) ;
687
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
688
let root = dir.path ( ) ;
689
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
690
write ( root, " a.txt" , b " a\n " ) ;
691
let h0 = repo.commit ( " unsigned" , & [ ] ) .unwrap ( ) ;
692
assert_eq! ( repo.verify_change ( & h0) .unwrap ( ) , SignatureStatus:: Unsigned) ;
75167672 Andy McCall 1 month ago
693
assert_eq! (
694
repo.store ( ) .get_change ( & h0) .unwrap ( ) .author,
695
ObjectRef:: ZERO
696
) ;
98286406 Andy McCall 1 month ago ↶
697
75167672 Andy McCall 1 month ago
698
let ( key, id) = repo
699
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
700
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
701
assert_eq! ( id.hash ( ) , key) ;
702
assert_eq! ( repo.config.author_key, Some ( key) ) ;
703
704
let reopened = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
705
assert_eq! ( reopened.config.author_key, Some ( key) ) ;
706
assert_eq! ( reopened.secret_key ( ) .unwrap ( ) .unwrap ( ) .0 , key) ;
707
708
709
write ( root, " a.txt" , b " a\n b\n " ) ;
710
let h1 = repo.commit ( " authored" , & [ ] ) .unwrap ( ) ;
711
assert_eq! ( repo.store ( ) .get_change ( & h1) .unwrap ( ) .author, key) ;
712
assert_eq! ( repo.verify_change ( & h1) .unwrap ( ) , SignatureStatus:: Unsigned) ;
713
714
repo.sign_change ( & h1) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
715
assert! (
716
matches! ( repo.verify_change ( & h1) .unwrap ( ) , SignatureStatus:: Valid { key: k, .. } if k = = key)
717
) ;
98286406 Andy McCall 1 month ago ↶
718
719
repo.config.set ( " core.sign" , " change" ) .unwrap ( ) ;
720
repo.save_config ( ) .unwrap ( ) ;
721
write ( root, " a.txt" , b " a\n b\n c\n " ) ;
722
let h2 = repo.commit ( " signed" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
723
assert_eq! (
724
repo.verify_change ( & h2) .unwrap ( ) ,
725
SignatureStatus:: Valid {
726
key,
727
name: " Ada" .into ( ) ,
728
email: " [email protected] " .into ( ) ,
729
sealed: false ,
730
revoked: false
731
}
732
) ;
98286406 Andy McCall 1 month ago ↶
733
05e71ed6 Andy McCall 1 month ago ↶
734
735
736
98286406 Andy McCall 1 month ago ↶
737
let other = SecretKey:: generate( ) ;
738
let other_id = other.identity ( " Mallory" , " [email protected] " , 0 ) ;
739
let other_key = repo.store ( ) .put_identity ( & other_id) .unwrap ( ) ;
05e71ed6 Andy McCall 1 month ago ↶
740
write ( root, " a.txt" , b " a\n b\n c\n d\n " ) ;
741
repo.config.set ( " core.sign" , " seal" ) .unwrap ( ) ;
742
let h3 = repo.commit ( " ada's, unsigned" , & [ ] ) .unwrap ( ) ;
743
let forged = other.sign ( Domain:: Change, & other_key, & h3) ;
744
repo.store ( ) .put_signature ( & h3, & forged) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
745
assert! (
05e71ed6 Andy McCall 1 month ago ↶
746
matches! ( repo.verify_change ( & h3) .unwrap ( ) , SignatureStatus:: Invalid( m) if m.contains ( " neither the author" ) )
75167672 Andy McCall 1 month ago
747
) ;
05e71ed6 Andy McCall 1 month ago ↶
748
let adopted = other.sign ( Domain:: Change, & other_key, & h0) ;
749
repo.store ( ) .put_signature ( & h0, & adopted) .unwrap ( ) ;
750
assert! (
751
matches! ( repo.verify_change ( & h0) .unwrap ( ) , SignatureStatus:: Valid { name, .. } if name = = " Mallory" )
752
) ;
98286406 Andy McCall 1 month ago ↶
753
754
755
let mut sig = repo.store ( ) .signatures ( & h2) .unwrap ( ) .remove ( 0 ) ;
756
sig.ed[ 0 ] ^ = 1 ;
757
repo.store ( ) .put_signature ( & h2, & sig) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
758
assert! ( matches! (
759
repo.verify_change ( & h2) .unwrap ( ) ,
760
SignatureStatus:: Invalid( _ )
761
) ) ;
98286406 Andy McCall 1 month ago ↶
762
763
764
let no_keys = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " nowhere" ) ) ;
765
write ( root, " a.txt" , b " z\n " ) ;
766
assert! ( no_keys.commit ( " cannot sign" , & [ ] ) .is_err ( ) ) ;
767
}
768
769
# [ test ]
770
fn checkout_moves_the_working_copy ( ) {
771
hermetic ( ) ;
772
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
773
let root = dir.path ( ) ;
774
let repo = Repo:: init( root) .unwrap ( ) ;
775
write ( root, " a.txt" , b " one\n " ) ;
776
write ( root, " sub/deep/b.txt" , b " b\n " ) ;
777
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
778
write ( root, " a.txt" , b " two\n " ) ;
779
fs:: remove_file( root.join ( " sub/deep/b.txt" ) ) .unwrap ( ) ;
780
write ( root, " bin.dat" , & [ 0 , 1 , 2 , 3 ] ) ;
781
let h2 = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
782
783
let r = repo.checkout ( & [ h1] , false , " oxy checkout" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
784
assert_eq! (
785
r,
786
CheckoutReport {
787
written: 2 ,
788
removed: 1 ,
9b723f69 Andy McCall 25 days ago ↶
789
removed_paths: vec! [ " bin.dat" .into ( ) ] ,
75167672 Andy McCall 1 month ago
790
conflicted: false ,
d3cd7ef9 Andy McCall 1 month ago ↶
791
notes: vec! [ ] ,
792
lacks: 1 ,
4afe288b Andy McCall 11 days ago ↶
793
kept_both: vec! [ ] ,
75167672 Andy McCall 1 month ago
794
}
795
) ;
98286406 Andy McCall 1 month ago ↶
796
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n " ) ;
797
assert_eq! ( fs:: read( root.join ( " sub/deep/b.txt" ) ) .unwrap ( ) , b " b\n " ) ;
798
assert! ( ! root.join ( " bin.dat" ) .exists ( ) ) ;
799
let ( _ , wc) = repo.working_copy ( ) .unwrap ( ) ;
800
assert_eq! ( wc.deps, vec! [ h1] ) ;
75167672 Andy McCall 1 month ago
801
assert! (
802
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
803
" checkout leaves a clean working copy"
804
) ;
98286406 Andy McCall 1 month ago ↶
805
806
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
807
808
809
write ( root, " a.txt" , b " dirty\n " ) ;
810
assert! ( repo.checkout ( & [ h2] , false , " oxy checkout" ) .is_err ( ) ) ;
75167672 Andy McCall 1 month ago
811
let r = repo
812
.checkout ( & [ h2] , true , " oxy checkout --discard" )
813
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
814
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " two\n " ) ;
815
assert_eq! ( fs:: read( root.join ( " bin.dat" ) ) .unwrap ( ) , [ 0 , 1 , 2 , 3 ] ) ;
816
assert! ( ! root.join ( " sub" ) .exists ( ) , " empty directories are pruned" ) ;
817
assert_eq! ( r.removed, 1 ) ;
818
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
819
820
821
# [ cfg ( unix ) ]
822
{
823
fs:: remove_dir_all( root.join ( " sub" ) ) .ok ( ) ;
824
std:: os:: unix:: fs:: symlink( " /tmp" , root.join ( " sub" ) ) .unwrap ( ) ;
825
assert! ( repo.checkout ( & [ h1] , true , " oxy checkout" ) .is_err ( ) ) ;
826
}
827
}
828
829
# [ test ]
830
fn large_files_are_chunked_and_read_back ( ) {
831
hermetic ( ) ;
832
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
833
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
834
let mut big = vec! [ 0 u8 ; 700 * 1024 ] ;
835
for ( i, b) in big.iter_mut ( ) .enumerate ( ) {
836
* b = ( i as u32 ) .wrapping_mul ( 2654435761 ) .to_le_bytes ( ) [ 0 ] ;
837
}
838
let r = repo.store_blob ( & big) .unwrap ( ) ;
839
let blob = Blob:: decode( & repo.store ( ) .get_typed ( & r, ObjectType:: Blob) .unwrap ( ) ) .unwrap ( ) ;
840
assert! ( blob.chunks.len ( ) > = 3 , " {}" , blob.chunks.len ( ) ) ;
841
assert_eq! ( repo.read_blob ( & r) .unwrap ( ) , big) ;
842
assert_eq! ( Repo:: blob_ref( & big) , r) ;
843
}
844
845
# [ test ]
846
fn clone_push_pull_with_seals_and_attestations ( ) {
847
hermetic ( ) ;
848
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
849
let root = dir.path ( ) ;
850
let origin_path = root.join ( " origin" ) ;
851
let origin = Repo:: init( & origin_path) .unwrap ( ) ;
852
let origin_url = origin_path.to_string_lossy ( ) .to_string ( ) ;
853
854
75167672 Andy McCall 1 month ago
855
let mut a = Repo:: clone( & origin_url, & root.join ( " a" ) )
856
.unwrap ( )
857
.with_home ( root.join ( " home-a" ) ) ;
98286406 Andy McCall 1 month ago ↶
858
assert_eq! ( a.id ( ) .unwrap ( ) , origin.id ( ) .unwrap ( ) ) ;
75167672 Andy McCall 1 month ago
859
let ( key_a, _ ) = a
860
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
861
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
862
write ( & root.join ( " a" ) , " f.txt" , b " a\n " ) ;
863
let h1 = a.commit ( " first" , & [ ] ) .unwrap ( ) ;
864
let results = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
865
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
866
assert! ( results[ 0 ] .ok, " {}" , results[ 0 ] .reason) ;
867
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
868
75167672 Andy McCall 1 month ago
869
assert! (
870
matches! ( origin.authenticate_change ( & h1) .unwrap ( ) , SignatureStatus:: Valid { key, sealed: true , .. } if key = = key_a)
871
) ;
872
assert_eq! (
873
origin.verify_change ( & h1) .unwrap ( ) ,
874
SignatureStatus:: Unsigned
875
) ;
f8c69576 Andy McCall 1 month ago ↶
876
let serial_of = | att : & SignedAttest| match att.attest.body {
877
AttestBody:: Ref { serial, .. } => serial,
878
_ => unreachable! ( ) ,
879
} ;
98286406 Andy McCall 1 month ago ↶
880
let ( _ , att) = origin.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
881
882
883
let first = serial_of ( & att) ;
884
assert! ( first > 1_000_000_000_000 , " {first}" ) ;
98286406 Andy McCall 1 month ago ↶
885
let again = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
886
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
887
assert! ( again[ 0 ] .ok & & again[ 0 ] .reason.contains ( " up to date" ) ) ;
888
let ( _ , att) = origin.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
889
assert_eq! ( serial_of ( & att) , first, " a no-op push does not attest again" ) ;
98286406 Andy McCall 1 month ago ↶
890
891
892
let b = Repo:: clone( & origin_url, & root.join ( " b" ) ) .unwrap ( ) ;
893
assert_eq! ( fs:: read( root.join ( " b/f.txt" ) ) .unwrap ( ) , b " a\n " ) ;
894
assert_eq! ( b.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
75167672 Andy McCall 1 month ago
895
assert! ( matches! (
896
b.authenticate_change ( & h1) .unwrap ( ) ,
897
SignatureStatus:: Valid { sealed: true , .. }
898
) ) ;
98286406 Andy McCall 1 month ago ↶
899
900
901
write ( & root.join ( " b" ) , " f.txt" , b " a\n b\n " ) ;
902
let h2 = b.commit ( " second" , & [ ] ) .unwrap ( ) ;
903
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
904
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
905
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
906
907
908
write ( & root.join ( " a" ) , " g.txt" , b " g\n " ) ;
909
let h3 = a.commit ( " third" , & [ ] ) .unwrap ( ) ;
910
assert! ( a.push ( " origin" , false ) .is_err ( ) ) ;
911
let pr = a.pull ( " origin" ) .unwrap ( ) ;
912
assert! ( pr.changed & & pr.checked_out) ;
913
let mut want = vec! [ h2, h3] ;
914
want.sort ( ) ;
915
assert_eq! ( pr.heads, want) ;
916
assert_eq! ( fs:: read( root.join ( " a/f.txt" ) ) .unwrap ( ) , b " a\n b\n " ) ;
917
assert_eq! ( fs:: read( root.join ( " a/g.txt" ) ) .unwrap ( ) , b " g\n " ) ;
918
let results = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
919
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
920
assert! ( results[ 0 ] .ok, " {}" , results[ 0 ] .reason) ;
921
let mut origin_heads = origin.bookmark_heads ( ) .unwrap ( ) ;
922
origin_heads.sort ( ) ;
923
assert_eq! ( origin_heads, want) ;
924
let ( _ , att) = origin.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
925
assert_eq! ( serial_of ( & att) , first + 1 ) ;
98286406 Andy McCall 1 month ago ↶
926
927
928
let pr = b.pull ( " origin" ) .unwrap ( ) ;
929
assert_eq! ( pr.heads, want) ;
930
assert_eq! ( fs:: read( root.join ( " b/g.txt" ) ) .unwrap ( ) , b " g\n " ) ;
931
assert! ( ! b.snapshot ( " status" ) .unwrap ( ) .changed) ;
932
933
934
write ( & root.join ( " a" ) , " f.txt" , b " a\n A\n " ) ;
935
a.commit ( " a-edit" , & [ ] ) .unwrap ( ) ;
936
a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
937
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
938
write ( & root.join ( " b" ) , " f.txt" , b " a\n B\n " ) ;
939
b.commit ( " b-edit" , & [ ] ) .unwrap ( ) ;
940
let pr = b.pull ( " origin" ) .unwrap ( ) ;
941
assert! ( pr.changed & & pr.checked_out) ;
942
let text = fs:: read_to_string( root.join ( " b/f.txt" ) ) .unwrap ( ) ;
943
assert! ( text.contains ( " <<<<<<< oxy conflict" ) , " {text}" ) ;
944
write ( & root.join ( " b" ) , " f.txt" , b " a\n AB\n " ) ;
945
let hr = b.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
946
assert_eq! (
947
b.store ( ) .get_change ( & hr) .unwrap ( ) .kind,
948
ChangeKind:: Resolution
949
) ;
98286406 Andy McCall 1 month ago ↶
950
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
951
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
952
a.pull ( " origin" ) .unwrap ( ) ;
953
assert_eq! ( fs:: read( root.join ( " a/f.txt" ) ) .unwrap ( ) , b " a\n AB\n " ) ;
954
955
956
let mut want = vec! [ hr, h3] ;
957
want.sort ( ) ;
958
assert_eq! ( a.bookmark_heads ( ) .unwrap ( ) , want) ;
959
assert! ( ! a.load_state ( & want) .unwrap ( ) .materialize ( ) .is_conflicted ( ) ) ;
960
}
961
962
# [ test ]
963
fn receive_push_rejects_bad_updates ( ) {
964
hermetic ( ) ;
965
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
966
let root = dir.path ( ) ;
967
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
968
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
969
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
970
.unwrap ( )
971
.with_home ( root.join ( " home" ) ) ;
972
let ( key, _ ) = a
973
.create_identity ( " Ada" , " a@b" , IdentityScope:: Repo)
974
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
975
write ( & root.join ( " a" ) , " f.txt" , b " x\n " ) ;
976
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
977
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
978
let empty = origin.store ( ) .put_state ( & StateObject:: new( vec! [ ] ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
979
let s1 = origin
980
.store ( )
981
.put_state ( & StateObject:: new( vec! [ h1] ) )
982
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
983
984
75167672 Andy McCall 1 month ago
985
let ghost = origin
986
.store ( )
987
.put_state ( & StateObject:: new( vec! [ ObjectRef( [ 9 ; 32 ] ) ] ) )
988
.unwrap ( ) ;
989
let r = origin
990
.receive_push (
991
Bundle:: default( ) ,
992
vec! [ RefUpdate {
993
name: " main" .into ( ) ,
994
old_state: s1,
995
new_state: ghost,
996
force: false ,
997
attestation: None ,
998
} ] ,
999
None ,
1000
vec! [ ] ,
1001
)
1002
.unwrap ( ) ;
1003
assert! (
1004
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " missing" ) ,
1005
" {}" ,
1006
r[ 0 ] .reason
1007
) ;
98286406 Andy McCall 1 month ago ↶
1008
1009
75167672 Andy McCall 1 month ago
1010
let r = origin
1011
.receive_push (
1012
Bundle:: default( ) ,
1013
vec! [ RefUpdate {
1014
name: " main" .into ( ) ,
1015
old_state: empty,
1016
new_state: s1,
1017
force: false ,
1018
attestation: None ,
1019
} ] ,
1020
None ,
1021
vec! [ ] ,
1022
)
1023
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1024
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " stale" ) , " {}" , r[ 0 ] .reason) ;
1025
1026
75167672 Andy McCall 1 month ago
1027
let r = origin
1028
.receive_push (
1029
Bundle:: default( ) ,
1030
vec! [ RefUpdate {
1031
name: " main" .into ( ) ,
1032
old_state: s1,
1033
new_state: empty,
1034
force: false ,
1035
attestation: None ,
1036
} ] ,
1037
None ,
1038
vec! [ ] ,
1039
)
1040
.unwrap ( ) ;
1041
assert! (
1042
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " fast-forward" ) ,
1043
" {}" ,
1044
r[ 0 ] .reason
1045
) ;
1046
let r = origin
1047
.receive_push (
1048
Bundle:: default( ) ,
1049
vec! [ RefUpdate {
1050
name: " main" .into ( ) ,
1051
old_state: s1,
1052
new_state: empty,
1053
force: true ,
1054
attestation: None ,
1055
} ] ,
1056
None ,
1057
vec! [ ] ,
1058
)
1059
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1060
assert! ( r[ 0 ] .ok) ;
1061
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ ] ) ;
1062
1063
75167672 Andy McCall 1 month ago
1064
let sa = a
1065
.sign_attest ( & Attest {
1066
repo: a.id ( ) .unwrap ( ) ,
1067
hlc: Hlc:: now( ) ,
1068
body: AttestBody:: Ref {
1069
name: " main" .into ( ) ,
1070
old_state: empty,
1071
new_state: s1,
1072
serial: 1 ,
1073
} ,
1074
} )
1075
.unwrap ( ) ;
1076
let r = origin
1077
.receive_push (
1078
Bundle:: default( ) ,
1079
vec! [ RefUpdate {
1080
name: " main" .into ( ) ,
1081
old_state: empty,
1082
new_state: s1,
1083
force: false ,
1084
attestation: Some ( sa.clone ( ) ) ,
1085
} ] ,
1086
None ,
1087
vec! [ ] ,
1088
)
1089
.unwrap ( ) ;
1090
assert! (
1091
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " serial" ) ,
1092
" {}" ,
1093
r[ 0 ] .reason
1094
) ;
98286406 Andy McCall 1 month ago ↶
1095
75167672 Andy McCall 1 month ago
1096
let sa2 = a
1097
.sign_attest ( & Attest {
1098
repo: a.id ( ) .unwrap ( ) ,
1099
hlc: Hlc:: now( ) ,
1100
body: AttestBody:: Ref {
1101
name: " main" .into ( ) ,
1102
old_state: s1,
1103
new_state: empty,
1104
serial: 5 ,
1105
} ,
1106
} )
1107
.unwrap ( ) ;
1108
let r = origin
1109
.receive_push (
1110
Bundle:: default( ) ,
1111
vec! [ RefUpdate {
1112
name: " main" .into ( ) ,
1113
old_state: empty,
1114
new_state: s1,
1115
force: false ,
1116
attestation: Some ( sa2) ,
1117
} ] ,
1118
None ,
1119
vec! [ ] ,
1120
)
1121
.unwrap ( ) ;
1122
assert! (
1123
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " describe" ) ,
1124
" {}" ,
1125
r[ 0 ] .reason
1126
) ;
98286406 Andy McCall 1 month ago ↶
1127
let _ = key;
1128
}
1129
1130
# [ test ]
1131
fn user_config_supplies_defaults_and_clones_sign ( ) {
1132
hermetic ( ) ;
1133
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1134
let root = dir.path ( ) ;
1135
let home = root.join ( " home" ) ;
1136
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1137
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1138
1139
1140
let mut a = Repo:: init( & root.join ( " a" ) ) .unwrap ( ) .with_home ( & home) ;
75167672 Andy McCall 1 month ago
1141
let ( key, _ ) = a
1142
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1143
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1144
assert! ( ! a.config.repo_layer ( ) .contains_key ( " author.key" ) ) ;
1145
assert_eq! ( a.config.author_key, Some ( key) ) ;
1146
assert! ( home.join ( " config.toml" ) .exists ( ) ) ;
1147
a.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
1148
assert! ( a.config.sign_changes) ;
1149
1150
1151
a.config.set ( " core.sign" , " seal" ) .unwrap ( ) ;
1152
a.save_config ( ) .unwrap ( ) ;
1153
let again = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) .with_home ( & home) ;
1154
assert! ( ! again.config.sign_changes) ;
1155
assert_eq! ( again.config.author_key, Some ( key) ) ;
75167672 Andy McCall 1 month ago
1156
assert_eq! (
1157
read_config_file ( & root.join ( " a/.oxy/config" ) )
1158
.unwrap ( )
1159
.get ( " core.sign" )
1160
.map ( String :: as_str) ,
1161
Some ( " seal" )
1162
) ;
98286406 Andy McCall 1 month ago ↶
1163
1164
1165
1166
let b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) .with_home ( & home) ;
1167
assert! ( b.config.sign_changes) ;
1168
write ( & root.join ( " b" ) , " f.txt" , b " f\n " ) ;
1169
let h = b.commit ( " signed from a clone" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1170
assert! (
1171
matches! ( b.verify_change ( & h) .unwrap ( ) , SignatureStatus:: Valid { key: k, .. } if k = = key)
1172
) ;
98286406 Andy McCall 1 month ago ↶
1173
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
75167672 Andy McCall 1 month ago
1174
assert! ( matches! (
1175
origin.verify_change ( & h) .unwrap ( ) ,
1176
SignatureStatus:: Valid { .. }
1177
) ) ;
98286406 Andy McCall 1 month ago ↶
1178
}
1179
1180
# [ test ]
1181
fn format1_repositories_are_migrated_on_open ( ) {
1182
hermetic ( ) ;
1183
use crate :: oplog:: { Format1Op, Format1View} ;
1184
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1185
let root = dir.path ( ) ;
1186
let repo = Repo:: init( root) .unwrap ( ) ;
1187
write ( root, " a.txt" , b " a\n " ) ;
1188
let h1 = repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
1189
let ( wc_hash, _ ) = repo.working_copy ( ) .unwrap ( ) ;
1190
1191
let mut v1 = Format1View:: default( ) ;
1192
v1.bookmarks.insert ( " main" .into ( ) , vec! [ ] ) ;
1193
v1.workspaces.insert ( repo.workspace ( ) , wc_hash) ;
75167672 Andy McCall 1 month ago
1194
let init = Format1Op {
1195
parents: vec! [ ] ,
1196
hlc: Hlc:: default( ) ,
1197
command: " oxy init" .into ( ) ,
1198
view: v1.clone ( ) ,
1199
} ;
98286406 Andy McCall 1 month ago ↶
1200
let init_ref = repo.store ( ) .put ( ObjectType:: Op, & init.encode ( ) ) .unwrap ( ) ;
1201
v1.bookmarks.insert ( " main" .into ( ) , vec! [ h1] ) ;
75167672 Andy McCall 1 month ago
1202
let commit = Format1Op {
1203
parents: vec! [ init_ref] ,
1204
hlc: Hlc:: default( ) ,
1205
command: " oxy commit" .into ( ) ,
1206
view: v1,
1207
} ;
98286406 Andy McCall 1 month ago ↶
1208
let commit_ref = repo.store ( ) .put ( ObjectType:: Op, & commit.encode ( ) ) .unwrap ( ) ;
1209
write_atomic ( & root.join ( " .oxy/ops/head" ) , commit_ref.to_hex ( ) .as_bytes ( ) ) .unwrap ( ) ;
1210
write_atomic ( & root.join ( " .oxy/format" ) , FORMAT1_LINE .as_bytes ( ) ) .unwrap ( ) ;
1211
drop ( repo) ;
1212
1213
let repo = Repo:: open( root) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1214
assert_eq! (
1215
fs:: read_to_string( root.join ( " .oxy/format" ) ) .unwrap ( ) ,
1216
FORMAT_LINE
1217
) ;
98286406 Andy McCall 1 month ago ↶
1218
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
1219
let ops = repo.op_log ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1220
assert_eq! (
1221
ops.iter ( )
1222
.map ( | ( _, o ) | o.command.as_str ( ) )
1223
.collect:: < Vec< _ > > ( ) ,
1224
[ " oxy commit" , " oxy init" ]
1225
) ;
98286406 Andy McCall 1 month ago ↶
1226
assert_eq! ( repo.heads_of ( & ops[ 1 ] .1. view, " main" ) .unwrap ( ) , vec! [ ] ) ;
1227
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
1228
1229
assert_eq! ( Repo:: open( root) .unwrap ( ) .op_log ( ) .unwrap ( ) .len ( ) , 2 ) ;
1230
}
1231
1232
# [ test ]
1233
fn undo_skips_snapshot_operations ( ) {
1234
hermetic ( ) ;
1235
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1236
let root = dir.path ( ) ;
1237
let repo = Repo:: init( root) .unwrap ( ) ;
1238
write ( root, " a.txt" , b " a\n " ) ;
1239
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
1240
1241
write ( root, " b.txt" , b " b\n " ) ;
1242
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
1243
assert! ( s.changed) ;
1244
assert! ( repo.head_op ( ) .unwrap ( ) .1. is_snapshot ( ) ) ;
1245
assert_eq! ( repo.last_command ( ) .unwrap ( ) .command, " oxy commit -m \" a\" " ) ;
1246
1247
repo.undo ( ) .unwrap ( ) ;
1248
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ ] ) ;
1249
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
1250
let paths: Vec< _ > = s.statuses.iter ( ) .map ( | p | p.path.as_str ( ) ) .collect ( ) ;
75167672 Andy McCall 1 month ago
1251
assert_eq! (
1252
paths,
1253
[ " a.txt" , " b.txt" ] ,
1254
" both files are back in the working copy"
1255
) ;
98286406 Andy McCall 1 month ago ↶
1256
assert! ( repo.store ( ) .get_change ( & h1) .unwrap ( ) .deps.is_empty ( ) ) ;
1257
1258
assert_eq! ( repo.last_command ( ) .unwrap ( ) .command, " oxy init" ) ;
1259
assert! ( repo.undo ( ) .is_err ( ) ) ;
1260
}
1261
1262
# [ test ]
1263
fn independent_changes_commute_and_make_multiple_heads ( ) {
1264
hermetic ( ) ;
1265
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1266
let root = dir.path ( ) ;
1267
let repo = Repo:: init( root) .unwrap ( ) ;
1268
write ( root, " a.txt" , b " a\n " ) ;
1269
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
1270
write ( root, " b.txt" , b " b\n " ) ;
1271
let h2 = repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
1272
let c2 = repo.store ( ) .get_change ( & h2) .unwrap ( ) ;
1273
assert! ( c2.deps.is_empty ( ) , " b.txt touches nothing from h1" ) ;
1274
let mut heads = repo.bookmark_heads ( ) .unwrap ( ) ;
1275
heads.sort ( ) ;
1276
let mut want = vec! [ h1, h2] ;
1277
want.sort ( ) ;
1278
assert_eq! ( heads, want) ;
1279
1280
write ( root, " a.txt" , b " a\n a2\n " ) ;
1281
let h3 = repo.commit ( " a2" , & [ ] ) .unwrap ( ) ;
1282
assert_eq! ( repo.store ( ) .get_change ( & h3) .unwrap ( ) .deps, vec! [ h1] ) ;
1283
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
1284
assert_eq! ( heads, [ h2, h3] .into_iter ( ) .collect ( ) ) ;
1285
1286
write ( root, " c.txt" , b " c\n " ) ;
1287
let h4 = repo.commit ( " c" , & [ h2] ) .unwrap ( ) ;
1288
assert_eq! ( repo.store ( ) .get_change ( & h4) .unwrap ( ) .deps, vec! [ h2] ) ;
1289
}
1290
1291
# [ test ]
1292
fn mode_change_and_text_binary_switch ( ) {
1293
hermetic ( ) ;
1294
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1295
let root = dir.path ( ) ;
1296
let repo = Repo:: init( root) .unwrap ( ) ;
1297
write ( root, " run.sh" , b " #!/bin/sh\n " ) ;
1298
repo.commit ( " add script" , & [ ] ) .unwrap ( ) ;
1299
# [ cfg ( unix ) ]
1300
{
1301
use std:: os:: unix:: fs:: PermissionsExt;
1302
fs:: set_permissions( root.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
1303
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1304
assert_eq! (
1305
statuses ( & s) ,
1306
vec! [ ( " run.sh" .into ( ) , ChangeStatus:: ModeChanged) ]
1307
) ;
98286406 Andy McCall 1 month ago ↶
1308
let h = repo.commit ( " exec" , & [ ] ) .unwrap ( ) ;
1309
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1310
assert! ( snap.files[ " run.sh" ] .mode.exec) ;
1311
}
1312
1313
write ( root, " run.sh" , b " \0 binary" ) ;
1314
let h = repo.commit ( " binary" , & [ ] ) .unwrap ( ) ;
1315
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1316
assert! ( snap.files[ " run.sh" ] .mode.opaque) ;
1317
write ( root, " run.sh" , b " text again\n " ) ;
1318
let h = repo.commit ( " text" , & [ ] ) .unwrap ( ) ;
1319
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1320
assert! ( ! snap.files[ " run.sh" ] .mode.opaque) ;
75167672 Andy McCall 1 month ago
1321
assert! (
1322
matches! ( & snap.files[ " run.sh" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " text again\n " )
1323
) ;
98286406 Andy McCall 1 month ago ↶
1324
assert! ( ! snap.is_conflicted ( ) ) ;
1325
}
83e0677b Andy McCall 1 month ago ↶
1326
6eae06b6 Andy McCall 29 days ago ↶
1327
1328
1329
# [ cfg ( unix ) ]
1330
# [ test ]
1331
fn a_binary_file_changed_and_made_executable_at_once_keeps_both ( ) {
1332
use std:: os:: unix:: fs:: PermissionsExt;
1333
hermetic ( ) ;
1334
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1335
let root = dir.path ( ) ;
1336
let repo = Repo:: init( root) .unwrap ( ) ;
1337
write ( root, " tool.exe" , b " MZ\0 one" ) ;
1338
repo.commit ( " add" , & [ ] ) .unwrap ( ) ;
1339
write ( root, " tool.exe" , b " MZ\0 two" ) ;
1340
fs:: set_permissions( root.join ( " tool.exe" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
1341
let h = repo.commit ( " update and +x" , & [ ] ) .unwrap ( ) ;
1342
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1343
let f = & snap.files[ " tool.exe" ] ;
1344
assert! ( f.mode.opaque & & f.mode.exec, " {:?}" , f.mode) ;
1345
assert! (
1346
repo.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ,
1347
" nothing left to commit"
1348
) ;
1349
}
1350
f0a517d2 Andy McCall 20 days ago ↶
1351
1352
1353
1354
83e0677b Andy McCall 1 month ago ↶
1355
# [ test ]
f0a517d2 Andy McCall 20 days ago ↶
1356
fn a_certified_key_pushes_under_a_subject_grant ( ) {
1357
use crate :: cert:: { Certificate, SignedCertificate} ;
1358
use crate :: identity:: Scope;
1359
hermetic ( ) ;
1360
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1361
let root = dir.path ( ) ;
1362
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1363
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1364
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1365
.unwrap ( )
1366
.with_home ( root.join ( " home-a" ) ) ;
1367
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1368
.unwrap ( ) ;
1369
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1370
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1371
a.policy_init ( ) .unwrap ( ) ;
1372
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1373
let hub_sk = SecretKey:: generate( ) ;
1374
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1375
a.policy_grant_subject (
1376
" agent:claude@ada" ,
1377
hub.clone ( ) ,
1378
Scope:: CHANGES | Scope:: REFS ,
1379
None ,
1380
)
1381
.unwrap ( ) ;
1382
assert! ( a
1383
.push ( " origin" , false )
1384
.unwrap ( )
1385
.iter ( )
1386
.any ( | r | r.name = = " policy" & & r.ok ) ) ;
1387
1388
let now = Hlc:: now( ) .wall_ms;
1389
1390
1391
let agent = | name : & str , subject : & str , from : u64 , to : u64 | {
1392
let mut r = Repo:: clone( & url, & root.join ( name) )
1393
.unwrap ( )
1394
.with_home ( root.join ( format! ( " home-{name} " ) ) ) ;
1395
let ( _ , id) = r
1396
.create_identity ( name, " [email protected] " , IdentityScope:: User)
1397
.unwrap ( ) ;
1398
let cert = SignedCertificate:: sign(
1399
Certificate {
1400
key: id,
1401
issuer: hub.hash ( ) ,
1402
subject: subject.into ( ) ,
1403
claims: vec! [ ] ,
1404
scope: Scope:: CHANGES | Scope:: REFS ,
1405
not_before: from,
1406
expires: to,
a2387d54 Andy McCall 20 days ago ↶
1407
grant: None ,
f0a517d2 Andy McCall 20 days ago ↶
1408
} ,
1409
& hub,
1410
& hub_sk,
1411
) ;
1412
r.store ( ) .put_identity ( & hub) .unwrap ( ) ;
1413
r.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1414
write ( & root.join ( name) , & format! ( " {name} .txt" ) , b " agent work\n " ) ;
1415
r.commit ( " work" , & [ ] ) .unwrap ( ) ;
1416
r
1417
} ;
1418
1419
let claude = agent ( " claude" , " agent:claude@ada" , now - 60_000 , now + 600_000 ) ;
1420
let r = claude.push ( " origin" , false ) .unwrap ( ) ;
1421
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
1422
1423
a.pull ( " origin" ) .unwrap ( ) ;
1424
assert! ( a.bookmark_heads ( ) .unwrap ( ) .iter ( ) .any ( | h | a
1425
.store ( )
1426
.get_change ( h)
1427
.map ( | c | c.message = = " work" )
1428
.unwrap_or ( false ) ) ) ;
1429
f53cbe7a Andy McCall 20 days ago ↶
1430
1431
1432
1433
1434
let host = Repo:: open( & root.join ( " origin" ) )
1435
.unwrap ( )
1436
.with_acceptor ( hub.clone ( ) , hub_sk.clone ( ) ) ;
1437
let ( attest_ref, sa) = host.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
1438
host.record_acceptance ( & attest_ref) .unwrap ( ) ;
1439
let served = host.remote_refs ( ) .unwrap ( ) ;
1440
let acc = served
1441
.refs
1442
.iter ( )
1443
.find ( | r | r.name = = " main" )
1444
.unwrap ( )
1445
.accepted
1446
.clone ( )
1447
.expect ( " served with the refs" ) ;
1448
let signer = sa.sigs[ 0 ] .key;
1449
assert_eq! (
1450
a.certified_time ( & signer, & sa, Some ( & acc) ) ,
1451
acc.attest.hlc.wall_ms
1452
) ;
1453
assert_eq! ( a.certified_time ( & signer, & sa, None ) , sa.attest.hlc.wall_ms) ;
1454
let stranger_sk = SecretKey:: generate( ) ;
1455
let stranger = stranger_sk.identity ( " stranger" , " s@x" , 1 ) ;
1456
let mut forged = acc.clone ( ) ;
1457
forged.sigs = vec! [ stranger_sk.sign ( Domain:: Attest, & stranger.hash ( ) , & acc.attest.hash ( ) ) ] ;
1458
assert_eq! (
1459
a.certified_time ( & signer, & sa, Some ( & forged) ) ,
1460
sa.attest.hlc.wall_ms,
1461
" not the issuer's"
1462
) ;
1463
let mut elsewhere = acc.clone ( ) ;
1464
elsewhere.attest.body = AttestBody:: Accepted {
1465
attest: ObjectRef( [ 7 ; 32 ] ) ,
1466
} ;
1467
elsewhere.sigs = vec! [ hub_sk.sign ( Domain:: Attest, & hub.hash ( ) , & elsewhere.attest.hash ( ) ) ] ;
1468
assert_eq! (
1469
a.certified_time ( & signer, & sa, Some ( & elsewhere) ) ,
1470
sa.attest.hlc.wall_ms,
1471
" another attestation's"
1472
) ;
1473
a.pull ( " origin" ) .unwrap ( ) ;
1474
1475
host.gc ( true ) .unwrap ( ) ;
1476
assert! ( host.acceptance_of ( & attest_ref) .unwrap ( ) .is_some ( ) ) ;
1477
f0a517d2 Andy McCall 20 days ago ↶
1478
let late = agent ( " late" , " agent:claude@ada" , now - 1_200_000 , now - 600_000 ) ;
1479
let r = late.push ( " origin" , false ) .unwrap ( ) ;
1480
assert! (
1481
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " may not update" ) ,
1482
" an expired certificate: {r:?}"
1483
) ;
1484
let other = agent ( " other" , " agent:mallory@ada" , now - 60_000 , now + 600_000 ) ;
1485
let r = other.push ( " origin" , false ) .unwrap ( ) ;
1486
assert! (
1487
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " may not update" ) ,
1488
" a subject not granted: {r:?}"
1489
) ;
1490
}
1491
d8524a9f Andy McCall 17 days ago ↶
1492
1493
1494
1495
1496
# [ test ]
1497
fn a_tag_signed_by_an_earlier_short_lived_key_is_pushed ( ) {
1498
use crate :: cert:: { Certificate, SignedCertificate} ;
1499
use crate :: identity:: Scope;
1500
hermetic ( ) ;
1501
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1502
let root = dir.path ( ) ;
1503
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1504
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1505
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1506
.unwrap ( )
1507
.with_home ( root.join ( " home-a" ) ) ;
1508
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1509
.unwrap ( ) ;
1510
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1511
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1512
a.policy_init ( ) .unwrap ( ) ;
1513
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1514
let hub_sk = SecretKey:: generate( ) ;
1515
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1516
a.policy_grant_subject (
1517
" agent:claude@ada" ,
1518
hub.clone ( ) ,
1519
Scope:: CHANGES | Scope:: REFS ,
1520
Some ( " *" ) ,
1521
)
1522
.unwrap ( ) ;
1523
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1524
1525
let now = Hlc:: now( ) .wall_ms;
1526
let mut agent = Repo:: clone( & url, & root.join ( " agent" ) )
1527
.unwrap ( )
1528
.with_home ( root.join ( " home-agent" ) ) ;
1529
agent.store ( ) .put_identity ( & hub) .unwrap ( ) ;
1530
let certify = | agent : & Repo, id | {
1531
let cert = SignedCertificate:: sign(
1532
Certificate {
1533
key: id,
1534
issuer: hub.hash ( ) ,
1535
subject: " agent:claude@ada" .into ( ) ,
1536
claims: vec! [ ] ,
1537
scope: Scope:: CHANGES | Scope:: REFS ,
1538
not_before: now - 60_000 ,
1539
expires: now + 600_000 ,
1540
grant: None ,
1541
} ,
1542
& hub,
1543
& hub_sk,
1544
) ;
1545
agent.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1546
} ;
1547
1548
let ( first, first_id) = agent
1549
.create_identity ( " claude" , " [email protected] " , IdentityScope:: User)
1550
.unwrap ( ) ;
1551
certify ( & agent, first_id) ;
1552
let heads = agent.bookmark_heads ( ) .unwrap ( ) ;
1553
agent.tag_create ( " v1" , & heads, " first" ) .unwrap ( ) ;
1554
1555
let ( second, second_id) = agent
1556
.create_identity ( " claude" , " [email protected] " , IdentityScope:: User)
1557
.unwrap ( ) ;
1558
assert_ne! ( first, second) ;
1559
certify ( & agent, second_id) ;
1560
agent
1561
.set_user_config ( " author.signer" , & second.to_hex ( ) )
1562
.unwrap ( ) ;
1563
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
1ec6fdbc Andy McCall 17 days ago ↶
1564
assert! (
1565
host.store ( ) .get_identity ( & first) .is_err ( ) ,
1566
" the host has never seen the tag's key"
1567
) ;
d8524a9f Andy McCall 17 days ago ↶
1568
let r = agent.push ( " origin" , false ) .unwrap ( ) ;
1ec6fdbc Andy McCall 17 days ago ↶
1569
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
1570
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
1571
assert! (
1572
host.tag_refs ( ) .unwrap ( ) .iter ( ) .any ( | t | t.name = = " v1" ) ,
1573
" the tag arrived"
1574
) ;
1575
}
1576
1577
1578
1579
1580
1581
1582
1583
# [ test ]
1584
fn a_short_lived_key_s_tag_is_fetched_at_its_issuer_s_acceptance ( ) {
1585
use crate :: cert:: { Certificate, SignedCertificate} ;
1586
use crate :: identity:: Scope;
1587
hermetic ( ) ;
1588
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1589
let root = dir.path ( ) ;
1590
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1591
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1592
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1593
.unwrap ( )
1594
.with_home ( root.join ( " home-a" ) ) ;
1595
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1596
.unwrap ( ) ;
1597
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1598
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1599
a.policy_init ( ) .unwrap ( ) ;
1600
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1601
let hub_sk = SecretKey:: generate( ) ;
1602
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1603
a.policy_grant_subject (
1604
" agent:claude@ada" ,
1605
hub.clone ( ) ,
1606
Scope:: CHANGES | Scope:: REFS ,
1607
Some ( " *" ) ,
1608
)
1609
.unwrap ( ) ;
1610
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1611
1612
let now = Hlc:: now( ) .wall_ms;
1613
1614
let agent = | name : & str , from : u64 , to : u64 | {
1615
let mut r = Repo:: clone( & url, & root.join ( name) )
1616
.unwrap ( )
1617
.with_home ( root.join ( format! ( " home-{name} " ) ) ) ;
1618
r.store ( ) .put_identity ( & hub) .unwrap ( ) ;
1619
let ( _ , id) = r
1620
.create_identity ( name, " [email protected] " , IdentityScope:: User)
1621
.unwrap ( ) ;
1622
let cert = SignedCertificate:: sign(
1623
Certificate {
1624
key: id,
1625
issuer: hub.hash ( ) ,
1626
subject: " agent:claude@ada" .into ( ) ,
1627
claims: vec! [ ] ,
1628
scope: Scope:: CHANGES | Scope:: REFS ,
1629
not_before: from,
1630
expires: to,
1631
grant: None ,
1632
} ,
1633
& hub,
1634
& hub_sk,
1635
) ;
1636
r.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1637
r
1638
} ;
1639
1640
1641
1642
1643
let claude = agent ( " claude" , now - 60_000 , now + 600_000 ) ;
1644
let heads = claude.bookmark_heads ( ) .unwrap ( ) ;
1645
claude.tag_create ( " v1" , & heads, " one" ) .unwrap ( ) ;
1646
let r = claude.push ( " origin" , false ) .unwrap ( ) ;
d8524a9f Andy McCall 17 days ago ↶
1647
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
1ec6fdbc Andy McCall 17 days ago ↶
1648
let b = Repo:: clone( & url, & root.join ( " b" ) )
1649
.unwrap ( )
1650
.with_home ( root.join ( " home-b" ) ) ;
1651
let rep = b.fetch ( " origin" ) .unwrap ( ) ;
1652
assert! (
1653
rep.skipped
1654
.iter ( )
1655
.any ( | ( n , why ) | n = = " tags/v1" & & why.contains ( " no time to check its certificate at" ) ) ,
1656
" {rep:?}"
1657
) ;
1658
assert_eq! ( b.tag_heads ( " v1" ) .unwrap ( ) , None ) ;
1659
1660
1661
1662
let host = Repo:: open( & root.join ( " origin" ) )
1663
.unwrap ( )
1664
.with_acceptor ( hub.clone ( ) , hub_sk.clone ( ) ) ;
1665
let ( v1, _ ) = host.tag_attest ( " v1" ) .unwrap ( ) .unwrap ( ) ;
1666
host.record_acceptance ( & v1) .unwrap ( ) ;
1667
let served = host.tag_refs ( ) .unwrap ( ) ;
1668
assert! ( served
1669
.iter ( )
1670
.any ( | t | t.name = = " v1" & & t.accepted.is_some ( ) ) ) ;
1671
let rep = b.fetch ( " origin" ) .unwrap ( ) ;
1672
assert! ( rep.skipped.is_empty ( ) , " {rep:?}" ) ;
1673
assert_eq! ( b.tag_heads ( " v1" ) .unwrap ( ) , Some ( heads.clone ( ) ) ) ;
1674
1675
host.gc ( true ) .unwrap ( ) ;
1676
assert! ( host.acceptance_of ( & v1) .unwrap ( ) .is_some ( ) ) ;
1677
1678
1679
1680
let late = agent ( " late" , now - 7_200_000 , now - 3_600_000 ) ;
1681
late.tag_create ( " v2" , & heads, " two" ) .unwrap ( ) ;
1682
let r = late.push ( " origin" , false ) .unwrap ( ) ;
1683
assert! (
1684
r.iter ( )
1685
.any ( | x | x.name = = " tags/v2" & & ! x.ok & & x.reason.contains ( " may not create" ) ) ,
1686
" {r:?}"
1687
) ;
1688
1689
1690
1691
let ( v2, _ ) = late.tag_attest ( " v2" ) .unwrap ( ) .unwrap ( ) ;
1692
let acc = Attest {
1693
repo: late.id ( ) .unwrap ( ) ,
1694
hlc: Hlc {
1695
wall_ms: now - 5_400_000 ,
1696
logical: 0 ,
1697
} ,
1698
body: AttestBody:: Accepted { attest: v2 } ,
1699
} ;
1700
let sig = hub_sk.sign ( Domain:: Attest, & hub.hash ( ) , & acc.hash ( ) ) ;
1701
1702
let stranger_sk = SecretKey:: generate( ) ;
1703
let stranger = stranger_sk.identity ( " stranger" , " [email protected] " , 1 ) ;
1704
let forged = stranger_sk.sign ( Domain:: Attest, & stranger.hash ( ) , & acc.hash ( ) ) ;
1705
late.keep_acceptance (
1706
& v2,
1707
& SignedAttest {
1708
attest: acc.clone ( ) ,
1709
sigs: vec! [ forged] ,
1710
} ,
1711
)
1712
.unwrap ( ) ;
1713
let r = late.push ( " origin" , false ) .unwrap ( ) ;
1714
assert! (
1715
r.iter ( ) .any ( | x | x.name = = " tags/v2" & & ! x.ok ) ,
1716
" not the issuer's: {r:?}"
1717
) ;
1718
let theirs = SignedAttest {
1719
attest: acc,
1720
sigs: vec! [ sig] ,
1721
} ;
1722
1723
late.keep_acceptance ( & v2, & theirs) .unwrap ( ) ;
1724
let r = late.push ( " origin" , false ) .unwrap ( ) ;
1725
1726
1727
assert! ( r.iter ( ) .any ( | x | x.name = = " tags/v2" & & x.ok ) , " {r:?}" ) ;
d8524a9f Andy McCall 17 days ago ↶
1728
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
1ec6fdbc Andy McCall 17 days ago ↶
1729
let ( hv2, _ ) = host.tag_attest ( " v2" ) .unwrap ( ) .unwrap ( ) ;
1730
assert_eq! ( hv2, v2) ;
1731
assert! ( host
1732
.store ( )
1733
.index_get_pub ( " accepted" , & v2)
1734
.unwrap ( )
1735
.contains ( & theirs.attest.hash ( ) ) ) ;
1736
let c = Repo:: clone( & url, & root.join ( " c" ) )
1737
.unwrap ( )
1738
.with_home ( root.join ( " home-c" ) ) ;
1739
assert_eq! ( c.tag_heads ( " v2" ) .unwrap ( ) , Some ( heads.clone ( ) ) ) ;
1740
assert_eq! ( c.tag_heads ( " v1" ) .unwrap ( ) , Some ( heads) ) ;
d8524a9f Andy McCall 17 days ago ↶
1741
}
1742
d36ba2f7 Andy McCall 18 days ago ↶
1743
1744
1745
1746
1747
1748
# [ test ]
1749
fn a_bookmark_moved_by_a_certified_key_alone_is_fetched ( ) {
1750
use crate :: cert:: { Certificate, SignedCertificate} ;
1751
use crate :: identity:: Scope;
1752
hermetic ( ) ;
1753
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1754
let root = dir.path ( ) ;
1755
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1756
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1757
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1758
.unwrap ( )
1759
.with_home ( root.join ( " home-a" ) ) ;
1760
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1761
.unwrap ( ) ;
1762
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1763
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1764
a.policy_init ( ) .unwrap ( ) ;
1765
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1766
let hub_sk = SecretKey:: generate( ) ;
1767
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1768
a.policy_grant_subject (
1769
" agent:claude@ada" ,
1770
hub.clone ( ) ,
1771
Scope:: CHANGES | Scope:: REFS ,
1772
None ,
1773
)
1774
.unwrap ( ) ;
1775
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1776
1777
let now = Hlc:: now( ) .wall_ms;
1778
let mut agent = Repo:: clone( & url, & root.join ( " agent" ) )
1779
.unwrap ( )
1780
.with_home ( root.join ( " home-agent" ) ) ;
1781
let ( key, id) = agent
1782
.create_identity ( " claude" , " [email protected] " , IdentityScope:: User)
1783
.unwrap ( ) ;
1784
let cert = SignedCertificate:: sign(
1785
Certificate {
1786
key: id,
1787
issuer: hub.hash ( ) ,
1788
subject: " agent:claude@ada" .into ( ) ,
1789
claims: vec! [ ] ,
1790
scope: Scope:: CHANGES | Scope:: REFS ,
1791
not_before: now - 60_000 ,
1792
expires: now + 600_000 ,
1793
grant: None ,
1794
} ,
1795
& hub,
1796
& hub_sk,
1797
) ;
1798
agent.store ( ) .put_identity ( & hub) .unwrap ( ) ;
1799
agent.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1800
let heads = agent.bookmark_heads ( ) .unwrap ( ) ;
1801
agent.bookmark_create ( " agent-work" , Some ( & heads) ) .unwrap ( ) ;
1802
let r = agent.push_bookmark ( " origin" , " agent-work" , false ) .unwrap ( ) ;
1803
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
1804
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
1805
let ( _ , sa) = host.latest_ref_attest ( " agent-work" ) .unwrap ( ) .unwrap ( ) ;
1806
assert_eq! ( sa.sigs[ 0 ] .key, key, " the certified key alone moved it" ) ;
1807
1808
1809
let bundle = host.bundle_raw_for ( & [ ] , None ) .unwrap ( ) ;
1810
assert! ( bundle.changes.is_empty ( ) & & bundle.raw_changes.is_empty ( ) ) ;
1811
assert_eq! ( bundle.certificates.len ( ) , 1 ) ;
1812
for k in [ key, hub.hash ( ) ] {
1813
assert! ( bundle
1814
.objects
1815
.iter ( )
1816
.any ( | ( t , b ) | * t = = ObjectType:: Identity & & hash_object ( * t, b) = = k) ) ;
1817
}
1818
1819
let c = Repo:: clone( & url, & root.join ( " c" ) ) .unwrap ( ) ;
1820
let tracked = c.view ( ) .unwrap ( ) .remotes[ " origin" ] .clone ( ) ;
1821
assert_eq! (
1822
tracked.get ( " agent-work" ) ,
1823
host.view ( ) .unwrap ( ) .bookmarks.get ( " agent-work" )
1824
) ;
1825
assert! ( c.store ( ) .certificate_of ( & key) .unwrap ( ) .is_some ( ) ) ;
1826
}
1827
028545df Andy McCall 19 days ago ↶
1828
1829
1830
1831
1832
1833
1834
# [ test ]
1835
fn a_fetched_attestation_is_judged_at_its_integrated_time ( ) {
1836
use super :: transparency:: LogCheck;
1837
use crate :: cert:: { Certificate, SignedCertificate} ;
1838
use crate :: identity:: Scope;
1839
use crate :: protocol:: { Integration, Logged, RefsResponse, RemoteRef} ;
1840
use crate :: tlog:: { self , Checkpoint, Entry, Hash, Integrated, Note} ;
1841
hermetic ( ) ;
1842
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1843
let root = dir.path ( ) ;
1844
let mut a = Repo:: init( & root.join ( " a" ) )
1845
.unwrap ( )
1846
.with_home ( root.join ( " home-a" ) ) ;
1847
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1848
.unwrap ( ) ;
1849
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1850
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1851
a.policy_init ( ) .unwrap ( ) ;
1852
let issuer_sk = SecretKey:: generate( ) ;
1853
let issuer = issuer_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1854
a.policy_grant_subject (
1855
" agent:claude@ada" ,
1856
issuer.clone ( ) ,
1857
Scope:: CHANGES | Scope:: REFS ,
1858
None ,
1859
)
1860
.unwrap ( ) ;
1861
let p = a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy;
1862
1863
1864
let now = Hlc:: now( ) .wall_ms;
1865
let t0 = now - 2 * 3_600_000 ;
1866
let agent_sk = SecretKey:: generate( ) ;
1867
let agent = agent_sk.identity ( " claude" , " [email protected] " , t0) ;
1868
let cert = SignedCertificate:: sign(
1869
Certificate {
1870
key: agent.clone ( ) ,
1871
issuer: issuer.hash ( ) ,
1872
subject: " agent:claude@ada" .into ( ) ,
1873
claims: vec! [ ] ,
1874
scope: Scope:: CHANGES | Scope:: REFS ,
1875
not_before: t0,
1876
expires: t0 + 600_000 ,
1877
grant: None ,
1878
} ,
1879
& issuer,
1880
& issuer_sk,
1881
) ;
1882
a.store ( ) .put_identity ( & agent) .unwrap ( ) ;
1883
a.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1884
let signer = agent.hash ( ) ;
1885
let attested = | claimed : u64 | {
1886
let attest = Attest {
1887
repo: a.id ( ) .unwrap ( ) ,
1888
hlc: Hlc {
1889
wall_ms: claimed,
1890
logical: 0 ,
1891
} ,
1892
body: AttestBody:: Ref {
1893
name: " main" .into ( ) ,
1894
old_state: ObjectRef( [ 1 ; 32 ] ) ,
1895
new_state: ObjectRef( [ 2 ; 32 ] ) ,
1896
serial: 1 ,
1897
} ,
1898
} ;
1899
let sig = agent_sk.sign ( Domain:: Attest, & signer, & attest.hash ( ) ) ;
1900
SignedAttest {
1901
attest,
1902
sigs: vec! [ sig] ,
1903
}
1904
} ;
1905
1906
1907
1908
let log_sk = SecretKey:: generate( ) ;
1909
let log_key = log_sk.identity ( " hub.example" , " [email protected] " , 1 ) ;
1910
let origin = " hub.example/log" ;
1911
let hub = " https://hub.example" ;
1912
let url = " https://hub.example/ada/a" ;
1913
let w_sk = SecretKey:: generate( ) ;
1914
let w = w_sk.identity ( " w" , " [email protected] " , 1 ) ;
1915
let stranger_sk = SecretKey:: generate( ) ;
1916
let stranger = stranger_sk.identity ( " s" , " [email protected] " , 1 ) ;
1917
let sa = attested ( t0 + 300_000 ) ;
1918
let entry_of = | i : u64 | {
1919
let ( kind, object) = if i = = 3 {
1920
( " ref" , sa.attest.hash ( ) )
1921
} else {
1922
( " seal" , ObjectRef( [ i as u8 ; 32 ] ) )
1923
} ;
1924
Entry {
1925
kind: kind.into ( ) ,
1926
repo: sa.attest.repo,
1927
object,
1928
signers: vec! [ signer] ,
1929
received: t0 + 300_000 ,
1930
}
1931
.encode ( )
1932
} ;
1933
let mut stored: Vec< Hash> = Vec :: new( ) ;
1934
for i in 0 .. 8 {
1935
let add = tlog:: stored_hashes_for_leaf( i, tlog:: leaf_hash( & entry_of ( i) ) , & stored) .unwrap ( ) ;
1936
stored.extend ( add) ;
1937
}
1938
let note_of = | n : u64 , cosigners : & [( & str , & Identity, & SecretKey, u64 ) ]| {
1939
let mut note = Checkpoint {
1940
origin: origin.into ( ) ,
1941
size: n,
1942
root: tlog:: tree_hash( n, & stored) .unwrap ( ) ,
1943
extensions: vec! [ ] ,
1944
}
1945
.sign ( & log_key, & log_sk)
1946
.unwrap ( ) ;
1947
for ( name, id, sk, t) in cosigners {
1948
note.sigs
1949
.extend ( tlog:: cosign( & note.text, name, id, sk, * t) .unwrap ( ) ) ;
1950
}
1951
note.encode ( )
1952
} ;
1953
let latest = note_of ( 8 , & [ ] ) ;
1954
let witnessed_at = ( t0 + 360_000 ) / 1000 ;
1955
let cosigned = note_of (
1956
4 ,
1957
& [
1958
( " w.example" , & w, & w_sk, witnessed_at) ,
1959
( " s.example" , & stranger, & stranger_sk, 1 ) ,
1960
] ,
1961
) ;
1962
let answer = | integration : Vec< Integration> , witnesses : Vec< ( String, Vec< u8 > ) > | RefsResponse {
1963
refs: vec! [ RemoteRef {
1964
name: " main" .into ( ) ,
1965
state: ObjectRef( [ 2 ; 32 ] ) ,
1966
heads: vec! [ ] ,
1967
attestation: Some ( sa.clone ( ) ) ,
1968
last_serial: None ,
1969
accepted: None ,
1970
logged: Some ( Logged {
1971
index: 3 ,
1972
entry: entry_of ( 3 ) ,
1973
proof: tlog:: prove_inclusion( 8 , 3 , & stored) .unwrap ( ) ,
1974
} ) ,
1975
integration,
1976
} ] ,
1977
log: Some ( latest.clone ( ) ) ,
1978
witnesses,
1979
.. RefsResponse:: default( )
1980
} ;
1981
let proof4 = tlog:: prove_inclusion( 4 , 3 , & stored) .unwrap ( ) ;
1982
let served = vec! [ Integration {
1983
note: cosigned.clone ( ) ,
1984
proof: proof4.clone ( ) ,
1985
} ] ;
1986
let listed = vec! [ ( " w.example" .to_string ( ) , w.encode ( ) ) ] ;
1987
1988
a.remember_log ( hub, & log_key, & Note:: parse( & latest) .unwrap ( ) )
1989
.unwrap ( ) ;
1990
1991
1992
1993
let resp = answer ( served.clone ( ) , listed.clone ( ) ) ;
1994
let lc = a.check_log ( url, None , & resp) .unwrap ( ) ;
1995
assert! ( lc.notes.is_empty ( ) , " {:?}" , lc.notes) ;
1996
let t = lc.time_of ( " main" ) .unwrap ( ) ;
1997
assert_eq! ( ( t.time, t.witness.as_str ( ) ) , ( witnessed_at, " w.example" ) ) ;
1998
1999
2000
assert! ( cert.cert.expires < now) ;
2001
let mut notes = Vec :: new( ) ;
2002
a.authorize_in_time ( & p, " origin" , & resp.refs[ 0 ] , & sa, & signer, & lc, & mut notes)
2003
.unwrap ( ) ;
2004
assert! ( notes.is_empty ( ) , " {notes:?}" ) ;
2005
2006
2007
2008
let lonely = vec! [ Integration {
2009
note: note_of ( 4 , & [ ( " s.example" , & stranger, & stranger_sk, 1 ) ] ) ,
2010
proof: proof4.clone ( ) ,
2011
} ] ;
2012
let resp = answer ( lonely, vec! [ ] ) ;
2013
let lc = a.check_log ( url, None , & resp) .unwrap ( ) ;
2014
assert! ( lc
2015
.time_of ( " main" )
2016
.unwrap_err ( )
2017
.contains ( " no witness this user trusts" ) ) ;
2018
let mut notes = Vec :: new( ) ;
2019
a.authorize_in_time ( & p, " origin" , & resp.refs[ 0 ] , & sa, & signer, & lc, & mut notes)
2020
.unwrap ( ) ;
2021
assert! (
2022
notes.len ( ) = = 1 & & notes[ 0 ] .contains ( " not at a witnessed time" ) ,
2023
" {notes:?}"
2024
) ;
2025
2026
2027
2028
let resp = answer (
2029
served.clone ( ) ,
2030
vec! [ ( " w.example" .into ( ) , stranger.encode ( ) ) ] ,
2031
) ;
2032
let lc = a.check_log ( url, None , & resp) .unwrap ( ) ;
2033
assert! (
2034
lc.notes.iter ( ) .any ( | n | n.contains ( " another key" ) ) ,
2035
" {:?}" ,
2036
lc.notes
2037
) ;
2038
assert! (
2039
lc.time_of ( " main" ) .is_err ( ) ,
2040
" w's cosignature is by its old key"
2041
) ;
2042
a.check_log ( url, None , & answer ( served.clone ( ) , listed.clone ( ) ) )
2043
.unwrap ( ) ;
2044
2045
2046
2047
let mut old = answer ( vec! [ ] , vec! [ ] ) ;
2048
old.log = None ;
2049
old.refs[ 0 ] .logged = None ;
2050
let lc = a.check_log ( url, None , & old) .unwrap ( ) ;
2051
assert! ( ! lc.kept & & lc.notes.is_empty ( ) ) ;
2052
let mut notes = Vec :: new( ) ;
2053
a.authorize_in_time ( & p, " origin" , & old.refs[ 0 ] , & sa, & signer, & lc, & mut notes)
2054
.unwrap ( ) ;
2055
assert! ( notes.is_empty ( ) , " {notes:?}" ) ;
2056
2057
2058
let at = | secs_after_t0 : u64 | {
2059
let mut times = std:: collections:: HashMap:: new( ) ;
2060
times.insert (
2061
" main" .to_string ( ) ,
2062
Ok ( Integrated {
2063
time: ( t0 + secs_after_t0 * 1000 ) / 1000 ,
2064
witness: " w.example" .into ( ) ,
2065
size: 4 ,
2066
} ) ,
2067
) ;
2068
LogCheck {
2069
kept: true ,
2070
times,
2071
.. LogCheck:: default( )
2072
}
2073
} ;
2074
let judged = | sa : & SignedAttest, lc : & LogCheck| {
2075
let mut notes = Vec :: new( ) ;
2076
a.authorize_in_time ( & p, " origin" , & old.refs[ 0 ] , sa, & signer, lc, & mut notes)
2077
.map ( | _| notes )
2078
} ;
2079
2080
2081
2082
let notes = judged ( & sa, & at ( 3600 ) ) .unwrap ( ) ;
2083
assert! (
2084
notes
2085
.iter ( )
2086
.any ( | n | n.contains ( " had lapsed when witness w.example" ) )
2087
& & notes
2088
.iter ( )
2089
.any ( | n | n.contains ( " 55 minutes before witness" ) ) ,
2090
" {notes:?}"
2091
) ;
2092
2093
2094
2095
let fast = attested ( now) ;
2096
let notes = judged ( & fast, & at ( 400 ) ) .unwrap ( ) ;
2097
assert! (
2098
notes.len ( ) = = 1 & & notes[ 0 ] .contains ( " after witness w.example saw it" ) ,
2099
" {notes:?}"
2100
) ;
2101
2102
assert! ( judged ( & fast, & at ( 3600 ) ) .is_err ( ) ) ;
2103
2104
assert! ( judged ( & sa, & at ( 500 ) ) .unwrap ( ) .is_empty ( ) ) ;
2105
}
2106
a2387d54 Andy McCall 20 days ago ↶
2107
2108
2109
2110
45a6f5ec Andy McCall 13 days ago ↶
2111
2112
2113
f0a517d2 Andy McCall 20 days ago ↶
2114
# [ test ]
45a6f5ec Andy McCall 13 days ago ↶
2115
fn a_grant_outlives_its_owner_s_rotation ( ) {
2116
use crate :: cert:: { Certificate, Grant, SignedCertificate, SignedGrant} ;
2117
use crate :: identity:: Scope;
2118
hermetic ( ) ;
2119
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2120
let root = dir.path ( ) ;
2121
let mut a = Repo:: init( & root.join ( " a" ) )
2122
.unwrap ( )
2123
.with_home ( root.join ( " home-a" ) ) ;
2124
let ( old, _ ) = a
2125
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
2126
.unwrap ( ) ;
2127
let ( _ , old_sk) = a.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
2128
let old_id = a.identity ( & old) .unwrap ( ) ;
2129
let hub_sk = SecretKey:: generate( ) ;
2130
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
2131
let now = Hlc:: now( ) .wall_ms;
2132
let grant = | from : u64 | {
2133
SignedGrant:: sign(
2134
Grant {
2135
grantor: old,
2136
subject: " agent:claude@ada" .into ( ) ,
2137
issuer: hub.hash ( ) ,
2138
scope: Scope:: CHANGES | Scope:: SEAL ,
2139
not_before: from,
2140
expires: now + 86_400_000 ,
2141
} ,
2142
& old_id,
2143
& old_sk,
2144
)
2145
} ;
2146
let before = grant ( now - 60_000 ) ;
2147
let after = grant ( now + 60_000 ) ;
2148
let rot = a.renew_identity ( None ) .unwrap ( ) ;
2149
assert_ne! ( rot.new, old) ;
2150
let agent = | name : & str , g : SignedGrant| {
2151
let mut r = Repo:: init( & root.join ( name) )
2152
.unwrap ( )
2153
.with_home ( root.join ( format! ( " home-{name} " ) ) ) ;
2154
let ( key, id) = r
2155
.create_identity ( name, " [email protected] " , IdentityScope:: User)
2156
.unwrap ( ) ;
2157
for i in [ & old_id, & rot.identity, & hub] {
2158
r.store ( ) .put_identity ( i) .unwrap ( ) ;
2159
}
2160
r.set_user_config ( " author.key" , & rot.new.to_hex ( ) ) .unwrap ( ) ;
2161
r.set_user_config ( " author.signer" , & key.to_hex ( ) ) .unwrap ( ) ;
2162
let cert = SignedCertificate:: sign(
2163
Certificate {
2164
key: id,
2165
issuer: hub.hash ( ) ,
2166
subject: " agent:claude@ada" .into ( ) ,
2167
claims: vec! [ ] ,
2168
scope: Scope:: CHANGES | Scope:: REFS | Scope:: SEAL ,
2169
not_before: now - 60_000 ,
2170
expires: now + 600_000 ,
2171
grant: Some ( g) ,
2172
} ,
2173
& hub,
2174
& hub_sk,
2175
) ;
2176
r.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
2177
write ( & root.join ( name) , " work.txt" , b " agent work\n " ) ;
2178
let h = r.commit ( " work" , & [ ] ) .unwrap ( ) ;
2179
assert_eq! ( r.store ( ) .get_change_meta ( & h) .unwrap ( ) .author, rot.new) ;
2180
r.seal ( & [ h] ) .unwrap ( ) ;
2181
( r, h)
2182
} ;
2183
let ( r, h) = agent ( " kept" , before) ;
2184
assert! (
2185
matches! (
2186
r.authenticate_change ( & h) .unwrap ( ) ,
2187
SignatureStatus:: Valid { .. }
2188
) ,
2189
" {:?}" ,
2190
r.authenticate_change ( & h) .unwrap ( )
2191
) ;
2192
let ( r, h) = agent ( " late" , after) ;
2193
assert! (
2194
matches! ( r.authenticate_change ( & h) .unwrap ( ) , SignatureStatus:: Invalid( e) if e.contains ( " neither" ) ) ,
2195
" a grant the old key made after the rotation: {:?}" ,
2196
r.authenticate_change ( & h) .unwrap ( )
2197
) ;
2198
}
2199
2200
# [ test ]
a2387d54 Andy McCall 20 days ago ↶
2201
fn a_certified_key_acts_for_its_author_under_a_grant ( ) {
2202
use crate :: cert:: { Certificate, Grant, SignedCertificate, SignedGrant} ;
2203
use crate :: identity:: { Identity, Scope} ;
2204
hermetic ( ) ;
2205
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2206
let root = dir.path ( ) ;
2207
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
2208
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
2209
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
2210
.unwrap ( )
2211
.with_home ( root.join ( " home-a" ) ) ;
2212
let ( ada, _ ) = a
2213
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
2214
.unwrap ( ) ;
2215
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
2216
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
2217
a.policy_init ( ) .unwrap ( ) ;
2218
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
2219
let hub_sk = SecretKey:: generate( ) ;
2220
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
2221
a.policy_grant_subject (
2222
" agent:claude@ada" ,
2223
hub.clone ( ) ,
2224
Scope:: CHANGES | Scope:: REFS ,
2225
None ,
2226
)
2227
.unwrap ( ) ;
2228
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
2229
let ( _ , ada_sk) = a.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
2230
let ada_id = a.identity ( & ada) .unwrap ( ) ;
2231
2232
let now = Hlc:: now( ) .wall_ms;
2233
let grant = | by : & Identity, sk : & SecretKey, from : u64 , to : u64 | {
2234
SignedGrant:: sign(
2235
Grant {
2236
grantor: by.hash ( ) ,
2237
subject: " agent:claude@ada" .into ( ) ,
2238
issuer: hub.hash ( ) ,
2239
scope: Scope:: CHANGES | Scope:: SEAL ,
2240
not_before: from,
2241
expires: to,
2242
} ,
2243
by,
2244
sk,
2245
)
2246
} ;
2247
2248
2249
let agent = | name : & str , g : Option< SignedGrant> | {
2250
let mut r = Repo:: clone( & url, & root.join ( name) )
2251
.unwrap ( )
2252
.with_home ( root.join ( format! ( " home-{name} " ) ) ) ;
2253
let ( key, id) = r
2254
.create_identity ( name, " [email protected] " , IdentityScope:: User)
2255
.unwrap ( ) ;
2256
r.set_user_config ( " author.key" , & ada.to_hex ( ) ) .unwrap ( ) ;
2257
r.set_user_config ( " author.signer" , & key.to_hex ( ) ) .unwrap ( ) ;
2258
let cert = SignedCertificate:: sign(
2259
Certificate {
2260
key: id,
2261
issuer: hub.hash ( ) ,
2262
subject: " agent:claude@ada" .into ( ) ,
2263
claims: vec! [ ] ,
2264
scope: Scope:: CHANGES | Scope:: REFS | Scope:: SEAL ,
2265
not_before: now - 60_000 ,
2266
expires: now + 600_000 ,
2267
grant: g,
2268
} ,
2269
& hub,
2270
& hub_sk,
2271
) ;
2272
r.store ( ) .put_identity ( & hub) .unwrap ( ) ;
2273
r.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
2274
write ( & root.join ( name) , & format! ( " {name} .txt" ) , b " agent work\n " ) ;
2275
let h = r.commit ( " work" , & [ ] ) .unwrap ( ) ;
2276
assert_eq! ( r.store ( ) .get_change_meta ( & h) .unwrap ( ) .author, ada) ;
2277
( r, key, h)
2278
} ;
2279
2280
let ( claude, key, h) = agent (
2281
" claude" ,
2282
Some ( grant ( & ada_id, & ada_sk, now - 60_000 , now + 86_400_000 ) ) ,
2283
) ;
2284
let r = claude.push ( " origin" , false ) .unwrap ( ) ;
2285
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
2286
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
68dcf21f Andy McCall 20 days ago ↶
2287
assert! (
2288
matches! (
2289
host.authenticate_change ( & h) .unwrap ( ) ,
2290
SignatureStatus:: Valid { key: k, sealed: true , name, .. } if k = = key & & name = = " agent:claude@ada"
2291
) ,
2292
" named by its certificate's subject"
2293
) ;
a2387d54 Andy McCall 20 days ago ↶
2294
a.pull ( " origin" ) .unwrap ( ) ;
2295
assert! ( matches! (
2296
a.authenticate_change ( & h) .unwrap ( ) ,
2297
SignatureStatus:: Valid { key: k, .. } if k = = key
2298
) ) ;
2299
2300
let ( none, _ , _ ) = agent ( " none" , None ) ;
2301
let r = none.push ( " origin" , false ) .unwrap ( ) ;
2302
assert! ( r.iter ( ) .all ( | x | ! x.ok) , " no grant: {r:?}" ) ;
2303
let mallory_sk = SecretKey:: generate( ) ;
2304
let mallory = mallory_sk.identity ( " Mallory" , " [email protected] " , 1 ) ;
2305
let ( stranger, _ , _ ) = agent (
2306
" stranger" ,
2307
Some ( grant ( & mallory, & mallory_sk, now - 60_000 , now + 600_000 ) ) ,
2308
) ;
2309
let r = stranger.push ( " origin" , false ) .unwrap ( ) ;
2310
assert! ( r.iter ( ) .all ( | x | ! x.ok) , " a stranger's grant: {r:?}" ) ;
2311
let ( ended, _ , eh) = agent (
2312
" ended" ,
2313
Some ( grant ( & ada_id, & ada_sk, now - 7_200_000 , now - 3_600_000 ) ) ,
2314
) ;
2315
let r = ended.push ( " origin" , false ) .unwrap ( ) ;
2316
assert! ( r.iter ( ) .all ( | x | ! x.ok) , " an ended grant: {r:?}" ) ;
2317
assert! ( matches! (
2318
ended.authenticate_change ( & eh) .unwrap ( ) ,
2319
SignatureStatus:: Invalid( e) if e.contains ( " grant did not cover" )
2320
) ) ;
2321
}
2322
2323
# [ test ]
83e0677b Andy McCall 1 month ago ↶
2324
fn trust_policy_governs_pushes_and_fetches ( ) {
2325
hermetic ( ) ;
2326
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2327
let root = dir.path ( ) ;
2328
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
2329
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
2330
2331
75167672 Andy McCall 1 month ago
2332
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
2333
.unwrap ( )
2334
.with_home ( root.join ( " home-a" ) ) ;
2335
let ( key_a, _ ) = a
2336
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
2337
.unwrap ( ) ;
2338
let mut b = Repo:: clone( & url, & root.join ( " b" ) )
2339
.unwrap ( )
2340
.with_home ( root.join ( " home-b" ) ) ;
83e0677b Andy McCall 1 month ago ↶
2341
write ( & root.join ( " b" ) , " b.txt" , b " b\n " ) ;
2342
b.commit ( " unsigned before policy" , & [ ] ) .unwrap ( ) ;
2343
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
2344
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2345
2346
2347
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
2348
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
2349
let sp = a.policy_init ( ) .unwrap ( ) ;
2350
assert_eq! ( sp.policy.serial, 1 ) ;
2351
a.pull ( " origin" ) .unwrap ( ) ;
2352
let results = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
2353
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2354
assert! ( results.iter ( ) .all ( | r | r.ok ) , " {results:?}" ) ;
2355
assert_eq! ( origin.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 1 ) ;
2356
2357
2358
b.pull ( " origin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2359
assert! (
2360
b.current_policy ( ) .unwrap ( ) .is_some ( ) ,
2361
" the policy arrived with the fetch"
2362
) ;
83e0677b Andy McCall 1 month ago ↶
2363
assert! ( b.push ( " origin" , false ) .is_err ( ) ) ;
2364
75167672 Andy McCall 1 month ago
2365
let ( key_b, id_b) = b
2366
.create_identity ( " Bob" , " [email protected] " , IdentityScope:: User)
2367
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2368
write ( & root.join ( " b" ) , " b.txt" , b " b2\n " ) ;
2369
let h_b2 = b.commit ( " b2 as Bob" , & [ ] ) .unwrap ( ) ;
2370
assert_eq! ( b.store ( ) .get_change ( & h_b2) .unwrap ( ) .author, key_b) ;
2371
let r = b.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
2372
origin.refresh ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2373
assert! (
2374
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " may not update" ) ,
2375
" {:?}" ,
2376
r
2377
) ;
83e0677b Andy McCall 1 month ago ↶
2378
2379
2380
let sp = a.policy_grant ( id_b.clone ( ) , None ) .unwrap ( ) ;
2381
assert_eq! ( sp.policy.serial, 2 ) ;
2382
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
2383
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2384
assert! ( r.iter ( ) .any ( | x | x.name = = " policy" & & x.ok ) , " {r:?}" ) ;
2385
75167672 Andy McCall 1 month ago
2386
assert! ( b
2387
.push ( " origin" , false )
2388
.unwrap_or_else ( | e | vec! [ RefResult {
2389
name: " main" .into ( ) ,
2390
ok: false ,
2391
reason: e.to_string ( )
2392
} ] )
2393
.iter ( )
2394
.all ( | x | ! x.ok) ) ;
83e0677b Andy McCall 1 month ago ↶
2395
b.pull ( " origin" ) .unwrap ( ) ;
2396
assert_eq! ( b.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 2 ) ;
2397
let r = b.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
2398
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2399
assert! ( r[ 0 ] .ok, " {:?}" , r) ;
2400
2401
let heads = origin.bookmark_heads ( ) .unwrap ( ) ;
2402
for h in & heads {
2403
let c = origin.store ( ) .get_change ( h) .unwrap ( ) ;
2404
if c.author = = key_b {
75167672 Andy McCall 1 month ago
2405
origin
2406
.change_allowed (
2407
h,
2408
& origin.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy,
2409
" main" ,
2410
)
2411
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2412
}
2413
}
2414
2415
2416
let mut forged = b.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.clone ( ) ;
2417
forged.serial = 3 ;
2418
forged.roots = vec! [ key_b] ;
2419
let fh = forged.hash ( ) ;
2420
let ( _ , sk_b) = b.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2421
let forged = SignedPolicy {
2422
policy: forged,
2423
sigs: vec! [ sk_b.sign ( Domain:: Policy, & key_b, & fh) ] ,
2424
} ;
2425
let r = origin
2426
.receive_push ( Bundle:: default( ) , vec! [ ] , Some ( forged) , vec! [ ] )
2427
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2428
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " root" ) , " {:?}" , r) ;
2429
let stale = a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1 ;
2430
let mut old = stale.clone ( ) ;
2431
old.policy.serial = 1 ;
75167672 Andy McCall 1 month ago
2432
let r = origin
2433
.receive_push ( Bundle:: default( ) , vec! [ ] , Some ( old) , vec! [ ] )
2434
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2435
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " serial" ) , " {:?}" , r) ;
2436
2437
2438
a.pull ( " origin" ) .unwrap ( ) ;
2439
a.policy_revoke ( & key_b) .unwrap ( ) ;
2440
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
2441
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2442
assert! ( r.iter ( ) .any ( | x | x.name = = " policy" & & x.ok ) , " {r:?}" ) ;
2443
b.pull ( " origin" ) .unwrap ( ) ;
2444
write ( & root.join ( " b" ) , " b.txt" , b " b3\n " ) ;
2445
b.commit ( " b3" , & [ ] ) .unwrap ( ) ;
2446
let r = b.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
2447
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2448
assert! ( ! r[ 0 ] .ok, " {:?}" , r) ;
2449
2450
75167672 Andy McCall 1 month ago
2451
let c = Repo:: clone( & url, & root.join ( " c" ) )
2452
.unwrap ( )
2453
.with_home ( root.join ( " home-c" ) ) ;
83e0677b Andy McCall 1 month ago ↶
2454
assert_eq! ( c.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 3 ) ;
75167672 Andy McCall 1 month ago
2455
assert! ( c
2456
.current_policy ( )
2457
.unwrap ( )
2458
.unwrap ( )
2459
.1
2460
.policy
2461
.roots
2462
.contains ( & key_a) ) ;
83e0677b Andy McCall 1 month ago ↶
2463
assert! ( ! c.snapshot ( " status" ) .unwrap ( ) .changed) ;
2464
}
d02e545d Andy McCall 1 month ago ↶
2465
9ae4d15a Andy McCall 1 month ago ↶
2466
d02e545d Andy McCall 1 month ago ↶
2467
# [ test ]
9ae4d15a Andy McCall 1 month ago ↶
2468
fn gc_reports_its_phases ( ) {
2469
hermetic ( ) ;
2470
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2471
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
2472
for i in 0 .. 3 {
2473
write (
2474
dir.path ( ) ,
2475
& format! ( " f{i} .txt" ) ,
2476
format! ( " {i} \n " ) .as_bytes ( ) ,
2477
) ;
2478
repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ;
2479
}
2480
let mut phases = Vec :: new( ) ;
2481
repo.gc_with ( true , & mut | p| phases.push ( p.to_string ( ) ) )
2482
.unwrap ( ) ;
2483
assert_eq! ( phases[ 0 ] , " finding what is reachable" , " {phases:?}" ) ;
2484
assert! (
2485
phases.iter ( ) .any ( | p | p.starts_with ( " pruning" ) ) ,
2486
" {phases:?}"
2487
) ;
2488
assert! (
2489
phases.iter ( ) .any ( | p | p.starts_with ( " packing 0 of " ) ) ,
2490
" {phases:?}"
2491
) ;
2492
assert! (
2493
phases
2494
.iter ( )
2495
.any ( | p | p.starts_with ( " writing the index of " ) ) ,
2496
" {phases:?}"
2497
) ;
2498
}
2499
2500
# [ test ]
d02e545d Andy McCall 1 month ago ↶
2501
fn gc_packs_objects_and_history_still_works ( ) {
2502
hermetic ( ) ;
2503
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2504
let root = dir.path ( ) ;
2505
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
2506
repo.create_identity ( " Ada" , " a@b" , IdentityScope:: User)
2507
.unwrap ( ) ;
d02e545d Andy McCall 1 month ago ↶
2508
repo.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
2509
write ( root, " a.txt" , b " one\n " ) ;
2510
let h1 = repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
2511
write ( root, " a.txt" , b " two\n " ) ;
2512
write ( root, " big.bin" , & vec! [ 0 u8 ; 100_000 ] ) ;
2513
let h2 = repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
ba160f60 Andy McCall 1 month ago ↶
2514
2515
for i in 0 .. 5 {
2516
write ( root, " a.txt" , format! ( " two\n {i} \n " ) .as_bytes ( ) ) ;
2517
repo.snapshot ( " status" ) .unwrap ( ) ;
2518
}
2519
write ( root, " a.txt" , b " two\n " ) ;
2520
repo.snapshot ( " status" ) .unwrap ( ) ;
d02e545d Andy McCall 1 month ago ↶
2521
let before = repo.store ( ) .list ( ) .unwrap ( ) ;
2522
assert! ( repo.store ( ) .loose_count ( ) .unwrap ( ) > 0 ) ;
ba160f60 Andy McCall 1 month ago ↶
2523
2524
2525
let r = repo.gc ( true ) .unwrap ( ) ;
2526
assert_eq! ( r.packed.objects, before.len ( ) ) ;
2527
assert_eq! ( r.pruned.objects, 0 ) ;
d02e545d Andy McCall 1 month ago ↶
2528
assert_eq! ( repo.store ( ) .loose_count ( ) .unwrap ( ) , 0 ) ;
2529
assert_eq! ( repo.store ( ) .list ( ) .unwrap ( ) , before) ;
ba160f60 Andy McCall 1 month ago ↶
2530
2531
2532
2533
repo.config.set ( " core.op-retention" , " 0" ) .unwrap ( ) ;
2534
repo.save_config ( ) .unwrap ( ) ;
21b2aec2 Andy McCall 1 month ago ↶
2535
let mut repo = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
ba160f60 Andy McCall 1 month ago ↶
2536
let r = repo.gc ( true ) .unwrap ( ) ;
2537
assert! ( r.pruned.objects > = 5 , " {r:?}" ) ;
2538
let after = repo.store ( ) .list ( ) .unwrap ( ) ;
2539
assert! ( after.len ( ) < before.len ( ) ) ;
2540
assert! ( after.contains ( & h1) & & after.contains ( & h2) ) ;
2541
for ( _ , op) in repo.op_log ( ) .unwrap ( ) {
2542
assert! ( ! op.command.is_empty ( ) , " operation objects are all kept" ) ;
2543
}
2544
d02e545d Andy McCall 1 month ago ↶
2545
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
75167672 Andy McCall 1 month ago
2546
assert! ( matches! (
2547
repo.verify_change ( & h2) .unwrap ( ) ,
2548
SignatureStatus:: Valid { .. }
2549
) ) ;
d02e545d Andy McCall 1 month ago ↶
2550
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 3 ) ;
2551
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
2552
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n " ) ;
2553
repo.checkout ( & [ h2] , false , " checkout" ) .unwrap ( ) ;
2554
assert_eq! ( fs:: read( root.join ( " big.bin" ) ) .unwrap ( ) .len ( ) , 100_000 ) ;
2555
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
ba160f60 Andy McCall 1 month ago ↶
2556
d02e545d Andy McCall 1 month ago ↶
2557
write ( root, " a.txt" , b " three\n " ) ;
2558
repo.commit ( " three" , & [ ] ) .unwrap ( ) ;
ba160f60 Andy McCall 1 month ago ↶
2559
let r = repo.gc ( false ) .unwrap ( ) ;
2560
assert_eq! ( r.pruned.objects, 0 ) ;
2561
assert! ( r.pruned.deferred > 0 ) ;
2562
let r = repo.gc ( true ) .unwrap ( ) ;
2563
assert! ( r.pruned.objects > 0 ) ;
2564
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 ) ;
21b2aec2 Andy McCall 1 month ago ↶
2565
2566
2567
repo.config.set ( " core.op-retention" , " 90" ) .unwrap ( ) ;
2568
repo.save_config ( ) .unwrap ( ) ;
2569
let repo = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2570
let r = repo.gc ( false ) .unwrap ( ) ;
2571
assert_eq! ( r.pruned.objects, 0 ) ;
2572
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 ) ;
d02e545d Andy McCall 1 month ago ↶
2573
}
614541c3 Andy McCall 1 month ago ↶
2574
2575
# [ test ]
2576
fn describe_amend_abandon_heads ( ) {
2577
hermetic ( ) ;
2578
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2579
let root = dir.path ( ) ;
2580
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
2581
repo.create_identity ( " Ada" , " a@b" , IdentityScope:: User)
2582
.unwrap ( ) ;
614541c3 Andy McCall 1 month ago ↶
2583
repo.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
2584
write ( root, " a.txt" , b " one\n " ) ;
2585
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
2586
write ( root, " a.txt" , b " one\n two\n " ) ;
2587
let h2 = repo.commit ( " secnod" , & [ ] ) .unwrap ( ) ;
2588
let id2 = repo.store ( ) .get_change ( & h2) .unwrap ( ) .change_id;
2589
2590
2591
let h2b = repo.describe ( & h2, " second" ) .unwrap ( ) ;
2592
assert_ne! ( h2b, h2) ;
2593
let c = repo.store ( ) .get_change ( & h2b) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2594
assert_eq! (
2595
( c.change_id, c.message.as_str ( ) , c.deps.clone ( ) ) ,
2596
( id2, " second" , vec! [ h1] )
2597
) ;
614541c3 Andy McCall 1 month ago ↶
2598
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2b] ) ;
75167672 Andy McCall 1 month ago
2599
assert! ( matches! (
2600
repo.verify_change ( & h2b) .unwrap ( ) ,
2601
SignatureStatus:: Valid { .. }
2602
) ) ;
614541c3 Andy McCall 1 month ago ↶
2603
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
2604
assert! ( s.statuses.is_empty ( ) , " {:?}" , s.statuses) ;
75167672 Andy McCall 1 month ago
2605
assert_eq! (
2606
repo.resolve_change ( & id2.short ( 8 ) ) .unwrap ( ) ,
2607
h2b,
2608
" the old object is no longer reachable from the log"
2609
) ;
c07e0e7d Andy McCall 1 month ago ↶
2610
2611
let h1b = repo.describe ( & h1, " first, described" ) .unwrap ( ) ;
2612
assert_ne! ( h1b, h1) ;
2613
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
2614
assert_eq! ( heads.len ( ) , 1 ) ;
2615
let top = repo.store ( ) .get_change ( & heads[ 0 ] ) .unwrap ( ) ;
2616
assert_eq! ( ( top.change_id, top.deps.clone ( ) ) , ( id2, vec! [ h1b] ) ) ;
75167672 Andy McCall 1 month ago
2617
assert! (
2618
matches! (
2619
repo.verify_change ( & heads[ 0 ] ) .unwrap ( ) ,
2620
SignatureStatus:: Valid { .. }
2621
) ,
2622
" rewritten own change is re-signed"
2623
) ;
c07e0e7d Andy McCall 1 month ago ↶
2624
let ( _ , wc) = repo.working_copy ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2625
assert_eq! (
2626
wc.deps,
2627
vec! [ heads[ 0 ] ] ,
2628
" the working copy follows the rewrite"
2629
) ;
c07e0e7d Andy McCall 1 month ago ↶
2630
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
2631
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
2632
assert! (
2633
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n " )
2634
) ;
614541c3 Andy McCall 1 month ago ↶
2635
2636
2637
write ( root, " a.txt" , b " one\n two\n three\n " ) ;
2638
write ( root, " b.txt" , b " b\n " ) ;
2639
let h2c = repo.amend ( None ) .unwrap ( ) ;
2640
let c = repo.store ( ) .get_change ( & h2c) .unwrap ( ) ;
2641
assert_eq! ( c.change_id, id2) ;
2642
assert_eq! ( c.message, " second" ) ;
2643
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2c] ) ;
75167672 Andy McCall 1 month ago
2644
assert_eq! (
2645
repo.log ( ) .unwrap ( ) .len ( ) ,
2646
3 ,
2647
" working copy, amended head, first"
2648
) ;
614541c3 Andy McCall 1 month ago ↶
2649
let snap = repo.load_state ( & [ h2c] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
2650
assert! (
2651
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n three\n " )
2652
) ;
614541c3 Andy McCall 1 month ago ↶
2653
assert! ( snap.files.contains_key ( " b.txt" ) ) ;
2654
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
75167672 Andy McCall 1 month ago
2655
assert! (
2656
repo.amend ( None ) .is_err ( ) ,
2657
" clean working copy and no message"
2658
) ;
614541c3 Andy McCall 1 month ago ↶
2659
let h2d = repo.amend ( Some ( " second, amended" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2660
assert_eq! (
2661
repo.store ( ) .get_change ( & h2d) .unwrap ( ) .message,
2662
" second, amended"
2663
) ;
614541c3 Andy McCall 1 month ago ↶
2664
2665
2666
repo.abandon ( & h2d) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2667
assert_eq! (
2668
repo.bookmark_heads ( ) .unwrap ( ) ,
2669
vec! [ h1b] ,
2670
" the described first change is the head again"
2671
) ;
614541c3 Andy McCall 1 month ago ↶
2672
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2673
let paths: Vec< _ > = s
2674
.statuses
2675
.iter ( )
2676
.map ( | p | ( p.path.as_str ( ) , p.status) )
2677
.collect ( ) ;
2678
assert_eq! (
2679
paths,
2680
vec! [
2681
( " a.txt" , ChangeStatus:: Modified) ,
2682
( " b.txt" , ChangeStatus:: Added)
2683
]
2684
) ;
614541c3 Andy McCall 1 month ago ↶
2685
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n two\n three\n " ) ;
2686
let h3 = repo.commit ( " recommitted" , & [ ] ) .unwrap ( ) ;
2687
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h3] ) ;
2688
2689
repo.undo ( ) .unwrap ( ) ;
2690
repo.undo ( ) .unwrap ( ) ;
2691
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2d] ) ;
c07e0e7d Andy McCall 1 month ago ↶
2692
6706bdcf Andy McCall 1 month ago ↶
2693
2694
2695
c07e0e7d Andy McCall 1 month ago ↶
2696
let base = repo.bookmark_heads ( ) .unwrap ( ) [ 0 ] ;
2697
let base_change = repo.store ( ) .get_change ( & base) .unwrap ( ) ;
2698
let h1_now = base_change.deps[ 0 ] ;
6706bdcf Andy McCall 1 month ago ↶
2699
let notes = repo.abandon ( & h1_now) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2700
assert! (
2701
! notes.is_empty ( ) ,
2702
" the dependent's edit of a.txt was reified"
2703
) ;
6706bdcf Andy McCall 1 month ago ↶
2704
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
2705
let after = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
2706
assert! (
2707
! after.files.contains_key ( " a.txt" ) & & after.files.contains_key ( " b.txt" ) ,
2708
" {:?}" ,
2709
after.files.keys ( )
2710
) ;
6706bdcf Andy McCall 1 month ago ↶
2711
repo.undo ( ) .unwrap ( ) ;
2712
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ base] ) ;
c07e0e7d Andy McCall 1 month ago ↶
2713
write ( root, " c.txt" , b " c\n " ) ;
2714
let hc = repo.commit ( " c" , & [ ] ) .unwrap ( ) ;
2715
write ( root, " d.txt" , b " d\n " ) ;
2716
let hd = repo.commit ( " d" , & [ hc] ) .unwrap ( ) ;
2717
assert_eq! ( repo.store ( ) .get_change ( & hd) .unwrap ( ) .deps, vec! [ hc] ) ;
2718
repo.abandon ( & hc) .unwrap ( ) ;
2719
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
2720
assert! ( ! heads.contains ( & hc) ) ;
75167672 Andy McCall 1 month ago
2721
let d_now = heads
2722
.iter ( )
2723
.find ( | h | repo.store ( ) .get_change ( h) .unwrap ( ) .message = = " d" )
2724
.copied ( )
2725
.unwrap ( ) ;
2726
assert! (
2727
repo.store ( ) .get_change ( & d_now) .unwrap ( ) .deps.is_empty ( ) ,
2728
" d inherited c's (empty) dependencies"
2729
) ;
c07e0e7d Andy McCall 1 month ago ↶
2730
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2731
assert_eq! (
2732
s.statuses
2733
.iter ( )
2734
.map ( | p | p.path.as_str ( ) )
2735
.collect:: < Vec< _ > > ( ) ,
2736
vec! [ " c.txt" ] ,
2737
" c's content is now uncommitted work"
2738
) ;
614541c3 Andy McCall 1 month ago ↶
2739
}
79ab8528 Andy McCall 1 month ago ↶
2740
2741
# [ test ]
2742
fn git_export_mirrors_history ( ) {
2743
hermetic ( ) ;
75167672 Andy McCall 1 month ago
2744
if std:: process:: Command:: new( " git" )
2745
.arg ( " --version" )
2746
.output ( )
2747
.is_err ( )
2748
{
79ab8528 Andy McCall 1 month ago ↶
2749
eprintln! ( " git not installed; skipping" ) ;
2750
return ;
2751
}
2752
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2753
let root = dir.path ( ) ;
75167672 Andy McCall 1 month ago
2754
let mut repo = Repo:: init( & root.join ( " r" ) )
2755
.unwrap ( )
2756
.with_home ( root.join ( " home" ) ) ;
2757
repo.create_identity ( " Ada Lovelace" , " [email protected] " , IdentityScope:: User)
2758
.unwrap ( ) ;
79ab8528 Andy McCall 1 month ago ↶
2759
let r = root.join ( " r" ) ;
2760
write ( & r, " README.md" , b " hello\n " ) ;
2761
write ( & r, " src/main.rs" , b " fn main() {}\n " ) ;
2762
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
2763
write ( & r, " src/main.rs" , b " fn main() { run(); }\n " ) ;
2764
write ( & r, " logo.png" , b " \x89 PNG\0 \0 " ) ;
2765
let h2 = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
2766
2767
let git_dir = root.join ( " mirror.git" ) ;
2768
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
2769
assert_eq! ( ( rep.exported, rep.skipped, rep.heads) , ( 2 , 0 , 1 ) ) ;
2770
let git = | args : & [& str ]| -> String {
75167672 Andy McCall 1 month ago
2771
let out = std:: process:: Command:: new( " git" )
2772
.arg ( " -C" )
2773
.arg ( & git_dir)
2774
.args ( args)
2775
.output ( )
2776
.unwrap ( ) ;
2777
assert! (
2778
out.status.success ( ) ,
2779
" {}" ,
2780
String :: from_utf8_lossy( & out.stderr)
2781
) ;
79ab8528 Andy McCall 1 month ago ↶
2782
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
2783
} ;
2784
assert_eq! ( git ( & [ " log" , " --format=%s" , " main" ] ) , " second\n first" ) ;
2785
assert_eq! ( git ( & [ " show" , " main:src/main.rs" ] ) , " fn main() { run(); }" ) ;
2786
assert_eq! ( git ( & [ " show" , " main:README.md" ] ) , " hello" ) ;
2787
let body = git ( & [ " log" , " -1" , " --format=%B" , " main" ] ) ;
75167672 Andy McCall 1 month ago
2788
assert! (
2789
body.contains ( & format! ( " Oxy-Hash: {} " , h2.to_hex ( ) ) ) ,
2790
" {body}"
2791
) ;
79ab8528 Andy McCall 1 month ago ↶
2792
assert! ( body.contains ( " Change-Id: " ) , " {body}" ) ;
75167672 Andy McCall 1 month ago
2793
assert_eq! (
2794
git ( & [ " log" , " -1" , " --format=%an <%ae>" , " main" ] ) ,
2795
" Ada Lovelace <[email protected] >"
2796
) ;
2797
assert_eq! (
2798
git ( & [ " ls-tree" , " --name-only" , " main" ] ) ,
2799
" README.md\n logo.png\n src"
2800
) ;
79ab8528 Andy McCall 1 month ago ↶
2801
2802
2803
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
2804
assert_eq! ( ( rep.exported, rep.skipped) , ( 0 , 2 ) ) ;
2805
2806
2807
2808
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
2809
write ( & r, " other.txt" , b " o\n " ) ;
2810
let h3 = repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
2811
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
2812
assert_eq! ( ( rep.exported, rep.heads) , ( 1 , 2 ) ) ;
2813
assert_eq! ( git ( & [ " log" , " --format=%s" , " main" ] ) , " third\n second\n first" ) ;
2814
let parents = git ( & [ " rev-list" , " --parents" , " -1" , " main" ] ) ;
2815
assert_eq! ( parents.split ( ' ' ) .count ( ) , 2 , " one parent: {parents}" ) ;
2816
assert_eq! ( git ( & [ " show" , " main:other.txt" ] ) , " o" ) ;
2817
assert_eq! ( git ( & [ " show" , " main:src/main.rs" ] ) , " fn main() { run(); }" ) ;
75167672 Andy McCall 1 month ago
2818
assert_eq! (
2819
git ( & [ " ls-tree" , " --name-only" , " main" ] ) ,
2820
" README.md\n logo.png\n other.txt\n src"
2821
) ;
79ab8528 Andy McCall 1 month ago ↶
2822
let body = git ( & [ " log" , " -1" , " --format=%B" , " main" ] ) ;
75167672 Andy McCall 1 month ago
2823
assert! (
2824
body.contains ( & format! ( " Oxy-Hash: {} " , h3.to_hex ( ) ) ) & & body.contains ( " Oxy-State: " ) ,
2825
" {body}"
2826
) ;
79ab8528 Andy McCall 1 month ago ↶
2827
2828
repo.describe ( & h3, " third, described" ) .unwrap ( ) ;
2829
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
2830
assert_eq! ( rep.exported, 1 ) ;
75167672 Andy McCall 1 month ago
2831
assert_eq! (
2832
git ( & [ " log" , " -1" , " --format=%s" , " main" ] ) ,
2833
" third, described"
2834
) ;
79ab8528 Andy McCall 1 month ago ↶
2835
assert_eq! ( git ( & [ " log" , " --format=%s" , " main" ] ) .lines ( ) .count ( ) , 4 ) ;
2836
let _ = h2;
2837
}
c3069537 Andy McCall 1 month ago ↶
2838
2839
# [ test ]
2840
fn git_import_reproduces_trees ( ) {
c3d2ef61 Andy McCall 1 month ago ↶
2841
git_import_reproduces_trees_with ( None ) ;
2842
}
2843
2844
2845
2846
# [ test ]
2847
fn git_import_reproduces_trees_in_batches ( ) {
2848
git_import_reproduces_trees_with ( Some ( 1 ) ) ;
2849
}
2850
2851
fn git_import_reproduces_trees_with ( batch : Option< usize > ) {
2852
crate :: repo:: git:: set_merge_batch_for_tests( batch) ;
c3069537 Andy McCall 1 month ago ↶
2853
hermetic ( ) ;
75167672 Andy McCall 1 month ago
2854
if std:: process:: Command:: new( " git" )
2855
.arg ( " --version" )
2856
.output ( )
2857
.is_err ( )
2858
{
c3069537 Andy McCall 1 month ago ↶
2859
eprintln! ( " git not installed; skipping" ) ;
2860
return ;
2861
}
2862
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2863
let root = dir.path ( ) ;
2864
let g = root.join ( " src.git" ) ;
2865
fs:: create_dir_all( & g) .unwrap ( ) ;
2866
let git = | args : & [& str ]| -> String {
2867
let out = std:: process:: Command:: new( " git" )
2868
.arg ( " -C" )
2869
.arg ( & g)
75167672 Andy McCall 1 month ago
2870
.args ( [
2871
" -c" ,
2872
" user.name=Grace" ,
2873
" -c" ,
2874
" [email protected] " ,
2875
" -c" ,
2876
" commit.gpgsign=false" ,
2877
] )
c3069537 Andy McCall 1 month ago ↶
2878
.args ( args)
2879
.output ( )
2880
.unwrap ( ) ;
75167672 Andy McCall 1 month ago
2881
assert! (
2882
out.status.success ( ) ,
2883
" git {args:?}: {}" ,
2884
String :: from_utf8_lossy( & out.stderr)
2885
) ;
c3069537 Andy McCall 1 month ago ↶
2886
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
2887
} ;
2888
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
2889
write ( & g, " README.md" , b " # Hi\n " ) ;
2890
write ( & g, " src/lib.rs" , b " pub fn a() {}\n " ) ;
2891
git ( & [ " add" , " ." ] ) ;
2892
git ( & [ " commit" , " -q" , " -m" , " initial" ] ) ;
2893
write ( & g, " src/lib.rs" , b " pub fn a() {}\n pub fn b() {}\n " ) ;
2894
write ( & g, " run.sh" , b " #!/bin/sh\n " ) ;
2895
# [ cfg ( unix ) ]
2896
{
2897
use std:: os:: unix:: fs:: PermissionsExt;
2898
fs:: set_permissions( g.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
2899
}
2900
write ( & g, " logo.png" , b " \x89 PNG\0 \0 \x01 " ) ;
2901
git ( & [ " add" , " ." ] ) ;
75167672 Andy McCall 1 month ago
2902
git ( & [
2903
" commit" ,
2904
" -q" ,
2905
" -m" ,
2906
" second\n \n Change-Id: 0123456789abcdef0123456789abcdef" ,
2907
] ) ;
c3069537 Andy McCall 1 month ago ↶
2908
2909
git ( & [ " checkout" , " -q" , " -b" , " topic" , " HEAD~1" ] ) ;
2910
write ( & g, " README.md" , b " # Hi\n \n More.\n " ) ;
2911
git ( & [ " commit" , " -q" , " -am" , " readme" ] ) ;
2912
git ( & [ " checkout" , " -q" , " main" ] ) ;
2913
git ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge topic" , " topic" ] ) ;
2914
2915
2916
let initial = git ( & [ " rev-list" , " --max-parents=0" , " HEAD" ] ) ;
2917
git ( & [ " checkout" , " -q" , " -b" , " clash" , & initial] ) ;
2918
write ( & g, " src/lib.rs" , b " pub fn a() {}\n pub fn c() {}\n " ) ;
2919
git ( & [ " commit" , " -q" , " -am" , " clash" ] ) ;
2920
git ( & [ " checkout" , " -q" , " main" ] ) ;
75167672 Andy McCall 1 month ago
2921
let _ = std:: process:: Command:: new( " git" )
2922
.arg ( " -C" )
2923
.arg ( & g)
2924
.args ( [ " merge" , " -q" , " clash" ] )
2925
.output ( ) ;
2926
write (
2927
& g,
2928
" src/lib.rs" ,
2929
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n " ,
2930
) ;
c3069537 Andy McCall 1 month ago ↶
2931
git ( & [ " add" , " ." ] ) ;
2932
git ( & [ " commit" , " -q" , " -m" , " resolve clash" ] ) ;
2933
git ( & [ " rm" , " -q" , " logo.png" ] ) ;
2934
git ( & [ " commit" , " -q" , " -m" , " drop logo" ] ) ;
6e723b81 Andy McCall 1 month ago ↶
2935
2936
git ( & [ " mv" , " src/lib.rs" , " src/lib2.rs" ] ) ;
75167672 Andy McCall 1 month ago
2937
write (
2938
& g,
2939
" src/lib2.rs" ,
2940
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n pub fn d() {}\n " ,
2941
) ;
6e723b81 Andy McCall 1 month ago ↶
2942
git ( & [ " add" , " ." ] ) ;
2943
git ( & [ " commit" , " -q" , " -m" , " rename lib" ] ) ;
958fb27d Andy McCall 1 month ago ↶
2944
2945
2946
2947
git ( & [ " checkout" , " -q" , " -b" , " discarded" ] ) ;
2948
write ( & g, " run.sh" , b " #!/bin/sh\n echo discarded\n " ) ;
2949
git ( & [ " commit" , " -q" , " -am" , " discarded edit" ] ) ;
2950
git ( & [ " checkout" , " -q" , " main" ] ) ;
314db9b4 Andy McCall 1 month ago ↶
2951
git ( & [
2952
" merge" ,
2953
" -q" ,
2954
" -s" ,
2955
" ours" ,
2956
" -m" ,
2957
" merge discarded, keeping ours" ,
2958
" discarded" ,
2959
] ) ;
c3069537 Andy McCall 1 month ago ↶
2960
let expected_count: usize = git ( & [ " rev-list" , " --count" , " main" ] ) .parse ( ) .unwrap ( ) ;
2961
2962
let dest = root.join ( " oxy" ) ;
2963
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2964
let rep = repo.import_git ( & g, " main" ) .unwrap ( ) ;
2965
assert_eq! ( rep.commits, expected_count) ;
958fb27d Andy McCall 1 month ago ↶
2966
assert_eq! ( rep.merges, 3 ) ;
314db9b4 Andy McCall 1 month ago ↶
2967
assert_eq! (
2968
fs:: read( dest.join ( " run.sh" ) ) .unwrap ( ) ,
2969
b " #!/bin/sh\n " ,
2970
" the ours merge kept main's run.sh"
2971
) ;
958fb27d Andy McCall 1 month ago ↶
2972
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
2973
assert_eq! ( v.commits, expected_count, " every commit's tree reproduced" ) ;
2974
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
c3069537 Andy McCall 1 month ago ↶
2975
75167672 Andy McCall 1 month ago
2976
assert_eq! (
2977
fs:: read( dest.join ( " src/lib2.rs" ) ) .unwrap ( ) ,
2978
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n pub fn d() {}\n "
2979
) ;
6e723b81 Andy McCall 1 month ago ↶
2980
assert! ( ! dest.join ( " src/lib.rs" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
2981
assert_eq! (
2982
fs:: read( dest.join ( " README.md" ) ) .unwrap ( ) ,
2983
b " # Hi\n \n More.\n "
2984
) ;
c3069537 Andy McCall 1 month ago ↶
2985
assert! ( ! dest.join ( " logo.png" ) .exists ( ) ) ;
2986
# [ cfg ( unix ) ]
2987
{
2988
use std:: os:: unix:: fs:: PermissionsExt;
75167672 Andy McCall 1 month ago
2989
assert! (
2990
fs:: metadata( dest.join ( " run.sh" ) )
2991
.unwrap ( )
2992
.permissions ( )
2993
.mode ( )
2994
& 0o111
2995
! = 0
2996
) ;
c3069537 Andy McCall 1 month ago ↶
2997
}
75167672 Andy McCall 1 month ago
2998
assert! (
2999
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
3000
" clean after import"
3001
) ;
c3069537 Andy McCall 1 month ago ↶
3002
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
3003
let st = repo.load_state ( & heads) .unwrap ( ) ;
3004
assert! ( ! st.materialize ( ) .is_conflicted ( ) ) ;
3005
assert_eq! ( st.len ( ) , expected_count) ;
75167672 Andy McCall 1 month ago
3006
let tip = repo
3007
.log ( )
3008
.unwrap ( )
3009
.into_iter ( )
3010
.find ( | e | e.change.message.starts_with ( " rename lib" ) )
3011
.unwrap ( ) ;
6e723b81 Andy McCall 1 month ago ↶
3012
let c = repo.store ( ) .get_change ( & tip.hash) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3013
assert! (
3014
c.tree_ops
3015
.iter ( )
3016
.any ( | op | matches! ( op, TreeOp:: Move { name, .. } if name = = " lib2.rs" ) ) ,
3017
" {:?}" ,
3018
c.tree_ops
3019
) ;
3020
assert! ( ! c
3021
.tree_ops
3022
.iter ( )
3023
.any ( | op | matches! ( op, TreeOp:: AddNode { .. } ) ) ) ;
c3069537 Andy McCall 1 month ago ↶
3024
3025
let log = repo.log ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3026
let second = log
3027
.iter ( )
3028
.find ( | e | e.change.message.starts_with ( " second" ) )
3029
.unwrap ( ) ;
3030
assert_eq! (
3031
second.change.change_id,
3032
ChangeId:: from_hex( " 0123456789abcdef0123456789abcdef" ) .unwrap ( )
3033
) ;
3034
assert! ( second
3035
.change
3036
.message
3037
.contains ( " Git-Author: Grace <[email protected] >" ) ) ;
3038
let resolve = log
3039
.iter ( )
3040
.find ( | e | e.change.message.starts_with ( " resolve clash" ) )
3041
.unwrap ( ) ;
3042
assert_eq! (
3043
resolve.change.kind,
3044
ChangeKind:: Resolution,
3045
" a conflicting Git merge becomes a resolution"
3046
) ;
c3069537 Andy McCall 1 month ago ↶
3047
75167672 Andy McCall 1 month ago
3048
let repo2 = Repo:: init( & root.join ( " oxy2" ) )
3049
.unwrap ( )
3050
.with_home ( root.join ( " home" ) ) ;
c3069537 Andy McCall 1 month ago ↶
3051
repo2.import_git ( & g, " main" ) .unwrap ( ) ;
dfb23a3a Andy McCall 1 month ago ↶
3052
3053
3054
assert_eq! (
3055
repo2.bookmark_heads ( ) .unwrap ( ) ,
3056
repo.bookmark_heads ( ) .unwrap ( )
3057
) ;
75167672 Andy McCall 1 month ago
3058
let ids: BTreeSet< ChangeId> = repo
3059
.log ( )
3060
.unwrap ( )
3061
.iter ( )
3062
.filter ( | e | ! e.is_working_copy)
3063
.map ( | e | e.change.change_id )
3064
.collect ( ) ;
3065
let ids2: BTreeSet< ChangeId> = repo2
3066
.log ( )
3067
.unwrap ( )
3068
.iter ( )
3069
.filter ( | e | ! e.is_working_copy)
3070
.map ( | e | e.change.change_id )
3071
.collect ( ) ;
c3069537 Andy McCall 1 month ago ↶
3072
assert_eq! ( ids, ids2) ;
3073
3074
let mirror = root.join ( " mirror.git" ) ;
3075
repo.git_export ( & mirror) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3076
let out = std:: process:: Command:: new( " git" )
3077
.arg ( " -C" )
3078
.arg ( & mirror)
3079
.args ( [ " show" , " main:src/lib2.rs" ] )
3080
.output ( )
3081
.unwrap ( ) ;
3082
assert_eq! (
3083
String :: from_utf8_lossy( & out.stdout) ,
3084
" pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n pub fn d() {}\n "
3085
) ;
3086
let out = std:: process:: Command:: new( " git" )
3087
.arg ( " -C" )
3088
.arg ( & mirror)
3089
.args ( [ " show" , " main:src/lib.rs" ] )
3090
.output ( )
3091
.unwrap ( ) ;
3092
assert! (
3093
! out.status.success ( ) ,
3094
" the old path is gone from the export"
3095
) ;
c3069537 Andy McCall 1 month ago ↶
3096
}
252e8cb2 Andy McCall 1 month ago ↶
3097
3098
# [ test ]
3099
fn checkpoints_accelerate_loading_and_stay_correct ( ) {
3100
hermetic ( ) ;
3101
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3102
let root = dir.path ( ) ;
3103
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
3104
repo.config.set ( " core.checkpoint-interval" , " 5" ) .unwrap ( ) ;
3105
repo.save_config ( ) .unwrap ( ) ;
3106
let repo = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
3107
let mut hashes = Vec :: new( ) ;
3108
for i in 0 .. 12 {
75167672 Andy McCall 1 month ago
3109
write (
3110
root,
3111
" a.txt" ,
3112
format! (
3113
" {} \n " ,
3114
( 0 .. = i)
3115
.map ( | k | k.to_string ( ) )
3116
.collect:: < Vec< _ > > ( )
3117
.join ( " \n " )
3118
)
3119
.as_bytes ( ) ,
3120
) ;
252e8cb2 Andy McCall 1 month ago ↶
3121
write ( root, & format! ( " f{i} .txt" ) , b " x\n " ) ;
3122
hashes.push ( repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ) ;
3123
}
75167672 Andy McCall 1 month ago
3124
let ( _ , cp) = repo
3125
.latest_checkpoint ( " main" )
3126
.unwrap ( )
3127
.expect ( " automatic checkpoint" ) ;
252e8cb2 Andy McCall 1 month ago ↶
3128
assert! ( cp.count > = 5 & & cp.count < = 12 , " {}" , cp.count) ;
3129
3130
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
3131
let fast = repo.load_state ( & heads) .unwrap ( ) ;
3132
let full = repo.load_state_full ( & heads) .unwrap ( ) ;
3133
assert_eq! ( fast.len ( ) , full.len ( ) ) ;
3134
assert_eq! ( fast.heads ( ) , full.heads ( ) ) ;
3135
let ( a, b) = ( fast.materialize ( ) , full.materialize ( ) ) ;
75167672 Andy McCall 1 month ago
3136
assert_eq! (
3137
a.files.keys ( ) .collect:: < Vec< _ > > ( ) ,
3138
b.files.keys ( ) .collect:: < Vec< _ > > ( )
3139
) ;
252e8cb2 Andy McCall 1 month ago ↶
3140
for ( p, f) in & a.files {
3141
assert_eq! ( f.content, b.files[ p] .content, " {p}" ) ;
3142
}
3143
assert! ( repo.load_state_checkpointed ( & heads) .unwrap ( ) .is_some ( ) ) ;
3144
75167672 Andy McCall 1 month ago
3145
assert! ( repo
3146
.load_state_checkpointed ( & [ hashes[ 0 ] ] )
3147
.unwrap ( )
3148
.is_none ( ) ) ;
252e8cb2 Andy McCall 1 month ago ↶
3149
assert_eq! ( repo.load_state ( & [ hashes[ 0 ] ] ) .unwrap ( ) .len ( ) , 1 ) ;
3150
3151
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
3152
let h = repo.describe ( & hashes[ 3 ] , " c3 described" ) .unwrap ( ) ;
3153
assert_eq! ( repo.store ( ) .get_change ( & h) .unwrap ( ) .message, " c3 described" ) ;
3154
let ( r, n, bytes) = repo.checkpoint ( ) .unwrap ( ) ;
3155
assert! ( n > = 12 & & bytes > 0 ) ;
3156
assert_eq! ( repo.latest_checkpoint ( " main" ) .unwrap ( ) .unwrap ( ) .0 , r) ;
3157
write ( root, " a.txt" , b " after\n " ) ;
3158
let h_after = repo.commit ( " after" , & [ ] ) .unwrap ( ) ;
3159
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
3160
assert! ( st.contains ( & h_after) ) ;
75167672 Andy McCall 1 month ago
3161
assert! (
3162
matches! ( & st.materialize ( ) .files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " after\n " )
3163
) ;
252e8cb2 Andy McCall 1 month ago ↶
3164
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 14 ) ;
3165
3166
repo.gc ( true ) .unwrap ( ) ;
3167
assert! ( repo.latest_checkpoint ( " main" ) .unwrap ( ) .is_some ( ) ) ;
75167672 Andy McCall 1 month ago
3168
assert! ( repo
3169
.load_state_checkpointed ( & repo.bookmark_heads ( ) .unwrap ( ) )
3170
.unwrap ( )
3171
.is_some ( ) ) ;
252e8cb2 Andy McCall 1 month ago ↶
3172
}
135212de Andy McCall 1 month ago ↶
3173
3174
# [ test ]
3175
fn quick_status_for_prompts ( ) {
3176
hermetic ( ) ;
3177
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3178
let root = dir.path ( ) ;
3179
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
3180
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
3181
let a = Repo:: clone( & url, & root.join ( " a" ) )
3182
.unwrap ( )
3183
.with_home ( root.join ( " home" ) ) ;
135212de Andy McCall 1 month ago ↶
3184
let ops_before = a.op_log ( ) .unwrap ( ) .len ( ) ;
3185
let info = a.quick_status ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3186
assert_eq! (
3187
(
3188
info.bookmark.as_str ( ) ,
3189
info.heads,
3190
info.dirty,
3191
info.conflicted,
3192
info.ahead,
3193
info.behind
3194
) ,
3195
( " main" , 0 , false , false , Some ( 0 ) , Some ( 0 ) )
3196
) ;
135212de Andy McCall 1 month ago ↶
3197
write ( & root.join ( " a" ) , " f.txt" , b " f\n " ) ;
3198
assert! ( a.quick_status ( ) .unwrap ( ) .dirty, " a new file is dirty" ) ;
75167672 Andy McCall 1 month ago
3199
assert_eq! (
3200
a.op_log ( ) .unwrap ( ) .len ( ) ,
3201
ops_before,
3202
" quick_status records nothing"
3203
) ;
135212de Andy McCall 1 month ago ↶
3204
3205
a.snapshot ( " status" ) .unwrap ( ) ;
3206
assert! ( a.quick_status ( ) .unwrap ( ) .dirty) ;
3207
a.commit ( " f" , & [ ] ) .unwrap ( ) ;
3208
let info = a.quick_status ( ) .unwrap ( ) ;
3209
assert! ( ! info.dirty, " clean right after a commit" ) ;
3210
assert_eq! ( ( info.ahead, info.behind) , ( Some ( 1 ) , Some ( 0 ) ) ) ;
75167672 Andy McCall 1 month ago
3211
assert_eq! (
3212
info.format ( " %b %c%d%x%A%D" ) ,
3213
format! ( " main {} +1" , info.change_id)
3214
) ;
135212de Andy McCall 1 month ago ↶
3215
assert_eq! ( info.format ( " [%h heads] %a/%B %%" ) , " [1 heads] 1/0 %" ) ;
3216
3217
3218
let p = root.join ( " a/f.txt" ) ;
3219
let content = fs:: read( & p) .unwrap ( ) ;
3220
fs:: write( & p, & content) .unwrap ( ) ;
3221
assert! ( ! a.quick_status ( ) .unwrap ( ) .dirty) ;
3222
fs:: write( & p, b " g\n " ) .unwrap ( ) ;
3223
assert! ( a.quick_status ( ) .unwrap ( ) .dirty) ;
3224
fs:: write( & p, b " f\n " ) .unwrap ( ) ;
3225
3226
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
3227
assert_eq! ( a.quick_status ( ) .unwrap ( ) .ahead, Some ( 0 ) ) ;
75167672 Andy McCall 1 month ago
3228
let b = Repo:: clone( & url, & root.join ( " b" ) )
3229
.unwrap ( )
3230
.with_home ( root.join ( " home-b" ) ) ;
135212de Andy McCall 1 month ago ↶
3231
write ( & root.join ( " b" ) , " g.txt" , b " g\n " ) ;
3232
b.commit ( " g" , & [ ] ) .unwrap ( ) ;
3233
b.push ( " origin" , false ) .unwrap ( ) ;
3234
a.fetch ( " origin" ) .unwrap ( ) ;
3235
let info = a.quick_status ( ) .unwrap ( ) ;
3236
assert_eq! ( ( info.ahead, info.behind) , ( Some ( 0 ) , Some ( 1 ) ) ) ;
3237
assert_eq! ( info.format ( " %A%D" ) , " -1" ) ;
3238
let _ = origin;
3239
}
b3c5a59a Andy McCall 1 month ago ↶
3240
3241
# [ test ]
3242
fn lazy_blobs_are_fetched_on_first_read ( ) {
3243
hermetic ( ) ;
3244
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3245
let root = dir.path ( ) ;
3246
let origin_dir = root.join ( " origin" ) ;
75167672 Andy McCall 1 month ago
3247
let origin = Repo:: init( & origin_dir)
3248
.unwrap ( )
3249
.with_home ( root.join ( " home" ) ) ;
b3c5a59a Andy McCall 1 month ago ↶
3250
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
3251
let big: Vec< u8 > = ( 0 .. 200_000 u32 ) .map ( | i | ( i % 251 ) as u8 ) .collect ( ) ;
3252
write ( & origin_dir, " big.bin" , & big) ;
3253
write ( & origin_dir, " small.bin" , & [ 0 u8 , 1 , 2 ] ) ;
3254
write ( & origin_dir, " t.txt" , b " t\n " ) ;
3255
let h = origin.commit ( " blobs" , & [ ] ) .unwrap ( ) ;
3256
let big_ref = Repo:: blob_ref( & big) ;
3257
3258
75167672 Andy McCall 1 month ago
3259
let a = Repo:: init_with_id( & root.join ( " a" ) , origin.id ( ) .unwrap ( ) )
3260
.unwrap ( )
3261
.with_home ( root.join ( " home" ) ) ;
b3c5a59a Andy McCall 1 month ago ↶
3262
let a = {
3263
let mut a = a;
3264
a.config.set ( " remote.origin.url" , & url) .unwrap ( ) ;
3265
a.config.set ( " filter.max-blob" , " 100K" ) .unwrap ( ) ;
3266
a.save_config ( ) .unwrap ( ) ;
3267
a
3268
} ;
3269
assert_eq! ( a.config.max_blob, Some ( 100 * 1024 ) ) ;
3270
a.fetch ( " origin" ) .unwrap ( ) ;
3271
assert! ( a.store ( ) .has ( & h) ) ;
3272
assert! ( ! a.store ( ) .has ( & big_ref) , " withheld" ) ;
75167672 Andy McCall 1 month ago
3273
assert! (
3274
a.store ( ) .has ( & Repo:: blob_ref( & [ 0 u8 , 1 , 2 ] ) ) ,
3275
" small blob came along"
3276
) ;
b3c5a59a Andy McCall 1 month ago ↶
3277
assert_eq! ( a.lazy_remote ( & big_ref) .as_deref ( ) , Some ( " origin" ) ) ;
3278
3279
3280
let ( op_ref, op) = a.head_op ( ) .unwrap ( ) ;
3281
let mut view = op.view.clone ( ) ;
75167672 Andy McCall 1 month ago
3282
view.bookmarks.insert (
3283
" main" .into ( ) ,
3284
a.store ( ) .put_state ( & StateObject:: new( vec! [ h] ) ) .unwrap ( ) ,
3285
) ;
b3c5a59a Andy McCall 1 month ago ↶
3286
a.record_op ( & [ op_ref] , " adopt" , view) .unwrap ( ) ;
3287
3288
a.checkout ( & [ h] , true , " checkout" ) .unwrap ( ) ;
3289
assert! ( a.store ( ) .has ( & big_ref) , " fetched on first read" ) ;
3290
assert_eq! ( fs:: read( root.join ( " a/big.bin" ) ) .unwrap ( ) , big) ;
3291
assert! ( a.lazy_remote ( & big_ref) .is_none ( ) , " no longer registered" ) ;
3292
write ( & root.join ( " a" ) , " t.txt" , b " t2\n " ) ;
3293
a.commit ( " text edit" , & [ ] ) .unwrap ( ) ;
3294
3295
7421309f Andy McCall 1 month ago ↶
3296
let b = Repo:: clone_with( & url, & root.join ( " b" ) , Some ( 1024 ) , & [ ] ) .unwrap ( ) ;
b3c5a59a Andy McCall 1 month ago ↶
3297
assert_eq! ( b.config.max_blob, Some ( 1024 ) ) ;
3298
assert_eq! ( fs:: read( root.join ( " b/big.bin" ) ) .unwrap ( ) , big) ;
3299
assert_eq! ( parse_size ( " 2M" ) .unwrap ( ) , 2 < < 20 ) ;
3300
assert! ( parse_size ( " lots" ) .is_err ( ) ) ;
3301
}
7421309f Andy McCall 1 month ago ↶
3302
3303
# [ test ]
3304
fn sparse_checkout_materializes_only_chosen_paths ( ) {
3305
hermetic ( ) ;
3306
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3307
let root = dir.path ( ) ;
3308
let origin_dir = root.join ( " origin" ) ;
75167672 Andy McCall 1 month ago
3309
let origin = Repo:: init( & origin_dir)
3310
.unwrap ( )
3311
.with_home ( root.join ( " home" ) ) ;
7421309f Andy McCall 1 month ago ↶
3312
write ( & origin_dir, " src/main.rs" , b " fn main() {}\n " ) ;
3313
write ( & origin_dir, " src/lib.rs" , b " pub fn a() {}\n " ) ;
3314
write ( & origin_dir, " docs/guide.md" , b " # guide\n " ) ;
3315
let logo: Vec< u8 > = ( 0 .. 50_000 u32 ) .map ( | i | ( i % 7 ) as u8 ) .collect ( ) ;
3316
write ( & origin_dir, " docs/logo.png" , & logo) ;
3317
write ( & origin_dir, " README.md" , b " # top\n " ) ;
3318
let h1 = origin.commit ( " tree" , & [ ] ) .unwrap ( ) ;
3319
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
3320
75167672 Andy McCall 1 month ago
3321
let a = Repo:: clone_with( & url, & root.join ( " a" ) , None , & [ " src" .into ( ) ] )
3322
.unwrap ( )
3323
.with_home ( root.join ( " home" ) ) ;
7421309f Andy McCall 1 month ago ↶
3324
assert_eq! ( a.config.sparse_paths, vec! [ " src" ] ) ;
3325
assert_eq! ( a.config.max_blob, Some ( 0 ) , " sparse implies lazy blobs" ) ;
3326
assert! ( root.join ( " a/src/main.rs" ) .exists ( ) ) ;
3327
assert! ( ! root.join ( " a/docs" ) .exists ( ) & & ! root.join ( " a/README.md" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
3328
assert! (
3329
! a.store ( ) .has ( & Repo:: blob_ref( & logo) ) ,
3330
" out-of-scope blob never fetched"
3331
) ;
3332
assert! (
3333
! a.snapshot ( " status" ) .unwrap ( ) .changed,
3334
" out-of-scope files are not removals"
3335
) ;
05e71ed6 Andy McCall 1 month ago ↶
3336
assert! ( ! a.quick_status ( ) .unwrap ( ) .dirty) ;
7421309f Andy McCall 1 month ago ↶
3337
3338
75167672 Andy McCall 1 month ago
3339
write (
3340
& root.join ( " a" ) ,
3341
" src/lib.rs" ,
3342
b " pub fn a() {}\n pub fn b() {}\n " ,
3343
) ;
7421309f Andy McCall 1 month ago ↶
3344
let h2 = a.commit ( " edit lib" , & [ ] ) .unwrap ( ) ;
3345
let st = a.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
3346
assert! (
3347
st.files.contains_key ( " docs/guide.md" ) ,
3348
" the state still has everything"
3349
) ;
7421309f Andy McCall 1 month ago ↶
3350
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
3351
origin.refresh ( ) .unwrap ( ) ;
7421309f Andy McCall 1 month ago ↶
3352
3353
write ( & root.join ( " a" ) , " docs/new.md" , b " ignored\n " ) ;
3354
assert! ( a.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
3355
3356
3357
let mut a = a;
3358
a.config.set ( " filter.paths" , " src,docs" ) .unwrap ( ) ;
3359
a.save_config ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3360
let r = a
3361
.checkout ( & a.bookmark_heads ( ) .unwrap ( ) , true , " widen" )
3362
.unwrap ( ) ;
7421309f Andy McCall 1 month ago ↶
3363
assert! ( r.written > = 2 ) ;
75167672 Andy McCall 1 month ago
3364
assert_eq! (
3365
fs:: read( root.join ( " a/docs/logo.png" ) ) .unwrap ( ) ,
3366
logo,
3367
" now fetched lazily"
3368
) ;
7421309f Andy McCall 1 month ago ↶
3369
assert! ( ! root.join ( " a/README.md" ) .exists ( ) ) ;
3370
3371
origin.pull ( " origin" ) .err ( ) ;
75167672 Andy McCall 1 month ago
3372
let full = origin
3373
.load_state ( & origin.bookmark_heads ( ) .unwrap ( ) )
3374
.unwrap ( )
3375
.materialize ( ) ;
3376
assert! (
3377
matches! ( & full.files[ " src/lib.rs" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " pub fn a() {}\n pub fn b() {}\n " )
3378
) ;
7421309f Andy McCall 1 month ago ↶
3379
assert! ( full.files.contains_key ( " README.md" ) ) ;
3380
let _ = h1;
3381
}
2863456a Andy McCall 1 month ago ↶
3382
7bd805ff Andy McCall 12 days ago ↶
3383
3384
3385
3386
2863456a Andy McCall 1 month ago ↶
3387
# [ test ]
7bd805ff Andy McCall 12 days ago ↶
3388
fn a_depth_0_clone_brings_its_binary_files ( ) {
3389
hermetic ( ) ;
3390
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3391
let root = dir.path ( ) ;
3392
let origin_dir = root.join ( " origin" ) ;
3393
let origin = Repo:: init( & origin_dir)
3394
.unwrap ( )
3395
.with_home ( root.join ( " home" ) ) ;
3396
let png: Vec< u8 > = ( 0 .. 4000 u32 )
3397
.map ( | i | ( i * 7 % 251 ) as u8 )
3398
.chain ( [ 0 , 0xff ] )
3399
.collect ( ) ;
3400
3401
3402
write ( & origin_dir, " logo.png" , & png) ;
3403
write ( & origin_dir, " a.txt" , b " first\n " ) ;
3404
origin.commit ( " logo" , & [ ] ) .unwrap ( ) ;
3405
write ( & origin_dir, " a.txt" , b " later\n " ) ;
3406
origin.commit ( " later" , & [ ] ) .unwrap ( ) ;
3407
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) .len ( ) , 1 ) ;
3408
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
3409
Repo:: clone_shallow( & url, & root.join ( " a" ) , 0 , None , & [ ] ) .unwrap ( ) ;
3410
assert_eq! ( fs:: read( root.join ( " a/logo.png" ) ) .unwrap ( ) , png) ;
3411
let lazy = Repo:: clone_shallow( & url, & root.join ( " b" ) , 0 , Some ( 0 ) , & [ ] ) .unwrap ( ) ;
3412
assert_eq! ( fs:: read( root.join ( " b/a.txt" ) ) .unwrap ( ) , b " later\n " ) ;
3413
let blob = {
3414
let heads = lazy.bookmark_heads ( ) .unwrap ( ) ;
3415
let st = lazy.load_state_history ( & heads) .unwrap ( ) ;
3416
st.opaque_nodes ( )
3417
.iter ( )
3418
.flat_map ( | n | st.live_blobs ( n) )
3419
.next ( )
3420
.unwrap ( )
3421
} ;
3422
assert_eq! ( lazy.read_blob ( & blob) .unwrap ( ) , png, " fetched on read" ) ;
f3811eb2 Andy McCall 12 days ago ↶
3423
3424
3425
let mut a = Repo:: open( & root.join ( " a" ) )
3426
.unwrap ( )
3427
.with_home ( root.join ( " home" ) ) ;
3428
let head = origin.bookmark_heads ( ) .unwrap ( ) [ 0 ] ;
3429
assert! ( ! a.store ( ) .has ( & head) , " no change fetched" ) ;
3430
assert! ( a.boundary ( ) .contains ( & head) ) ;
3431
assert_eq! (
3432
a.log ( )
3433
.unwrap ( )
3434
.iter ( )
3435
.filter ( | e | ! e.is_working_copy)
3436
.count ( ) ,
3437
2 ,
3438
" the log from the checkpoint"
3439
) ;
3440
write ( & root.join ( " a" ) , " a.txt" , b " later\n mine\n " ) ;
3441
a.config.set ( " remote.origin.url" , & url) .unwrap ( ) ;
3442
a.save_config ( ) .unwrap ( ) ;
3443
let mine = a.commit ( " mine" , & [ ] ) .unwrap ( ) ;
3444
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
3445
origin.refresh ( ) .unwrap ( ) ;
3446
assert! ( origin.store ( ) .has ( & mine) ) ;
3447
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ mine] ) ;
3448
3449
origin
3450
.checkout ( & origin.bookmark_heads ( ) .unwrap ( ) , false , " co" )
3451
.unwrap ( ) ;
3452
write ( & origin_dir, " a.txt" , b " later\n mine\n theirs\n " ) ;
3453
origin.snapshot ( " t" ) .unwrap ( ) ;
3454
origin.commit ( " theirs" , & [ ] ) .unwrap ( ) ;
3455
a.pull ( " origin" ) .unwrap ( ) ;
3456
assert_eq! (
3457
fs:: read( root.join ( " a/a.txt" ) ) .unwrap ( ) ,
3458
b " later\n mine\n theirs\n "
3459
) ;
7bd805ff Andy McCall 12 days ago ↶
3460
}
3461
3462
# [ test ]
2863456a Andy McCall 1 month ago ↶
3463
fn shallow_clone_and_deepen ( ) {
3464
hermetic ( ) ;
3465
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3466
let root = dir.path ( ) ;
3467
let origin_dir = root.join ( " origin" ) ;
75167672 Andy McCall 1 month ago
3468
let origin = Repo:: init( & origin_dir)
3469
.unwrap ( )
3470
.with_home ( root.join ( " home" ) ) ;
2863456a Andy McCall 1 month ago ↶
3471
let mut hashes = Vec :: new( ) ;
3472
for i in 0 .. 8 {
75167672 Andy McCall 1 month ago
3473
write (
3474
& origin_dir,
3475
" a.txt" ,
3476
format! (
3477
" {} \n " ,
3478
( 0 .. = i)
3479
.map ( | k | k.to_string ( ) )
3480
.collect:: < Vec< _ > > ( )
3481
.join ( " \n " )
3482
)
3483
.as_bytes ( ) ,
3484
) ;
2863456a Andy McCall 1 month ago ↶
3485
hashes.push ( origin.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ) ;
3486
}
3487
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
3488
let a = Repo:: clone_shallow( & url, & root.join ( " a" ) , 3 , None , & [ ] )
3489
.unwrap ( )
3490
.with_home ( root.join ( " home" ) ) ;
2863456a Andy McCall 1 month ago ↶
3491
assert! ( a.is_shallow ( ) ) ;
75167672 Andy McCall 1 month ago
3492
assert_eq! (
3493
fs:: read_to_string( root.join ( " a/a.txt" ) ) .unwrap ( ) ,
3494
" 0\n 1\n 2\n 3\n 4\n 5\n 6\n 7\n "
3495
) ;
3496
assert! (
3497
a.store ( ) .has ( & hashes[ 7 ] ) & & a.store ( ) .has ( & hashes[ 5 ] ) ,
3498
" three levels present"
3499
) ;
2863456a Andy McCall 1 month ago ↶
3500
assert! ( ! a.store ( ) .has ( & hashes[ 4 ] ) , " beyond the depth is absent" ) ;
3501
assert_eq! ( a.boundary ( ) , [ hashes[ 4 ] ] .into_iter ( ) .collect ( ) ) ;
75167672 Andy McCall 1 month ago
3502
assert_eq! (
3503
a.log ( ) .unwrap ( ) .len ( ) ,
3504
9 ,
3505
" the log shows every change from the checkpoint's metadata"
3506
) ;
2863456a Andy McCall 1 month ago ↶
3507
assert! ( ! a.snapshot ( " status" ) .unwrap ( ) .changed) ;
3508
assert_eq! ( a.quick_status ( ) .unwrap ( ) .heads, 1 ) ;
3509
3510
3511
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 " ) ;
3512
let h_new = a.commit ( " c8" , & [ ] ) .unwrap ( ) ;
3513
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
3514
origin.refresh ( ) .unwrap ( ) ;
2863456a Andy McCall 1 month ago ↶
3515
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ h_new] ) ;
3516
3517
write ( & origin_dir, " b.txt" , b " b\n " ) ;
3518
origin.pull ( " origin" ) .err ( ) ;
3519
let ( _ , wc) = origin.working_copy ( ) .unwrap ( ) ;
3520
let _ = wc;
75167672 Andy McCall 1 month ago
3521
origin
3522
.checkout ( & origin.bookmark_heads ( ) .unwrap ( ) , true , " co" )
3523
.unwrap ( ) ;
2863456a Andy McCall 1 month ago ↶
3524
write ( & origin_dir, " b.txt" , b " b\n " ) ;
3525
let h_b = origin.commit ( " b" , & [ ] ) .unwrap ( ) ;
3526
let pr = a.pull ( " origin" ) .unwrap ( ) ;
3527
assert! ( pr.changed) ;
3528
assert! ( a.store ( ) .has ( & h_b) ) ;
3529
assert! ( root.join ( " a/b.txt" ) .exists ( ) ) ;
3530
3531
3532
3533
3534
assert! ( a.deepen ( " origin" , 2 ) .unwrap ( ) > = 2 ) ;
3535
assert! ( a.store ( ) .has ( & hashes[ 3 ] ) ) ;
3536
assert! ( a.is_shallow ( ) ) ;
3537
a.deepen ( " origin" , 100 ) .unwrap ( ) ;
3538
assert! ( ! a.is_shallow ( ) , " boundary reached the root" ) ;
3539
assert! ( a.store ( ) .has ( & hashes[ 0 ] ) ) ;
75167672 Andy McCall 1 month ago
3540
assert_eq! (
3541
a.load_state_full ( & a.bookmark_heads ( ) .unwrap ( ) )
3542
.unwrap ( )
3543
.len ( ) ,
3544
10
3545
) ;
2863456a Andy McCall 1 month ago ↶
3546
}
132b884b Andy McCall 1 month ago ↶
3547
3548
# [ test ]
3549
fn tags_are_immutable_named_states_and_travel ( ) {
3550
hermetic ( ) ;
3551
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3552
let root = dir.path ( ) ;
3553
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
3554
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
3555
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
3556
.unwrap ( )
3557
.with_home ( root.join ( " home-a" ) ) ;
3558
let ( key_a, _ ) = a
3559
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
3560
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3561
write ( & root.join ( " a" ) , " f.txt" , b " 1\n " ) ;
3562
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
3563
write ( & root.join ( " a" ) , " f.txt" , b " 1\n 2\n " ) ;
3564
let h2 = a.commit ( " two" , & [ ] ) .unwrap ( ) ;
3565
3566
let t = a.tag_create ( " v1" , & [ h1] , " first release" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3567
assert_eq! (
3568
( t.heads.clone ( ) , t.signer.as_deref ( ) ) ,
3569
( vec! [ h1] , Some ( " Ada" ) )
3570
) ;
132b884b Andy McCall 1 month ago ↶
3571
assert! ( a.tag_create ( " v1" , & [ h2] , " " ) .is_err ( ) , " immutable" ) ;
3572
assert! ( a.tag_create ( " bad/name" , & [ h2] , " " ) .is_err ( ) ) ;
3573
assert_eq! ( a.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
3574
assert_eq! ( a.resolve_heads ( " v1" ) .unwrap ( ) , vec! [ h1] ) ;
3575
assert_eq! ( a.resolve_heads ( " main" ) .unwrap ( ) , vec! [ h2] ) ;
3576
let ( _ , sa) = a.tag_attest ( " v1" ) .unwrap ( ) .unwrap ( ) ;
3577
assert_eq! ( sa.sigs[ 0 ] .key, key_a) ;
ee130dd9 Andy McCall 17 days ago ↶
3578
3579
3580
let mut refs = a.remote_refs ( ) .unwrap ( ) ;
3581
a.refs_for_client ( & mut refs, None ) ;
3582
assert_eq! ( refs.tags.len ( ) , 1 ) ;
132b884b Andy McCall 1 month ago ↶
3583
75167672 Andy McCall 1 month ago
3584
a.checkout ( & a.resolve_heads ( " v1" ) .unwrap ( ) , false , " co" )
3585
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3586
assert_eq! ( fs:: read( root.join ( " a/f.txt" ) ) .unwrap ( ) , b " 1\n " ) ;
75167672 Andy McCall 1 month ago
3587
a.checkout ( & a.bookmark_heads ( ) .unwrap ( ) , false , " co" )
3588
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3589
3590
3591
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3592
origin.refresh ( ) .unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3593
assert! ( r.iter ( ) .any ( | x | x.name = = " tags/v1" & & x.ok ) , " {r:?}" ) ;
3594
assert_eq! ( origin.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
75167672 Andy McCall 1 month ago
3595
let mut forged = a
3596
.tag_refs ( )
3597
.unwrap ( )
3598
.into_iter ( )
3599
.find ( | t | t.name = = " v1" )
3600
.unwrap ( ) ;
3601
forged.state = origin
3602
.store ( )
3603
.put_state ( & StateObject:: new( vec! [ h2] ) )
3604
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3605
forged.heads = vec! [ h2] ;
3606
forged.attestation = None ;
75167672 Andy McCall 1 month ago
3607
let r = origin
3608
.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ forged] )
3609
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3610
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " immutable" ) , " {r:?}" ) ;
3611
3612
75167672 Andy McCall 1 month ago
3613
let b = Repo:: clone( & url, & root.join ( " b" ) )
3614
.unwrap ( )
3615
.with_home ( root.join ( " home-b" ) ) ;
132b884b Andy McCall 1 month ago ↶
3616
assert_eq! ( b.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
3617
assert! ( b.tags ( ) .unwrap ( ) [ 0 ] .signer.as_deref ( ) = = Some ( " Ada" ) ) ;
3618
a.tag_create ( " v2" , & [ h2] , " second" ) .unwrap ( ) ;
3619
a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3620
origin.refresh ( ) .unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3621
let rep = b.fetch ( " origin" ) .unwrap ( ) ;
3622
assert_eq! ( rep.new_tags, 1 ) ;
3623
assert_eq! ( b.tag_heads ( " v2" ) .unwrap ( ) , Some ( vec! [ h2] ) ) ;
3624
3625
3626
a.policy_init ( ) .unwrap ( ) ;
3627
a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3628
origin.refresh ( ) .unwrap ( ) ;
1ec6fdbc Andy McCall 17 days ago ↶
3629
let unattested = crate :: protocol:: TagRef:: new(
3630
" v3" .into ( ) ,
3631
origin
75167672 Andy McCall 1 month ago
3632
.store ( )
3633
.put_state ( & StateObject:: new( vec! [ h2] ) )
3634
.unwrap ( ) ,
1ec6fdbc Andy McCall 17 days ago ↶
3635
vec! [ h2] ,
3636
None ,
3637
) ;
75167672 Andy McCall 1 month ago
3638
let r = origin
3639
.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ unattested] )
3640
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3641
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " attestation" ) , " {r:?}" ) ;
3642
a.tag_create ( " v3" , & [ h2] , " " ) .unwrap ( ) ;
3643
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3644
origin.refresh ( ) .unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3645
assert! ( r.iter ( ) .any ( | x | x.name = = " tags/v3" & & x.ok ) , " {r:?}" ) ;
3646
assert! ( a.gc ( true ) .is_ok ( ) ) ;
3647
assert_eq! ( a.tags ( ) .unwrap ( ) .len ( ) , 3 , " tags survive gc" ) ;
e5d9b051 Andy McCall 20 days ago ↶
3648
3649
3650
3651
3652
let rot = a.rotate_identity ( ) .unwrap ( ) ;
3653
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3654
origin.refresh ( ) .unwrap ( ) ;
e5d9b051 Andy McCall 20 days ago ↶
3655
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
3656
let c = Repo:: clone( & url, & root.join ( " c" ) )
3657
.unwrap ( )
3658
.with_home ( root.join ( " home-c" ) ) ;
3659
assert_eq! ( c.tag_heads ( " v3" ) .unwrap ( ) , Some ( vec! [ h2] ) ) ;
3660
let ( _ , sp) = c.current_policy ( ) .unwrap ( ) .unwrap ( ) ;
3661
assert! ( ! sp.policy.authorizes ( & key_a, " tags/v9" ) ) ;
3662
let made = c.identity ( & rot.new) .unwrap ( ) .created;
3663
assert! ( c.predecessor_authorized ( & sp.policy, & key_a, " tags/v9" , made - 1 ) ) ;
3664
assert! ( ! c.predecessor_authorized ( & sp.policy, & key_a, " tags/v9" , made) ) ;
3665
assert! ( ! c.predecessor_authorized ( & sp.policy, & rot.new, " tags/v9" , 0 ) ) ;
132b884b Andy McCall 1 month ago ↶
3666
}
4e88e774 Andy McCall 1 month ago ↶
3667
3668
# [ test ]
3669
fn multiple_workspaces_share_a_repository ( ) {
3670
hermetic ( ) ;
3671
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3672
let root = dir.path ( ) ;
3673
let main_dir = root.join ( " main" ) ;
3674
let repo = Repo:: init( & main_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
3675
write ( & main_dir, " a.txt" , b " a\n " ) ;
3676
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
3677
assert_eq! ( repo.workspaces ( ) .unwrap ( ) .len ( ) , 1 ) ;
3678
3679
3680
let ws_dir = root.join ( " ws2" ) ;
4476cdbb Andy McCall 12 days ago ↶
3681
let ops = repo.op_log ( ) .unwrap ( ) .len ( ) ;
4e88e774 Andy McCall 1 month ago ↶
3682
let ws = repo.workspace_add ( & ws_dir, None ) .unwrap ( ) ;
4476cdbb Andy McCall 12 days ago ↶
3683
assert_eq! (
3684
ws.op_log ( ) .unwrap ( ) .len ( ) ,
3685
ops + 2 ,
3686
" the add and its checkout, and no snapshot finding the tree deleted (oxygen#137)"
3687
) ;
4e88e774 Andy McCall 1 month ago ↶
3688
assert! ( ws_dir.join ( " .oxy" ) .is_file ( ) ) ;
3689
assert_eq! ( fs:: read( ws_dir.join ( " a.txt" ) ) .unwrap ( ) , b " a\n " ) ;
3690
assert_ne! ( ws.workspace ( ) , repo.workspace ( ) ) ;
3691
let list = repo.workspaces ( ) .unwrap ( ) ;
3692
assert_eq! ( list.len ( ) , 2 ) ;
75167672 Andy McCall 1 month ago
3693
assert! ( list
3694
.iter ( )
3695
.any ( | w | w.id = = ws.workspace ( ) & & w.path = = ws_dir.canonicalize ( ) .unwrap ( ) & & ! w.current ) ) ;
4e88e774 Andy McCall 1 month ago ↶
3696
assert! ( ! ws.snapshot ( " status" ) .unwrap ( ) .changed) ;
3697
3698
3699
write ( & ws_dir, " b.txt" , b " b\n " ) ;
3700
let s = ws.snapshot ( " status" ) .unwrap ( ) ;
3701
assert_eq! ( s.statuses.len ( ) , 1 ) ;
75167672 Andy McCall 1 month ago
3702
assert! (
3703
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
3704
" the first workspace is untouched"
3705
) ;
4e88e774 Andy McCall 1 month ago ↶
3706
let h2 = ws.commit ( " b from ws2" , & [ ] ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
3707
repo.refresh ( ) .unwrap ( ) ;
4e88e774 Andy McCall 1 month ago ↶
3708
let mut heads = repo.bookmark_heads ( ) .unwrap ( ) ;
3709
heads.sort ( ) ;
3710
let mut want = vec! [ h1, h2] ;
3711
want.sort ( ) ;
3712
assert_eq! ( heads, want, " both workspaces see the bookmark move" ) ;
3713
assert! ( ! main_dir.join ( " b.txt" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
3714
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " co" )
3715
.unwrap ( ) ;
4e88e774 Andy McCall 1 month ago ↶
3716
assert! ( main_dir.join ( " b.txt" ) .exists ( ) ) ;
3717
3718
3719
let reopened = Repo:: open( & ws_dir) .unwrap ( ) ;
3720
assert_eq! ( reopened.workspace ( ) , ws.workspace ( ) ) ;
3721
assert_eq! ( reopened.id ( ) .unwrap ( ) , repo.id ( ) .unwrap ( ) ) ;
3722
assert_eq! ( reopened.quick_status ( ) .unwrap ( ) .heads, 2 ) ;
3723
3724
3725
assert! ( repo.workspace_add ( & main_dir.join ( " inner" ) , None ) .is_err ( ) ) ;
3726
assert! ( repo.workspace_forget ( & repo.workspace ( ) ) .is_err ( ) ) ;
3727
repo.workspace_forget ( & ws.workspace ( ) ) .unwrap ( ) ;
3728
assert_eq! ( repo.workspaces ( ) .unwrap ( ) .len ( ) , 1 ) ;
3729
assert! ( ws_dir.join ( " b.txt" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
3730
assert! (
3731
ws.snapshot ( " status" ) .is_err ( ) ,
3732
" a forgotten workspace is no longer in the view"
3733
) ;
4e88e774 Andy McCall 1 month ago ↶
3734
6caacfff Andy McCall 1 month ago ↶
3735
3736
4e88e774 Andy McCall 1 month ago ↶
3737
let lock = repo.lock_snapshot ( ) .unwrap ( ) ;
6caacfff Andy McCall 1 month ago ↶
3738
let again = repo.lock_working_copy ( ) .unwrap ( ) ;
3739
drop ( again) ;
4e88e774 Andy McCall 1 month ago ↶
3740
drop ( lock) ;
75167672 Andy McCall 1 month ago
3741
let lock_path = main_dir
3742
.join ( " .oxy/workspaces" )
3743
.join ( repo.workspace ( ) .to_hex ( ) )
3744
.join ( " lock" ) ;
4e88e774 Andy McCall 1 month ago ↶
3745
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 120 ) ;
6caacfff Andy McCall 1 month ago ↶
3746
let age = | p : & Path| fs:: File:: open( p) .unwrap ( ) .set_modified ( old) .unwrap ( ) ;
3747
fs:: write( & lock_path, std:: process:: id( ) .to_string ( ) ) .unwrap ( ) ;
3748
age ( & lock_path) ;
3749
assert! (
3750
! super :: workspace:: lock_is_stale( & lock_path) ,
3751
" a live process keeps its lock"
3752
) ;
3753
fs:: write( & lock_path, " 4294967295" ) .unwrap ( ) ;
3754
age ( & lock_path) ;
4e88e774 Andy McCall 1 month ago ↶
3755
assert! ( repo.lock_snapshot ( ) .is_ok ( ) , " stale lock taken over" ) ;
3756
}
cdb5beba Andy McCall 1 month ago ↶
3757
3758
# [ test ]
3759
fn attributes_binary_eol_nocompress_lockable ( ) {
3760
hermetic ( ) ;
3761
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3762
let root = dir.path ( ) ;
3763
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
3764
write (
3765
root,
3766
" .oxyattributes" ,
3767
b " *.dat binary nocompress\n *.txt eol=crlf\n assets/** lockable\n " ,
3768
) ;
cdb5beba Andy McCall 1 month ago ↶
3769
write ( root, " plain.dat" , b " no nul bytes here\n " ) ;
3770
write ( root, " notes.txt" , b " one\r \n two\r \n " ) ;
3771
write ( root, " assets/model.bin" , b " \0 model" ) ;
3772
let h = repo.commit ( " attrs" , & [ ] ) .unwrap ( ) ;
3773
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
3774
assert! (
3775
snap.files[ " plain.dat" ] .mode.opaque,
3776
" binary attribute forces opaque without NUL bytes"
3777
) ;
3778
assert! (
3779
matches! ( & snap.files[ " notes.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n " ) ,
3780
" stored with LF"
3781
) ;
3782
assert! (
3783
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
3784
" CRLF on disk equals LF in the state"
3785
) ;
cdb5beba Andy McCall 1 month ago ↶
3786
3787
let r = repo.gc ( true ) .unwrap ( ) ;
3788
assert! ( r.packed.objects > 0 ) ;
3789
3790
fs:: remove_file( root.join ( " notes.txt" ) ) .unwrap ( ) ;
3791
repo.checkout ( & [ h] , true , " co" ) .unwrap ( ) ;
3792
assert_eq! ( fs:: read( root.join ( " notes.txt" ) ) .unwrap ( ) , b " one\r \n two\r \n " ) ;
75167672 Andy McCall 1 month ago
3793
assert! ( fs:: metadata( root.join ( " assets/model.bin" ) )
3794
.unwrap ( )
3795
.permissions ( )
3796
.readonly ( ) ) ;
cdb5beba Andy McCall 1 month ago ↶
3797
assert! ( repo.lock_path ( " plain.dat" ) .is_err ( ) , " not lockable" ) ;
3798
repo.lock_path ( " assets/model.bin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3799
assert! ( ! fs:: metadata( root.join ( " assets/model.bin" ) )
3800
.unwrap ( )
3801
.permissions ( )
3802
.readonly ( ) ) ;
cdb5beba Andy McCall 1 month ago ↶
3803
assert! ( repo.is_locked ( " assets/model.bin" ) ) ;
3804
write ( root, " assets/model.bin" , b " \0 model2" ) ;
3805
repo.commit ( " edit model" , & [ ] ) .unwrap ( ) ;
3806
repo.unlock_path ( " assets/model.bin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3807
assert! ( fs:: metadata( root.join ( " assets/model.bin" ) )
3808
.unwrap ( )
3809
.permissions ( )
3810
.readonly ( ) ) ;
cdb5beba Andy McCall 1 month ago ↶
3811
3812
repo.checkout ( & [ h] , true , " co" ) .unwrap ( ) ;
3813
assert_eq! ( fs:: read( root.join ( " assets/model.bin" ) ) .unwrap ( ) , b " \0 model" ) ;
3814
}
6d3eefbc Andy McCall 1 month ago ↶
3815
3816
# [ cfg ( unix ) ]
3817
# [ test ]
3818
fn hooks_can_veto_observe_and_add_dependencies ( ) {
3819
use std:: os:: unix:: fs:: PermissionsExt;
3820
hermetic ( ) ;
3821
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3822
let root = dir.path ( ) ;
3823
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
3824
let hooks = root.join ( " .oxy/hooks" ) ;
3825
fs:: create_dir_all( & hooks) .unwrap ( ) ;
3826
let hook = | name : & str , body : & str | {
3827
let p = hooks.join ( name) ;
3828
fs:: write( & p, format! ( " #!/bin/sh\n {body} \n " ) ) .unwrap ( ) ;
3829
fs:: set_permissions( & p, fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
3830
} ;
3831
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" ) ;
3832
hook ( " post-commit" , " cat > \" $OXY_ROOT/.last-post-commit\" " ) ;
3833
hook ( " post-checkout" , " cat > \" $OXY_ROOT/.last-post-checkout\" " ) ;
3834
hook ( " pre-push" , " echo pushing blocked >&2; exit 3" ) ;
3835
write ( root, " .oxyignore" , b " .last-*\n " ) ;
3836
write ( root, " a.txt" , b " a\n " ) ;
3837
let err = repo.commit ( " WIP: nope" , & [ ] ) .unwrap_err ( ) .to_string ( ) ;
3838
assert! ( err.contains ( " pre-commit refused: no WIP commits" ) , " {err}" ) ;
3839
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
3840
let pre = fs:: read_to_string( root.join ( " .last-pre-commit" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3841
assert! (
3842
pre.contains ( " \" event\" :\" pre-commit\" " )
3843
& & pre.contains ( " \" path\" :\" a.txt\" " )
3844
& & pre.contains ( " \" message\" :\" first\" " ) ,
3845
" {pre}"
3846
) ;
6d3eefbc Andy McCall 1 month ago ↶
3847
let post = fs:: read_to_string( root.join ( " .last-post-commit" ) ) .unwrap ( ) ;
3848
assert! ( post.contains ( & h1.to_hex ( ) ) , " {post}" ) ;
3849
repo.checkout ( & [ h1] , false , " co" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3850
assert! ( fs:: read_to_string( root.join ( " .last-post-checkout" ) )
3851
.unwrap ( )
3852
.contains ( " post-checkout" ) ) ;
6d3eefbc Andy McCall 1 month ago ↶
3853
repo.config.set ( " remote.origin.url" , " /nowhere" ) .unwrap ( ) ;
3854
repo.save_config ( ) .unwrap ( ) ;
3855
let err = repo.push ( " origin" , false ) .unwrap_err ( ) .to_string ( ) ;
3856
assert! ( err.contains ( " pre-push refused: pushing blocked" ) , " {err}" ) ;
3857
3858
3859
write ( root, " b.txt" , b " b\n " ) ;
75167672 Andy McCall 1 month ago
3860
hook (
3861
" semantic-deps" ,
3862
& format! (
3863
" cat > \" $OXY_ROOT/.last-semantic\" ; echo '{{ \" deps\" : [\" {} \" ]}} '" ,
3864
h1.to_hex ( )
3865
) ,
3866
) ;
6d3eefbc Andy McCall 1 month ago ↶
3867
let h2 = repo.commit ( " b without hook" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3868
assert! (
3869
repo.store ( ) .get_change ( & h2) .unwrap ( ) .deps.is_empty ( ) ,
3870
" hook disabled by default"
3871
) ;
6d3eefbc Andy McCall 1 month ago ↶
3872
repo.config.set ( " hooks.semantic-deps" , " true" ) .unwrap ( ) ;
3873
repo.save_config ( ) .unwrap ( ) ;
3874
write ( root, " c.txt" , b " c\n " ) ;
3875
let h3 = repo.commit ( " c with hook" , & [ ] ) .unwrap ( ) ;
3876
assert_eq! ( repo.store ( ) .get_change ( & h3) .unwrap ( ) .deps, vec! [ h1] ) ;
3877
let sem = fs:: read_to_string( root.join ( " .last-semantic" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3878
assert! (
3879
sem.contains ( " \" touched_paths\" :[\" c.txt\" ]" ) & & sem.contains ( " candidate_deps" ) ,
3880
" {sem}"
3881
) ;
6d3eefbc Andy McCall 1 month ago ↶
3882
assert_eq! ( json_str ( " a\" b\\ c\n " ) , " \" a\\ \" b\\ \\ c\\ n\" " ) ;
3883
}
23b217d7 Andy McCall 1 month ago ↶
3884
3885
# [ test ]
3886
fn amend_into_older_change_and_split ( ) {
3887
hermetic ( ) ;
3888
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3889
let root = dir.path ( ) ;
3890
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
3891
repo.create_identity ( " Ada" , " a@b" , IdentityScope:: User)
3892
.unwrap ( ) ;
23b217d7 Andy McCall 1 month ago ↶
3893
repo.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
3894
write ( root, " a.txt" , b " one\n two\n " ) ;
3895
let h0 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
3896
write ( root, " a.txt" , b " one\n TWO\n " ) ;
3897
let h1 = repo.commit ( " edit two" , & [ ] ) .unwrap ( ) ;
3898
write ( root, " a.txt" , b " one\n TWO\n three\n " ) ;
3899
let h2 = repo.commit ( " add three" , & [ ] ) .unwrap ( ) ;
3900
let id1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) .change_id;
3901
3902
3903
write ( root, " a.txt" , b " one\n TWO!\n three\n " ) ;
3904
let h1b = repo.amend_into ( & h1, None ) .unwrap ( ) ;
3905
assert_ne! ( h1b, h1) ;
3906
let c1b = repo.store ( ) .get_change ( & h1b) .unwrap ( ) ;
3907
assert_eq! ( c1b.change_id, id1) ;
3908
assert_eq! ( c1b.deps, vec! [ h0] ) ;
75167672 Andy McCall 1 month ago
3909
assert! ( matches! (
3910
repo.verify_change ( & h1b) .unwrap ( ) ,
3911
SignatureStatus:: Valid { .. }
3912
) ) ;
23b217d7 Andy McCall 1 month ago ↶
3913
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
3914
assert_eq! ( heads.len ( ) , 1 ) ;
3915
let top = repo.store ( ) .get_change ( & heads[ 0 ] ) .unwrap ( ) ;
3916
assert_eq! ( top.message, " add three" ) ;
75167672 Andy McCall 1 month ago
3917
assert_eq! (
3918
top.deps.iter ( ) .copied ( ) .collect:: < BTreeSet< _ > > ( ) ,
3919
[ h0, h1b] .into_iter ( ) .collect ( ) ,
3920
" depends on the file's creator and the rewritten line"
3921
) ;
23b217d7 Andy McCall 1 month ago ↶
3922
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
3923
assert! (
3924
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n TWO!\n three\n " )
3925
) ;
3926
assert! (
3927
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
3928
" the working copy is clean after folding"
3929
) ;
23b217d7 Andy McCall 1 month ago ↶
3930
3931
let only = repo.load_state ( & [ h1b] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
3932
assert! (
3933
matches! ( & only.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n TWO!\n " )
3934
) ;
23b217d7 Andy McCall 1 month ago ↶
3935
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 ) ;
3936
3937
3938
write ( root, " a.txt" , b " one\n TWO!\n three!\n " ) ;
3939
let err = repo.amend_into ( & h1b, None ) .unwrap_err ( ) .to_string ( ) ;
3940
assert! ( err.contains ( " introduced after" ) , " {err}" ) ;
75167672 Andy McCall 1 month ago
3941
assert! (
3942
repo.snapshot ( " status" ) .unwrap ( ) .statuses.len ( ) = = 1 ,
3943
" nothing was changed by the refusal"
3944
) ;
23b217d7 Andy McCall 1 month ago ↶
3945
write ( root, " a.txt" , b " one\n TWO!\n three\n " ) ;
3946
3947
3948
write ( root, " b.txt" , b " b\n " ) ;
3949
write ( root, " c.txt" , b " c\n " ) ;
3950
let h3 = repo.commit ( " b and c" , & [ ] ) .unwrap ( ) ;
3951
let id3 = repo.store ( ) .get_change ( & h3) .unwrap ( ) .change_id;
3952
let ( p1, p2) = repo.split ( & h3, & [ " b.txt" .into ( ) ] ) .unwrap ( ) ;
3953
let c_1 = repo.store ( ) .get_change ( & p1) .unwrap ( ) ;
3954
let c_2 = repo.store ( ) .get_change ( & p2) .unwrap ( ) ;
3955
assert! ( c_1 .message.contains ( " part 1: b.txt" ) ) ;
3956
assert_eq! ( c_2 .change_id, id3) ;
3957
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
75167672 Andy McCall 1 month ago
3958
assert! (
3959
heads.contains ( & p1) & & heads.contains ( & p2) ,
3960
" independent parts are both heads"
3961
) ;
3962
let snap = repo
3963
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
3964
.unwrap ( )
3965
.materialize ( ) ;
23b217d7 Andy McCall 1 month ago ↶
3966
assert! ( snap.files.contains_key ( " b.txt" ) & & snap.files.contains_key ( " c.txt" ) ) ;
3967
let only1 = repo.load_state ( & [ p1] ) .unwrap ( ) .materialize ( ) ;
3968
assert! ( only1.files.contains_key ( " b.txt" ) & & ! only1.files.contains_key ( " c.txt" ) ) ;
3969
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
3970
assert! ( repo.split ( & p2, & [ " nope.txt" .into ( ) ] ) .is_err ( ) ) ;
3971
let _ = h2;
3972
}
28fc23d7 Andy McCall 1 month ago ↶
3973
3974
# [ test ]
3975
fn identity_lifecycle ( ) {
3976
hermetic ( ) ;
3977
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3978
let root = dir.path ( ) ;
3979
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
3980
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
3981
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
3982
.unwrap ( )
3983
.with_home ( root.join ( " home-a" ) ) ;
28fc23d7 Andy McCall 1 month ago ↶
3984
a.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3985
let ( key_a, _ ) = a
3986
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
3987
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3988
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
3989
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
3990
assert! ( a.policy_init ( ) .unwrap ( ) .applied) ;
3991
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
767ac6cd Andy McCall 19 days ago
3992
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
3993
3994
75167672 Andy McCall 1 month ago
3995
let mut e = Repo:: init( & root.join ( " e" ) )
3996
.unwrap ( )
3997
.with_home ( root.join ( " home-e" ) ) ;
28fc23d7 Andy McCall 1 month ago ↶
3998
e.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3999
e.create_identity_with (
4000
" Eve" ,
4001
" [email protected] " ,
4002
IdentityScope:: User,
4003
Some ( Hlc:: now( ) .wall_ms - 1 ) ,
4004
)
4005
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4006
write ( & root.join ( " e" ) , " e.txt" , b " e\n " ) ;
4007
let he = e.commit ( " late" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4008
assert! (
4009
matches! ( e.verify_change ( & he) .unwrap ( ) , SignatureStatus:: Invalid( m) if m.contains ( " expired" ) )
4010
) ;
28fc23d7 Andy McCall 1 month ago ↶
4011
75167672 Andy McCall 1 month ago
4012
let r = e
4013
.renew_identity ( Some ( Hlc:: now( ) .wall_ms + 86_400_000 ) )
4014
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4015
assert_eq! ( e.identity ( & r.new) .unwrap ( ) .previous, Some ( r.old) ) ;
4016
write ( & root.join ( " e" ) , " e.txt" , b " e2\n " ) ;
4017
let he2 = e.commit ( " in time" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4018
assert! (
4019
matches! ( e.verify_change ( & he2) .unwrap ( ) , SignatureStatus:: Valid { key, .. } if key = = r.new)
4020
) ;
28fc23d7 Andy McCall 1 month ago ↶
4021
4022
4023
let r = a.rotate_identity ( ) .unwrap ( ) ;
4024
assert_eq! ( r.old, key_a) ;
4025
assert_eq! ( a.config.author_key, Some ( r.new) ) ;
4026
assert_eq! ( a.identity ( & r.new) .unwrap ( ) .previous, Some ( key_a) ) ;
4027
assert! ( r.policy.as_ref ( ) .unwrap ( ) .applied) ;
4028
let ( _ , sp) = a.current_policy ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4029
assert_eq! (
4030
( sp.policy.serial, sp.policy.roots.clone ( ) ) ,
4031
( 2 , vec! [ r.new] )
4032
) ;
4033
assert! (
4034
! sp.policy.revoked.contains ( & key_a) & & sp.policy.authorizes ( & r.new, " main" ) ,
4035
" superseded, not revoked"
4036
) ;
28fc23d7 Andy McCall 1 month ago ↶
4037
assert! ( sp.policy.succeeds ( & r.new, & key_a) & & ! sp.policy.succeeds ( & key_a, & r.new) ) ;
75167672 Andy McCall 1 month ago
4038
assert! (
4039
matches! ( a.verify_change ( & h1) .unwrap ( ) , SignatureStatus:: Valid { key, revoked: false , .. } if key = = key_a)
4040
) ;
28fc23d7 Andy McCall 1 month ago ↶
4041
write ( & root.join ( " a" ) , " a.txt" , b " a2\n " ) ;
4042
let h2 = a.commit ( " two" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4043
assert! (
4044
matches! ( a.verify_change ( & h2) .unwrap ( ) , SignatureStatus:: Valid { key, revoked: false , .. } if key = = r.new)
4045
) ;
28fc23d7 Andy McCall 1 month ago ↶
4046
4047
let res = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
4048
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4049
assert! ( res.iter ( ) .all ( | x | x.ok ) , " {res:?}" ) ;
4050
assert_eq! ( origin.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 2 ) ;
4051
let key_a = r.new;
4052
4053
4054
75167672 Andy McCall 1 month ago
4055
let mut b = Repo:: clone( & url, & root.join ( " b" ) )
4056
.unwrap ( )
4057
.with_home ( root.join ( " home-b" ) ) ;
4058
let ( key_b, id_b) = b
4059
.create_identity ( " Bob" , " [email protected] " , IdentityScope:: User)
4060
.unwrap ( ) ;
7ba155f2 Andy McCall 1 month ago ↶
4061
let r = a.delegate_add ( id_b.clone ( ) , Scope:: ALL , None ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4062
assert_eq! ( r.old, key_a) ;
4063
let key_a2 = r.new;
75167672 Andy McCall 1 month ago
4064
assert_eq! (
7ba155f2 Andy McCall 1 month ago ↶
4065
a.identity ( & key_a2) .unwrap ( ) .delegates,
4066
vec! [ Delegate:: full( key_b) ]
4067
) ;
4068
assert_eq! (
75167672 Andy McCall 1 month ago
4069
a.identity ( & key_a2) .unwrap ( ) .ed25519_pk,
4070
a.identity ( & key_a) .unwrap ( ) .ed25519_pk
7ba155f2 Andy McCall 1 month ago ↶
4071
) ;
4072
assert! (
4073
a.delegate_add ( id_b.clone ( ) , Scope:: ALL , None ) .is_err ( ) ,
4074
" already a delegate"
75167672 Andy McCall 1 month ago
4075
) ;
28fc23d7 Andy McCall 1 month ago ↶
4076
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | x | x.ok ) ) ;
767ac6cd Andy McCall 19 days ago
4077
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4078
write ( & root.join ( " a" ) , " a.txt" , b " a3\n " ) ;
4079
let h3 = a.commit ( " three" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4080
assert! (
4081
matches! ( a.verify_change ( & h3) .unwrap ( ) , SignatureStatus:: Valid { key, .. } if key = = key_a2) ,
4082
" the same secret signs under the new KeyId"
4083
) ;
28fc23d7 Andy McCall 1 month ago ↶
4084
4085
a.set_user_config ( " core.sign" , " seal" ) .unwrap ( ) ;
4086
write ( & root.join ( " a" ) , " a.txt" , b " a4\n " ) ;
4087
let h4 = a.commit ( " four" , & [ ] ) .unwrap ( ) ;
4088
assert_eq! ( a.verify_change ( & h4) .unwrap ( ) , SignatureStatus:: Unsigned) ;
4089
let ( _ , sk_b) = b.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4090
a.store ( )
4091
.put_signature ( & h4, & sk_b.sign ( Domain:: Change, & key_b, & h4) )
4092
.unwrap ( ) ;
4093
assert! (
4094
matches! ( a.verify_change ( & h4) .unwrap ( ) , SignatureStatus:: Valid { key, .. } if key = = key_b)
4095
) ;
28fc23d7 Andy McCall 1 month ago ↶
4096
a.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
4097
4098
let r = a.delegate_remove ( & key_b) .unwrap ( ) ;
4099
assert! ( a.identity ( & r.new) .unwrap ( ) .delegates.is_empty ( ) ) ;
4100
assert! ( a.delegate_remove ( & key_b) .is_err ( ) ) ;
4101
let key_a3 = r.new;
4102
let res = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
4103
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4104
assert! ( res.iter ( ) .all ( | x | x.ok ) , " {res:?}" ) ;
4105
4106
4107
4108
assert! ( a.policy_root_add ( id_b.clone ( ) ) .unwrap ( ) .applied) ;
4109
assert! ( a.policy_root_add ( id_b.clone ( ) ) .is_err ( ) ) ;
4110
assert! ( a.policy_set_threshold ( 3 ) .is_err ( ) ) ;
4111
assert! ( a.policy_set_threshold ( 2 ) .unwrap ( ) .applied) ;
4112
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | x | x.ok ) ) ;
767ac6cd Andy McCall 19 days ago
4113
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4114
b.pull ( " origin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4115
assert_eq! (
4116
b.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.root_threshold,
4117
2
4118
) ;
4119
let mut c = Repo:: init( & root.join ( " c" ) )
4120
.unwrap ( )
4121
.with_home ( root.join ( " home-c" ) ) ;
4122
let ( key_c, id_c) = c
4123
.create_identity ( " Cy" , " [email protected] " , IdentityScope:: User)
4124
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4125
let serial_before = a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial;
4126
let proposal = a.policy_grant ( id_c, None ) .unwrap ( ) ;
4127
assert! ( ! proposal.applied & & proposal.sigs.len ( ) = = 1 ) ;
4128
assert_eq! ( proposal.policy.serial, serial_before + 1 ) ;
75167672 Andy McCall 1 month ago
4129
assert_eq! (
4130
a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial,
4131
serial_before,
4132
" a proposal changes nothing yet"
4133
) ;
28fc23d7 Andy McCall 1 month ago ↶
4134
4135
let bytes = proposal.signed ( ) .encode ( ) ;
75167672 Andy McCall 1 month ago
4136
let signed = b
4137
.policy_sign ( SignedPolicy:: decode( & bytes) .unwrap ( ) )
4138
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4139
assert! ( signed.applied & & signed.sigs.len ( ) = = 2 ) ;
75167672 Andy McCall 1 month ago
4140
assert! ( b
4141
.current_policy ( )
4142
.unwrap ( )
4143
.unwrap ( )
4144
.1
4145
.policy
4146
.authorizes ( & key_c, " main" ) ) ;
4147
assert! (
4148
b.policy_sign ( signed.signed ( ) ) .is_err ( ) ,
4149
" a stale proposal is refused"
4150
) ;
28fc23d7 Andy McCall 1 month ago ↶
4151
75167672 Andy McCall 1 month ago
4152
assert! ( b
4153
.push ( " origin" , false )
4154
.unwrap ( )
4155
.iter ( )
4156
.any ( | x | x.name = = " policy" & & x.ok ) ) ;
767ac6cd Andy McCall 19 days ago
4157
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4158
a.pull ( " origin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4159
assert_eq! (
4160
a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial,
4161
serial_before + 1
4162
) ;
28fc23d7 Andy McCall 1 month ago ↶
4163
4164
assert! ( a.policy_revoke ( & key_a3) .is_err ( ) ) ;
75167672 Andy McCall 1 month ago
4165
let u = a
4166
.revoke_identity ( & key_c, " left the project" )
4167
.unwrap ( )
4168
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4169
assert! ( ! u.applied) ;
4170
assert! ( a.is_revoked ( & key_c) .unwrap ( ) ) ;
4171
assert! ( ! b.is_revoked ( & key_c) .unwrap ( ) ) ;
4172
let u = b.policy_sign ( u.signed ( ) ) .unwrap ( ) ;
4173
assert! ( u.applied & & u.policy.is_revoked ( & key_c) ) ;
4174
assert! ( b.is_revoked ( & key_c) .unwrap ( ) ) ;
4175
75167672 Andy McCall 1 month ago
4176
assert! (
4177
b.policy_revoke ( & key_a3) .is_err ( ) ,
4178
" would leave one root for a threshold of 2"
4179
) ;
28fc23d7 Andy McCall 1 month ago ↶
4180
4181
let key_c_own = c.config.author_key.unwrap ( ) ;
75167672 Andy McCall 1 month ago
4182
assert! ( c
4183
.revoke_identity ( & key_c_own, " lost laptop" )
4184
.unwrap ( )
4185
.is_none ( ) ) ;
28fc23d7 Andy McCall 1 month ago ↶
4186
assert! ( c.is_revoked ( & key_c_own) .unwrap ( ) ) ;
4187
assert_eq! ( key_c_own, key_c) ;
4188
}
4189
6e723b81 Andy McCall 1 month ago ↶
4190
# [ test ]
4191
fn renames_are_detected_and_tracked_as_moves ( ) {
4192
hermetic ( ) ;
4193
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4194
let root = dir.path ( ) ;
4195
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
4196
write (
4197
root,
4198
" src/old.rs" ,
4199
b " fn a() {}\n fn b() {}\n fn c() {}\n fn d() {}\n " ,
4200
) ;
6e723b81 Andy McCall 1 month ago ↶
4201
write ( root, " img.bin" , b " \x89 PNG\0 \0 \x01 \x02 " ) ;
4202
write ( root, " tiny.txt" , b " x\n " ) ;
4203
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4204
let node_old = repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) .files[ " src/old.rs" ] .node;
4205
let node_img = repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) .files[ " img.bin" ] .node;
4206
4207
4208
4209
fs:: remove_file( root.join ( " src/old.rs" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4210
write (
4211
root,
4212
" lib/new.rs" ,
4213
b " fn a() {}\n fn b() {}\n fn c() {}\n fn e() {}\n " ,
4214
) ;
6e723b81 Andy McCall 1 month ago ↶
4215
fs:: remove_file( root.join ( " img.bin" ) ) .unwrap ( ) ;
4216
write ( root, " assets/img.bin" , b " \x89 PNG\0 \0 \x01 \x02 " ) ;
4217
fs:: remove_file( root.join ( " tiny.txt" ) ) .unwrap ( ) ;
4218
write ( root, " other.txt" , b " completely different\n " ) ;
4219
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4220
let statuses: Vec< ( & str , ChangeStatus) > = snap
4221
.statuses
4222
.iter ( )
4223
.map ( | s | ( s.path.as_str ( ) , s.status) )
4224
.collect ( ) ;
6e723b81 Andy McCall 1 month ago ↶
4225
assert_eq! (
4226
statuses,
75167672 Andy McCall 1 month ago
4227
vec! [
4228
( " assets/img.bin" , ChangeStatus:: Renamed) ,
4229
( " lib/new.rs" , ChangeStatus:: Renamed) ,
4230
( " other.txt" , ChangeStatus:: Added) ,
4231
( " src" , ChangeStatus:: Removed) ,
4232
( " tiny.txt" , ChangeStatus:: Removed)
4233
]
6e723b81 Andy McCall 1 month ago ↶
4234
) ;
4235
let h2 = repo.commit ( " move things" , & [ ] ) .unwrap ( ) ;
4236
let c = repo.store ( ) .get_change ( & h2) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4237
let moves: Vec< _ > = c
4238
.tree_ops
4239
.iter ( )
4240
.filter ( | op | matches! ( op, TreeOp:: Move { .. } ) )
4241
.collect ( ) ;
6e723b81 Andy McCall 1 month ago ↶
4242
assert_eq! ( moves.len ( ) , 2 , " {:?}" , c.tree_ops) ;
75167672 Andy McCall 1 month ago
4243
assert! ( c.tree_ops.iter ( ) .any (
4244
| op | matches! ( op, TreeOp:: Move { node, name, .. } if * node = = node_old & & name = = " new.rs" )
4245
) ) ;
4246
assert! ( c.tree_ops.iter ( ) .any (
4247
| op | matches! ( op, TreeOp:: Move { node, name, .. } if * node = = node_img & & name = = " img.bin" )
4248
) ) ;
6e723b81 Andy McCall 1 month ago ↶
4249
let snap = repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
4250
assert_eq! (
4251
snap.files[ " lib/new.rs" ] .node, node_old,
4252
" the node survives the rename"
4253
) ;
4254
assert! (
4255
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 " )
4256
) ;
6e723b81 Andy McCall 1 month ago ↶
4257
assert! ( ! snap.files.contains_key ( " src/old.rs" ) & & ! snap.files.contains_key ( " src" ) ) ;
75167672 Andy McCall 1 month ago
4258
assert! ( render_change (
4259
& c,
4260
& repo.load_state ( & [ h1] ) .unwrap ( ) ,
4261
& repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( )
4262
)
4263
.contains ( " R " ) ) ;
6e723b81 Andy McCall 1 month ago ↶
4264
4265
let other = tempfile:: tempdir( ) .unwrap ( ) ;
4266
let clone = Repo:: clone( & root.to_string_lossy ( ) , other.path ( ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4267
assert_eq! (
4268
fs:: read( other.path ( ) .join ( " lib/new.rs" ) ) .unwrap ( ) ,
4269
b " fn a() {}\n fn b() {}\n fn c() {}\n fn e() {}\n "
4270
) ;
6e723b81 Andy McCall 1 month ago ↶
4271
assert! ( ! other.path ( ) .join ( " src" ) .exists ( ) ) ;
4272
assert! ( ! clone.snapshot ( " status" ) .unwrap ( ) .changed) ;
4273
4274
assert_eq! ( similarity ( b " a\n b\n " , b " a\n b\n " ) , 100 ) ;
4275
assert_eq! ( similarity ( b " a\n b\n c\n d\n " , b " a\n b\n x\n y\n " ) , 50 ) ;
4276
assert_eq! ( similarity ( b " hello\n " , b " world\n " ) , 0 ) ;
4277
assert_eq! ( similarity ( b " " , b " x\n " ) , 0 ) ;
4278
}
4279
30ea9c1e Andy McCall 1 month ago ↶
4280
# [ test ]
4281
fn git_fetch_and_push_round_trip ( ) {
4282
hermetic ( ) ;
75167672 Andy McCall 1 month ago
4283
if std:: process:: Command:: new( " git" )
4284
.arg ( " --version" )
4285
.output ( )
4286
.is_err ( )
4287
{
30ea9c1e Andy McCall 1 month ago ↶
4288
eprintln! ( " git not installed; skipping" ) ;
4289
return ;
4290
}
4291
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4292
let root = dir.path ( ) ;
4293
let git_in = | d : & Path, args : & [& str ]| -> String {
4294
let out = std:: process:: Command:: new( " git" )
4295
.arg ( " -C" )
4296
.arg ( d)
75167672 Andy McCall 1 month ago
4297
.args ( [
4298
" -c" ,
4299
" user.name=Grace" ,
4300
" -c" ,
4301
" [email protected] " ,
4302
" -c" ,
4303
" commit.gpgsign=false" ,
4304
] )
30ea9c1e Andy McCall 1 month ago ↶
4305
.args ( args)
4306
.output ( )
4307
.unwrap ( ) ;
75167672 Andy McCall 1 month ago
4308
assert! (
4309
out.status.success ( ) ,
4310
" git {args:?}: {}" ,
4311
String :: from_utf8_lossy( & out.stderr)
4312
) ;
30ea9c1e Andy McCall 1 month ago ↶
4313
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
4314
} ;
4315
4316
let remote = root.join ( " remote.git" ) ;
4317
fs:: create_dir_all( & remote) .unwrap ( ) ;
4318
git_in ( & remote, & [ " init" , " -q" , " --bare" , " -b" , " main" ] ) ;
4319
let work = root.join ( " work" ) ;
75167672 Andy McCall 1 month ago
4320
git_in (
4321
root,
4322
& [
4323
" clone" ,
4324
" -q" ,
4325
remote.to_str ( ) .unwrap ( ) ,
4326
work.to_str ( ) .unwrap ( ) ,
4327
] ,
4328
) ;
30ea9c1e Andy McCall 1 month ago ↶
4329
git_in ( & work, & [ " checkout" , " -q" , " -b" , " main" ] ) ;
4330
write ( & work, " README.md" , b " # Project\n " ) ;
4331
write ( & work, " src/lib.rs" , b " pub fn a() {}\n " ) ;
4332
git_in ( & work, & [ " add" , " ." ] ) ;
4333
git_in ( & work, & [ " commit" , " -q" , " -m" , " initial" ] ) ;
4334
write ( & work, " src/lib.rs" , b " pub fn a() {}\n pub fn b() {}\n " ) ;
4335
git_in ( & work, & [ " commit" , " -q" , " -am" , " second" ] ) ;
4336
git_in ( & work, & [ " push" , " -q" , " origin" , " main" ] ) ;
4337
let tip1 = git_in ( & work, & [ " rev-parse" , " HEAD" ] ) ;
4338
4339
4340
let o = root.join ( " oxy" ) ;
4341
let mut repo = Repo:: init( & o) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
4342
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
4343
.unwrap ( ) ;
30ea9c1e Andy McCall 1 month ago ↶
4344
let url = remote.to_string_lossy ( ) .to_string ( ) ;
4345
let r = repo.git_fetch ( & url, " main" ) .unwrap ( ) ;
4346
assert_eq! ( ( r.commits, r.heads) , ( 2 , 1 ) ) ;
75167672 Andy McCall 1 month ago
4347
assert_eq! (
4348
fs:: read( o.join ( " src/lib.rs" ) ) .unwrap ( ) ,
4349
b " pub fn a() {}\n pub fn b() {}\n "
4350
) ;
30ea9c1e Andy McCall 1 month ago ↶
4351
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
75167672 Andy McCall 1 month ago
4352
assert_eq! (
4353
repo.git_fetch ( & url, " main" ) .unwrap ( ) .commits,
4354
0 ,
4355
" nothing new"
4356
) ;
30ea9c1e Andy McCall 1 month ago ↶
4357
4358
75167672 Andy McCall 1 month ago
4359
write (
4360
& o,
4361
" src/lib.rs" ,
4362
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n " ,
4363
) ;
30ea9c1e Andy McCall 1 month ago ↶
4364
write ( & o, " NOTES.md" , b " from oxy\n " ) ;
4365
let h_oxy = repo.commit ( " add c and notes" , & [ ] ) .unwrap ( ) ;
4366
let r = repo.git_push ( & url, " main" ) .unwrap ( ) ;
4367
assert_eq! ( r.exported, 1 , " {r:?}" ) ;
4368
git_in ( & work, & [ " pull" , " -q" , " --ff-only" , " origin" , " main" ] ) ;
75167672 Andy McCall 1 month ago
4369
assert_eq! (
4370
git_in ( & work, & [ " log" , " --format=%s" , " main" ] ) ,
4371
" add c and notes\n second\n initial"
4372
) ;
30ea9c1e Andy McCall 1 month ago ↶
4373
assert_eq! ( git_in ( & work, & [ " rev-parse" , " HEAD~1" ] ) , tip1) ;
4374
assert_eq! ( fs:: read( work.join ( " NOTES.md" ) ) .unwrap ( ) , b " from oxy\n " ) ;
4375
assert_eq! ( git_in ( & work, & [ " log" , " -1" , " --format=%an" , " main" ] ) , " Ada" ) ;
75167672 Andy McCall 1 month ago
4376
assert! ( git_in ( & work, & [ " log" , " -1" , " --format=%B" , " main" ] )
4377
.contains ( & format! ( " Oxy-Hash: {} " , h_oxy.to_hex ( ) ) ) ) ;
4378
assert_eq! (
4379
repo.git_push ( & url, " main" ) .unwrap ( ) .exported,
4380
0 ,
4381
" already pushed"
4382
) ;
30ea9c1e Andy McCall 1 month ago ↶
4383
4384
4385
4386
write ( & work, " README.md" , b " # Project\n \n More.\n " ) ;
4387
git_in ( & work, & [ " commit" , " -q" , " -am" , " readme from git" ] ) ;
4388
git_in ( & work, & [ " push" , " -q" , " origin" , " main" ] ) ;
4389
let tip2 = git_in ( & work, & [ " rev-parse" , " HEAD" ] ) ;
75167672 Andy McCall 1 month ago
4390
assert! ( repo
4391
.git_push ( & url, " main" )
4392
.unwrap_err ( )
4393
.to_string ( )
4394
.contains ( " fetch" ) ) ;
30ea9c1e Andy McCall 1 month ago ↶
4395
let r = repo.git_fetch ( & url, " main" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4396
assert_eq! (
4397
( r.commits, r.heads) ,
4398
( 1 , 2 ) ,
4399
" the Git change and the oxy change commute"
4400
) ;
4401
assert_eq! (
4402
fs:: read( o.join ( " README.md" ) ) .unwrap ( ) ,
4403
b " # Project\n \n More.\n "
4404
) ;
30ea9c1e Andy McCall 1 month ago ↶
4405
assert_eq! ( fs:: read( o.join ( " NOTES.md" ) ) .unwrap ( ) , b " from oxy\n " ) ;
4406
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4407
4408
write ( & o, " NOTES.md" , b " from oxy\n again\n " ) ;
4409
repo.commit ( " notes again" , & [ ] ) .unwrap ( ) ;
4410
assert_eq! ( repo.git_push ( & url, " main" ) .unwrap ( ) .exported, 1 ) ;
4411
git_in ( & work, & [ " pull" , " -q" , " --ff-only" , " origin" , " main" ] ) ;
4412
assert_eq! ( git_in ( & work, & [ " rev-parse" , " HEAD~1" ] ) , tip2) ;
75167672 Andy McCall 1 month ago
4413
assert_eq! (
4414
fs:: read( work.join ( " NOTES.md" ) ) .unwrap ( ) ,
4415
b " from oxy\n again\n "
4416
) ;
4417
assert_eq! (
4418
git_in ( & work, & [ " log" , " --format=%s" , " main" ] )
4419
.lines ( )
4420
.count ( ) ,
4421
5
4422
) ;
30ea9c1e Andy McCall 1 month ago ↶
4423
}
4424
2b872d07 Andy McCall 1 month ago ↶
4425
4426
4427
# [ test ]
4428
# [ ignore ]
4429
fn bench_one_gibibyte_file ( ) {
4430
hermetic ( ) ;
4431
4432
let dir = match std:: env:: var_os( " OXY_BENCH_DIR" ) {
4433
Some ( d) => tempfile:: tempdir_in( d) .unwrap ( ) ,
4434
None => tempfile:: tempdir( ) .unwrap ( ) ,
4435
} ;
4436
let root = dir.path ( ) ;
75167672 Andy McCall 1 month ago
4437
let repo = Repo:: init( & root.join ( " r" ) )
4438
.unwrap ( )
4439
.with_home ( root.join ( " home" ) ) ;
2b872d07 Andy McCall 1 month ago ↶
4440
let size = 1 usize < < 30 ;
4441
let mut data = vec! [ 0 u8 ; size] ;
4442
let mut x = 0x9e3779b97f4a7c15 u64 ;
4443
for chunk in data.chunks_mut ( 8 ) {
4444
x ^ = x < < 13 ;
4445
x ^ = x > > 7 ;
4446
x ^ = x < < 17 ;
4447
let b = x.to_le_bytes ( ) ;
4448
chunk.copy_from_slice ( & b[ .. chunk.len ( ) ] ) ;
4449
}
4450
let t = std:: time:: Instant:: now( ) ;
4451
write ( & root.join ( " r" ) , " big.bin" , & data) ;
4452
eprintln! ( " write 1 GiB to disk: {:.2?}" , t.elapsed ( ) ) ;
4453
let t = std:: time:: Instant:: now( ) ;
4454
let h = repo.commit ( " big" , & [ ] ) .unwrap ( ) ;
4455
eprintln! ( " commit (chunk + hash + store): {:.2?}" , t.elapsed ( ) ) ;
4456
let t = std:: time:: Instant:: now( ) ;
4457
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4458
eprintln! ( " status on a clean 1 GiB working copy: {:.2?}" , t.elapsed ( ) ) ;
4459
let t = std:: time:: Instant:: now( ) ;
4460
let objects = repo.store ( ) .list ( ) .unwrap ( ) .len ( ) ;
4461
eprintln! ( " objects stored: {objects} ({:.2?} to list)" , t.elapsed ( ) ) ;
4462
let t = std:: time:: Instant:: now( ) ;
4463
let g = repo.gc ( true ) .unwrap ( ) ;
4464
eprintln! ( " gc (pack): {:.2?} {g:?}" , t.elapsed ( ) ) ;
4465
let t = std:: time:: Instant:: now( ) ;
4466
let clone = Repo:: clone( & root.join ( " r" ) .to_string_lossy ( ) , & root.join ( " c" ) ) .unwrap ( ) ;
4467
eprintln! ( " clone (fetch + checkout): {:.2?}" , t.elapsed ( ) ) ;
4468
let t = std:: time:: Instant:: now( ) ;
4469
let back = fs:: read( root.join ( " c/big.bin" ) ) .unwrap ( ) ;
4470
assert_eq! ( blake3:: hash( & back) , blake3:: hash( & data) ) ;
4471
eprintln! ( " read back and verify: {:.2?}" , t.elapsed ( ) ) ;
4472
assert_eq! ( clone.bookmark_heads ( ) .unwrap ( ) , vec! [ h] ) ;
4473
let store_bytes: u64 = walkdir_size ( & root.join ( " r/.oxy/objects" ) ) ;
75167672 Andy McCall 1 month ago
4474
eprintln! (
4475
" store size: {:.1} MiB for 1 GiB of data" ,
4476
store_bytes as f64 / ( 1024. 0 * 1024. 0 )
4477
) ;
2b872d07 Andy McCall 1 month ago ↶
4478
}
4479
4480
fn walkdir_size ( p : & Path) -> u64 {
4481
let mut total = 0 ;
4482
if let Ok ( rd) = fs:: read_dir( p) {
4483
for e in rd.flatten ( ) {
4484
let path = e.path ( ) ;
4485
if path.is_dir ( ) {
4486
total + = walkdir_size ( & path) ;
4487
} else if let Ok ( m) = fs:: metadata( & path) {
4488
total + = m.len ( ) ;
4489
}
4490
}
4491
}
4492
total
4493
}
4494
b7f102fc Andy McCall 1 month ago ↶
4495
4496
4497
# [ cfg ( unix ) ]
4498
# [ test ]
4499
fn stat_cache_skips_reading_unchanged_opaque_files ( ) {
4500
use std:: os:: unix:: fs:: PermissionsExt;
4501
hermetic ( ) ;
4502
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4503
let root = dir.path ( ) ;
4504
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4505
let mut big = vec! [ 0 u8 ; 300 * 1024 ] ;
4506
for ( i, b) in big.iter_mut ( ) .enumerate ( ) {
4507
* b = ( i as u32 ) .wrapping_mul ( 2654435761 ) .to_le_bytes ( ) [ 1 ] ;
4508
}
4509
big[ 0 ] = 0 ;
4510
write ( root, " big.bin" , & big) ;
4511
write ( root, " a.txt" , b " text\n " ) ;
4512
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4513
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4514
assert! (
4515
m.entries[ " big.bin" ] .blob.is_some ( ) ,
4516
" opaque files record their blob"
4517
) ;
b7f102fc Andy McCall 1 month ago ↶
4518
assert! ( m.entries[ " a.txt" ] .blob.is_none ( ) ) ;
4519
let blob = m.entries[ " big.bin" ] .blob.unwrap ( ) ;
4520
assert! ( repo.store ( ) .has ( & blob) ) ;
4521
4522
4523
4524
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
75167672 Andy McCall 1 month ago
4525
fs:: File:: options( )
4526
.write ( true )
4527
.open ( root.join ( " big.bin" ) )
4528
.unwrap ( )
4529
.set_modified ( old)
4530
.unwrap ( ) ;
b7f102fc Andy McCall 1 month ago ↶
4531
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4532
4533
fs:: set_permissions( root.join ( " big.bin" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
4534
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4535
assert! (
4536
! snap.changed & & snap.statuses.is_empty ( ) ,
4537
" {:?}" ,
4538
snap.statuses
4539
) ;
b7f102fc Andy McCall 1 month ago ↶
4540
fs:: set_permissions( root.join ( " big.bin" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
4541
4542
let mut big2 = big.clone ( ) ;
4543
big2[ 10 ] ^ = 0xff ;
4544
write ( root, " big.bin" , & big2) ;
4545
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4546
assert_eq! (
4547
snap.statuses
4548
.iter ( )
4549
.map ( | s | ( s.path.as_str ( ) , s.status) )
4550
.collect:: < Vec< _ > > ( ) ,
4551
vec! [ ( " big.bin" , ChangeStatus:: Modified) ]
4552
) ;
b7f102fc Andy McCall 1 month ago ↶
4553
let h2 = repo.commit ( " edit" , & [ ] ) .unwrap ( ) ;
4554
let files = repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
4555
assert_eq! (
4556
files[ " big.bin" ] .content,
4557
FileContent:: Opaque {
4558
blobs: vec! [ Repo:: blob_ref( & big2) ]
4559
}
4560
) ;
b7f102fc Andy McCall 1 month ago ↶
4561
4562
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
4563
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
4564
assert_eq! ( m.entries[ " big.bin" ] .blob, Some ( Repo:: blob_ref( & big) ) ) ;
4565
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4566
}
4567
50a046e4 Andy McCall 1 month ago ↶
4568
# [ test ]
4569
fn status_json_lists_conflicts_with_contributors ( ) {
4570
hermetic ( ) ;
4571
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4572
let root = dir.path ( ) ;
4573
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4574
write ( root, " a.txt" , b " one\n " ) ;
4575
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4576
write ( root, " a.txt" , b " one\n left\n " ) ;
4577
let ha = repo.commit ( " left" , & [ ] ) .unwrap ( ) ;
4578
repo.checkout ( & [ h1] , true , " checkout" ) .unwrap ( ) ;
4579
write ( root, " a.txt" , b " one\n right\n " ) ;
4580
let hb = repo.commit ( " right" , & [ ] ) .unwrap ( ) ;
4581
repo.checkout ( & [ ha, hb] , true , " checkout" ) .unwrap ( ) ;
4582
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4583
assert! ( snap.base_snapshot.is_conflicted ( ) ) ;
4584
let json = repo.status_json ( & snap) .unwrap ( ) ;
4585
let ida = repo.store ( ) .get_change ( & ha) .unwrap ( ) .change_id.to_hex ( ) ;
4586
let idb = repo.store ( ) .get_change ( & hb) .unwrap ( ) .change_id.to_hex ( ) ;
4587
assert! ( json.contains ( " \" conflicted\" :true" ) , " {json}" ) ;
75167672 Andy McCall 1 month ago
4588
assert! (
4589
json.contains ( " \" kind\" :\" text\" ,\" path\" :\" a.txt\" " ) ,
4590
" {json}"
4591
) ;
50a046e4 Andy McCall 1 month ago ↶
4592
assert! ( json.contains ( & ida) & & json.contains ( & idb) , " {json}" ) ;
4593
assert! ( json.contains ( " \" resolved\" :false" ) , " {json}" ) ;
4594
assert! ( json.starts_with ( ' {' ) & & json.ends_with ( ' }' ) ) ;
4595
4596
write ( root, " a.txt" , b " one\n left\n right\n " ) ;
4597
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4598
let json = repo.status_json ( & snap) .unwrap ( ) ;
4599
assert! ( json.contains ( " \" resolved\" :true" ) , " {json}" ) ;
4600
repo.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
4601
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4602
let json = repo.status_json ( & snap) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4603
assert! (
4604
json.contains ( " \" clean\" :true,\" conflicted\" :false,\" paths\" :[],\" conflicts\" :[]" ) ,
4605
" {json}"
4606
) ;
50a046e4 Andy McCall 1 month ago ↶
4607
}
4608
9ac1fbe2 Andy McCall 1 month ago ↶
4609
# [ test ]
4610
fn ignore_rules_do_not_untrack_files ( ) {
4611
hermetic ( ) ;
4612
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4613
let root = dir.path ( ) ;
4614
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4615
write ( root, " src.c" , b " int x;\n " ) ;
4616
write ( root, " build/out.o" , b " \0 obj" ) ;
4617
write ( root, " sub/keep.log" , b " keep\n " ) ;
4618
write ( root, " sub/junk.log" , b " junk\n " ) ;
4619
write ( root, " .oxyignore" , b " /build\n *.log\n !sub/keep.log\n " ) ;
4620
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4621
let added: Vec< & str > = snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect ( ) ;
75167672 Andy McCall 1 month ago
4622
assert_eq! (
4623
added,
4624
vec! [ " .oxyignore" , " src.c" , " sub/keep.log" ] ,
4625
" anchored, glob, and negated rules"
4626
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4627
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4628
4629
write ( root, " .oxyignore" , b " /build\n *.log\n !sub/keep.log\n src.c\n " ) ;
4630
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4631
assert_eq! (
4632
snap.statuses
4633
.iter ( )
4634
.map ( | s | s.path.as_str ( ) )
4635
.collect:: < Vec< _ > > ( ) ,
4636
vec! [ " .oxyignore" ]
4637
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4638
let h2 = repo.commit ( " ignore src.c" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4639
assert! ( repo
4640
.load_state ( & [ h2] )
4641
.unwrap ( )
4642
.materialize ( )
4643
.files
4644
.contains_key ( " src.c" ) ) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4645
4646
assert_eq! ( repo.quick_dirty ( ) .unwrap ( ) , Some ( false ) ) ;
4647
write ( root, " src.c" , b " int x, y;\n " ) ;
4648
assert_eq! ( repo.quick_dirty ( ) .unwrap ( ) , Some ( true ) ) ;
4649
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4650
assert_eq! (
4651
snap.statuses
4652
.iter ( )
4653
.map ( | s | ( s.path.as_str ( ) , s.status) )
4654
.collect:: < Vec< _ > > ( ) ,
4655
vec! [ ( " src.c" , ChangeStatus:: Modified) ]
4656
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4657
let h3 = repo.commit ( " edit src.c" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4658
assert! (
4659
matches! ( & repo.load_state ( & [ h3] ) .unwrap ( ) .materialize ( ) .files[ " src.c" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " int x, y;\n " )
4660
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4661
4662
fs:: remove_file( root.join ( " src.c" ) ) .unwrap ( ) ;
4663
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4664
assert_eq! (
4665
snap.statuses
4666
.iter ( )
4667
.map ( | s | ( s.path.as_str ( ) , s.status) )
4668
.collect:: < Vec< _ > > ( ) ,
4669
vec! [ ( " src.c" , ChangeStatus:: Removed) ]
4670
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4671
repo.checkout ( & [ h3] , true , " checkout" ) .unwrap ( ) ;
4672
assert_eq! ( fs:: read( root.join ( " src.c" ) ) .unwrap ( ) , b " int x, y;\n " ) ;
4673
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4674
let _ = h1;
4675
}
4676
b837d2b5 Andy McCall 1 month ago ↶
4677
# [ test ]
4678
fn split_works_on_non_heads_by_node ( ) {
4679
hermetic ( ) ;
4680
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4681
let root = dir.path ( ) ;
4682
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4683
write ( root, " a.txt" , b " one\n " ) ;
4684
write ( root, " junk/x.lock" , b " lock\n " ) ;
4685
let h1 = repo.commit ( " work plus junk" , & [ ] ) .unwrap ( ) ;
4686
write ( root, " a.txt" , b " one\n two\n " ) ;
4687
let h2 = repo.commit ( " more work" , & [ ] ) .unwrap ( ) ;
4688
assert_eq! ( repo.store ( ) .get_change ( & h2) .unwrap ( ) .deps, vec! [ h1] ) ;
4689
let id1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) .change_id;
4690
4691
let ( hj, h1b) = repo.split ( & h1, & [ " junk" .into ( ) ] ) .unwrap ( ) ;
4692
assert_ne! ( h1b, h1) ;
75167672 Andy McCall 1 month ago
4693
assert_eq! (
4694
repo.store ( ) .get_change ( & h1b) .unwrap ( ) .change_id,
4695
id1,
4696
" the rest keeps the ChangeId"
4697
) ;
b837d2b5 Andy McCall 1 month ago ↶
4698
let junk = repo.store ( ) .get_change ( & hj) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4699
assert! ( junk.tree_ops.iter ( ) .all (
4700
| op | matches! ( op, TreeOp:: AddNode { name, .. } if name = = " junk" | | name = = " x.lock" )
4701
) ) ;
b837d2b5 Andy McCall 1 month ago ↶
4702
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4703
assert! (
4704
heads.contains ( & hj) ,
4705
" the split-off part is a head of its own"
4706
) ;
b837d2b5 Andy McCall 1 month ago ↶
4707
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
4708
assert! (
4709
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n " ) ,
4710
" the dependent was rewritten onto the new hash"
4711
) ;
b837d2b5 Andy McCall 1 month ago ↶
4712
assert! ( snap.files.contains_key ( " junk/x.lock" ) ) ;
75167672 Andy McCall 1 month ago
4713
let h2b = heads
4714
.iter ( )
4715
.find ( | h | repo.store ( ) .get_change ( h) .unwrap ( ) .message = = " more work" )
4716
.copied ( )
4717
.unwrap ( ) ;
b837d2b5 Andy McCall 1 month ago ↶
4718
assert_eq! ( repo.store ( ) .get_change ( & h2b) .unwrap ( ) .deps, vec! [ h1b] ) ;
4719
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4720
4721
repo.abandon ( & hj) .unwrap ( ) ;
4722
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
4723
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
4724
assert! ( ! snap.files.contains_key ( " junk/x.lock" ) & & ! snap.files.contains_key ( " junk" ) ) ;
4725
assert! ( snap.files.contains_key ( " a.txt" ) ) ;
4726
4727
write ( root, " junk/y.lock" , b " y\n " ) ;
4728
let hb = repo.commit ( " junk base" , & [ ] ) .unwrap ( ) ;
4729
write ( root, " junk/y.lock" , b " yy\n " ) ;
4730
write ( root, " b.txt" , b " b\n " ) ;
4731
repo.commit ( " touches junk" , & [ ] ) .unwrap ( ) ;
4732
let err = repo.split ( & hb, & [ " junk" .into ( ) ] ) .unwrap_err ( ) .to_string ( ) ;
4733
assert! ( err.contains ( " depends on the split-off paths" ) , " {err}" ) ;
4734
}
4735
7b9809e6 Andy McCall 1 month ago ↶
4736
# [ test ]
4737
fn bookmarks_switch_merge_and_push ( ) {
4738
hermetic ( ) ;
4739
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4740
let root = dir.path ( ) ;
4741
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
4742
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
4743
let mut repo = Repo:: clone( & url, & root.join ( " r" ) )
4744
.unwrap ( )
4745
.with_home ( root.join ( " home" ) ) ;
7b9809e6 Andy McCall 1 month ago ↶
4746
let r = root.join ( " r" ) ;
4747
write ( & r, " a.txt" , b " a\n " ) ;
4748
let h_a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
4749
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
4750
origin.refresh ( ) .unwrap ( ) ;
7b9809e6 Andy McCall 1 month ago ↶
4751
4752
4753
assert! ( repo.bookmark_create ( " bad name.." , None ) .is_err ( ) ) ;
4754
assert! ( repo.bookmark_create ( " -x" , None ) .is_err ( ) ) ;
4755
assert_eq! ( repo.bookmark_create ( " feature" , None ) .unwrap ( ) , vec! [ h_a] ) ;
4756
assert! ( repo.bookmark_create ( " feature" , None ) .is_err ( ) , " exists" ) ;
4757
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
4758
assert_eq! ( repo.config.bookmark, " feature" ) ;
4759
write ( & r, " a.txt" , b " a\n b\n " ) ;
4760
write ( & r, " b.txt" , b " b\n " ) ;
4761
let h_b = repo.commit ( " b on feature" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4762
assert_eq! (
4763
repo.bookmark_heads ( ) .unwrap ( ) ,
4764
vec! [ h_b] ,
4765
" b edits a.txt, so it depends on a"
4766
) ;
4767
assert_eq! (
4768
repo.resolve_heads ( " main" ) .unwrap ( ) ,
4769
vec! [ h_a] ,
4770
" main is untouched"
4771
) ;
4772
assert_eq! (
4773
repo.resolve_change ( " feature" ) .unwrap ( ) ,
4774
h_b,
4775
" a one-head bookmark names its change"
4776
) ;
7b9809e6 Andy McCall 1 month ago ↶
4777
4778
let reopened = Repo:: open( & r) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4779
assert_eq! ( reopened.config.bookmark, " feature" ) ;
4780
4781
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
4782
assert! ( ! r.join ( " b.txt" ) .exists ( ) ) ;
4783
assert_eq! ( fs:: read( r.join ( " a.txt" ) ) .unwrap ( ) , b " a\n " ) ;
4784
write ( & r, " c.txt" , b " c\n " ) ;
4785
let h_c = repo.commit ( " c on main" , & [ ] ) .unwrap ( ) ;
4786
let list = repo.bookmarks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4787
assert_eq! (
4788
list.iter ( )
4789
.map ( | b | ( b.name.as_str ( ) , b.current) )
4790
.collect:: < Vec< _ > > ( ) ,
4791
vec! [ ( " feature" , false ) , ( " main" , true ) ]
4792
) ;
4793
assert_eq! (
4794
list.iter ( ) .find ( | b | b.name = = " main" ) .unwrap ( ) .ahead,
4795
Some ( 1 ) ,
4796
" c is not on origin yet"
4797
) ;
7b9809e6 Andy McCall 1 month ago ↶
4798
4799
let m = repo.merge ( " feature" ) .unwrap ( ) ;
4800
assert! ( m.changed & & m.checked_out) ;
4801
let mut want = vec! [ h_b, h_c] ;
4802
want.sort ( ) ;
4803
assert_eq! ( m.heads, want) ;
4804
assert_eq! ( fs:: read( r.join ( " b.txt" ) ) .unwrap ( ) , b " b\n " ) ;
4805
assert! ( ! repo.merge ( " feature" ) .unwrap ( ) .changed, " already merged" ) ;
4806
assert! ( repo.merge ( " main" ) .is_err ( ) ) ;
4807
4808
assert! ( repo.push_bookmark ( " origin" , " feature" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
4809
origin.refresh ( ) .unwrap ( ) ;
7b9809e6 Andy McCall 1 month ago ↶
4810
assert_eq! ( origin.resolve_heads ( " feature" ) .unwrap ( ) , vec! [ h_b] ) ;
4811
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
4812
origin.refresh ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4813
assert_eq! (
4814
repo.bookmarks ( )
4815
.unwrap ( )
4816
.iter ( )
4817
.find ( | b | b.name = = " main" )
4818
.unwrap ( )
4819
.ahead,
4820
Some ( 0 )
4821
) ;
7b9809e6 Andy McCall 1 month ago ↶
4822
4823
let ws = repo.workspace_add ( & root.join ( " ws" ) , None ) .unwrap ( ) ;
4824
assert_eq! ( ws.config.bookmark, " main" ) ;
4825
let mut ws = ws;
4826
ws.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4827
assert_eq! (
4828
Repo:: open( & r) .unwrap ( ) .config.bookmark,
4829
" main" ,
4830
" the first workspace is unaffected"
4831
) ;
4832
assert! (
4833
repo.bookmark_delete ( " feature" ) .is_err ( ) ,
4834
" current bookmark of another workspace"
4835
) ;
7b9809e6 Andy McCall 1 month ago ↶
4836
ws.bookmark_switch ( " main" , false ) .unwrap ( ) ;
4837
4838
assert_eq! ( repo.bookmark_set ( " feature" , & [ h_a] ) .unwrap ( ) , vec! [ h_a] ) ;
4839
assert! ( repo.bookmark_delete ( " main" ) .is_err ( ) , " current here" ) ;
4840
repo.bookmark_delete ( " feature" ) .unwrap ( ) ;
4841
assert! ( repo.resolve_heads ( " feature" ) .is_err ( ) ) ;
4842
assert! ( repo.bookmark_set ( " feature" , & [ h_a] ) .is_err ( ) ) ;
75167672 Andy McCall 1 month ago
4843
assert! (
4844
repo.checkout ( & [ h_a] , false , " checkout" ) .is_ok ( ) ,
4845
" changes of a deleted bookmark remain"
4846
) ;
7b9809e6 Andy McCall 1 month ago ↶
4847
}
4848
4dee277c Andy McCall 1 month ago ↶
4849
# [ test ]
4850
fn oxygen_subrepos_pin_follow_and_update ( ) {
4851
hermetic ( ) ;
4852
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4853
let root = dir.path ( ) ;
4854
4855
let lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
4856
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
4857
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
4858
.unwrap ( )
4859
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4860
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
4861
let l1 = lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
4862
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
4863
let _ = lib_origin;
4864
4865
4866
let app_origin = Repo:: init( & root.join ( " app-origin" ) ) .unwrap ( ) ;
4867
let app_url = root.join ( " app-origin" ) .to_string_lossy ( ) .to_string ( ) ;
4868
let _ = app_origin;
4869
let app_dir = root.join ( " app" ) ;
75167672 Andy McCall 1 month ago
4870
let app = Repo:: clone( & app_url, & app_dir)
4871
.unwrap ( )
4872
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4873
write ( & app_dir, " main.txt" , b " app\n " ) ;
4874
let pin = app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
4875
assert! ( matches! ( & pin, crate :: subrepo:: Pin:: Oxy { heads, .. } if heads = = & vec! [ l1] ) ) ;
4876
assert! ( app_dir.join ( " .oxymodules" ) .exists ( ) & & app_dir.join ( " libs/lib/.oxy" ) .is_dir ( ) ) ;
4877
let snap = app.snapshot ( " status" ) .unwrap ( ) ;
4878
let paths: Vec< & str > = snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect ( ) ;
75167672 Andy McCall 1 month ago
4879
assert_eq! (
4880
paths,
4881
vec! [ " .oxymodules" , " libs/lib" , " main.txt" ] ,
4882
" the nested files are not walked"
4883
) ;
4dee277c Andy McCall 1 month ago ↶
4884
let a1 = app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
4885
let files = app.load_state ( & [ a1] ) .unwrap ( ) .materialize ( ) .files;
4886
assert_eq! ( files[ " libs/lib" ] .kind, NodeKind:: Subrepo) ;
4887
assert! ( ! files.contains_key ( " libs/lib/lib.txt" ) ) ;
4888
assert! ( ! app.snapshot ( " status" ) .unwrap ( ) .changed) ;
4889
let subs = app.subrepos ( ) .unwrap ( ) ;
4890
assert_eq! ( subs.len ( ) , 1 ) ;
4891
assert_eq! ( subs[ 0 ] .actual.as_ref ( ) , Some ( & subs[ 0 ] .pin) ) ;
4892
4893
75167672 Andy McCall 1 month ago
4894
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) )
4895
.unwrap ( )
4896
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4897
write ( & app_dir.join ( " libs/lib" ) , " lib.txt" , b " v2\n " ) ;
4898
let l2 = nested.commit ( " lib v2" , & [ ] ) .unwrap ( ) ;
4899
assert! ( nested.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
4900
let snap = app.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4901
assert_eq! (
4902
snap.statuses
4903
.iter ( )
4904
.map ( | s | ( s.path.as_str ( ) , s.status) )
4905
.collect:: < Vec< _ > > ( ) ,
4906
vec! [ ( " libs/lib" , ChangeStatus:: Modified) ]
4907
) ;
4dee277c Andy McCall 1 month ago ↶
4908
let a2 = app.commit ( " bump lib" , & [ ] ) .unwrap ( ) ;
1a3b6203 Andy McCall 1 month ago ↶
4909
4910
4dee277c Andy McCall 1 month ago ↶
4911
let r = app.checkout ( & [ a1] , false , " checkout" ) .unwrap ( ) ;
1a3b6203 Andy McCall 1 month ago ↶
4912
assert! (
4913
r.notes.len ( ) = = 1 & & r.notes[ 0 ] .contains ( " keeps 1 newer change" ) ,
4914
" {:?}" ,
4915
r.notes
4916
) ;
4dee277c Andy McCall 1 month ago ↶
4917
assert_eq! ( fs:: read( app_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v1\n " ) ;
1a3b6203 Andy McCall 1 month ago ↶
4918
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) ) .unwrap ( ) ;
4919
assert_eq! ( nested.bookmark_heads ( ) .unwrap ( ) , vec! [ l2] ) ;
4920
assert_eq! ( nested.working_copy ( ) .unwrap ( ) .1. deps, vec! [ l1] ) ;
4dee277c Andy McCall 1 month ago ↶
4921
assert! ( ! app.snapshot ( " status" ) .unwrap ( ) .changed) ;
4922
4923
write ( & app_dir.join ( " libs/lib" ) , " scratch.txt" , b " wip\n " ) ;
4924
let r = app.checkout ( & [ a2] , false , " checkout" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4925
assert! (
4926
r.notes.iter ( ) .any ( | n | n.contains ( " uncommitted" ) ) ,
4927
" {:?}" ,
4928
r.notes
4929
) ;
4dee277c Andy McCall 1 month ago ↶
4930
assert_eq! ( fs:: read( app_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v1\n " ) ;
4931
fs:: remove_file( app_dir.join ( " libs/lib/scratch.txt" ) ) .unwrap ( ) ;
4932
let r = app.submodule_update ( false , & [ ] ) .unwrap ( ) ;
4933
assert_eq! ( r, vec! [ ( " libs/lib" .to_string ( ) , SubrepoSync:: Updated) ] ) ;
4934
assert_eq! ( fs:: read( app_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v2\n " ) ;
4935
let _ = l2;
4936
4937
4938
assert! ( app.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
4939
let clone_dir = root.join ( " app-clone" ) ;
75167672 Andy McCall 1 month ago
4940
let clone = Repo:: clone( & app_url, & clone_dir)
4941
.unwrap ( )
4942
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4943
assert! ( ! clone_dir.join ( " libs/lib/.oxy" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
4944
assert_eq! (
4945
clone.submodule_update ( false , & [ ] ) .unwrap ( ) ,
4946
vec! [ ( " libs/lib" .to_string ( ) , SubrepoSync:: Missing) ]
4947
) ;
4948
assert_eq! (
4949
clone.submodule_update ( true , & [ ] ) .unwrap ( ) ,
4950
vec! [ ( " libs/lib" .to_string ( ) , SubrepoSync:: Updated) ]
4951
) ;
4952
assert_eq! (
4953
fs:: read( clone_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) ,
4954
b " v2\n "
4955
) ;
4dee277c Andy McCall 1 month ago ↶
4956
assert! ( ! clone.snapshot ( " status" ) .unwrap ( ) .changed) ;
4957
4958
clone.submodule_remove ( " libs/lib" ) .unwrap ( ) ;
4959
let snap = clone.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4960
assert! (
4961
snap.statuses
4962
.iter ( )
4963
.any ( | s | s.path = = " libs/lib" & & s.status = = ChangeStatus:: Removed ) ,
4964
" {:?}" ,
4965
snap.statuses
4966
) ;
4dee277c Andy McCall 1 month ago ↶
4967
let c = clone.commit ( " drop lib" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4968
assert! ( ! clone
4969
.load_state ( & [ c] )
4970
.unwrap ( )
4971
.materialize ( )
4972
.files
4973
.contains_key ( " libs/lib" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4974
assert! ( clone_dir.join ( " libs/lib/lib.txt" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
4975
assert! (
4976
! clone.snapshot ( " status" ) .unwrap ( ) .changed,
4977
" an unregistered nested repository is ignored"
4978
) ;
4dee277c Andy McCall 1 month ago ↶
4979
}
4980
4981
# [ test ]
4982
fn git_submodules_import_and_export_as_gitlinks ( ) {
4983
hermetic ( ) ;
75167672 Andy McCall 1 month ago
4984
if std:: process:: Command:: new( " git" )
4985
.arg ( " --version" )
4986
.output ( )
4987
.is_err ( )
4988
{
4dee277c Andy McCall 1 month ago ↶
4989
eprintln! ( " git not installed; skipping" ) ;
4990
return ;
4991
}
4992
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4993
let root = dir.path ( ) ;
4994
let git_in = | d : & Path, args : & [& str ]| -> String {
4995
let out = std:: process:: Command:: new( " git" )
4996
.arg ( " -C" )
4997
.arg ( d)
75167672 Andy McCall 1 month ago
4998
.args ( [
4999
" -c" ,
5000
" user.name=Grace" ,
5001
" -c" ,
5002
" [email protected] " ,
5003
" -c" ,
5004
" commit.gpgsign=false" ,
5005
" -c" ,
5006
" protocol.file.allow=always" ,
5007
] )
4dee277c Andy McCall 1 month ago ↶
5008
.args ( args)
5009
.output ( )
5010
.unwrap ( ) ;
75167672 Andy McCall 1 month ago
5011
assert! (
5012
out.status.success ( ) ,
5013
" git {args:?}: {}" ,
5014
String :: from_utf8_lossy( & out.stderr)
5015
) ;
4dee277c Andy McCall 1 month ago ↶
5016
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
5017
} ;
5018
let sub = root.join ( " sub" ) ;
5019
fs:: create_dir_all( & sub) .unwrap ( ) ;
5020
git_in ( & sub, & [ " init" , " -q" , " -b" , " main" ] ) ;
5021
write ( & sub, " s.txt" , b " sub\n " ) ;
5022
git_in ( & sub, & [ " add" , " ." ] ) ;
5023
git_in ( & sub, & [ " commit" , " -q" , " -m" , " sub initial" ] ) ;
5024
let sub_sha = git_in ( & sub, & [ " rev-parse" , " HEAD" ] ) ;
5025
let g = root.join ( " main.git" ) ;
5026
fs:: create_dir_all( & g) .unwrap ( ) ;
5027
git_in ( & g, & [ " init" , " -q" , " -b" , " main" ] ) ;
5028
write ( & g, " README.md" , b " # app\n " ) ;
5029
git_in ( & g, & [ " add" , " ." ] ) ;
5030
git_in ( & g, & [ " commit" , " -q" , " -m" , " initial" ] ) ;
75167672 Andy McCall 1 month ago
5031
git_in (
5032
& g,
5033
& [
5034
" submodule" ,
5035
" add" ,
5036
" -q" ,
5037
sub.to_str ( ) .unwrap ( ) ,
5038
" vendor/sub" ,
5039
] ,
5040
) ;
4dee277c Andy McCall 1 month ago ↶
5041
git_in ( & g, & [ " commit" , " -q" , " -m" , " add submodule" ] ) ;
5042
5043
let dest = root.join ( " oxy" ) ;
5044
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5045
let rep = repo.import_git ( & g, " main" ) .unwrap ( ) ;
5046
assert_eq! ( ( rep.commits, rep.submodules) , ( 2 , 1 ) ) ;
75167672 Andy McCall 1 month ago
5047
let files = repo
5048
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
5049
.unwrap ( )
5050
.materialize ( )
5051
.files;
4dee277c Andy McCall 1 month ago ↶
5052
assert_eq! ( files[ " vendor/sub" ] .kind, NodeKind:: Subrepo) ;
75167672 Andy McCall 1 month ago
5053
let FileContent:: Opaque { blobs } = & files[ " vendor/sub" ] .content else {
5054
panic! ( )
5055
} ;
5056
assert_eq! (
5057
crate :: subrepo:: Pin:: decode( & repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) ) .unwrap ( ) ,
5058
crate :: subrepo:: Pin:: Git {
5059
sha: sub_sha.clone ( ) ,
5060
branch: " HEAD" .into ( )
5061
}
5062
) ;
5063
let modules =
5064
crate :: subrepo:: Modules:: parse( & fs:: read_to_string( dest.join ( " .oxymodules" ) ) .unwrap ( ) ) ;
4dee277c Andy McCall 1 month ago ↶
5065
assert_eq! ( modules.entries[ " vendor/sub" ] .url, sub.to_string_lossy ( ) ) ;
5066
assert! ( modules.entries[ " vendor/sub" ] .git) ;
75167672 Andy McCall 1 month ago
5067
assert! (
5068
files.contains_key ( " .gitmodules" ) ,
5069
" the Git file is kept for round trips"
5070
) ;
5071
assert! (
5072
! dest.join ( " vendor/sub/.git" ) .exists ( ) ,
5073
" not cloned until update --init"
5074
) ;
4dee277c Andy McCall 1 month ago ↶
5075
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
5076
75167672 Andy McCall 1 month ago
5077
assert_eq! (
5078
repo.submodule_update ( true , & [ ] ) .unwrap ( ) ,
5079
vec! [ ( " vendor/sub" .to_string ( ) , SubrepoSync:: Updated) ]
5080
) ;
5081
assert_eq! (
5082
git_in ( & dest.join ( " vendor/sub" ) , & [ " rev-parse" , " HEAD" ] ) ,
5083
sub_sha
5084
) ;
4dee277c Andy McCall 1 month ago ↶
5085
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
5086
5087
let mirror = root.join ( " mirror.git" ) ;
5088
let rep = repo.git_export ( & mirror) .unwrap ( ) ;
5089
assert_eq! ( rep.skipped_subrepos, 0 ) ;
5090
let ls = git_in ( & mirror, & [ " ls-tree" , " main" , " vendor/sub" ] ) ;
75167672 Andy McCall 1 month ago
5091
assert! (
5092
ls.starts_with ( " 160000 commit " ) & & ls.contains ( & sub_sha) ,
5093
" {ls}"
5094
) ;
5095
assert_eq! (
5096
git_in ( & mirror, & [ " ls-tree" , " --name-only" , " main" ] ) ,
5097
" .gitmodules\n README.md\n vendor"
5098
) ;
4dee277c Andy McCall 1 month ago ↶
5099
let orig_tree = git_in ( & g, & [ " rev-parse" , " main^{tree}" ] ) ;
75167672 Andy McCall 1 month ago
5100
assert_eq! (
5101
git_in ( & mirror, & [ " rev-parse" , " main^{tree}" ] ) ,
5102
orig_tree,
5103
" tree round trip"
5104
) ;
4dee277c Andy McCall 1 month ago ↶
5105
}
5106
957fee30 Andy McCall 1 month ago ↶
5107
# [ test ]
5108
fn partial_commits_by_path_and_by_hunk ( ) {
5109
hermetic ( ) ;
5110
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5111
let root = dir.path ( ) ;
5112
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5113
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
5114
write ( root, " b.txt" , b " b\n " ) ;
5115
let h0 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
5116
write ( root, " a.txt" , b " 0\n 1\n 2\n 3\n 4\n " ) ;
5117
write ( root, " b.txt" , b " b\n bb\n " ) ;
5118
write ( root, " c.txt" , b " c\n " ) ;
5119
75167672 Andy McCall 1 month ago
5120
let h1 = repo
5121
.commit_selection ( " a only" , & [ ] , & Selection:: Paths( vec! [ " a.txt" .into ( ) ] ) )
5122
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5123
let c1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5124
assert! (
5125
c1.tree_ops.is_empty ( ) & & c1.edits.iter ( ) .all ( | e | e.node ( ) = = c1.edits[ 0 ] .node ( ) ) ,
5126
" {:?}" ,
5127
c1.tree_ops
5128
) ;
957fee30 Andy McCall 1 month ago ↶
5129
assert_eq! ( c1.deps, vec! [ h0] ) ;
5130
let files = repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
5131
assert! (
5132
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 0\n 1\n 2\n 3\n 4\n " )
5133
) ;
957fee30 Andy McCall 1 month ago ↶
5134
assert! ( matches! ( & files[ " b.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " b\n " ) ) ;
5135
assert! ( ! files.contains_key ( " c.txt" ) ) ;
5136
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5137
assert_eq! (
5138
snap.statuses
5139
.iter ( )
5140
.map ( | s | ( s.path.as_str ( ) , s.status) )
5141
.collect:: < Vec< _ > > ( ) ,
5142
vec! [
5143
( " b.txt" , ChangeStatus:: Modified) ,
5144
( " c.txt" , ChangeStatus:: Added)
5145
]
5146
) ;
5147
assert_eq! (
5148
fs:: read( root.join ( " b.txt" ) ) .unwrap ( ) ,
5149
b " b\n bb\n " ,
5150
" disk untouched"
5151
) ;
957fee30 Andy McCall 1 month ago ↶
5152
5153
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5154
let b_hunk = hunks
5155
.iter ( )
5156
.find ( | h | h.path = = " b.txt" & & h.text.contains ( " +bb" ) )
5157
.unwrap ( )
5158
.index;
5159
let h2 = repo
5160
.commit_selection ( " b only" , & [ ] , & Selection:: Hunks( vec! [ b_hunk] ) )
5161
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5162
let files = repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
5163
assert! (
5164
matches! ( & files[ " b.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " b\n bb\n " )
5165
) ;
957fee30 Andy McCall 1 month ago ↶
5166
assert! ( ! files.contains_key ( " c.txt" ) ) ;
5167
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5168
assert_eq! (
5169
snap.statuses
5170
.iter ( )
5171
.map ( | s | s.path.as_str ( ) )
5172
.collect:: < Vec< _ > > ( ) ,
5173
vec! [ " c.txt" ]
5174
) ;
957fee30 Andy McCall 1 month ago ↶
5175
5176
5177
write ( root, " d/e.txt" , b " e\n " ) ;
5178
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5179
let e_insert = hunks
5180
.iter ( )
5181
.find ( | h | h.path = = " d/e.txt" & & h.text.contains ( " +e" ) )
5182
.unwrap ( )
5183
.index;
5184
let h3 = repo
5185
.commit_selection ( " e only" , & [ ] , & Selection:: Hunks( vec! [ e_insert] ) )
5186
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5187
let c3 = repo.store ( ) .get_change ( & h3) .unwrap ( ) ;
5188
assert_eq! ( c3.tree_ops.len ( ) , 2 , " {:?}" , c3.tree_ops) ;
5189
let files = repo.load_state ( & [ h3] ) .unwrap ( ) .materialize ( ) .files;
5190
assert! ( files.contains_key ( " d/e.txt" ) & & ! files.contains_key ( " c.txt" ) ) ;
d8c086ca Andy McCall 1 month ago ↶
5191
5192
write ( root, " f/g.txt" , b " g\n " ) ;
5193
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5194
let g_add = hunks
5195
.iter ( )
5196
.find ( | h | h.path = = " f/g.txt" & & h.text.starts_with ( " A " ) )
5197
.unwrap ( )
5198
.index;
5199
let h4 = repo
5200
.commit_selection ( " g by addition" , & [ ] , & Selection:: Hunks( vec! [ g_add] ) )
5201
.unwrap ( ) ;
d8c086ca Andy McCall 1 month ago ↶
5202
let files = repo.load_state ( & [ h4] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
5203
assert! (
5204
matches! ( & files[ " f/g.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " g\n " )
5205
) ;
5206
assert! ( ! repo
5207
.snapshot ( " status" )
5208
.unwrap ( )
5209
.statuses
5210
.iter ( )
5211
.any ( | s | s.path = = " f/g.txt" ) ) ;
957fee30 Andy McCall 1 month ago ↶
5212
5213
repo.commit ( " the rest" , & [ ] ) .unwrap ( ) ;
5214
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
75167672 Andy McCall 1 month ago
5215
let files = repo
5216
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
5217
.unwrap ( )
5218
.materialize ( )
5219
.files;
957fee30 Andy McCall 1 month ago ↶
5220
assert! ( files.contains_key ( " c.txt" ) ) ;
5221
}
5222
5223
# [ test ]
5224
fn blame_attributes_lines_to_introducing_changes_across_renames ( ) {
5225
hermetic ( ) ;
5226
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5227
let root = dir.path ( ) ;
5228
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
5229
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
5230
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5231
write ( root, " a.txt" , b " one\n " ) ;
5232
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
5233
write ( root, " a.txt" , b " one\n two\n " ) ;
5234
let h2 = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
5235
let b = repo.blame ( " a.txt" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5236
assert_eq! (
5237
b.iter ( )
5238
.map ( | l | ( l.line, l.hash, l.text.as_slice ( ) ) )
5239
.collect:: < Vec< _ > > ( ) ,
5240
vec! [ ( 1 , h1, b " one\n " .as_slice ( ) ) , ( 2 , h2, b " two\n " .as_slice ( ) ) ]
5241
) ;
957fee30 Andy McCall 1 month ago ↶
5242
assert! ( b.iter ( ) .all ( | l | l.author = = " Ada" ) ) ;
75167672 Andy McCall 1 month ago
5243
assert_eq! (
5244
b[ 0 ] .change_id,
5245
repo.store ( ) .get_change ( & h1) .unwrap ( ) .change_id
5246
) ;
957fee30 Andy McCall 1 month ago ↶
5247
fs:: rename( root.join ( " a.txt" ) , root.join ( " b.txt" ) ) .unwrap ( ) ;
5248
repo.commit ( " rename" , & [ ] ) .unwrap ( ) ;
5249
let b = repo.blame ( " b.txt" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5250
assert_eq! (
5251
b.iter ( ) .map ( | l | l.hash ) .collect:: < Vec< _ > > ( ) ,
5252
vec! [ h1, h2] ,
5253
" the node survives the rename"
5254
) ;
957fee30 Andy McCall 1 month ago ↶
5255
assert! ( repo.blame ( " a.txt" ) .is_err ( ) ) ;
5256
}
5257
5258
# [ test ]
5259
fn bisect_finds_the_first_bad_change ( ) {
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
let mut content = String :: new( ) ;
5265
let mut hashes = Vec :: new( ) ;
5266
for i in 1 .. = 8 {
5267
content.push_str ( & format! ( " line {i} {} \n " , if i = = 5 { " bug" } else { " " } ) ) ;
5268
write ( root, " f.txt" , content.as_bytes ( ) ) ;
5269
hashes.push ( repo.commit ( & format! ( " step {i} " ) , & [ ] ) .unwrap ( ) ) ;
5270
}
5271
5272
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ hashes[ 7 ] ] ) ;
5273
repo.bisect_start ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5274
assert! ( matches! (
5275
repo.bisect_mark ( Some ( true ) , & [ hashes[ 0 ] ] ) .unwrap ( ) ,
5276
BisectStep:: NeedMarks { good: 1 , bad: 0 }
5277
) ) ;
957fee30 Andy McCall 1 month ago ↶
5278
let step = repo.bisect_mark ( Some ( false ) , & [ hashes[ 7 ] ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5279
assert! (
5280
matches! ( step, BisectStep:: Testing { remaining: 7 , .. } ) ,
5281
" {step:?}"
5282
) ;
957fee30 Andy McCall 1 month ago ↶
5283
75167672 Andy McCall 1 month ago
5284
let step = repo
5285
.bisect_run ( & [ " sh" .into ( ) , " -c" .into ( ) , " ! grep -q bug f.txt" .into ( ) ] )
5286
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5287
assert_eq! ( step, BisectStep:: Found { culprit: hashes[ 4 ] } ) ;
75167672 Andy McCall 1 month ago
5288
assert_eq! (
5289
repo.bisect_status ( ) .unwrap ( ) ,
5290
Some ( BisectStep:: Found { culprit: hashes[ 4 ] } )
5291
) ;
957fee30 Andy McCall 1 month ago ↶
5292
repo.bisect_reset ( ) .unwrap ( ) ;
5293
assert_eq! ( fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) , content) ;
5294
assert! ( repo.bisect_status ( ) .unwrap ( ) .is_none ( ) ) ;
5295
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
5296
5297
repo.bisect_start ( ) .unwrap ( ) ;
5298
repo.bisect_mark ( Some ( true ) , & [ hashes[ 0 ] ] ) .unwrap ( ) ;
5299
let mut step = repo.bisect_mark ( Some ( false ) , & [ hashes[ 7 ] ] ) .unwrap ( ) ;
5300
let mut skipped_once = false ;
5301
let mut guard = 0 ;
5302
while let BisectStep:: Testing { .. } = step {
5303
guard + = 1 ;
5304
assert! ( guard < 20 ) ;
5305
let text = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
5306
if ! skipped_once & & text.contains ( " line 6" ) {
5307
5308
skipped_once = true ;
5309
step = repo.bisect_mark ( None , & [ ] ) .unwrap ( ) ;
5310
continue ;
5311
}
5312
step = repo.bisect_mark ( Some ( ! text.contains ( " bug" ) ) , & [ ] ) .unwrap ( ) ;
5313
}
5314
assert! ( skipped_once) ;
5315
assert_eq! ( step, BisectStep:: Found { culprit: hashes[ 4 ] } ) ;
5316
repo.bisect_reset ( ) .unwrap ( ) ;
5317
5318
repo.bisect_start ( ) .unwrap ( ) ;
5319
repo.bisect_mark ( Some ( true ) , & [ hashes[ 0 ] ] ) .unwrap ( ) ;
5320
let mut step = repo.bisect_mark ( Some ( false ) , & [ hashes[ 7 ] ] ) .unwrap ( ) ;
5321
let mut guard = 0 ;
5322
while let BisectStep:: Testing { heads, .. } = & step {
5323
guard + = 1 ;
5324
assert! ( guard < 20 ) ;
5325
let text = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5326
let is_culprit_state =
5327
heads.contains ( & hashes[ 4 ] ) & & ! heads.iter ( ) .any ( | h | hashes[ 5 .. ] .contains ( h) ) ;
5328
step = if is_culprit_state {
5329
repo.bisect_mark ( None , & [ ] ) .unwrap ( )
5330
} else {
5331
repo.bisect_mark ( Some ( ! text.contains ( " bug" ) ) , & [ ] ) .unwrap ( )
5332
} ;
957fee30 Andy McCall 1 month ago ↶
5333
}
5334
match step {
75167672 Andy McCall 1 month ago
5335
BisectStep:: Inconclusive { candidates } => assert! (
5336
candidates.contains ( & hashes[ 4 ] ) & & candidates.len ( ) < = 2 ,
5337
" {candidates:?}"
5338
) ,
957fee30 Andy McCall 1 month ago ↶
5339
other => panic! ( " {other:?}" ) ,
5340
}
5341
repo.bisect_reset ( ) .unwrap ( ) ;
5342
}
5343
728da0f9 Andy McCall 1 month ago ↶
5344
# [ test ]
5345
fn squash_folds_a_change_into_its_dependency ( ) {
5346
hermetic ( ) ;
5347
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5348
let root = dir.path ( ) ;
5349
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5350
write ( root, " a.txt" , b " 1\n " ) ;
5351
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
5352
write ( root, " a.txt" , b " 1\n 2\n " ) ;
5353
let c = repo.commit ( " C" , & [ ] ) .unwrap ( ) ;
5354
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
5355
let e = repo.commit ( " E" , & [ ] ) .unwrap ( ) ;
5356
write ( root, " b.txt" , b " b\n " ) ;
5357
let b = repo.commit ( " B" , & [ ] ) .unwrap ( ) ;
5358
let id_a = repo.store ( ) .get_change ( & a) .unwrap ( ) .change_id;
5359
let id_e = repo.store ( ) .get_change ( & e) .unwrap ( ) .change_id;
5360
5361
let err = repo.squash ( & e, & a) .unwrap_err ( ) .to_string ( ) ;
5362
assert! ( err.contains ( & c.short ( 12 ) ) , " {err}" ) ;
5363
5364
let a2 = repo.squash ( & c, & a) .unwrap ( ) ;
5365
let a2c = repo.store ( ) .get_change ( & a2) .unwrap ( ) ;
5366
assert_eq! ( a2c.change_id, id_a) ;
5367
assert! ( a2c.deps.is_empty ( ) ) ;
5368
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5369
let st = repo.load_state ( & heads) .unwrap ( ) ;
5370
assert! ( ! st.contains ( & c) & & ! st.contains ( & a) & & ! st.contains ( & e) ) ;
75167672 Andy McCall 1 month ago
5371
let e2 = st
5372
.changes ( )
5373
.find ( | ( _, m ) | m.change_id = = id_e )
5374
.map ( | ( h , _) | * h)
5375
.unwrap ( ) ;
5376
assert_eq! (
5377
repo.store ( ) .get_change ( & e2) .unwrap ( ) .deps,
5378
vec! [ a2] ,
5379
" E now depends on A' alone"
5380
) ;
728da0f9 Andy McCall 1 month ago ↶
5381
let files = st.materialize ( ) .files;
75167672 Andy McCall 1 month ago
5382
assert! (
5383
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 1\n 2\n 3\n " )
5384
) ;
728da0f9 Andy McCall 1 month ago ↶
5385
assert! ( files.contains_key ( " b.txt" ) ) ;
5386
assert! ( st.contains ( & b) ) ;
5387
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
5388
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 , " working copy, B, E', A'" ) ;
5389
5390
repo.undo ( ) .unwrap ( ) ;
5391
repo.undo ( ) .unwrap ( ) ;
5392
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
5393
assert! ( st.contains ( & c) & & st.contains ( & a) & & st.contains ( & e) ) ;
5394
}
5395
5396
# [ test ]
5397
fn rebase_re_expresses_a_change_against_another_base ( ) {
5398
hermetic ( ) ;
5399
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5400
let root = dir.path ( ) ;
5401
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5402
write ( root, " a.txt" , b " 1\n 2\n " ) ;
5403
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
5404
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
5405
let c = repo.commit ( " C: append 3" , & [ ] ) .unwrap ( ) ;
5406
write ( root, " a.txt" , b " 1\n 2\n 3\n 4\n " ) ;
5407
let e = repo.commit ( " E: append 4" , & [ ] ) .unwrap ( ) ;
5408
5409
repo.checkout ( & [ a] , true , " checkout" ) .unwrap ( ) ;
5410
write ( root, " a.txt" , b " 0\n 1\n 2\n " ) ;
5411
let d = repo.commit ( " D: prepend 0" , & [ ] ) .unwrap ( ) ;
5412
let id_c = repo.store ( ) .get_change ( & c) .unwrap ( ) .change_id;
5413
let id_e = repo.store ( ) .get_change ( & e) .unwrap ( ) .change_id;
5414
5415
let c2 = repo.rebase ( & c, & [ d] ) .unwrap ( ) ;
5416
let c2c = repo.store ( ) .get_change ( & c2) .unwrap ( ) ;
5417
assert_eq! ( c2c.change_id, id_c) ;
5418
assert! ( c2c.deps.contains ( & d) , " {:?}" , c2c.deps) ;
5419
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5420
let st = repo.load_state ( & heads) .unwrap ( ) ;
5421
assert! ( ! st.contains ( & c) & & st.contains ( & d) ) ;
75167672 Andy McCall 1 month ago
5422
let e2 = st
5423
.changes ( )
5424
.find ( | ( _, m ) | m.change_id = = id_e )
5425
.map ( | ( h , _) | * h)
5426
.unwrap ( ) ;
728da0f9 Andy McCall 1 month ago ↶
5427
let e2deps = repo.store ( ) .get_change ( & e2) .unwrap ( ) .deps;
75167672 Andy McCall 1 month ago
5428
assert! (
5429
e2deps.contains ( & c2) & & ! e2deps.contains ( & c) & & e2deps.contains ( & a) ,
5430
" E follows C by content and still depends on A, which introduced the file: {e2deps:?}"
5431
) ;
728da0f9 Andy McCall 1 month ago ↶
5432
let files = st.materialize ( ) .files;
75167672 Andy McCall 1 month ago
5433
assert! (
5434
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 0\n 1\n 2\n 3\n 4\n " ) ,
5435
" {:?}" ,
5436
files[ " a.txt" ] .content
5437
) ;
728da0f9 Andy McCall 1 month ago ↶
5438
assert! ( ! files[ " a.txt" ] .is_conflicted ( ) ) ;
5439
5440
assert! ( repo.rebase ( & c2, & [ c2] ) .is_err ( ) ) ;
5441
let err = repo.rebase ( & e2, & [ d] ) .unwrap_err ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
5442
assert! (
5443
err.contains ( " lacks dependencies" ) ,
5444
" E needs C, which D's state does not have: {err}"
5445
) ;
728da0f9 Andy McCall 1 month ago ↶
5446
}
5447
c792033a Andy McCall 1 month ago ↶
5448
# [ test ]
5449
fn unsigned_tags_keep_their_message ( ) {
5450
hermetic ( ) ;
5451
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5452
let root = dir.path ( ) ;
5453
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5454
write ( root, " a.txt" , b " a\n " ) ;
5455
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
5456
assert! ( repo.secret_key ( ) .unwrap ( ) .is_none ( ) ) ;
75167672 Andy McCall 1 month ago
5457
repo.tag_create ( " v1" , & repo.bookmark_heads ( ) .unwrap ( ) , " first release" )
5458
.unwrap ( ) ;
c792033a Andy McCall 1 month ago ↶
5459
let tags = repo.tags ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5460
assert_eq! (
5461
(
5462
tags[ 0 ] .name.as_str ( ) ,
5463
tags[ 0 ] .message.as_str ( ) ,
5464
tags[ 0 ] .signer.as_deref ( )
5465
) ,
5466
( " v1" , " first release" , None )
5467
) ;
5468
assert! (
5469
repo.tag_refs ( ) .unwrap ( ) [ 0 ] .attestation.is_none ( ) ,
5470
" an unsigned attestation is not advertised"
5471
) ;
c792033a Andy McCall 1 month ago ↶
5472
}
387335ff Andy McCall 1 month ago ↶
5473
5474
# [ test ]
5475
fn fsck_passes_a_sound_repository_and_names_each_fault ( ) {
64818938 Andy McCall 1 month ago ↶
5476
5477
5478
if std:: env:: var_os( " OXY_STORE_BACKEND" ) .is_some ( ) {
5479
return ;
5480
}
387335ff Andy McCall 1 month ago ↶
5481
hermetic ( ) ;
5482
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5483
let root = dir.path ( ) ;
5484
let origin_path = root.join ( " origin" ) ;
5485
let origin = Repo:: init( & origin_path) .unwrap ( ) ;
5486
let mut a = Repo:: clone( & origin_path.to_string_lossy ( ) , & root.join ( " a" ) )
5487
.unwrap ( )
5488
.with_home ( root.join ( " home-a" ) ) ;
5489
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
5490
.unwrap ( ) ;
5491
write ( & root.join ( " a" ) , " f.txt" , b " one\n " ) ;
5492
write ( & root.join ( " a" ) , " bin.dat" , b " \0 \x01 \x02 binary" ) ;
5493
let h1 = a.commit ( " first" , & [ ] ) .unwrap ( ) ;
5494
write ( & root.join ( " a" ) , " f.txt" , b " one\n two\n " ) ;
5495
let h2 = a.commit ( " second" , & [ ] ) .unwrap ( ) ;
5496
a.tag_create ( " v1" , & [ h2] , " first release" ) .unwrap ( ) ;
5497
a.checkpoint ( ) .unwrap ( ) ;
5498
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
5499
origin.refresh ( ) .unwrap ( ) ;
387335ff Andy McCall 1 month ago ↶
5500
5501
5502
for repo in [ & a, & origin] {
5503
let rep = repo.fsck ( ) .unwrap ( ) ;
5504
assert_eq! ( rep.problems, vec! [ ] , " {rep:?}" ) ;
5505
5506
assert! ( rep.changes > = 2 & & rep.signed > = 2 , " {rep:?}" ) ;
5507
assert! ( rep.ops > 1 & & rep.objects > 5 ) ;
5508
}
5509
assert! ( origin.fsck ( ) .unwrap ( ) .attestations > = 1 ) ;
5510
5511
5512
let c1 = a.store ( ) .path_for ( & h1) ;
5513
let pristine = fs:: read( & c1) .unwrap ( ) ;
5514
let mut bytes = pristine.clone ( ) ;
5515
* bytes.last_mut ( ) .unwrap ( ) ^ = 1 ;
5516
fs:: write( & c1, & bytes) .unwrap ( ) ;
5517
let rep = a.fsck ( ) .unwrap ( ) ;
5518
assert! ( rep.errors ( ) > = 1 ) ;
5519
assert! (
5520
rep.problems
5521
.iter ( )
5522
.any ( | p | p.object = = Some ( h1) & & p.what.contains ( " unreadable" ) ) ,
5523
" {rep:?}"
5524
) ;
5525
fs:: write( & c1, & pristine) .unwrap ( ) ;
5526
assert_eq! ( a.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
5527
5528
5529
fs:: remove_file( & c1) .unwrap ( ) ;
5530
let rep = a.fsck ( ) .unwrap ( ) ;
5531
assert! (
5532
rep.problems.iter ( ) .any ( | p | p.severity = = Severity:: Error
5533
& & p.object = = Some ( h1)
5534
& & p.what.contains ( " change missing, needed by change" ) ) ,
5535
" {rep:?}"
5536
) ;
5537
fs:: write( & c1, & pristine) .unwrap ( ) ;
5538
5539
5540
let c2 = a.store ( ) .get_change ( & h1) .unwrap ( ) ;
5541
let blob = c2
5542
.edits
5543
.iter ( )
5544
.find_map ( | e | match e {
5545
Edit:: Replace { new: Some ( b) , .. } => Some ( * b) ,
5546
_ => None ,
5547
} )
5548
.expect ( " the binary file is a blob" ) ;
5549
let bp = a.store ( ) .path_for ( & blob) ;
5550
let blob_bytes = fs:: read( & bp) .unwrap ( ) ;
5551
fs:: remove_file( & bp) .unwrap ( ) ;
5552
let rep = a.fsck ( ) .unwrap ( ) ;
5553
assert! (
5554
rep.problems
5555
.iter ( )
5556
.any ( | p | p.object = = Some ( blob) & & p.what.contains ( " blob missing" ) ) ,
5557
" {rep:?}"
5558
) ;
5559
fs:: write( & bp, & blob_bytes) .unwrap ( ) ;
5560
5561
5562
let head = root.join ( " a/.oxy/ops/head" ) ;
5563
let saved = fs:: read( & head) .unwrap ( ) ;
5564
fs:: write( & head, b " not a hash" ) .unwrap ( ) ;
5565
let rep = a.fsck ( ) .unwrap ( ) ;
5566
assert! (
5567
rep.problems.iter ( ) .any ( | p | p.what.contains ( " ops/head" ) ) ,
5568
" {rep:?}"
5569
) ;
5570
fs:: write( & head, saved) .unwrap ( ) ;
5571
assert_eq! ( a.fsck ( ) .unwrap ( ) .problems, vec! [ ] ) ;
5572
}
4dcb8c09 Andy McCall 1 month ago ↶
5573
5574
5575
fn copy_tree ( from : & Path, to : & Path) {
5576
fs:: create_dir_all( to) .unwrap ( ) ;
5577
for e in fs:: read_dir( from) .unwrap ( ) {
5578
let e = e.unwrap ( ) ;
5579
let target = to.join ( e.file_name ( ) ) ;
5580
if e.file_type ( ) .unwrap ( ) .is_dir ( ) {
5581
copy_tree ( & e.path ( ) , & target) ;
5582
} else {
5583
fs:: copy( e.path ( ) , & target) .unwrap ( ) ;
5584
}
5585
}
5586
}
5587
5588
5589
5590
5591
# [ test ]
5592
fn crash_at_every_step_of_commit_and_push_leaves_sound_repositories ( ) {
64818938 Andy McCall 1 month ago ↶
5593
5594
5595
if std:: env:: var_os( " OXY_STORE_BACKEND" ) .is_some ( ) {
5596
return ;
5597
}
4dcb8c09 Andy McCall 1 month ago ↶
5598
use crate :: durable:: failpoint;
5599
hermetic ( ) ;
5600
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5601
let pristine = dir.path ( ) .join ( " pristine" ) ;
5602
let origin_path = pristine.join ( " origin" ) ;
5603
Repo:: init( & origin_path) .unwrap ( ) ;
5604
let mut a = Repo:: clone( & origin_path.to_string_lossy ( ) , & pristine.join ( " a" ) )
5605
.unwrap ( )
5606
.with_home ( dir.path ( ) .join ( " home" ) ) ;
5607
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
5608
.unwrap ( ) ;
5609
write ( & pristine.join ( " a" ) , " f.txt" , b " one\n " ) ;
5610
a.commit ( " first" , & [ ] ) .unwrap ( ) ;
5611
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
5612
drop ( a) ;
5613
5614
type Op = fn ( & Repo) -> Result< ( ) > ;
5615
let edit_and_commit: Op = | r | {
5616
write ( r.root ( ) , " f.txt" , b " one\n two\n " ) ;
5617
write ( r.root ( ) , " big.bin" , & vec! [ 7 u8 ; 300_000 ] ) ;
5618
r.commit ( " second" , & [ ] ) .map ( | _| ( ) )
5619
} ;
5620
let push: Op = | r | {
5621
let res = r.push ( " origin" , false ) ? ;
5622
if res.iter ( ) .all ( | x | x.ok ) {
5623
Ok ( ( ) )
5624
} else {
5625
Err ( RepoError:: Msg( format! ( " {res:?} " ) ) )
5626
}
5627
} ;
5628
5629
for ( name, prepare, op) in [
5630
( " commit" , None , edit_and_commit) ,
5631
( " push" , Some ( edit_and_commit) , push) ,
5632
] {
5633
let mut steps = 0 ;
5634
for n in 1 .. {
5635
let run = dir.path ( ) .join ( format! ( " {name} -{n} " ) ) ;
5636
copy_tree ( & pristine, & run) ;
5637
5638
let open = | p : & Path| {
5639
let mut r = Repo:: open( p) .unwrap ( ) .with_home ( dir.path ( ) .join ( " home" ) ) ;
5640
r.config
5641
.set ( " remote.origin.url" , & run.join ( " origin" ) .to_string_lossy ( ) )
5642
.unwrap ( ) ;
5643
r.save_config ( ) .unwrap ( ) ;
5644
r
5645
} ;
5646
let a = open ( & run.join ( " a" ) ) ;
5647
if let Some ( prep) = prepare {
5648
prep ( & a) .unwrap ( ) ;
5649
}
5650
failpoint:: crash_after( n) ;
5651
let outcome = op ( & a) ;
5652
let crashed = failpoint:: disarm( ) ;
5653
drop ( a) ;
5654
if ! crashed {
5655
outcome.unwrap_or_else ( | e | panic! ( " {name} failed without a crash: {e}" ) ) ;
5656
steps = n - 1 ;
5657
break ;
5658
}
5659
assert! (
5660
outcome.is_err ( ) ,
5661
" {name}: a crash at step {n} was swallowed"
5662
) ;
5663
for repo in [ " a" , " origin" ] {
5664
let r = Repo:: open( & run.join ( repo) ) .unwrap_or_else ( | e | {
5665
panic! ( " {name} crash at step {n}: {repo} no longer opens: {e}" )
5666
} ) ;
5667
let rep = r.fsck ( ) .unwrap ( ) ;
5668
assert_eq! (
5669
rep.errors ( ) ,
5670
0 ,
5671
" {name} crash at step {n}: {repo} fails fsck: {:?}" ,
5672
rep.problems
5673
) ;
5674
}
5675
5676
let a = open ( & run.join ( " a" ) ) ;
5677
5678
let landed = a
5679
.bookmark_heads ( )
5680
.unwrap ( )
5681
.iter ( )
5682
.any ( | h | a.store ( ) .get_change ( h) .unwrap ( ) .message = = " second" ) ;
5683
let again = match ( name, landed) {
5684
( " commit" , true ) => Ok ( ( ) ) ,
5685
( " commit" , false ) => a.commit ( " second" , & [ ] ) .map ( | _| ( ) ) ,
5686
_ => op ( & a) ,
5687
} ;
5688
again.unwrap_or_else ( | e | panic! ( " {name} crash at step {n}: retry failed: {e}" ) ) ;
5689
assert_eq! ( a.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
5690
fs:: remove_dir_all( & run) .unwrap ( ) ;
5691
}
5692
assert! ( steps > = 3 , " {name} has only {steps} durable steps?" ) ;
5693
}
5694
}
19555025 Andy McCall 1 month ago ↶
5695
5696
5697
5698
5699
# [ test ]
5700
fn concurrent_commits_in_two_workspaces_lose_nothing ( ) {
5701
hermetic ( ) ;
5702
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5703
let root = dir.path ( ) ;
5704
let origin = root.join ( " origin" ) ;
5705
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
5706
let main = root.join ( " main" ) ;
5707
let mut repo = Repo:: init( & main) .unwrap ( ) ;
5708
repo.config
5709
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
5710
.unwrap ( ) ;
5711
repo.save_config ( ) .unwrap ( ) ;
5712
write ( & main, " seed.txt" , b " seed\n " ) ;
5713
repo.commit ( " seed" , & [ ] ) .unwrap ( ) ;
5714
let second = root.join ( " second" ) ;
5715
repo.workspace_add ( & second, None ) .unwrap ( ) ;
5716
drop ( repo) ;
5717
5718
const EACH : usize = 15 ;
5719
std:: thread:: scope( | s | {
5720
for ( tag, dir, push) in [ ( " a" , main.clone ( ) , true ) , ( " b" , second.clone ( ) , false ) ] {
5721
s.spawn ( move | | {
5722
let r = Repo:: open( & dir) .unwrap ( ) ;
5723
for i in 0 .. EACH {
5724
write (
5725
& dir,
5726
& format! ( " {tag} -{i} .txt" ) ,
5727
format! ( " {tag} {i} \n " ) .as_bytes ( ) ,
5728
) ;
5729
r.commit ( & format! ( " {tag} {i} " ) , & [ ] ) .unwrap ( ) ;
5730
if push & & i % 5 = = 4 {
5731
5732
let _ = r.push ( " origin" , false ) ;
5733
}
5734
}
5735
} ) ;
5736
}
5737
} ) ;
5738
5739
let repo = Repo:: open( & main) .unwrap ( ) ;
5740
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5741
let state = repo.load_state ( & heads) .unwrap ( ) ;
5742
let messages: BTreeSet< String> = state.changes ( ) .map ( | ( _, m ) | m.message.clone ( ) ) .collect ( ) ;
5743
for tag in [ " a" , " b" ] {
5744
for i in 0 .. EACH {
5745
assert! (
5746
messages.contains ( & format! ( " {tag} {i} " ) ) ,
5747
" lost commit {tag} {i}; have {messages:?}"
5748
) ;
5749
}
5750
}
5751
let rep = repo.fsck ( ) .unwrap ( ) ;
5752
assert_eq! ( rep.errors ( ) , 0 , " {:?}" , rep.problems) ;
5753
assert! ( repo.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
5754
let hosted = Repo:: open( & origin) .unwrap ( ) ;
5755
let hosted_heads = hosted.bookmark_heads ( ) .unwrap ( ) ;
5756
assert_eq! (
5757
hosted.load_state ( & hosted_heads) .unwrap ( ) .len ( ) ,
5758
state.len ( ) ,
5759
" the remote has every commit"
5760
) ;
5761
}
5762
5763
5764
5765
5766
# [ test ]
5767
fn operations_from_the_same_parent_are_merged ( ) {
5768
hermetic ( ) ;
5769
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5770
let root = dir.path ( ) ;
5771
let repo = Repo:: init( root) .unwrap ( ) ;
5772
write ( root, " a.txt" , b " a\n " ) ;
5773
let a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
5774
write ( root, " b.txt" , b " b\n " ) ;
5775
let b = repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
5776
let ( x, op) = repo.head_op ( ) .unwrap ( ) ;
5777
let state = | heads : Vec< ChangeHash> | repo.store ( ) .put_state ( & StateObject:: new( heads) ) .unwrap ( ) ;
5778
5779
5780
let mut one = op.view.clone ( ) ;
5781
one.bookmarks.insert ( " main" .into ( ) , state ( vec! [ a] ) ) ;
5782
one.bookmarks.insert ( " topic" .into ( ) , state ( vec! [ a] ) ) ;
5783
let mut two = op.view.clone ( ) ;
5784
two.bookmarks.insert ( " main" .into ( ) , state ( vec! [ b] ) ) ;
5785
repo.record_op ( & [ x] , " one" , one) .unwrap ( ) ;
5786
let merged = repo.record_op ( & [ x] , " two" , two) .unwrap ( ) ;
5787
5788
let ( head, op) = repo.head_op ( ) .unwrap ( ) ;
5789
assert_eq! ( head, merged) ;
5790
assert_eq! ( op.parents.len ( ) , 2 , " a merge of the two operations" ) ;
5791
assert! ( op.command.contains ( " merged with a concurrent operation" ) ) ;
5792
let mut main = repo
5793
.store ( )
5794
.get_state ( & op.view.bookmarks[ " main" ] )
5795
.unwrap ( )
5796
.heads;
5797
main.sort ( ) ;
5798
let mut want = vec! [ a, b] ;
5799
want.sort ( ) ;
5800
assert_eq! ( main, want, " both sides' heads are kept" ) ;
5801
assert! (
5802
op.view.bookmarks.contains_key ( " topic" ) ,
5803
" a one-sided change survives"
5804
) ;
5805
assert_eq! ( repo.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
5806
}
3e5772cc Andy McCall 1 month ago ↶
5807
5808
# [ test ]
5809
fn failed_requests_come_with_a_next_step ( ) {
5810
use crate :: protocol:: hint_for;
5811
assert! ( hint_for ( " https://h/x/oxy/v1/push: HTTP 401: bad token" )
5812
.unwrap ( )
5813
.contains ( " token" ) ) ;
5814
assert! ( hint_for ( " …: HTTP 403: forbidden" )
5815
.unwrap ( )
5816
.contains ( " repo:write" ) ) ;
5817
assert! ( hint_for ( " …: HTTP 404: no such repository" )
5818
.unwrap ( )
5819
.contains ( " owner and repository name" ) ) ;
5820
assert! ( hint_for ( " invalid peer certificate: UnknownIssuer" )
5821
.unwrap ( )
5822
.contains ( " oxyhub-ca.pem" ) ) ;
5823
assert! ( hint_for ( " Connect error: Connection refused (os error 111)" )
5824
.unwrap ( )
5825
.contains ( " running" ) ) ;
5826
assert! ( hint_for ( " Dns Failed: failed to lookup address information" )
5827
.unwrap ( )
5828
.contains ( " resolve" ) ) ;
5829
assert_eq! ( hint_for ( " something else entirely" ) , None ) ;
5830
}
5831
5832
# [ test ]
5833
fn doctor_names_problems_and_what_to_do ( ) {
5834
hermetic ( ) ;
5835
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5836
let root = dir.path ( ) ;
5837
let origin = root.join ( " origin" ) ;
5838
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
5839
let mut repo = Repo:: init( & root.join ( " r" ) )
5840
.unwrap ( )
5841
.with_home ( root.join ( " home" ) ) ;
5842
let find = | r : & DoctorReport, area : & str , h : Health| {
5843
r.checks
5844
.iter ( )
5845
.find ( | c | c.area = = area & & c.health = = h )
5846
.cloned ( )
5847
} ;
5848
5849
let r = repo.doctor ( true ) ;
5850
assert! ( find ( & r, " identity" , Health:: Warn)
5851
.unwrap ( )
5852
.hint
5853
.unwrap ( )
5854
.contains ( " oxy identity create" ) ) ;
5855
assert! ( find ( & r, " repository" , Health:: Ok) .is_some ( ) ) ;
5856
5857
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
5858
.unwrap ( ) ;
5859
repo.config.set ( " author.name" , " Ada" ) .unwrap ( ) ;
5860
repo.config.set ( " author.email" , " [email protected] " ) .unwrap ( ) ;
5861
repo.config
5862
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
5863
.unwrap ( ) ;
5864
repo.config
5865
.set ( " remote.down.url" , " http://127.0.0.1:9/a/b" )
5866
.unwrap ( ) ;
5867
repo.config.set ( " remote.down.token" , " t" ) .unwrap ( ) ;
5868
repo.config
5869
.set ( " remote.badca.url" , " https://example.invalid/a/b" )
5870
.unwrap ( ) ;
5871
repo.config
5872
.set (
5873
" remote.badca.ca" ,
5874
& root.join ( " missing.pem" ) .to_string_lossy ( ) ,
5875
)
5876
.unwrap ( ) ;
5877
repo.save_config ( ) .unwrap ( ) ;
5878
5879
let r = repo.doctor ( true ) ;
5880
assert! ( find ( & r, " identity" , Health:: Ok) .is_some ( ) , " {r:?}" ) ;
5881
assert! ( find ( & r, " author" , Health:: Ok) .is_some ( ) , " {r:?}" ) ;
5882
assert! ( find ( & r, " remote origin" , Health:: Ok)
5883
.unwrap ( )
5884
.message
5885
.contains ( " first push binds it" ) ) ;
5886
let down = find ( & r, " remote down" , Health:: Fail) .unwrap ( ) ;
5887
assert! (
5888
down.hint.as_deref ( ) .unwrap_or ( " " ) .contains ( " running" ) ,
5889
" {down:?}"
5890
) ;
5891
let badca = find ( & r, " remote badca" , Health:: Fail) .unwrap ( ) ;
5892
assert! (
5893
badca.hint.unwrap ( ) .contains ( " remote.badca.ca" ) ,
5894
" the remote's own name is in the hint"
5895
) ;
5896
assert_eq! ( r.worst ( ) , Health:: Fail) ;
5897
5898
5899
let r = repo.doctor ( false ) ;
5900
assert! ( find ( & r, " remote down" , Health:: Fail) .is_none ( ) ) ;
5901
assert! ( find ( & r, " remote down" , Health:: Ok)
5902
.unwrap ( )
5903
.message
5904
.contains ( " not contacted" ) ) ;
5905
}
62794ecc Andy McCall 1 month ago ↶
5906
5907
5908
5909
5910
# [ test ]
5911
fn deleting_a_line_after_an_insertion_below_it_is_not_a_conflict ( ) {
5912
hermetic ( ) ;
5913
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5914
let root = dir.path ( ) ;
5915
let repo = Repo:: init( root) .unwrap ( ) ;
5916
write ( root, " f" , b " a\n b\n c\n " ) ;
5917
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
5918
write ( root, " f" , b " a\n b\n x\n c\n " ) ;
5919
let insert = repo.commit ( " insert x after b" , & [ ] ) .unwrap ( ) ;
5920
write ( root, " f" , b " a\n x\n c\n " ) ;
5921
let delete = repo.commit ( " delete b" , & [ ] ) .unwrap ( ) ;
5922
assert! (
5923
repo.store ( )
5924
.get_change ( & delete)
5925
.unwrap ( )
5926
.deps
5927
.contains ( & insert) ,
5928
" the deletion depends on the insertion below the deleted line"
5929
) ;
5930
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5931
assert_eq! ( heads, vec! [ delete] ) ;
5932
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
5933
assert! ( ! snap.is_conflicted ( ) ) ;
5934
assert! (
5935
matches! ( & snap.files[ " f" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n x\n c\n " )
5936
) ;
5937
}
5938
5939
5940
5941
# [ test ]
5942
fn a_binary_file_reverted_to_an_earlier_blob_keeps_it ( ) {
5943
hermetic ( ) ;
5944
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5945
let root = dir.path ( ) ;
5946
let repo = Repo:: init( root) .unwrap ( ) ;
5947
write ( root, " bin" , b " \0 A" ) ;
5948
let v1 = repo.commit ( " v1" , & [ ] ) .unwrap ( ) ;
5949
write ( root, " bin" , b " \0 B" ) ;
5950
let v2 = repo.commit ( " v2" , & [ ] ) .unwrap ( ) ;
5951
assert! ( repo.store ( ) .get_change ( & v2) .unwrap ( ) .deps.contains ( & v1) ) ;
5952
write ( root, " bin" , b " \0 A" ) ;
5953
let v3 = repo.commit ( " back to A" , & [ ] ) .unwrap ( ) ;
5954
assert! ( repo.store ( ) .get_change ( & v3) .unwrap ( ) .deps.contains ( & v2) ) ;
5955
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ v3] ) ;
5956
let state = repo.load_state ( & [ v3] ) .unwrap ( ) ;
5957
let snap = state.materialize ( ) ;
5958
let FileContent:: Opaque { blobs } = & snap.files[ " bin" ] .content else {
5959
panic! ( " opaque" )
5960
} ;
5961
assert_eq! ( blobs.len ( ) , 1 , " one live blob, not none and not two" ) ;
5962
assert_eq! ( repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) , b " \0 A" ) ;
5963
}
5964
5965
5966
5967
5968
# [ test ]
5969
fn git_import_resolves_a_binary_merged_in_git_and_verifies ( ) {
5970
hermetic ( ) ;
5971
if std:: process:: Command:: new( " git" )
5972
.arg ( " --version" )
5973
.output ( )
5974
.is_err ( )
5975
{
5976
eprintln! ( " git not installed; skipping" ) ;
5977
return ;
5978
}
5979
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5980
let root = dir.path ( ) ;
5981
let g = root.join ( " src.git" ) ;
5982
fs:: create_dir_all( & g) .unwrap ( ) ;
5983
let git = | args : & [& str ]| -> String {
5984
let out = std:: process:: Command:: new( " git" )
5985
.arg ( " -C" )
5986
.arg ( & g)
5987
.args ( [
5988
" -c" ,
5989
" user.name=Grace" ,
5990
" -c" ,
5991
" [email protected] " ,
5992
" -c" ,
5993
" commit.gpgsign=false" ,
5994
] )
5995
.args ( args)
5996
.output ( )
5997
.unwrap ( ) ;
5998
assert! (
5999
out.status.success ( ) ,
6000
" git {args:?}: {}" ,
6001
String :: from_utf8_lossy( & out.stderr)
6002
) ;
6003
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
6004
} ;
6005
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
6006
write ( & g, " logo.bin" , b " \0 base" ) ;
6007
write ( & g, " notes.txt" , b " a\n b\n c\n " ) ;
6008
git ( & [ " add" , " ." ] ) ;
6009
git ( & [ " commit" , " -qm" , " base" ] ) ;
6010
git ( & [ " checkout" , " -qb" , " side" ] ) ;
6011
write ( & g, " logo.bin" , b " \0 side" ) ;
6012
write ( & g, " notes.txt" , b " a\n b\n x\n c\n " ) ;
6013
git ( & [ " commit" , " -qam" , " side" ] ) ;
6014
git ( & [ " checkout" , " -q" , " main" ] ) ;
6015
write ( & g, " logo.bin" , b " \0 main" ) ;
6016
git ( & [ " commit" , " -qam" , " main" ] ) ;
6017
6018
let _ = std:: process:: Command:: new( " git" )
6019
.arg ( " -C" )
6020
.arg ( & g)
6021
.args ( [
6022
" -c" ,
6023
" user.name=G" ,
6024
" -c" ,
6025
" user.email=g@e" ,
6026
" merge" ,
6027
" -q" ,
6028
" side" ,
6029
] )
6030
.output ( ) ;
6031
write ( & g, " logo.bin" , b " \0 main" ) ;
6032
git ( & [ " add" , " ." ] ) ;
6033
git ( & [ " commit" , " -qm" , " merge side" ] ) ;
6034
6035
write ( & g, " logo.bin" , b " \0 base" ) ;
6036
git ( & [ " commit" , " -qam" , " back to base" ] ) ;
6037
6038
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
6039
let rep = repo.import_git ( & g, " main" ) .unwrap ( ) ;
6040
assert_eq! ( rep.merges, 1 ) ;
6041
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
6042
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
6043
assert_eq! ( v.commits, 5 ) ;
6044
let snap = repo
6045
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
6046
.unwrap ( )
6047
.materialize ( ) ;
6048
assert! ( ! snap.is_conflicted ( ) ) ;
6049
let FileContent:: Opaque { blobs } = & snap.files[ " logo.bin" ] .content else {
6050
panic! ( " opaque" )
6051
} ;
6052
assert_eq! ( repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) , b " \0 base" ) ;
6053
}
c863c7a7 Andy McCall 1 month ago ↶
6054
6055
6056
6057
6058
# [ cfg ( unix ) ]
6059
# [ test ]
6060
fn windows_file_systems_keep_exec_bits_and_symlinks ( ) {
6061
use crate :: portable:: Platform;
6062
use std:: os:: unix:: fs:: PermissionsExt;
6063
hermetic ( ) ;
6064
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6065
let root = dir.path ( ) ;
6066
let repo = Repo:: init( root) .unwrap ( ) ;
6067
write ( root, " run.sh" , b " #!/bin/sh\n " ) ;
6068
fs:: set_permissions( root.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
6069
std:: os:: unix:: fs:: symlink( " run.sh" , root.join ( " link" ) ) .unwrap ( ) ;
6070
repo.commit ( " script and link" , & [ ] ) .unwrap ( ) ;
6071
drop ( repo) ;
6072
6073
6074
fs:: set_permissions( root.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6075
fs:: remove_file( root.join ( " link" ) ) .unwrap ( ) ;
6076
write ( root, " link" , b " run.sh" ) ;
6077
let win = Repo:: open( root) .unwrap ( ) .with_platform ( Platform:: windows( ) ) ;
6078
let s = win.snapshot ( " oxy status" ) .unwrap ( ) ;
6079
assert! (
6080
s.statuses.is_empty ( ) ,
6081
" nothing changed on Windows: {:?}" ,
6082
statuses ( & s)
6083
) ;
6084
6085
write ( root, " run.sh" , b " #!/bin/sh\n echo hi\n " ) ;
6086
write ( root, " link" , b " other" ) ;
6087
let h = win.commit ( " edit on Windows" , & [ ] ) .unwrap ( ) ;
6088
let snap = win.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
6089
assert! ( snap.files[ " run.sh" ] .mode.exec, " the exec bit survives" ) ;
6090
assert_eq! ( snap.files[ " link" ] .kind, NodeKind:: Symlink) ;
6091
6092
let linux = Repo:: open( root) .unwrap ( ) ;
6093
assert! ( ! linux.snapshot ( " oxy status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6094
}
6095
6096
6097
6098
# [ test ]
6099
fn paths_other_platforms_cannot_hold_are_reported ( ) {
6100
use crate :: portable:: Platform;
6101
hermetic ( ) ;
6102
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6103
let root = dir.path ( ) ;
6104
let repo = Repo:: init( root) .unwrap ( ) ;
6105
write ( root, " README.md" , b " a\n " ) ;
6106
write ( root, " readme.md" , b " b\n " ) ;
6107
write ( root, " src/aux.c" , b " int x;\n " ) ;
6108
let h = repo.commit ( " unportable" , & [ ] ) .unwrap ( ) ;
6109
let r = repo.doctor ( false ) ;
6110
let port = r.checks.iter ( ) .find ( | c | c.area = = " portability" ) .unwrap ( ) ;
6111
assert_eq! ( port.health, Health:: Warn) ;
6112
assert! (
6113
port.message.contains ( " differ only in case" ) & & port.message.contains ( " aux.c" ) ,
6114
" {}" ,
6115
port.message
6116
) ;
6117
let mac = Repo:: open( root) .unwrap ( ) .with_platform ( Platform:: macos( ) ) ;
6118
let rep = mac.checkout ( & [ h] , true , " oxy checkout" ) .unwrap ( ) ;
6119
assert! (
6120
rep.notes
6121
.iter ( )
6122
.any ( | n | n.contains ( " README.md and readme.md" ) ) ,
6123
" {:?}" ,
6124
rep.notes
6125
) ;
6126
assert! (
6127
! rep.notes.iter ( ) .any ( | n | n.contains ( " aux.c" ) ) ,
6128
" macOS allows aux.c"
6129
) ;
6130
}
bc489b6c Andy McCall 1 month ago ↶
6131
6132
# [ test ]
6133
fn history_and_blame_of_a_loaded_state_match_the_full_walk ( ) {
6134
hermetic ( ) ;
6135
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6136
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
6137
write ( dir.path ( ) , " a.txt" , b " one\n " ) ;
6138
write ( dir.path ( ) , " other.txt" , b " x\n " ) ;
6139
write ( dir.path ( ) , " bin.dat" , & [ 0 u8 , 1 , 2 , 0 , 255 ] ) ;
6140
repo.snapshot ( " t" ) .unwrap ( ) ;
6141
let c1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
6142
write ( dir.path ( ) , " other.txt" , b " y\n " ) ;
6143
repo.snapshot ( " t" ) .unwrap ( ) ;
6144
repo.commit ( " other only" , & [ ] ) .unwrap ( ) ;
6145
write ( dir.path ( ) , " a.txt" , b " one\n two\n " ) ;
6146
repo.snapshot ( " t" ) .unwrap ( ) ;
6147
let c3 = repo.commit ( " second line" , & [ ] ) .unwrap ( ) ;
6148
write ( dir.path ( ) , " other.txt" , b " z\n " ) ;
6149
repo.snapshot ( " t" ) .unwrap ( ) ;
6150
repo.commit ( " other again" , & [ ] ) .unwrap ( ) ;
6151
6152
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6153
let state = repo.load_state ( & heads) .unwrap ( ) ;
6154
let full: Vec< ChangeHash> = repo
6155
.log_for_path_in ( " a.txt" , & heads)
6156
.unwrap ( )
6157
.iter ( )
6158
.map ( | e | e.hash )
6159
.collect ( ) ;
6160
assert_eq! (
6161
full,
6162
vec! [ c3, c1] ,
6163
" newest first, only changes touching the path"
6164
) ;
6165
let newest: Vec< ChangeHash> = repo
6166
.log_for_path_of ( & state, " a.txt" , Some ( 1 ) )
6167
.unwrap ( )
6168
.iter ( )
6169
.map ( | e | e.hash )
6170
.collect ( ) ;
6171
assert_eq! ( newest, vec! [ c3] , " stops at the first" ) ;
6172
assert_eq! (
6173
repo.log_for_path_of ( & state, " a.txt" , None ) .unwrap ( ) .len ( ) ,
6174
2
6175
) ;
6176
assert! ( repo
6177
.log_for_path_of ( & state, " missing.txt" , Some ( 1 ) )
6178
.is_err ( ) ) ;
6179
assert_eq! (
6180
repo.log_state ( & heads) .unwrap ( ) .len ( ) ,
6181
repo.log_of ( & state) .len ( )
6182
) ;
6183
6184
let tree = state.tree ( ) .view ( ) ;
6185
let via_state = repo.blame_of ( & state, & tree, " a.txt" ) .unwrap ( ) ;
6186
let via_heads = repo.blame_in ( " a.txt" , & heads) .unwrap ( ) ;
6187
assert_eq! (
6188
via_state
6189
.iter ( )
6190
.map ( | l | ( l.hash, l.text.clone ( ) ) )
6191
.collect:: < Vec< _ > > ( ) ,
6192
via_heads
6193
.iter ( )
6194
.map ( | l | ( l.hash, l.text.clone ( ) ) )
6195
.collect:: < Vec< _ > > ( )
6196
) ;
6197
assert_eq! (
6198
via_state.iter ( ) .map ( | l | l.hash ) .collect:: < Vec< _ > > ( ) ,
6199
vec! [ c1, c3]
6200
) ;
6201
assert! ( repo.blame_of ( & state, & tree, " missing.txt" ) .is_err ( ) ) ;
6202
}
696bbf29 Andy McCall 1 month ago ↶
6203
6204
6205
6206
6207
# [ cfg ( unix ) ]
6208
# [ test ]
6209
fn stat_cache_takes_unchanged_text_from_the_base_and_misses_no_edit ( ) {
6210
use std:: os:: unix:: fs:: PermissionsExt;
6211
hermetic ( ) ;
6212
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6213
let root = dir.path ( ) ;
6214
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6215
write ( root, " a.txt" , b " one\n two\n " ) ;
6216
write ( root, " b.txt" , b " bee\n " ) ;
6217
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6218
let age = | p : & str | {
6219
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6220
fs:: File:: options( )
6221
.write ( true )
6222
.open ( root.join ( p) )
6223
.unwrap ( )
6224
.set_modified ( old)
6225
.unwrap ( ) ;
6226
} ;
6227
age ( " a.txt" ) ;
6228
age ( " b.txt" ) ;
6229
assert! (
6230
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
6231
" records the aged stat"
6232
) ;
6233
6234
6235
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6236
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
6237
assert! (
6238
! snap.changed & & snap.statuses.is_empty ( ) ,
6239
" {:?}" ,
6240
snap.statuses
6241
) ;
6242
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6243
6244
6245
6246
write ( root, " b.txt" , b " bee edited\n " ) ;
6247
age ( " b.txt" ) ;
6248
let first = repo.snapshot ( " status" ) .unwrap ( ) ;
6249
assert_eq! (
6250
statuses ( & first) ,
6251
vec! [ ( " b.txt" .to_string ( ) , ChangeStatus:: Modified) ]
6252
) ;
6253
let again = repo.snapshot ( " status" ) .unwrap ( ) ;
6254
assert_eq! (
6255
statuses ( & again) ,
6256
vec! [ ( " b.txt" .to_string ( ) , ChangeStatus:: Modified) ] ,
6257
" still seen once stat-clean"
6258
) ;
6259
assert! ( ! again.changed, " and the working copy hashes the same" ) ;
6260
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6261
assert! (
6262
repo.snapshot ( " status" ) .is_err ( ) ,
6263
" an edited file must be read, never taken from the base"
6264
) ;
6265
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6266
6267
6268
write ( root, " a.txt" , b " one\n TWO\n " ) ;
6269
assert_eq! (
6270
statuses ( & repo.snapshot ( " status" ) .unwrap ( ) ) ,
6271
vec! [
6272
( " a.txt" .to_string ( ) , ChangeStatus:: Modified) ,
6273
( " b.txt" .to_string ( ) , ChangeStatus:: Modified)
6274
]
6275
) ;
6276
6277
repo.commit ( " edits" , & [ ] ) .unwrap ( ) ;
6278
age ( " a.txt" ) ;
6279
age ( " b.txt" ) ;
6280
assert! ( repo.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6281
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6282
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6283
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
6284
assert! ( ! snap.changed & & snap.statuses.is_empty ( ) ) ;
6285
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6286
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6287
}
afaba5f1 Andy McCall 1 month ago ↶
6288
6289
6290
6291
6292
# [ test ]
6293
fn checkpoints_keep_large_graphs_as_blobs_of_their_own ( ) {
6294
hermetic ( ) ;
6295
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6296
let root = dir.path ( ) ;
6297
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6298
let long: String = ( 0 .. 300 )
6299
.map ( | i | format! ( " line {i} of a long file\n " ) )
6300
.collect ( ) ;
6301
write ( root, " long.txt" , long.as_bytes ( ) ) ;
6302
write ( root, " short.txt" , b " short\n " ) ;
6303
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6304
let ( r1, _ , _ ) = repo.checkpoint ( ) .unwrap ( ) ;
6305
let cp1 = crate :: objects:: Checkpoint:: decode(
6306
& repo.store ( ) .get_typed ( & r1, ObjectType:: Checkpoint) .unwrap ( ) ,
6307
)
6308
.unwrap ( ) ;
6309
let graphs1 = repo.checkpoint_graph_blobs ( & cp1) .unwrap ( ) ;
6310
assert_eq! ( graphs1.len ( ) , 1 , " only the long file's graph is a blob" ) ;
6311
6312
6313
repo.gc ( true ) .unwrap ( ) ;
6314
assert! ( repo.store ( ) .has ( & graphs1[ 0 ] ) ) ;
6315
let report = repo.fsck ( ) .unwrap ( ) ;
6316
assert_eq! ( report.errors ( ) , 0 , " {:?}" , report.problems) ;
6317
6318
6319
write ( root, " short.txt" , b " short, edited\n " ) ;
6320
repo.commit ( " edit the short file" , & [ ] ) .unwrap ( ) ;
6321
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6322
let fast = repo.load_state ( & heads) .unwrap ( ) ;
6323
let full = repo.load_state_full ( & heads) .unwrap ( ) ;
6324
assert_eq! ( fast.checkpoint_payload ( ) , full.checkpoint_payload ( ) ) ;
6325
6326
6327
let ( r2, _ , _ ) = repo.checkpoint ( ) .unwrap ( ) ;
6328
let cp2 = crate :: objects:: Checkpoint:: decode(
6329
& repo.store ( ) .get_typed ( & r2, ObjectType:: Checkpoint) .unwrap ( ) ,
6330
)
6331
.unwrap ( ) ;
6332
assert_eq! ( repo.checkpoint_graph_blobs ( & cp2) .unwrap ( ) , graphs1) ;
6333
6334
6335
let bundle = repo.checkpoint_bundle_for ( & heads) .unwrap ( ) ;
6336
assert! ( bundle.objects.iter ( ) .any ( | ( t , body ) | * t = = ObjectType:: Blob
6337
& & crate :: hash:: hash_object( ObjectType:: Blob, body) = = graphs1[ 0 ] ) ) ;
6338
6339
6340
let state = repo.load_state_full ( & heads) .unwrap ( ) ;
6341
let old = repo.store_blob ( & state.checkpoint_payload ( ) ) .unwrap ( ) ;
6342
let cp_old = crate :: objects:: Checkpoint {
6343
heads: cp2.heads.clone ( ) ,
6344
count: cp2.count,
6345
payload: old,
6346
} ;
6347
let r_old = repo
6348
.store ( )
6349
.put ( ObjectType:: Checkpoint, & cp_old.encode ( ) )
6350
.unwrap ( ) ;
6351
repo.install_checkpoint ( " main" , & r_old) .unwrap ( ) ;
6352
assert_eq! (
6353
repo.load_state ( & heads) .unwrap ( ) .checkpoint_payload ( ) ,
6354
full.checkpoint_payload ( )
6355
) ;
6356
assert_eq! ( repo.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
6357
6358
6359
6360
let dir2 = tempfile:: tempdir( ) .unwrap ( ) ;
6361
let root2 = dir2.path ( ) ;
6362
let repo2 = Repo:: init( root2) .unwrap ( ) .with_home ( root2.join ( " home" ) ) ;
6363
write ( root2, " long.txt" , long.as_bytes ( ) ) ;
6364
repo2.commit ( " base" , & [ ] ) .unwrap ( ) ;
6365
let ( r, _ , _ ) = repo2.checkpoint ( ) .unwrap ( ) ;
6366
let cp = crate :: objects:: Checkpoint:: decode(
6367
& repo2.store ( ) .get_typed ( & r, ObjectType:: Checkpoint) .unwrap ( ) ,
6368
)
6369
.unwrap ( ) ;
6370
let graph = repo2.checkpoint_graph_blobs ( & cp) .unwrap ( ) [ 0 ] ;
64818938 Andy McCall 1 month ago ↶
6371
repo2.store ( ) .remove_loose_for_test ( & graph) .unwrap ( ) ;
afaba5f1 Andy McCall 1 month ago ↶
6372
assert! ( repo2.load_state ( & repo2.bookmark_heads ( ) .unwrap ( ) ) .is_err ( ) ) ;
6373
assert! ( repo2.fsck ( ) .unwrap ( ) .errors ( ) > 0 , " fsck reports it" ) ;
6374
}
e3d071ce Andy McCall 1 month ago ↶
6375
6376
6377
6378
6379
# [ test ]
6380
fn identity_is_found_in_the_key_directory_for_a_new_repository ( ) {
6381
hermetic ( ) ;
6382
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6383
let home = dir.path ( ) .join ( " home" ) ;
6384
let mut first = Repo:: init( & dir.path ( ) .join ( " a" ) )
6385
.unwrap ( )
6386
.with_home ( home.clone ( ) ) ;
6387
let ( key, _ ) = first
6388
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
6389
.unwrap ( ) ;
6390
let fresh = Repo:: init( & dir.path ( ) .join ( " b" ) )
6391
.unwrap ( )
6392
.with_home ( home.clone ( ) ) ;
6393
assert! ( ! fresh.store ( ) .has ( & key) , " nothing stored yet" ) ;
6394
let id = fresh.identity ( & key) .unwrap ( ) ;
6395
assert_eq! ( ( id.name.as_str ( ) , id.hash ( ) ) , ( " Ada" , key) ) ;
6396
6397
let keys = fresh.keys_dir.clone ( ) ;
6398
let ( other, _ ) = first
6399
.create_identity ( " Bob" , " [email protected] " , IdentityScope:: User)
6400
.unwrap ( ) ;
6401
fs:: copy(
6402
keys.join ( format! ( " {} .identity" , other.to_hex ( ) ) ) ,
6403
keys.join ( format! ( " {} .identity" , key.to_hex ( ) ) ) ,
6404
)
6405
.unwrap ( ) ;
6406
assert! ( fresh
6407
.identity ( & key)
6408
.unwrap_err ( )
6409
.to_string ( )
6410
.contains ( " does not hold identity" ) ) ;
6411
}
cc7a0056 Andy McCall 1 month ago ↶
6412
6413
6414
6415
6416
6417
# [ cfg ( unix ) ]
6418
# [ test ]
6419
fn status_snapshots_skip_files_unchanged_since_the_manifest ( ) {
6420
use std:: os:: unix:: fs:: PermissionsExt;
6421
hermetic ( ) ;
6422
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6423
let root = dir.path ( ) ;
6424
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6425
for ( p, c) in [ ( " a.txt" , " one\n " ) , ( " b.txt" , " bee\n " ) , ( " d/c.txt" , " sea\n " ) ] {
6426
write ( root, p, c.as_bytes ( ) ) ;
6427
}
6428
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6429
let age = | p : & str | {
6430
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6431
fs:: File:: options( )
6432
.write ( true )
6433
.open ( root.join ( p) )
6434
.unwrap ( )
6435
.set_modified ( old)
6436
.unwrap ( ) ;
6437
} ;
6438
let lock = | p : & str , mode : u32 | {
6439
fs:: set_permissions( root.join ( p) , fs:: Permissions:: from_mode( mode) ) .unwrap ( )
6440
} ;
6441
for p in [ " a.txt" , " b.txt" , " d/c.txt" ] {
6442
age ( p) ;
6443
}
6444
assert! ( repo.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6445
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
6446
assert! (
6447
m.entries.values ( ) .all ( | e | e.graph.is_some ( ) ) ,
6448
" keys recorded"
6449
) ;
6450
6451
6452
6453
lock ( " a.txt" , 0o000 ) ;
6454
lock ( " b.txt" , 0o000 ) ;
6455
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6456
assert! ( ! snap.changed & & snap.statuses.is_empty ( ) ) ;
6457
assert_eq! (
6458
snap.base_snapshot
6459
.unrendered
6460
.iter ( )
6461
.cloned ( )
6462
.collect:: < Vec< _ > > ( ) ,
6463
vec! [ " a.txt" , " b.txt" , " d/c.txt" ]
6464
) ;
6465
assert! ( ! snap.base.is_full ( ) ) ;
6466
assert! ( ! snap.base_snapshot.is_conflicted ( ) ) ;
6467
6468
let full = repo.snapshot ( " status" ) .unwrap ( ) ;
6469
assert! ( full.statuses.is_empty ( ) & & full.base.is_full ( ) ) ;
6470
assert! ( full.base_snapshot.unrendered.is_empty ( ) ) ;
6471
assert! ( matches! ( & full.base_snapshot.files[ " a.txt" ] .content,
6472
FileContent:: Text { bytes, .. } if bytes = = b " one\n " ) ) ;
6473
6474
6475
write ( root, " d/c.txt" , b " sea edited\n " ) ;
6476
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6477
assert_eq! (
6478
statuses ( & snap) ,
6479
vec! [ ( " d/c.txt" .to_string ( ) , ChangeStatus:: Modified) ]
6480
) ;
6481
assert! ( ! snap.base_snapshot.unrendered.contains ( " d/c.txt" ) ) ;
6482
6483
lock ( " b.txt" , 0o100 ) ;
6484
assert_eq! (
6485
statuses ( & repo.snapshot_status ( " status" ) .unwrap ( ) ) ,
6486
vec! [
6487
( " b.txt" .to_string ( ) , ChangeStatus:: ModeChanged) ,
6488
( " d/c.txt" .to_string ( ) , ChangeStatus:: Modified)
6489
]
6490
) ;
6491
lock ( " b.txt" , 0o000 ) ;
6492
6493
6494
6495
repo.commit_selection ( " edit c" , & [ ] , & Selection:: Paths( vec! [ " d/c.txt" .into ( ) ] ) )
6496
.unwrap ( ) ;
6497
age ( " d/c.txt" ) ;
6498
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6499
assert! ( snap.statuses.is_empty ( ) , " {:?}" , snap.statuses) ;
6500
assert! ( snap.base_snapshot.unrendered.contains ( " a.txt" ) ) ;
6501
lock ( " d/c.txt" , 0o000 ) ;
6502
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6503
assert! ( snap.statuses.is_empty ( ) ) ;
6504
assert! ( snap.base_snapshot.unrendered.contains ( " d/c.txt" ) ) ;
6505
for p in [ " a.txt" , " b.txt" , " d/c.txt" ] {
6506
lock ( p, 0o644 ) ;
6507
}
6508
}
6509
6510
6511
6512
# [ cfg ( unix ) ]
6513
# [ test ]
6514
fn a_changed_attributes_file_disables_skipping_unchanged_files ( ) {
6515
hermetic ( ) ;
6516
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6517
let root = dir.path ( ) ;
6518
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6519
write ( root, " a.txt" , b " crlf\r \n " ) ;
6520
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6521
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6522
fs:: File:: options( )
6523
.write ( true )
6524
.open ( root.join ( " a.txt" ) )
6525
.unwrap ( )
6526
.set_modified ( old)
6527
.unwrap ( ) ;
6528
assert! ( repo.snapshot_status ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6529
assert! ( repo
6530
.snapshot_status ( " status" )
6531
.unwrap ( )
6532
.base_snapshot
6533
.unrendered
6534
.contains ( " a.txt" ) ) ;
6535
6536
write ( root, " .oxyattributes" , b " *.txt eol=lf\n " ) ;
6537
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6538
assert_eq! (
6539
statuses ( & snap) ,
6540
vec! [
6541
( " .oxyattributes" .to_string ( ) , ChangeStatus:: Added) ,
6542
( " a.txt" .to_string ( ) , ChangeStatus:: Modified)
6543
]
6544
) ;
6545
}
54cd08a1 Andy McCall 1 month ago ↶
6546
6547
6548
6549
6550
# [ test ]
6551
fn bookmarks_with_slashes_push_and_checkpoint ( ) {
6552
hermetic ( ) ;
6553
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6554
let root = dir.path ( ) ;
6555
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
6556
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
6557
let mut repo = Repo:: clone( & url, & root.join ( " r" ) )
6558
.unwrap ( )
6559
.with_home ( root.join ( " home" ) ) ;
6560
6561
6562
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
6563
.unwrap ( ) ;
6564
let r = root.join ( " r" ) ;
6565
write ( & r, " a.txt" , b " a\n " ) ;
6566
let h_a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
6567
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
6568
origin.refresh ( ) .unwrap ( ) ;
54cd08a1 Andy McCall 1 month ago ↶
6569
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
6570
repo.bookmark_create ( " feature/x" , None ) .unwrap ( ) ;
6571
repo.bookmark_switch ( " feature/x" , false ) .unwrap ( ) ;
6572
write ( & r, " b.txt" , b " b\n " ) ;
6573
let h_b = repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
6574
for name in [ " feature" , " feature/x" ] {
6575
let res = repo.push_bookmark ( " origin" , name, false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
6576
origin.refresh ( ) .unwrap ( ) ;
54cd08a1 Andy McCall 1 month ago ↶
6577
assert! ( res[ 0 ] .ok, " {name}: {res:?}" ) ;
6578
}
6579
assert_eq! ( origin.resolve_heads ( " feature" ) .unwrap ( ) , vec! [ h_a] ) ;
6580
assert_eq! (
6581
origin.resolve_heads ( " feature/x" ) .unwrap ( ) ,
6582
repo.resolve_heads ( " feature/x" ) .unwrap ( )
6583
) ;
6584
assert! ( origin.resolve_heads ( " feature/x" ) .unwrap ( ) .contains ( & h_b) ) ;
6585
assert! ( origin.latest_ref_attest ( " feature/x" ) .unwrap ( ) .is_some ( ) ) ;
6586
assert! ( origin.latest_ref_attest ( " feature" ) .unwrap ( ) .is_some ( ) ) ;
6587
6588
6589
let ( cp, _ , _ ) = repo.checkpoint ( ) .unwrap ( ) ;
6590
assert_eq! ( repo.latest_checkpoint ( " feature/x" ) .unwrap ( ) .unwrap ( ) .0 , cp) ;
6591
assert! ( repo.latest_checkpoint ( " feature" ) .unwrap ( ) .is_none ( ) ) ;
6592
let heads = repo.resolve_heads ( " feature/x" ) .unwrap ( ) ;
6593
assert! ( repo.load_state_checkpointed ( & heads) .unwrap ( ) .is_some ( ) ) ;
f8c69576 Andy McCall 1 month ago ↶
6594
}
6595
6596
6597
6598
6599
# [ test ]
6600
fn bookmarks_are_deleted_on_a_remote ( ) {
6601
hermetic ( ) ;
6602
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6603
let root = dir.path ( ) ;
6604
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
6605
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
6606
let mut repo = Repo:: clone( & url, & root.join ( " r" ) )
6607
.unwrap ( )
6608
.with_home ( root.join ( " home" ) ) ;
6609
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
6610
.unwrap ( ) ;
6611
let r = root.join ( " r" ) ;
6612
write ( & r, " a.txt" , b " a\n " ) ;
6613
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
6614
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
6615
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6616
repo.bookmark_create ( " feature/x" , None ) .unwrap ( ) ;
6617
assert! ( repo.push_bookmark ( " origin" , " feature/x" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
6618
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6619
assert! ( origin.resolve_heads ( " feature/x" ) .is_ok ( ) ) ;
6620
6621
let res = repo.push_delete ( " origin" , " feature/x" ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
6622
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6623
assert! ( res[ 0 ] .ok, " {res:?}" ) ;
6624
assert! ( ! origin.view ( ) .unwrap ( ) .bookmarks.contains_key ( " feature/x" ) ) ;
6625
let ( _ , att) = origin.latest_ref_attest ( " feature/x" ) .unwrap ( ) .unwrap ( ) ;
6626
assert! (
6627
matches! ( att.attest.body, AttestBody:: Ref { new_state, .. } if new_state.is_zero ( ) ) ,
6628
" the deletion is attested"
6629
) ;
6630
assert! (
6631
repo.view ( ) .unwrap ( ) .bookmarks.contains_key ( " feature/x" ) ,
6632
" the local bookmark stays"
6633
) ;
6634
assert! ( ! repo
6635
.view ( )
6636
.unwrap ( )
6637
.remotes
6638
.get ( " origin" )
6639
.is_some_and ( | m | m.contains_key ( " feature/x" ) ) ) ;
6640
6641
6642
let res = repo.push_bookmark ( " origin" , " feature/x" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
6643
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6644
assert! ( res[ 0 ] .ok, " {res:?}" ) ;
6645
assert! ( origin.view ( ) .unwrap ( ) .bookmarks.contains_key ( " feature/x" ) ) ;
6646
6647
6648
assert! ( repo.push_delete ( " origin" , " nope" ) .is_err ( ) ) ;
6649
let res = repo.push_delete ( " origin" , " main" ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
6650
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6651
assert! (
6652
! res[ 0 ] .ok & & res[ 0 ] .reason.contains ( " default bookmark" ) ,
6653
" {res:?}"
6654
) ;
6655
assert! ( origin.view ( ) .unwrap ( ) .bookmarks.contains_key ( " main" ) ) ;
54cd08a1 Andy McCall 1 month ago ↶
6656
}
61e3bce7 Andy McCall 1 month ago ↶
6657
6658
6659
6660
6661
# [ test ]
6662
fn switching_bookmarks_carries_uncommitted_work ( ) {
6663
hermetic ( ) ;
6664
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6665
let root = dir.path ( ) ;
6666
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6667
write ( root, " a.txt" , b " a\n " ) ;
6668
write ( root, " b.txt" , b " b\n " ) ;
6669
write ( root, " gone.txt" , b " gone\n " ) ;
6670
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6671
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
6672
6673
write ( root, " a.txt" , b " a\n more\n " ) ;
6674
write ( root, " new.txt" , b " new\n " ) ;
6675
fs:: remove_file( root.join ( " gone.txt" ) ) .unwrap ( ) ;
44f1ebe7 Andy McCall 19 days ago ↶
6676
let ( report, carried) = repo.bookmark_switch_carry ( " feature" ) .unwrap ( ) ;
61e3bce7 Andy McCall 1 month ago ↶
6677
assert_eq! ( carried, vec! [ " a.txt" , " gone.txt" , " new.txt" ] ) ;
44f1ebe7 Andy McCall 19 days ago ↶
6678
6679
assert_eq! (
6680
( report.removed, report.removed_paths.len ( ) ) ,
6681
( 0 , 0 ) ,
6682
" {report:?}"
6683
) ;
61e3bce7 Andy McCall 1 month ago ↶
6684
assert_eq! ( repo.config.bookmark, " feature" ) ;
6685
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " a\n more\n " ) ;
6686
assert_eq! ( fs:: read( root.join ( " new.txt" ) ) .unwrap ( ) , b " new\n " ) ;
6687
assert! ( ! root.join ( " gone.txt" ) .exists ( ) ) ;
6688
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
6689
assert_eq! (
6690
statuses ( & snap) ,
6691
vec! [
6692
( " a.txt" .to_string ( ) , ChangeStatus:: Modified) ,
6693
( " gone.txt" .to_string ( ) , ChangeStatus:: Removed) ,
6694
( " new.txt" .to_string ( ) , ChangeStatus:: Added)
6695
]
6696
) ;
6697
repo.commit ( " the work" , & [ ] ) .unwrap ( ) ;
6698
assert! ( repo.resolve_heads ( " main" ) .unwrap ( ) .len ( ) = = 1 ) ;
6699
6700
6701
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
6702
write ( root, " b.txt" , b " b on main\n " ) ;
6703
repo.commit ( " b on main" , & [ ] ) .unwrap ( ) ;
6704
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
6705
write ( root, " b.txt" , b " b edited\n " ) ;
6706
let err = repo.bookmark_switch_carry ( " main" ) .unwrap_err ( ) .to_string ( ) ;
6707
assert! ( err.contains ( " b.txt" ) & & err.contains ( " --discard" ) , " {err}" ) ;
6708
assert_eq! ( repo.config.bookmark, " feature" , " nothing happened" ) ;
6709
assert_eq! ( fs:: read( root.join ( " b.txt" ) ) .unwrap ( ) , b " b edited\n " ) ;
6710
}
1c2bd9ab Andy McCall 1 month ago ↶
6711
6712
6713
6714
6715
# [ test ]
6716
fn merges_move_clean_working_copies_and_checkouts_say_what_blocks_them ( ) {
6717
hermetic ( ) ;
6718
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6719
let root = dir.path ( ) ;
6720
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6721
write ( root, " a.txt" , b " a\n " ) ;
6722
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
6723
write ( root, " b.txt" , b " b\n " ) ;
6724
repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
6725
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
6726
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
6727
write ( root, " c.txt" , b " c\n " ) ;
6728
repo.commit ( " c on feature" , & [ ] ) .unwrap ( ) ;
6729
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
6730
6731
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
6732
assert! ( ! root.join ( " b.txt" ) .exists ( ) ) ;
6733
let m = repo.merge ( " feature" ) .unwrap ( ) ;
6734
assert! ( m.checked_out, " a clean working copy follows the merge" ) ;
6735
for f in [ " a.txt" , " b.txt" , " c.txt" ] {
6736
assert! ( root.join ( f) .exists ( ) , " {f}" ) ;
6737
}
6738
6739
6740
fs:: remove_file( root.join ( " b.txt" ) ) .unwrap ( ) ;
6741
write ( root, " a.txt" , b " a edited\n " ) ;
6742
let err = repo
6743
.checkout ( & [ h1] , false , " checkout" )
6744
.unwrap_err ( )
6745
.to_string ( ) ;
6746
assert! (
6747
err.contains ( " modified a.txt" ) & & err.contains ( " removed b.txt" ) & & err.contains ( " --carry" ) ,
6748
" {err}"
6749
) ;
fe9586d9 Andy McCall 25 days ago ↶
6750
6751
6752
1c2bd9ab Andy McCall 1 month ago ↶
6753
let ( _ , carried) = repo
6754
.checkout_carry ( & [ h1] , " the first change" , " checkout" )
6755
.unwrap ( ) ;
6756
assert_eq! ( carried, vec! [ " a.txt" ] ) ;
6757
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " a edited\n " ) ;
6758
assert! ( ! root.join ( " b.txt" ) .exists ( ) , " h1 has no b.txt" ) ;
6759
}
e7ac6c2e Andy McCall 1 month ago ↶
6760
6761
6762
6763
6764
# [ test ]
6765
fn a_new_repository_is_healthy_before_its_first_commit ( ) {
6766
hermetic ( ) ;
6767
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6768
let root = dir.path ( ) ;
6769
let home = root.join ( " home" ) ;
6770
let mut first = Repo:: init( & root.join ( " first" ) )
6771
.unwrap ( )
6772
.with_home ( home.clone ( ) ) ;
6773
let ( key, _ ) = first
6774
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
6775
.unwrap ( ) ;
6776
let repo = Repo:: init( & root.join ( " second" ) )
6777
.unwrap ( )
6778
.with_home ( home.clone ( ) ) ;
6779
6780
let keys = repo.keys_dir.clone ( ) ;
6781
let _ = fs:: remove_file( keys.join ( format! ( " {} .identity" , key.to_hex ( ) ) ) ) ;
6782
assert! ( keys.join ( format! ( " {} .secret" , key.to_hex ( ) ) ) .is_file ( ) ) ;
6783
let r = repo.doctor ( true ) ;
6784
let id = r.checks.iter ( ) .find ( | c | c.area = = " identity" ) .unwrap ( ) ;
6785
assert_eq! ( id.health, Health:: Ok, " {}" , id.message) ;
6786
assert! ( id.message.contains ( " first signed change" ) , " {}" , id.message) ;
6787
6788
6789
let lib = root.join ( " lib" ) ;
6790
let l = Repo:: init( & lib) .unwrap ( ) .with_home ( home.clone ( ) ) ;
6791
write ( & lib, " x.txt" , b " x\n " ) ;
6792
l.commit ( " x" , & [ ] ) .unwrap ( ) ;
6793
let second = root.join ( " second" ) ;
6794
write ( & second, " a.txt" , b " a\n " ) ;
6795
repo.submodule_add ( & lib.to_string_lossy ( ) , " deps/lib" , None )
6796
.unwrap ( ) ;
6797
let subs = repo.subrepos ( ) .unwrap ( ) ;
6798
assert_eq! ( subs.len ( ) , 1 ) ;
6799
assert_eq! (
6800
( subs[ 0 ] .path.as_str ( ) , subs[ 0 ] .committed) ,
6801
( " deps/lib" , false )
6802
) ;
6803
assert_eq! ( subs[ 0 ] .actual.as_ref ( ) , Some ( & subs[ 0 ] .pin) ) ;
6804
repo.commit ( " add lib" , & [ ] ) .unwrap ( ) ;
6805
let subs = repo.subrepos ( ) .unwrap ( ) ;
6806
assert! ( subs[ 0 ] .committed, " committed now" ) ;
6807
}
8ab265dc Andy McCall 1 month ago ↶
6808
6809
6810
6811
6812
# [ cfg ( unix ) ]
6813
# [ test ]
6814
fn commits_and_diffs_leave_unchanged_files_alone ( ) {
6815
use std:: os:: unix:: fs:: PermissionsExt;
6816
hermetic ( ) ;
6817
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6818
let root = dir.path ( ) ;
6819
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6820
write ( root, " big.txt" , b " lots\n of\n lines\n " ) ;
6821
write ( root, " a.txt" , b " one\n two\n " ) ;
6822
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6823
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6824
for f in [ " big.txt" , " a.txt" ] {
6825
fs:: File:: options( )
6826
.write ( true )
6827
.open ( root.join ( f) )
6828
.unwrap ( )
6829
.set_modified ( old)
6830
.unwrap ( ) ;
6831
}
6832
assert! ( repo.snapshot_status ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6833
6834
fs:: set_permissions( root.join ( " big.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6835
6836
write ( root, " a.txt" , b " one\n TWO\n three\n " ) ;
6837
let diff = repo.diff_files ( None ) .unwrap ( ) ;
6838
assert_eq! (
6839
diff.iter ( ) .map ( | f | f.path.as_str ( ) ) .collect:: < Vec< _ > > ( ) ,
6840
[ " a.txt" ]
6841
) ;
6842
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
6843
assert! ( ! hunks.is_empty ( ) & & hunks.iter ( ) .all ( | h | h.path = = " a.txt" ) ) ;
6844
let h = repo.commit ( " edit a" , & [ ] ) .unwrap ( ) ;
6845
6846
6847
let c = repo.store ( ) .get_change ( & h) .unwrap ( ) ;
6848
let full = repo.load_state ( & c.deps) .unwrap ( ) ;
6849
let want = render_change ( & c, & full, & full.materialize ( ) ) ;
6850
assert_eq! ( repo.describe_change ( & h) .unwrap ( ) , want) ;
6851
let ( base, snap) = repo.change_base ( & c) .unwrap ( ) ;
6852
assert! ( ! base.is_full ( ) & & snap.unrendered.contains ( " big.txt" ) ) ;
6853
6854
6855
write ( root, " a.txt" , b " scratch\n " ) ;
6856
assert_eq! ( repo.discard ( & [ " a.txt" .into ( ) ] ) .unwrap ( ) , 1 ) ;
6857
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n TWO\n three\n " ) ;
6858
fs:: set_permissions( root.join ( " big.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6859
}
f7510b63 Andy McCall 1 month ago ↶
6860
6861
6862
6863
6864
# [ test ]
6865
fn replace_conflicts_are_settled_when_asked ( ) {
6866
hermetic ( ) ;
6867
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6868
let root = dir.path ( ) ;
6869
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6870
let blob = | n : u8 | {
6871
let mut v = vec! [ 0 u8 ; 64 ] ;
6872
v[ 1 ] = n;
6873
v
6874
} ;
6875
write ( root, " img.bin" , & blob ( 1 ) ) ;
6876
repo.commit ( " add img" , & [ ] ) .unwrap ( ) ;
6877
repo.bookmark_create ( " other" , None ) .unwrap ( ) ;
6878
write ( root, " img.bin" , & blob ( 2 ) ) ;
6879
repo.commit ( " img two" , & [ ] ) .unwrap ( ) ;
6880
repo.bookmark_switch ( " other" , false ) .unwrap ( ) ;
6881
write ( root, " img.bin" , & blob ( 3 ) ) ;
6882
repo.commit ( " img three" , & [ ] ) .unwrap ( ) ;
6883
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
6884
repo.merge ( " other" ) .unwrap ( ) ;
6885
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6886
let merged = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
6887
assert! (
6888
matches! ( & merged.files[ " img.bin" ] .content, FileContent:: Opaque { blobs } if blobs.len ( ) = = 2 ) ,
6889
" a replace conflict"
6890
) ;
6891
6892
assert! ( repo
6893
.snapshot ( " status" )
6894
.unwrap ( )
6895
.statuses
6896
.iter ( )
6897
.all ( | s | s.path ! = " img.bin" ) ) ;
6898
6899
6900
let author = repo.author ( ) ;
6901
let edits = [ FileEdit {
6902
path: " img.bin" .into ( ) ,
6903
content: Some ( blob ( 3 ) ) ,
6904
} ] ;
6905
let c = repo
6906
.build_change ( & heads, & edits, " keep three" , author)
6907
.unwrap ( ) ;
6908
assert_eq! ( c.kind, ChangeKind:: Resolution) ;
6909
let mut st = repo.load_state ( & heads) .unwrap ( ) ;
6910
for d in & c.deps {
6911
assert! ( st.contains ( d) ) ;
6912
}
6913
assert! (
6914
heads.iter ( ) .all ( | h | c.deps.contains ( h) ) ,
6915
" depends on both contributors"
6916
) ;
6917
st.add ( c) .unwrap ( ) ;
6918
let after = st.materialize ( ) ;
6919
assert! ( ! after.is_conflicted ( ) ) ;
6920
assert_eq! (
6921
after.files[ " img.bin" ] .content,
6922
FileContent:: Opaque {
6923
blobs: vec! [ Repo:: blob_ref( & blob ( 3 ) ) ]
6924
}
6925
) ;
6926
6927
6928
let edits = [ FileEdit {
6929
path: " img.bin" .into ( ) ,
6930
content: Some ( blob ( 9 ) ) ,
6931
} ] ;
6932
let c = repo.build_change ( & heads, & edits, " new" , author) .unwrap ( ) ;
6933
let mut st = repo.load_state ( & heads) .unwrap ( ) ;
6934
st.add ( c) .unwrap ( ) ;
6935
assert_eq! (
6936
st.materialize ( ) .files[ " img.bin" ] .content,
6937
FileContent:: Opaque {
6938
blobs: vec! [ Repo:: blob_ref( & blob ( 9 ) ) ]
6939
}
6940
) ;
6941
}
83d61e80 Andy McCall 1 month ago ↶
6942
6943
6944
6945
6946
# [ test ]
6947
fn line_ranges_have_exact_histories ( ) {
6948
hermetic ( ) ;
6949
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6950
let root = dir.path ( ) ;
6951
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6952
write ( root, " f.rs" , b " use x;\n fn a() {\n one();\n two();\n }\n " ) ;
6953
let base = repo.commit ( " add f" , & [ ] ) .unwrap ( ) ;
6954
write ( root, " f.rs" , b " use y;\n fn a() {\n one();\n two();\n }\n " ) ;
6955
let outside = repo.commit ( " change the import" , & [ ] ) .unwrap ( ) ;
6956
write ( root, " f.rs" , b " use y;\n fn a() {\n ONE();\n two();\n }\n " ) ;
6957
let inside = repo.commit ( " edit a line of a" , & [ ] ) .unwrap ( ) ;
6958
write ( root, " f.rs" , b " use y;\n fn a() {\n ONE();\n }\n " ) ;
6959
let deleted = repo.commit ( " delete a line of a" , & [ ] ) .unwrap ( ) ;
6960
write ( root, " f.rs" , b " use y;\n fn a() {\n ONE();\n }\n fn b() {}\n " ) ;
6961
let after = repo.commit ( " add b after a" , & [ ] ) .unwrap ( ) ;
6962
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6963
6964
6965
let hist: Vec< ChangeHash> = repo
6966
.line_history ( & heads, " f.rs" , 2 , 4 )
6967
.unwrap ( )
6968
.iter ( )
6969
.map ( | e | e.hash )
6970
.collect ( ) ;
6971
for ( h, what) in [
6972
( base, " wrote it" ) ,
6973
( inside, " edited a line" ) ,
6974
( deleted, " deleted a line from inside" ) ,
6975
] {
6976
assert! ( hist.contains ( & h) , " {what}" ) ;
6977
}
6978
for ( h, what) in [ ( outside, " an edit above" ) , ( after, " an addition below" ) ] {
6979
assert! ( ! hist.contains ( & h) , " {what}" ) ;
6980
}
6981
assert_eq! ( hist[ 0 ] , deleted, " newest first" ) ;
6982
6983
let one: Vec< ChangeHash> = repo
6984
.line_history ( & heads, " f.rs" , 1 , 1 )
6985
.unwrap ( )
6986
.iter ( )
6987
.map ( | e | e.hash )
6988
.collect ( ) ;
6989
assert! ( one.contains ( & outside) & & ! one.contains ( & inside) ) ;
6990
assert! ( repo.line_history ( & heads, " f.rs" , 3 , 9 ) .is_err ( ) ) ;
6991
assert! ( repo.line_history ( & heads, " f.rs" , 0 , 1 ) .is_err ( ) ) ;
6992
assert! ( repo.line_history ( & heads, " nope.rs" , 1 , 1 ) .is_err ( ) ) ;
6993
}
fc1a466a Andy McCall 1 month ago ↶
6994
6995
# [ test ]
6996
fn receipts_are_kept_with_their_state_and_verified ( ) {
6997
use crate :: receipt:: { Receipt, SignedReceipt} ;
6998
hermetic ( ) ;
6999
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7000
let root = dir.path ( ) ;
7001
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
7002
write ( root, " a.txt" , b " 1\n " ) ;
7003
repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
7004
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
7005
let state = repo
7006
.store ( )
7007
.put_state ( & StateObject:: new( heads.clone ( ) ) )
7008
.unwrap ( ) ;
7009
let out = repo.store_blob ( b " built\n " ) .unwrap ( ) ;
7010
let receipt = Receipt {
7011
job_name: " build" .into ( ) ,
7012
pipeline_hash: ObjectRef( [ 1 ; 32 ] ) ,
7013
scope_tree_hash: ObjectRef( [ 2 ; 32 ] ) ,
7014
inputs: vec! [ ] ,
7015
environment: ObjectRef( [ 3 ; 32 ] ) ,
7016
outputs: vec! [ out] ,
7017
exit_code: 0 ,
7018
duration_ms: 42 ,
7019
} ;
7020
let sk = SecretKey:: generate( ) ;
7021
let runner = sk.identity ( " earth-1" , " runner@earth" , 1 ) ;
7022
let sr = SignedReceipt:: sign(
7023
receipt.clone ( ) ,
7024
repo.id ( ) .unwrap ( ) ,
7025
state,
7026
" demo#1" ,
7027
& runner,
7028
& sk,
7029
) ;
7030
7031
let r = repo.add_receipt ( & sr) .unwrap ( ) ;
7032
let found = repo.receipts ( & heads) .unwrap ( ) ;
7033
assert_eq! ( found.len ( ) , 1 ) ;
7034
assert_eq! ( found[ 0 ] .attest, r) ;
7035
assert_eq! ( found[ 0 ] .signed, sr) ;
7036
assert! ( found[ 0 ] .problem.is_none ( ) & & ! found[ 0 ] .revoked) ;
7037
assert! ( repo.receipts ( & [ ObjectRef( [ 5 ; 32 ] ) ] ) .unwrap ( ) .is_empty ( ) ) ;
7038
7039
7040
let elsewhere = SignedReceipt:: sign( receipt.clone ( ) , [ 0 ; 16 ] , state, " x" , & runner, & sk) ;
7041
assert! ( repo
7042
.add_receipt ( & elsewhere)
7043
.unwrap_err ( )
7044
.to_string ( )
7045
.contains ( " another repository" ) ) ;
7046
let mut forged = sr.clone ( ) ;
7047
forged.receipt.exit_code = 1 ;
7048
assert! ( repo
7049
.add_receipt ( & forged)
7050
.unwrap_err ( )
7051
.to_string ( )
7052
.contains ( " does not verify" ) ) ;
7053
c63fed2e Andy McCall 20 days ago ↶
7054
7055
7056
let hub_sk = SecretKey:: generate( ) ;
7057
let hub = hub_sk.identity ( " oxygenhub.io" , " [email protected] " , 1 ) ;
7058
let job_sk = SecretKey:: generate( ) ;
7059
let job = job_sk.identity ( " job 2 test" , " [email protected] " , 1 ) ;
7060
let mut test_receipt = receipt.clone ( ) ;
7061
test_receipt.job_name = " test" .into ( ) ;
7062
let plain = SignedReceipt:: sign(
7063
test_receipt,
7064
repo.id ( ) .unwrap ( ) ,
7065
state,
7066
" demo#2" ,
7067
& job,
7068
& job_sk,
7069
) ;
7070
let now = plain.attest.hlc.wall_ms;
7071
let cert = crate :: cert:: SignedCertificate:: sign(
7072
crate :: cert:: Certificate {
7073
key: job.clone ( ) ,
7074
issuer: hub.hash ( ) ,
7075
subject: " repo:andy/demo:bookmark:main" .into ( ) ,
7076
claims: vec! [ ] ,
7077
scope: crate :: identity:: Scope:: SEAL ,
7078
not_before: now - 1_000 ,
7079
expires: now + 600_000 ,
a2387d54 Andy McCall 20 days ago ↶
7080
grant: None ,
c63fed2e Andy McCall 20 days ago ↶
7081
} ,
7082
& hub,
7083
& hub_sk,
7084
) ;
7085
let certified = plain.with_certificate ( cert, hub.clone ( ) ) ;
7086
repo.add_receipt ( & certified) .unwrap ( ) ;
7087
let found = repo.receipts ( & heads) .unwrap ( ) ;
7088
let got = found
7089
.iter ( )
7090
.find ( | i | i.signed.receipt.job_name = = " test" )
7091
.unwrap ( ) ;
7092
assert_eq! ( got.signed, certified) ;
7093
assert! ( got.problem.is_none ( ) , " {:?}" , got.problem) ;
7094
7095
7096
fc1a466a Andy McCall 1 month ago ↶
7097
repo.gc ( true ) .unwrap ( ) ;
c63fed2e Andy McCall 20 days ago ↶
7098
let found = repo.receipts ( & heads) .unwrap ( ) ;
7099
assert_eq! ( found.len ( ) , 2 ) ;
7100
let got = found
7101
.iter ( )
7102
.find ( | i | i.signed.receipt.job_name = = " test" )
7103
.unwrap ( ) ;
7104
assert_eq! ( got.signed, certified, " the certificate survives gc" ) ;
7105
assert! ( got.problem.is_none ( ) ) ;
fc1a466a Andy McCall 1 month ago ↶
7106
assert_eq! ( repo.read_blob ( & out) .unwrap ( ) , b " built\n " ) ;
7107
assert! ( repo.fsck ( ) .unwrap ( ) .problems.is_empty ( ) ) ;
dfb23a3a Andy McCall 1 month ago ↶
7108
}
7109
7110
# [ test ]
3a4b3e01 Andy McCall 21 days ago ↶
7111
fn approvals_are_kept_with_their_state_and_survive_gc ( ) {
7112
hermetic ( ) ;
7113
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7114
let root = dir.path ( ) ;
7115
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
7116
write ( root, " a.txt" , b " 1\n " ) ;
7117
repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
7118
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
7119
let state = repo.store ( ) .put_state ( & StateObject:: new( heads) ) .unwrap ( ) ;
7120
let plan = repo.store_blob ( b " the reviewed plan\n " ) .unwrap ( ) ;
7121
let sk = SecretKey:: generate( ) ;
7122
let andy = sk.identity ( " Andy" , " [email protected] " , 1 ) ;
7123
let statement = crate :: approval:: Statement {
7124
repo: repo.id ( ) .unwrap ( ) ,
7125
run: " https://hub.example/andy/infra/flow/8" .into ( ) ,
7126
job: " apply" .into ( ) ,
7127
job_hash: ObjectRef( [ 1 ; 32 ] ) ,
7128
state,
7129
inputs: vec! [ plan] ,
7130
environment: " prod" .into ( ) ,
7131
approve: true ,
7132
} ;
7133
let sa = crate :: approval:: SignedApproval:: sign( & statement, & andy, & sk) ;
7134
let r = repo.add_approval ( & sa) .unwrap ( ) ;
7135
assert_eq! ( repo.store ( ) .approvals_for ( & state) .unwrap ( ) , vec! [ r] ) ;
7136
7137
let elsewhere = crate :: approval:: SignedApproval:: sign(
7138
& crate :: approval:: Statement {
7139
repo: [ 0 ; 16 ] ,
7140
.. statement.clone ( )
7141
} ,
7142
& andy,
7143
& sk,
7144
) ;
7145
assert! ( repo
7146
.add_approval ( & elsewhere)
7147
.unwrap_err ( )
7148
.to_string ( )
7149
.contains ( " another repository" ) ) ;
7150
let mut forged = sa.clone ( ) ;
7151
if let AttestBody:: Approval { environment, .. } = & mut forged.attest.body {
7152
* environment = " dev" .into ( ) ;
7153
}
7154
assert! ( repo
7155
.add_approval ( & forged)
7156
.unwrap_err ( )
7157
.to_string ( )
7158
.contains ( " does not verify" ) ) ;
7159
7160
repo.gc ( true ) .unwrap ( ) ;
7161
assert_eq! ( repo.store ( ) .approvals_for ( & state) .unwrap ( ) , vec! [ r] ) ;
7162
assert_eq! ( repo.store ( ) .get_attest ( & r) .unwrap ( ) , sa.attest) ;
7163
assert_eq! ( repo.store ( ) .signatures ( & r) .unwrap ( ) , sa.sigs) ;
7164
assert! ( repo.fsck ( ) .unwrap ( ) .problems.is_empty ( ) ) ;
7165
}
7166
7167
# [ test ]
dfb23a3a Andy McCall 1 month ago ↶
7168
fn status_leaves_an_unchanged_manifest_alone ( ) {
7169
hermetic ( ) ;
7170
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7171
let root = dir.path ( ) ;
7172
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
7173
write ( root, " a.txt" , b " 1\n " ) ;
7174
write ( root, " b.txt" , b " 2\n " ) ;
7175
repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
7176
7177
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 60 ) ;
7178
for f in [ " a.txt" , " b.txt" ] {
7179
fs:: File:: options( )
7180
.write ( true )
7181
.open ( root.join ( f) )
7182
.unwrap ( )
7183
.set_modified ( old)
7184
.unwrap ( ) ;
7185
}
7186
repo.snapshot_status ( " status" ) .unwrap ( ) ;
7187
let path = repo.manifest_path ( ) ;
7188
let first = fs:: read( & path) .unwrap ( ) ;
7189
repo.snapshot_status ( " status" ) .unwrap ( ) ;
7190
assert_eq! (
7191
fs:: read( & path) .unwrap ( ) ,
7192
first,
7193
" an unchanged tree does not rewrite the manifest"
7194
) ;
7195
7196
write ( root, " a.txt" , b " 1\n 2\n " ) ;
7197
repo.snapshot_status ( " status" ) .unwrap ( ) ;
7198
assert_ne! ( fs:: read( & path) .unwrap ( ) , first) ;
fc1a466a Andy McCall 1 month ago ↶
7199
}
6f8aeb99 Andy McCall 1 month ago ↶
7200
7201
# [ test ]
7202
fn large_text_files_travel_and_survive_gc_as_text_blobs ( ) {
7203
hermetic ( ) ;
7204
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7205
let root = dir.path ( ) ;
7206
let origin_path = root.join ( " origin" ) ;
7207
Repo:: init( & origin_path) .unwrap ( ) ;
7208
let origin_url = origin_path.to_string_lossy ( ) .to_string ( ) ;
7209
let a = Repo:: clone( & origin_url, & root.join ( " a" ) ) .unwrap ( ) ;
7210
let big: String = ( 0 .. 5000 ) .map ( | i | format! ( " line {i} \n " ) ) .collect ( ) ;
7211
write ( & root.join ( " a" ) , " big.txt" , big.as_bytes ( ) ) ;
7212
write ( & root.join ( " a" ) , " small.txt" , b " small\n " ) ;
7213
let h1 = a.commit ( " large" , & [ ] ) .unwrap ( ) ;
7214
let ( _ , body) = a.store ( ) .get ( & h1) .unwrap ( ) ;
7215
assert! ( body.len ( ) < 2048 , " the change is {} bytes" , body.len ( ) ) ;
7216
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7217
7218
7219
let b = Repo:: clone( & origin_url, & root.join ( " b" ) ) .unwrap ( ) ;
7220
assert_eq! ( fs:: read( root.join ( " b/big.txt" ) ) .unwrap ( ) , big.as_bytes ( ) ) ;
7221
7222
let edited = big.replace ( " line 2500\n " , " line 2500, edited\n " ) ;
7223
write ( & root.join ( " b" ) , " big.txt" , edited.as_bytes ( ) ) ;
7224
let h2 = b.commit ( " edit" , & [ ] ) .unwrap ( ) ;
7225
assert! ( b.store ( ) .get_change ( & h2) .unwrap ( ) .edits.len ( ) < = 2 ) ;
7226
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7227
assert! ( a.pull ( " origin" ) .unwrap ( ) .checked_out) ;
7228
assert_eq! ( fs:: read( root.join ( " a/big.txt" ) ) .unwrap ( ) , edited.as_bytes ( ) ) ;
7229
assert_eq! (
7230
a.fsck ( ) .unwrap ( ) .errors ( ) ,
7231
0 ,
7232
" {:?}" ,
7233
a.fsck ( ) .unwrap ( ) .problems
7234
) ;
7235
7236
7237
let mut a = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) ;
7238
a.config.set ( " core.op-retention" , " 0" ) .unwrap ( ) ;
7239
a.save_config ( ) .unwrap ( ) ;
7240
let a = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) ;
7241
a.gc ( true ) .unwrap ( ) ;
7242
let a = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) ;
7243
assert_eq! ( a.store ( ) .get_change ( & h1) .unwrap ( ) .message, " large" ) ;
7244
assert_eq! (
7245
a.fsck ( ) .unwrap ( ) .errors ( ) ,
7246
0 ,
7247
" {:?}" ,
7248
a.fsck ( ) .unwrap ( ) .problems
7249
) ;
7250
7251
let c = Repo:: clone( & root.join ( " a" ) .to_string_lossy ( ) , & root.join ( " c" ) ) .unwrap ( ) ;
7252
assert_eq! ( fs:: read( root.join ( " c/big.txt" ) ) .unwrap ( ) , edited.as_bytes ( ) ) ;
7253
drop ( c) ;
6caacfff Andy McCall 1 month ago ↶
7254
}
7255
7256
# [ test ]
7257
fn operations_that_both_replace_a_working_copy_are_not_merged ( ) {
7258
hermetic ( ) ;
7259
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7260
let root = dir.path ( ) ;
7261
let repo = Repo:: init( root) .unwrap ( ) ;
7262
write ( root, " a.txt" , b " a\n " ) ;
7263
let a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
7264
write ( root, " b.txt" , b " b\n " ) ;
7265
repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
7266
let ( x, op) = repo.head_op ( ) .unwrap ( ) ;
7267
let ws = * op.view.workspaces.keys ( ) .next ( ) .unwrap ( ) ;
7268
let mut one = op.view.clone ( ) ;
7269
one.workspaces.insert ( ws, a) ;
7270
let mut two = op.view.clone ( ) ;
7271
two.workspaces.insert ( ws, ObjectRef( [ 7 ; 32 ] ) ) ;
7272
let first = repo.record_op ( & [ x] , " one" , one) .unwrap ( ) ;
7273
let e = repo.record_op ( & [ x] , " two" , two) .unwrap_err ( ) ;
7274
assert! (
7275
e.to_string ( )
7276
.contains ( " changed this working copy at the same time" ) ,
7277
" {e}"
7278
) ;
7279
assert_eq! (
7280
repo.head_op ( ) .unwrap ( ) .0 ,
7281
first,
7282
" the second is not recorded"
7283
) ;
7284
}
7285
7286
7287
7288
7289
# [ test ]
7290
fn a_snapshot_during_a_switch_never_records_the_switch_as_edits ( ) {
7291
hermetic ( ) ;
7292
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7293
let root = dir.path ( ) .to_path_buf ( ) ;
7294
let mut repo = Repo:: init( & root) .unwrap ( ) ;
7295
write ( & root, " keep.txt" , b " keep\n " ) ;
7296
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7297
repo.bookmark_create ( " small" , None ) .unwrap ( ) ;
7298
for i in 0 .. 300 {
7299
write (
7300
& root,
7301
& format! ( " many/f{i} .txt" ) ,
7302
format! ( " file {i} \n " ) .as_bytes ( ) ,
7303
) ;
7304
}
7305
repo.commit ( " many" , & [ ] ) .unwrap ( ) ;
7306
let stop = std:: sync:: Arc:: new( std:: sync:: atomic:: AtomicBool:: new( false ) ) ;
7307
let watcher = {
7308
let ( root, stop) = ( root.clone ( ) , stop.clone ( ) ) ;
7309
std:: thread:: spawn( move | | {
7310
let r = Repo:: open( & root) .unwrap ( ) ;
7311
let mut ran = 0 ;
7312
while ! stop.load ( std:: sync:: atomic:: Ordering:: Relaxed) {
7313
if r.snapshot_status ( " oxy status" ) .is_ok ( ) {
7314
ran + = 1 ;
7315
}
7316
}
7317
ran
7318
} )
7319
} ;
7320
for i in 0 .. 20 {
7321
let to = if i % 2 = = 0 { " small" } else { " main" } ;
7322
repo.bookmark_switch ( to, false ) .unwrap ( ) ;
7323
}
7324
stop.store ( true , std:: sync:: atomic:: Ordering:: Relaxed) ;
7325
assert! ( watcher.join ( ) .unwrap ( ) > 0 , " the watcher snapshotted" ) ;
7326
7327
let repo = Repo:: open( & root) .unwrap ( ) ;
7328
let s = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7329
assert! ( s.statuses.is_empty ( ) , " {:?}" , statuses ( & s) ) ;
7330
assert! ( s.wc.edits.is_empty ( ) & & s.wc.tree_ops.is_empty ( ) ) ;
7331
assert_eq! ( s.wc.deps, repo.bookmark_heads ( ) .unwrap ( ) ) ;
7332
assert! ( root.join ( " many/f299.txt" ) .exists ( ) ) ;
1a3b6203 Andy McCall 1 month ago ↶
7333
}
7334
7335
7336
7337
7338
# [ test ]
7339
fn a_parent_switch_with_carry_leaves_a_subrepo_ahead_of_its_pin_alone ( ) {
7340
hermetic ( ) ;
7341
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7342
let root = dir.path ( ) ;
7343
Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
7344
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
7345
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) ) .unwrap ( ) ;
7346
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
7347
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
7348
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7349
7350
let app_dir = root.join ( " app" ) ;
7351
let mut app = Repo:: init( & app_dir) .unwrap ( ) ;
7352
write ( & app_dir, " main.txt" , b " app\n " ) ;
7353
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
7354
app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
7355
app.bookmark_create ( " feature" , None ) .unwrap ( ) ;
7356
7357
7358
let nested_dir = app_dir.join ( " libs/lib" ) ;
7359
let nested = Repo:: open( & nested_dir) .unwrap ( ) ;
7360
write ( & nested_dir, " new.txt" , b " new\n " ) ;
7361
let l2 = nested.commit ( " lib: new file" , & [ ] ) .unwrap ( ) ;
7362
drop ( nested) ;
7363
7364
let ( _ , carried) = app.bookmark_switch_carry ( " feature" ) .unwrap ( ) ;
7365
assert! ( carried.contains ( & " libs/lib" .to_string ( ) ) , " {carried:?}" ) ;
7366
let nested = Repo:: open( & nested_dir) .unwrap ( ) ;
7367
assert! (
7368
nested.bookmark_heads ( ) .unwrap ( ) .contains ( & l2) ,
7369
" its bookmark kept its work"
7370
) ;
7371
assert! ( nested_dir.join ( " new.txt" ) .exists ( ) , " and its files" ) ;
7372
let s = app.snapshot ( " status" ) .unwrap ( ) ;
7373
assert_eq! (
7374
statuses ( & s) ,
7375
vec! [ ( " libs/lib" .to_string ( ) , ChangeStatus:: Modified) ]
7376
) ;
6f8aeb99 Andy McCall 1 month ago ↶
7377
}
7d9dab13 Andy McCall 1 month ago ↶
7378
7379
7380
7381
7382
# [ test ]
7383
fn pull_takes_a_remote_move_to_changes_already_here ( ) {
7384
hermetic ( ) ;
7385
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7386
let root = dir.path ( ) ;
7387
let origin_path = root.join ( " origin" ) ;
7388
let origin = Repo:: init( & origin_path) .unwrap ( ) ;
7389
let url = origin_path.to_string_lossy ( ) .to_string ( ) ;
7390
let mut b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) ;
7391
write ( & root.join ( " b" ) , " base.txt" , b " base\n " ) ;
7392
b.commit ( " base" , & [ ] ) .unwrap ( ) ;
7393
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
7394
origin.refresh ( ) .unwrap ( ) ;
7d9dab13 Andy McCall 1 month ago ↶
7395
7396
7397
b.bookmark_create ( " feat" , None ) .unwrap ( ) ;
7398
b.bookmark_switch ( " feat" , false ) .unwrap ( ) ;
7399
write ( & root.join ( " b" ) , " x.txt" , b " x\n " ) ;
7400
let x = b.commit ( " x" , & [ ] ) .unwrap ( ) ;
7401
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 19 days ago
7402
origin.refresh ( ) .unwrap ( ) ;
7d9dab13 Andy McCall 1 month ago ↶
7403
b.bookmark_switch ( " main" , false ) .unwrap ( ) ;
7404
7405
7406
let feat = origin
7407
.store ( )
7408
.get_state ( & origin.view ( ) .unwrap ( ) .bookmarks[ " feat" ] )
7409
.unwrap ( )
7410
.heads;
7411
origin.bookmark_set ( " main" , & feat) .unwrap ( ) ;
7412
7413
let r = b.pull ( " origin" ) .unwrap ( ) ;
7414
assert! ( r.changed, " {r:?}" ) ;
7415
assert! ( r.heads.contains ( & x) | | b.load_state ( & r.heads) .unwrap ( ) .contains ( & x) ) ;
7416
let main = b
7417
.bookmarks ( )
7418
.unwrap ( )
7419
.into_iter ( )
7420
.find ( | m | m.name = = " main" )
7421
.unwrap ( ) ;
7422
assert_eq! ( main.behind, Some ( 0 ) , " {main:?}" ) ;
7423
}
86e1824f Andy McCall 1 month ago ↶
7424
14edbaaf Andy McCall 1 month ago ↶
7425
7426
7427
7428
7429
# [ test ]
7430
fn remote_bookmarks_are_named_switched_to_pulled_and_cloned ( ) {
7431
hermetic ( ) ;
7432
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7433
let root = dir.path ( ) ;
7434
let origin_path = root.join ( " origin" ) ;
7435
let mut origin = Repo:: init( & origin_path) .unwrap ( ) ;
7436
let url = origin_path.to_string_lossy ( ) .to_string ( ) ;
7437
let mut a = Repo:: clone( & url, & root.join ( " a" ) ) .unwrap ( ) ;
7438
write ( & root.join ( " a" ) , " base.txt" , b " base\n " ) ;
7439
a.commit ( " base" , & [ ] ) .unwrap ( ) ;
7440
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7441
a.bookmark_create ( " develop" , None ) .unwrap ( ) ;
7442
a.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
7443
write ( & root.join ( " a" ) , " base.txt" , b " base\n d1\n " ) ;
7444
let d1 = a.commit ( " d1" , & [ ] ) .unwrap ( ) ;
7445
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7446
7447
7448
let c_root = root.join ( " c" ) ;
7449
let mut c = Repo:: clone( & url, & c_root ) .unwrap ( ) ;
7450
assert_eq! ( c.config.bookmark, " main" ) ;
7451
assert! ( ! c.view ( ) .unwrap ( ) .bookmarks.contains_key ( " develop" ) ) ;
7452
assert_eq! ( c.resolve_heads ( " origin/develop" ) .unwrap ( ) , vec! [ d1] ) ;
7453
assert_eq! ( c.resolve_change ( " origin/develop" ) .unwrap ( ) , d1) ;
7454
assert_eq! ( c.remote_only_bookmarks ( ) .unwrap ( ) .len ( ) , 1 ) ;
7455
7456
7457
c.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
7458
assert_eq! ( c.config.bookmark, " develop" ) ;
7459
assert_eq! ( c.bookmark_heads ( ) .unwrap ( ) , vec! [ d1] ) ;
7460
assert_eq! ( fs:: read( c_root .join ( " base.txt" ) ) .unwrap ( ) , b " base\n d1\n " ) ;
7461
assert! ( c.remote_only_bookmarks ( ) .unwrap ( ) .is_empty ( ) ) ;
7462
assert! ( c.bookmark_switch ( " nowhere" , false ) .is_err ( ) ) ;
7463
7464
7465
write ( & root.join ( " a" ) , " base.txt" , b " base\n d1\n d2\n " ) ;
7466
let d2 = a.commit ( " d2" , & [ ] ) .unwrap ( ) ;
7467
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7468
a.bookmark_switch ( " main" , false ) .unwrap ( ) ;
7469
write ( & root.join ( " a" ) , " m.txt" , b " m\n " ) ;
7470
let m = a.commit ( " m" , & [ ] ) .unwrap ( ) ;
7471
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7472
let r = c.pull ( " origin" ) .unwrap ( ) ;
7473
assert! ( r.changed & & r.checked_out, " {r:?}" ) ;
7474
assert_eq! ( r.heads, vec! [ d2] ) ;
7475
assert_eq! ( r.others, vec! [ " main" .to_string ( ) ] ) ;
7476
assert! ( c.resolve_heads ( " main" ) .unwrap ( ) .contains ( & m) ) ;
7477
assert_eq! (
7478
fs:: read( c_root .join ( " base.txt" ) ) .unwrap ( ) ,
7479
b " base\n d1\n d2\n "
7480
) ;
7481
let again = c.pull ( " origin" ) .unwrap ( ) ;
7482
assert! ( ! again.changed & & again.others.is_empty ( ) , " {again:?}" ) ;
7483
7484
7485
origin.config.set ( " workspace.bookmark" , " develop" ) .unwrap ( ) ;
7486
origin.save_config ( ) .unwrap ( ) ;
7487
let e = Repo:: clone( & url, & root.join ( " e" ) ) .unwrap ( ) ;
7488
assert_eq! ( e.config.bookmark, " develop" ) ;
7489
assert_eq! ( e.bookmark_heads ( ) .unwrap ( ) , vec! [ d2] ) ;
7490
let reopened = Repo:: open( & root.join ( " e" ) ) .unwrap ( ) ;
7491
assert_eq! ( reopened.config.bookmark, " develop" ) ;
7492
7493
let mut e = reopened;
7494
e.bookmark_switch ( " origin/main" , false ) .unwrap ( ) ;
7495
assert_eq! ( e.config.bookmark, " main" ) ;
7496
assert! ( e.bookmark_heads ( ) .unwrap ( ) .contains ( & m) ) ;
7497
}
7498
98a1620a Andy McCall 1 month ago ↶
7499
7500
7501
7502
7503
# [ test ]
7504
fn operations_can_be_read_restored_and_reverted ( ) {
7505
hermetic ( ) ;
7506
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7507
let root = dir.path ( ) ;
7508
let repo = Repo:: init( root) .unwrap ( ) ;
7509
write ( root, " a.txt" , b " a\n " ) ;
7510
let first = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
7511
write ( root, " a.txt" , b " a\n b\n " ) ;
7512
let second = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
7513
let at_second = repo.head_op ( ) .unwrap ( ) .0. to_hex ( ) ;
7514
repo.bookmark_create ( " keep" , None ) .unwrap ( ) ;
7515
let created = repo.head_op ( ) .unwrap ( ) .0. to_hex ( ) ;
7516
write ( root, " n.txt" , b " n\n " ) ;
7517
let third = repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
7518
7519
7520
let ops = repo.op_log ( ) .unwrap ( ) ;
7521
let changes = repo.view_changes ( & ops[ 1 ] .1. view, & ops[ 0 ] .1. view) .unwrap ( ) ;
7522
assert! (
7523
changes
7524
.iter ( )
7525
.any ( | c | matches! ( c, ViewChange:: BookmarkMoved { name, .. } if name = = " main" ) ) ,
7526
" {changes:?}"
7527
) ;
7528
assert! ( changes
7529
.iter ( )
7530
.any ( | c | matches! ( c, ViewChange:: WorkingCopy { .. } ) ) ) ;
7531
7532
7533
let old = Repo:: open( root) .unwrap ( ) .at_op ( & at_second[ .. 10 ] ) .unwrap ( ) ;
7534
assert_eq! ( old.bookmark_heads ( ) .unwrap ( ) , vec! [ second] ) ;
7535
assert! ( ! old.view ( ) .unwrap ( ) .bookmarks.contains_key ( " keep" ) ) ;
7536
write ( root, " z.txt" , b " z\n " ) ;
7537
assert! ( old.commit ( " refused" , & [ ] ) .is_err ( ) , " --at-op is read-only" ) ;
7538
fs:: remove_file( root.join ( " z.txt" ) ) .unwrap ( ) ;
7539
assert! ( repo.resolve_op ( " zz" ) .is_err ( ) ) ;
7540
assert_eq! ( repo.resolve_op ( " @" ) .unwrap ( ) .0 , repo.head_op ( ) .unwrap ( ) .0 ) ;
7541
7542
7543
repo.op_restore ( & at_second, false ) .unwrap ( ) ;
7544
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ second] ) ;
7545
assert! ( ! root.join ( " n.txt" ) .exists ( ) ) ;
7546
repo.undo ( ) .unwrap ( ) ;
7547
let mut heads = repo.bookmark_heads ( ) .unwrap ( ) ;
7548
heads.sort ( ) ;
7549
let mut want = vec! [ second, third] ;
7550
want.sort ( ) ;
7551
assert_eq! ( heads, want) ;
7552
assert! (
7553
root.join ( " n.txt" ) .exists ( ) ,
7554
" the files came back with the undo"
7555
) ;
7556
7557
7558
let r = repo.op_revert ( & created) .unwrap ( ) ;
7559
assert_eq! ( r.reverted, vec! [ " keep" .to_string ( ) ] ) ;
7560
assert! ( ! repo.view ( ) .unwrap ( ) .bookmarks.contains_key ( " keep" ) ) ;
7561
assert! ( repo
7562
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
7563
.unwrap ( )
7564
.contains ( & third) ) ;
7565
assert! ( repo.op_revert ( & created) .is_err ( ) , " nothing left to revert" ) ;
7566
let _ = first;
7567
}
7568
36041796 Andy McCall 1 month ago ↶
7569
7570
7571
7572
# [ test ]
7573
fn status_with_uncommitted_work_comes_from_the_cache_until_a_file_changes ( ) {
7574
hermetic ( ) ;
7575
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7576
let root = dir.path ( ) ;
7577
let repo = Repo:: init( root) .unwrap ( ) ;
7578
write ( root, " a.txt" , b " a\n " ) ;
7579
write ( root, " b.txt" , b " b\n " ) ;
7580
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7581
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , Some ( vec! [ ] ) , " clean" ) ;
7582
7583
write ( root, " a.txt" , b " a\n more\n " ) ;
7584
assert_eq! (
7585
repo.status_by_manifest ( ) .unwrap ( ) ,
7586
None ,
7587
" no snapshot of the edit yet"
7588
) ;
7589
let snap = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7590
assert_eq! ( snap.statuses.len ( ) , 1 ) ;
7591
let cached = repo
7592
.status_by_manifest ( )
7593
.unwrap ( )
7594
.expect ( " answered from the cache" ) ;
7595
assert_eq! ( cached.len ( ) , 1 ) ;
7596
assert_eq! (
7597
( cached[ 0 ] .path.as_str ( ) , cached[ 0 ] .status) ,
7598
( " a.txt" , ChangeStatus:: Modified)
7599
) ;
7600
7601
7602
write ( root, " a.txt" , b " a\n more\n and more\n " ) ;
7603
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None , " edited again" ) ;
7604
repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7605
assert! ( repo.status_by_manifest ( ) .unwrap ( ) .is_some ( ) ) ;
7606
write ( root, " new.txt" , b " n\n " ) ;
7607
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None , " a file added" ) ;
7608
fs:: remove_file( root.join ( " new.txt" ) ) .unwrap ( ) ;
7609
assert! (
7610
repo.status_by_manifest ( ) .unwrap ( ) .is_some ( ) ,
7611
" back as it was"
7612
) ;
7613
fs:: remove_file( root.join ( " b.txt" ) ) .unwrap ( ) ;
7614
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None , " a file removed" ) ;
7615
write ( root, " b.txt" , b " b\n " ) ;
7616
7617
7618
repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7619
repo.commit ( " more" , & [ ] ) .unwrap ( ) ;
7620
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , Some ( vec! [ ] ) ) ;
7621
}
7622
77445673 Andy McCall 1 month ago ↶
7623
7624
7625
7626
# [ test ]
7627
fn the_working_copy_diff_from_the_cache_matches_a_snapshot ( ) {
7628
hermetic ( ) ;
7629
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7630
let root = dir.path ( ) ;
7631
let repo = Repo:: init( root) .unwrap ( ) ;
7632
write ( root, " a.txt" , b " one\n two\n three\n " ) ;
7633
write ( root, " gone.txt" , b " bye\n " ) ;
7634
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7635
write ( root, " a.txt" , b " one\n 2\n three\n four\n " ) ;
7636
write ( root, " new.txt" , b " hello\n " ) ;
7637
fs:: remove_file( root.join ( " gone.txt" ) ) .unwrap ( ) ;
7638
let snap = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7639
let slow = render_change ( & snap.wc, & snap.base, & snap.base_snapshot) ;
7640
assert! (
7641
repo.status_by_manifest ( ) .unwrap ( ) .is_some ( ) ,
7642
" the cache holds"
7643
) ;
7644
let ( wc, base, snapshot) = repo.working_copy_with_base ( ) .unwrap ( ) ;
7645
assert_eq! ( render_change ( & wc, & base, & snapshot) , slow) ;
7646
assert_eq! ( repo.diff_files ( None ) .unwrap ( ) .len ( ) , 3 ) ;
7647
}
7648
1ec3f008 Andy McCall 1 month ago ↶
7649
7650
7651
7652
7653
# [ test ]
7654
fn the_manifest_check_skips_listing_while_no_directory_changed ( ) {
7655
hermetic ( ) ;
7656
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7657
let root = dir.path ( ) ;
7658
let repo = Repo:: init( root) .unwrap ( ) ;
7659
write ( root, " a.txt" , b " a\n " ) ;
7660
write ( root, " sub/deep/b.txt" , b " b\n " ) ;
7661
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7662
7663
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 60 ) ;
7664
for p in [ " a.txt" , " sub/deep/b.txt" , " sub/deep" , " sub" , " " ] {
7665
fs:: File:: open( root.join ( p) )
7666
.unwrap ( )
7667
.set_modified ( old)
7668
.unwrap ( ) ;
7669
}
7670
repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7671
repo.write_manifest ( false , false ) .unwrap ( ) ;
7672
let m = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) .2 ;
7673
assert! ( m.dirs.iter ( ) .any ( | ( d , _) | d = = " sub/deep" ) , " {:?}" , m.dirs) ;
7674
assert_eq! (
7675
repo.disk_differs_by_dirs ( & m) ,
7676
Some ( false ) ,
7677
" no listing needed"
7678
) ;
7679
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , Some ( vec! [ ] ) ) ;
7680
7681
7682
write ( root, " sub/deep/b.txt" , b " c\n " ) ;
7683
assert_eq! ( repo.disk_differs_by_dirs ( & m) , Some ( true ) ) ;
7684
write ( root, " sub/deep/b.txt" , b " b\n " ) ;
7685
fs:: File:: open( root.join ( " sub/deep/b.txt" ) )
7686
.unwrap ( )
7687
.set_modified ( old)
7688
.unwrap ( ) ;
7689
assert_eq! ( repo.disk_differs_by_dirs ( & m) , Some ( false ) ) ;
7690
7691
7692
write ( root, " sub/deep/new.txt" , b " n\n " ) ;
7693
assert_eq! ( repo.disk_differs_by_dirs ( & m) , None ) ;
7694
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None ) ;
7695
fs:: remove_file( root.join ( " sub/deep/new.txt" ) ) .unwrap ( ) ;
7696
7697
fs:: remove_file( root.join ( " a.txt" ) ) .unwrap ( ) ;
7698
assert_eq! ( repo.disk_differs_by_dirs ( & m) , None ) ;
7699
}
7700
e226a6cc Andy McCall 1 month ago ↶
7701
7702
7703
7704
# [ test ]
7705
fn a_commit_after_a_status_uses_the_cached_snapshot ( ) {
7706
hermetic ( ) ;
7707
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7708
let root = dir.path ( ) ;
7709
let repo = Repo:: init( root) .unwrap ( ) ;
7710
write ( root, " a.txt" , b " a\n b\n " ) ;
7711
write ( root, " .oxyattributes" , b " *.bin binary\n " ) ;
7712
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7713
write ( root, " a.txt" , b " a\n B\n " ) ;
7714
write ( root, " new.txt" , b " new\n " ) ;
7715
let snap = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7716
let cached = repo
7717
.snapshot_from_cache ( )
7718
.unwrap ( )
7719
.expect ( " the cache holds" ) ;
7720
assert_eq! ( cached.wc_hash, snap.wc_hash) ;
7721
assert_eq! ( cached.statuses, snap.statuses) ;
7722
assert! (
7723
! cached.attributes.is_empty ( ) ,
7724
" the .oxyattributes file was read"
7725
) ;
7726
let h = repo.commit ( " edit" , & [ ] ) .unwrap ( ) ;
7727
let c = repo.store ( ) .get_change ( & h) .unwrap ( ) ;
7728
assert_eq! (
7729
c.tree_ops.len ( ) + c.edits.len ( ) ,
7730
snap.wc.tree_ops.len ( ) + snap.wc.edits.len ( )
7731
) ;
7732
assert_eq! (
7733
repo.status_by_manifest ( ) .unwrap ( ) ,
7734
Some ( vec! [ ] ) ,
7735
" clean after the commit"
7736
) ;
7737
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " a\n B\n " ) ;
7738
}
7739
0389c3e8 Andy McCall 1 month ago ↶
7740
7741
7742
7743
7744
# [ test ]
7745
fn a_verified_clean_check_restamps_the_manifest_safely ( ) {
7746
hermetic ( ) ;
7747
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7748
let root = dir.path ( ) ;
7749
let repo = Repo:: init( root) .unwrap ( ) ;
7750
write ( root, " a.txt" , b " one\n " ) ;
7751
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
7752
let before = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) .2. written_ns;
7753
assert! ( repo.clean_by_manifest ( ) .unwrap ( ) ) ;
7754
let after = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) .2. written_ns;
7755
assert! ( after > before, " restamped" ) ;
7756
write ( root, " a.txt" , b " two\n " ) ;
7757
assert! (
7758
! repo.clean_by_manifest ( ) .unwrap ( ) ,
7759
" a same-size rewrite is still seen"
7760
) ;
7761
}
7762
86e1824f Andy McCall 1 month ago ↶
7763
7764
7765
7766
# [ test ]
7767
fn history_only_states_read_text_only_for_the_graphs_they_build ( ) {
7768
hermetic ( ) ;
7769
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7770
let root = dir.path ( ) ;
7771
let repo = Repo:: init( root) .unwrap ( ) ;
7772
let big: String = ( 0 .. 3000 ) .map ( | i | format! ( " big line {i} \n " ) ) .collect ( ) ;
7773
let other: String = ( 0 .. 3000 ) .map ( | i | format! ( " other line {i} \n " ) ) .collect ( ) ;
7774
write ( root, " big.txt" , big.as_bytes ( ) ) ;
7775
write ( root, " other.txt" , other.as_bytes ( ) ) ;
7776
let h = repo.commit ( " two large files" , & [ ] ) .unwrap ( ) ;
7777
write ( root, " big.txt" , format! ( " {big} one more\n " ) .as_bytes ( ) ) ;
7778
let h2 = repo.commit ( " append" , & [ ] ) .unwrap ( ) ;
7779
7780
let full = repo.load_state_full ( & [ h2] ) .unwrap ( ) .materialize ( ) ;
7781
let text = | snap : & crate:: state:: Snapshot, p : & str | match & snap.files[ p] .content {
7782
FileContent:: Text { bytes, .. } => bytes.clone ( ) ,
7783
other => panic! ( " {other:?}" ) ,
7784
} ;
7785
7786
let mut hist = repo.load_state_history ( & [ h2] ) .unwrap ( ) ;
7787
let big_node = full.files[ " big.txt" ] .node;
7788
assert! ( hist.graph_key ( & big_node) .is_some ( ) , " not built yet" ) ;
7789
repo.load_all_graphs ( & mut hist) .unwrap ( ) ;
7790
let rendered = hist.materialize ( ) ;
7791
for p in [ " big.txt" , " other.txt" ] {
7792
assert_eq! ( text ( & rendered, p) , text ( & full, p) , " {p}" ) ;
7793
}
7794
7795
7796
7797
let ( _ , texts) = repo.store ( ) .get_change_lazy ( & h) .unwrap ( ) ;
7798
assert_eq! ( texts.len ( ) , 2 ) ;
7799
let content = | r : & ObjectRef| -> Vec< u8 > {
7800
let blob = crate :: objects:: Blob:: decode( & repo.store ( ) .get ( r) .unwrap ( ) .1 ) .unwrap ( ) ;
7801
blob.chunks
7802
.iter ( )
7803
.flat_map ( | c | repo.store ( ) .get ( c) .unwrap ( ) .1 )
7804
.collect ( )
7805
} ;
7806
let other_text = texts
7807
.iter ( )
a4fad5b0 Andy McCall 1 month ago ↶
7808
.filter_map ( | t | match t {
0f93f49c Andy McCall 1 month ago ↶
7809
crate :: objects:: TextRef:: Blob( r, _ , _ ) => Some ( * r) ,
a4fad5b0 Andy McCall 1 month ago ↶
7810
_ => None ,
7811
} )
86e1824f Andy McCall 1 month ago ↶
7812
.find ( | r | content ( r) .windows ( 12 ) .any ( | w | w = = b " other line 1" ) )
7813
.unwrap ( ) ;
64818938 Andy McCall 1 month ago ↶
7814
repo.store ( ) .remove_loose_for_test ( & other_text) .unwrap ( ) ;
86e1824f Andy McCall 1 month ago ↶
7815
let mut hist = repo.load_state_history ( & [ h2] ) .unwrap ( ) ;
7816
repo.load_graphs_of ( & mut hist, & [ big_node] ) .unwrap ( ) ;
7817
assert! ( repo
7818
.load_graphs_of ( & mut hist, & [ full.files[ " other.txt" ] .node] )
7819
.is_err ( ) ) ;
7820
}
7821
7822
7823
7824
7825
# [ test ]
7826
fn a_commit_leaves_a_manifest_the_next_status_trusts ( ) {
7827
use std:: os:: unix:: fs:: PermissionsExt;
7828
hermetic ( ) ;
7829
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7830
let root = dir.path ( ) ;
7831
let repo = Repo:: init( root) .unwrap ( ) ;
7832
let files = [ " a.txt" , " b.txt" , " d/c.txt" ] ;
7833
for p in files {
7834
write ( root, p, format! ( " {p} \n " ) .as_bytes ( ) ) ;
7835
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
7836
fs:: File:: options( )
7837
.write ( true )
7838
.open ( root.join ( p) )
7839
.unwrap ( )
7840
.set_modified ( old)
7841
.unwrap ( ) ;
7842
}
7843
repo.commit ( " added" , & [ ] ) .unwrap ( ) ;
7844
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
7845
assert! (
7846
m.entries.values ( ) .all ( | e | e.graph.is_some ( ) ) ,
7847
" keys recorded at commit"
7848
) ;
7849
7850
for p in files {
7851
fs:: set_permissions( root.join ( p) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
7852
}
7853
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
7854
for p in files {
7855
fs:: set_permissions( root.join ( p) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
7856
}
7857
assert! ( ! snap.changed & & snap.statuses.is_empty ( ) ) ;
7858
assert_eq! ( snap.base_snapshot.unrendered.len ( ) , files.len ( ) ) ;
7859
}
c5ce0367 Andy McCall 1 month ago ↶
7860
7861
7862
7863
# [ test ]
7864
fn checkout_touches_only_what_differs_and_renders_in_batches ( ) {
7865
use std:: os:: unix:: fs:: PermissionsExt;
7866
hermetic ( ) ;
7867
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7868
let root = dir.path ( ) ;
7869
let origin = root.join ( " origin" ) ;
7870
let mut repo = Repo:: init( & origin) .unwrap ( ) ;
7871
7872
let n = 2100 ;
7873
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
7874
for i in 0 .. n {
7875
let p = format! ( " f/{i:04} .txt" ) ;
7876
write ( & origin, & p, format! ( " file {i} \n " ) .as_bytes ( ) ) ;
7877
fs:: File:: options( )
7878
.write ( true )
7879
.open ( origin.join ( & p) )
7880
.unwrap ( )
7881
.set_modified ( old)
7882
.unwrap ( ) ;
7883
}
7884
repo.commit ( " many" , & [ ] ) .unwrap ( ) ;
7885
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
7886
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
7887
write ( & origin, " f/0007.txt" , b " changed\n " ) ;
7888
repo.commit ( " one change" , & [ ] ) .unwrap ( ) ;
7889
7890
7891
for i in 0 .. n {
7892
if i ! = 7 {
7893
let p = origin.join ( format! ( " f/{i:04} .txt" ) ) ;
7894
fs:: set_permissions( & p, fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
7895
}
7896
}
7897
let r = repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
7898
for i in 0 .. n {
7899
let p = origin.join ( format! ( " f/{i:04} .txt" ) ) ;
7900
fs:: set_permissions( & p, fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
7901
}
7902
assert_eq! ( ( r.written, r.removed) , ( 1 , 0 ) , " {r:?}" ) ;
7903
assert_eq! ( fs:: read( origin.join ( " f/0007.txt" ) ) .unwrap ( ) , b " file 7\n " ) ;
7904
assert! ( repo.snapshot_status ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
7905
7906
7907
let c = root.join ( " clone" ) ;
7908
Repo:: clone( & origin.to_string_lossy ( ) , & c) .unwrap ( ) ;
7909
for i in [ 0 , 2047 , 2048 , n - 1 ] {
7910
assert_eq! (
7911
fs:: read( c.join ( format! ( " f/{i:04} .txt" ) ) ) .unwrap ( ) ,
7912
format! ( " file {i} \n " ) .as_bytes ( )
7913
) ;
7914
}
7915
let cloned = Repo:: open( & c) .unwrap ( ) ;
7916
assert! ( cloned
7917
.snapshot_status ( " status" )
7918
.unwrap ( )
7919
.statuses
7920
.is_empty ( ) ) ;
d3cd7ef9 Andy McCall 1 month ago ↶
7921
}
7922
7923
7924
7925
7926
# [ test ]
7927
fn a_racy_manifest_entry_is_checked_by_hash ( ) {
7928
hermetic ( ) ;
7929
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7930
let root = dir.path ( ) ;
7931
let repo = Repo:: init( root) .unwrap ( ) ;
7932
write ( root, " a.txt" , b " one\n " ) ;
7933
repo.commit ( " fresh" , & [ ] ) .unwrap ( ) ;
7934
assert! ( repo.clean_by_manifest ( ) .unwrap ( ) , " fresh but equal" ) ;
7935
let t = fs:: metadata( root.join ( " a.txt" ) )
7936
.unwrap ( )
7937
.modified ( )
7938
.unwrap ( ) ;
7939
write ( root, " a.txt" , b " two\n " ) ;
7940
fs:: File:: options( )
7941
.write ( true )
7942
.open ( root.join ( " a.txt" ) )
7943
.unwrap ( )
7944
.set_modified ( t)
7945
.unwrap ( ) ;
7946
assert! (
7947
! repo.clean_by_manifest ( ) .unwrap ( ) ,
7948
" same size and time, other bytes"
7949
) ;
7950
assert_eq! (
7951
statuses ( & repo.snapshot_status ( " status" ) .unwrap ( ) ) ,
7952
vec! [ ( " a.txt" .to_string ( ) , ChangeStatus:: Modified) ]
7953
) ;
c5ce0367 Andy McCall 1 month ago ↶
7954
}
2c75672f Andy McCall 1 month ago ↶
7955
7956
7957
7958
# [ test ]
7959
fn a_raw_bundle_without_a_changes_text_is_refused ( ) {
7960
hermetic ( ) ;
7961
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7962
let root = dir.path ( ) ;
7963
let a = Repo:: init( & root.join ( " a" ) ) .unwrap ( ) ;
7964
let big: String = ( 0 .. 3000 ) .map ( | i | format! ( " line {i} \n " ) ) .collect ( ) ;
7965
write ( & root.join ( " a" ) , " big.txt" , big.as_bytes ( ) ) ;
7966
let h = a.commit ( " big" , & [ ] ) .unwrap ( ) ;
7967
let bundle = a.bundle_raw_for ( & [ h] , None ) .unwrap ( ) ;
7968
assert_eq! ( bundle.raw_changes.len ( ) , 1 ) ;
7969
assert! ( bundle.changes.is_empty ( ) ) ;
7970
let b = Repo:: init( & root.join ( " b" ) ) .unwrap ( ) ;
7971
let mut without = bundle.clone ( ) ;
7972
without.objects.retain ( | ( ty , _) | * ty ! = ObjectType:: Blob) ;
7973
let e = b.import_bundle ( & without, false ) .unwrap_err ( ) ;
7974
assert! ( e.to_string ( ) .contains ( " came without its text blob" ) , " {e}" ) ;
7975
assert! ( ! b.store ( ) .has ( & h) ) ;
7976
assert_eq! ( b.import_bundle ( & bundle, false ) .unwrap ( ) , vec! [ h] ) ;
7977
assert_eq! ( b.store ( ) .get_change ( & h) .unwrap ( ) .message, " big" ) ;
7978
}
a87c7b9d Andy McCall 1 month ago ↶
7979
7980
7981
7982
7983
7984
# [ test ]
7985
fn git_import_brings_a_directory_back_rather_than_making_a_second ( ) {
7986
hermetic ( ) ;
7987
if std:: process:: Command:: new( " git" )
7988
.arg ( " --version" )
7989
.output ( )
7990
.is_err ( )
7991
{
7992
eprintln! ( " git not installed; skipping" ) ;
7993
return ;
7994
}
7995
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7996
let root = dir.path ( ) ;
7997
let g = root.join ( " src.git" ) ;
7998
fs:: create_dir_all( & g) .unwrap ( ) ;
7999
let git = | args : & [& str ]| -> String {
8000
let out = std:: process:: Command:: new( " git" )
8001
.arg ( " -C" )
8002
.arg ( & g)
8003
.args ( [
8004
" -c" ,
8005
" user.name=Grace" ,
8006
" -c" ,
8007
" [email protected] " ,
8008
" -c" ,
8009
" commit.gpgsign=false" ,
8010
] )
8011
.args ( args)
8012
.output ( )
8013
.unwrap ( ) ;
8014
assert! (
8015
out.status.success ( ) ,
8016
" git {args:?}: {}" ,
8017
String :: from_utf8_lossy( & out.stderr)
8018
) ;
8019
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
8020
} ;
8021
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
8022
write ( & g, " d/a.txt" , b " a\n " ) ;
8023
write ( & g, " c/x.txt" , b " x\n " ) ;
8024
git ( & [ " add" , " ." ] ) ;
8025
git ( & [ " commit" , " -qm" , " base" ] ) ;
8026
8027
git ( & [ " checkout" , " -qb" , " side" ] ) ;
8028
git ( & [ " mv" , " c/x.txt" , " d/x.txt" ] ) ;
8029
git ( & [ " commit" , " -qm" , " x into d" ] ) ;
8030
8031
git ( & [ " checkout" , " -q" , " main" ] ) ;
8032
git ( & [ " rm" , " -q" , " d/a.txt" ] ) ;
8033
git ( & [ " commit" , " -qm" , " d goes" ] ) ;
8034
write ( & g, " d/b.txt" , b " b\n " ) ;
8035
git ( & [ " add" , " ." ] ) ;
8036
git ( & [ " commit" , " -qm" , " d comes back" ] ) ;
8037
git ( & [ " mv" , " c/x.txt" , " d/x.txt" ] ) ;
8038
git ( & [ " commit" , " -qm" , " x into d again" ] ) ;
8039
git ( & [ " merge" , " -q" , " --no-edit" , " side" ] ) ;
8040
8041
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
8042
repo.import_git ( & g, " main" ) .unwrap ( ) ;
8043
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
8044
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
8045
let st = repo
8046
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
8047
.unwrap ( ) ;
8048
let view = st.tree ( ) .view ( ) ;
8049
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
8050
8051
8052
let first = repo
8053
.log ( )
8054
.unwrap ( )
8055
.into_iter ( )
8056
.find ( | e | e.change.message.starts_with ( " base" ) )
8057
.unwrap ( ) ;
8058
let base = repo
8059
.load_state_history ( & [ first.hash] )
8060
.unwrap ( )
8061
.tree ( )
8062
.view ( ) ;
8063
assert_eq! ( view.entries[ " d" ] .node, base.entries[ " d" ] .node) ;
8064
}
8065
8066
8067
8068
8069
# [ test ]
8070
fn git_import_merge_retires_a_placement_under_a_removed_directory ( ) {
8071
hermetic ( ) ;
8072
if std:: process:: Command:: new( " git" )
8073
.arg ( " --version" )
8074
.output ( )
8075
.is_err ( )
8076
{
8077
eprintln! ( " git not installed; skipping" ) ;
8078
return ;
8079
}
8080
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8081
let root = dir.path ( ) ;
8082
let g = root.join ( " src.git" ) ;
8083
fs:: create_dir_all( & g) .unwrap ( ) ;
8084
let run = | args : & [& str ]| {
8085
std:: process:: Command:: new( " git" )
8086
.arg ( " -C" )
8087
.arg ( & g)
8088
.args ( [
8089
" -c" ,
8090
" user.name=Grace" ,
8091
" -c" ,
8092
" [email protected] " ,
8093
" -c" ,
8094
" commit.gpgsign=false" ,
8095
] )
8096
.args ( args)
8097
.output ( )
8098
.unwrap ( )
8099
} ;
8100
let git = | args : & [& str ]| {
8101
let out = run ( args) ;
8102
assert! (
8103
out.status.success ( ) ,
8104
" git {args:?}: {}" ,
8105
String :: from_utf8_lossy( & out.stderr)
8106
) ;
8107
} ;
8108
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
8109
write ( & g, " c/x.txt" , b " x\n " ) ;
8110
write ( & g, " keep.txt" , b " k\n " ) ;
8111
git ( & [ " add" , " ." ] ) ;
8112
git ( & [ " commit" , " -qm" , " base" ] ) ;
8113
8114
8115
git ( & [ " checkout" , " -qb" , " side" ] ) ;
8116
git ( & [ " mv" , " c/x.txt" , " c/y.txt" ] ) ;
8117
git ( & [ " commit" , " -qm" , " rename within c" ] ) ;
8118
git ( & [ " checkout" , " -q" , " main" ] ) ;
8119
fs:: create_dir_all( g.join ( " n" ) ) .unwrap ( ) ;
8120
git ( & [ " mv" , " c/x.txt" , " n/x.txt" ] ) ;
8121
git ( & [ " commit" , " -qm" , " move out of c" ] ) ;
8122
8123
let _ = run ( & [ " merge" , " -q" , " side" ] ) ;
8124
let _ = run ( & [
8125
" rm" ,
8126
" -q" ,
8127
" --cached" ,
8128
" --ignore-unmatch" ,
8129
" c/y.txt" ,
8130
" c/x.txt" ,
8131
] ) ;
8132
let _ = std:: fs:: remove_dir_all( g.join ( " c" ) ) ;
8133
write ( & g, " n/x.txt" , b " x\n " ) ;
8134
git ( & [ " add" , " -A" ] ) ;
8135
git ( & [ " commit" , " -qm" , " merge: keep n/x.txt" ] ) ;
8136
8137
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
8138
repo.import_git ( & g, " main" ) .unwrap ( ) ;
8139
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
8140
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
8141
let st = repo
8142
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
8143
.unwrap ( ) ;
8144
let view = st.tree ( ) .view ( ) ;
8145
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
8146
assert! ( view.entries.contains_key ( " n/x.txt" ) ) ;
8147
}
8148
8149
8150
8151
8152
# [ test ]
8153
fn git_import_merge_leaves_no_orphan_behind ( ) {
8154
hermetic ( ) ;
8155
if std:: process:: Command:: new( " git" )
8156
.arg ( " --version" )
8157
.output ( )
8158
.is_err ( )
8159
{
8160
eprintln! ( " git not installed; skipping" ) ;
8161
return ;
8162
}
8163
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8164
let root = dir.path ( ) ;
8165
let g = root.join ( " src.git" ) ;
8166
fs:: create_dir_all( & g) .unwrap ( ) ;
8167
let git = | args : & [& str ]| {
8168
let out = std:: process:: Command:: new( " git" )
8169
.arg ( " -C" )
8170
.arg ( & g)
8171
.args ( [
8172
" -c" ,
8173
" user.name=Grace" ,
8174
" -c" ,
8175
" [email protected] " ,
8176
" -c" ,
8177
" commit.gpgsign=false" ,
8178
] )
8179
.args ( args)
8180
.output ( )
8181
.unwrap ( ) ;
8182
assert! (
8183
out.status.success ( ) ,
8184
" git {args:?}: {}" ,
8185
String :: from_utf8_lossy( & out.stderr)
8186
) ;
8187
} ;
8188
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
8189
write ( & g, " d/f.txt" , b " f\n " ) ;
8190
write ( & g, " keep.txt" , b " k\n " ) ;
8191
git ( & [ " add" , " ." ] ) ;
8192
git ( & [ " commit" , " -qm" , " base" ] ) ;
8193
git ( & [ " checkout" , " -qb" , " side" ] ) ;
8194
write ( & g, " d/new.txt" , b " new\n " ) ;
8195
git ( & [ " add" , " ." ] ) ;
8196
git ( & [ " commit" , " -qm" , " add into d" ] ) ;
8197
git ( & [ " checkout" , " -q" , " main" ] ) ;
8198
git ( & [ " rm" , " -q" , " d/f.txt" ] ) ;
8199
git ( & [ " commit" , " -qm" , " d goes" ] ) ;
8200
git ( & [ " merge" , " -q" , " --no-edit" , " side" ] ) ;
8201
8202
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
8203
repo.import_git ( & g, " main" ) .unwrap ( ) ;
8204
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
8205
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
8206
let st = repo
8207
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
8208
.unwrap ( ) ;
8209
let view = st.tree ( ) .view ( ) ;
8210
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
8211
assert! ( view.entries.contains_key ( " d/new.txt" ) ) ;
8212
}
d77e1702 Andy McCall 1 month ago ↶
8213
8214
8215
8216
# [ test ]
8217
fn fsck_names_a_missing_text_blob ( ) {
8218
hermetic ( ) ;
8219
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8220
let root = dir.path ( ) ;
8221
let repo = Repo:: init( root) .unwrap ( ) ;
8222
let big: String = ( 0 .. 3000 ) .map ( | i | format! ( " line {i} \n " ) ) .collect ( ) ;
8223
write ( root, " big.txt" , big.as_bytes ( ) ) ;
8224
let h = repo.commit ( " big" , & [ ] ) .unwrap ( ) ;
8225
assert_eq! ( repo.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
8226
let ( _ , texts) = repo.store ( ) .get_change_lazy ( & h) .unwrap ( ) ;
8227
let Some ( crate :: objects:: TextRef:: Blob( b, _ , _ ) ) = texts.first ( ) .copied ( ) else {
8228
panic! ( " a stored text" )
8229
} ;
64818938 Andy McCall 1 month ago ↶
8230
repo.store ( ) .remove_loose_for_test ( & b) .unwrap ( ) ;
d77e1702 Andy McCall 1 month ago ↶
8231
let rep = repo.fsck ( ) .unwrap ( ) ;
8232
assert! ( rep.errors ( ) > 0 ) ;
8233
assert! (
8234
rep.problems.iter ( ) .any ( | p | p.what.contains ( & h.short ( 12 ) ) ) ,
8235
" {:?}" ,
8236
rep.problems
8237
) ;
8238
}
a07f60a3 Andy McCall 1 month ago ↶
8239
8240
8241
8242
8243
# [ test ]
8244
fn a_subrepo_on_the_parents_hub_borrows_its_credentials ( ) {
8245
hermetic ( ) ;
8246
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8247
let mut repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
8248
repo.config
8249
.set (
8250
" remote.origin.url" ,
8251
" https://Hub.example:7343/doomsdayone/metalgear" ,
8252
)
8253
.unwrap ( ) ;
8254
repo.config.set ( " remote.origin.token" , " secret" ) .unwrap ( ) ;
8255
repo.config
8256
.set ( " remote.origin.ca" , " /tmp/hub-ca.pem" )
8257
.unwrap ( ) ;
8258
let same = repo.credentials_for ( " https://hub.example:7343/doomsdayone/rstar-api" ) ;
8259
assert_eq! ( same.token.as_deref ( ) , Some ( " secret" ) ) ;
8260
assert_eq! ( same.ca.as_deref ( ) , Some ( Path:: new( " /tmp/hub-ca.pem" ) ) ) ;
8261
for other in [
8262
" https://elsewhere.example:7343/doomsdayone/rstar-api" ,
8263
" https://hub.example/doomsdayone/rstar-api" ,
8264
" http://hub.example:7343/doomsdayone/rstar-api" ,
8265
] {
8266
let o = repo.credentials_for ( other) ;
8267
assert_ne! ( o.token.as_deref ( ) , Some ( " secret" ) , " {other}" ) ;
8268
}
8269
}
ce14f0d6 Andy McCall 1 month ago ↶
8270
ff1f62fe Andy McCall 1 month ago ↶
8271
8272
8273
ce14f0d6 Andy McCall 1 month ago ↶
8274
# [ test ]
ff1f62fe Andy McCall 1 month ago ↶
8275
fn remotes_use_the_credential_store_and_the_enclosing_checkout ( ) {
8276
hermetic ( ) ;
8277
let home = tempfile:: tempdir( ) .unwrap ( ) ;
8278
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8279
let mut parent = Repo:: init( dir.path ( ) ) .unwrap ( ) .with_home ( home.path ( ) ) ;
8280
parent
8281
.config
8282
.set ( " remote.origin.url" , " https://hub.example/o/parent" )
8283
.unwrap ( ) ;
8284
parent.save_config ( ) .unwrap ( ) ;
8285
assert_eq! ( parent.remote_options ( " origin" ) .token, None ) ;
8286
crate :: credentials:: Credentials:: at( home.path ( ) )
8287
.save ( " https://hub.example" , " stored" , Some ( " andy" ) , None )
8288
.unwrap ( ) ;
8289
assert_eq! (
8290
parent.remote_options ( " origin" ) .token.as_deref ( ) ,
8291
Some ( " stored" )
8292
) ;
8293
8294
let empty_home = tempfile:: tempdir( ) .unwrap ( ) ;
8295
let nested = dir.path ( ) .join ( " deps" ) .join ( " child" ) ;
8296
let mut child = Repo:: init( & nested) .unwrap ( ) .with_home ( empty_home.path ( ) ) ;
8297
child
8298
.config
8299
.set ( " remote.origin.url" , " https://hub.example/o/child" )
8300
.unwrap ( ) ;
8301
child.save_config ( ) .unwrap ( ) ;
8302
assert_eq! ( child.remote_options ( " origin" ) .token, None ) ;
8303
parent
8304
.config
8305
.set ( " remote.origin.token" , " checkout" )
8306
.unwrap ( ) ;
8307
parent.save_config ( ) .unwrap ( ) ;
8308
assert_eq! (
8309
child.remote_options ( " origin" ) .token.as_deref ( ) ,
8310
Some ( " checkout" )
8311
) ;
8312
8313
child
8314
.config
8315
.set ( " remote.origin.url" , " https://other.example/o/child" )
8316
.unwrap ( ) ;
8317
assert_eq! ( child.remote_options ( " origin" ) .token, None ) ;
8318
}
8319
8320
# [ test ]
ce14f0d6 Andy McCall 1 month ago ↶
8321
fn the_metadata_cache_survives_a_torn_tail ( ) {
8322
hermetic ( ) ;
8323
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8324
let root = dir.path ( ) ;
8325
let repo = Repo:: init( root) .unwrap ( ) ;
8326
write ( root, " a.txt" , b " 1\n " ) ;
8327
let h = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8328
let meta = repo.store ( ) .get_change_meta ( & h) .unwrap ( ) ;
8329
let cache = repo.store ( ) .dir ( ) .join ( " metas" ) ;
8330
let len = fs:: metadata( & cache) .unwrap ( ) .len ( ) ;
8331
assert! ( len > 0 , " a change read is cached" ) ;
8332
8333
fs:: OpenOptions:: new( )
8334
.append ( true )
8335
.open ( & cache)
8336
.unwrap ( )
8337
.write_all ( & [ 7 ; 20 ] )
8338
.unwrap ( ) ;
8339
let again = Repo:: open( root) .unwrap ( ) ;
8340
assert_eq! ( again.store ( ) .get_change_meta ( & h) .unwrap ( ) , meta) ;
8341
assert_eq! ( fs:: metadata( & cache) .unwrap ( ) .len ( ) , len) ;
8342
}
f9466021 Andy McCall 1 month ago ↶
8343
8344
# [ test ]
8345
fn a_manifest_that_does_not_check_reads_as_none ( ) {
8346
hermetic ( ) ;
8347
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8348
let root = dir.path ( ) ;
8349
let repo = Repo:: init( root) .unwrap ( ) ;
8350
write ( root, " a.txt" , b " 1\n " ) ;
8351
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8352
let path = repo.manifest_path ( ) ;
8353
let mut bytes = fs:: read( & path) .unwrap ( ) ;
8354
assert! ( Repo:: open( root) .unwrap ( ) .read_manifest ( ) .unwrap ( ) .is_some ( ) ) ;
8355
let last = bytes.len ( ) - 1 ;
8356
bytes[ last] ^ = 1 ;
8357
fs:: write( & path, & bytes) .unwrap ( ) ;
8358
assert! ( Repo:: open( root) .unwrap ( ) .read_manifest ( ) .unwrap ( ) .is_none ( ) ) ;
8359
8360
let again = Repo:: open( root) .unwrap ( ) ;
8361
assert! ( ! again.snapshot ( " status" ) .unwrap ( ) .changed) ;
8362
assert! ( again.read_manifest ( ) .unwrap ( ) .is_some ( ) ) ;
8363
}
8364
8365
# [ test ]
8366
fn a_commit_leaves_a_manifest_that_proves_the_working_copy_clean ( ) {
8367
hermetic ( ) ;
8368
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8369
let root = dir.path ( ) ;
8370
let repo = Repo:: init( root) .unwrap ( ) ;
8371
write ( root, " a.txt" , b " 1\n " ) ;
8372
write ( root, " b.txt" , b " 2\n " ) ;
8373
8374
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 60 ) ;
8375
fs:: File:: options( )
8376
.write ( true )
8377
.open ( root.join ( " b.txt" ) )
8378
.unwrap ( )
8379
.set_modified ( old)
8380
.unwrap ( ) ;
8381
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8382
assert! ( repo.clean_by_manifest ( ) .unwrap ( ) , " after the first commit" ) ;
8383
repo.snapshot_status ( " status" ) .unwrap ( ) ;
8384
write ( root, " a.txt" , b " 1\n 2\n " ) ;
8385
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
8386
assert! (
8387
repo.clean_by_manifest ( ) .unwrap ( ) ,
8388
" after a commit of one file"
8389
) ;
8390
8391
8392
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
8393
repo.snapshot_status ( " status" ) .unwrap ( ) ;
8394
write ( root, " a.txt" , b " 1\n 2\n 3\n 4\n 5\n " ) ;
8395
repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
8396
assert! (
8397
repo.clean_by_manifest ( ) .unwrap ( ) ,
8398
" after an edit a status saw"
8399
) ;
8400
}
e266b7d4 Andy McCall 1 month ago ↶
8401
8402
# [ test ]
8403
fn a_conflicted_rebase_can_be_resolved_in_the_working_copy ( ) {
8404
hermetic ( ) ;
8405
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8406
let root = dir.path ( ) ;
8407
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8408
write ( root, " a.txt" , b " 1\n 2\n " ) ;
8409
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
8410
repo.bookmark_create ( " develop" , Some ( & [ a] ) ) .unwrap ( ) ;
8411
write ( root, " a.txt" , b " 1\n X\n " ) ;
8412
let c = repo.commit ( " C: 2 to X" , & [ ] ) .unwrap ( ) ;
8413
repo.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
8414
write ( root, " a.txt" , b " 1\n Y\n " ) ;
8415
let d = repo.commit ( " D: 2 to Y" , & [ ] ) .unwrap ( ) ;
8416
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
8417
8418
let err = repo.rebase ( & c, & [ d] ) .unwrap_err ( ) ;
8419
assert! ( matches! ( err, RepoError:: Conflicted( _ ) ) , " {err}" ) ;
8420
let r = repo.rebase_into_working_copy ( & c, & [ d] ) .unwrap ( ) ;
8421
assert! ( r.changed & & r.checked_out) ;
8422
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8423
assert! (
8424
snap.base_snapshot.is_conflicted ( ) ,
8425
" the conflict is in the working copy"
8426
) ;
8427
let text = fs:: read_to_string( root.join ( " a.txt" ) ) .unwrap ( ) ;
8428
assert! ( text.contains ( ' X' ) & & text.contains ( ' Y' ) , " {text}" ) ;
8429
8430
write ( root, " a.txt" , b " 1\n XY\n " ) ;
8431
repo.commit ( " Resolve" , & [ ] ) .unwrap ( ) ;
8432
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
8433
assert! ( st.contains ( & c) & & st.contains ( & d) ) ;
8434
let files = st.materialize ( ) .files;
8435
assert! ( ! files[ " a.txt" ] .is_conflicted ( ) ) ;
8436
assert! (
8437
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 1\n XY\n " )
8438
) ;
8439
8440
8441
repo.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
8442
write ( root, " b.txt" , b " b\n " ) ;
8443
let e = repo.commit ( " E" , & [ ] ) .unwrap ( ) ;
8444
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
8445
let err = repo.rebase_into_working_copy ( & e, & [ a] ) .unwrap_err ( ) ;
8446
assert! (
8447
err.to_string ( ) .contains ( " not on the current bookmark" ) ,
8448
" {err}"
8449
) ;
8450
}
d4dbab97 Andy McCall 1 month ago ↶
8451
8452
# [ test ]
8453
fn an_ignore_rule_drops_an_added_file_but_not_a_tracked_one ( ) {
8454
hermetic ( ) ;
8455
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8456
let root = dir.path ( ) ;
8457
let repo = Repo:: init( root) .unwrap ( ) ;
8458
write ( root, " a.txt" , b " a\n " ) ;
8459
write ( root, " kept.log" , b " tracked\n " ) ;
8460
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8461
write ( root, " build.log" , b " made\n " ) ;
8462
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8463
assert! ( snap.statuses.iter ( ) .any ( | s | s.path = = " build.log" ) ) ;
8464
8465
8466
write ( root, " .gitignore" , b " *.log\n " ) ;
8467
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8468
let paths: Vec< & str > = snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect ( ) ;
8469
assert_eq! ( paths, vec! [ " .gitignore" ] , " {paths:?}" ) ;
8470
write ( root, " kept.log" , b " tracked, edited\n " ) ;
8471
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8472
assert! ( snap.statuses.iter ( ) .any ( | s | s.path = = " kept.log" ) ) ;
8473
}
fd280228 Andy McCall 1 month ago ↶
8474
8475
# [ test ]
8476
fn shed_graphs_come_back_the_same ( ) {
8477
hermetic ( ) ;
8478
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8479
let root = dir.path ( ) ;
8480
let repo = Repo:: init( root) .unwrap ( ) ;
8481
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
8482
write ( root, " b.txt" , b " x\n " ) ;
8483
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8484
write ( root, " a.txt" , b " 1\n two\n 3\n 4\n " ) ;
8485
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
8486
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
8487
let mut st = repo.load_state_history ( & heads) .unwrap ( ) ;
8488
let view = st.tree ( ) .view ( ) ;
8489
let nodes: Vec< NodeId> = [ " a.txt" , " b.txt" ]
8490
.iter ( )
8491
.map ( | p | view.entries[ * p] .node )
8492
.collect ( ) ;
8493
repo.load_graphs_of ( & mut st, & nodes) .unwrap ( ) ;
8494
let before: Vec< Vec< u8 > > = nodes.iter ( ) .map ( | n | st.render_file ( n) .0 ) .collect ( ) ;
8495
assert_eq! ( st.built_count ( ) , 2 ) ;
8496
let shed = st.shed_graphs ( | b | repo.store_blob ( b) ) .unwrap ( ) ;
8497
assert_eq! ( ( shed, st.built_count ( ) ) , ( 2 , 0 ) ) ;
8498
8499
write ( root, " a.txt" , b " 1\n two\n 3\n 4\n 5\n " ) ;
8500
let h3 = repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
8501
let ( skeleton, texts) = repo.store ( ) .get_change_lazy ( & h3) .unwrap ( ) ;
8502
st.add_known ( h3, skeleton, texts) ;
8503
repo.load_graphs_of ( & mut st, & nodes) .unwrap ( ) ;
8504
let after: Vec< Vec< u8 > > = nodes.iter ( ) .map ( | n | st.render_file ( n) .0 ) .collect ( ) ;
8505
assert_eq! ( after[ 1 ] , before[ 1 ] ) ;
8506
assert_eq! ( after[ 0 ] , b " 1\n two\n 3\n 4\n 5\n " ) ;
8507
}
360aa74a Andy McCall 1 month ago ↶
8508
8509
# [ test ]
8510
fn many_edited_files_diff_in_parallel ( ) {
8511
8512
8513
8514
hermetic ( ) ;
8515
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8516
let root = dir.path ( ) ;
8517
let repo = Repo:: init( root) .unwrap ( ) ;
8518
let text = | i : usize , round : usize | {
8519
let mut s = String :: new( ) ;
8520
for line in 0 .. 50 {
8521
if round > 0 & & line % 7 = = i % 7 {
8522
s.push_str ( & format! ( " edited {round} {i} {line} \n " ) ) ;
8523
} else {
8524
s.push_str ( & format! ( " file {i} line {line} \n " ) ) ;
8525
}
8526
}
8527
if round > 0 & & i.is_multiple_of ( 3 ) {
8528
s.push_str ( " appended\n " ) ;
8529
}
8530
s
8531
} ;
8532
for i in 0 .. 40 {
8533
write ( root, & format! ( " d/f{i} .txt" ) , text ( i, 0 ) .as_bytes ( ) ) ;
8534
}
8535
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8536
for i in 0 .. 40 {
8537
write ( root, & format! ( " d/f{i} .txt" ) , text ( i, 1 ) .as_bytes ( ) ) ;
8538
}
8539
let h = repo.commit ( " edit every file" , & [ ] ) .unwrap ( ) ;
8540
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
8541
for i in 0 .. 40 {
8542
let want = text ( i, 1 ) ;
8543
assert! (
8544
matches! ( & snap.files[ & format! ( " d/f{i} .txt" ) ] .content, FileContent:: Text { bytes, .. } if bytes = = want.as_bytes ( ) ) ,
8545
" d/f{i}.txt as committed"
8546
) ;
8547
}
8548
assert! (
8549
repo.commit ( " nothing" , & [ ] ) .is_err ( ) ,
8550
" nothing left to commit"
8551
) ;
8552
}
b35df36b Andy McCall 1 month ago ↶
8553
8554
8555
8556
8557
8558
# [ test ]
8559
fn a_fetched_change_shows_as_behind_and_resolves ( ) {
8560
hermetic ( ) ;
8561
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8562
let root = dir.path ( ) ;
8563
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
8564
drop ( origin) ;
8565
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
8566
let a = Repo:: clone( & url, & root.join ( " a" ) )
8567
.unwrap ( )
8568
.with_home ( root.join ( " home-a" ) ) ;
8569
write ( & root.join ( " a" ) , " f.txt" , b " one\n " ) ;
8570
a.commit ( " one" , & [ ] ) .unwrap ( ) ;
8571
a.push ( " origin" , false ) .unwrap ( ) ;
8572
let b = Repo:: clone( & url, & root.join ( " b" ) )
8573
.unwrap ( )
8574
.with_home ( root.join ( " home-b" ) ) ;
8575
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 0 ) ) ) ;
8576
8577
write ( & root.join ( " a" ) , " f.txt" , b " one\n two\n " ) ;
8578
let h2 = a.commit ( " two" , & [ ] ) .unwrap ( ) ;
8579
a.push ( " origin" , false ) .unwrap ( ) ;
8580
b.fetch ( " origin" ) .unwrap ( ) ;
8581
8582
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 1 ) ) ) ;
8583
assert_eq! ( b.tracking ( " origin" , " nope" ) .unwrap ( ) , None ) ;
8584
assert_eq! (
8585
b.resolve_change ( & format! ( " #{} " , & h2.to_hex ( ) [ .. 12 ] ) )
8586
.unwrap ( ) ,
8587
h2
8588
) ;
8589
assert_eq! (
8590
b.remote_only_location ( & h2) .unwrap ( ) .as_deref ( ) ,
8591
Some ( " origin/main" )
8592
) ;
8593
8594
8595
b.fetch ( " origin" ) .unwrap ( ) ;
8596
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 1 ) ) ) ;
8597
b.pull ( " origin" ) .unwrap ( ) ;
8598
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 0 ) ) ) ;
8599
assert_eq! ( b.remote_only_location ( & h2) .unwrap ( ) , None ) ;
8600
8601
8602
let mut c = Repo:: init( & root.join ( " c" ) ) .unwrap ( ) ;
8603
assert_eq! ( c.default_remote ( ) , " origin" ) ;
8604
c.config.set ( " remote.earth.url" , & url) .unwrap ( ) ;
8605
assert_eq! ( c.default_remote ( ) , " earth" ) ;
8606
c.config.set ( " remote.origin.url" , & url) .unwrap ( ) ;
8607
assert_eq! ( c.default_remote ( ) , " origin" ) ;
8608
}
827281ee Andy McCall 1 month ago ↶
8609
8610
8611
8612
# [ test ]
8613
fn a_clone_from_a_relative_path_records_it_absolute ( ) {
8614
hermetic ( ) ;
8615
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8616
let root = dir.path ( ) ;
8617
Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
8618
let rel = crate :: protocol:: stored_url( " relative/repo" ) ;
8619
assert! ( std:: path:: Path:: new( & rel) .is_absolute ( ) , " {rel}" ) ;
8620
assert_eq! (
8621
crate :: protocol:: stored_url( " https://hub.example/o/n" ) ,
8622
" https://hub.example/o/n"
8623
) ;
8624
assert! ( crate :: protocol:: stored_url( " file://x" ) .starts_with ( " file:///" ) ) ;
8d803563 Andy McCall 1 month ago ↶
8625
assert! ( crate :: protocol:: stored_url( " dir://x" ) .starts_with ( " dir:///" ) ) ;
8626
8627
assert_eq! (
8628
crate :: protocol:: stored_url( " s3://bucket/prefix?profile=p" ) ,
8629
" s3://bucket/prefix?profile=p"
8630
) ;
827281ee Andy McCall 1 month ago ↶
8631
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
8632
let b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) ;
8633
assert_eq! ( b.config.remotes[ " origin" ] , url) ;
8634
b.fetch ( " origin" ) .unwrap ( ) ;
8635
}
a46f3e4f Andy McCall 1 month ago ↶
8636
8637
8638
8639
8640
# [ test ]
8641
fn a_workspace_added_on_a_bookmark_is_on_it_with_its_subrepos ( ) {
8642
hermetic ( ) ;
8643
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8644
let root = dir.path ( ) ;
8645
Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
8646
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
8647
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
8648
.unwrap ( )
8649
.with_home ( root.join ( " home" ) ) ;
8650
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
8651
let l1 = lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
8652
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
8653
8654
let app_dir = root.join ( " app" ) ;
8655
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8656
write ( & app_dir, " main.txt" , b " app\n " ) ;
8657
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
8658
app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
8659
8660
app.bookmark_create ( " develop" , None ) .unwrap ( ) ;
8661
app.bookmark_create ( " feature" , None ) .unwrap ( ) ;
8662
let mut app = app;
8663
app.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
8664
assert_eq! ( app.config.bookmark, " feature" ) ;
8665
8666
8667
std:: fs:: rename( root.join ( " lib-origin" ) , root.join ( " lib-origin-gone" ) ) .unwrap ( ) ;
8668
let heads = app.resolve_heads ( " develop" ) .unwrap ( ) ;
8669
let ws = app
8670
.workspace_add_on ( & root.join ( " ws" ) , Some ( & heads) , Some ( " develop" ) )
8671
.unwrap ( ) ;
8672
assert_eq! (
8673
ws.config.bookmark, " develop" ,
8674
" the new workspace is on develop"
8675
) ;
8676
assert_eq! (
8677
app.config.bookmark, " feature" ,
8678
" and this one stays on its own"
8679
) ;
8680
let sub = Repo:: open( & root.join ( " ws/libs/lib" ) ) .unwrap ( ) ;
8681
assert_eq! (
8682
sub.config.remotes[ " origin" ] , lib_url,
8683
" its origin is the subrepo's URL"
8684
) ;
8685
assert_eq! ( sub.bookmark_heads ( ) .unwrap ( ) , vec! [ l1] , " at its pin" ) ;
8686
assert_eq! (
8687
std:: fs:: read( root.join ( " ws/libs/lib/lib.txt" ) ) .unwrap ( ) ,
8688
b " v1\n "
8689
) ;
8690
}
47ce8b8e Andy McCall 1 month ago ↶
8691
8692
8693
8694
8695
# [ test ]
8696
fn revsets_select_changes_by_relation_author_text_date_and_path ( ) {
8697
hermetic ( ) ;
8698
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8699
let root = dir.path ( ) ;
8700
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8701
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
8702
.unwrap ( ) ;
8703
write ( root, " src/a.rs" , b " one\n " ) ;
8704
let a = repo.commit ( " first: add a" , & [ ] ) .unwrap ( ) ;
8705
write ( root, " src/a.rs" , b " one\n two\n " ) ;
8706
let b = repo.commit ( " second: grow a" , & [ ] ) .unwrap ( ) ;
8707
8708
repo.checkout ( & [ a] , false , " checkout a" ) .unwrap ( ) ;
8709
write ( root, " docs/notes.md" , b " notes\n " ) ;
8710
let c = repo.commit ( " third: notes" , & [ ] ) .unwrap ( ) ;
8711
8712
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
8713
.unwrap ( ) ;
8714
fs:: remove_file( root.join ( " src/a.rs" ) ) .unwrap ( ) ;
8715
write ( root, " src/z.rs" , b " one\n two\n " ) ;
8716
let d = repo.commit ( " fourth: rename a to z" , & [ ] ) .unwrap ( ) ;
8717
let wc = repo.working_copy ( ) .unwrap ( ) .0 ;
8718
8719
repo.set_user_config ( " revset.mine" , " author(me) & ::main" )
8720
.unwrap ( ) ;
8721
assert! ( repo.set_user_config ( " revset.bad" , " a &" ) .is_err ( ) ) ;
8722
let set = | src : & str | -> Vec< ChangeHash> {
8723
let mut v = repo.revset ( src) .unwrap ( ) .hashes;
8724
v.sort ( ) ;
8725
v
8726
} ;
8727
let sorted = | hs : & [ChangeHash]| -> Vec< ChangeHash> {
8728
let mut v = hs.to_vec ( ) ;
8729
v.sort ( ) ;
8730
v
8731
} ;
8732
8733
8734
8735
8736
let heads = sorted ( & repo.bookmark_heads ( ) .unwrap ( ) ) ;
8737
let meta = | h : & ChangeHash| repo.store ( ) .get_change_meta ( h) .unwrap ( ) ;
8738
assert! ( heads.contains ( & d) & & heads.contains ( & c) ) ;
8739
8740
assert_eq! ( set ( " main" ) , heads) ;
8741
assert_eq! ( set ( " ::main" ) , sorted ( & [ a, b, c, d] ) ) ;
8742
assert_eq! ( set ( " @" ) , vec! [ wc] ) ;
8743
assert_eq! ( set ( " ::@" ) , sorted ( & [ a, b, c, d, wc] ) ) ;
8744
assert_eq! ( set ( & format! ( " ::#{} " , b.short ( 12 ) ) ) , sorted ( & [ a, b] ) ) ;
8745
assert_eq! ( set ( & format! ( " #{} ::" , a.short ( 12 ) ) ) , sorted ( & [ a, b, d, wc] ) ) ;
8746
assert_eq! (
8747
set ( & format! ( " #{} ::#{} " , a.short ( 12 ) , b.short ( 12 ) ) ) ,
8748
sorted ( & [ a, b] )
8749
) ;
8750
8751
assert_eq! ( set ( & format! ( " #{} ..main" , b.short ( 12 ) ) ) , sorted ( & [ c, d] ) ) ;
8752
assert_eq! ( set ( & format! ( " #{} .." , c.short ( 12 ) ) ) , sorted ( & [ a, b, d, wc] ) ) ;
8753
assert_eq! ( set ( " ..main" ) , sorted ( & [ a, b, c, d] ) ) ;
8754
8755
assert_eq! (
8756
set ( & format! ( " ::main ~ #{} " , d.short ( 12 ) ) ) ,
8757
sorted ( & [ a, b, c] )
8758
) ;
8759
assert_eq! (
8760
set ( & format! ( " ::main & #{} | #{} " , b.short ( 12 ) , c.short ( 12 ) ) ) ,
8761
sorted ( & [ b, c] )
8762
) ;
8763
assert_eq! ( set ( " all() ~ ::main" ) , vec! [ wc] ) ;
8764
8765
let mut head_deps: Vec< ChangeHash> = heads.iter ( ) .flat_map ( | h | meta ( h) .deps) .collect ( ) ;
8766
head_deps.sort ( ) ;
8767
head_deps.dedup ( ) ;
8768
assert_eq! ( set ( " deps(main)" ) , head_deps) ;
8769
let on_a: Vec< ChangeHash> = [ b, c, d]
8770
.into_iter ( )
8771
.filter ( | h | meta ( h) .deps.contains ( & a) )
8772
.collect ( ) ;
8773
assert_eq! ( set ( & format! ( " dependents(#{} )" , a.short ( 12 ) ) ) , sorted ( & on_a) ) ;
8774
assert_eq! ( set ( " heads(::main)" ) , heads) ;
8775
assert_eq! ( set ( " roots(::main)" ) , sorted ( & [ a, c] ) ) ;
8776
assert_eq! (
8777
set ( & format! (
8778
" heads(#{} | #{} | #{} )" ,
8779
a.short ( 12 ) ,
8780
b.short ( 12 ) ,
8781
c.short ( 12 )
8782
) ) ,
8783
sorted ( & [ b, c] )
8784
) ;
8785
assert_eq! ( set ( " latest(::main, 1)" ) , vec! [ d] ) ;
8786
assert_eq! ( set ( " kind(working-copy)" ) , vec! [ wc] ) ;
8787
assert_eq! ( set ( " none()" ) , vec! [ ] ) ;
8788
8789
let key = repo.config.author_key.unwrap ( ) ;
8790
assert_eq! ( set ( " author(ada) & ::main" ) , sorted ( & [ a, b, c, d] ) ) ;
8791
assert_eq! ( set ( " author(\" [email protected] \" ) & ::main" ) .len ( ) , 4 ) ;
8792
assert_eq! ( set ( & format! ( " author({} ) & ::main" , key.short ( 12 ) ) ) .len ( ) , 4 ) ;
8793
assert_eq! ( set ( " author(me) & ::main" ) .len ( ) , 4 ) ;
8794
assert_eq! ( set ( " author(nobody)" ) , vec! [ ] ) ;
8795
assert_eq! ( set ( " description(notes)" ) , vec! [ c] ) ;
8796
assert_eq! ( set ( " description(\" rename a\" )" ) , vec! [ d] ) ;
8797
assert_eq! ( set ( " date(-1h..) & ::main" ) .len ( ) , 4 ) ;
8798
assert_eq! ( set ( " date(..2000-01-01)" ) , vec! [ ] ) ;
8799
8800
assert_eq! ( set ( " touches(\" src/z.rs\" )" ) , sorted ( & [ a, b, d] ) ) ;
8801
assert_eq! ( set ( " touches(\" src/**\" ) & ::main" ) , sorted ( & [ a, b, d] ) ) ;
8802
assert_eq! ( set ( " touches(\" docs/*\" )" ) , vec! [ c] ) ;
8803
assert_eq! ( set ( " touches(\" nothing/*\" )" ) , vec! [ ] ) ;
8804
8805
assert_eq! ( repo.revset ( " ::main" ) .unwrap ( ) .hashes[ 0 ] , d) ;
8806
8807
assert_eq! ( set ( " mine()" ) .len ( ) , 4 ) ;
8808
let e = repo.revset ( " nosuch()" ) .unwrap_err ( ) .to_string ( ) ;
8809
assert! ( e.contains ( " unknown function nosuch()" ) , " {e}" ) ;
8810
let e = repo.revset ( " main &" ) .unwrap_err ( ) .to_string ( ) ;
8811
assert! ( e.contains ( " at character" ) , " {e}" ) ;
8812
let e = repo.revset ( " zzzz" ) .unwrap_err ( ) .to_string ( ) ;
8813
assert! ( e.contains ( " no change matches" ) , " {e}" ) ;
8814
let e = repo
8815
.revset_with_budget ( " ::main" , std:: time:: Duration:: ZERO )
8816
.unwrap_err ( )
8817
.to_string ( ) ;
8818
assert! ( e.contains ( " budget" ) , " {e}" ) ;
8819
8820
let entries = repo.log_revset ( " ::@" ) .unwrap ( ) ;
8821
assert! ( entries[ 0 ] .is_working_copy & & entries.iter ( ) .any ( | e | e.is_head & & e.hash = = d ) ) ;
2c9ddac3 Andy McCall 1 month ago ↶
8822
8823
8824
assert_eq! ( set ( " unsigned() & ::main" ) , sorted ( & [ a, b, c, d] ) ) ;
8825
assert_eq! ( set ( " signed_by(me)" ) , vec! [ ] ) ;
8826
repo.sign_change ( & d) .unwrap ( ) ;
8827
assert_eq! ( set ( " signed_by(me)" ) , vec! [ d] ) ;
8828
assert_eq! ( set ( " signed_by([email protected] )" ) , vec! [ d] ) ;
8829
assert_eq! ( set ( & format! ( " signed_by({} )" , key.short ( 12 ) ) ) , vec! [ d] ) ;
8830
assert_eq! ( set ( " unsigned() & ::main" ) , sorted ( & [ a, b, c] ) ) ;
8831
assert_eq! ( set ( " delegated()" ) , vec! [ ] ) ;
8832
assert_eq! ( set ( " revoked()" ) , vec! [ ] ) ;
8833
assert_eq! ( set ( " attested(tag)" ) , vec! [ ] ) ;
8834
repo.tag_create ( " v1" , & [ b] , " first" ) .unwrap ( ) ;
8835
assert_eq! ( set ( " attested(tag)" ) , sorted ( & [ a, b] ) ) ;
8836
assert_eq! ( set ( " attested(tag) & unsigned()" ) , sorted ( & [ a, b] ) ) ;
8837
assert_eq! ( set ( " attested(receipt)" ) , vec! [ ] ) ;
8838
assert_eq! ( set ( " built(test)" ) , vec! [ ] ) ;
8839
let e = repo.revset ( " attested(seal)" ) .unwrap_err ( ) .to_string ( ) ;
8840
assert! ( e.contains ( " tag, receipt or ref" ) , " {e}" ) ;
8841
repo.revoke_identity ( & key, " test" ) .unwrap ( ) ;
8842
assert_eq! ( set ( " revoked()" ) , vec! [ d] ) ;
8843
assert_eq! ( set ( " signed_by(me) ~ revoked()" ) , vec! [ ] ) ;
47ce8b8e Andy McCall 1 month ago ↶
8844
}
614f55e1 Andy McCall 1 month ago ↶
8845
8846
# [ test ]
8847
fn a_rotation_is_recorded_wherever_the_old_key_was_named ( ) {
8848
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8849
let root = dir.path ( ) ;
8850
let home = root.join ( " home" ) ;
8851
8852
8853
let mut a = Repo:: init( & root.join ( " a" ) ) .unwrap ( ) .with_home ( & home) ;
8854
let ( key, _ ) = a
8855
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
8856
.unwrap ( ) ;
8857
a.config.set ( " author.key" , & key.to_hex ( ) ) .unwrap ( ) ;
8858
a.save_config ( ) .unwrap ( ) ;
8859
let agent = SecretKey:: generate( ) .identity ( " Agent" , " [email protected] " , 1 ) ;
8860
let r = a
8861
.delegate_add ( agent.clone ( ) , crate :: identity:: Scope:: CHANGES , None )
8862
.unwrap ( ) ;
8863
assert_eq! (
8864
read_config_file ( & home.join ( " config.toml" ) ) .unwrap ( ) [ " author.key" ] ,
8865
r.new.to_hex ( ) ,
8866
" the user file follows"
8867
) ;
8868
assert_eq! (
8869
read_config_file ( & root.join ( " a/.oxy/config" ) ) .unwrap ( ) [ " author.key" ] ,
8870
r.new.to_hex ( ) ,
8871
" the pinning repository follows"
8872
) ;
8873
let b = Repo:: init( & root.join ( " b" ) ) .unwrap ( ) .with_home ( & home) ;
8874
assert_eq! (
8875
b.config.author_key,
8876
Some ( r.new) ,
8877
" a checkout without a pin follows the user file"
8878
) ;
8879
8880
let other_home = root.join ( " home2" ) ;
8881
let mut c = Repo:: init( & root.join ( " c" ) ) .unwrap ( ) .with_home ( & other_home) ;
8882
let ( ck, _ ) = c
8883
.create_identity ( " Cy" , " [email protected] " , IdentityScope:: Repo)
8884
.unwrap ( ) ;
8885
assert! ( ! other_home.join ( " config.toml" ) .exists ( ) ) ;
8886
let r2 = c.rotate_identity ( ) .unwrap ( ) ;
8887
assert_ne! ( r2.new, ck) ;
8888
assert_eq! (
8889
read_config_file ( & root.join ( " c/.oxy/config" ) ) .unwrap ( ) [ " author.key" ] ,
8890
r2.new.to_hex ( )
8891
) ;
8892
assert! (
8893
! other_home.join ( " config.toml" ) .exists ( ) ,
8894
" a repository-scoped identity stays repository-scoped"
8895
) ;
8896
8897
let ( d1, d2) = (
8898
SecretKey:: generate( ) .identity ( " D1" , " d1@x" , 1 ) ,
8899
SecretKey:: generate( ) .identity ( " D2" , " d2@x" , 1 ) ,
8900
) ;
8901
let r3 = c
8902
.delegate_add_many ( vec! [ d1.clone ( ) , d2] , crate :: identity:: Scope:: CHANGES , None )
8903
.unwrap ( ) ;
8904
assert_eq! ( r3.identity.delegates.len ( ) , 2 ) ;
8905
assert! ( c
8906
.delegate_add ( d1, crate :: identity:: Scope:: CHANGES , None )
8907
.is_err ( ) ) ;
8908
}
33be2409 Andy McCall 1 month ago ↶
8909
8910
8911
8912
8913
8914
# [ test ]
8915
fn git_import_joins_unrelated_histories_that_add_the_same_file ( ) {
8916
8917
8918
crate :: repo:: git:: set_merge_batch_for_tests( Some ( 1 ) ) ;
8919
hermetic ( ) ;
8920
if std:: process:: Command:: new( " git" )
8921
.arg ( " --version" )
8922
.output ( )
8923
.is_err ( )
8924
{
8925
eprintln! ( " git not installed; skipping" ) ;
8926
return ;
8927
}
8928
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8929
let root = dir.path ( ) ;
8930
let g = root.join ( " src.git" ) ;
8931
fs:: create_dir_all( & g) .unwrap ( ) ;
8932
let git = | args : & [& str ]| -> String {
8933
let out = std:: process:: Command:: new( " git" )
8934
.arg ( " -C" )
8935
.arg ( & g)
8936
.args ( [
8937
" -c" ,
8938
" user.name=Grace" ,
8939
" -c" ,
8940
" [email protected] " ,
8941
" -c" ,
8942
" commit.gpgsign=false" ,
8943
] )
8944
.args ( args)
8945
.output ( )
8946
.unwrap ( ) ;
8947
assert! (
8948
out.status.success ( ) ,
8949
" git {args:?}: {}" ,
8950
String :: from_utf8_lossy( & out.stderr)
8951
) ;
8952
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
8953
} ;
8954
let text = b " /* header */\n const a = 1;\n const b = 2;\n " ;
8955
8956
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
8957
write ( & g, " gulpfile.js" , text) ;
8958
write ( & g, " a.txt" , b " a\n " ) ;
8959
git ( & [ " add" , " ." ] ) ;
8960
git ( & [ " commit" , " -q" , " -m" , " A root" ] ) ;
8961
8962
git ( & [ " checkout" , " -q" , " --orphan" , " fork" ] ) ;
8963
git ( & [ " rm" , " -q" , " -r" , " --cached" , " ." ] ) ;
8964
for f in [ " gulpfile.js" , " a.txt" ] {
8965
let _ = fs:: remove_file( g.join ( f) ) ;
8966
}
8967
write ( & g, " gulpfile.js" , text) ;
8968
write ( & g, " b.txt" , b " b\n " ) ;
8969
git ( & [ " add" , " ." ] ) ;
8970
git ( & [ " commit" , " -q" , " -m" , " B root" ] ) ;
8971
8972
git ( & [ " checkout" , " -q" , " -b" , " one" , " main" ] ) ;
8973
git ( & [
8974
" merge" ,
8975
" -q" ,
8976
" --no-ff" ,
8977
" --allow-unrelated-histories" ,
8978
" -m" ,
8979
" join on one" ,
8980
" fork" ,
8981
] ) ;
8982
git ( & [ " checkout" , " -q" , " -b" , " two" , " main" ] ) ;
8983
write ( & g, " a.txt" , b " a\n two\n " ) ;
8984
git ( & [ " commit" , " -q" , " -am" , " work on two" ] ) ;
8985
git ( & [
8986
" merge" ,
8987
" -q" ,
8988
" --no-ff" ,
8989
" --allow-unrelated-histories" ,
8990
" -m" ,
8991
" join on two" ,
8992
" fork" ,
8993
] ) ;
8994
8995
git ( & [ " checkout" , " -q" , " main" ] ) ;
8996
git ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge one" , " one" ] ) ;
8997
git ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge two" , " two" ] ) ;
8998
write (
8999
& g,
9000
" gulpfile.js" ,
9001
b " /* header */\n const a = 1;\n const b = 3;\n " ,
9002
) ;
9003
git ( & [ " commit" , " -q" , " -am" , " after" ] ) ;
9004
9005
let dest = root.join ( " dest" ) ;
9006
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9007
repo.import_git ( & g, " main" ) .unwrap ( ) ;
9008
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
9009
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
9010
assert_eq! (
9011
fs:: read( dest.join ( " gulpfile.js" ) ) .unwrap ( ) ,
9012
b " /* header */\n const a = 1;\n const b = 3;\n "
9013
) ;
9014
}
9015
9016
9017
9018
9019
# [ test ]
9020
fn git_import_joins_where_each_join_kept_a_different_copy ( ) {
9021
9022
9023
crate :: repo:: git:: set_merge_batch_for_tests( Some ( 1 ) ) ;
9024
hermetic ( ) ;
9025
if std:: process:: Command:: new( " git" )
9026
.arg ( " --version" )
9027
.output ( )
9028
.is_err ( )
9029
{
9030
eprintln! ( " git not installed; skipping" ) ;
9031
return ;
9032
}
9033
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9034
let root = dir.path ( ) ;
9035
let g = root.join ( " src.git" ) ;
9036
fs:: create_dir_all( & g) .unwrap ( ) ;
9037
let run = | args : & [& str ]| {
9038
std:: process:: Command:: new( " git" )
9039
.arg ( " -C" )
9040
.arg ( & g)
9041
.args ( [
9042
" -c" ,
9043
" user.name=Grace" ,
9044
" -c" ,
9045
" [email protected] " ,
9046
" -c" ,
9047
" commit.gpgsign=false" ,
9048
] )
9049
.args ( args)
9050
.output ( )
9051
.unwrap ( )
9052
} ;
9053
let git = | args : & [& str ]| -> String {
9054
let out = run ( args) ;
9055
assert! (
9056
out.status.success ( ) ,
9057
" git {args:?}: {}" ,
9058
String :: from_utf8_lossy( & out.stderr)
9059
) ;
9060
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
9061
} ;
9062
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
9063
write (
9064
& g,
9065
" gulpfile.js" ,
9066
b " /* header */\n const a = 1;\n const b = 2;\n " ,
9067
) ;
9068
git ( & [ " add" , " ." ] ) ;
9069
git ( & [ " commit" , " -q" , " -m" , " A root" ] ) ;
9070
write (
9071
& g,
9072
" gulpfile.js" ,
9073
b " /* header */\n const a = 1;\n \n const b = 2;\n " ,
9074
) ;
9075
git ( & [ " commit" , " -q" , " -am" , " whitespace" ] ) ;
9076
git ( & [ " checkout" , " -q" , " --orphan" , " fork" ] ) ;
9077
git ( & [ " rm" , " -q" , " -r" , " --cached" , " ." ] ) ;
9078
let _ = fs:: remove_file( g.join ( " gulpfile.js" ) ) ;
9079
write (
9080
& g,
9081
" gulpfile.js" ,
9082
b " /* header */\n const a = 1;\n const b = 2;\n const c = 3;\n " ,
9083
) ;
9084
git ( & [ " add" , " ." ] ) ;
9085
git ( & [ " commit" , " -q" , " -m" , " B root" ] ) ;
9086
9087
for ( branch, keep) in [ ( " one" , " ours" ) , ( " two" , " theirs" ) ] {
9088
git ( & [ " checkout" , " -q" , " -b" , branch, " main" ] ) ;
9089
let _ = run ( & [
9090
" merge" ,
9091
" -q" ,
9092
" --no-ff" ,
9093
" --allow-unrelated-histories" ,
9094
" -m" ,
9095
" join" ,
9096
" fork" ,
9097
] ) ;
9098
git ( & [ " checkout" , & format! ( " --{keep} " ) , " gulpfile.js" ] ) ;
9099
git ( & [ " add" , " gulpfile.js" ] ) ;
9100
git ( & [ " commit" , " -q" , " -m" , & format! ( " join on {branch} " ) ] ) ;
9101
}
9102
9103
git ( & [ " checkout" , " -q" , " one" ] ) ;
9104
write (
9105
& g,
9106
" gulpfile.js" ,
9107
b " /* header */\n const a = 1;\n \n const b = 2;\n // tslint\n " ,
9108
) ;
9109
git ( & [ " commit" , " -q" , " -am" , " tslint on one" ] ) ;
9110
git ( & [ " checkout" , " -q" , " two" ] ) ;
9111
write (
9112
& g,
9113
" gulpfile.js" ,
9114
b " /* header */\n const a = 1;\n const b = 2;\n const c = 3;\n // fmt\n " ,
9115
) ;
9116
write ( & g, " extra.txt" , b " x\n " ) ;
9117
write ( & g, " more.txt" , b " m\n " ) ;
9118
git ( & [ " add" , " ." ] ) ;
9119
git ( & [ " commit" , " -q" , " -am" , " fmt on two" ] ) ;
9120
9121
git ( & [ " checkout" , " -q" , " one" ] ) ;
9122
let _ = run ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge two" , " two" ] ) ;
9123
write (
9124
& g,
9125
" gulpfile.js" ,
9126
b " /* header */\n const a = 1;\n const b = 2;\n const c = 3;\n // tslint\n // fmt\n " ,
9127
) ;
9128
git ( & [ " add" , " gulpfile.js" ] ) ;
9129
git ( & [ " commit" , " -q" , " -m" , " merge two into one" ] ) ;
9130
9131
let dest = root.join ( " dest" ) ;
9132
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9133
repo.import_git ( & g, " one" ) .unwrap ( ) ;
9134
let v = repo.verify_git_import ( & g, " one" ) .unwrap ( ) ;
9135
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
9136
}
dd21fd82 Andy McCall 1 month ago ↶
9137
9138
9139
9140
9141
9142
9143
# [ test ]
9144
fn over_a_store_the_operation_head_moves_by_compare_and_swap ( ) {
9145
use crate :: strata:: { MemoryStore, Store as _ } ;
9146
let backend = std:: sync:: Arc:: new( MemoryStore:: new( ) ) ;
9147
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = Some ( backend.clone ( ) ) ) ;
9148
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9149
let a = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9150
assert! (
9151
! dir.path ( ) .join ( " .oxy/ops/head" ) .exists ( ) ,
9152
" the head is not a file"
9153
) ;
9154
let ( h0, op0) = a.head_op ( ) .unwrap ( ) ;
9155
let record = backend
9156
.read_ref ( HEAD_RECORD )
9157
.unwrap ( )
9158
.expect ( " the head is the store's record" ) ;
9159
assert_eq! ( String :: from_utf8( record.0 ) .unwrap ( ) , h0.to_hex ( ) ) ;
9160
9161
let b = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
9162
let state = a.store.put_state ( & StateObject:: new( vec! [ ] ) ) .unwrap ( ) ;
9163
let mut va = op0.view.clone ( ) ;
9164
va.bookmarks.insert ( " from-a" .into ( ) , state) ;
9165
let mut vb = op0.view.clone ( ) ;
9166
vb.bookmarks.insert ( " from-b" .into ( ) , state) ;
9167
let rb = b.record_op ( & [ h0] , " bookmark from-b" , vb) .unwrap ( ) ;
9168
assert_eq! ( b.head_ref ( ) .unwrap ( ) , rb) ;
9169
9170
let ra = a.record_op ( & [ h0] , " bookmark from-a" , va) .unwrap ( ) ;
9171
let ( head, op) = Repo:: open( dir.path ( ) ) .unwrap ( ) .head_op ( ) .unwrap ( ) ;
9172
assert_eq! ( head, ra) ;
9173
assert! (
9174
op.command.ends_with ( " (merged with a concurrent operation)" ) ,
9175
" {}" ,
9176
op.command
9177
) ;
9178
assert_eq! ( op.parents, vec! [ rb, op.parents[ 1 ] ] ) ;
9179
assert! ( op.view.bookmarks.contains_key ( " from-a" ) & & op.view.bookmarks.contains_key ( " from-b" ) ) ;
9180
9181
9182
let ( h, v) = a.read_head ( ) .unwrap ( ) ;
9183
assert! ( b.swap_head ( & rb, v.as_ref ( ) ) .unwrap ( ) ) ;
9184
assert! ( ! a.swap_head ( & h, v.as_ref ( ) ) .unwrap ( ) , " stale" ) ;
767ac6cd Andy McCall 19 days ago
9185
a.refresh ( ) .unwrap ( ) ;
dd21fd82 Andy McCall 1 month ago ↶
9186
assert_eq! ( a.head_ref ( ) .unwrap ( ) , rb) ;
9187
9188
let other = tempfile:: tempdir( ) .unwrap ( ) ;
9189
let err = Repo:: init( other.path ( ) )
9190
.err ( )
9191
.expect ( " the store already holds a log" )
9192
.to_string ( ) ;
9193
assert! (
9194
err.contains ( " already holds a repository's operation log" ) ,
9195
" {err}"
9196
) ;
9197
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = None ) ;
9198
}
d069701a Andy McCall 1 month ago ↶
9199
9200
9201
9202
9203
# [ test ]
9204
fn a_repository_runs_on_a_store_with_a_cache_in_front ( ) {
9205
hermetic ( ) ;
9206
if std:: env:: var_os( " OXY_STORE_BACKEND" ) .is_some ( ) {
9207
return ;
9208
}
9209
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9210
let ( root, bucket) = ( dir.path ( ) .join ( " work" ) , dir.path ( ) .join ( " bucket" ) ) ;
9211
let url = format! ( " dir://{} " , bucket.display ( ) ) ;
9212
let repo = Repo:: init_on( & root, [ 7 ; 16 ] , Some ( & url) ) .unwrap ( ) ;
9213
assert_eq! ( repo.config.storage_url.as_deref ( ) , Some ( url.as_str ( ) ) ) ;
9214
write ( & root, " a.txt" , b " one\n " ) ;
9215
let h1 = repo.commit ( " First" , & [ ] ) .unwrap ( ) ;
9216
write ( & root, " a.txt" , b " one\n two\n " ) ;
9217
let h2 = repo.commit ( " Second" , & [ ] ) .unwrap ( ) ;
9218
assert! (
9219
! root.join ( " .oxy/ops/head" ) .exists ( ) ,
9220
" the head is in the store"
9221
) ;
9222
assert! ( bucket.join ( " ops/head" ) .is_file ( ) ) ;
9223
assert! ( root.join ( " .oxy/cache" ) .is_dir ( ) ) ;
9224
drop ( repo) ;
9225
9226
9227
for drop_cache in [ false , true ] {
9228
if drop_cache {
9229
std:: fs:: remove_dir_all( root.join ( " .oxy/cache" ) ) .unwrap ( ) ;
9230
}
9231
let repo = Repo:: open( & root) .unwrap ( ) ;
9232
let log: Vec< String> = repo
9233
.op_log ( )
9234
.unwrap ( )
9235
.into_iter ( )
9236
.map ( | ( _, op ) | op.command )
9237
.collect ( ) ;
9238
assert! ( log.len ( ) > = 3 , " {log:?}" ) ;
9239
assert_eq! ( repo.store ( ) .get_change ( & h1) .unwrap ( ) .message, " First" ) ;
9240
let heads = repo
9241
.store ( )
9242
.get_state ( & repo.view ( ) .unwrap ( ) .bookmarks[ " main" ] )
9243
.unwrap ( )
9244
.heads;
9245
assert_eq! ( heads, vec! [ h2] ) ;
9246
assert_eq! (
9247
repo.load_state ( & heads) .unwrap ( ) .materialize ( ) .files.len ( ) ,
9248
1
9249
) ;
9250
}
9251
9252
9253
let err = Repo:: init_on( & dir.path ( ) .join ( " other" ) , [ 8 ; 16 ] , Some ( & url) )
9254
.err ( )
9255
.unwrap ( )
9256
.to_string ( ) ;
9257
assert! (
9258
err.contains ( " already holds a repository's operation log" ) ,
9259
" {err}"
9260
) ;
9261
let err = Repo:: init_on( & dir.path ( ) .join ( " third" ) , [ 9 ; 16 ] , Some ( " nope://x" ) )
9262
.err ( )
9263
.unwrap ( )
9264
.to_string ( ) ;
9265
assert! ( err.contains ( " cannot open that kind of store" ) , " {err}" ) ;
9266
}
80677b7a Andy McCall 1 month ago ↶
9267
9268
9269
9270
9271
9272
# [ test ]
9273
fn the_quick_dirty_check_compares_a_subrepo_by_its_pin ( ) {
9274
hermetic ( ) ;
9275
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9276
let root = dir.path ( ) ;
9277
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9278
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9279
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9280
.unwrap ( )
9281
.with_home ( root.join ( " home" ) ) ;
9282
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9283
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9284
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9285
let app_dir = root.join ( " app" ) ;
9286
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9287
write ( & app_dir, " main.txt" , b " app\n " ) ;
9288
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9289
app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
9290
9291
std:: thread:: sleep( std:: time:: Duration:: from_millis( 2100 ) ) ;
9292
let reopen = | | Repo:: open( & app_dir) .unwrap ( ) ;
9293
assert_eq! (
9294
reopen ( ) .quick_dirty ( ) .unwrap ( ) ,
9295
Some ( false ) ,
9296
" clean, as status says"
9297
) ;
9298
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) ) .unwrap ( ) ;
9299
write ( & app_dir.join ( " libs/lib" ) , " lib.txt" , b " v2\n " ) ;
9300
nested.commit ( " lib v2" , & [ ] ) .unwrap ( ) ;
9301
assert_eq! (
9302
reopen ( ) .quick_dirty ( ) .unwrap ( ) ,
9303
Some ( true ) ,
9304
" the subrepo moved"
9305
) ;
9306
reopen ( ) .commit ( " pin lib v2" , & [ ] ) .unwrap ( ) ;
9307
std:: thread:: sleep( std:: time:: Duration:: from_millis( 2100 ) ) ;
9308
assert_eq! (
9309
reopen ( ) .quick_dirty ( ) .unwrap ( ) ,
9310
Some ( false ) ,
9311
" the new pin is committed"
9312
) ;
9313
}
7993d465 Andy McCall 1 month ago ↶
9314
9315
# [ test ]
9316
fn a_module_entry_that_was_never_added_is_reported_as_unpinned ( ) {
9317
9318
hermetic ( ) ;
9319
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9320
let root = dir.path ( ) ;
9321
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9322
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9323
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9324
.unwrap ( )
9325
.with_home ( root.join ( " home" ) ) ;
9326
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9327
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9328
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9329
let app_dir = root.join ( " app" ) ;
9330
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9331
write (
9332
& app_dir,
9333
" .oxymodules" ,
9334
format! ( " [libs/lib]\n url = {lib_url} \n " ) .as_bytes ( ) ,
9335
) ;
9336
let unpinned = app.unpinned_modules ( ) .unwrap ( ) ;
9337
assert_eq! ( unpinned.len ( ) , 1 ) ;
9338
assert_eq! ( unpinned[ 0 ] .0 , " libs/lib" ) ;
9339
assert_eq! ( unpinned[ 0 ] .1. url, lib_url) ;
9340
assert! ( app.submodule_update ( true , & [ ] ) .unwrap ( ) .is_empty ( ) ) ;
9341
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9342
assert! ( app.unpinned_modules ( ) .unwrap ( ) .is_empty ( ) , " added now" ) ;
9343
}
bf3a6781 Andy McCall 1 month ago ↶
9344
9345
# [ test ]
9346
fn the_bookmark_revset_function_names_only_bookmarks ( ) {
9347
9348
hermetic ( ) ;
9349
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9350
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9351
write ( dir.path ( ) , " a.txt" , b " a\n " ) ;
9352
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
9353
write ( dir.path ( ) , " b.txt" , b " b\n " ) ;
9354
let b = repo.commit ( " B" , & [ ] ) .unwrap ( ) ;
9355
let mut heads = repo.revset ( " bookmark(main)" ) .unwrap ( ) .hashes;
9356
heads.sort ( ) ;
9357
let mut want = repo.bookmark_heads ( ) .unwrap ( ) ;
9358
want.sort ( ) ;
9359
assert_eq! ( heads, want) ;
9360
assert! ( heads.contains ( & b) ) ;
9361
assert_eq! ( repo.revset ( " main" ) .unwrap ( ) .hashes.len ( ) , heads.len ( ) ) ;
9362
9363
let prefix = repo.store ( ) .get_change_meta ( & a) .unwrap ( ) .change_id.short ( 8 ) ;
9364
assert! ( repo.revset ( & prefix) .is_ok ( ) ) ;
9365
let e = repo
9366
.revset ( & format! ( " bookmark({prefix} )" ) )
9367
.unwrap_err ( )
9368
.to_string ( ) ;
9369
assert! ( e.contains ( " no such bookmark" ) , " {e}" ) ;
9370
}
72484195 Andy McCall 1 month ago ↶
9371
157d02e9 Andy McCall 12 days ago ↶
9372
9373
9374
72484195 Andy McCall 1 month ago ↶
9375
# [ test ]
157d02e9 Andy McCall 12 days ago ↶
9376
fn a_replace_conflict_is_settled_from_the_working_copy ( ) {
9377
hermetic ( ) ;
9378
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9379
let root = dir.path ( ) ;
9380
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9381
let blob = | n : u8 | {
9382
let mut v = vec! [ 0 u8 ; 64 ] ;
9383
v[ 1 ] = n;
9384
v
9385
} ;
9386
write ( root, " img.bin" , & blob ( 1 ) ) ;
9387
write ( root, " a.txt" , b " a\n " ) ;
9388
repo.commit ( " add img" , & [ ] ) .unwrap ( ) ;
9389
repo.bookmark_create ( " other" , None ) .unwrap ( ) ;
9390
write ( root, " img.bin" , & blob ( 2 ) ) ;
9391
repo.commit ( " img two" , & [ ] ) .unwrap ( ) ;
9392
repo.bookmark_switch ( " other" , false ) .unwrap ( ) ;
9393
write ( root, " img.bin" , & blob ( 3 ) ) ;
9394
repo.commit ( " img three" , & [ ] ) .unwrap ( ) ;
9395
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
9396
repo.merge ( " other" ) .unwrap ( ) ;
9397
9398
9399
write ( root, " img.bin" , & blob ( 9 ) ) ;
9400
write ( root, " a.txt" , b " a\n more\n " ) ;
9401
assert! (
9402
repo.commit ( " edit a" , & [ ] ) .is_err ( ) ,
9403
" refused while conflicted"
9404
) ;
9405
let snap = repo.snapshot ( " oxy resolve" ) .unwrap ( ) ;
9406
assert! (
9407
repo.choose_resolutions ( & snap.base_snapshot, & [ " a.txt" .to_string ( ) ] )
9408
.is_err ( ) ,
9409
" a.txt has no replace conflict"
9410
) ;
9411
repo.choose_resolutions ( & snap.base_snapshot, & [ " img.bin" .to_string ( ) ] )
9412
.unwrap ( ) ;
9413
let snap = repo.snapshot ( " oxy resolve" ) .unwrap ( ) ;
9414
assert! ( snap
9415
.statuses
9416
.iter ( )
9417
.any ( | s | s.path = = " img.bin" & & s.status = = ChangeStatus:: Resolved ) ) ;
9418
let c = repo.commit ( " settle img" , & [ ] ) .unwrap ( ) ;
9419
assert_eq! (
9420
repo.store ( ) .get_change ( & c) .unwrap ( ) .kind,
9421
ChangeKind:: Resolution
9422
) ;
9423
let after = repo
9424
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
9425
.unwrap ( )
9426
.materialize ( ) ;
9427
assert! ( ! after.is_conflicted ( ) ) ;
9428
assert_eq! (
9429
after.files[ " img.bin" ] .content,
9430
FileContent:: Opaque {
9431
blobs: vec! [ Repo:: blob_ref( & blob ( 9 ) ) ]
9432
}
9433
) ;
9434
assert! (
9435
matches! ( & after.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n more\n " )
9436
) ;
9437
9438
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
9439
assert! ( snap.statuses.is_empty ( ) , " {:?}" , snap.statuses) ;
9440
assert! ( repo.resolutions ( ) .unwrap ( ) .is_empty ( ) ) ;
9441
}
9442
9443
9444
# [ test ]
9445
fn a_pin_conflict_is_settled_from_the_working_copy ( ) {
9446
hermetic ( ) ;
9447
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9448
let root = dir.path ( ) ;
9449
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9450
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9451
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9452
.unwrap ( )
9453
.with_home ( root.join ( " home" ) ) ;
9454
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9455
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9456
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9457
let app_dir = root.join ( " app" ) ;
9458
let mut app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9459
write ( & app_dir, " main.txt" , b " app\n " ) ;
9460
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9461
app.commit ( " pin v1" , & [ ] ) .unwrap ( ) ;
9462
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) )
9463
.unwrap ( )
9464
.with_home ( root.join ( " home" ) ) ;
9465
fn pin ( app : & Repo, nested : & Repo, text : & [u8 ], message : & str ) {
9466
write ( nested.root ( ) , " lib.txt" , text) ;
9467
nested.commit ( message, & [ ] ) .unwrap ( ) ;
9468
app.commit ( & format! ( " pin {message} " ) , & [ ] ) .unwrap ( ) ;
9469
}
9470
app.bookmark_create ( " other" , None ) .unwrap ( ) ;
9471
pin ( & app, & nested, b " v2\n " , " v2" ) ;
9472
app.bookmark_switch ( " other" , false ) .unwrap ( ) ;
9473
pin ( & app, & nested, b " v3\n " , " v3" ) ;
9474
app.bookmark_switch ( " main" , false ) .unwrap ( ) ;
9475
app.merge ( " other" ) .unwrap ( ) ;
9476
let merged = app
9477
.load_state ( & app.bookmark_heads ( ) .unwrap ( ) )
9478
.unwrap ( )
9479
.materialize ( ) ;
9480
assert! (
9481
matches! ( & merged.files[ " libs/lib" ] .content, FileContent:: Opaque { blobs } if blobs.len ( ) = = 2 ) ,
9482
" a pin conflict"
9483
) ;
9484
9485
write ( & app_dir.join ( " libs/lib" ) , " lib.txt" , b " v4\n " ) ;
9486
nested.commit ( " v4" , & [ ] ) .unwrap ( ) ;
9487
let snap = app.snapshot ( " oxy resolve" ) .unwrap ( ) ;
9488
app.choose_resolutions ( & snap.base_snapshot, & [ " libs/lib" .to_string ( ) ] )
9489
.unwrap ( ) ;
9490
let c = app.commit ( " settle the pin" , & [ ] ) .unwrap ( ) ;
9491
assert_eq! (
9492
app.store ( ) .get_change ( & c) .unwrap ( ) .kind,
9493
ChangeKind:: Resolution
9494
) ;
9495
let after = app
9496
.load_state ( & app.bookmark_heads ( ) .unwrap ( ) )
9497
.unwrap ( )
9498
.materialize ( ) ;
9499
assert! ( ! after.is_conflicted ( ) ) ;
9500
assert! ( app.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
9501
}
9502
9503
# [ test ]
72484195 Andy McCall 1 month ago ↶
9504
fn carrying_work_over_a_subrepo_already_at_the_target_pin_is_no_conflict ( ) {
9505
9506
9507
hermetic ( ) ;
9508
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9509
let root = dir.path ( ) ;
9510
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9511
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9512
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9513
.unwrap ( )
9514
.with_home ( root.join ( " home" ) ) ;
9515
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9516
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9517
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9518
let app_dir = root.join ( " app" ) ;
9519
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9520
write ( & app_dir, " main.txt" , b " app\n " ) ;
9521
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9522
9523
fs:: rename( app_dir.join ( " libs/lib" ) , root.join ( " aside" ) ) .unwrap ( ) ;
9524
app.commit ( " initial" , & [ ] ) .unwrap ( ) ;
9525
let stale = app.bookmark_heads ( ) .unwrap ( ) ;
9526
fs:: rename( root.join ( " aside" ) , app_dir.join ( " libs/lib" ) ) .unwrap ( ) ;
9527
app.commit ( " pin lib" , & [ ] ) .unwrap ( ) ;
9528
let newer = app.bookmark_heads ( ) .unwrap ( ) ;
9529
app.checkout ( & stale, false , " test" ) .unwrap ( ) ;
9530
write ( & app_dir, " notes.txt" , b " mine\n " ) ;
9531
let ( _ , carried) = app.checkout_carry ( & newer, " main" , " test" ) .unwrap ( ) ;
9532
assert_eq! ( carried, vec! [ " notes.txt" .to_string ( ) ] ) ;
9533
assert! ( app_dir.join ( " notes.txt" ) .exists ( ) ) ;
9534
}
9535
c8d9059e Andy McCall 12 days ago ↶
9536
9537
72484195 Andy McCall 1 month ago ↶
9538
# [ test ]
c8d9059e Andy McCall 12 days ago ↶
9539
fn a_new_bookmark_sends_only_what_the_remote_lacks ( ) {
9540
hermetic ( ) ;
9541
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9542
let root = dir.path ( ) ;
9543
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
9544
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
9545
let mut one = Repo:: clone( & url, & root.join ( " one" ) )
9546
.unwrap ( )
9547
.with_home ( root.join ( " home" ) ) ;
9548
for n in 0 .. 5 {
9549
write ( & root.join ( " one" ) , " a.txt" , format! ( " {n} \n " ) .as_bytes ( ) ) ;
9550
one.commit ( & format! ( " main {n} " ) , & [ ] ) .unwrap ( ) ;
9551
}
9552
assert! ( one.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9553
one.bookmark_create ( " feature" , None ) .unwrap ( ) ;
9554
one.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
9555
write ( & root.join ( " one" ) , " b.txt" , b " feature\n " ) ;
9556
let added = one.commit ( " feature" , & [ ] ) .unwrap ( ) ;
9557
let heads = one.bookmark_heads ( ) .unwrap ( ) ;
9558
let refs = one
9559
.open_named_remote ( " origin" )
9560
.unwrap ( )
9561
.refs ( )
9562
.unwrap ( )
9563
.refs;
9564
assert! (
9565
refs.iter ( ) .all ( | r | r.name ! = " feature" ) ,
9566
" new to the remote"
9567
) ;
9568
let sent = one
9569
.negotiate ( & one.remote_holds ( & refs, & [ ] ) , & heads)
9570
.unwrap ( ) ;
9571
assert_eq! ( sent, vec! [ added] , " only the change main lacks" ) ;
9572
assert! ( one.push_bookmark ( " origin" , " feature" , false ) .unwrap ( ) [ 0 ] .ok) ;
9573
let two = Repo:: clone( & url, & root.join ( " two" ) ) .unwrap ( ) ;
9574
assert! ( two.store ( ) .has ( & added) ) ;
9575
}
9576
9577
# [ test ]
72484195 Andy McCall 1 month ago ↶
9578
fn a_bookmark_only_behind_its_remote_catches_up ( ) {
9579
9580
hermetic ( ) ;
9581
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9582
let root = dir.path ( ) ;
9583
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
9584
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
9585
let one = Repo:: clone( & url, & root.join ( " one" ) )
9586
.unwrap ( )
9587
.with_home ( root.join ( " home" ) ) ;
9588
write ( & root.join ( " one" ) , " a.txt" , b " a\n " ) ;
9589
one.commit ( " A" , & [ ] ) .unwrap ( ) ;
9590
assert! ( one.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9591
let two = Repo:: clone( & url, & root.join ( " two" ) )
9592
.unwrap ( )
9593
.with_home ( root.join ( " home" ) ) ;
9594
write ( & root.join ( " two" ) , " b.txt" , b " b\n " ) ;
9595
two.commit ( " B" , & [ ] ) .unwrap ( ) ;
9596
assert! ( two.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9597
assert_eq! ( one.catch_up_bookmark ( " main" ) .unwrap ( ) , None , " not fetched" ) ;
9598
one.fetch ( " origin" ) .unwrap ( ) ;
9599
assert_eq! ( one.catch_up_bookmark ( " main" ) .unwrap ( ) , Some ( 1 ) ) ;
9600
assert_eq! ( one.bookmarks ( ) .unwrap ( ) [ 0 ] .behind, Some ( 0 ) ) ;
9601
9602
write ( & root.join ( " one" ) , " c.txt" , b " c\n " ) ;
9603
one.commit ( " C" , & [ ] ) .unwrap ( ) ;
9604
write ( & root.join ( " two" ) , " d.txt" , b " d\n " ) ;
9605
two.commit ( " D" , & [ ] ) .unwrap ( ) ;
9606
assert! ( two.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9607
one.fetch ( " origin" ) .unwrap ( ) ;
9608
assert_eq! ( one.catch_up_bookmark ( " main" ) .unwrap ( ) , None ) ;
9609
}
42ccf504 Andy McCall 29 days ago ↶
9610
9611
# [ test ]
9612
fn a_bookmark_is_compared_with_a_remote_that_has_it_when_there_is_no_origin ( ) {
9613
9614
hermetic ( ) ;
9615
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9616
let root = dir.path ( ) ;
9617
let _up = Repo:: init( & root.join ( " up" ) ) .unwrap ( ) ;
9618
let url = root.join ( " up" ) .to_string_lossy ( ) .to_string ( ) ;
9619
let other = Repo:: clone( & url, & root.join ( " other" ) )
9620
.unwrap ( )
9621
.with_home ( root.join ( " home" ) ) ;
9622
write ( & root.join ( " other" ) , " a.txt" , b " a\n " ) ;
9623
other.commit ( " a" , & [ ] ) .unwrap ( ) ;
9624
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9625
let mut one = Repo:: clone( & url, & root.join ( " one" ) )
9626
.unwrap ( )
9627
.with_home ( root.join ( " home" ) ) ;
9628
one.config.set ( " remote.earth.url" , & url) .unwrap ( ) ;
9629
one.config.set ( " remote.gcs.url" , & url) .unwrap ( ) ;
9630
one.config.set ( " remote.origin.url" , " " ) .unwrap ( ) ;
9631
one.save_config ( ) .unwrap ( ) ;
9632
write ( & root.join ( " other" ) , " b.txt" , b " b\n " ) ;
9633
other.commit ( " b" , & [ ] ) .unwrap ( ) ;
9634
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9635
one.fetch ( " earth" ) .unwrap ( ) ;
9636
let b = one
9637
.bookmarks ( )
9638
.unwrap ( )
9639
.into_iter ( )
9640
.find ( | b | b.name = = " main" )
9641
.unwrap ( ) ;
9642
assert_eq! (
9643
( b.remote.as_deref ( ) , b.behind) ,
9644
( Some ( " earth" ) , Some ( 1 ) ) ,
9645
" a stale origin copy is not used"
9646
) ;
9647
assert_eq! (
9648
one.default_remote ( ) ,
9649
" origin" ,
9650
" no default chosen among several"
9651
) ;
9652
let e = one.pull ( & one.default_remote ( ) ) .unwrap_err ( ) .to_string ( ) ;
9653
assert! ( e.contains ( " earth, gcs" ) & & e.contains ( " core.remote" ) , " {e}" ) ;
9654
one.config.set ( " core.remote" , " earth" ) .unwrap ( ) ;
9655
assert_eq! ( one.default_remote ( ) , " earth" ) ;
9656
}
cbe8499a Andy McCall 28 days ago ↶
9657
9658
# [ test ]
9659
fn a_missing_author_identity_says_how_to_fix_it ( ) {
9660
9661
9662
hermetic ( ) ;
9663
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9664
let mut repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9665
let missing = KeyId:: from_hex( & " ab" .repeat ( 32 ) ) .unwrap ( ) ;
9666
repo.config.set ( " author.key" , & missing.to_hex ( ) ) .unwrap ( ) ;
9667
let e = repo.identity ( & missing) .unwrap_err ( ) .to_string ( ) ;
9668
assert! (
9669
e.contains ( " this repository has no identity" ) & & e.contains ( " oxy config author.key" ) ,
9670
" {e}"
9671
) ;
9672
assert! ( e.contains ( " this repository's config" ) , " {e}" ) ;
9673
}
f6dccc45 Andy McCall 28 days ago ↶
9674
9675
# [ test ]
9676
fn a_working_copy_behind_its_bookmark_is_flagged_and_pull_brings_it_up ( ) {
9677
9678
9679
9680
hermetic ( ) ;
9681
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9682
let root = dir.path ( ) ;
9683
let _up = Repo:: init( & root.join ( " up" ) ) .unwrap ( ) ;
9684
let url = root.join ( " up" ) .to_string_lossy ( ) .to_string ( ) ;
9685
let other = Repo:: clone( & url, & root.join ( " other" ) )
9686
.unwrap ( )
9687
.with_home ( root.join ( " home" ) ) ;
9688
write ( & root.join ( " other" ) , " a.txt" , b " a\n " ) ;
9689
other.commit ( " a" , & [ ] ) .unwrap ( ) ;
9690
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9691
let one = Repo:: clone( & url, & root.join ( " one" ) )
9692
.unwrap ( )
9693
.with_home ( root.join ( " home" ) ) ;
9694
write ( & root.join ( " other" ) , " b.txt" , b " b\n " ) ;
9695
other.commit ( " b" , & [ ] ) .unwrap ( ) ;
9696
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9697
one.fetch ( " origin" ) .unwrap ( ) ;
9698
9699
let theirs = one.resolve_heads ( " origin/main" ) .unwrap ( ) ;
9700
one.bookmark_set ( " main" , & theirs) .unwrap ( ) ;
9701
assert! ( ! root.join ( " one/b.txt" ) .exists ( ) ) ;
9702
assert_eq! ( one.working_copy_behind ( ) .unwrap ( ) , Some ( 1 ) ) ;
9703
let r = one.pull ( " origin" ) .unwrap ( ) ;
9704
assert! (
9705
! r.changed & & r.checked_out,
9706
" nothing new, but the working copy caught up"
9707
) ;
9708
assert! ( root.join ( " one/b.txt" ) .exists ( ) ) ;
9709
assert_eq! ( one.working_copy_behind ( ) .unwrap ( ) , None ) ;
9710
}
170bb436 Andy McCall 28 days ago ↶
9711
9712
# [ test ]
9713
fn touched_nodes_cache_answers_as_the_changes_do ( ) {
9714
hermetic ( ) ;
9715
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9716
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9717
write ( dir.path ( ) , " a.txt" , b " one\n " ) ;
9718
write ( dir.path ( ) , " b.txt" , b " x\n " ) ;
9719
repo.snapshot ( " t" ) .unwrap ( ) ;
9720
let c1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
9721
write ( dir.path ( ) , " b.txt" , b " y\n " ) ;
9722
repo.snapshot ( " t" ) .unwrap ( ) ;
9723
repo.commit ( " b only" , & [ ] ) .unwrap ( ) ;
9724
fs:: rename( dir.path ( ) .join ( " a.txt" ) , dir.path ( ) .join ( " c.txt" ) ) .unwrap ( ) ;
9725
write ( dir.path ( ) , " c.txt" , b " one\n two\n " ) ;
9726
repo.snapshot ( " t" ) .unwrap ( ) ;
9727
let c3 = repo.commit ( " rename and edit" , & [ ] ) .unwrap ( ) ;
9728
9729
write ( dir.path ( ) , " c.txt" , b " one\n two\n three\n " ) ;
9730
repo.snapshot ( " t" ) .unwrap ( ) ;
9731
9732
let path = dir.path ( ) .join ( " .oxy/index/touched" ) ;
9733
let hashes = | es : Vec< LogEntry> | es.iter ( ) .map ( | e | e.hash ) .collect:: < Vec< _ > > ( ) ;
9734
let cold = hashes ( repo.log_for_path ( " c.txt" ) .unwrap ( ) ) ;
9735
assert! ( path.exists ( ) , " the first query writes the cache" ) ;
9736
let warm = hashes ( repo.log_for_path ( " c.txt" ) .unwrap ( ) ) ;
9737
assert_eq! ( cold, warm, " the cache answers as the changes do" ) ;
9738
let ( wc, _ ) = repo.working_copy ( ) .unwrap ( ) ;
9739
assert_eq! (
9740
cold,
9741
vec! [ wc, c3, c1] ,
9742
" renames followed, the working copy included"
9743
) ;
9744
9745
9746
let cached = super :: touched:: read_touched_for_test( & fs:: read( & path) .unwrap ( ) ) .unwrap ( ) ;
9747
assert! ( ! cached.contains ( & wc) , " a working-copy change is not cached" ) ;
9748
assert! ( cached.contains ( & c1) & & cached.contains ( & c3) ) ;
9749
9750
9751
let mut bytes = fs:: read( & path) .unwrap ( ) ;
9752
let last = bytes.len ( ) - 40 ;
9753
bytes[ last] ^ = 1 ;
9754
fs:: write( & path, & bytes) .unwrap ( ) ;
9755
assert_eq! ( hashes ( repo.log_for_path ( " c.txt" ) .unwrap ( ) ) , cold) ;
9756
let touches = repo.log_revset ( " touches(\" c.txt\" ) ~ @" ) .unwrap ( ) ;
9757
assert_eq! ( hashes ( touches) , vec! [ c3, c1] ) ;
9758
}
e8e6e0ca Andy McCall 27 days ago ↶
9759
9760
9761
9762
9763
9764
# [ test ]
9765
fn git_import_removes_every_copy_of_a_file_at_one_place ( ) {
9766
hermetic ( ) ;
9767
if std:: process:: Command:: new( " git" )
9768
.arg ( " --version" )
9769
.output ( )
9770
.is_err ( )
9771
{
9772
eprintln! ( " git not installed; skipping" ) ;
9773
return ;
9774
}
9775
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9776
let root = dir.path ( ) ;
9777
let g = root.join ( " src.git" ) ;
9778
fs:: create_dir_all( & g) .unwrap ( ) ;
9779
let git = | args : & [& str ]| {
9780
let out = std:: process:: Command:: new( " git" )
9781
.arg ( " -C" )
9782
.arg ( & g)
9783
.args ( [
9784
" -c" ,
9785
" user.name=Grace" ,
9786
" -c" ,
9787
" [email protected] " ,
9788
" -c" ,
9789
" commit.gpgsign=false" ,
9790
] )
9791
.args ( args)
9792
.output ( )
9793
.unwrap ( ) ;
9794
assert! (
9795
out.status.success ( ) ,
9796
" git {args:?}: {}" ,
9797
String :: from_utf8_lossy( & out.stderr)
9798
) ;
9799
} ;
9800
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
9801
write ( & g, " d/list.txt" , b " one\n two\n three\n four\n " ) ;
9802
write ( & g, " keep.txt" , b " k\n " ) ;
9803
git ( & [ " add" , " ." ] ) ;
9804
git ( & [ " commit" , " -qm" , " base" ] ) ;
9805
fs:: create_dir_all( g.join ( " d/sub" ) ) .unwrap ( ) ;
9806
git ( & [ " mv" , " d/list.txt" , " d/sub/list.txt" ] ) ;
9807
git ( & [ " commit" , " -qm" , " into sub" ] ) ;
9808
git ( & [ " checkout" , " -qb" , " side" ] ) ;
9809
git ( & [ " mv" , " d/sub/list.txt" , " d/list.txt" ] ) ;
9810
git ( & [ " commit" , " -qm" , " back, on side" ] ) ;
9811
git ( & [ " checkout" , " -q" , " main" ] ) ;
9812
git ( & [ " mv" , " d/sub/list.txt" , " d/list.txt" ] ) ;
9813
git ( & [ " commit" , " -qm" , " back, on main" ] ) ;
9814
git ( & [ " merge" , " -q" , " --no-edit" , " side" ] ) ;
9815
git ( & [ " rm" , " -rq" , " d" ] ) ;
9816
git ( & [ " commit" , " -qm" , " d goes" ] ) ;
9817
9818
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
9819
repo.import_git ( & g, " main" ) .unwrap ( ) ;
9820
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
9821
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
9822
let st = repo
9823
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
9824
.unwrap ( ) ;
9825
let view = st.tree ( ) .view ( ) ;
9826
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
9827
assert_eq! ( view.entries.keys ( ) .collect:: < Vec< _ > > ( ) , vec! [ " keep.txt" ] ) ;
9828
}
5552e69c Andy McCall 27 days ago ↶
9829
9830
9831
9832
9833
# [ test ]
9834
fn a_damaged_file_history_in_the_state_index_is_rebuilt ( ) {
9835
hermetic ( ) ;
9836
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9837
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9838
for i in 0 .. 6 {
9839
write (
9840
dir.path ( ) ,
9841
& format! ( " f{i} .txt" ) ,
9842
format! ( " one {i} \n " ) .as_bytes ( ) ,
9843
) ;
9844
}
9845
repo.snapshot ( " t" ) .unwrap ( ) ;
9846
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
9847
for i in 0 .. 6 {
9848
write (
9849
dir.path ( ) ,
9850
& format! ( " f{i} .txt" ) ,
9851
format! ( " one {i} \n two\n " ) .as_bytes ( ) ,
9852
) ;
9853
}
9854
repo.snapshot ( " t" ) .unwrap ( ) ;
9855
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
9856
let want: Vec< Vec< u8 > > = ( 0 .. 6 )
9857
.map ( | i | format! ( " one {i} \n two\n " ) .into_bytes ( ) )
9858
.collect ( ) ;
9859
9860
9861
let ( _ , wc) = repo.working_copy ( ) .unwrap ( ) ;
9862
let base = repo.load_state_history ( & wc.deps) .unwrap ( ) ;
9863
repo.write_state_index ( & wc.deps, & base) ;
9864
for ( i, w) in want.iter ( ) .enumerate ( ) {
9865
assert_eq! ( & repo.file_at ( & format! ( " f{i} .txt" ) , None ) .unwrap ( ) , w) ;
9866
}
9867
let index = repo.state_indexes ( ) ;
9868
assert! ( ! index.is_empty ( ) , " a state index was written" ) ;
9869
9870
let mut bytes = fs:: read( & index[ 0 ] ) .unwrap ( ) ;
9871
let last = bytes.len ( ) - 1 ;
9872
bytes[ last] ^ = 0x40 ;
9873
fs:: write( & index[ 0 ] , & bytes) .unwrap ( ) ;
9874
9875
let reads: Vec< _ > = ( 0 .. 6 )
9876
.map ( | i | repo.file_at ( & format! ( " f{i} .txt" ) , None ) )
9877
.collect ( ) ;
9878
let damaged: Vec< String> = reads
9879
.iter ( )
9880
.filter_map ( | r | r.as_ref ( ) .err ( ) .map ( | e | e.to_string ( ) ) )
9881
.collect ( ) ;
9882
assert_eq! ( damaged.len ( ) , 1 , " {damaged:?}" ) ;
9883
assert! (
9884
damaged[ 0 ] .contains ( " state index was damaged" )
9885
& & damaged[ 0 ] .contains ( " run the command again" ) ,
9886
" {}" ,
9887
damaged[ 0 ]
9888
) ;
9889
9890
for ( i, w) in want.iter ( ) .enumerate ( ) {
9891
assert_eq! ( & repo.file_at ( & format! ( " f{i} .txt" ) , None ) .unwrap ( ) , w) ;
9892
}
9893
}
cb8b8082 Andy McCall 27 days ago ↶
9894
9895
9896
9897
9898
9899
# [ test ]
9900
fn a_long_push_goes_in_parts ( ) {
9901
hermetic ( ) ;
9902
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9903
let root = dir.path ( ) ;
9904
let origin = root.join ( " origin" ) ;
9905
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
9906
let main = root.join ( " main" ) ;
9907
let mut repo = Repo:: init( & main) .unwrap ( ) ;
9908
repo.config
9909
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
9910
.unwrap ( ) ;
9911
9912
repo.config.set ( " push.batch-bytes" , " 1" ) .unwrap ( ) ;
9913
repo.save_config ( ) .unwrap ( ) ;
9914
for i in 0 .. 6 {
9915
write (
9916
& main,
9917
& format! ( " f{i} .txt" ) ,
9918
format! ( " file {i} \n " ) .as_bytes ( ) ,
9919
) ;
9920
repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ;
9921
}
9922
let mut parts = Vec :: new( ) ;
9923
let results = repo
9924
.push_bookmark_with ( " origin" , " main" , false , & mut | m| parts.push ( m.to_string ( ) ) )
9925
.unwrap ( ) ;
9926
assert! (
9927
results.iter ( ) .any ( | r | r.ok & & r.name = = " main" ) ,
9928
" {results:?}"
9929
) ;
9930
assert_eq! ( parts.len ( ) , 6 , " {parts:?}" ) ;
9931
assert_eq! ( parts[ 0 ] , " part 1 of 6: 1 changes" ) ;
9932
9933
let remote = Repo:: open( & origin) .unwrap ( ) ;
9934
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
9935
assert_eq! ( remote.bookmark_heads ( ) .unwrap ( ) , heads) ;
9936
assert_eq! (
9937
remote.log_state ( & heads) .unwrap ( ) .len ( ) ,
9938
repo.log_state ( & heads) .unwrap ( ) .len ( )
9939
) ;
9940
9941
let got = remote
9942
.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ ] )
9943
.unwrap ( ) ;
9944
assert_eq! ( got.len ( ) , 1 ) ;
9945
assert! ( got[ 0 ] .ok & & got[ 0 ] .name = = OBJECTS_RESULT , " {got:?}" ) ;
9946
}
d6e0d5b8 Andy McCall 27 days ago ↶
9947
d3b27090 Andy McCall 13 days ago ↶
9948
9949
9950
9951
9952
# [ test ]
9953
fn a_heavy_change_sends_its_blobs_in_parts ( ) {
9954
hermetic ( ) ;
9955
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9956
let root = dir.path ( ) ;
9957
let origin = root.join ( " origin" ) ;
9958
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
9959
let main = root.join ( " main" ) ;
9960
let mut repo = Repo:: init( & main) .unwrap ( ) ;
9961
repo.config
9962
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
9963
.unwrap ( ) ;
9964
repo.config.set ( " push.batch-bytes" , " 50000" ) .unwrap ( ) ;
9965
repo.save_config ( ) .unwrap ( ) ;
9966
let mut x: u64 = 0x9e3779b97f4a7c15 ;
9967
let mut noise = | n : usize | -> Vec< u8 > {
9968
( 0 .. n)
9969
.map ( | _| {
9970
x ^ = x < < 13 ;
9971
x ^ = x > > 7 ;
9972
x ^ = x < < 17 ;
9973
( x > > 24 ) as u8
9974
} )
9975
.collect ( )
9976
} ;
9977
let files: Vec< Vec< u8 > > = ( 0 .. 4 ) .map ( | _| noise ( 40_000 ) ) .collect ( ) ;
9978
for ( i, f) in files.iter ( ) .enumerate ( ) {
9979
write ( & main, & format! ( " bin{i} .dat" ) , f) ;
9980
}
9981
repo.commit ( " four blobs" , & [ ] ) .unwrap ( ) ;
9982
let mut parts = Vec :: new( ) ;
9983
let results = repo
9984
.push_bookmark_with ( " origin" , " main" , false , & mut | m| parts.push ( m.to_string ( ) ) )
9985
.unwrap ( ) ;
9986
assert! (
9987
results.iter ( ) .any ( | r | r.ok & & r.name = = " main" ) ,
9988
" {results:?}"
9989
) ;
9990
assert! (
9991
parts.len ( ) > = 3 & & parts[ 0 ] .starts_with ( " objects 1 of" ) ,
9992
" {parts:?}"
9993
) ;
9994
let back = Repo:: clone( & origin.to_string_lossy ( ) , & root.join ( " back" ) ) .unwrap ( ) ;
9995
for ( i, f) in files.iter ( ) .enumerate ( ) {
9996
assert_eq! ( & fs:: read( root.join ( format! ( " back/bin{i} .dat" ) ) ) .unwrap ( ) , f) ;
9997
}
9998
drop ( back) ;
9999
}
10000
570e3531 Andy McCall 12 days ago ↶
10001
10002
10003
10004
10005
# [ test ]
10006
fn a_replacement_does_not_send_the_old_content_again ( ) {
10007
hermetic ( ) ;
10008
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10009
let root = dir.path ( ) ;
10010
let origin = root.join ( " origin" ) ;
10011
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
10012
let main = root.join ( " main" ) ;
10013
let mut repo = Repo:: init( & main) .unwrap ( ) ;
10014
repo.config
10015
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
10016
.unwrap ( ) ;
10017
repo.save_config ( ) .unwrap ( ) ;
10018
let blobs = | b : & Bundle| {
10019
b.objects
10020
.iter ( )
10021
.filter ( | ( ty , _) | * ty = = ObjectType:: Blob)
10022
.count ( )
10023
} ;
10024
write ( & main, " bin.dat" , b " \0 \x01 first binary" ) ;
10025
write ( & main, " gone.dat" , b " \0 \x02 to be removed" ) ;
10026
let added = repo.commit ( " add" , & [ ] ) .unwrap ( ) ;
10027
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10028
write ( & main, " bin.dat" , b " \0 \x01 second binary" ) ;
10029
fs:: remove_file( main.join ( " gone.dat" ) ) .unwrap ( ) ;
10030
let changed = repo.commit ( " replace and remove" , & [ ] ) .unwrap ( ) ;
10031
assert_eq! ( blobs ( & repo.bundle_for ( & [ changed] ) .unwrap ( ) ) , 1 ) ;
10032
assert_eq! ( blobs ( & repo.bundle_for ( & [ added, changed] ) .unwrap ( ) ) , 3 ) ;
10033
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10034
let origin = Repo:: open( & origin) .unwrap ( ) ;
10035
assert_eq! ( origin.fsck ( ) .unwrap ( ) .problems, vec! [ ] ) ;
10036
let back = Repo:: clone( & origin.root ( ) .to_string_lossy ( ) , & root.join ( " back" ) ) .unwrap ( ) ;
10037
assert_eq! ( back.fsck ( ) .unwrap ( ) .problems, vec! [ ] ) ;
10038
assert_eq! (
10039
fs:: read( root.join ( " back/bin.dat" ) ) .unwrap ( ) ,
10040
b " \0 \x01 second binary"
10041
) ;
10042
assert! ( ! root.join ( " back/gone.dat" ) .exists ( ) ) ;
10043
}
10044
d6e0d5b8 Andy McCall 27 days ago ↶
10045
10046
10047
10048
# [ test ]
10049
fn git_import_claims_your_own_commits ( ) {
10050
hermetic ( ) ;
10051
if std:: process:: Command:: new( " git" )
10052
.arg ( " --version" )
10053
.output ( )
10054
.is_err ( )
10055
{
10056
eprintln! ( " git not installed; skipping" ) ;
10057
return ;
10058
}
10059
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10060
let root = dir.path ( ) ;
10061
let g = root.join ( " src.git" ) ;
10062
fs:: create_dir_all( & g) .unwrap ( ) ;
10063
let git = | who : & str , args : & [& str ]| {
10064
let ( name, email) = who.split_once ( ' ' ) .unwrap ( ) ;
10065
let out = std:: process:: Command:: new( " git" )
10066
.arg ( " -C" )
10067
.arg ( & g)
10068
.args ( [
10069
" -c" ,
10070
& format! ( " user.name={name} " ) ,
10071
" -c" ,
10072
& format! ( " user.email={email} " ) ,
10073
" -c" ,
10074
" commit.gpgsign=false" ,
10075
] )
10076
.args ( args)
10077
.output ( )
10078
.unwrap ( ) ;
10079
assert! (
10080
out.status.success ( ) ,
10081
" git {args:?}: {}" ,
10082
String :: from_utf8_lossy( & out.stderr)
10083
) ;
10084
} ;
10085
git ( " Grace [email protected] " , & [ " init" , " -q" , " -b" , " main" ] ) ;
10086
write ( & g, " a.txt" , b " a\n " ) ;
10087
git ( " Grace [email protected] " , & [ " add" , " ." ] ) ;
10088
git ( " Grace [email protected] " , & [ " commit" , " -qm" , " mine" ] ) ;
10089
write ( & g, " b.txt" , b " b\n " ) ;
10090
git ( " Ada [email protected] " , & [ " add" , " ." ] ) ;
10091
git ( " Ada [email protected] " , & [ " commit" , " -qm" , " someone else's" ] ) ;
10092
10093
let mut repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
10094
let ( key, _ ) = repo
10095
.create_identity ( " Grace" , " [email protected] " , IdentityScope:: Repo)
10096
.unwrap ( ) ;
10097
repo.config.set ( " git.claim" , " true" ) .unwrap ( ) ;
10098
repo.save_config ( ) .unwrap ( ) ;
10099
let repo = Repo:: open( & root.join ( " o" ) ) .unwrap ( ) ;
10100
repo.import_git ( & g, " main" ) .unwrap ( ) ;
10101
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
10102
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
10103
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
10104
let by_message: std:: collections:: HashMap< String, KeyId> = repo
10105
.log_state ( & heads)
10106
.unwrap ( )
10107
.into_iter ( )
10108
.map ( | e | {
10109
(
10110
e.change.message.lines ( ) .next ( ) .unwrap_or ( " " ) .to_string ( ) ,
10111
e.change.author,
10112
)
10113
} )
10114
.collect ( ) ;
10115
assert_eq! ( by_message[ " mine" ] , key, " her own commit, any case of email" ) ;
10116
assert_eq! ( by_message[ " someone else's" ] , ObjectRef:: ZERO , " not hers" ) ;
10117
}
34fad4a9 Andy McCall 27 days ago ↶
10118
10119
10120
10121
# [ test ]
10122
fn removing_a_remote_leaves_nothing_of_it ( ) {
10123
hermetic ( ) ;
10124
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10125
let origin = dir.path ( ) .join ( " origin" ) ;
10126
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
10127
let main = dir.path ( ) .join ( " main" ) ;
10128
let mut repo = Repo:: init( & main) .unwrap ( ) ;
10129
repo.config
10130
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
10131
.unwrap ( ) ;
10132
repo.config
10133
.set ( " remote.origin.token" , " secret-ish" )
10134
.unwrap ( ) ;
10135
repo.config
10136
.set ( " remote.keep.url" , & origin.to_string_lossy ( ) )
10137
.unwrap ( ) ;
10138
repo.save_config ( ) .unwrap ( ) ;
10139
write ( & main, " a.txt" , b " a\n " ) ;
10140
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10141
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10142
assert! (
10143
repo.view ( ) .unwrap ( ) .remotes.contains_key ( " origin" ) ,
10144
" a tracking bookmark"
10145
) ;
10146
10147
repo.remove_remote ( " origin" ) .unwrap ( ) ;
10148
let again = Repo:: open( & main) .unwrap ( ) ;
10149
assert! (
10150
! again
10151
.config
10152
.repo_layer ( )
10153
.keys ( )
10154
.any ( | k | k.starts_with ( " remote.origin." ) ) ,
10155
" {:?}" ,
10156
again.config.repo_layer ( )
10157
) ;
10158
assert! ( ! again.config.remotes.contains_key ( " origin" ) ) ;
10159
assert! (
10160
! again.view ( ) .unwrap ( ) .remotes.contains_key ( " origin" ) ,
10161
" its tracking bookmarks go"
10162
) ;
10163
assert! (
10164
again.config.remotes.contains_key ( " keep" ) ,
10165
" another remote stays"
10166
) ;
10167
let mut again = again;
10168
assert! (
10169
again.remove_remote ( " origin" ) .is_err ( ) ,
10170
" a second removal is refused"
10171
) ;
10172
}
9d580f21 Andy McCall 26 days ago ↶
10173
10174
10175
10176
10177
# [ test ]
10178
fn gc_keeps_the_blobs_a_host_names ( ) {
10179
hermetic ( ) ;
10180
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10181
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
10182
write ( dir.path ( ) , " a.txt" , b " a\n " ) ;
10183
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10184
10185
let body: Vec< u8 > = ( 0 .. 400_000 u32 )
10186
.flat_map ( | i | ( i.wrapping_mul ( 2654435761 ) ) .to_le_bytes ( ) )
10187
.collect ( ) ;
10188
let kept = repo.store_blob ( & body) .unwrap ( ) ;
10189
let lost = repo.store_blob ( b " an artifact nobody names" ) .unwrap ( ) ;
10190
repo.gc_keeping ( true , & [ kept] , & mut | _ | { } ) .unwrap ( ) ;
10191
assert_eq! ( repo.read_blob ( & kept) .unwrap ( ) , body, " named: kept whole" ) ;
10192
assert! ( ! repo.store ( ) .has ( & lost) , " not named: pruned" ) ;
10193
repo.gc ( true ) .unwrap ( ) ;
10194
assert! ( ! repo.store ( ) .has ( & kept) , " a plain gc prunes it" ) ;
10195
}
530a0fc6 Andy McCall 26 days ago ↶
10196
10197
10198
10199
10200
# [ test ]
10201
fn undo_is_scoped_to_its_workspace ( ) {
10202
hermetic ( ) ;
10203
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10204
let root = dir.path ( ) .join ( " main" ) ;
10205
let a = Repo:: init( & root) .unwrap ( ) ;
10206
write ( & root, " base.txt" , b " base\n " ) ;
10207
let base = a.commit ( " base" , & [ ] ) .unwrap ( ) ;
10208
a.bookmark_create ( " feature" , None ) .unwrap ( ) ;
10209
let ws_dir = dir.path ( ) .join ( " ws" ) ;
10210
let heads = a.resolve_heads ( " feature" ) .unwrap ( ) ;
10211
let b = a
10212
.workspace_add_on ( & ws_dir, Some ( & heads) , Some ( " feature" ) )
10213
.unwrap ( ) ;
10214
10215
10216
write ( & root, " a.txt" , b " a\n " ) ;
10217
let ca = a.commit ( " from a" , & [ ] ) .unwrap ( ) ;
10218
write ( & ws_dir, " b.txt" , b " b\n " ) ;
10219
let cb = b.commit ( " from b" , & [ ] ) .unwrap ( ) ;
10220
10221
10222
let u = a.undo ( ) .unwrap ( ) ;
10223
assert_eq! ( u.command, " oxy commit -m \" from a\" " ) ;
10224
assert_eq! ( u.others_kept, 1 , " B's newer commit is left" ) ;
10225
assert! ( ! u.changes.is_empty ( ) ) ;
10226
let view = a.view ( ) .unwrap ( ) ;
10227
10228
assert_eq! ( a.heads_of ( & view, " main" ) .unwrap ( ) , vec! [ base] ) ;
10229
assert! (
10230
a.heads_of ( & view, " feature" ) .unwrap ( ) .contains ( & cb) ,
10231
" B's commit stands"
10232
) ;
10233
assert! (
10234
a.store ( ) .get_change ( & ca) .is_ok ( ) ,
10235
" the change itself is still in the store"
10236
) ;
10237
10238
assert! ( ! root.join ( " a.txt" ) .exists ( ) ) ;
10239
assert! ( a.snapshot ( " oxy status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
10240
assert! ( ws_dir.join ( " b.txt" ) .exists ( ) ) ;
10241
10242
10243
let u = b.undo ( ) .unwrap ( ) ;
10244
assert_eq! ( u.command, " oxy commit -m \" from b\" " ) ;
10245
assert! ( ! b
10246
.heads_of ( & b.view ( ) .unwrap ( ) , " feature" )
10247
.unwrap ( )
10248
.contains ( & cb) ) ;
10249
assert_eq! (
10250
a.heads_of ( & a.view ( ) .unwrap ( ) , " main" ) .unwrap ( ) ,
10251
vec! [ base] ,
10252
" A's undo stands"
10253
) ;
10254
10255
10256
10257
let e = a.undo ( ) .unwrap_err ( ) .to_string ( ) ;
10258
assert! ( e.contains ( " the workspace at" ) & & e.contains ( " ws" ) , " {e}" ) ;
10259
}
10260
10261
10262
10263
# [ test ]
10264
fn undo_on_a_shared_bookmark_keeps_the_other_workspaces_head ( ) {
10265
hermetic ( ) ;
10266
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10267
let root = dir.path ( ) .join ( " main" ) ;
10268
let a = Repo:: init( & root) .unwrap ( ) ;
10269
write ( & root, " base.txt" , b " base\n " ) ;
10270
a.commit ( " base" , & [ ] ) .unwrap ( ) ;
10271
let ws_dir = dir.path ( ) .join ( " ws" ) ;
10272
let b = a.workspace_add ( & ws_dir, None ) .unwrap ( ) ;
10273
write ( & root, " a.txt" , b " a\n " ) ;
10274
let ca = a.commit ( " from a" , & [ ] ) .unwrap ( ) ;
10275
write ( & ws_dir, " b.txt" , b " b\n " ) ;
10276
let cb = b.commit ( " from b" , & [ ] ) .unwrap ( ) ;
10277
let u = a.undo ( ) .unwrap ( ) ;
10278
assert_eq! ( u.command, " oxy commit -m \" from a\" " ) ;
10279
let heads = a.heads_of ( & a.view ( ) .unwrap ( ) , " main" ) .unwrap ( ) ;
10280
assert! ( heads.contains ( & cb) & & ! heads.contains ( & ca) , " {heads:?}" ) ;
10281
}
10282
10283
10284
10285
# [ test ]
10286
fn undo_refuses_to_take_back_another_workspaces_work ( ) {
10287
hermetic ( ) ;
10288
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10289
let root = dir.path ( ) .join ( " main" ) ;
10290
let a = Repo:: init( & root) .unwrap ( ) ;
10291
write ( & root, " base.txt" , b " base\n " ) ;
10292
a.commit ( " base" , & [ ] ) .unwrap ( ) ;
10293
let ws_dir = dir.path ( ) .join ( " ws" ) ;
10294
let b = a.workspace_add ( & ws_dir, None ) .unwrap ( ) ;
10295
10296
write ( & root, " a.txt" , b " a\n " ) ;
10297
a.commit ( " from a" , & [ ] ) .unwrap ( ) ;
767ac6cd Andy McCall 19 days ago
10298
b.refresh ( ) .unwrap ( ) ;
530a0fc6 Andy McCall 26 days ago ↶
10299
let heads = b.resolve_heads ( " main" ) .unwrap ( ) ;
10300
b.checkout ( & heads, false , " oxy checkout" ) .unwrap ( ) ;
10301
write ( & ws_dir, " a.txt" , b " a, then b\n " ) ;
10302
b.commit ( " from b" , & [ ] ) .unwrap ( ) ;
10303
let e = a.undo ( ) .unwrap_err ( ) .to_string ( ) ;
10304
assert! (
10305
e.contains ( " bookmark main" ) & & e.contains ( " builds on" ) ,
10306
" {e}"
10307
) ;
10308
assert! ( e.contains ( " oxy op restore" ) , " {e}" ) ;
10309
}
10310
10311
10312
10313
# [ test ]
10314
fn undo_says_when_the_commit_was_pushed ( ) {
10315
hermetic ( ) ;
10316
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10317
let root = dir.path ( ) ;
10318
Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
10319
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
10320
let repo = Repo:: clone( & url, & root.join ( " work" ) )
10321
.unwrap ( )
10322
.with_home ( root.join ( " home" ) ) ;
10323
write ( & root.join ( " work" ) , " a.txt" , b " a\n " ) ;
10324
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10325
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10326
10327
let u = repo.undo ( ) .unwrap ( ) ;
10328
assert_eq! ( u.command, " oxy commit -m \" a\" " ) ;
10329
assert_eq! ( u.pushed, vec! [ ( " main" .to_string ( ) , " origin" .to_string ( ) ) ] ) ;
10330
}
b689779e Andy McCall 26 days ago ↶
10331
10332
10333
10334
10335
# [ test ]
10336
fn large_new_files_are_left_out_of_snapshots ( ) {
10337
hermetic ( ) ;
10338
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10339
let root = dir.path ( ) ;
10340
let mut repo = Repo:: init( root) .unwrap ( ) ;
10341
repo.config.max_new_file = 1024 ;
10342
write ( root, " small.txt" , b " small\n " ) ;
10343
write ( root, " big.bin" , & vec! [ 0 u8 ; 4096 ] ) ;
10344
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10345
let paths: Vec< _ > = s.statuses.iter ( ) .map ( | p | p.path.as_str ( ) ) .collect ( ) ;
10346
assert_eq! ( paths, [ " small.txt" ] ) ;
10347
assert_eq! ( repo.skipped_large ( ) , vec! [ ( " big.bin" .to_string ( ) , 4096 ) ] ) ;
10348
repo.commit ( " small" , & [ ] ) .unwrap ( ) ;
10349
10350
10351
write ( root, " small.txt" , & vec! [ b ' x' ; 4096 ] ) ;
10352
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10353
assert! ( s.statuses.iter ( ) .any ( | p | p.path = = " small.txt" ) ) ;
10354
10355
repo.config.max_new_file = 0 ;
10356
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10357
assert! ( s.statuses.iter ( ) .any ( | p | p.path = = " big.bin" ) ) ;
10358
repo.config.max_new_file = 1024 ;
10359
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10360
assert! (
10361
s.statuses.iter ( ) .any ( | p | p.path = = " big.bin" ) ,
10362
" already in the working copy"
10363
) ;
10364
assert! ( repo.skipped_large ( ) .is_empty ( ) ) ;
10365
}
10366
10367
10368
10369
# [ test ]
10370
fn gc_prunes_what_only_expired_snapshots_held ( ) {
10371
hermetic ( ) ;
10372
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10373
let root = dir.path ( ) ;
10374
let mut repo = Repo:: init( root) .unwrap ( ) ;
10375
repo.config.gc_grace_days = 0 ;
10376
write ( root, " a.txt" , b " a\n " ) ;
10377
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10378
let image: Vec< u8 > = ( 0 .. 200_000 u32 ) .map ( | i | ( i % 7 ) as u8 ) .collect ( ) ;
10379
write ( root, " image.tar" , & image) ;
10380
repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10381
std:: fs:: remove_file( root.join ( " image.tar" ) ) .unwrap ( ) ;
10382
repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10383
let blob = Repo:: blob_ref( & image) ;
10384
assert! ( repo.store ( ) .has ( & blob) ) ;
10385
repo.gc ( true ) .unwrap ( ) ;
10386
assert! ( repo.store ( ) .has ( & blob) , " a recent snapshot keeps it" ) ;
10387
repo.config.snapshot_retention_days = 0 ;
10388
repo.gc ( true ) .unwrap ( ) ;
10389
assert! ( ! repo.store ( ) .has ( & blob) , " an expired snapshot does not" ) ;
10390
assert_eq! (
10391
repo.file_at ( " a.txt" , None ) .unwrap ( ) ,
10392
b " a\n " ,
10393
" committed files stay"
10394
) ;
fe9586d9 Andy McCall 25 days ago ↶
10395
}
10396
10397
10398
10399
10400
# [ test ]
10401
fn carrying_work_the_target_already_has_is_no_conflict ( ) {
10402
hermetic ( ) ;
10403
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10404
let root = dir.path ( ) ;
10405
let mut repo = Repo:: init( root) .unwrap ( ) ;
10406
write ( root, " a.txt" , b " one\n " ) ;
10407
write ( root, " b.txt" , b " b\n " ) ;
10408
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
10409
repo.bookmark_create ( " landed" , None ) .unwrap ( ) ;
10410
10411
repo.bookmark_switch ( " landed" , false ) .unwrap ( ) ;
10412
write ( root, " a.txt" , b " two\n " ) ;
10413
repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
10414
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
10415
assert_eq! ( std:: fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n " ) ;
10416
10417
write ( root, " a.txt" , b " two\n " ) ;
10418
write ( root, " b.txt" , b " b, edited\n " ) ;
10419
let ( _ , carried) = repo.bookmark_switch_carry ( " landed" ) .unwrap ( ) ;
10420
assert_eq! (
10421
carried,
10422
vec! [ " b.txt" .to_string ( ) ] ,
10423
" only the work landed lacks is carried"
10424
) ;
10425
assert_eq! ( std:: fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " two\n " ) ;
10426
assert_eq! ( std:: fs:: read( root.join ( " b.txt" ) ) .unwrap ( ) , b " b, edited\n " ) ;
10427
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10428
let paths: Vec< _ > = s.statuses.iter ( ) .map ( | p | p.path.as_str ( ) ) .collect ( ) ;
10429
assert_eq! ( paths, [ " b.txt" ] ) ;
10430
10431
10432
repo.bookmark_switch ( " main" , true ) .unwrap ( ) ;
10433
write ( root, " a.txt" , b " three\n " ) ;
10434
let e = repo
10435
.bookmark_switch_carry ( " landed" )
10436
.unwrap_err ( )
10437
.to_string ( ) ;
10438
assert! ( e.contains ( " a.txt" ) & & e.contains ( " --discard" ) , " {e}" ) ;
b689779e Andy McCall 26 days ago ↶
10439
}
aa4939e0 Andy McCall 25 days ago ↶
10440
10441
10442
10443
# [ test ]
10444
fn a_full_hash_resolves_without_its_hash_sign ( ) {
10445
hermetic ( ) ;
10446
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10447
let root = dir.path ( ) ;
10448
let repo = Repo:: init( root) .unwrap ( ) ;
10449
write ( root, " a.txt" , b " a\n " ) ;
10450
let h = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10451
assert_eq! ( repo.resolve_change ( & h.to_hex ( ) ) .unwrap ( ) , h) ;
10452
assert_eq! ( repo.resolve_change ( & format! ( " #{} " , h.to_hex ( ) ) ) .unwrap ( ) , h) ;
10453
assert! ( repo
10454
.resolve_change ( & " 0" .repeat ( 64 ) )
10455
.unwrap_err ( )
10456
.to_string ( )
10457
.contains ( " no change matches" ) ) ;
10458
}
f2f91ceb Andy McCall 24 days ago ↶
10459
10460
# [ test ]
10461
fn a_pin_change_moves_only_the_pin ( ) {
10462
hermetic ( ) ;
10463
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10464
let root = dir.path ( ) ;
10465
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
10466
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
10467
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
10468
.unwrap ( )
10469
.with_home ( root.join ( " home" ) ) ;
10470
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
10471
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
10472
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10473
let _app_origin = Repo:: init( & root.join ( " app-origin" ) ) .unwrap ( ) ;
10474
let app_dir = root.join ( " app" ) ;
10475
let app = Repo:: clone( & root.join ( " app-origin" ) .to_string_lossy ( ) , & app_dir)
10476
.unwrap ( )
10477
.with_home ( root.join ( " home" ) ) ;
10478
write ( & app_dir, " main.txt" , b " app\n " ) ;
10479
let pin1 = app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
10480
let a1 = app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
10481
10482
write ( & root.join ( " lib" ) , " more.txt" , b " v2\n " ) ;
10483
let l2 = lib.commit ( " lib v2" , & [ ] ) .unwrap ( ) ;
10484
let crate :: subrepo:: Pin:: Oxy { repo, bookmark, .. } = pin1 else {
10485
panic! ( " an Oxygen pin" )
10486
} ;
10487
let pin2 = crate :: subrepo:: Pin:: Oxy {
10488
repo,
10489
heads: vec! [ l2] ,
10490
bookmark,
10491
} ;
10492
let author = app.store ( ) .get_change ( & a1) .unwrap ( ) .author;
10493
let change = app
10494
.build_pin_change ( & [ a1] , " libs/lib" , & pin2, " Move lib to v2" , author)
10495
.unwrap ( ) ;
10496
let mut state = app.load_state ( & [ a1] ) .unwrap ( ) ;
10497
state.add ( change) .unwrap ( ) ;
10498
let files = state.materialize ( ) .files;
10499
assert_eq! ( files[ " libs/lib" ] .kind, NodeKind:: Subrepo) ;
10500
let FileContent:: Opaque { blobs } = & files[ " libs/lib" ] .content else {
10501
panic! ( " a pin is opaque" )
10502
} ;
10503
let read = crate :: subrepo:: Pin:: decode( & app.read_blob ( & blobs[ 0 ] ) .unwrap ( ) ) .unwrap ( ) ;
10504
assert_eq! ( read, pin2, " the new pin" ) ;
10505
assert! (
10506
matches! ( & files[ " main.txt" ] .content, FileContent:: Text { bytes, .. } if bytes.as_slice ( ) = = b " app\n " ) ,
10507
" nothing else changes"
10508
) ;
10509
10510
let e = app.build_pin_change ( & [ a1] , " main.txt" , & pin2, " x" , author) ;
10511
assert! ( format! ( " {:?} " , e.unwrap_err ( ) ) .contains ( " not a subrepo" ) ) ;
10512
let e = app.build_pin_change ( & [ a1] , " libs/none" , & pin2, " x" , author) ;
10513
assert! ( format! ( " {:?} " , e.unwrap_err ( ) ) .contains ( " does not exist" ) ) ;
10514
}
767ac6cd Andy McCall 19 days ago
10515
10516
10517
10518
10519
10520
10521
# [ test ]
10522
fn one_handle_keeps_one_head_until_it_refreshes ( ) {
10523
hermetic ( ) ;
10524
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10525
let root = dir.path ( ) ;
10526
let writer = Repo:: init( root) .unwrap ( ) ;
10527
write ( root, " a.txt" , b " one\n " ) ;
10528
let h1 = writer.commit ( " one" , & [ ] ) .unwrap ( ) ;
10529
let reader = Repo:: open( root) .unwrap ( ) ;
10530
let before = reader.view ( ) .unwrap ( ) ;
10531
assert_eq! ( reader.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
10532
write ( root, " a.txt" , b " two\n " ) ;
10533
let h2 = writer.commit ( " two" , & [ ] ) .unwrap ( ) ;
10534
assert_eq! (
10535
writer.bookmark_heads ( ) .unwrap ( ) ,
10536
vec! [ h2] ,
10537
" its own operation"
10538
) ;
10539
assert_eq! ( reader.view ( ) .unwrap ( ) , before, " the head it read first" ) ;
10540
assert_eq! ( reader.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
10541
reader.refresh ( ) .unwrap ( ) ;
10542
assert_eq! ( reader.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
10543
10544
10545
let stale = Repo:: open( root) .unwrap ( ) ;
10546
assert_eq! ( stale.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
10547
write ( root, " a.txt" , b " three\n " ) ;
10548
let h3 = writer.commit ( " three" , & [ ] ) .unwrap ( ) ;
10549
stale.bookmark_create ( " feature" , Some ( & [ h1] ) ) .unwrap ( ) ;
10550
let view = stale.view ( ) .unwrap ( ) ;
10551
assert! ( view.bookmarks.contains_key ( " feature" ) ) ;
10552
assert_eq! (
10553
stale.bookmark_heads ( ) .unwrap ( ) ,
10554
vec! [ h3] ,
10555
" merged, not lost"
10556
) ;
10557
let fresh = Repo:: open( root) .unwrap ( ) ;
10558
assert_eq! ( fresh.view ( ) .unwrap ( ) , view) ;
10559
}
10560
10561
10562
# [ test ]
10563
fn over_a_store_one_handle_keeps_one_head ( ) {
10564
hermetic ( ) ;
10565
let backend = std:: sync:: Arc:: new( crate :: strata:: MemoryStore:: new( ) ) ;
10566
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = Some ( backend.clone ( ) ) ) ;
10567
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10568
let a = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
10569
let ( h0, _ ) = a.head_op ( ) .unwrap ( ) ;
10570
let b = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
10571
assert_eq! ( b.head_op ( ) .unwrap ( ) .0 , h0) ;
10572
let state = a.store.put_state ( & StateObject:: new( vec! [ ] ) ) .unwrap ( ) ;
10573
let mut view = a.view ( ) .unwrap ( ) ;
10574
view.bookmarks.insert ( " x" .into ( ) , state) ;
10575
let r = a.record_op ( & [ h0] , " bookmark x" , view) .unwrap ( ) ;
10576
assert_eq! ( a.head_op ( ) .unwrap ( ) .0 , r) ;
10577
assert_eq! ( b.head_op ( ) .unwrap ( ) .0 , h0, " b keeps its head" ) ;
10578
b.refresh ( ) .unwrap ( ) ;
10579
assert_eq! ( b.head_op ( ) .unwrap ( ) .0 , r) ;
10580
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = None ) ;
10581
}
10582
10583
10584
10585
10586
# [ test ]
10587
fn gc_retires_and_a_later_gc_deletes_after_the_hour ( ) {
10588
hermetic ( ) ;
10589
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10590
let root = dir.path ( ) ;
10591
let repo = Repo:: init( root) .unwrap ( ) ;
10592
let mut heads = Vec :: new( ) ;
10593
for i in 0 .. 4 {
10594
write ( root, & format! ( " f{i} .txt" ) , format! ( " {i} \n " ) .as_bytes ( ) ) ;
10595
heads.push ( repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ) ;
10596
}
10597
let reader = Repo:: open( root) .unwrap ( ) ;
10598
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 5 ) ;
10599
let r = repo.gc ( true ) .unwrap ( ) ;
10600
assert_eq! ( r.retired, crate :: store:: RetiredReport:: default( ) ) ;
10601
assert_eq! ( repo.store ( ) .loose_count ( ) .unwrap ( ) , 0 ) ;
10602
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 5 , " read on from what it had" ) ;
10603
10604
let r = repo.gc ( true ) .unwrap ( ) ;
10605
assert_eq! ( ( r.retired.packs, r.retired.loose) , ( 0 , 0 ) ) ;
10606
assert! ( r.retired.kept > 0 , " {r:?}" ) ;
10607
write ( root, " f9.txt" , b " 9\n " ) ;
10608
heads.push ( repo.commit ( " c9" , & [ ] ) .unwrap ( ) ) ;
10609
crate :: store:: reader_tests:: age_retired(
10610
repo.store ( ) ,
10611
crate :: store:: RETIRED_FOR + std:: time:: Duration:: from_secs( 60 ) ,
10612
) ;
10613
let r = repo.gc ( true ) .unwrap ( ) ;
10614
assert! ( r.retired.packs > = 1 & & r.retired.loose > 0 , " {r:?}" ) ;
10615
assert_eq! ( r.retired.kept, 0 , " {r:?}" ) ;
10616
let r = repo.gc ( true ) .unwrap ( ) ;
10617
assert_eq! ( ( r.retired.packs, r.retired.loose) , ( 0 , 0 ) ) ;
10618
assert! ( r.retired.kept > 0 , " what the last gc retired is young" ) ;
10619
10620
10621
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 5 ) ;
10622
for h in & heads[ .. 4 ] {
10623
reader.store ( ) .get_change ( h) .unwrap ( ) ;
10624
}
10625
reader.refresh ( ) .unwrap ( ) ;
10626
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 6 ) ;
10627
let fresh = Repo:: open( root) .unwrap ( ) ;
10628
assert_eq! (
10629
fresh.store ( ) .pack_count ( ) ,
10630
1 ,
10631
" retired packs are not loaded"
10632
) ;
10633
}
10634
10635
10636
10637
# [ test ]
10638
fn a_repository_opens_when_a_listed_index_is_gone ( ) {
10639
hermetic ( ) ;
10640
let backend = crate :: store:: reader_tests:: racing( true ) ;
10641
let dyn_backend: std:: sync:: Arc< crate:: store:: Backend> = backend.clone ( ) ;
10642
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = Some ( dyn_backend) ) ;
10643
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10644
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
10645
write ( dir.path ( ) , " a.txt" , b " a\n " ) ;
10646
let h = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10647
repo.gc ( true ) .unwrap ( ) ;
10648
let again = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
10649
assert_eq! ( again.store ( ) .pack_count ( ) , 1 ) ;
10650
assert_eq! ( again.bookmark_heads ( ) .unwrap ( ) , vec! [ h] ) ;
10651
10652
10653
use crate :: strata:: Store as _ ;
10654
let idx = backend
10655
.list ( " pack-" )
10656
.unwrap ( )
10657
.into_iter ( )
10658
.map ( | e | e.key )
10659
.find ( | k | k.ends_with ( " .idx" ) & & k ! = " pack-0000.idx" )
10660
.unwrap ( ) ;
10661
let stem = idx.trim_end_matches ( " .idx" ) ;
10662
for ext in [ " pack" , " idx" ] {
10663
let bytes = backend.get ( & format! ( " {stem} .{ext} " ) ) .unwrap ( ) ;
10664
backend
10665
.put_new ( & format! ( " pack-copy.{ext} " ) , & bytes)
10666
.unwrap ( ) ;
10667
}
10668
* backend.doomed.lock ( ) .unwrap ( ) = Some ( " pack-copy.idx" .into ( ) ) ;
10669
let again = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
10670
assert_eq! ( again.store ( ) .pack_count ( ) , 1 ) ;
10671
assert_eq! ( again.bookmark_heads ( ) .unwrap ( ) , vec! [ h] ) ;
10672
assert! ( ! backend.has ( " pack-copy.idx" ) .unwrap ( ) ) ;
10673
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = None ) ;
10674
}
2452b440 Andy McCall 11 days ago ↶
10675
10676
10677
10678
10679
10680
# [ test ]
10681
fn author_for_follows_the_key_the_hub_named ( ) {
10682
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10683
let home = dir.path ( ) .join ( " home" ) ;
10684
fs:: create_dir_all( home.join ( " keys" ) ) .unwrap ( ) ;
10685
let user = home.join ( " config.toml" ) ;
10686
let repo_cfg = dir.path ( ) .join ( " config" ) ;
10687
let ( old, new, pin) = (
10688
KeyId:: from_hex( & " a" .repeat ( 64 ) ) .unwrap ( ) ,
10689
KeyId:: from_hex( & " b" .repeat ( 64 ) ) .unwrap ( ) ,
10690
KeyId:: from_hex( & " c" .repeat ( 64 ) ) .unwrap ( ) ,
10691
) ;
10692
fs:: write( & user, format! ( " author.key = \" {} \" \n " , old.to_hex ( ) ) ) .unwrap ( ) ;
10693
fs:: write(
10694
home.join ( " keys" ) .join ( super :: config:: AUTHOR_FOR_FILE ) ,
10695
format! ( " {} \n " , new.to_hex ( ) ) ,
10696
)
10697
.unwrap ( ) ;
10698
fs:: write( & repo_cfg, " " ) .unwrap ( ) ;
10699
assert_eq! (
10700
Config:: load( Some ( & user) , & repo_cfg) .unwrap ( ) .author_key,
10701
Some ( old) ,
10702
" no author.for: the note is ignored"
10703
) ;
10704
fs:: write(
10705
& user,
10706
format! (
10707
" author.key = \" {} \" \n author.for = \" andymccall\" \n " ,
10708
old.to_hex ( )
10709
) ,
10710
)
10711
.unwrap ( ) ;
10712
assert_eq! (
10713
Config:: load( Some ( & user) , & repo_cfg) .unwrap ( ) .author_key,
10714
Some ( new)
10715
) ;
10716
fs:: write( & repo_cfg, format! ( " author.key = \" {} \" \n " , pin.to_hex ( ) ) ) .unwrap ( ) ;
10717
assert_eq! (
10718
Config:: load( Some ( & user) , & repo_cfg) .unwrap ( ) .author_key,
10719
Some ( pin) ,
10720
" a repository's pin wins"
10721
) ;
10722
}