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}" ) ;
0912a033 Andy McCall 1 month ago ↶
137
assert! (
138
d.lines ( )
139
.any ( | l | l.starts_with ( " M " ) & & l.ends_with ( " main.rs" ) ) ,
140
" a modified file is in the summary too: {d}"
141
) ;
98286406 Andy McCall 1 month ago ↶
142
assert! ( d.contains ( " + println!(\" hi\" );" ) , " {d}" ) ;
143
assert! ( d.contains ( " -fn main() {}" ) , " {d}" ) ;
144
145
146
assert_eq! ( repo.resolve_change ( & c2.change_id.short ( 6 ) ) .unwrap ( ) , h2) ;
75167672 Andy McCall 1 month ago
147
assert_eq! (
148
repo.resolve_change ( & format! ( " #{} " , h1.short ( 8 ) ) ) .unwrap ( ) ,
149
h1
150
) ;
98286406 Andy McCall 1 month ago ↶
151
assert! ( repo.resolve_change ( " zzzz" ) .is_err ( ) ) ;
152
153
154
repo.undo ( ) .unwrap ( ) ;
155
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
156
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
157
assert! (
158
! s.changed,
159
" files on disk still match the pre-commit working copy"
160
) ;
98286406 Andy McCall 1 month ago ↶
161
assert_eq! ( s.statuses.len ( ) , 3 ) ;
162
}
163
164
165
fn conflicted_repo ( ) -> ( tempfile:: TempDir, Repo, ChangeHash, ChangeHash, ChangeHash) {
166
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
167
let root = dir.path ( ) ;
168
let repo = Repo:: init( root) .unwrap ( ) ;
169
write ( root, " f.txt" , b " a\n b\n c\n " ) ;
170
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
171
write ( root, " f.txt" , b " a\n B\n c\n " ) ;
172
let h2 = repo.commit ( " upper" , & [ ] ) .unwrap ( ) ;
173
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
174
write ( root, " f.txt" , b " a\n X\n c\n " ) ;
175
let h3 = repo.commit ( " ex" , & [ ] ) .unwrap ( ) ;
176
( dir, repo, h1, h2, h3)
177
}
178
3652104a Andy McCall 17 days ago ↶
179
180
181
98286406 Andy McCall 1 month ago ↶
182
# [ test ]
3652104a Andy McCall 17 days ago ↶
183
fn marker_like_text_does_not_block_a_resolution ( ) {
184
hermetic ( ) ;
185
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
186
let root = dir.path ( ) ;
187
let repo = Repo:: init( root) .unwrap ( ) ;
188
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 " ;
189
write (
190
root,
191
" doc.md" ,
192
format! ( " intro\n {example} b\n end\n " ) .as_bytes ( ) ,
193
) ;
194
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
195
write (
196
root,
197
" doc.md" ,
198
format! ( " intro\n {example} B\n end\n " ) .as_bytes ( ) ,
199
) ;
200
repo.commit ( " upper" , & [ ] ) .unwrap ( ) ;
201
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
202
write (
203
root,
204
" doc.md" ,
205
format! ( " intro\n {example} X\n end\n " ) .as_bytes ( ) ,
206
) ;
207
repo.commit ( " ex" , & [ ] ) .unwrap ( ) ;
208
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
209
.unwrap ( ) ;
210
let marked = fs:: read_to_string( root.join ( " doc.md" ) ) .unwrap ( ) ;
211
assert! ( marked.contains ( " oxy conflict 1 of 1" ) , " {marked}" ) ;
212
213
write ( root, " doc.md" , marked.replace ( " B\n " , " BB\n " ) .as_bytes ( ) ) ;
214
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
215
assert_eq! (
216
statuses ( & s) ,
217
vec! [ ( " doc.md" .into ( ) , ChangeStatus:: ConflictEdited) ]
218
) ;
219
220
write (
221
root,
222
" doc.md" ,
223
format! ( " intro\n {example} X\n end\n " ) .as_bytes ( ) ,
224
) ;
225
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
226
assert_eq! (
227
statuses ( & s) ,
228
vec! [ ( " doc.md" .into ( ) , ChangeStatus:: Resolved) ]
229
) ;
230
let h = repo.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
231
assert_eq! (
232
repo.store ( ) .get_change ( & h) .unwrap ( ) .kind,
233
ChangeKind:: Resolution
234
) ;
235
assert! ( ! repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) .is_conflicted ( ) ) ;
236
}
237
9bd98cfb Andy McCall 10 days ago ↶
238
239
240
3652104a Andy McCall 17 days ago ↶
241
# [ test ]
9bd98cfb Andy McCall 10 days ago ↶
242
fn adjacent_insertions_are_settled_by_keeping_both ( ) {
243
hermetic ( ) ;
244
for reverse in [ false , true ] {
245
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
246
let root = dir.path ( ) ;
247
let repo = Repo:: init( root) .unwrap ( ) ;
248
write ( root, " f.txt" , b " a\n b\n " ) ;
249
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
250
write ( root, " f.txt" , b " a\n first\n b\n " ) ;
251
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
252
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
253
write ( root, " f.txt" , b " a\n second\n b\n " ) ;
254
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
255
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
256
.unwrap ( ) ;
257
let marked = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
258
let ( f, s) = (
259
marked.find ( " first\n " ) .unwrap ( ) ,
260
marked.find ( " second\n " ) .unwrap ( ) ,
261
) ;
262
assert! ( f < s, " the earlier change's side comes first: {marked}" ) ;
263
264
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
265
let node = snap.base_snapshot.files[ " f.txt" ] .node;
266
assert_eq! ( snap.base.both_sides ( & node) .iter ( ) .flatten ( ) .count ( ) , 1 ) ;
267
let kept = repo.keep_both ( & snap, & [ ] , reverse) .unwrap ( ) ;
268
assert_eq! (
269
kept,
270
vec! [ KeptBoth {
271
path: " f.txt" .into ( ) ,
272
settled: vec! [ 1 ] ,
c0d4075c Andy McCall 9 days ago ↶
273
identical: vec! [ ] ,
9bd98cfb Andy McCall 10 days ago ↶
274
left: vec! [ ]
275
} ]
276
) ;
277
let want: & [ u8 ] = if reverse {
278
b " a\n second\n first\n b\n "
279
} else {
280
b " a\n first\n second\n b\n "
281
} ;
282
assert_eq! ( fs:: read( root.join ( " f.txt" ) ) .unwrap ( ) , want) ;
283
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
284
assert_eq! ( statuses ( & s) , vec! [ ( " f.txt" .into ( ) , ChangeStatus:: Resolved) ] ) ;
285
let h = repo.commit ( " both" , & [ ] ) .unwrap ( ) ;
286
assert_eq! (
287
repo.store ( ) .get_change ( & h) .unwrap ( ) .kind,
288
ChangeKind:: Resolution
289
) ;
290
assert! ( ! repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) .is_conflicted ( ) ) ;
291
}
292
}
293
4afe288b Andy McCall 10 days ago ↶
294
295
296
297
# [ test ]
298
fn union_files_keep_both_sides_on_checkout ( ) {
299
hermetic ( ) ;
300
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
301
let root = dir.path ( ) ;
302
let repo = Repo:: init( root) .unwrap ( ) ;
303
write ( root, " .oxyattributes" , b " CHANGELOG.md merge=union\n " ) ;
304
write (
305
root,
306
" CHANGELOG.md" ,
307
b " # Changelog\n ## Unreleased\n ## 1.0\n " ,
308
) ;
309
write ( root, " f.txt" , b " a\n b\n " ) ;
310
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
311
write (
312
root,
313
" CHANGELOG.md" ,
314
b " # Changelog\n ## Unreleased\n - one\n ## 1.0\n " ,
315
) ;
316
write ( root, " f.txt" , b " a\n one\n b\n " ) ;
317
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
318
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
319
write (
320
root,
321
" CHANGELOG.md" ,
322
b " # Changelog\n ## Unreleased\n - two\n ## 1.0\n " ,
323
) ;
324
write ( root, " f.txt" , b " a\n two\n b\n " ) ;
325
repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
326
let r = repo
327
.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
328
.unwrap ( ) ;
329
assert! ( r.conflicted) ;
330
assert_eq! ( r.kept_both.len ( ) , 1 , " {:?}" , r.kept_both) ;
331
assert_eq! ( r.kept_both[ 0 ] .path, " CHANGELOG.md" ) ;
332
assert_eq! (
333
fs:: read_to_string( root.join ( " CHANGELOG.md" ) ) .unwrap ( ) ,
334
" # Changelog\n ## Unreleased\n - one\n - two\n ## 1.0\n "
335
) ;
336
assert! ( has_conflict_markers ( & fs:: read( root.join ( " f.txt" ) ) .unwrap ( ) ) ) ;
337
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
338
assert_eq! (
339
statuses ( & s) ,
340
vec! [ ( " CHANGELOG.md" .into ( ) , ChangeStatus:: Resolved) ]
341
) ;
342
}
343
c0d4075c Andy McCall 9 days ago ↶
344
345
346
347
348
# [ test ]
349
fn identical_resolutions_on_two_bookmarks_settle_by_themselves ( ) {
350
hermetic ( ) ;
351
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
352
let root = dir.path ( ) ;
353
let repo = Repo:: init( root) .unwrap ( ) ;
354
write ( root, " f.txt" , b " a\n x\n b\n " ) ;
355
let base = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
356
write ( root, " f.txt" , b " a\n X1\n b\n " ) ;
357
let c1 = repo.commit ( " X1" , & [ ] ) .unwrap ( ) ;
358
repo.checkout ( & [ base] , false , " checkout" ) .unwrap ( ) ;
359
write ( root, " f.txt" , b " a\n X2\n b\n " ) ;
360
let c2 = repo.commit ( " X2" , & [ ] ) .unwrap ( ) ;
361
362
repo.checkout ( & [ c1, c2] , false , " checkout" ) .unwrap ( ) ;
363
assert! ( has_conflict_markers ( & fs:: read( root.join ( " f.txt" ) ) .unwrap ( ) ) ) ;
364
write ( root, " f.txt" , b " a\n X\n b\n " ) ;
365
let r1 = repo.commit ( " resolve on one" , & [ ] ) .unwrap ( ) ;
366
repo.checkout ( & [ c1, c2] , false , " checkout" ) .unwrap ( ) ;
367
write ( root, " f.txt" , b " a\n X\n b\n " ) ;
368
let r2 = repo.commit ( " resolve on two" , & [ ] ) .unwrap ( ) ;
369
assert_ne! ( r1, r2) ;
370
371
let st = repo.load_state ( & [ r1, r2] ) .unwrap ( ) ;
372
assert! (
373
st.materialize ( ) .is_conflicted ( ) ,
374
" two resolutions, one place"
375
) ;
376
assert_eq! (
377
Repo:: union_resolution( & st, & st.materialize ( ) ) ,
378
Some ( vec! [ ( " f.txt" .into ( ) , b " a\n X\n b\n " .to_vec ( ) ) ] ) ,
379
" a host settles it"
380
) ;
381
let r = repo.checkout ( & [ r1, r2] , false , " checkout" ) .unwrap ( ) ;
382
assert! ( r.conflicted) ;
383
assert_eq! ( r.kept_both.len ( ) , 1 , " {:?}" , r.kept_both) ;
384
assert_eq! (
385
( r.kept_both[ 0 ] .path.as_str ( ) , & r.kept_both[ 0 ] .identical) ,
386
( " f.txt" , & vec! [ 1 ] )
387
) ;
388
assert_eq! ( fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) , " a\n X\n b\n " ) ;
389
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
390
assert_eq! ( statuses ( & s) , vec! [ ( " f.txt" .into ( ) , ChangeStatus:: Resolved) ] ) ;
391
let merged = repo.commit ( " merge" , & [ ] ) .unwrap ( ) ;
392
let st = repo.load_state ( & [ merged] ) .unwrap ( ) ;
393
assert! ( ! st.materialize ( ) .is_conflicted ( ) ) ;
394
}
395
ac4ec124 Andy McCall 10 days ago ↶
396
397
398
399
# [ test ]
400
fn a_host_settles_a_state_whose_conflicts_are_all_union ( ) {
401
hermetic ( ) ;
402
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
403
let root = dir.path ( ) ;
404
let repo = Repo:: init( root) .unwrap ( ) ;
405
write ( root, " .oxyattributes" , b " *.md merge=union\n " ) ;
406
write ( root, " A.md" , b " a\n b\n " ) ;
407
write ( root, " B.md" , b " x\n y\n " ) ;
408
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
409
write ( root, " A.md" , b " a\n one\n b\n " ) ;
410
write ( root, " B.md" , b " x\n Y1\n " ) ;
411
let h2 = repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
412
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
413
write ( root, " A.md" , b " a\n two\n b\n " ) ;
414
let h3 = repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
415
let st = repo.load_state ( & [ h2, h3] ) .unwrap ( ) ;
416
assert_eq! (
417
Repo:: union_resolution( & st, & st.materialize ( ) ) ,
418
Some ( vec! [ ( " A.md" .into ( ) , b " a\n one\n two\n b\n " .to_vec ( ) ) ] )
419
) ;
420
421
repo.checkout ( & [ h3] , false , " checkout" ) .unwrap ( ) ;
422
write ( root, " B.md" , b " x\n Y2\n " ) ;
423
let h4 = repo.commit ( " Y2" , & [ ] ) .unwrap ( ) ;
424
let st = repo.load_state ( & [ h2, h4] ) .unwrap ( ) ;
425
assert! ( st.materialize ( ) .is_conflicted ( ) ) ;
426
assert_eq! ( Repo:: union_resolution( & st, & st.materialize ( ) ) , None ) ;
427
}
428
9bd98cfb Andy McCall 10 days ago ↶
429
430
431
432
# [ test ]
433
fn keeping_both_refuses_replaced_or_edited_lines ( ) {
434
hermetic ( ) ;
435
let ( dir, repo, _ , _ , _ ) = conflicted_repo ( ) ;
436
let root = dir.path ( ) ;
437
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
438
.unwrap ( ) ;
439
let marked = fs:: read( root.join ( " f.txt" ) ) .unwrap ( ) ;
440
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
441
let node = snap.base_snapshot.files[ " f.txt" ] .node;
442
assert_eq! ( snap.base.both_sides ( & node) , vec! [ None ] ) ;
443
let kept = repo.keep_both ( & snap, & [ " f.txt" .into ( ) ] , false ) .unwrap ( ) ;
444
assert! ( kept[ 0 ] .settled.is_empty ( ) ) ;
445
assert_eq! ( kept[ 0 ] .left[ 0 ] .0 , 1 ) ;
446
assert! ( kept[ 0 ] .left[ 0 ] .1. contains ( " both sides changed" ) , " {kept:?}" ) ;
447
assert_eq! ( fs:: read( root.join ( " f.txt" ) ) .unwrap ( ) , marked) ;
448
449
450
let sides = vec! [ Some ( vec! [ b " x\n " .to_vec ( ) , b " y\n " .to_vec ( ) ] ) ] ;
451
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 " ;
452
let ( out, settled, left) = super :: snapshot:: keep_both_text( text, & sides, false ) ;
453
assert_eq! ( out, text.to_vec ( ) ) ;
454
assert! ( settled.is_empty ( ) ) ;
455
assert! ( left[ 0 ] .1. contains ( " edited" ) , " {left:?}" ) ;
456
457
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 " ;
458
let ( out, settled, _ ) = super :: snapshot:: keep_both_text( text, & sides, false ) ;
459
assert_eq! ( out, b " a\n x\n y\n b\n " .to_vec ( ) ) ;
460
assert_eq! ( settled, vec! [ 1 ] ) ;
461
}
462
463
# [ test ]
98286406 Andy McCall 1 month ago ↶
464
fn concurrent_edits_conflict_and_can_be_resolved ( ) {
465
hermetic ( ) ;
466
let ( dir, repo, h1, h2, h3) = conflicted_repo ( ) ;
467
let root = dir.path ( ) ;
468
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
469
assert_eq! ( heads, [ h2, h3] .into_iter ( ) .collect ( ) ) ;
470
let st = repo.load_state ( & [ h2, h3] ) .unwrap ( ) ;
471
assert! ( st.materialize ( ) .is_conflicted ( ) ) ;
472
473
75167672 Andy McCall 1 month ago
474
let r = repo
475
.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
476
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
477
assert! ( r.conflicted) ;
478
let marked = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
479
assert! (
480
marked.starts_with ( " a\n <<<<<<< oxy conflict 1 of 1" ) ,
481
" {marked}"
482
) ;
483
assert! (
484
marked.contains ( " B\n " ) & & marked.contains ( " X\n " ) & & marked.ends_with ( " end\n c\n " ) ,
485
" {marked}"
486
) ;
98286406 Andy McCall 1 month ago ↶
487
assert! ( has_conflict_markers ( marked.as_bytes ( ) ) ) ;
488
489
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
490
assert! ( s.statuses.is_empty ( ) ) ;
491
assert! ( ! s.resolves) ;
492
493
494
write ( root, " f.txt" , marked.replace ( " B\n " , " BB\n " ) .as_bytes ( ) ) ;
495
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
496
assert_eq! (
497
statuses ( & s) ,
498
vec! [ ( " f.txt" .into ( ) , ChangeStatus:: ConflictEdited) ]
499
) ;
98286406 Andy McCall 1 month ago ↶
500
assert! ( repo.commit ( " no" , & [ ] ) .is_err ( ) ) ;
501
502
503
write ( root, " f.txt" , b " a\n X\n c\n d\n " ) ;
504
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
505
assert_eq! ( statuses ( & s) , vec! [ ( " f.txt" .into ( ) , ChangeStatus:: Resolved) ] ) ;
506
assert! ( s.resolves) ;
507
let h4 = repo.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
508
let c4 = repo.store ( ) .get_change ( & h4) .unwrap ( ) ;
509
assert_eq! ( c4.kind, ChangeKind:: Resolution) ;
510
let mut want = vec! [ h1, h2, h3] ;
511
want.sort ( ) ;
75167672 Andy McCall 1 month ago
512
assert_eq! (
513
c4.deps, want,
514
" a resolution depends on exactly the contributors and what it references"
515
) ;
98286406 Andy McCall 1 month ago ↶
516
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h4] ) ;
517
let snap = repo.load_state ( & [ h4] ) .unwrap ( ) .materialize ( ) ;
518
assert! ( ! snap.is_conflicted ( ) ) ;
75167672 Andy McCall 1 month ago
519
assert! (
520
matches! ( & snap.files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n X\n c\n d\n " )
521
) ;
98286406 Andy McCall 1 month ago ↶
522
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
523
524
525
let mut st = State:: new( ) ;
526
for h in [ h4, h3, h1, h2] {
527
st.add_unchecked ( repo.store ( ) .get_change ( & h) .unwrap ( ) ) ;
528
}
529
st.validate ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
530
assert! (
531
matches! ( & st.materialize ( ) .files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n X\n c\n d\n " )
532
) ;
98286406 Andy McCall 1 month ago ↶
533
}
534
69953b50 Andy McCall 17 days ago ↶
535
536
537
98286406 Andy McCall 1 month ago ↶
538
# [ test ]
69953b50 Andy McCall 17 days ago ↶
539
fn resolutions_and_amends_take_paths ( ) {
540
hermetic ( ) ;
541
let ( dir, repo, _h1, _h2, _h3) = conflicted_repo ( ) ;
542
let root = dir.path ( ) ;
543
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
544
.unwrap ( ) ;
545
546
write ( root, " f.txt" , b " a\n X\n c\n " ) ;
547
write ( root, " notes.txt" , b " pending\n " ) ;
548
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
549
assert! ( s.resolves) ;
550
let err = repo
551
.commit_selection_partial ( " resolve" , & [ ] , & Selection:: Paths( vec! [ " notes.txt" .into ( ) ] ) )
552
.unwrap_err ( )
553
.to_string ( ) ;
554
assert! (
555
err.contains ( " takes every file it resolves" ) & & err.contains ( " f.txt" ) ,
556
" {err}"
557
) ;
558
let ( h4, partial) = repo
559
.commit_selection_partial ( " resolve" , & [ ] , & Selection:: Paths( vec! [ " f.txt" .into ( ) ] ) )
560
.unwrap ( ) ;
561
assert! ( partial) ;
562
assert_eq! (
563
repo.store ( ) .get_change ( & h4) .unwrap ( ) .kind,
564
ChangeKind:: Resolution
565
) ;
566
assert! ( ! repo
567
.load_state ( & [ h4] )
568
.unwrap ( )
569
.materialize ( )
570
.is_conflicted ( ) ) ;
571
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
572
assert_eq! (
573
statuses ( & s) ,
574
vec! [ ( " notes.txt" .into ( ) , ChangeStatus:: Added) ] ,
575
" the other work waits"
576
) ;
577
578
579
580
let h5 = repo.commit ( " notes" , & [ h4] ) .unwrap ( ) ;
581
let id5 = repo.store ( ) .get_change ( & h5) .unwrap ( ) .change_id;
582
write ( root, " notes.txt" , b " pending\n more\n " ) ;
583
write ( root, " other.txt" , b " later\n " ) ;
584
let h6 = repo
585
.amend_paths ( Some ( " notes, amended" ) , & [ " notes.txt" .into ( ) ] )
586
.unwrap ( ) ;
587
let c6 = repo.store ( ) .get_change ( & h6) .unwrap ( ) ;
588
assert_eq! ( ( c6.change_id, c6.message.as_str ( ) ) , ( id5, " notes, amended" ) ) ;
589
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h6] ) ;
590
let snap6 = repo.load_state ( & [ h6] ) .unwrap ( ) .materialize ( ) ;
591
assert! (
592
matches! ( & snap6.files[ " notes.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " pending\n more\n " )
593
) ;
594
assert! (
595
! snap6.files.contains_key ( " other.txt" ) ,
596
" other.txt stays out"
597
) ;
598
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
599
assert_eq! (
600
statuses ( & s) ,
601
vec! [ ( " other.txt" .into ( ) , ChangeStatus:: Added) ]
602
) ;
603
}
604
605
# [ test ]
98286406 Andy McCall 1 month ago ↶
606
fn resolution_may_keep_both_sides_or_neither ( ) {
607
hermetic ( ) ;
608
let ( dir, repo, _h1, _h2, _h3) = conflicted_repo ( ) ;
609
let root = dir.path ( ) ;
75167672 Andy McCall 1 month ago
610
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
611
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
612
613
write ( root, " f.txt" , b " a\n X\n B\n c\n " ) ;
614
let h = repo.commit ( " both" , & [ ] ) .unwrap ( ) ;
615
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
616
assert! ( ! snap.is_conflicted ( ) ) ;
75167672 Andy McCall 1 month ago
617
assert! (
618
matches! ( & snap.files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n X\n B\n c\n " )
619
) ;
98286406 Andy McCall 1 month ago ↶
620
621
repo.undo ( ) .unwrap ( ) ;
622
write ( root, " f.txt" , b " a\n c\n " ) ;
623
let h = repo.commit ( " neither" , & [ ] ) .unwrap ( ) ;
624
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
625
assert! ( ! snap.is_conflicted ( ) ) ;
75167672 Andy McCall 1 month ago
626
assert! (
627
matches! ( & snap.files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n c\n " )
628
) ;
98286406 Andy McCall 1 month ago ↶
629
}
630
631
# [ test ]
632
fn name_conflict_resolved_by_removing_a_candidate ( ) {
633
hermetic ( ) ;
634
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
635
let root = dir.path ( ) ;
636
let repo = Repo:: init( root) .unwrap ( ) ;
637
write ( root, " keep.txt" , b " k\n " ) ;
638
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
639
write ( root, " new.txt" , b " one\n " ) ;
640
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
641
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
642
write ( root, " new.txt" , b " two\n " ) ;
643
repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
644
let r = repo
645
.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
646
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
647
assert! ( r.conflicted) ;
75167672 Andy McCall 1 month ago
648
assert! (
649
root.join ( " new.txt.oxy-conflict-1" ) .exists ( )
650
& & root.join ( " new.txt.oxy-conflict-2" ) .exists ( )
651
) ;
98286406 Andy McCall 1 month ago ↶
652
assert! ( ! root.join ( " new.txt" ) .exists ( ) ) ;
653
654
75167672 Andy McCall 1 month ago
655
let ( keep, drop) = if fs:: read( root.join ( " new.txt.oxy-conflict-1" ) ) .unwrap ( ) = = b " two\n " {
656
( 1 , 2 )
657
} else {
658
( 2 , 1 )
659
} ;
98286406 Andy McCall 1 month ago ↶
660
fs:: remove_file( root.join ( format! ( " new.txt.oxy-conflict-{drop} " ) ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
661
fs:: rename(
662
root.join ( format! ( " new.txt.oxy-conflict-{keep} " ) ) ,
663
root.join ( " new.txt" ) ,
664
)
665
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
666
let h = repo.commit ( " pick two" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
667
assert_eq! (
668
repo.store ( ) .get_change ( & h) .unwrap ( ) .kind,
669
ChangeKind:: Resolution
670
) ;
98286406 Andy McCall 1 month ago ↶
671
672
673
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
674
assert_eq! ( heads, [ h1, h] .into_iter ( ) .collect ( ) ) ;
75167672 Andy McCall 1 month ago
675
let snap = repo
676
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
677
.unwrap ( )
678
.materialize ( ) ;
98286406 Andy McCall 1 month ago ↶
679
assert! ( ! snap.is_conflicted ( ) , " {:?}" , snap.tree.conflicts) ;
75167672 Andy McCall 1 month ago
680
assert_eq! (
681
snap.files.keys ( ) .cloned ( ) .collect:: < Vec< _ > > ( ) ,
682
[ " keep.txt" , " new.txt" ]
683
) ;
684
assert! (
685
matches! ( & snap.files[ " new.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " two\n " )
686
) ;
98286406 Andy McCall 1 month ago ↶
687
}
688
c2e1d090 Andy McCall 17 days ago
689
690
691
692
fn merged_additions (
693
side : impl Fn( &Path , bool ) ,
694
) -> ( tempfile:: TempDir, Repo, Vec< ChangeHash> , [ChangeHash; 3]) {
695
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
696
let root = dir.path ( ) .to_path_buf ( ) ;
697
let mut repo = Repo:: init( & root) .unwrap ( ) ;
698
write ( & root, " keep.txt" , b " k\n " ) ;
699
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
700
repo.bookmark_create ( " side" , None ) .unwrap ( ) ;
701
side ( & root, false ) ;
702
let ha = repo.commit ( " add on main" , & [ ] ) .unwrap ( ) ;
703
repo.bookmark_switch ( " side" , false ) .unwrap ( ) ;
704
side ( & root, true ) ;
705
let hb = repo.commit ( " add on side" , & [ ] ) .unwrap ( ) ;
706
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
707
let m = repo.merge ( " side" ) .unwrap ( ) ;
708
assert! ( m.changed & & m.checked_out) ;
709
( dir, repo, m.heads, [ h1, ha, hb] )
710
}
711
712
fn conflict_copies ( root : & Path) -> Vec< String> {
713
let mut out: Vec< String> = fs:: read_dir( root)
714
.unwrap ( )
715
.map ( | e | e.unwrap ( ) .file_name ( ) .to_string_lossy ( ) .into_owned ( ) )
716
.filter ( | n | n.contains ( " .oxy-conflict-" ) )
717
.collect ( ) ;
718
out.sort ( ) ;
719
out
720
}
721
722
723
724
725
# [ test ]
726
fn identical_additions_unify_on_merge ( ) {
727
hermetic ( ) ;
728
let ( dir, repo, heads, [ h1, ha, hb] ) =
729
merged_additions ( | root , _| write ( root, " x.txt" , b " same\n lines\n " ) ) ;
730
let root = dir.path ( ) ;
731
732
let st = repo.load_state ( & heads) .unwrap ( ) ;
733
assert! ( st
734
.tree ( )
735
.view ( )
736
.conflicts
737
.iter ( )
738
.any ( | c | matches! ( c, crate :: tree:: TreeConflict:: Name { .. } ) ) ) ;
739
let snap = st.materialize ( ) ;
740
assert! ( ! snap.is_conflicted ( ) , " {:?}" , snap.tree.conflicts) ;
741
assert_eq! (
742
snap.files.keys ( ) .cloned ( ) .collect:: < Vec< _ > > ( ) ,
743
[ " keep.txt" , " x.txt" ]
744
) ;
745
assert! (
746
matches! ( & snap.files[ " x.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " same\n lines\n " )
747
) ;
748
assert_eq! ( snap.tree.unified.len ( ) , 1 ) ;
749
750
assert_eq! ( fs:: read( root.join ( " x.txt" ) ) .unwrap ( ) , b " same\n lines\n " ) ;
751
assert! (
752
conflict_copies ( root) .is_empty ( ) ,
753
" {:?}" ,
754
conflict_copies ( root)
755
) ;
756
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
757
assert! ( s.statuses.is_empty ( ) , " {:?}" , statuses ( & s) ) ;
758
assert! ( ! s.changed & & ! s.resolves) ;
759
assert! ( ! s.base_snapshot.is_conflicted ( ) ) ;
760
assert! ( s.wc.tree_ops.is_empty ( ) & & s.wc.edits.is_empty ( ) ) ;
761
let json = repo.status_json ( & s) .unwrap ( ) ;
762
assert! ( json.contains ( " \" conflicted\" :false" ) , " {json}" ) ;
763
assert! ( repo.commit ( " nothing" , & [ ] ) .is_err ( ) ) ;
764
assert_eq! ( repo.file_at ( " x.txt" , None ) .unwrap ( ) , b " same\n lines\n " ) ;
765
766
let mut peer = State:: new( ) ;
767
for h in [ hb, ha, h1] {
768
peer.add_unchecked ( repo.store ( ) .get_change ( & h) .unwrap ( ) ) ;
769
}
770
peer.validate ( ) .unwrap ( ) ;
771
let ps = peer.materialize ( ) ;
772
assert! ( ! ps.is_conflicted ( ) ) ;
773
assert_eq! ( ps.tree.entries[ " x.txt" ] , snap.tree.entries[ " x.txt" ] ) ;
774
assert_eq! ( ps.tree.unified, snap.tree.unified) ;
775
776
let log: Vec< ChangeHash> = repo
777
.log_for_path ( " x.txt" )
778
.unwrap ( )
779
.iter ( )
780
.map ( | e | e.hash )
781
.collect ( ) ;
782
assert! ( log.contains ( & ha) & & log.contains ( & hb) , " {log:?}" ) ;
783
784
785
write ( root, " x.txt" , b " same\n edited\n " ) ;
786
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
787
assert_eq! ( statuses ( & s) , vec! [ ( " x.txt" .into ( ) , ChangeStatus:: Modified) ] ) ;
788
let he = repo.commit ( " edit x" , & [ ] ) .unwrap ( ) ;
789
let c = repo.store ( ) .get_change ( & he) .unwrap ( ) ;
790
assert_eq! ( c.kind, ChangeKind:: Normal) ;
791
let ( dup, _ ) = snap.tree.unified.iter ( ) .next ( ) .unwrap ( ) ;
792
assert! (
793
c.tree_ops
794
.iter ( )
795
.any ( | o | matches! ( o, TreeOp:: RemoveNode { node, .. } if node = = dup) ) ,
796
" {:?}" ,
797
c.tree_ops
798
) ;
799
let d = repo.describe_change ( & he) .unwrap ( ) ;
800
assert! ( ! d.contains ( " D x.txt" ) , " {d}" ) ;
801
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
802
assert! (
803
st.tree ( ) .view ( ) .conflicts.is_empty ( ) ,
804
" nothing left to unify"
805
) ;
806
let snap = st.materialize ( ) ;
807
assert! ( ! snap.is_conflicted ( ) ) ;
808
assert! (
809
matches! ( & snap.files[ " x.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " same\n edited\n " )
810
) ;
811
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
812
813
814
fs:: remove_file( root.join ( " x.txt" ) ) .unwrap ( ) ;
815
repo.commit ( " drop x" , & [ ] ) .unwrap ( ) ;
816
let snap = repo
817
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
818
.unwrap ( )
819
.materialize ( ) ;
820
assert! ( ! snap.is_conflicted ( ) , " {:?}" , snap.tree.conflicts) ;
821
assert_eq! ( snap.files.keys ( ) .cloned ( ) .collect:: < Vec< _ > > ( ) , [ " keep.txt" ] ) ;
822
assert! ( ! root.join ( " x.txt" ) .exists ( ) ) ;
823
}
824
825
826
827
# [ test ]
828
fn unified_additions_stay_one_when_removed_renamed_or_committed_alone ( ) {
829
hermetic ( ) ;
830
let add = | root : & Path, _: bool | write ( root, " x.txt" , b " same\n " ) ;
831
for how in [ " remove" , " rename" , " path" , " hunk" ] {
832
let ( dir, repo, _ , _ ) = merged_additions ( add) ;
833
let root = dir.path ( ) ;
834
match how {
835
" remove" => fs:: remove_file( root.join ( " x.txt" ) ) .unwrap ( ) ,
836
" rename" => fs:: rename( root.join ( " x.txt" ) , root.join ( " y.txt" ) ) .unwrap ( ) ,
837
_ => {
838
write ( root, " x.txt" , b " same\n more\n " ) ;
839
write ( root, " other.txt" , b " o\n " ) ;
840
}
841
}
842
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
843
let h = match how {
844
" path" => repo
845
.commit_selection ( " x alone" , & [ ] , & Selection:: Paths( vec! [ " x.txt" .into ( ) ] ) )
846
.unwrap ( ) ,
847
" hunk" => {
848
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
849
850
assert! ( hunks.iter ( ) .all ( | h | ! h.text.starts_with ( " D " ) ) , " {hunks:?}" ) ;
851
let x: Vec< usize > = hunks
852
.iter ( )
853
.filter ( | h | h.path = = " x.txt" )
854
.map ( | h | h.index )
855
.collect ( ) ;
856
assert! ( ! x.is_empty ( ) ) ;
857
repo.commit_selection ( " x hunks" , & [ ] , & Selection:: Hunks( x) )
858
.unwrap ( )
859
}
860
_ => repo.commit ( how, & [ ] ) .unwrap ( ) ,
861
} ;
862
assert! ( ! s.base_snapshot.is_conflicted ( ) ) ;
863
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
864
assert! ( heads.contains ( & h) ) ;
865
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
866
assert! ( ! snap.is_conflicted ( ) , " {how}: {:?}" , snap.tree.conflicts) ;
867
let want: & [ & str ] = match how {
868
" remove" => & [ " keep.txt" ] ,
869
" rename" => & [ " keep.txt" , " y.txt" ] ,
870
_ => & [ " keep.txt" , " x.txt" ] ,
871
} ;
872
assert_eq! (
873
snap.files.keys ( ) .cloned ( ) .collect:: < Vec< _ > > ( ) ,
874
want,
875
" {how}"
876
) ;
877
assert! ( conflict_copies ( root) .is_empty ( ) , " {how}" ) ;
878
}
879
}
880
881
882
883
# [ test ]
884
fn additions_that_differ_still_conflict ( ) {
885
hermetic ( ) ;
886
# [ cfg ( unix ) ]
887
use std:: os:: unix:: fs:: PermissionsExt;
888
let content =
889
| root : & Path, side : bool | write ( root, " x.txt" , if side { b " two\n " } else { b " one\n " } ) ;
890
let mode = | root : & Path, side : bool | {
891
write ( root, " x.txt" , b " same\n " ) ;
892
# [ cfg ( unix ) ]
893
if side {
894
fs:: set_permissions( root.join ( " x.txt" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
895
}
896
} ;
897
let check = | what : & str , side : & dyn Fn( & Path, bool ) | {
898
let ( dir, repo, heads, _ ) = merged_additions ( side) ;
899
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
900
assert! ( snap.tree.unified.is_empty ( ) , " {what}" ) ;
901
assert! (
902
snap.tree.conflicts.iter ( ) .any (
903
| c | matches! ( c, crate :: tree:: TreeConflict:: Name { name, .. } if name = = " x.txt" )
904
) ,
905
" {what}: {:?}" ,
906
snap.tree.conflicts
907
) ;
908
assert_eq! (
909
conflict_copies ( dir.path ( ) ) ,
910
[ " x.txt.oxy-conflict-1" , " x.txt.oxy-conflict-2" ] ,
911
" {what}"
912
) ;
913
assert! ( ! dir.path ( ) .join ( " x.txt" ) .exists ( ) , " {what}" ) ;
914
} ;
915
check ( " content" , & content) ;
916
# [ cfg ( unix ) ]
917
check ( " mode" , & mode) ;
918
# [ cfg ( not ( unix ) ) ]
919
let _ = mode;
920
}
921
922
923
924
925
# [ test ]
926
fn the_same_subrepo_added_on_two_bookmarks_unifies ( ) {
927
hermetic ( ) ;
928
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
929
let root = dir.path ( ) ;
930
let lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
931
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
932
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
933
.unwrap ( )
934
.with_home ( root.join ( " home" ) ) ;
935
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
936
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
937
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
938
let _ = lib_origin;
939
940
let app_dir = root.join ( " app" ) ;
941
let mut app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
942
write ( & app_dir, " main.txt" , b " app\n " ) ;
943
app.commit ( " base" , & [ ] ) .unwrap ( ) ;
944
app.bookmark_create ( " side" , None ) .unwrap ( ) ;
945
app.submodule_add ( & lib_url, " rstar-api" , None ) .unwrap ( ) ;
946
let modules = fs:: read( app_dir.join ( " .oxymodules" ) ) .unwrap ( ) ;
947
app.commit ( " rstar-api on main" , & [ ] ) .unwrap ( ) ;
948
949
950
app.bookmark_switch ( " side" , false ) .unwrap ( ) ;
951
assert! ( app_dir.join ( " rstar-api/.oxy" ) .is_dir ( ) ) ;
952
write ( & app_dir, " .oxymodules" , & modules) ;
953
let s = app.snapshot ( " status" ) .unwrap ( ) ;
954
assert_eq! (
955
statuses ( & s) ,
956
vec! [
957
( " .oxymodules" .into ( ) , ChangeStatus:: Added) ,
958
( " rstar-api" .into ( ) , ChangeStatus:: Added)
959
]
960
) ;
961
app.commit ( " rstar-api on side" , & [ ] ) .unwrap ( ) ;
962
app.bookmark_switch ( " main" , false ) .unwrap ( ) ;
963
964
let m = app.merge ( " side" ) .unwrap ( ) ;
965
assert! ( m.changed & & m.checked_out) ;
966
let snap = app.load_state ( & m.heads) .unwrap ( ) .materialize ( ) ;
967
assert! ( ! snap.is_conflicted ( ) , " {:?}" , snap.tree.conflicts) ;
968
assert_eq! ( snap.tree.unified.len ( ) , 2 ) ;
969
assert_eq! ( snap.files[ " rstar-api" ] .kind, NodeKind:: Subrepo) ;
970
assert! ( conflict_copies ( & app_dir) .is_empty ( ) ) ;
971
let s = app.snapshot ( " status" ) .unwrap ( ) ;
972
assert! ( s.statuses.is_empty ( ) , " {:?}" , statuses ( & s) ) ;
973
assert! ( ! s.base_snapshot.is_conflicted ( ) ) ;
974
assert_eq! (
975
fs:: read_to_string( app_dir.join ( " rstar-api/lib.txt" ) ) .unwrap ( ) ,
976
" v1\n "
977
) ;
978
}
979
98286406 Andy McCall 1 month ago ↶
980
# [ test ]
981
fn identities_and_signed_commits ( ) {
982
hermetic ( ) ;
983
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
984
let root = dir.path ( ) ;
985
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
986
write ( root, " a.txt" , b " a\n " ) ;
987
let h0 = repo.commit ( " unsigned" , & [ ] ) .unwrap ( ) ;
988
assert_eq! ( repo.verify_change ( & h0) .unwrap ( ) , SignatureStatus:: Unsigned) ;
75167672 Andy McCall 1 month ago
989
assert_eq! (
990
repo.store ( ) .get_change ( & h0) .unwrap ( ) .author,
991
ObjectRef:: ZERO
992
) ;
98286406 Andy McCall 1 month ago ↶
993
75167672 Andy McCall 1 month ago
994
let ( key, id) = repo
995
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
996
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
997
assert_eq! ( id.hash ( ) , key) ;
998
assert_eq! ( repo.config.author_key, Some ( key) ) ;
999
1000
let reopened = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
1001
assert_eq! ( reopened.config.author_key, Some ( key) ) ;
1002
assert_eq! ( reopened.secret_key ( ) .unwrap ( ) .unwrap ( ) .0 , key) ;
1003
1004
1005
write ( root, " a.txt" , b " a\n b\n " ) ;
1006
let h1 = repo.commit ( " authored" , & [ ] ) .unwrap ( ) ;
1007
assert_eq! ( repo.store ( ) .get_change ( & h1) .unwrap ( ) .author, key) ;
1008
assert_eq! ( repo.verify_change ( & h1) .unwrap ( ) , SignatureStatus:: Unsigned) ;
1009
1010
repo.sign_change ( & h1) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1011
assert! (
1012
matches! ( repo.verify_change ( & h1) .unwrap ( ) , SignatureStatus:: Valid { key: k, .. } if k = = key)
1013
) ;
98286406 Andy McCall 1 month ago ↶
1014
1015
repo.config.set ( " core.sign" , " change" ) .unwrap ( ) ;
1016
repo.save_config ( ) .unwrap ( ) ;
1017
write ( root, " a.txt" , b " a\n b\n c\n " ) ;
1018
let h2 = repo.commit ( " signed" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1019
assert_eq! (
1020
repo.verify_change ( & h2) .unwrap ( ) ,
1021
SignatureStatus:: Valid {
1022
key,
1023
name: " Ada" .into ( ) ,
1024
email: " [email protected] " .into ( ) ,
1025
sealed: false ,
1026
revoked: false
1027
}
1028
) ;
98286406 Andy McCall 1 month ago ↶
1029
05e71ed6 Andy McCall 1 month ago ↶
1030
1031
1032
98286406 Andy McCall 1 month ago ↶
1033
let other = SecretKey:: generate( ) ;
1034
let other_id = other.identity ( " Mallory" , " [email protected] " , 0 ) ;
1035
let other_key = repo.store ( ) .put_identity ( & other_id) .unwrap ( ) ;
05e71ed6 Andy McCall 1 month ago ↶
1036
write ( root, " a.txt" , b " a\n b\n c\n d\n " ) ;
1037
repo.config.set ( " core.sign" , " seal" ) .unwrap ( ) ;
1038
let h3 = repo.commit ( " ada's, unsigned" , & [ ] ) .unwrap ( ) ;
1039
let forged = other.sign ( Domain:: Change, & other_key, & h3) ;
1040
repo.store ( ) .put_signature ( & h3, & forged) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1041
assert! (
05e71ed6 Andy McCall 1 month ago ↶
1042
matches! ( repo.verify_change ( & h3) .unwrap ( ) , SignatureStatus:: Invalid( m) if m.contains ( " neither the author" ) )
75167672 Andy McCall 1 month ago
1043
) ;
05e71ed6 Andy McCall 1 month ago ↶
1044
let adopted = other.sign ( Domain:: Change, & other_key, & h0) ;
1045
repo.store ( ) .put_signature ( & h0, & adopted) .unwrap ( ) ;
1046
assert! (
1047
matches! ( repo.verify_change ( & h0) .unwrap ( ) , SignatureStatus:: Valid { name, .. } if name = = " Mallory" )
1048
) ;
98286406 Andy McCall 1 month ago ↶
1049
1050
1051
let mut sig = repo.store ( ) .signatures ( & h2) .unwrap ( ) .remove ( 0 ) ;
1052
sig.ed[ 0 ] ^ = 1 ;
1053
repo.store ( ) .put_signature ( & h2, & sig) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1054
assert! ( matches! (
1055
repo.verify_change ( & h2) .unwrap ( ) ,
1056
SignatureStatus:: Invalid( _ )
1057
) ) ;
98286406 Andy McCall 1 month ago ↶
1058
1059
1060
let no_keys = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " nowhere" ) ) ;
1061
write ( root, " a.txt" , b " z\n " ) ;
1062
assert! ( no_keys.commit ( " cannot sign" , & [ ] ) .is_err ( ) ) ;
1063
}
1064
1065
# [ test ]
1066
fn checkout_moves_the_working_copy ( ) {
1067
hermetic ( ) ;
1068
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1069
let root = dir.path ( ) ;
1070
let repo = Repo:: init( root) .unwrap ( ) ;
1071
write ( root, " a.txt" , b " one\n " ) ;
1072
write ( root, " sub/deep/b.txt" , b " b\n " ) ;
1073
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
1074
write ( root, " a.txt" , b " two\n " ) ;
1075
fs:: remove_file( root.join ( " sub/deep/b.txt" ) ) .unwrap ( ) ;
1076
write ( root, " bin.dat" , & [ 0 , 1 , 2 , 3 ] ) ;
1077
let h2 = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
1078
1079
let r = repo.checkout ( & [ h1] , false , " oxy checkout" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1080
assert_eq! (
1081
r,
1082
CheckoutReport {
1083
written: 2 ,
1084
removed: 1 ,
9b723f69 Andy McCall 24 days ago ↶
1085
removed_paths: vec! [ " bin.dat" .into ( ) ] ,
75167672 Andy McCall 1 month ago
1086
conflicted: false ,
d3cd7ef9 Andy McCall 1 month ago ↶
1087
notes: vec! [ ] ,
1088
lacks: 1 ,
4afe288b Andy McCall 10 days ago ↶
1089
kept_both: vec! [ ] ,
75167672 Andy McCall 1 month ago
1090
}
1091
) ;
98286406 Andy McCall 1 month ago ↶
1092
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n " ) ;
1093
assert_eq! ( fs:: read( root.join ( " sub/deep/b.txt" ) ) .unwrap ( ) , b " b\n " ) ;
1094
assert! ( ! root.join ( " bin.dat" ) .exists ( ) ) ;
1095
let ( _ , wc) = repo.working_copy ( ) .unwrap ( ) ;
1096
assert_eq! ( wc.deps, vec! [ h1] ) ;
75167672 Andy McCall 1 month ago
1097
assert! (
1098
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
1099
" checkout leaves a clean working copy"
1100
) ;
98286406 Andy McCall 1 month ago ↶
1101
1102
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
1103
1104
1105
write ( root, " a.txt" , b " dirty\n " ) ;
1106
assert! ( repo.checkout ( & [ h2] , false , " oxy checkout" ) .is_err ( ) ) ;
75167672 Andy McCall 1 month ago
1107
let r = repo
1108
.checkout ( & [ h2] , true , " oxy checkout --discard" )
1109
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1110
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " two\n " ) ;
1111
assert_eq! ( fs:: read( root.join ( " bin.dat" ) ) .unwrap ( ) , [ 0 , 1 , 2 , 3 ] ) ;
1112
assert! ( ! root.join ( " sub" ) .exists ( ) , " empty directories are pruned" ) ;
1113
assert_eq! ( r.removed, 1 ) ;
1114
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
1115
1116
1117
# [ cfg ( unix ) ]
1118
{
1119
fs:: remove_dir_all( root.join ( " sub" ) ) .ok ( ) ;
1120
std:: os:: unix:: fs:: symlink( " /tmp" , root.join ( " sub" ) ) .unwrap ( ) ;
1121
assert! ( repo.checkout ( & [ h1] , true , " oxy checkout" ) .is_err ( ) ) ;
1122
}
1123
}
1124
1125
# [ test ]
1126
fn large_files_are_chunked_and_read_back ( ) {
1127
hermetic ( ) ;
1128
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1129
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
1130
let mut big = vec! [ 0 u8 ; 700 * 1024 ] ;
1131
for ( i, b) in big.iter_mut ( ) .enumerate ( ) {
1132
* b = ( i as u32 ) .wrapping_mul ( 2654435761 ) .to_le_bytes ( ) [ 0 ] ;
1133
}
1134
let r = repo.store_blob ( & big) .unwrap ( ) ;
1135
let blob = Blob:: decode( & repo.store ( ) .get_typed ( & r, ObjectType:: Blob) .unwrap ( ) ) .unwrap ( ) ;
1136
assert! ( blob.chunks.len ( ) > = 3 , " {}" , blob.chunks.len ( ) ) ;
1137
assert_eq! ( repo.read_blob ( & r) .unwrap ( ) , big) ;
1138
assert_eq! ( Repo:: blob_ref( & big) , r) ;
1139
}
1140
1141
# [ test ]
1142
fn clone_push_pull_with_seals_and_attestations ( ) {
1143
hermetic ( ) ;
1144
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1145
let root = dir.path ( ) ;
1146
let origin_path = root.join ( " origin" ) ;
1147
let origin = Repo:: init( & origin_path) .unwrap ( ) ;
1148
let origin_url = origin_path.to_string_lossy ( ) .to_string ( ) ;
1149
1150
75167672 Andy McCall 1 month ago
1151
let mut a = Repo:: clone( & origin_url, & root.join ( " a" ) )
1152
.unwrap ( )
1153
.with_home ( root.join ( " home-a" ) ) ;
98286406 Andy McCall 1 month ago ↶
1154
assert_eq! ( a.id ( ) .unwrap ( ) , origin.id ( ) .unwrap ( ) ) ;
75167672 Andy McCall 1 month ago
1155
let ( key_a, _ ) = a
1156
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
1157
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1158
write ( & root.join ( " a" ) , " f.txt" , b " a\n " ) ;
1159
let h1 = a.commit ( " first" , & [ ] ) .unwrap ( ) ;
1160
let results = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 17 days ago
1161
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1162
assert! ( results[ 0 ] .ok, " {}" , results[ 0 ] .reason) ;
1163
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
1164
75167672 Andy McCall 1 month ago
1165
assert! (
1166
matches! ( origin.authenticate_change ( & h1) .unwrap ( ) , SignatureStatus:: Valid { key, sealed: true , .. } if key = = key_a)
1167
) ;
1168
assert_eq! (
1169
origin.verify_change ( & h1) .unwrap ( ) ,
1170
SignatureStatus:: Unsigned
1171
) ;
f8c69576 Andy McCall 1 month ago ↶
1172
let serial_of = | att : & SignedAttest| match att.attest.body {
1173
AttestBody:: Ref { serial, .. } => serial,
1174
_ => unreachable! ( ) ,
1175
} ;
98286406 Andy McCall 1 month ago ↶
1176
let ( _ , att) = origin.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
1177
1178
1179
let first = serial_of ( & att) ;
1180
assert! ( first > 1_000_000_000_000 , " {first}" ) ;
98286406 Andy McCall 1 month ago ↶
1181
let again = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 17 days ago
1182
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1183
assert! ( again[ 0 ] .ok & & again[ 0 ] .reason.contains ( " up to date" ) ) ;
1184
let ( _ , att) = origin.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
1185
assert_eq! ( serial_of ( & att) , first, " a no-op push does not attest again" ) ;
98286406 Andy McCall 1 month ago ↶
1186
1187
1188
let b = Repo:: clone( & origin_url, & root.join ( " b" ) ) .unwrap ( ) ;
1189
assert_eq! ( fs:: read( root.join ( " b/f.txt" ) ) .unwrap ( ) , b " a\n " ) ;
1190
assert_eq! ( b.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
75167672 Andy McCall 1 month ago
1191
assert! ( matches! (
1192
b.authenticate_change ( & h1) .unwrap ( ) ,
1193
SignatureStatus:: Valid { sealed: true , .. }
1194
) ) ;
98286406 Andy McCall 1 month ago ↶
1195
1196
1197
write ( & root.join ( " b" ) , " f.txt" , b " a\n b\n " ) ;
1198
let h2 = b.commit ( " second" , & [ ] ) .unwrap ( ) ;
1199
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 17 days ago
1200
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1201
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
1202
1203
1204
write ( & root.join ( " a" ) , " g.txt" , b " g\n " ) ;
1205
let h3 = a.commit ( " third" , & [ ] ) .unwrap ( ) ;
1206
assert! ( a.push ( " origin" , false ) .is_err ( ) ) ;
1207
let pr = a.pull ( " origin" ) .unwrap ( ) ;
1208
assert! ( pr.changed & & pr.checked_out) ;
1209
let mut want = vec! [ h2, h3] ;
1210
want.sort ( ) ;
1211
assert_eq! ( pr.heads, want) ;
1212
assert_eq! ( fs:: read( root.join ( " a/f.txt" ) ) .unwrap ( ) , b " a\n b\n " ) ;
1213
assert_eq! ( fs:: read( root.join ( " a/g.txt" ) ) .unwrap ( ) , b " g\n " ) ;
1214
let results = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 17 days ago
1215
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1216
assert! ( results[ 0 ] .ok, " {}" , results[ 0 ] .reason) ;
1217
let mut origin_heads = origin.bookmark_heads ( ) .unwrap ( ) ;
1218
origin_heads.sort ( ) ;
1219
assert_eq! ( origin_heads, want) ;
1220
let ( _ , att) = origin.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
1221
assert_eq! ( serial_of ( & att) , first + 1 ) ;
98286406 Andy McCall 1 month ago ↶
1222
1223
1224
let pr = b.pull ( " origin" ) .unwrap ( ) ;
1225
assert_eq! ( pr.heads, want) ;
1226
assert_eq! ( fs:: read( root.join ( " b/g.txt" ) ) .unwrap ( ) , b " g\n " ) ;
1227
assert! ( ! b.snapshot ( " status" ) .unwrap ( ) .changed) ;
1228
1229
1230
write ( & root.join ( " a" ) , " f.txt" , b " a\n A\n " ) ;
1231
a.commit ( " a-edit" , & [ ] ) .unwrap ( ) ;
1232
a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 17 days ago
1233
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1234
write ( & root.join ( " b" ) , " f.txt" , b " a\n B\n " ) ;
1235
b.commit ( " b-edit" , & [ ] ) .unwrap ( ) ;
1236
let pr = b.pull ( " origin" ) .unwrap ( ) ;
1237
assert! ( pr.changed & & pr.checked_out) ;
1238
let text = fs:: read_to_string( root.join ( " b/f.txt" ) ) .unwrap ( ) ;
1239
assert! ( text.contains ( " <<<<<<< oxy conflict" ) , " {text}" ) ;
1240
write ( & root.join ( " b" ) , " f.txt" , b " a\n AB\n " ) ;
1241
let hr = b.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1242
assert_eq! (
1243
b.store ( ) .get_change ( & hr) .unwrap ( ) .kind,
1244
ChangeKind:: Resolution
1245
) ;
98286406 Andy McCall 1 month ago ↶
1246
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 17 days ago
1247
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1248
a.pull ( " origin" ) .unwrap ( ) ;
1249
assert_eq! ( fs:: read( root.join ( " a/f.txt" ) ) .unwrap ( ) , b " a\n AB\n " ) ;
1250
1251
1252
let mut want = vec! [ hr, h3] ;
1253
want.sort ( ) ;
1254
assert_eq! ( a.bookmark_heads ( ) .unwrap ( ) , want) ;
1255
assert! ( ! a.load_state ( & want) .unwrap ( ) .materialize ( ) .is_conflicted ( ) ) ;
1256
}
1257
1258
# [ test ]
1259
fn receive_push_rejects_bad_updates ( ) {
1260
hermetic ( ) ;
1261
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1262
let root = dir.path ( ) ;
1263
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1264
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
1265
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1266
.unwrap ( )
1267
.with_home ( root.join ( " home" ) ) ;
1268
let ( key, _ ) = a
1269
.create_identity ( " Ada" , " a@b" , IdentityScope:: Repo)
1270
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1271
write ( & root.join ( " a" ) , " f.txt" , b " x\n " ) ;
1272
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
1273
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
1274
let empty = origin.store ( ) .put_state ( & StateObject:: new( vec! [ ] ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1275
let s1 = origin
1276
.store ( )
1277
.put_state ( & StateObject:: new( vec! [ h1] ) )
1278
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1279
1280
75167672 Andy McCall 1 month ago
1281
let ghost = origin
1282
.store ( )
1283
.put_state ( & StateObject:: new( vec! [ ObjectRef( [ 9 ; 32 ] ) ] ) )
1284
.unwrap ( ) ;
1285
let r = origin
1286
.receive_push (
1287
Bundle:: default( ) ,
1288
vec! [ RefUpdate {
1289
name: " main" .into ( ) ,
1290
old_state: s1,
1291
new_state: ghost,
1292
force: false ,
1293
attestation: None ,
1294
} ] ,
1295
None ,
1296
vec! [ ] ,
1297
)
1298
.unwrap ( ) ;
1299
assert! (
1300
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " missing" ) ,
1301
" {}" ,
1302
r[ 0 ] .reason
1303
) ;
98286406 Andy McCall 1 month ago ↶
1304
1305
75167672 Andy McCall 1 month ago
1306
let r = origin
1307
.receive_push (
1308
Bundle:: default( ) ,
1309
vec! [ RefUpdate {
1310
name: " main" .into ( ) ,
1311
old_state: empty,
1312
new_state: s1,
1313
force: false ,
1314
attestation: None ,
1315
} ] ,
1316
None ,
1317
vec! [ ] ,
1318
)
1319
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1320
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " stale" ) , " {}" , r[ 0 ] .reason) ;
1321
1322
75167672 Andy McCall 1 month ago
1323
let r = origin
1324
.receive_push (
1325
Bundle:: default( ) ,
1326
vec! [ RefUpdate {
1327
name: " main" .into ( ) ,
1328
old_state: s1,
1329
new_state: empty,
1330
force: false ,
1331
attestation: None ,
1332
} ] ,
1333
None ,
1334
vec! [ ] ,
1335
)
1336
.unwrap ( ) ;
1337
assert! (
1338
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " fast-forward" ) ,
1339
" {}" ,
1340
r[ 0 ] .reason
1341
) ;
1342
let r = origin
1343
.receive_push (
1344
Bundle:: default( ) ,
1345
vec! [ RefUpdate {
1346
name: " main" .into ( ) ,
1347
old_state: s1,
1348
new_state: empty,
1349
force: true ,
1350
attestation: None ,
1351
} ] ,
1352
None ,
1353
vec! [ ] ,
1354
)
1355
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1356
assert! ( r[ 0 ] .ok) ;
1357
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ ] ) ;
1358
1359
75167672 Andy McCall 1 month ago
1360
let sa = a
1361
.sign_attest ( & Attest {
1362
repo: a.id ( ) .unwrap ( ) ,
1363
hlc: Hlc:: now( ) ,
1364
body: AttestBody:: Ref {
1365
name: " main" .into ( ) ,
1366
old_state: empty,
1367
new_state: s1,
1368
serial: 1 ,
1369
} ,
1370
} )
1371
.unwrap ( ) ;
1372
let r = origin
1373
.receive_push (
1374
Bundle:: default( ) ,
1375
vec! [ RefUpdate {
1376
name: " main" .into ( ) ,
1377
old_state: empty,
1378
new_state: s1,
1379
force: false ,
1380
attestation: Some ( sa.clone ( ) ) ,
1381
} ] ,
1382
None ,
1383
vec! [ ] ,
1384
)
1385
.unwrap ( ) ;
1386
assert! (
1387
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " serial" ) ,
1388
" {}" ,
1389
r[ 0 ] .reason
1390
) ;
98286406 Andy McCall 1 month ago ↶
1391
75167672 Andy McCall 1 month ago
1392
let sa2 = a
1393
.sign_attest ( & Attest {
1394
repo: a.id ( ) .unwrap ( ) ,
1395
hlc: Hlc:: now( ) ,
1396
body: AttestBody:: Ref {
1397
name: " main" .into ( ) ,
1398
old_state: s1,
1399
new_state: empty,
1400
serial: 5 ,
1401
} ,
1402
} )
1403
.unwrap ( ) ;
1404
let r = origin
1405
.receive_push (
1406
Bundle:: default( ) ,
1407
vec! [ RefUpdate {
1408
name: " main" .into ( ) ,
1409
old_state: empty,
1410
new_state: s1,
1411
force: false ,
1412
attestation: Some ( sa2) ,
1413
} ] ,
1414
None ,
1415
vec! [ ] ,
1416
)
1417
.unwrap ( ) ;
1418
assert! (
1419
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " describe" ) ,
1420
" {}" ,
1421
r[ 0 ] .reason
1422
) ;
98286406 Andy McCall 1 month ago ↶
1423
let _ = key;
1424
}
1425
1426
# [ test ]
1427
fn user_config_supplies_defaults_and_clones_sign ( ) {
1428
hermetic ( ) ;
1429
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1430
let root = dir.path ( ) ;
1431
let home = root.join ( " home" ) ;
1432
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1433
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1434
1435
1436
let mut a = Repo:: init( & root.join ( " a" ) ) .unwrap ( ) .with_home ( & home) ;
75167672 Andy McCall 1 month ago
1437
let ( key, _ ) = a
1438
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1439
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1440
assert! ( ! a.config.repo_layer ( ) .contains_key ( " author.key" ) ) ;
1441
assert_eq! ( a.config.author_key, Some ( key) ) ;
1442
assert! ( home.join ( " config.toml" ) .exists ( ) ) ;
1443
a.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
1444
assert! ( a.config.sign_changes) ;
1445
1446
1447
a.config.set ( " core.sign" , " seal" ) .unwrap ( ) ;
1448
a.save_config ( ) .unwrap ( ) ;
1449
let again = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) .with_home ( & home) ;
1450
assert! ( ! again.config.sign_changes) ;
1451
assert_eq! ( again.config.author_key, Some ( key) ) ;
75167672 Andy McCall 1 month ago
1452
assert_eq! (
1453
read_config_file ( & root.join ( " a/.oxy/config" ) )
1454
.unwrap ( )
1455
.get ( " core.sign" )
1456
.map ( String :: as_str) ,
1457
Some ( " seal" )
1458
) ;
98286406 Andy McCall 1 month ago ↶
1459
1460
1461
1462
let b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) .with_home ( & home) ;
1463
assert! ( b.config.sign_changes) ;
1464
write ( & root.join ( " b" ) , " f.txt" , b " f\n " ) ;
1465
let h = b.commit ( " signed from a clone" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1466
assert! (
1467
matches! ( b.verify_change ( & h) .unwrap ( ) , SignatureStatus:: Valid { key: k, .. } if k = = key)
1468
) ;
98286406 Andy McCall 1 month ago ↶
1469
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
75167672 Andy McCall 1 month ago
1470
assert! ( matches! (
1471
origin.verify_change ( & h) .unwrap ( ) ,
1472
SignatureStatus:: Valid { .. }
1473
) ) ;
98286406 Andy McCall 1 month ago ↶
1474
}
1475
1476
# [ test ]
1477
fn format1_repositories_are_migrated_on_open ( ) {
1478
hermetic ( ) ;
1479
use crate :: oplog:: { Format1Op, Format1View} ;
1480
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1481
let root = dir.path ( ) ;
1482
let repo = Repo:: init( root) .unwrap ( ) ;
1483
write ( root, " a.txt" , b " a\n " ) ;
1484
let h1 = repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
1485
let ( wc_hash, _ ) = repo.working_copy ( ) .unwrap ( ) ;
1486
1487
let mut v1 = Format1View:: default( ) ;
1488
v1.bookmarks.insert ( " main" .into ( ) , vec! [ ] ) ;
1489
v1.workspaces.insert ( repo.workspace ( ) , wc_hash) ;
75167672 Andy McCall 1 month ago
1490
let init = Format1Op {
1491
parents: vec! [ ] ,
1492
hlc: Hlc:: default( ) ,
1493
command: " oxy init" .into ( ) ,
1494
view: v1.clone ( ) ,
1495
} ;
98286406 Andy McCall 1 month ago ↶
1496
let init_ref = repo.store ( ) .put ( ObjectType:: Op, & init.encode ( ) ) .unwrap ( ) ;
1497
v1.bookmarks.insert ( " main" .into ( ) , vec! [ h1] ) ;
75167672 Andy McCall 1 month ago
1498
let commit = Format1Op {
1499
parents: vec! [ init_ref] ,
1500
hlc: Hlc:: default( ) ,
1501
command: " oxy commit" .into ( ) ,
1502
view: v1,
1503
} ;
98286406 Andy McCall 1 month ago ↶
1504
let commit_ref = repo.store ( ) .put ( ObjectType:: Op, & commit.encode ( ) ) .unwrap ( ) ;
1505
write_atomic ( & root.join ( " .oxy/ops/head" ) , commit_ref.to_hex ( ) .as_bytes ( ) ) .unwrap ( ) ;
1506
write_atomic ( & root.join ( " .oxy/format" ) , FORMAT1_LINE .as_bytes ( ) ) .unwrap ( ) ;
1507
drop ( repo) ;
1508
1509
let repo = Repo:: open( root) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1510
assert_eq! (
1511
fs:: read_to_string( root.join ( " .oxy/format" ) ) .unwrap ( ) ,
1512
FORMAT_LINE
1513
) ;
98286406 Andy McCall 1 month ago ↶
1514
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
1515
let ops = repo.op_log ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1516
assert_eq! (
1517
ops.iter ( )
1518
.map ( | ( _, o ) | o.command.as_str ( ) )
1519
.collect:: < Vec< _ > > ( ) ,
1520
[ " oxy commit" , " oxy init" ]
1521
) ;
98286406 Andy McCall 1 month ago ↶
1522
assert_eq! ( repo.heads_of ( & ops[ 1 ] .1. view, " main" ) .unwrap ( ) , vec! [ ] ) ;
1523
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
1524
1525
assert_eq! ( Repo:: open( root) .unwrap ( ) .op_log ( ) .unwrap ( ) .len ( ) , 2 ) ;
1526
}
1527
1528
# [ test ]
1529
fn undo_skips_snapshot_operations ( ) {
1530
hermetic ( ) ;
1531
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1532
let root = dir.path ( ) ;
1533
let repo = Repo:: init( root) .unwrap ( ) ;
1534
write ( root, " a.txt" , b " a\n " ) ;
1535
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
1536
1537
write ( root, " b.txt" , b " b\n " ) ;
1538
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
1539
assert! ( s.changed) ;
1540
assert! ( repo.head_op ( ) .unwrap ( ) .1. is_snapshot ( ) ) ;
1541
assert_eq! ( repo.last_command ( ) .unwrap ( ) .command, " oxy commit -m \" a\" " ) ;
1542
1543
repo.undo ( ) .unwrap ( ) ;
1544
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ ] ) ;
1545
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
1546
let paths: Vec< _ > = s.statuses.iter ( ) .map ( | p | p.path.as_str ( ) ) .collect ( ) ;
75167672 Andy McCall 1 month ago
1547
assert_eq! (
1548
paths,
1549
[ " a.txt" , " b.txt" ] ,
1550
" both files are back in the working copy"
1551
) ;
98286406 Andy McCall 1 month ago ↶
1552
assert! ( repo.store ( ) .get_change ( & h1) .unwrap ( ) .deps.is_empty ( ) ) ;
1553
1554
assert_eq! ( repo.last_command ( ) .unwrap ( ) .command, " oxy init" ) ;
1555
assert! ( repo.undo ( ) .is_err ( ) ) ;
1556
}
1557
1558
# [ test ]
1559
fn independent_changes_commute_and_make_multiple_heads ( ) {
1560
hermetic ( ) ;
1561
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1562
let root = dir.path ( ) ;
1563
let repo = Repo:: init( root) .unwrap ( ) ;
1564
write ( root, " a.txt" , b " a\n " ) ;
1565
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
1566
write ( root, " b.txt" , b " b\n " ) ;
1567
let h2 = repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
1568
let c2 = repo.store ( ) .get_change ( & h2) .unwrap ( ) ;
1569
assert! ( c2.deps.is_empty ( ) , " b.txt touches nothing from h1" ) ;
1570
let mut heads = repo.bookmark_heads ( ) .unwrap ( ) ;
1571
heads.sort ( ) ;
1572
let mut want = vec! [ h1, h2] ;
1573
want.sort ( ) ;
1574
assert_eq! ( heads, want) ;
1575
1576
write ( root, " a.txt" , b " a\n a2\n " ) ;
1577
let h3 = repo.commit ( " a2" , & [ ] ) .unwrap ( ) ;
1578
assert_eq! ( repo.store ( ) .get_change ( & h3) .unwrap ( ) .deps, vec! [ h1] ) ;
1579
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
1580
assert_eq! ( heads, [ h2, h3] .into_iter ( ) .collect ( ) ) ;
1581
1582
write ( root, " c.txt" , b " c\n " ) ;
1583
let h4 = repo.commit ( " c" , & [ h2] ) .unwrap ( ) ;
1584
assert_eq! ( repo.store ( ) .get_change ( & h4) .unwrap ( ) .deps, vec! [ h2] ) ;
1585
}
1586
1587
# [ test ]
1588
fn mode_change_and_text_binary_switch ( ) {
1589
hermetic ( ) ;
1590
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1591
let root = dir.path ( ) ;
1592
let repo = Repo:: init( root) .unwrap ( ) ;
1593
write ( root, " run.sh" , b " #!/bin/sh\n " ) ;
1594
repo.commit ( " add script" , & [ ] ) .unwrap ( ) ;
1595
# [ cfg ( unix ) ]
1596
{
1597
use std:: os:: unix:: fs:: PermissionsExt;
1598
fs:: set_permissions( root.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
1599
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1600
assert_eq! (
1601
statuses ( & s) ,
1602
vec! [ ( " run.sh" .into ( ) , ChangeStatus:: ModeChanged) ]
1603
) ;
98286406 Andy McCall 1 month ago ↶
1604
let h = repo.commit ( " exec" , & [ ] ) .unwrap ( ) ;
1605
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1606
assert! ( snap.files[ " run.sh" ] .mode.exec) ;
1607
}
1608
1609
write ( root, " run.sh" , b " \0 binary" ) ;
1610
let h = repo.commit ( " binary" , & [ ] ) .unwrap ( ) ;
1611
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1612
assert! ( snap.files[ " run.sh" ] .mode.opaque) ;
1613
write ( root, " run.sh" , b " text again\n " ) ;
1614
let h = repo.commit ( " text" , & [ ] ) .unwrap ( ) ;
1615
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1616
assert! ( ! snap.files[ " run.sh" ] .mode.opaque) ;
75167672 Andy McCall 1 month ago
1617
assert! (
1618
matches! ( & snap.files[ " run.sh" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " text again\n " )
1619
) ;
98286406 Andy McCall 1 month ago ↶
1620
assert! ( ! snap.is_conflicted ( ) ) ;
1621
}
83e0677b Andy McCall 1 month ago ↶
1622
6eae06b6 Andy McCall 27 days ago ↶
1623
1624
1625
# [ cfg ( unix ) ]
1626
# [ test ]
1627
fn a_binary_file_changed_and_made_executable_at_once_keeps_both ( ) {
1628
use std:: os:: unix:: fs:: PermissionsExt;
1629
hermetic ( ) ;
1630
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1631
let root = dir.path ( ) ;
1632
let repo = Repo:: init( root) .unwrap ( ) ;
1633
write ( root, " tool.exe" , b " MZ\0 one" ) ;
1634
repo.commit ( " add" , & [ ] ) .unwrap ( ) ;
1635
write ( root, " tool.exe" , b " MZ\0 two" ) ;
1636
fs:: set_permissions( root.join ( " tool.exe" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
1637
let h = repo.commit ( " update and +x" , & [ ] ) .unwrap ( ) ;
1638
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1639
let f = & snap.files[ " tool.exe" ] ;
1640
assert! ( f.mode.opaque & & f.mode.exec, " {:?}" , f.mode) ;
1641
assert! (
1642
repo.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ,
1643
" nothing left to commit"
1644
) ;
1645
}
1646
f0a517d2 Andy McCall 18 days ago ↶
1647
1648
1649
1650
83e0677b Andy McCall 1 month ago ↶
1651
# [ test ]
f0a517d2 Andy McCall 18 days ago ↶
1652
fn a_certified_key_pushes_under_a_subject_grant ( ) {
1653
use crate :: cert:: { Certificate, SignedCertificate} ;
1654
use crate :: identity:: Scope;
1655
hermetic ( ) ;
1656
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1657
let root = dir.path ( ) ;
1658
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1659
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1660
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1661
.unwrap ( )
1662
.with_home ( root.join ( " home-a" ) ) ;
1663
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1664
.unwrap ( ) ;
1665
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1666
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1667
a.policy_init ( ) .unwrap ( ) ;
1668
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1669
let hub_sk = SecretKey:: generate( ) ;
1670
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1671
a.policy_grant_subject (
1672
" agent:claude@ada" ,
1673
hub.clone ( ) ,
1674
Scope:: CHANGES | Scope:: REFS ,
1675
None ,
1676
)
1677
.unwrap ( ) ;
1678
assert! ( a
1679
.push ( " origin" , false )
1680
.unwrap ( )
1681
.iter ( )
1682
.any ( | r | r.name = = " policy" & & r.ok ) ) ;
1683
1684
let now = Hlc:: now( ) .wall_ms;
1685
1686
1687
let agent = | name : & str , subject : & str , from : u64 , to : u64 | {
1688
let mut r = Repo:: clone( & url, & root.join ( name) )
1689
.unwrap ( )
1690
.with_home ( root.join ( format! ( " home-{name} " ) ) ) ;
1691
let ( _ , id) = r
1692
.create_identity ( name, " [email protected] " , IdentityScope:: User)
1693
.unwrap ( ) ;
1694
let cert = SignedCertificate:: sign(
1695
Certificate {
1696
key: id,
1697
issuer: hub.hash ( ) ,
1698
subject: subject.into ( ) ,
1699
claims: vec! [ ] ,
1700
scope: Scope:: CHANGES | Scope:: REFS ,
1701
not_before: from,
1702
expires: to,
a2387d54 Andy McCall 18 days ago ↶
1703
grant: None ,
f0a517d2 Andy McCall 18 days ago ↶
1704
} ,
1705
& hub,
1706
& hub_sk,
1707
) ;
1708
r.store ( ) .put_identity ( & hub) .unwrap ( ) ;
1709
r.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1710
write ( & root.join ( name) , & format! ( " {name} .txt" ) , b " agent work\n " ) ;
1711
r.commit ( " work" , & [ ] ) .unwrap ( ) ;
1712
r
1713
} ;
1714
1715
let claude = agent ( " claude" , " agent:claude@ada" , now - 60_000 , now + 600_000 ) ;
1716
let r = claude.push ( " origin" , false ) .unwrap ( ) ;
1717
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
1718
1719
a.pull ( " origin" ) .unwrap ( ) ;
1720
assert! ( a.bookmark_heads ( ) .unwrap ( ) .iter ( ) .any ( | h | a
1721
.store ( )
1722
.get_change ( h)
1723
.map ( | c | c.message = = " work" )
1724
.unwrap_or ( false ) ) ) ;
1725
f53cbe7a Andy McCall 18 days ago ↶
1726
1727
1728
1729
1730
let host = Repo:: open( & root.join ( " origin" ) )
1731
.unwrap ( )
1732
.with_acceptor ( hub.clone ( ) , hub_sk.clone ( ) ) ;
1733
let ( attest_ref, sa) = host.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
1734
host.record_acceptance ( & attest_ref) .unwrap ( ) ;
1735
let served = host.remote_refs ( ) .unwrap ( ) ;
1736
let acc = served
1737
.refs
1738
.iter ( )
1739
.find ( | r | r.name = = " main" )
1740
.unwrap ( )
1741
.accepted
1742
.clone ( )
1743
.expect ( " served with the refs" ) ;
1744
let signer = sa.sigs[ 0 ] .key;
1745
assert_eq! (
1746
a.certified_time ( & signer, & sa, Some ( & acc) ) ,
1747
acc.attest.hlc.wall_ms
1748
) ;
1749
assert_eq! ( a.certified_time ( & signer, & sa, None ) , sa.attest.hlc.wall_ms) ;
1750
let stranger_sk = SecretKey:: generate( ) ;
1751
let stranger = stranger_sk.identity ( " stranger" , " s@x" , 1 ) ;
1752
let mut forged = acc.clone ( ) ;
1753
forged.sigs = vec! [ stranger_sk.sign ( Domain:: Attest, & stranger.hash ( ) , & acc.attest.hash ( ) ) ] ;
1754
assert_eq! (
1755
a.certified_time ( & signer, & sa, Some ( & forged) ) ,
1756
sa.attest.hlc.wall_ms,
1757
" not the issuer's"
1758
) ;
1759
let mut elsewhere = acc.clone ( ) ;
1760
elsewhere.attest.body = AttestBody:: Accepted {
1761
attest: ObjectRef( [ 7 ; 32 ] ) ,
1762
} ;
1763
elsewhere.sigs = vec! [ hub_sk.sign ( Domain:: Attest, & hub.hash ( ) , & elsewhere.attest.hash ( ) ) ] ;
1764
assert_eq! (
1765
a.certified_time ( & signer, & sa, Some ( & elsewhere) ) ,
1766
sa.attest.hlc.wall_ms,
1767
" another attestation's"
1768
) ;
1769
a.pull ( " origin" ) .unwrap ( ) ;
1770
1771
host.gc ( true ) .unwrap ( ) ;
1772
assert! ( host.acceptance_of ( & attest_ref) .unwrap ( ) .is_some ( ) ) ;
1773
f0a517d2 Andy McCall 18 days ago ↶
1774
let late = agent ( " late" , " agent:claude@ada" , now - 1_200_000 , now - 600_000 ) ;
1775
let r = late.push ( " origin" , false ) .unwrap ( ) ;
1776
assert! (
1777
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " may not update" ) ,
1778
" an expired certificate: {r:?}"
1779
) ;
1780
let other = agent ( " other" , " agent:mallory@ada" , now - 60_000 , now + 600_000 ) ;
1781
let r = other.push ( " origin" , false ) .unwrap ( ) ;
1782
assert! (
1783
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " may not update" ) ,
1784
" a subject not granted: {r:?}"
1785
) ;
1786
}
1787
d8524a9f Andy McCall 16 days ago ↶
1788
1789
1790
1791
1792
# [ test ]
1793
fn a_tag_signed_by_an_earlier_short_lived_key_is_pushed ( ) {
1794
use crate :: cert:: { Certificate, SignedCertificate} ;
1795
use crate :: identity:: Scope;
1796
hermetic ( ) ;
1797
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1798
let root = dir.path ( ) ;
1799
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1800
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1801
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1802
.unwrap ( )
1803
.with_home ( root.join ( " home-a" ) ) ;
1804
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1805
.unwrap ( ) ;
1806
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1807
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1808
a.policy_init ( ) .unwrap ( ) ;
1809
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1810
let hub_sk = SecretKey:: generate( ) ;
1811
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1812
a.policy_grant_subject (
1813
" agent:claude@ada" ,
1814
hub.clone ( ) ,
1815
Scope:: CHANGES | Scope:: REFS ,
1816
Some ( " *" ) ,
1817
)
1818
.unwrap ( ) ;
1819
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1820
1821
let now = Hlc:: now( ) .wall_ms;
1822
let mut agent = Repo:: clone( & url, & root.join ( " agent" ) )
1823
.unwrap ( )
1824
.with_home ( root.join ( " home-agent" ) ) ;
1825
agent.store ( ) .put_identity ( & hub) .unwrap ( ) ;
1826
let certify = | agent : & Repo, id | {
1827
let cert = SignedCertificate:: sign(
1828
Certificate {
1829
key: id,
1830
issuer: hub.hash ( ) ,
1831
subject: " agent:claude@ada" .into ( ) ,
1832
claims: vec! [ ] ,
1833
scope: Scope:: CHANGES | Scope:: REFS ,
1834
not_before: now - 60_000 ,
1835
expires: now + 600_000 ,
1836
grant: None ,
1837
} ,
1838
& hub,
1839
& hub_sk,
1840
) ;
1841
agent.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1842
} ;
1843
1844
let ( first, first_id) = agent
1845
.create_identity ( " claude" , " [email protected] " , IdentityScope:: User)
1846
.unwrap ( ) ;
1847
certify ( & agent, first_id) ;
1848
let heads = agent.bookmark_heads ( ) .unwrap ( ) ;
1849
agent.tag_create ( " v1" , & heads, " first" ) .unwrap ( ) ;
1850
1851
let ( second, second_id) = agent
1852
.create_identity ( " claude" , " [email protected] " , IdentityScope:: User)
1853
.unwrap ( ) ;
1854
assert_ne! ( first, second) ;
1855
certify ( & agent, second_id) ;
1856
agent
1857
.set_user_config ( " author.signer" , & second.to_hex ( ) )
1858
.unwrap ( ) ;
1859
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
1ec6fdbc Andy McCall 16 days ago ↶
1860
assert! (
1861
host.store ( ) .get_identity ( & first) .is_err ( ) ,
1862
" the host has never seen the tag's key"
1863
) ;
d8524a9f Andy McCall 16 days ago ↶
1864
let r = agent.push ( " origin" , false ) .unwrap ( ) ;
1ec6fdbc Andy McCall 16 days ago ↶
1865
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
1866
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
1867
assert! (
1868
host.tag_refs ( ) .unwrap ( ) .iter ( ) .any ( | t | t.name = = " v1" ) ,
1869
" the tag arrived"
1870
) ;
1871
}
1872
1873
1874
1875
1876
1877
1878
1879
# [ test ]
1880
fn a_short_lived_key_s_tag_is_fetched_at_its_issuer_s_acceptance ( ) {
1881
use crate :: cert:: { Certificate, SignedCertificate} ;
1882
use crate :: identity:: Scope;
1883
hermetic ( ) ;
1884
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1885
let root = dir.path ( ) ;
1886
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1887
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1888
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1889
.unwrap ( )
1890
.with_home ( root.join ( " home-a" ) ) ;
1891
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1892
.unwrap ( ) ;
1893
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1894
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1895
a.policy_init ( ) .unwrap ( ) ;
1896
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1897
let hub_sk = SecretKey:: generate( ) ;
1898
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1899
a.policy_grant_subject (
1900
" agent:claude@ada" ,
1901
hub.clone ( ) ,
1902
Scope:: CHANGES | Scope:: REFS ,
1903
Some ( " *" ) ,
1904
)
1905
.unwrap ( ) ;
1906
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1907
1908
let now = Hlc:: now( ) .wall_ms;
1909
1910
let agent = | name : & str , from : u64 , to : u64 | {
1911
let mut r = Repo:: clone( & url, & root.join ( name) )
1912
.unwrap ( )
1913
.with_home ( root.join ( format! ( " home-{name} " ) ) ) ;
1914
r.store ( ) .put_identity ( & hub) .unwrap ( ) ;
1915
let ( _ , id) = r
1916
.create_identity ( name, " [email protected] " , IdentityScope:: User)
1917
.unwrap ( ) ;
1918
let cert = SignedCertificate:: sign(
1919
Certificate {
1920
key: id,
1921
issuer: hub.hash ( ) ,
1922
subject: " agent:claude@ada" .into ( ) ,
1923
claims: vec! [ ] ,
1924
scope: Scope:: CHANGES | Scope:: REFS ,
1925
not_before: from,
1926
expires: to,
1927
grant: None ,
1928
} ,
1929
& hub,
1930
& hub_sk,
1931
) ;
1932
r.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1933
r
1934
} ;
1935
1936
1937
1938
1939
let claude = agent ( " claude" , now - 60_000 , now + 600_000 ) ;
1940
let heads = claude.bookmark_heads ( ) .unwrap ( ) ;
1941
claude.tag_create ( " v1" , & heads, " one" ) .unwrap ( ) ;
1942
let r = claude.push ( " origin" , false ) .unwrap ( ) ;
d8524a9f Andy McCall 16 days ago ↶
1943
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
1ec6fdbc Andy McCall 16 days ago ↶
1944
let b = Repo:: clone( & url, & root.join ( " b" ) )
1945
.unwrap ( )
1946
.with_home ( root.join ( " home-b" ) ) ;
1947
let rep = b.fetch ( " origin" ) .unwrap ( ) ;
1948
assert! (
1949
rep.skipped
1950
.iter ( )
1951
.any ( | ( n , why ) | n = = " tags/v1" & & why.contains ( " no time to check its certificate at" ) ) ,
1952
" {rep:?}"
1953
) ;
1954
assert_eq! ( b.tag_heads ( " v1" ) .unwrap ( ) , None ) ;
1955
1956
1957
1958
let host = Repo:: open( & root.join ( " origin" ) )
1959
.unwrap ( )
1960
.with_acceptor ( hub.clone ( ) , hub_sk.clone ( ) ) ;
1961
let ( v1, _ ) = host.tag_attest ( " v1" ) .unwrap ( ) .unwrap ( ) ;
1962
host.record_acceptance ( & v1) .unwrap ( ) ;
1963
let served = host.tag_refs ( ) .unwrap ( ) ;
1964
assert! ( served
1965
.iter ( )
1966
.any ( | t | t.name = = " v1" & & t.accepted.is_some ( ) ) ) ;
1967
let rep = b.fetch ( " origin" ) .unwrap ( ) ;
1968
assert! ( rep.skipped.is_empty ( ) , " {rep:?}" ) ;
1969
assert_eq! ( b.tag_heads ( " v1" ) .unwrap ( ) , Some ( heads.clone ( ) ) ) ;
1970
1971
host.gc ( true ) .unwrap ( ) ;
1972
assert! ( host.acceptance_of ( & v1) .unwrap ( ) .is_some ( ) ) ;
1973
1974
1975
1976
let late = agent ( " late" , now - 7_200_000 , now - 3_600_000 ) ;
1977
late.tag_create ( " v2" , & heads, " two" ) .unwrap ( ) ;
1978
let r = late.push ( " origin" , false ) .unwrap ( ) ;
1979
assert! (
1980
r.iter ( )
1981
.any ( | x | x.name = = " tags/v2" & & ! x.ok & & x.reason.contains ( " may not create" ) ) ,
1982
" {r:?}"
1983
) ;
1984
1985
1986
1987
let ( v2, _ ) = late.tag_attest ( " v2" ) .unwrap ( ) .unwrap ( ) ;
1988
let acc = Attest {
1989
repo: late.id ( ) .unwrap ( ) ,
1990
hlc: Hlc {
1991
wall_ms: now - 5_400_000 ,
1992
logical: 0 ,
1993
} ,
1994
body: AttestBody:: Accepted { attest: v2 } ,
1995
} ;
1996
let sig = hub_sk.sign ( Domain:: Attest, & hub.hash ( ) , & acc.hash ( ) ) ;
1997
1998
let stranger_sk = SecretKey:: generate( ) ;
1999
let stranger = stranger_sk.identity ( " stranger" , " [email protected] " , 1 ) ;
2000
let forged = stranger_sk.sign ( Domain:: Attest, & stranger.hash ( ) , & acc.hash ( ) ) ;
2001
late.keep_acceptance (
2002
& v2,
2003
& SignedAttest {
2004
attest: acc.clone ( ) ,
2005
sigs: vec! [ forged] ,
2006
} ,
2007
)
2008
.unwrap ( ) ;
2009
let r = late.push ( " origin" , false ) .unwrap ( ) ;
2010
assert! (
2011
r.iter ( ) .any ( | x | x.name = = " tags/v2" & & ! x.ok ) ,
2012
" not the issuer's: {r:?}"
2013
) ;
2014
let theirs = SignedAttest {
2015
attest: acc,
2016
sigs: vec! [ sig] ,
2017
} ;
2018
2019
late.keep_acceptance ( & v2, & theirs) .unwrap ( ) ;
2020
let r = late.push ( " origin" , false ) .unwrap ( ) ;
2021
2022
2023
assert! ( r.iter ( ) .any ( | x | x.name = = " tags/v2" & & x.ok ) , " {r:?}" ) ;
d8524a9f Andy McCall 16 days ago ↶
2024
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
1ec6fdbc Andy McCall 16 days ago ↶
2025
let ( hv2, _ ) = host.tag_attest ( " v2" ) .unwrap ( ) .unwrap ( ) ;
2026
assert_eq! ( hv2, v2) ;
2027
assert! ( host
2028
.store ( )
2029
.index_get_pub ( " accepted" , & v2)
2030
.unwrap ( )
2031
.contains ( & theirs.attest.hash ( ) ) ) ;
2032
let c = Repo:: clone( & url, & root.join ( " c" ) )
2033
.unwrap ( )
2034
.with_home ( root.join ( " home-c" ) ) ;
2035
assert_eq! ( c.tag_heads ( " v2" ) .unwrap ( ) , Some ( heads.clone ( ) ) ) ;
2036
assert_eq! ( c.tag_heads ( " v1" ) .unwrap ( ) , Some ( heads) ) ;
d8524a9f Andy McCall 16 days ago ↶
2037
}
2038
d36ba2f7 Andy McCall 16 days ago ↶
2039
2040
2041
2042
2043
2044
# [ test ]
2045
fn a_bookmark_moved_by_a_certified_key_alone_is_fetched ( ) {
2046
use crate :: cert:: { Certificate, SignedCertificate} ;
2047
use crate :: identity:: Scope;
2048
hermetic ( ) ;
2049
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2050
let root = dir.path ( ) ;
2051
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
2052
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
2053
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
2054
.unwrap ( )
2055
.with_home ( root.join ( " home-a" ) ) ;
2056
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
2057
.unwrap ( ) ;
2058
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
2059
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
2060
a.policy_init ( ) .unwrap ( ) ;
2061
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
2062
let hub_sk = SecretKey:: generate( ) ;
2063
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
2064
a.policy_grant_subject (
2065
" agent:claude@ada" ,
2066
hub.clone ( ) ,
2067
Scope:: CHANGES | Scope:: REFS ,
2068
None ,
2069
)
2070
.unwrap ( ) ;
2071
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
2072
2073
let now = Hlc:: now( ) .wall_ms;
2074
let mut agent = Repo:: clone( & url, & root.join ( " agent" ) )
2075
.unwrap ( )
2076
.with_home ( root.join ( " home-agent" ) ) ;
2077
let ( key, id) = agent
2078
.create_identity ( " claude" , " [email protected] " , IdentityScope:: User)
2079
.unwrap ( ) ;
2080
let cert = SignedCertificate:: sign(
2081
Certificate {
2082
key: id,
2083
issuer: hub.hash ( ) ,
2084
subject: " agent:claude@ada" .into ( ) ,
2085
claims: vec! [ ] ,
2086
scope: Scope:: CHANGES | Scope:: REFS ,
2087
not_before: now - 60_000 ,
2088
expires: now + 600_000 ,
2089
grant: None ,
2090
} ,
2091
& hub,
2092
& hub_sk,
2093
) ;
2094
agent.store ( ) .put_identity ( & hub) .unwrap ( ) ;
2095
agent.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
2096
let heads = agent.bookmark_heads ( ) .unwrap ( ) ;
2097
agent.bookmark_create ( " agent-work" , Some ( & heads) ) .unwrap ( ) ;
2098
let r = agent.push_bookmark ( " origin" , " agent-work" , false ) .unwrap ( ) ;
2099
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
2100
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
2101
let ( _ , sa) = host.latest_ref_attest ( " agent-work" ) .unwrap ( ) .unwrap ( ) ;
2102
assert_eq! ( sa.sigs[ 0 ] .key, key, " the certified key alone moved it" ) ;
2103
2104
2105
let bundle = host.bundle_raw_for ( & [ ] , None ) .unwrap ( ) ;
2106
assert! ( bundle.changes.is_empty ( ) & & bundle.raw_changes.is_empty ( ) ) ;
2107
assert_eq! ( bundle.certificates.len ( ) , 1 ) ;
2108
for k in [ key, hub.hash ( ) ] {
2109
assert! ( bundle
2110
.objects
2111
.iter ( )
2112
.any ( | ( t , b ) | * t = = ObjectType:: Identity & & hash_object ( * t, b) = = k) ) ;
2113
}
2114
2115
let c = Repo:: clone( & url, & root.join ( " c" ) ) .unwrap ( ) ;
2116
let tracked = c.view ( ) .unwrap ( ) .remotes[ " origin" ] .clone ( ) ;
2117
assert_eq! (
2118
tracked.get ( " agent-work" ) ,
2119
host.view ( ) .unwrap ( ) .bookmarks.get ( " agent-work" )
2120
) ;
2121
assert! ( c.store ( ) .certificate_of ( & key) .unwrap ( ) .is_some ( ) ) ;
2122
}
2123
028545df Andy McCall 17 days ago ↶
2124
2125
2126
2127
2128
2129
2130
# [ test ]
2131
fn a_fetched_attestation_is_judged_at_its_integrated_time ( ) {
2132
use super :: transparency:: LogCheck;
2133
use crate :: cert:: { Certificate, SignedCertificate} ;
2134
use crate :: identity:: Scope;
2135
use crate :: protocol:: { Integration, Logged, RefsResponse, RemoteRef} ;
2136
use crate :: tlog:: { self , Checkpoint, Entry, Hash, Integrated, Note} ;
2137
hermetic ( ) ;
2138
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2139
let root = dir.path ( ) ;
2140
let mut a = Repo:: init( & root.join ( " a" ) )
2141
.unwrap ( )
2142
.with_home ( root.join ( " home-a" ) ) ;
2143
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
2144
.unwrap ( ) ;
2145
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
2146
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
2147
a.policy_init ( ) .unwrap ( ) ;
2148
let issuer_sk = SecretKey:: generate( ) ;
2149
let issuer = issuer_sk.identity ( " hub" , " [email protected] " , 1 ) ;
2150
a.policy_grant_subject (
2151
" agent:claude@ada" ,
2152
issuer.clone ( ) ,
2153
Scope:: CHANGES | Scope:: REFS ,
2154
None ,
2155
)
2156
.unwrap ( ) ;
2157
let p = a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy;
2158
2159
2160
let now = Hlc:: now( ) .wall_ms;
2161
let t0 = now - 2 * 3_600_000 ;
2162
let agent_sk = SecretKey:: generate( ) ;
2163
let agent = agent_sk.identity ( " claude" , " [email protected] " , t0) ;
2164
let cert = SignedCertificate:: sign(
2165
Certificate {
2166
key: agent.clone ( ) ,
2167
issuer: issuer.hash ( ) ,
2168
subject: " agent:claude@ada" .into ( ) ,
2169
claims: vec! [ ] ,
2170
scope: Scope:: CHANGES | Scope:: REFS ,
2171
not_before: t0,
2172
expires: t0 + 600_000 ,
2173
grant: None ,
2174
} ,
2175
& issuer,
2176
& issuer_sk,
2177
) ;
2178
a.store ( ) .put_identity ( & agent) .unwrap ( ) ;
2179
a.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
2180
let signer = agent.hash ( ) ;
2181
let attested = | claimed : u64 | {
2182
let attest = Attest {
2183
repo: a.id ( ) .unwrap ( ) ,
2184
hlc: Hlc {
2185
wall_ms: claimed,
2186
logical: 0 ,
2187
} ,
2188
body: AttestBody:: Ref {
2189
name: " main" .into ( ) ,
2190
old_state: ObjectRef( [ 1 ; 32 ] ) ,
2191
new_state: ObjectRef( [ 2 ; 32 ] ) ,
2192
serial: 1 ,
2193
} ,
2194
} ;
2195
let sig = agent_sk.sign ( Domain:: Attest, & signer, & attest.hash ( ) ) ;
2196
SignedAttest {
2197
attest,
2198
sigs: vec! [ sig] ,
2199
}
2200
} ;
2201
2202
2203
2204
let log_sk = SecretKey:: generate( ) ;
2205
let log_key = log_sk.identity ( " hub.example" , " [email protected] " , 1 ) ;
2206
let origin = " hub.example/log" ;
2207
let hub = " https://hub.example" ;
2208
let url = " https://hub.example/ada/a" ;
2209
let w_sk = SecretKey:: generate( ) ;
2210
let w = w_sk.identity ( " w" , " [email protected] " , 1 ) ;
2211
let stranger_sk = SecretKey:: generate( ) ;
2212
let stranger = stranger_sk.identity ( " s" , " [email protected] " , 1 ) ;
2213
let sa = attested ( t0 + 300_000 ) ;
2214
let entry_of = | i : u64 | {
2215
let ( kind, object) = if i = = 3 {
2216
( " ref" , sa.attest.hash ( ) )
2217
} else {
2218
( " seal" , ObjectRef( [ i as u8 ; 32 ] ) )
2219
} ;
2220
Entry {
2221
kind: kind.into ( ) ,
2222
repo: sa.attest.repo,
2223
object,
2224
signers: vec! [ signer] ,
2225
received: t0 + 300_000 ,
2226
}
2227
.encode ( )
2228
} ;
2229
let mut stored: Vec< Hash> = Vec :: new( ) ;
2230
for i in 0 .. 8 {
2231
let add = tlog:: stored_hashes_for_leaf( i, tlog:: leaf_hash( & entry_of ( i) ) , & stored) .unwrap ( ) ;
2232
stored.extend ( add) ;
2233
}
2234
let note_of = | n : u64 , cosigners : & [( & str , & Identity, & SecretKey, u64 ) ]| {
2235
let mut note = Checkpoint {
2236
origin: origin.into ( ) ,
2237
size: n,
2238
root: tlog:: tree_hash( n, & stored) .unwrap ( ) ,
2239
extensions: vec! [ ] ,
2240
}
2241
.sign ( & log_key, & log_sk)
2242
.unwrap ( ) ;
2243
for ( name, id, sk, t) in cosigners {
2244
note.sigs
2245
.extend ( tlog:: cosign( & note.text, name, id, sk, * t) .unwrap ( ) ) ;
2246
}
2247
note.encode ( )
2248
} ;
2249
let latest = note_of ( 8 , & [ ] ) ;
2250
let witnessed_at = ( t0 + 360_000 ) / 1000 ;
2251
let cosigned = note_of (
2252
4 ,
2253
& [
2254
( " w.example" , & w, & w_sk, witnessed_at) ,
2255
( " s.example" , & stranger, & stranger_sk, 1 ) ,
2256
] ,
2257
) ;
2258
let answer = | integration : Vec< Integration> , witnesses : Vec< ( String, Vec< u8 > ) > | RefsResponse {
2259
refs: vec! [ RemoteRef {
2260
name: " main" .into ( ) ,
2261
state: ObjectRef( [ 2 ; 32 ] ) ,
2262
heads: vec! [ ] ,
2263
attestation: Some ( sa.clone ( ) ) ,
2264
last_serial: None ,
2265
accepted: None ,
2266
logged: Some ( Logged {
2267
index: 3 ,
2268
entry: entry_of ( 3 ) ,
2269
proof: tlog:: prove_inclusion( 8 , 3 , & stored) .unwrap ( ) ,
2270
} ) ,
2271
integration,
2272
} ] ,
2273
log: Some ( latest.clone ( ) ) ,
2274
witnesses,
2275
.. RefsResponse:: default( )
2276
} ;
2277
let proof4 = tlog:: prove_inclusion( 4 , 3 , & stored) .unwrap ( ) ;
2278
let served = vec! [ Integration {
2279
note: cosigned.clone ( ) ,
2280
proof: proof4.clone ( ) ,
2281
} ] ;
2282
let listed = vec! [ ( " w.example" .to_string ( ) , w.encode ( ) ) ] ;
2283
2284
a.remember_log ( hub, & log_key, & Note:: parse( & latest) .unwrap ( ) )
2285
.unwrap ( ) ;
2286
2287
2288
2289
let resp = answer ( served.clone ( ) , listed.clone ( ) ) ;
2290
let lc = a.check_log ( url, None , & resp) .unwrap ( ) ;
2291
assert! ( lc.notes.is_empty ( ) , " {:?}" , lc.notes) ;
2292
let t = lc.time_of ( " main" ) .unwrap ( ) ;
2293
assert_eq! ( ( t.time, t.witness.as_str ( ) ) , ( witnessed_at, " w.example" ) ) ;
2294
2295
2296
assert! ( cert.cert.expires < now) ;
2297
let mut notes = Vec :: new( ) ;
2298
a.authorize_in_time ( & p, " origin" , & resp.refs[ 0 ] , & sa, & signer, & lc, & mut notes)
2299
.unwrap ( ) ;
2300
assert! ( notes.is_empty ( ) , " {notes:?}" ) ;
2301
2302
2303
2304
let lonely = vec! [ Integration {
2305
note: note_of ( 4 , & [ ( " s.example" , & stranger, & stranger_sk, 1 ) ] ) ,
2306
proof: proof4.clone ( ) ,
2307
} ] ;
2308
let resp = answer ( lonely, vec! [ ] ) ;
2309
let lc = a.check_log ( url, None , & resp) .unwrap ( ) ;
2310
assert! ( lc
2311
.time_of ( " main" )
2312
.unwrap_err ( )
2313
.contains ( " no witness this user trusts" ) ) ;
2314
let mut notes = Vec :: new( ) ;
2315
a.authorize_in_time ( & p, " origin" , & resp.refs[ 0 ] , & sa, & signer, & lc, & mut notes)
2316
.unwrap ( ) ;
2317
assert! (
2318
notes.len ( ) = = 1 & & notes[ 0 ] .contains ( " not at a witnessed time" ) ,
2319
" {notes:?}"
2320
) ;
2321
2322
2323
2324
let resp = answer (
2325
served.clone ( ) ,
2326
vec! [ ( " w.example" .into ( ) , stranger.encode ( ) ) ] ,
2327
) ;
2328
let lc = a.check_log ( url, None , & resp) .unwrap ( ) ;
2329
assert! (
2330
lc.notes.iter ( ) .any ( | n | n.contains ( " another key" ) ) ,
2331
" {:?}" ,
2332
lc.notes
2333
) ;
2334
assert! (
2335
lc.time_of ( " main" ) .is_err ( ) ,
2336
" w's cosignature is by its old key"
2337
) ;
2338
a.check_log ( url, None , & answer ( served.clone ( ) , listed.clone ( ) ) )
2339
.unwrap ( ) ;
2340
2341
2342
2343
let mut old = answer ( vec! [ ] , vec! [ ] ) ;
2344
old.log = None ;
2345
old.refs[ 0 ] .logged = None ;
2346
let lc = a.check_log ( url, None , & old) .unwrap ( ) ;
2347
assert! ( ! lc.kept & & lc.notes.is_empty ( ) ) ;
2348
let mut notes = Vec :: new( ) ;
2349
a.authorize_in_time ( & p, " origin" , & old.refs[ 0 ] , & sa, & signer, & lc, & mut notes)
2350
.unwrap ( ) ;
2351
assert! ( notes.is_empty ( ) , " {notes:?}" ) ;
2352
2353
2354
let at = | secs_after_t0 : u64 | {
2355
let mut times = std:: collections:: HashMap:: new( ) ;
2356
times.insert (
2357
" main" .to_string ( ) ,
2358
Ok ( Integrated {
2359
time: ( t0 + secs_after_t0 * 1000 ) / 1000 ,
2360
witness: " w.example" .into ( ) ,
2361
size: 4 ,
2362
} ) ,
2363
) ;
2364
LogCheck {
2365
kept: true ,
2366
times,
2367
.. LogCheck:: default( )
2368
}
2369
} ;
2370
let judged = | sa : & SignedAttest, lc : & LogCheck| {
2371
let mut notes = Vec :: new( ) ;
2372
a.authorize_in_time ( & p, " origin" , & old.refs[ 0 ] , sa, & signer, lc, & mut notes)
2373
.map ( | _| notes )
2374
} ;
2375
2376
2377
2378
let notes = judged ( & sa, & at ( 3600 ) ) .unwrap ( ) ;
2379
assert! (
2380
notes
2381
.iter ( )
2382
.any ( | n | n.contains ( " had lapsed when witness w.example" ) )
2383
& & notes
2384
.iter ( )
2385
.any ( | n | n.contains ( " 55 minutes before witness" ) ) ,
2386
" {notes:?}"
2387
) ;
2388
2389
2390
2391
let fast = attested ( now) ;
2392
let notes = judged ( & fast, & at ( 400 ) ) .unwrap ( ) ;
2393
assert! (
2394
notes.len ( ) = = 1 & & notes[ 0 ] .contains ( " after witness w.example saw it" ) ,
2395
" {notes:?}"
2396
) ;
2397
2398
assert! ( judged ( & fast, & at ( 3600 ) ) .is_err ( ) ) ;
2399
2400
assert! ( judged ( & sa, & at ( 500 ) ) .unwrap ( ) .is_empty ( ) ) ;
2401
}
2402
a2387d54 Andy McCall 18 days ago ↶
2403
2404
2405
2406
45a6f5ec Andy McCall 11 days ago ↶
2407
2408
2409
f0a517d2 Andy McCall 18 days ago ↶
2410
# [ test ]
45a6f5ec Andy McCall 11 days ago ↶
2411
fn a_grant_outlives_its_owner_s_rotation ( ) {
2412
use crate :: cert:: { Certificate, Grant, SignedCertificate, SignedGrant} ;
2413
use crate :: identity:: Scope;
2414
hermetic ( ) ;
2415
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2416
let root = dir.path ( ) ;
2417
let mut a = Repo:: init( & root.join ( " a" ) )
2418
.unwrap ( )
2419
.with_home ( root.join ( " home-a" ) ) ;
2420
let ( old, _ ) = a
2421
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
2422
.unwrap ( ) ;
2423
let ( _ , old_sk) = a.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
2424
let old_id = a.identity ( & old) .unwrap ( ) ;
2425
let hub_sk = SecretKey:: generate( ) ;
2426
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
2427
let now = Hlc:: now( ) .wall_ms;
2428
let grant = | from : u64 | {
2429
SignedGrant:: sign(
2430
Grant {
2431
grantor: old,
2432
subject: " agent:claude@ada" .into ( ) ,
2433
issuer: hub.hash ( ) ,
2434
scope: Scope:: CHANGES | Scope:: SEAL ,
2435
not_before: from,
2436
expires: now + 86_400_000 ,
2437
} ,
2438
& old_id,
2439
& old_sk,
2440
)
2441
} ;
2442
let before = grant ( now - 60_000 ) ;
2443
let after = grant ( now + 60_000 ) ;
2444
let rot = a.renew_identity ( None ) .unwrap ( ) ;
2445
assert_ne! ( rot.new, old) ;
2446
let agent = | name : & str , g : SignedGrant| {
2447
let mut r = Repo:: init( & root.join ( name) )
2448
.unwrap ( )
2449
.with_home ( root.join ( format! ( " home-{name} " ) ) ) ;
2450
let ( key, id) = r
2451
.create_identity ( name, " [email protected] " , IdentityScope:: User)
2452
.unwrap ( ) ;
2453
for i in [ & old_id, & rot.identity, & hub] {
2454
r.store ( ) .put_identity ( i) .unwrap ( ) ;
2455
}
2456
r.set_user_config ( " author.key" , & rot.new.to_hex ( ) ) .unwrap ( ) ;
2457
r.set_user_config ( " author.signer" , & key.to_hex ( ) ) .unwrap ( ) ;
2458
let cert = SignedCertificate:: sign(
2459
Certificate {
2460
key: id,
2461
issuer: hub.hash ( ) ,
2462
subject: " agent:claude@ada" .into ( ) ,
2463
claims: vec! [ ] ,
2464
scope: Scope:: CHANGES | Scope:: REFS | Scope:: SEAL ,
2465
not_before: now - 60_000 ,
2466
expires: now + 600_000 ,
2467
grant: Some ( g) ,
2468
} ,
2469
& hub,
2470
& hub_sk,
2471
) ;
2472
r.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
2473
write ( & root.join ( name) , " work.txt" , b " agent work\n " ) ;
2474
let h = r.commit ( " work" , & [ ] ) .unwrap ( ) ;
2475
assert_eq! ( r.store ( ) .get_change_meta ( & h) .unwrap ( ) .author, rot.new) ;
2476
r.seal ( & [ h] ) .unwrap ( ) ;
2477
( r, h)
2478
} ;
2479
let ( r, h) = agent ( " kept" , before) ;
2480
assert! (
2481
matches! (
2482
r.authenticate_change ( & h) .unwrap ( ) ,
2483
SignatureStatus:: Valid { .. }
2484
) ,
2485
" {:?}" ,
2486
r.authenticate_change ( & h) .unwrap ( )
2487
) ;
2488
let ( r, h) = agent ( " late" , after) ;
2489
assert! (
2490
matches! ( r.authenticate_change ( & h) .unwrap ( ) , SignatureStatus:: Invalid( e) if e.contains ( " neither" ) ) ,
2491
" a grant the old key made after the rotation: {:?}" ,
2492
r.authenticate_change ( & h) .unwrap ( )
2493
) ;
2494
}
2495
2496
# [ test ]
a2387d54 Andy McCall 18 days ago ↶
2497
fn a_certified_key_acts_for_its_author_under_a_grant ( ) {
2498
use crate :: cert:: { Certificate, Grant, SignedCertificate, SignedGrant} ;
2499
use crate :: identity:: { Identity, Scope} ;
2500
hermetic ( ) ;
2501
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2502
let root = dir.path ( ) ;
2503
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
2504
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
2505
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
2506
.unwrap ( )
2507
.with_home ( root.join ( " home-a" ) ) ;
2508
let ( ada, _ ) = a
2509
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
2510
.unwrap ( ) ;
2511
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
2512
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
2513
a.policy_init ( ) .unwrap ( ) ;
2514
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
2515
let hub_sk = SecretKey:: generate( ) ;
2516
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
2517
a.policy_grant_subject (
2518
" agent:claude@ada" ,
2519
hub.clone ( ) ,
2520
Scope:: CHANGES | Scope:: REFS ,
2521
None ,
2522
)
2523
.unwrap ( ) ;
2524
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
2525
let ( _ , ada_sk) = a.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
2526
let ada_id = a.identity ( & ada) .unwrap ( ) ;
2527
2528
let now = Hlc:: now( ) .wall_ms;
2529
let grant = | by : & Identity, sk : & SecretKey, from : u64 , to : u64 | {
2530
SignedGrant:: sign(
2531
Grant {
2532
grantor: by.hash ( ) ,
2533
subject: " agent:claude@ada" .into ( ) ,
2534
issuer: hub.hash ( ) ,
2535
scope: Scope:: CHANGES | Scope:: SEAL ,
2536
not_before: from,
2537
expires: to,
2538
} ,
2539
by,
2540
sk,
2541
)
2542
} ;
2543
2544
2545
let agent = | name : & str , g : Option< SignedGrant> | {
2546
let mut r = Repo:: clone( & url, & root.join ( name) )
2547
.unwrap ( )
2548
.with_home ( root.join ( format! ( " home-{name} " ) ) ) ;
2549
let ( key, id) = r
2550
.create_identity ( name, " [email protected] " , IdentityScope:: User)
2551
.unwrap ( ) ;
2552
r.set_user_config ( " author.key" , & ada.to_hex ( ) ) .unwrap ( ) ;
2553
r.set_user_config ( " author.signer" , & key.to_hex ( ) ) .unwrap ( ) ;
2554
let cert = SignedCertificate:: sign(
2555
Certificate {
2556
key: id,
2557
issuer: hub.hash ( ) ,
2558
subject: " agent:claude@ada" .into ( ) ,
2559
claims: vec! [ ] ,
2560
scope: Scope:: CHANGES | Scope:: REFS | Scope:: SEAL ,
2561
not_before: now - 60_000 ,
2562
expires: now + 600_000 ,
2563
grant: g,
2564
} ,
2565
& hub,
2566
& hub_sk,
2567
) ;
2568
r.store ( ) .put_identity ( & hub) .unwrap ( ) ;
2569
r.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
2570
write ( & root.join ( name) , & format! ( " {name} .txt" ) , b " agent work\n " ) ;
2571
let h = r.commit ( " work" , & [ ] ) .unwrap ( ) ;
2572
assert_eq! ( r.store ( ) .get_change_meta ( & h) .unwrap ( ) .author, ada) ;
2573
( r, key, h)
2574
} ;
2575
2576
let ( claude, key, h) = agent (
2577
" claude" ,
2578
Some ( grant ( & ada_id, & ada_sk, now - 60_000 , now + 86_400_000 ) ) ,
2579
) ;
2580
let r = claude.push ( " origin" , false ) .unwrap ( ) ;
2581
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
2582
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
68dcf21f Andy McCall 18 days ago ↶
2583
assert! (
2584
matches! (
2585
host.authenticate_change ( & h) .unwrap ( ) ,
2586
SignatureStatus:: Valid { key: k, sealed: true , name, .. } if k = = key & & name = = " agent:claude@ada"
2587
) ,
2588
" named by its certificate's subject"
2589
) ;
a2387d54 Andy McCall 18 days ago ↶
2590
a.pull ( " origin" ) .unwrap ( ) ;
2591
assert! ( matches! (
2592
a.authenticate_change ( & h) .unwrap ( ) ,
2593
SignatureStatus:: Valid { key: k, .. } if k = = key
2594
) ) ;
2595
2596
let ( none, _ , _ ) = agent ( " none" , None ) ;
2597
let r = none.push ( " origin" , false ) .unwrap ( ) ;
2598
assert! ( r.iter ( ) .all ( | x | ! x.ok) , " no grant: {r:?}" ) ;
2599
let mallory_sk = SecretKey:: generate( ) ;
2600
let mallory = mallory_sk.identity ( " Mallory" , " [email protected] " , 1 ) ;
2601
let ( stranger, _ , _ ) = agent (
2602
" stranger" ,
2603
Some ( grant ( & mallory, & mallory_sk, now - 60_000 , now + 600_000 ) ) ,
2604
) ;
2605
let r = stranger.push ( " origin" , false ) .unwrap ( ) ;
2606
assert! ( r.iter ( ) .all ( | x | ! x.ok) , " a stranger's grant: {r:?}" ) ;
2607
let ( ended, _ , eh) = agent (
2608
" ended" ,
2609
Some ( grant ( & ada_id, & ada_sk, now - 7_200_000 , now - 3_600_000 ) ) ,
2610
) ;
2611
let r = ended.push ( " origin" , false ) .unwrap ( ) ;
2612
assert! ( r.iter ( ) .all ( | x | ! x.ok) , " an ended grant: {r:?}" ) ;
2613
assert! ( matches! (
2614
ended.authenticate_change ( & eh) .unwrap ( ) ,
2615
SignatureStatus:: Invalid( e) if e.contains ( " grant did not cover" )
2616
) ) ;
2617
}
2618
2619
# [ test ]
83e0677b Andy McCall 1 month ago ↶
2620
fn trust_policy_governs_pushes_and_fetches ( ) {
2621
hermetic ( ) ;
2622
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2623
let root = dir.path ( ) ;
2624
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
2625
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
2626
2627
75167672 Andy McCall 1 month ago
2628
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
2629
.unwrap ( )
2630
.with_home ( root.join ( " home-a" ) ) ;
2631
let ( key_a, _ ) = a
2632
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
2633
.unwrap ( ) ;
2634
let mut b = Repo:: clone( & url, & root.join ( " b" ) )
2635
.unwrap ( )
2636
.with_home ( root.join ( " home-b" ) ) ;
83e0677b Andy McCall 1 month ago ↶
2637
write ( & root.join ( " b" ) , " b.txt" , b " b\n " ) ;
2638
b.commit ( " unsigned before policy" , & [ ] ) .unwrap ( ) ;
2639
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 17 days ago
2640
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2641
2642
2643
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
2644
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
2645
let sp = a.policy_init ( ) .unwrap ( ) ;
2646
assert_eq! ( sp.policy.serial, 1 ) ;
2647
a.pull ( " origin" ) .unwrap ( ) ;
2648
let results = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 17 days ago
2649
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2650
assert! ( results.iter ( ) .all ( | r | r.ok ) , " {results:?}" ) ;
2651
assert_eq! ( origin.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 1 ) ;
2652
2653
2654
b.pull ( " origin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2655
assert! (
2656
b.current_policy ( ) .unwrap ( ) .is_some ( ) ,
2657
" the policy arrived with the fetch"
2658
) ;
83e0677b Andy McCall 1 month ago ↶
2659
assert! ( b.push ( " origin" , false ) .is_err ( ) ) ;
2660
75167672 Andy McCall 1 month ago
2661
let ( key_b, id_b) = b
2662
.create_identity ( " Bob" , " [email protected] " , IdentityScope:: User)
2663
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2664
write ( & root.join ( " b" ) , " b.txt" , b " b2\n " ) ;
2665
let h_b2 = b.commit ( " b2 as Bob" , & [ ] ) .unwrap ( ) ;
2666
assert_eq! ( b.store ( ) .get_change ( & h_b2) .unwrap ( ) .author, key_b) ;
2667
let r = b.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 17 days ago
2668
origin.refresh ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2669
assert! (
2670
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " may not update" ) ,
2671
" {:?}" ,
2672
r
2673
) ;
83e0677b Andy McCall 1 month ago ↶
2674
2675
2676
let sp = a.policy_grant ( id_b.clone ( ) , None ) .unwrap ( ) ;
2677
assert_eq! ( sp.policy.serial, 2 ) ;
2678
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 17 days ago
2679
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2680
assert! ( r.iter ( ) .any ( | x | x.name = = " policy" & & x.ok ) , " {r:?}" ) ;
2681
75167672 Andy McCall 1 month ago
2682
assert! ( b
2683
.push ( " origin" , false )
2684
.unwrap_or_else ( | e | vec! [ RefResult {
2685
name: " main" .into ( ) ,
2686
ok: false ,
2687
reason: e.to_string ( )
2688
} ] )
2689
.iter ( )
2690
.all ( | x | ! x.ok) ) ;
83e0677b Andy McCall 1 month ago ↶
2691
b.pull ( " origin" ) .unwrap ( ) ;
2692
assert_eq! ( b.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 2 ) ;
2693
let r = b.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 17 days ago
2694
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2695
assert! ( r[ 0 ] .ok, " {:?}" , r) ;
2696
2697
let heads = origin.bookmark_heads ( ) .unwrap ( ) ;
2698
for h in & heads {
2699
let c = origin.store ( ) .get_change ( h) .unwrap ( ) ;
2700
if c.author = = key_b {
75167672 Andy McCall 1 month ago
2701
origin
2702
.change_allowed (
2703
h,
2704
& origin.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy,
2705
" main" ,
2706
)
2707
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2708
}
2709
}
2710
2711
2712
let mut forged = b.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.clone ( ) ;
2713
forged.serial = 3 ;
2714
forged.roots = vec! [ key_b] ;
2715
let fh = forged.hash ( ) ;
2716
let ( _ , sk_b) = b.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2717
let forged = SignedPolicy {
2718
policy: forged,
2719
sigs: vec! [ sk_b.sign ( Domain:: Policy, & key_b, & fh) ] ,
2720
} ;
2721
let r = origin
2722
.receive_push ( Bundle:: default( ) , vec! [ ] , Some ( forged) , vec! [ ] )
2723
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2724
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " root" ) , " {:?}" , r) ;
2725
let stale = a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1 ;
2726
let mut old = stale.clone ( ) ;
2727
old.policy.serial = 1 ;
75167672 Andy McCall 1 month ago
2728
let r = origin
2729
.receive_push ( Bundle:: default( ) , vec! [ ] , Some ( old) , vec! [ ] )
2730
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2731
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " serial" ) , " {:?}" , r) ;
2732
2733
2734
a.pull ( " origin" ) .unwrap ( ) ;
2735
a.policy_revoke ( & key_b) .unwrap ( ) ;
2736
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 17 days ago
2737
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2738
assert! ( r.iter ( ) .any ( | x | x.name = = " policy" & & x.ok ) , " {r:?}" ) ;
2739
b.pull ( " origin" ) .unwrap ( ) ;
2740
write ( & root.join ( " b" ) , " b.txt" , b " b3\n " ) ;
2741
b.commit ( " b3" , & [ ] ) .unwrap ( ) ;
2742
let r = b.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 17 days ago
2743
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2744
assert! ( ! r[ 0 ] .ok, " {:?}" , r) ;
2745
2746
75167672 Andy McCall 1 month ago
2747
let c = Repo:: clone( & url, & root.join ( " c" ) )
2748
.unwrap ( )
2749
.with_home ( root.join ( " home-c" ) ) ;
83e0677b Andy McCall 1 month ago ↶
2750
assert_eq! ( c.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 3 ) ;
75167672 Andy McCall 1 month ago
2751
assert! ( c
2752
.current_policy ( )
2753
.unwrap ( )
2754
.unwrap ( )
2755
.1
2756
.policy
2757
.roots
2758
.contains ( & key_a) ) ;
83e0677b Andy McCall 1 month ago ↶
2759
assert! ( ! c.snapshot ( " status" ) .unwrap ( ) .changed) ;
2760
}
d02e545d Andy McCall 1 month ago ↶
2761
9ae4d15a Andy McCall 1 month ago ↶
2762
d02e545d Andy McCall 1 month ago ↶
2763
# [ test ]
9ae4d15a Andy McCall 1 month ago ↶
2764
fn gc_reports_its_phases ( ) {
2765
hermetic ( ) ;
2766
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2767
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
2768
for i in 0 .. 3 {
2769
write (
2770
dir.path ( ) ,
2771
& format! ( " f{i} .txt" ) ,
2772
format! ( " {i} \n " ) .as_bytes ( ) ,
2773
) ;
2774
repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ;
2775
}
2776
let mut phases = Vec :: new( ) ;
2777
repo.gc_with ( true , & mut | p| phases.push ( p.to_string ( ) ) )
2778
.unwrap ( ) ;
2779
assert_eq! ( phases[ 0 ] , " finding what is reachable" , " {phases:?}" ) ;
2780
assert! (
2781
phases.iter ( ) .any ( | p | p.starts_with ( " pruning" ) ) ,
2782
" {phases:?}"
2783
) ;
2784
assert! (
2785
phases.iter ( ) .any ( | p | p.starts_with ( " packing 0 of " ) ) ,
2786
" {phases:?}"
2787
) ;
2788
assert! (
2789
phases
2790
.iter ( )
2791
.any ( | p | p.starts_with ( " writing the index of " ) ) ,
2792
" {phases:?}"
2793
) ;
2794
}
2795
2796
# [ test ]
d02e545d Andy McCall 1 month ago ↶
2797
fn gc_packs_objects_and_history_still_works ( ) {
2798
hermetic ( ) ;
2799
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2800
let root = dir.path ( ) ;
2801
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
2802
repo.create_identity ( " Ada" , " a@b" , IdentityScope:: User)
2803
.unwrap ( ) ;
d02e545d Andy McCall 1 month ago ↶
2804
repo.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
2805
write ( root, " a.txt" , b " one\n " ) ;
2806
let h1 = repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
2807
write ( root, " a.txt" , b " two\n " ) ;
2808
write ( root, " big.bin" , & vec! [ 0 u8 ; 100_000 ] ) ;
2809
let h2 = repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
ba160f60 Andy McCall 1 month ago ↶
2810
2811
for i in 0 .. 5 {
2812
write ( root, " a.txt" , format! ( " two\n {i} \n " ) .as_bytes ( ) ) ;
2813
repo.snapshot ( " status" ) .unwrap ( ) ;
2814
}
2815
write ( root, " a.txt" , b " two\n " ) ;
2816
repo.snapshot ( " status" ) .unwrap ( ) ;
d02e545d Andy McCall 1 month ago ↶
2817
let before = repo.store ( ) .list ( ) .unwrap ( ) ;
2818
assert! ( repo.store ( ) .loose_count ( ) .unwrap ( ) > 0 ) ;
ba160f60 Andy McCall 1 month ago ↶
2819
2820
2821
let r = repo.gc ( true ) .unwrap ( ) ;
2822
assert_eq! ( r.packed.objects, before.len ( ) ) ;
2823
assert_eq! ( r.pruned.objects, 0 ) ;
d02e545d Andy McCall 1 month ago ↶
2824
assert_eq! ( repo.store ( ) .loose_count ( ) .unwrap ( ) , 0 ) ;
2825
assert_eq! ( repo.store ( ) .list ( ) .unwrap ( ) , before) ;
ba160f60 Andy McCall 1 month ago ↶
2826
2827
2828
2829
repo.config.set ( " core.op-retention" , " 0" ) .unwrap ( ) ;
2830
repo.save_config ( ) .unwrap ( ) ;
21b2aec2 Andy McCall 1 month ago ↶
2831
let mut repo = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
ba160f60 Andy McCall 1 month ago ↶
2832
let r = repo.gc ( true ) .unwrap ( ) ;
2833
assert! ( r.pruned.objects > = 5 , " {r:?}" ) ;
2834
let after = repo.store ( ) .list ( ) .unwrap ( ) ;
2835
assert! ( after.len ( ) < before.len ( ) ) ;
2836
assert! ( after.contains ( & h1) & & after.contains ( & h2) ) ;
2837
for ( _ , op) in repo.op_log ( ) .unwrap ( ) {
2838
assert! ( ! op.command.is_empty ( ) , " operation objects are all kept" ) ;
2839
}
2840
d02e545d Andy McCall 1 month ago ↶
2841
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
75167672 Andy McCall 1 month ago
2842
assert! ( matches! (
2843
repo.verify_change ( & h2) .unwrap ( ) ,
2844
SignatureStatus:: Valid { .. }
2845
) ) ;
d02e545d Andy McCall 1 month ago ↶
2846
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 3 ) ;
2847
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
2848
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n " ) ;
2849
repo.checkout ( & [ h2] , false , " checkout" ) .unwrap ( ) ;
2850
assert_eq! ( fs:: read( root.join ( " big.bin" ) ) .unwrap ( ) .len ( ) , 100_000 ) ;
2851
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
ba160f60 Andy McCall 1 month ago ↶
2852
d02e545d Andy McCall 1 month ago ↶
2853
write ( root, " a.txt" , b " three\n " ) ;
2854
repo.commit ( " three" , & [ ] ) .unwrap ( ) ;
ba160f60 Andy McCall 1 month ago ↶
2855
let r = repo.gc ( false ) .unwrap ( ) ;
2856
assert_eq! ( r.pruned.objects, 0 ) ;
2857
assert! ( r.pruned.deferred > 0 ) ;
2858
let r = repo.gc ( true ) .unwrap ( ) ;
2859
assert! ( r.pruned.objects > 0 ) ;
2860
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 ) ;
21b2aec2 Andy McCall 1 month ago ↶
2861
2862
2863
repo.config.set ( " core.op-retention" , " 90" ) .unwrap ( ) ;
2864
repo.save_config ( ) .unwrap ( ) ;
2865
let repo = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2866
let r = repo.gc ( false ) .unwrap ( ) ;
2867
assert_eq! ( r.pruned.objects, 0 ) ;
2868
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 ) ;
d02e545d Andy McCall 1 month ago ↶
2869
}
614541c3 Andy McCall 1 month ago ↶
2870
2871
# [ test ]
2872
fn describe_amend_abandon_heads ( ) {
2873
hermetic ( ) ;
2874
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2875
let root = dir.path ( ) ;
2876
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
2877
repo.create_identity ( " Ada" , " a@b" , IdentityScope:: User)
2878
.unwrap ( ) ;
614541c3 Andy McCall 1 month ago ↶
2879
repo.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
2880
write ( root, " a.txt" , b " one\n " ) ;
2881
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
2882
write ( root, " a.txt" , b " one\n two\n " ) ;
2883
let h2 = repo.commit ( " secnod" , & [ ] ) .unwrap ( ) ;
2884
let id2 = repo.store ( ) .get_change ( & h2) .unwrap ( ) .change_id;
2885
2886
2887
let h2b = repo.describe ( & h2, " second" ) .unwrap ( ) ;
2888
assert_ne! ( h2b, h2) ;
2889
let c = repo.store ( ) .get_change ( & h2b) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2890
assert_eq! (
2891
( c.change_id, c.message.as_str ( ) , c.deps.clone ( ) ) ,
2892
( id2, " second" , vec! [ h1] )
2893
) ;
614541c3 Andy McCall 1 month ago ↶
2894
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2b] ) ;
75167672 Andy McCall 1 month ago
2895
assert! ( matches! (
2896
repo.verify_change ( & h2b) .unwrap ( ) ,
2897
SignatureStatus:: Valid { .. }
2898
) ) ;
614541c3 Andy McCall 1 month ago ↶
2899
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
2900
assert! ( s.statuses.is_empty ( ) , " {:?}" , s.statuses) ;
75167672 Andy McCall 1 month ago
2901
assert_eq! (
2902
repo.resolve_change ( & id2.short ( 8 ) ) .unwrap ( ) ,
2903
h2b,
2904
" the old object is no longer reachable from the log"
2905
) ;
c07e0e7d Andy McCall 1 month ago ↶
2906
2907
let h1b = repo.describe ( & h1, " first, described" ) .unwrap ( ) ;
2908
assert_ne! ( h1b, h1) ;
2909
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
2910
assert_eq! ( heads.len ( ) , 1 ) ;
2911
let top = repo.store ( ) .get_change ( & heads[ 0 ] ) .unwrap ( ) ;
2912
assert_eq! ( ( top.change_id, top.deps.clone ( ) ) , ( id2, vec! [ h1b] ) ) ;
75167672 Andy McCall 1 month ago
2913
assert! (
2914
matches! (
2915
repo.verify_change ( & heads[ 0 ] ) .unwrap ( ) ,
2916
SignatureStatus:: Valid { .. }
2917
) ,
2918
" rewritten own change is re-signed"
2919
) ;
c07e0e7d Andy McCall 1 month ago ↶
2920
let ( _ , wc) = repo.working_copy ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2921
assert_eq! (
2922
wc.deps,
2923
vec! [ heads[ 0 ] ] ,
2924
" the working copy follows the rewrite"
2925
) ;
c07e0e7d Andy McCall 1 month ago ↶
2926
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
2927
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
2928
assert! (
2929
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n " )
2930
) ;
614541c3 Andy McCall 1 month ago ↶
2931
2932
2933
write ( root, " a.txt" , b " one\n two\n three\n " ) ;
2934
write ( root, " b.txt" , b " b\n " ) ;
2935
let h2c = repo.amend ( None ) .unwrap ( ) ;
2936
let c = repo.store ( ) .get_change ( & h2c) .unwrap ( ) ;
2937
assert_eq! ( c.change_id, id2) ;
2938
assert_eq! ( c.message, " second" ) ;
2939
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2c] ) ;
75167672 Andy McCall 1 month ago
2940
assert_eq! (
2941
repo.log ( ) .unwrap ( ) .len ( ) ,
2942
3 ,
2943
" working copy, amended head, first"
2944
) ;
614541c3 Andy McCall 1 month ago ↶
2945
let snap = repo.load_state ( & [ h2c] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
2946
assert! (
2947
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n three\n " )
2948
) ;
614541c3 Andy McCall 1 month ago ↶
2949
assert! ( snap.files.contains_key ( " b.txt" ) ) ;
2950
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
75167672 Andy McCall 1 month ago
2951
assert! (
2952
repo.amend ( None ) .is_err ( ) ,
2953
" clean working copy and no message"
2954
) ;
614541c3 Andy McCall 1 month ago ↶
2955
let h2d = repo.amend ( Some ( " second, amended" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2956
assert_eq! (
2957
repo.store ( ) .get_change ( & h2d) .unwrap ( ) .message,
2958
" second, amended"
2959
) ;
614541c3 Andy McCall 1 month ago ↶
2960
2961
2962
repo.abandon ( & h2d) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2963
assert_eq! (
2964
repo.bookmark_heads ( ) .unwrap ( ) ,
2965
vec! [ h1b] ,
2966
" the described first change is the head again"
2967
) ;
614541c3 Andy McCall 1 month ago ↶
2968
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2969
let paths: Vec< _ > = s
2970
.statuses
2971
.iter ( )
2972
.map ( | p | ( p.path.as_str ( ) , p.status) )
2973
.collect ( ) ;
2974
assert_eq! (
2975
paths,
2976
vec! [
2977
( " a.txt" , ChangeStatus:: Modified) ,
2978
( " b.txt" , ChangeStatus:: Added)
2979
]
2980
) ;
614541c3 Andy McCall 1 month ago ↶
2981
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n two\n three\n " ) ;
2982
let h3 = repo.commit ( " recommitted" , & [ ] ) .unwrap ( ) ;
2983
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h3] ) ;
2984
2985
repo.undo ( ) .unwrap ( ) ;
2986
repo.undo ( ) .unwrap ( ) ;
2987
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2d] ) ;
c07e0e7d Andy McCall 1 month ago ↶
2988
6706bdcf Andy McCall 1 month ago ↶
2989
2990
2991
c07e0e7d Andy McCall 1 month ago ↶
2992
let base = repo.bookmark_heads ( ) .unwrap ( ) [ 0 ] ;
2993
let base_change = repo.store ( ) .get_change ( & base) .unwrap ( ) ;
2994
let h1_now = base_change.deps[ 0 ] ;
6706bdcf Andy McCall 1 month ago ↶
2995
let notes = repo.abandon ( & h1_now) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2996
assert! (
2997
! notes.is_empty ( ) ,
2998
" the dependent's edit of a.txt was reified"
2999
) ;
6706bdcf Andy McCall 1 month ago ↶
3000
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
3001
let after = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
3002
assert! (
3003
! after.files.contains_key ( " a.txt" ) & & after.files.contains_key ( " b.txt" ) ,
3004
" {:?}" ,
3005
after.files.keys ( )
3006
) ;
6706bdcf Andy McCall 1 month ago ↶
3007
repo.undo ( ) .unwrap ( ) ;
3008
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ base] ) ;
c07e0e7d Andy McCall 1 month ago ↶
3009
write ( root, " c.txt" , b " c\n " ) ;
3010
let hc = repo.commit ( " c" , & [ ] ) .unwrap ( ) ;
3011
write ( root, " d.txt" , b " d\n " ) ;
3012
let hd = repo.commit ( " d" , & [ hc] ) .unwrap ( ) ;
3013
assert_eq! ( repo.store ( ) .get_change ( & hd) .unwrap ( ) .deps, vec! [ hc] ) ;
3014
repo.abandon ( & hc) .unwrap ( ) ;
3015
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
3016
assert! ( ! heads.contains ( & hc) ) ;
75167672 Andy McCall 1 month ago
3017
let d_now = heads
3018
.iter ( )
3019
.find ( | h | repo.store ( ) .get_change ( h) .unwrap ( ) .message = = " d" )
3020
.copied ( )
3021
.unwrap ( ) ;
3022
assert! (
3023
repo.store ( ) .get_change ( & d_now) .unwrap ( ) .deps.is_empty ( ) ,
3024
" d inherited c's (empty) dependencies"
3025
) ;
c07e0e7d Andy McCall 1 month ago ↶
3026
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3027
assert_eq! (
3028
s.statuses
3029
.iter ( )
3030
.map ( | p | p.path.as_str ( ) )
3031
.collect:: < Vec< _ > > ( ) ,
3032
vec! [ " c.txt" ] ,
3033
" c's content is now uncommitted work"
3034
) ;
614541c3 Andy McCall 1 month ago ↶
3035
}
79ab8528 Andy McCall 1 month ago ↶
3036
3037
# [ test ]
3038
fn git_export_mirrors_history ( ) {
3039
hermetic ( ) ;
75167672 Andy McCall 1 month ago
3040
if std:: process:: Command:: new( " git" )
3041
.arg ( " --version" )
3042
.output ( )
3043
.is_err ( )
3044
{
79ab8528 Andy McCall 1 month ago ↶
3045
eprintln! ( " git not installed; skipping" ) ;
3046
return ;
3047
}
3048
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3049
let root = dir.path ( ) ;
75167672 Andy McCall 1 month ago
3050
let mut repo = Repo:: init( & root.join ( " r" ) )
3051
.unwrap ( )
3052
.with_home ( root.join ( " home" ) ) ;
3053
repo.create_identity ( " Ada Lovelace" , " [email protected] " , IdentityScope:: User)
3054
.unwrap ( ) ;
79ab8528 Andy McCall 1 month ago ↶
3055
let r = root.join ( " r" ) ;
3056
write ( & r, " README.md" , b " hello\n " ) ;
3057
write ( & r, " src/main.rs" , b " fn main() {}\n " ) ;
3058
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
3059
write ( & r, " src/main.rs" , b " fn main() { run(); }\n " ) ;
3060
write ( & r, " logo.png" , b " \x89 PNG\0 \0 " ) ;
3061
let h2 = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
3062
3063
let git_dir = root.join ( " mirror.git" ) ;
3064
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
3065
assert_eq! ( ( rep.exported, rep.skipped, rep.heads) , ( 2 , 0 , 1 ) ) ;
3066
let git = | args : & [& str ]| -> String {
75167672 Andy McCall 1 month ago
3067
let out = std:: process:: Command:: new( " git" )
3068
.arg ( " -C" )
3069
.arg ( & git_dir)
3070
.args ( args)
3071
.output ( )
3072
.unwrap ( ) ;
3073
assert! (
3074
out.status.success ( ) ,
3075
" {}" ,
3076
String :: from_utf8_lossy( & out.stderr)
3077
) ;
79ab8528 Andy McCall 1 month ago ↶
3078
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
3079
} ;
3080
assert_eq! ( git ( & [ " log" , " --format=%s" , " main" ] ) , " second\n first" ) ;
3081
assert_eq! ( git ( & [ " show" , " main:src/main.rs" ] ) , " fn main() { run(); }" ) ;
3082
assert_eq! ( git ( & [ " show" , " main:README.md" ] ) , " hello" ) ;
3083
let body = git ( & [ " log" , " -1" , " --format=%B" , " main" ] ) ;
75167672 Andy McCall 1 month ago
3084
assert! (
3085
body.contains ( & format! ( " Oxy-Hash: {} " , h2.to_hex ( ) ) ) ,
3086
" {body}"
3087
) ;
79ab8528 Andy McCall 1 month ago ↶
3088
assert! ( body.contains ( " Change-Id: " ) , " {body}" ) ;
75167672 Andy McCall 1 month ago
3089
assert_eq! (
3090
git ( & [ " log" , " -1" , " --format=%an <%ae>" , " main" ] ) ,
3091
" Ada Lovelace <[email protected] >"
3092
) ;
3093
assert_eq! (
3094
git ( & [ " ls-tree" , " --name-only" , " main" ] ) ,
3095
" README.md\n logo.png\n src"
3096
) ;
79ab8528 Andy McCall 1 month ago ↶
3097
3098
3099
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
3100
assert_eq! ( ( rep.exported, rep.skipped) , ( 0 , 2 ) ) ;
3101
3102
3103
3104
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
3105
write ( & r, " other.txt" , b " o\n " ) ;
3106
let h3 = repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
3107
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
3108
assert_eq! ( ( rep.exported, rep.heads) , ( 1 , 2 ) ) ;
3109
assert_eq! ( git ( & [ " log" , " --format=%s" , " main" ] ) , " third\n second\n first" ) ;
3110
let parents = git ( & [ " rev-list" , " --parents" , " -1" , " main" ] ) ;
3111
assert_eq! ( parents.split ( ' ' ) .count ( ) , 2 , " one parent: {parents}" ) ;
3112
assert_eq! ( git ( & [ " show" , " main:other.txt" ] ) , " o" ) ;
3113
assert_eq! ( git ( & [ " show" , " main:src/main.rs" ] ) , " fn main() { run(); }" ) ;
75167672 Andy McCall 1 month ago
3114
assert_eq! (
3115
git ( & [ " ls-tree" , " --name-only" , " main" ] ) ,
3116
" README.md\n logo.png\n other.txt\n src"
3117
) ;
79ab8528 Andy McCall 1 month ago ↶
3118
let body = git ( & [ " log" , " -1" , " --format=%B" , " main" ] ) ;
75167672 Andy McCall 1 month ago
3119
assert! (
3120
body.contains ( & format! ( " Oxy-Hash: {} " , h3.to_hex ( ) ) ) & & body.contains ( " Oxy-State: " ) ,
3121
" {body}"
3122
) ;
79ab8528 Andy McCall 1 month ago ↶
3123
3124
repo.describe ( & h3, " third, described" ) .unwrap ( ) ;
3125
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
3126
assert_eq! ( rep.exported, 1 ) ;
75167672 Andy McCall 1 month ago
3127
assert_eq! (
3128
git ( & [ " log" , " -1" , " --format=%s" , " main" ] ) ,
3129
" third, described"
3130
) ;
79ab8528 Andy McCall 1 month ago ↶
3131
assert_eq! ( git ( & [ " log" , " --format=%s" , " main" ] ) .lines ( ) .count ( ) , 4 ) ;
3132
let _ = h2;
3133
}
c3069537 Andy McCall 1 month ago ↶
3134
3135
# [ test ]
3136
fn git_import_reproduces_trees ( ) {
c3d2ef61 Andy McCall 1 month ago ↶
3137
git_import_reproduces_trees_with ( None ) ;
3138
}
3139
3140
3141
3142
# [ test ]
3143
fn git_import_reproduces_trees_in_batches ( ) {
3144
git_import_reproduces_trees_with ( Some ( 1 ) ) ;
3145
}
3146
3147
fn git_import_reproduces_trees_with ( batch : Option< usize > ) {
3148
crate :: repo:: git:: set_merge_batch_for_tests( batch) ;
c3069537 Andy McCall 1 month ago ↶
3149
hermetic ( ) ;
75167672 Andy McCall 1 month ago
3150
if std:: process:: Command:: new( " git" )
3151
.arg ( " --version" )
3152
.output ( )
3153
.is_err ( )
3154
{
c3069537 Andy McCall 1 month ago ↶
3155
eprintln! ( " git not installed; skipping" ) ;
3156
return ;
3157
}
3158
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3159
let root = dir.path ( ) ;
3160
let g = root.join ( " src.git" ) ;
3161
fs:: create_dir_all( & g) .unwrap ( ) ;
3162
let git = | args : & [& str ]| -> String {
3163
let out = std:: process:: Command:: new( " git" )
3164
.arg ( " -C" )
3165
.arg ( & g)
75167672 Andy McCall 1 month ago
3166
.args ( [
3167
" -c" ,
3168
" user.name=Grace" ,
3169
" -c" ,
3170
" [email protected] " ,
3171
" -c" ,
3172
" commit.gpgsign=false" ,
3173
] )
c3069537 Andy McCall 1 month ago ↶
3174
.args ( args)
3175
.output ( )
3176
.unwrap ( ) ;
75167672 Andy McCall 1 month ago
3177
assert! (
3178
out.status.success ( ) ,
3179
" git {args:?}: {}" ,
3180
String :: from_utf8_lossy( & out.stderr)
3181
) ;
c3069537 Andy McCall 1 month ago ↶
3182
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
3183
} ;
3184
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
3185
write ( & g, " README.md" , b " # Hi\n " ) ;
3186
write ( & g, " src/lib.rs" , b " pub fn a() {}\n " ) ;
3187
git ( & [ " add" , " ." ] ) ;
3188
git ( & [ " commit" , " -q" , " -m" , " initial" ] ) ;
3189
write ( & g, " src/lib.rs" , b " pub fn a() {}\n pub fn b() {}\n " ) ;
3190
write ( & g, " run.sh" , b " #!/bin/sh\n " ) ;
3191
# [ cfg ( unix ) ]
3192
{
3193
use std:: os:: unix:: fs:: PermissionsExt;
3194
fs:: set_permissions( g.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
3195
}
3196
write ( & g, " logo.png" , b " \x89 PNG\0 \0 \x01 " ) ;
3197
git ( & [ " add" , " ." ] ) ;
75167672 Andy McCall 1 month ago
3198
git ( & [
3199
" commit" ,
3200
" -q" ,
3201
" -m" ,
3202
" second\n \n Change-Id: 0123456789abcdef0123456789abcdef" ,
3203
] ) ;
c3069537 Andy McCall 1 month ago ↶
3204
3205
git ( & [ " checkout" , " -q" , " -b" , " topic" , " HEAD~1" ] ) ;
3206
write ( & g, " README.md" , b " # Hi\n \n More.\n " ) ;
3207
git ( & [ " commit" , " -q" , " -am" , " readme" ] ) ;
3208
git ( & [ " checkout" , " -q" , " main" ] ) ;
3209
git ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge topic" , " topic" ] ) ;
3210
3211
3212
let initial = git ( & [ " rev-list" , " --max-parents=0" , " HEAD" ] ) ;
3213
git ( & [ " checkout" , " -q" , " -b" , " clash" , & initial] ) ;
3214
write ( & g, " src/lib.rs" , b " pub fn a() {}\n pub fn c() {}\n " ) ;
3215
git ( & [ " commit" , " -q" , " -am" , " clash" ] ) ;
3216
git ( & [ " checkout" , " -q" , " main" ] ) ;
7df9007b Andy McCall 1 month ago ↶
3217
3218
3219
75167672 Andy McCall 1 month ago
3220
let _ = std:: process:: Command:: new( " git" )
3221
.arg ( " -C" )
3222
.arg ( & g)
7df9007b Andy McCall 1 month ago ↶
3223
.args ( [
3224
" -c" ,
3225
" user.name=Grace" ,
3226
" -c" ,
3227
" [email protected] " ,
3228
" merge" ,
3229
" -q" ,
3230
" clash" ,
3231
] )
75167672 Andy McCall 1 month ago
3232
.output ( ) ;
7df9007b Andy McCall 1 month ago ↶
3233
assert! (
3234
g.join ( " .git/MERGE_HEAD" ) .exists ( ) ,
3235
" the clash merge stopped at its conflict"
3236
) ;
75167672 Andy McCall 1 month ago
3237
write (
3238
& g,
3239
" src/lib.rs" ,
3240
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n " ,
3241
) ;
c3069537 Andy McCall 1 month ago ↶
3242
git ( & [ " add" , " ." ] ) ;
3243
git ( & [ " commit" , " -q" , " -m" , " resolve clash" ] ) ;
3244
git ( & [ " rm" , " -q" , " logo.png" ] ) ;
3245
git ( & [ " commit" , " -q" , " -m" , " drop logo" ] ) ;
6e723b81 Andy McCall 1 month ago ↶
3246
3247
git ( & [ " mv" , " src/lib.rs" , " src/lib2.rs" ] ) ;
75167672 Andy McCall 1 month ago
3248
write (
3249
& g,
3250
" src/lib2.rs" ,
3251
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n pub fn d() {}\n " ,
3252
) ;
6e723b81 Andy McCall 1 month ago ↶
3253
git ( & [ " add" , " ." ] ) ;
3254
git ( & [ " commit" , " -q" , " -m" , " rename lib" ] ) ;
958fb27d Andy McCall 1 month ago ↶
3255
3256
3257
3258
git ( & [ " checkout" , " -q" , " -b" , " discarded" ] ) ;
3259
write ( & g, " run.sh" , b " #!/bin/sh\n echo discarded\n " ) ;
3260
git ( & [ " commit" , " -q" , " -am" , " discarded edit" ] ) ;
3261
git ( & [ " checkout" , " -q" , " main" ] ) ;
314db9b4 Andy McCall 1 month ago ↶
3262
git ( & [
3263
" merge" ,
3264
" -q" ,
3265
" -s" ,
3266
" ours" ,
3267
" -m" ,
3268
" merge discarded, keeping ours" ,
3269
" discarded" ,
3270
] ) ;
c3069537 Andy McCall 1 month ago ↶
3271
let expected_count: usize = git ( & [ " rev-list" , " --count" , " main" ] ) .parse ( ) .unwrap ( ) ;
3272
3273
let dest = root.join ( " oxy" ) ;
3274
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
3275
let rep = repo.import_git ( & g, " main" ) .unwrap ( ) ;
3276
assert_eq! ( rep.commits, expected_count) ;
958fb27d Andy McCall 1 month ago ↶
3277
assert_eq! ( rep.merges, 3 ) ;
314db9b4 Andy McCall 1 month ago ↶
3278
assert_eq! (
3279
fs:: read( dest.join ( " run.sh" ) ) .unwrap ( ) ,
3280
b " #!/bin/sh\n " ,
3281
" the ours merge kept main's run.sh"
3282
) ;
958fb27d Andy McCall 1 month ago ↶
3283
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
3284
assert_eq! ( v.commits, expected_count, " every commit's tree reproduced" ) ;
3285
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
c3069537 Andy McCall 1 month ago ↶
3286
75167672 Andy McCall 1 month ago
3287
assert_eq! (
3288
fs:: read( dest.join ( " src/lib2.rs" ) ) .unwrap ( ) ,
3289
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n pub fn d() {}\n "
3290
) ;
6e723b81 Andy McCall 1 month ago ↶
3291
assert! ( ! dest.join ( " src/lib.rs" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
3292
assert_eq! (
3293
fs:: read( dest.join ( " README.md" ) ) .unwrap ( ) ,
3294
b " # Hi\n \n More.\n "
3295
) ;
c3069537 Andy McCall 1 month ago ↶
3296
assert! ( ! dest.join ( " logo.png" ) .exists ( ) ) ;
3297
# [ cfg ( unix ) ]
3298
{
3299
use std:: os:: unix:: fs:: PermissionsExt;
75167672 Andy McCall 1 month ago
3300
assert! (
3301
fs:: metadata( dest.join ( " run.sh" ) )
3302
.unwrap ( )
3303
.permissions ( )
3304
.mode ( )
3305
& 0o111
3306
! = 0
3307
) ;
c3069537 Andy McCall 1 month ago ↶
3308
}
75167672 Andy McCall 1 month ago
3309
assert! (
3310
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
3311
" clean after import"
3312
) ;
c3069537 Andy McCall 1 month ago ↶
3313
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
3314
let st = repo.load_state ( & heads) .unwrap ( ) ;
3315
assert! ( ! st.materialize ( ) .is_conflicted ( ) ) ;
3316
assert_eq! ( st.len ( ) , expected_count) ;
75167672 Andy McCall 1 month ago
3317
let tip = repo
3318
.log ( )
3319
.unwrap ( )
3320
.into_iter ( )
3321
.find ( | e | e.change.message.starts_with ( " rename lib" ) )
3322
.unwrap ( ) ;
6e723b81 Andy McCall 1 month ago ↶
3323
let c = repo.store ( ) .get_change ( & tip.hash) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3324
assert! (
3325
c.tree_ops
3326
.iter ( )
3327
.any ( | op | matches! ( op, TreeOp:: Move { name, .. } if name = = " lib2.rs" ) ) ,
3328
" {:?}" ,
3329
c.tree_ops
3330
) ;
3331
assert! ( ! c
3332
.tree_ops
3333
.iter ( )
3334
.any ( | op | matches! ( op, TreeOp:: AddNode { .. } ) ) ) ;
c3069537 Andy McCall 1 month ago ↶
3335
3336
let log = repo.log ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3337
let second = log
3338
.iter ( )
3339
.find ( | e | e.change.message.starts_with ( " second" ) )
3340
.unwrap ( ) ;
3341
assert_eq! (
3342
second.change.change_id,
3343
ChangeId:: from_hex( " 0123456789abcdef0123456789abcdef" ) .unwrap ( )
3344
) ;
3345
assert! ( second
3346
.change
3347
.message
3348
.contains ( " Git-Author: Grace <[email protected] >" ) ) ;
3349
let resolve = log
3350
.iter ( )
3351
.find ( | e | e.change.message.starts_with ( " resolve clash" ) )
3352
.unwrap ( ) ;
3353
assert_eq! (
3354
resolve.change.kind,
3355
ChangeKind:: Resolution,
3356
" a conflicting Git merge becomes a resolution"
3357
) ;
c3069537 Andy McCall 1 month ago ↶
3358
75167672 Andy McCall 1 month ago
3359
let repo2 = Repo:: init( & root.join ( " oxy2" ) )
3360
.unwrap ( )
3361
.with_home ( root.join ( " home" ) ) ;
c3069537 Andy McCall 1 month ago ↶
3362
repo2.import_git ( & g, " main" ) .unwrap ( ) ;
dfb23a3a Andy McCall 1 month ago ↶
3363
3364
3365
assert_eq! (
3366
repo2.bookmark_heads ( ) .unwrap ( ) ,
3367
repo.bookmark_heads ( ) .unwrap ( )
3368
) ;
75167672 Andy McCall 1 month ago
3369
let ids: BTreeSet< ChangeId> = repo
3370
.log ( )
3371
.unwrap ( )
3372
.iter ( )
3373
.filter ( | e | ! e.is_working_copy)
3374
.map ( | e | e.change.change_id )
3375
.collect ( ) ;
3376
let ids2: BTreeSet< ChangeId> = repo2
3377
.log ( )
3378
.unwrap ( )
3379
.iter ( )
3380
.filter ( | e | ! e.is_working_copy)
3381
.map ( | e | e.change.change_id )
3382
.collect ( ) ;
c3069537 Andy McCall 1 month ago ↶
3383
assert_eq! ( ids, ids2) ;
3384
3385
let mirror = root.join ( " mirror.git" ) ;
3386
repo.git_export ( & mirror) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3387
let out = std:: process:: Command:: new( " git" )
3388
.arg ( " -C" )
3389
.arg ( & mirror)
3390
.args ( [ " show" , " main:src/lib2.rs" ] )
3391
.output ( )
3392
.unwrap ( ) ;
3393
assert_eq! (
3394
String :: from_utf8_lossy( & out.stdout) ,
3395
" pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n pub fn d() {}\n "
3396
) ;
3397
let out = std:: process:: Command:: new( " git" )
3398
.arg ( " -C" )
3399
.arg ( & mirror)
3400
.args ( [ " show" , " main:src/lib.rs" ] )
3401
.output ( )
3402
.unwrap ( ) ;
3403
assert! (
3404
! out.status.success ( ) ,
3405
" the old path is gone from the export"
3406
) ;
c3069537 Andy McCall 1 month ago ↶
3407
}
252e8cb2 Andy McCall 1 month ago ↶
3408
3409
# [ test ]
3410
fn checkpoints_accelerate_loading_and_stay_correct ( ) {
3411
hermetic ( ) ;
3412
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3413
let root = dir.path ( ) ;
3414
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
3415
repo.config.set ( " core.checkpoint-interval" , " 5" ) .unwrap ( ) ;
3416
repo.save_config ( ) .unwrap ( ) ;
3417
let repo = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
3418
let mut hashes = Vec :: new( ) ;
3419
for i in 0 .. 12 {
75167672 Andy McCall 1 month ago
3420
write (
3421
root,
3422
" a.txt" ,
3423
format! (
3424
" {} \n " ,
3425
( 0 .. = i)
3426
.map ( | k | k.to_string ( ) )
3427
.collect:: < Vec< _ > > ( )
3428
.join ( " \n " )
3429
)
3430
.as_bytes ( ) ,
3431
) ;
252e8cb2 Andy McCall 1 month ago ↶
3432
write ( root, & format! ( " f{i} .txt" ) , b " x\n " ) ;
3433
hashes.push ( repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ) ;
3434
}
75167672 Andy McCall 1 month ago
3435
let ( _ , cp) = repo
3436
.latest_checkpoint ( " main" )
3437
.unwrap ( )
3438
.expect ( " automatic checkpoint" ) ;
252e8cb2 Andy McCall 1 month ago ↶
3439
assert! ( cp.count > = 5 & & cp.count < = 12 , " {}" , cp.count) ;
3440
3441
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
3442
let fast = repo.load_state ( & heads) .unwrap ( ) ;
3443
let full = repo.load_state_full ( & heads) .unwrap ( ) ;
3444
assert_eq! ( fast.len ( ) , full.len ( ) ) ;
3445
assert_eq! ( fast.heads ( ) , full.heads ( ) ) ;
3446
let ( a, b) = ( fast.materialize ( ) , full.materialize ( ) ) ;
75167672 Andy McCall 1 month ago
3447
assert_eq! (
3448
a.files.keys ( ) .collect:: < Vec< _ > > ( ) ,
3449
b.files.keys ( ) .collect:: < Vec< _ > > ( )
3450
) ;
252e8cb2 Andy McCall 1 month ago ↶
3451
for ( p, f) in & a.files {
3452
assert_eq! ( f.content, b.files[ p] .content, " {p}" ) ;
3453
}
3454
assert! ( repo.load_state_checkpointed ( & heads) .unwrap ( ) .is_some ( ) ) ;
3455
75167672 Andy McCall 1 month ago
3456
assert! ( repo
3457
.load_state_checkpointed ( & [ hashes[ 0 ] ] )
3458
.unwrap ( )
3459
.is_none ( ) ) ;
252e8cb2 Andy McCall 1 month ago ↶
3460
assert_eq! ( repo.load_state ( & [ hashes[ 0 ] ] ) .unwrap ( ) .len ( ) , 1 ) ;
3461
3462
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
3463
let h = repo.describe ( & hashes[ 3 ] , " c3 described" ) .unwrap ( ) ;
3464
assert_eq! ( repo.store ( ) .get_change ( & h) .unwrap ( ) .message, " c3 described" ) ;
3465
let ( r, n, bytes) = repo.checkpoint ( ) .unwrap ( ) ;
3466
assert! ( n > = 12 & & bytes > 0 ) ;
3467
assert_eq! ( repo.latest_checkpoint ( " main" ) .unwrap ( ) .unwrap ( ) .0 , r) ;
3468
write ( root, " a.txt" , b " after\n " ) ;
3469
let h_after = repo.commit ( " after" , & [ ] ) .unwrap ( ) ;
3470
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
3471
assert! ( st.contains ( & h_after) ) ;
75167672 Andy McCall 1 month ago
3472
assert! (
3473
matches! ( & st.materialize ( ) .files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " after\n " )
3474
) ;
252e8cb2 Andy McCall 1 month ago ↶
3475
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 14 ) ;
3476
3477
repo.gc ( true ) .unwrap ( ) ;
3478
assert! ( repo.latest_checkpoint ( " main" ) .unwrap ( ) .is_some ( ) ) ;
75167672 Andy McCall 1 month ago
3479
assert! ( repo
3480
.load_state_checkpointed ( & repo.bookmark_heads ( ) .unwrap ( ) )
3481
.unwrap ( )
3482
.is_some ( ) ) ;
252e8cb2 Andy McCall 1 month ago ↶
3483
}
135212de Andy McCall 1 month ago ↶
3484
3485
# [ test ]
3486
fn quick_status_for_prompts ( ) {
3487
hermetic ( ) ;
3488
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3489
let root = dir.path ( ) ;
3490
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
3491
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
3492
let a = Repo:: clone( & url, & root.join ( " a" ) )
3493
.unwrap ( )
3494
.with_home ( root.join ( " home" ) ) ;
135212de Andy McCall 1 month ago ↶
3495
let ops_before = a.op_log ( ) .unwrap ( ) .len ( ) ;
3496
let info = a.quick_status ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3497
assert_eq! (
3498
(
3499
info.bookmark.as_str ( ) ,
3500
info.heads,
3501
info.dirty,
3502
info.conflicted,
3503
info.ahead,
3504
info.behind
3505
) ,
3506
( " main" , 0 , false , false , Some ( 0 ) , Some ( 0 ) )
3507
) ;
135212de Andy McCall 1 month ago ↶
3508
write ( & root.join ( " a" ) , " f.txt" , b " f\n " ) ;
3509
assert! ( a.quick_status ( ) .unwrap ( ) .dirty, " a new file is dirty" ) ;
75167672 Andy McCall 1 month ago
3510
assert_eq! (
3511
a.op_log ( ) .unwrap ( ) .len ( ) ,
3512
ops_before,
3513
" quick_status records nothing"
3514
) ;
135212de Andy McCall 1 month ago ↶
3515
3516
a.snapshot ( " status" ) .unwrap ( ) ;
3517
assert! ( a.quick_status ( ) .unwrap ( ) .dirty) ;
3518
a.commit ( " f" , & [ ] ) .unwrap ( ) ;
3519
let info = a.quick_status ( ) .unwrap ( ) ;
3520
assert! ( ! info.dirty, " clean right after a commit" ) ;
3521
assert_eq! ( ( info.ahead, info.behind) , ( Some ( 1 ) , Some ( 0 ) ) ) ;
75167672 Andy McCall 1 month ago
3522
assert_eq! (
3523
info.format ( " %b %c%d%x%A%D" ) ,
3524
format! ( " main {} +1" , info.change_id)
3525
) ;
135212de Andy McCall 1 month ago ↶
3526
assert_eq! ( info.format ( " [%h heads] %a/%B %%" ) , " [1 heads] 1/0 %" ) ;
3527
3528
3529
let p = root.join ( " a/f.txt" ) ;
3530
let content = fs:: read( & p) .unwrap ( ) ;
3531
fs:: write( & p, & content) .unwrap ( ) ;
3532
assert! ( ! a.quick_status ( ) .unwrap ( ) .dirty) ;
3533
fs:: write( & p, b " g\n " ) .unwrap ( ) ;
3534
assert! ( a.quick_status ( ) .unwrap ( ) .dirty) ;
3535
fs:: write( & p, b " f\n " ) .unwrap ( ) ;
3536
3537
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
3538
assert_eq! ( a.quick_status ( ) .unwrap ( ) .ahead, Some ( 0 ) ) ;
75167672 Andy McCall 1 month ago
3539
let b = Repo:: clone( & url, & root.join ( " b" ) )
3540
.unwrap ( )
3541
.with_home ( root.join ( " home-b" ) ) ;
135212de Andy McCall 1 month ago ↶
3542
write ( & root.join ( " b" ) , " g.txt" , b " g\n " ) ;
3543
b.commit ( " g" , & [ ] ) .unwrap ( ) ;
3544
b.push ( " origin" , false ) .unwrap ( ) ;
3545
a.fetch ( " origin" ) .unwrap ( ) ;
3546
let info = a.quick_status ( ) .unwrap ( ) ;
3547
assert_eq! ( ( info.ahead, info.behind) , ( Some ( 0 ) , Some ( 1 ) ) ) ;
3548
assert_eq! ( info.format ( " %A%D" ) , " -1" ) ;
3549
let _ = origin;
3550
}
b3c5a59a Andy McCall 1 month ago ↶
3551
3552
# [ test ]
3553
fn lazy_blobs_are_fetched_on_first_read ( ) {
3554
hermetic ( ) ;
3555
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3556
let root = dir.path ( ) ;
3557
let origin_dir = root.join ( " origin" ) ;
75167672 Andy McCall 1 month ago
3558
let origin = Repo:: init( & origin_dir)
3559
.unwrap ( )
3560
.with_home ( root.join ( " home" ) ) ;
b3c5a59a Andy McCall 1 month ago ↶
3561
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
3562
let big: Vec< u8 > = ( 0 .. 200_000 u32 ) .map ( | i | ( i % 251 ) as u8 ) .collect ( ) ;
3563
write ( & origin_dir, " big.bin" , & big) ;
3564
write ( & origin_dir, " small.bin" , & [ 0 u8 , 1 , 2 ] ) ;
3565
write ( & origin_dir, " t.txt" , b " t\n " ) ;
3566
let h = origin.commit ( " blobs" , & [ ] ) .unwrap ( ) ;
3567
let big_ref = Repo:: blob_ref( & big) ;
3568
3569
75167672 Andy McCall 1 month ago
3570
let a = Repo:: init_with_id( & root.join ( " a" ) , origin.id ( ) .unwrap ( ) )
3571
.unwrap ( )
3572
.with_home ( root.join ( " home" ) ) ;
b3c5a59a Andy McCall 1 month ago ↶
3573
let a = {
3574
let mut a = a;
3575
a.config.set ( " remote.origin.url" , & url) .unwrap ( ) ;
3576
a.config.set ( " filter.max-blob" , " 100K" ) .unwrap ( ) ;
3577
a.save_config ( ) .unwrap ( ) ;
3578
a
3579
} ;
3580
assert_eq! ( a.config.max_blob, Some ( 100 * 1024 ) ) ;
3581
a.fetch ( " origin" ) .unwrap ( ) ;
3582
assert! ( a.store ( ) .has ( & h) ) ;
3583
assert! ( ! a.store ( ) .has ( & big_ref) , " withheld" ) ;
75167672 Andy McCall 1 month ago
3584
assert! (
3585
a.store ( ) .has ( & Repo:: blob_ref( & [ 0 u8 , 1 , 2 ] ) ) ,
3586
" small blob came along"
3587
) ;
b3c5a59a Andy McCall 1 month ago ↶
3588
assert_eq! ( a.lazy_remote ( & big_ref) .as_deref ( ) , Some ( " origin" ) ) ;
3589
3590
3591
let ( op_ref, op) = a.head_op ( ) .unwrap ( ) ;
3592
let mut view = op.view.clone ( ) ;
75167672 Andy McCall 1 month ago
3593
view.bookmarks.insert (
3594
" main" .into ( ) ,
3595
a.store ( ) .put_state ( & StateObject:: new( vec! [ h] ) ) .unwrap ( ) ,
3596
) ;
b3c5a59a Andy McCall 1 month ago ↶
3597
a.record_op ( & [ op_ref] , " adopt" , view) .unwrap ( ) ;
3598
3599
a.checkout ( & [ h] , true , " checkout" ) .unwrap ( ) ;
3600
assert! ( a.store ( ) .has ( & big_ref) , " fetched on first read" ) ;
3601
assert_eq! ( fs:: read( root.join ( " a/big.bin" ) ) .unwrap ( ) , big) ;
3602
assert! ( a.lazy_remote ( & big_ref) .is_none ( ) , " no longer registered" ) ;
3603
write ( & root.join ( " a" ) , " t.txt" , b " t2\n " ) ;
3604
a.commit ( " text edit" , & [ ] ) .unwrap ( ) ;
3605
3606
7421309f Andy McCall 1 month ago ↶
3607
let b = Repo:: clone_with( & url, & root.join ( " b" ) , Some ( 1024 ) , & [ ] ) .unwrap ( ) ;
b3c5a59a Andy McCall 1 month ago ↶
3608
assert_eq! ( b.config.max_blob, Some ( 1024 ) ) ;
3609
assert_eq! ( fs:: read( root.join ( " b/big.bin" ) ) .unwrap ( ) , big) ;
3610
assert_eq! ( parse_size ( " 2M" ) .unwrap ( ) , 2 < < 20 ) ;
3611
assert! ( parse_size ( " lots" ) .is_err ( ) ) ;
3612
}
7421309f Andy McCall 1 month ago ↶
3613
3614
# [ test ]
3615
fn sparse_checkout_materializes_only_chosen_paths ( ) {
3616
hermetic ( ) ;
3617
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3618
let root = dir.path ( ) ;
3619
let origin_dir = root.join ( " origin" ) ;
75167672 Andy McCall 1 month ago
3620
let origin = Repo:: init( & origin_dir)
3621
.unwrap ( )
3622
.with_home ( root.join ( " home" ) ) ;
7421309f Andy McCall 1 month ago ↶
3623
write ( & origin_dir, " src/main.rs" , b " fn main() {}\n " ) ;
3624
write ( & origin_dir, " src/lib.rs" , b " pub fn a() {}\n " ) ;
3625
write ( & origin_dir, " docs/guide.md" , b " # guide\n " ) ;
3626
let logo: Vec< u8 > = ( 0 .. 50_000 u32 ) .map ( | i | ( i % 7 ) as u8 ) .collect ( ) ;
3627
write ( & origin_dir, " docs/logo.png" , & logo) ;
3628
write ( & origin_dir, " README.md" , b " # top\n " ) ;
3629
let h1 = origin.commit ( " tree" , & [ ] ) .unwrap ( ) ;
3630
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
3631
75167672 Andy McCall 1 month ago
3632
let a = Repo:: clone_with( & url, & root.join ( " a" ) , None , & [ " src" .into ( ) ] )
3633
.unwrap ( )
3634
.with_home ( root.join ( " home" ) ) ;
7421309f Andy McCall 1 month ago ↶
3635
assert_eq! ( a.config.sparse_paths, vec! [ " src" ] ) ;
3636
assert_eq! ( a.config.max_blob, Some ( 0 ) , " sparse implies lazy blobs" ) ;
3637
assert! ( root.join ( " a/src/main.rs" ) .exists ( ) ) ;
3638
assert! ( ! root.join ( " a/docs" ) .exists ( ) & & ! root.join ( " a/README.md" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
3639
assert! (
3640
! a.store ( ) .has ( & Repo:: blob_ref( & logo) ) ,
3641
" out-of-scope blob never fetched"
3642
) ;
3643
assert! (
3644
! a.snapshot ( " status" ) .unwrap ( ) .changed,
3645
" out-of-scope files are not removals"
3646
) ;
05e71ed6 Andy McCall 1 month ago ↶
3647
assert! ( ! a.quick_status ( ) .unwrap ( ) .dirty) ;
7421309f Andy McCall 1 month ago ↶
3648
3649
75167672 Andy McCall 1 month ago
3650
write (
3651
& root.join ( " a" ) ,
3652
" src/lib.rs" ,
3653
b " pub fn a() {}\n pub fn b() {}\n " ,
3654
) ;
7421309f Andy McCall 1 month ago ↶
3655
let h2 = a.commit ( " edit lib" , & [ ] ) .unwrap ( ) ;
3656
let st = a.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
3657
assert! (
3658
st.files.contains_key ( " docs/guide.md" ) ,
3659
" the state still has everything"
3660
) ;
7421309f Andy McCall 1 month ago ↶
3661
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 17 days ago
3662
origin.refresh ( ) .unwrap ( ) ;
7421309f Andy McCall 1 month ago ↶
3663
3664
write ( & root.join ( " a" ) , " docs/new.md" , b " ignored\n " ) ;
3665
assert! ( a.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
3666
3667
3668
let mut a = a;
3669
a.config.set ( " filter.paths" , " src,docs" ) .unwrap ( ) ;
3670
a.save_config ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3671
let r = a
3672
.checkout ( & a.bookmark_heads ( ) .unwrap ( ) , true , " widen" )
3673
.unwrap ( ) ;
7421309f Andy McCall 1 month ago ↶
3674
assert! ( r.written > = 2 ) ;
75167672 Andy McCall 1 month ago
3675
assert_eq! (
3676
fs:: read( root.join ( " a/docs/logo.png" ) ) .unwrap ( ) ,
3677
logo,
3678
" now fetched lazily"
3679
) ;
7421309f Andy McCall 1 month ago ↶
3680
assert! ( ! root.join ( " a/README.md" ) .exists ( ) ) ;
3681
3682
origin.pull ( " origin" ) .err ( ) ;
75167672 Andy McCall 1 month ago
3683
let full = origin
3684
.load_state ( & origin.bookmark_heads ( ) .unwrap ( ) )
3685
.unwrap ( )
3686
.materialize ( ) ;
3687
assert! (
3688
matches! ( & full.files[ " src/lib.rs" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " pub fn a() {}\n pub fn b() {}\n " )
3689
) ;
7421309f Andy McCall 1 month ago ↶
3690
assert! ( full.files.contains_key ( " README.md" ) ) ;
3691
let _ = h1;
3692
}
2863456a Andy McCall 1 month ago ↶
3693
7bd805ff Andy McCall 11 days ago ↶
3694
3695
3696
3697
2863456a Andy McCall 1 month ago ↶
3698
# [ test ]
7bd805ff Andy McCall 11 days ago ↶
3699
fn a_depth_0_clone_brings_its_binary_files ( ) {
3700
hermetic ( ) ;
3701
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3702
let root = dir.path ( ) ;
3703
let origin_dir = root.join ( " origin" ) ;
3704
let origin = Repo:: init( & origin_dir)
3705
.unwrap ( )
3706
.with_home ( root.join ( " home" ) ) ;
3707
let png: Vec< u8 > = ( 0 .. 4000 u32 )
3708
.map ( | i | ( i * 7 % 251 ) as u8 )
3709
.chain ( [ 0 , 0xff ] )
3710
.collect ( ) ;
3711
3712
3713
write ( & origin_dir, " logo.png" , & png) ;
3714
write ( & origin_dir, " a.txt" , b " first\n " ) ;
3715
origin.commit ( " logo" , & [ ] ) .unwrap ( ) ;
3716
write ( & origin_dir, " a.txt" , b " later\n " ) ;
3717
origin.commit ( " later" , & [ ] ) .unwrap ( ) ;
3718
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) .len ( ) , 1 ) ;
3719
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
3720
Repo:: clone_shallow( & url, & root.join ( " a" ) , 0 , None , & [ ] ) .unwrap ( ) ;
3721
assert_eq! ( fs:: read( root.join ( " a/logo.png" ) ) .unwrap ( ) , png) ;
3722
let lazy = Repo:: clone_shallow( & url, & root.join ( " b" ) , 0 , Some ( 0 ) , & [ ] ) .unwrap ( ) ;
3723
assert_eq! ( fs:: read( root.join ( " b/a.txt" ) ) .unwrap ( ) , b " later\n " ) ;
3724
let blob = {
3725
let heads = lazy.bookmark_heads ( ) .unwrap ( ) ;
3726
let st = lazy.load_state_history ( & heads) .unwrap ( ) ;
3727
st.opaque_nodes ( )
3728
.iter ( )
3729
.flat_map ( | n | st.live_blobs ( n) )
3730
.next ( )
3731
.unwrap ( )
3732
} ;
3733
assert_eq! ( lazy.read_blob ( & blob) .unwrap ( ) , png, " fetched on read" ) ;
f3811eb2 Andy McCall 11 days ago ↶
3734
3735
3736
let mut a = Repo:: open( & root.join ( " a" ) )
3737
.unwrap ( )
3738
.with_home ( root.join ( " home" ) ) ;
3739
let head = origin.bookmark_heads ( ) .unwrap ( ) [ 0 ] ;
3740
assert! ( ! a.store ( ) .has ( & head) , " no change fetched" ) ;
3741
assert! ( a.boundary ( ) .contains ( & head) ) ;
3742
assert_eq! (
3743
a.log ( )
3744
.unwrap ( )
3745
.iter ( )
3746
.filter ( | e | ! e.is_working_copy)
3747
.count ( ) ,
3748
2 ,
3749
" the log from the checkpoint"
3750
) ;
3751
write ( & root.join ( " a" ) , " a.txt" , b " later\n mine\n " ) ;
3752
a.config.set ( " remote.origin.url" , & url) .unwrap ( ) ;
3753
a.save_config ( ) .unwrap ( ) ;
3754
let mine = a.commit ( " mine" , & [ ] ) .unwrap ( ) ;
3755
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
3756
origin.refresh ( ) .unwrap ( ) ;
3757
assert! ( origin.store ( ) .has ( & mine) ) ;
3758
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ mine] ) ;
3759
3760
origin
3761
.checkout ( & origin.bookmark_heads ( ) .unwrap ( ) , false , " co" )
3762
.unwrap ( ) ;
3763
write ( & origin_dir, " a.txt" , b " later\n mine\n theirs\n " ) ;
3764
origin.snapshot ( " t" ) .unwrap ( ) ;
3765
origin.commit ( " theirs" , & [ ] ) .unwrap ( ) ;
3766
a.pull ( " origin" ) .unwrap ( ) ;
3767
assert_eq! (
3768
fs:: read( root.join ( " a/a.txt" ) ) .unwrap ( ) ,
3769
b " later\n mine\n theirs\n "
3770
) ;
7bd805ff Andy McCall 11 days ago ↶
3771
}
3772
3773
# [ test ]
2863456a Andy McCall 1 month ago ↶
3774
fn shallow_clone_and_deepen ( ) {
3775
hermetic ( ) ;
3776
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3777
let root = dir.path ( ) ;
3778
let origin_dir = root.join ( " origin" ) ;
75167672 Andy McCall 1 month ago
3779
let origin = Repo:: init( & origin_dir)
3780
.unwrap ( )
3781
.with_home ( root.join ( " home" ) ) ;
2863456a Andy McCall 1 month ago ↶
3782
let mut hashes = Vec :: new( ) ;
3783
for i in 0 .. 8 {
75167672 Andy McCall 1 month ago
3784
write (
3785
& origin_dir,
3786
" a.txt" ,
3787
format! (
3788
" {} \n " ,
3789
( 0 .. = i)
3790
.map ( | k | k.to_string ( ) )
3791
.collect:: < Vec< _ > > ( )
3792
.join ( " \n " )
3793
)
3794
.as_bytes ( ) ,
3795
) ;
2863456a Andy McCall 1 month ago ↶
3796
hashes.push ( origin.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ) ;
3797
}
3798
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
3799
let a = Repo:: clone_shallow( & url, & root.join ( " a" ) , 3 , None , & [ ] )
3800
.unwrap ( )
3801
.with_home ( root.join ( " home" ) ) ;
2863456a Andy McCall 1 month ago ↶
3802
assert! ( a.is_shallow ( ) ) ;
75167672 Andy McCall 1 month ago
3803
assert_eq! (
3804
fs:: read_to_string( root.join ( " a/a.txt" ) ) .unwrap ( ) ,
3805
" 0\n 1\n 2\n 3\n 4\n 5\n 6\n 7\n "
3806
) ;
3807
assert! (
3808
a.store ( ) .has ( & hashes[ 7 ] ) & & a.store ( ) .has ( & hashes[ 5 ] ) ,
3809
" three levels present"
3810
) ;
2863456a Andy McCall 1 month ago ↶
3811
assert! ( ! a.store ( ) .has ( & hashes[ 4 ] ) , " beyond the depth is absent" ) ;
3812
assert_eq! ( a.boundary ( ) , [ hashes[ 4 ] ] .into_iter ( ) .collect ( ) ) ;
75167672 Andy McCall 1 month ago
3813
assert_eq! (
3814
a.log ( ) .unwrap ( ) .len ( ) ,
3815
9 ,
3816
" the log shows every change from the checkpoint's metadata"
3817
) ;
2863456a Andy McCall 1 month ago ↶
3818
assert! ( ! a.snapshot ( " status" ) .unwrap ( ) .changed) ;
3819
assert_eq! ( a.quick_status ( ) .unwrap ( ) .heads, 1 ) ;
3820
3821
3822
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 " ) ;
3823
let h_new = a.commit ( " c8" , & [ ] ) .unwrap ( ) ;
3824
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 17 days ago
3825
origin.refresh ( ) .unwrap ( ) ;
2863456a Andy McCall 1 month ago ↶
3826
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ h_new] ) ;
3827
3828
write ( & origin_dir, " b.txt" , b " b\n " ) ;
3829
origin.pull ( " origin" ) .err ( ) ;
3830
let ( _ , wc) = origin.working_copy ( ) .unwrap ( ) ;
3831
let _ = wc;
75167672 Andy McCall 1 month ago
3832
origin
3833
.checkout ( & origin.bookmark_heads ( ) .unwrap ( ) , true , " co" )
3834
.unwrap ( ) ;
2863456a Andy McCall 1 month ago ↶
3835
write ( & origin_dir, " b.txt" , b " b\n " ) ;
3836
let h_b = origin.commit ( " b" , & [ ] ) .unwrap ( ) ;
3837
let pr = a.pull ( " origin" ) .unwrap ( ) ;
3838
assert! ( pr.changed) ;
3839
assert! ( a.store ( ) .has ( & h_b) ) ;
3840
assert! ( root.join ( " a/b.txt" ) .exists ( ) ) ;
3841
3842
3843
3844
3845
assert! ( a.deepen ( " origin" , 2 ) .unwrap ( ) > = 2 ) ;
3846
assert! ( a.store ( ) .has ( & hashes[ 3 ] ) ) ;
3847
assert! ( a.is_shallow ( ) ) ;
3848
a.deepen ( " origin" , 100 ) .unwrap ( ) ;
3849
assert! ( ! a.is_shallow ( ) , " boundary reached the root" ) ;
3850
assert! ( a.store ( ) .has ( & hashes[ 0 ] ) ) ;
75167672 Andy McCall 1 month ago
3851
assert_eq! (
3852
a.load_state_full ( & a.bookmark_heads ( ) .unwrap ( ) )
3853
.unwrap ( )
3854
.len ( ) ,
3855
10
3856
) ;
2863456a Andy McCall 1 month ago ↶
3857
}
132b884b Andy McCall 1 month ago ↶
3858
3859
# [ test ]
b63fc7a7 Andy McCall 11 days ago ↶
3860
fn a_tag_is_withdrawn_by_a_signed_withdrawal ( ) {
3861
hermetic ( ) ;
3862
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3863
let root = dir.path ( ) ;
3864
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
3865
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
3866
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
3867
.unwrap ( )
3868
.with_home ( root.join ( " home-a" ) ) ;
3869
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
3870
.unwrap ( ) ;
3871
write ( & root.join ( " a" ) , " f.txt" , b " 1\n " ) ;
3872
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
3873
a.tag_create ( " v1" , & [ h1] , " first release" ) .unwrap ( ) ;
3874
a.tag_create ( " v2" , & [ h1] , " " ) .unwrap ( ) ;
3875
a.policy_init ( ) .unwrap ( ) ;
3876
a.push ( " origin" , false ) .unwrap ( ) ;
3877
origin.refresh ( ) .unwrap ( ) ;
3878
let original = a
3879
.tag_refs ( )
3880
.unwrap ( )
3881
.into_iter ( )
3882
.find ( | t | t.name = = " v1" )
3883
.unwrap ( ) ;
3884
let b = Repo:: clone( & url, & root.join ( " b" ) )
3885
.unwrap ( )
3886
.with_home ( root.join ( " home-b" ) ) ;
3887
assert_eq! ( b.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
3888
3889
3890
let mut c = Repo:: clone( & url, & root.join ( " c" ) )
3891
.unwrap ( )
3892
.with_home ( root.join ( " home-c" ) ) ;
3893
c.create_identity ( " Cy" , " [email protected] " , IdentityScope:: User)
3894
.unwrap ( ) ;
3895
let r = c.push_delete ( " origin" , " tags/v1" ) .unwrap ( ) ;
3896
assert! (
3897
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " may not withdraw" ) ,
3898
" {r:?}"
3899
) ;
3900
assert_eq! ( c.tag_heads ( " v1" ) .unwrap ( ) , None , " withdrawn there first" ) ;
3901
origin.refresh ( ) .unwrap ( ) ;
3902
assert_eq! ( origin.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
3903
3904
3905
let r = a.push_delete ( " origin" , " tags/v1" ) .unwrap ( ) ;
3906
assert! ( r[ 0 ] .ok & & r[ 0 ] .reason = = " withdrawn" , " {r:?}" ) ;
3907
origin.refresh ( ) .unwrap ( ) ;
3908
assert_eq! ( origin.tag_heads ( " v1" ) .unwrap ( ) , None ) ;
3909
assert_eq! ( a.tag_heads ( " v1" ) .unwrap ( ) , None ) ;
3910
assert! ( origin.tag_withdrawal ( " v1" ) .unwrap ( ) .is_some ( ) ) ;
3911
assert! ( origin.tag_attest ( " v1" ) .unwrap ( ) .is_some ( ) , " kept beside it" ) ;
3912
let refs = origin.remote_refs ( ) .unwrap ( ) ;
3913
assert! ( refs.tags.iter ( ) .all ( | t | t.name ! = " v1" ) ) ;
3914
assert_eq! ( refs.withdrawn.len ( ) , 1 ) ;
3915
let r = a.push_delete ( " origin" , " tags/v1" ) .unwrap ( ) ;
3916
assert! ( r[ 0 ] .ok & & r[ 0 ] .reason = = " already withdrawn" , " {r:?}" ) ;
3917
3918
3919
let rep = b.fetch ( " origin" ) .unwrap ( ) ;
3920
assert_eq! ( rep.withdrawn_tags, vec! [ " v1" .to_string ( ) ] ) ;
3921
assert_eq! ( b.tag_heads ( " v1" ) .unwrap ( ) , None ) ;
3922
assert! ( b.tag_create ( " v1" , & [ h1] , " " ) .is_err ( ) ) ;
3923
let r = origin
3924
.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ original] )
3925
.unwrap ( ) ;
3926
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " withdrawn" ) , " {r:?}" ) ;
3927
3928
3929
a.tag_delete ( " v2" , " tagged too early" ) .unwrap ( ) ;
3930
let r = a.push ( " origin" , false ) .unwrap ( ) ;
3931
assert! (
3932
r.iter ( )
3933
.any ( | x | x.name = = " tags/v2" & & x.ok & & x.reason = = " withdrawn" ) ,
3934
" {r:?}"
3935
) ;
3936
origin.refresh ( ) .unwrap ( ) ;
3937
assert_eq! ( origin.tag_heads ( " v2" ) .unwrap ( ) , None ) ;
3938
let ( _ , w) = origin.tag_withdrawal ( " v2" ) .unwrap ( ) .unwrap ( ) ;
3939
assert! (
3940
matches! ( & w.attest.body, AttestBody:: Untag { reason, .. } if reason = = " tagged too early" )
3941
) ;
3942
}
3943
3944
# [ test ]
132b884b Andy McCall 1 month ago ↶
3945
fn tags_are_immutable_named_states_and_travel ( ) {
3946
hermetic ( ) ;
3947
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3948
let root = dir.path ( ) ;
3949
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
3950
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
3951
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
3952
.unwrap ( )
3953
.with_home ( root.join ( " home-a" ) ) ;
3954
let ( key_a, _ ) = a
3955
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
3956
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3957
write ( & root.join ( " a" ) , " f.txt" , b " 1\n " ) ;
3958
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
3959
write ( & root.join ( " a" ) , " f.txt" , b " 1\n 2\n " ) ;
3960
let h2 = a.commit ( " two" , & [ ] ) .unwrap ( ) ;
3961
3962
let t = a.tag_create ( " v1" , & [ h1] , " first release" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3963
assert_eq! (
3964
( t.heads.clone ( ) , t.signer.as_deref ( ) ) ,
3965
( vec! [ h1] , Some ( " Ada" ) )
3966
) ;
132b884b Andy McCall 1 month ago ↶
3967
assert! ( a.tag_create ( " v1" , & [ h2] , " " ) .is_err ( ) , " immutable" ) ;
3968
assert! ( a.tag_create ( " bad/name" , & [ h2] , " " ) .is_err ( ) ) ;
3969
assert_eq! ( a.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
3970
assert_eq! ( a.resolve_heads ( " v1" ) .unwrap ( ) , vec! [ h1] ) ;
3971
assert_eq! ( a.resolve_heads ( " main" ) .unwrap ( ) , vec! [ h2] ) ;
3972
let ( _ , sa) = a.tag_attest ( " v1" ) .unwrap ( ) .unwrap ( ) ;
3973
assert_eq! ( sa.sigs[ 0 ] .key, key_a) ;
ee130dd9 Andy McCall 15 days ago ↶
3974
3975
3976
let mut refs = a.remote_refs ( ) .unwrap ( ) ;
3977
a.refs_for_client ( & mut refs, None ) ;
3978
assert_eq! ( refs.tags.len ( ) , 1 ) ;
132b884b Andy McCall 1 month ago ↶
3979
75167672 Andy McCall 1 month ago
3980
a.checkout ( & a.resolve_heads ( " v1" ) .unwrap ( ) , false , " co" )
3981
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3982
assert_eq! ( fs:: read( root.join ( " a/f.txt" ) ) .unwrap ( ) , b " 1\n " ) ;
75167672 Andy McCall 1 month ago
3983
a.checkout ( & a.bookmark_heads ( ) .unwrap ( ) , false , " co" )
3984
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3985
3986
3987
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 17 days ago
3988
origin.refresh ( ) .unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3989
assert! ( r.iter ( ) .any ( | x | x.name = = " tags/v1" & & x.ok ) , " {r:?}" ) ;
3990
assert_eq! ( origin.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
75167672 Andy McCall 1 month ago
3991
let mut forged = a
3992
.tag_refs ( )
3993
.unwrap ( )
3994
.into_iter ( )
3995
.find ( | t | t.name = = " v1" )
3996
.unwrap ( ) ;
3997
forged.state = origin
3998
.store ( )
3999
.put_state ( & StateObject:: new( vec! [ h2] ) )
4000
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
4001
forged.heads = vec! [ h2] ;
4002
forged.attestation = None ;
75167672 Andy McCall 1 month ago
4003
let r = origin
4004
.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ forged] )
4005
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
4006
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " immutable" ) , " {r:?}" ) ;
4007
4008
75167672 Andy McCall 1 month ago
4009
let b = Repo:: clone( & url, & root.join ( " b" ) )
4010
.unwrap ( )
4011
.with_home ( root.join ( " home-b" ) ) ;
132b884b Andy McCall 1 month ago ↶
4012
assert_eq! ( b.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
4013
assert! ( b.tags ( ) .unwrap ( ) [ 0 ] .signer.as_deref ( ) = = Some ( " Ada" ) ) ;
4014
a.tag_create ( " v2" , & [ h2] , " second" ) .unwrap ( ) ;
4015
a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 17 days ago
4016
origin.refresh ( ) .unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
4017
let rep = b.fetch ( " origin" ) .unwrap ( ) ;
4018
assert_eq! ( rep.new_tags, 1 ) ;
4019
assert_eq! ( b.tag_heads ( " v2" ) .unwrap ( ) , Some ( vec! [ h2] ) ) ;
4020
4021
4022
a.policy_init ( ) .unwrap ( ) ;
4023
a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 17 days ago
4024
origin.refresh ( ) .unwrap ( ) ;
1ec6fdbc Andy McCall 16 days ago ↶
4025
let unattested = crate :: protocol:: TagRef:: new(
4026
" v3" .into ( ) ,
4027
origin
75167672 Andy McCall 1 month ago
4028
.store ( )
4029
.put_state ( & StateObject:: new( vec! [ h2] ) )
4030
.unwrap ( ) ,
1ec6fdbc Andy McCall 16 days ago ↶
4031
vec! [ h2] ,
4032
None ,
4033
) ;
75167672 Andy McCall 1 month ago
4034
let r = origin
4035
.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ unattested] )
4036
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
4037
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " attestation" ) , " {r:?}" ) ;
4038
a.tag_create ( " v3" , & [ h2] , " " ) .unwrap ( ) ;
4039
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 17 days ago
4040
origin.refresh ( ) .unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
4041
assert! ( r.iter ( ) .any ( | x | x.name = = " tags/v3" & & x.ok ) , " {r:?}" ) ;
4042
assert! ( a.gc ( true ) .is_ok ( ) ) ;
4043
assert_eq! ( a.tags ( ) .unwrap ( ) .len ( ) , 3 , " tags survive gc" ) ;
e5d9b051 Andy McCall 19 days ago ↶
4044
4045
4046
4047
4048
let rot = a.rotate_identity ( ) .unwrap ( ) ;
4049
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 17 days ago
4050
origin.refresh ( ) .unwrap ( ) ;
e5d9b051 Andy McCall 19 days ago ↶
4051
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
4052
let c = Repo:: clone( & url, & root.join ( " c" ) )
4053
.unwrap ( )
4054
.with_home ( root.join ( " home-c" ) ) ;
4055
assert_eq! ( c.tag_heads ( " v3" ) .unwrap ( ) , Some ( vec! [ h2] ) ) ;
4056
let ( _ , sp) = c.current_policy ( ) .unwrap ( ) .unwrap ( ) ;
4057
assert! ( ! sp.policy.authorizes ( & key_a, " tags/v9" ) ) ;
4058
let made = c.identity ( & rot.new) .unwrap ( ) .created;
4059
assert! ( c.predecessor_authorized ( & sp.policy, & key_a, " tags/v9" , made - 1 ) ) ;
4060
assert! ( ! c.predecessor_authorized ( & sp.policy, & key_a, " tags/v9" , made) ) ;
4061
assert! ( ! c.predecessor_authorized ( & sp.policy, & rot.new, " tags/v9" , 0 ) ) ;
132b884b Andy McCall 1 month ago ↶
4062
}
4e88e774 Andy McCall 1 month ago ↶
4063
4064
# [ test ]
4065
fn multiple_workspaces_share_a_repository ( ) {
4066
hermetic ( ) ;
4067
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4068
let root = dir.path ( ) ;
4069
let main_dir = root.join ( " main" ) ;
4070
let repo = Repo:: init( & main_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4071
write ( & main_dir, " a.txt" , b " a\n " ) ;
4072
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
4073
assert_eq! ( repo.workspaces ( ) .unwrap ( ) .len ( ) , 1 ) ;
4074
4075
4076
let ws_dir = root.join ( " ws2" ) ;
4476cdbb Andy McCall 11 days ago ↶
4077
let ops = repo.op_log ( ) .unwrap ( ) .len ( ) ;
4e88e774 Andy McCall 1 month ago ↶
4078
let ws = repo.workspace_add ( & ws_dir, None ) .unwrap ( ) ;
4476cdbb Andy McCall 11 days ago ↶
4079
assert_eq! (
4080
ws.op_log ( ) .unwrap ( ) .len ( ) ,
4081
ops + 2 ,
4082
" the add and its checkout, and no snapshot finding the tree deleted (oxygen#137)"
4083
) ;
4e88e774 Andy McCall 1 month ago ↶
4084
assert! ( ws_dir.join ( " .oxy" ) .is_file ( ) ) ;
4085
assert_eq! ( fs:: read( ws_dir.join ( " a.txt" ) ) .unwrap ( ) , b " a\n " ) ;
4086
assert_ne! ( ws.workspace ( ) , repo.workspace ( ) ) ;
4087
let list = repo.workspaces ( ) .unwrap ( ) ;
4088
assert_eq! ( list.len ( ) , 2 ) ;
75167672 Andy McCall 1 month ago
4089
assert! ( list
4090
.iter ( )
4091
.any ( | w | w.id = = ws.workspace ( ) & & w.path = = ws_dir.canonicalize ( ) .unwrap ( ) & & ! w.current ) ) ;
4e88e774 Andy McCall 1 month ago ↶
4092
assert! ( ! ws.snapshot ( " status" ) .unwrap ( ) .changed) ;
4093
4094
4095
write ( & ws_dir, " b.txt" , b " b\n " ) ;
4096
let s = ws.snapshot ( " status" ) .unwrap ( ) ;
4097
assert_eq! ( s.statuses.len ( ) , 1 ) ;
75167672 Andy McCall 1 month ago
4098
assert! (
4099
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
4100
" the first workspace is untouched"
4101
) ;
4e88e774 Andy McCall 1 month ago ↶
4102
let h2 = ws.commit ( " b from ws2" , & [ ] ) .unwrap ( ) ;
767ac6cd Andy McCall 17 days ago
4103
repo.refresh ( ) .unwrap ( ) ;
4e88e774 Andy McCall 1 month ago ↶
4104
let mut heads = repo.bookmark_heads ( ) .unwrap ( ) ;
4105
heads.sort ( ) ;
4106
let mut want = vec! [ h1, h2] ;
4107
want.sort ( ) ;
4108
assert_eq! ( heads, want, " both workspaces see the bookmark move" ) ;
4109
assert! ( ! main_dir.join ( " b.txt" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
4110
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " co" )
4111
.unwrap ( ) ;
4e88e774 Andy McCall 1 month ago ↶
4112
assert! ( main_dir.join ( " b.txt" ) .exists ( ) ) ;
4113
4114
4115
let reopened = Repo:: open( & ws_dir) .unwrap ( ) ;
4116
assert_eq! ( reopened.workspace ( ) , ws.workspace ( ) ) ;
4117
assert_eq! ( reopened.id ( ) .unwrap ( ) , repo.id ( ) .unwrap ( ) ) ;
4118
assert_eq! ( reopened.quick_status ( ) .unwrap ( ) .heads, 2 ) ;
4119
4120
4121
assert! ( repo.workspace_add ( & main_dir.join ( " inner" ) , None ) .is_err ( ) ) ;
4122
assert! ( repo.workspace_forget ( & repo.workspace ( ) ) .is_err ( ) ) ;
4123
repo.workspace_forget ( & ws.workspace ( ) ) .unwrap ( ) ;
4124
assert_eq! ( repo.workspaces ( ) .unwrap ( ) .len ( ) , 1 ) ;
4125
assert! ( ws_dir.join ( " b.txt" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
4126
assert! (
4127
ws.snapshot ( " status" ) .is_err ( ) ,
4128
" a forgotten workspace is no longer in the view"
4129
) ;
4e88e774 Andy McCall 1 month ago ↶
4130
6caacfff Andy McCall 1 month ago ↶
4131
4132
4e88e774 Andy McCall 1 month ago ↶
4133
let lock = repo.lock_snapshot ( ) .unwrap ( ) ;
6caacfff Andy McCall 1 month ago ↶
4134
let again = repo.lock_working_copy ( ) .unwrap ( ) ;
4135
drop ( again) ;
4e88e774 Andy McCall 1 month ago ↶
4136
drop ( lock) ;
75167672 Andy McCall 1 month ago
4137
let lock_path = main_dir
4138
.join ( " .oxy/workspaces" )
4139
.join ( repo.workspace ( ) .to_hex ( ) )
4140
.join ( " lock" ) ;
4e88e774 Andy McCall 1 month ago ↶
4141
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 120 ) ;
6caacfff Andy McCall 1 month ago ↶
4142
let age = | p : & Path| fs:: File:: open( p) .unwrap ( ) .set_modified ( old) .unwrap ( ) ;
4143
fs:: write( & lock_path, std:: process:: id( ) .to_string ( ) ) .unwrap ( ) ;
4144
age ( & lock_path) ;
4145
assert! (
4146
! super :: workspace:: lock_is_stale( & lock_path) ,
4147
" a live process keeps its lock"
4148
) ;
4149
fs:: write( & lock_path, " 4294967295" ) .unwrap ( ) ;
4150
age ( & lock_path) ;
4e88e774 Andy McCall 1 month ago ↶
4151
assert! ( repo.lock_snapshot ( ) .is_ok ( ) , " stale lock taken over" ) ;
4152
}
cdb5beba Andy McCall 1 month ago ↶
4153
4154
# [ test ]
4155
fn attributes_binary_eol_nocompress_lockable ( ) {
4156
hermetic ( ) ;
4157
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4158
let root = dir.path ( ) ;
4159
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
4160
write (
4161
root,
4162
" .oxyattributes" ,
4163
b " *.dat binary nocompress\n *.txt eol=crlf\n assets/** lockable\n " ,
4164
) ;
cdb5beba Andy McCall 1 month ago ↶
4165
write ( root, " plain.dat" , b " no nul bytes here\n " ) ;
4166
write ( root, " notes.txt" , b " one\r \n two\r \n " ) ;
4167
write ( root, " assets/model.bin" , b " \0 model" ) ;
4168
let h = repo.commit ( " attrs" , & [ ] ) .unwrap ( ) ;
4169
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
4170
assert! (
4171
snap.files[ " plain.dat" ] .mode.opaque,
4172
" binary attribute forces opaque without NUL bytes"
4173
) ;
4174
assert! (
4175
matches! ( & snap.files[ " notes.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n " ) ,
4176
" stored with LF"
4177
) ;
4178
assert! (
4179
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
4180
" CRLF on disk equals LF in the state"
4181
) ;
cdb5beba Andy McCall 1 month ago ↶
4182
4183
let r = repo.gc ( true ) .unwrap ( ) ;
4184
assert! ( r.packed.objects > 0 ) ;
4185
4186
fs:: remove_file( root.join ( " notes.txt" ) ) .unwrap ( ) ;
4187
repo.checkout ( & [ h] , true , " co" ) .unwrap ( ) ;
4188
assert_eq! ( fs:: read( root.join ( " notes.txt" ) ) .unwrap ( ) , b " one\r \n two\r \n " ) ;
75167672 Andy McCall 1 month ago
4189
assert! ( fs:: metadata( root.join ( " assets/model.bin" ) )
4190
.unwrap ( )
4191
.permissions ( )
4192
.readonly ( ) ) ;
cdb5beba Andy McCall 1 month ago ↶
4193
assert! ( repo.lock_path ( " plain.dat" ) .is_err ( ) , " not lockable" ) ;
4194
repo.lock_path ( " assets/model.bin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4195
assert! ( ! fs:: metadata( root.join ( " assets/model.bin" ) )
4196
.unwrap ( )
4197
.permissions ( )
4198
.readonly ( ) ) ;
cdb5beba Andy McCall 1 month ago ↶
4199
assert! ( repo.is_locked ( " assets/model.bin" ) ) ;
4200
write ( root, " assets/model.bin" , b " \0 model2" ) ;
4201
repo.commit ( " edit model" , & [ ] ) .unwrap ( ) ;
4202
repo.unlock_path ( " assets/model.bin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4203
assert! ( fs:: metadata( root.join ( " assets/model.bin" ) )
4204
.unwrap ( )
4205
.permissions ( )
4206
.readonly ( ) ) ;
cdb5beba Andy McCall 1 month ago ↶
4207
4208
repo.checkout ( & [ h] , true , " co" ) .unwrap ( ) ;
4209
assert_eq! ( fs:: read( root.join ( " assets/model.bin" ) ) .unwrap ( ) , b " \0 model" ) ;
4210
}
6d3eefbc Andy McCall 1 month ago ↶
4211
4212
# [ cfg ( unix ) ]
4213
# [ test ]
4214
fn hooks_can_veto_observe_and_add_dependencies ( ) {
4215
use std:: os:: unix:: fs:: PermissionsExt;
4216
hermetic ( ) ;
4217
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4218
let root = dir.path ( ) ;
4219
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4220
let hooks = root.join ( " .oxy/hooks" ) ;
4221
fs:: create_dir_all( & hooks) .unwrap ( ) ;
4222
let hook = | name : & str , body : & str | {
4223
let p = hooks.join ( name) ;
4224
fs:: write( & p, format! ( " #!/bin/sh\n {body} \n " ) ) .unwrap ( ) ;
4225
fs:: set_permissions( & p, fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
4226
} ;
4227
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" ) ;
4228
hook ( " post-commit" , " cat > \" $OXY_ROOT/.last-post-commit\" " ) ;
4229
hook ( " post-checkout" , " cat > \" $OXY_ROOT/.last-post-checkout\" " ) ;
4230
hook ( " pre-push" , " echo pushing blocked >&2; exit 3" ) ;
4231
write ( root, " .oxyignore" , b " .last-*\n " ) ;
4232
write ( root, " a.txt" , b " a\n " ) ;
4233
let err = repo.commit ( " WIP: nope" , & [ ] ) .unwrap_err ( ) .to_string ( ) ;
4234
assert! ( err.contains ( " pre-commit refused: no WIP commits" ) , " {err}" ) ;
4235
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
4236
let pre = fs:: read_to_string( root.join ( " .last-pre-commit" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4237
assert! (
4238
pre.contains ( " \" event\" :\" pre-commit\" " )
4239
& & pre.contains ( " \" path\" :\" a.txt\" " )
4240
& & pre.contains ( " \" message\" :\" first\" " ) ,
4241
" {pre}"
4242
) ;
6d3eefbc Andy McCall 1 month ago ↶
4243
let post = fs:: read_to_string( root.join ( " .last-post-commit" ) ) .unwrap ( ) ;
4244
assert! ( post.contains ( & h1.to_hex ( ) ) , " {post}" ) ;
4245
repo.checkout ( & [ h1] , false , " co" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4246
assert! ( fs:: read_to_string( root.join ( " .last-post-checkout" ) )
4247
.unwrap ( )
4248
.contains ( " post-checkout" ) ) ;
6d3eefbc Andy McCall 1 month ago ↶
4249
repo.config.set ( " remote.origin.url" , " /nowhere" ) .unwrap ( ) ;
4250
repo.save_config ( ) .unwrap ( ) ;
4251
let err = repo.push ( " origin" , false ) .unwrap_err ( ) .to_string ( ) ;
4252
assert! ( err.contains ( " pre-push refused: pushing blocked" ) , " {err}" ) ;
4253
4254
4255
write ( root, " b.txt" , b " b\n " ) ;
75167672 Andy McCall 1 month ago
4256
hook (
4257
" semantic-deps" ,
4258
& format! (
4259
" cat > \" $OXY_ROOT/.last-semantic\" ; echo '{{ \" deps\" : [\" {} \" ]}} '" ,
4260
h1.to_hex ( )
4261
) ,
4262
) ;
6d3eefbc Andy McCall 1 month ago ↶
4263
let h2 = repo.commit ( " b without hook" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4264
assert! (
4265
repo.store ( ) .get_change ( & h2) .unwrap ( ) .deps.is_empty ( ) ,
4266
" hook disabled by default"
4267
) ;
6d3eefbc Andy McCall 1 month ago ↶
4268
repo.config.set ( " hooks.semantic-deps" , " true" ) .unwrap ( ) ;
4269
repo.save_config ( ) .unwrap ( ) ;
4270
write ( root, " c.txt" , b " c\n " ) ;
4271
let h3 = repo.commit ( " c with hook" , & [ ] ) .unwrap ( ) ;
4272
assert_eq! ( repo.store ( ) .get_change ( & h3) .unwrap ( ) .deps, vec! [ h1] ) ;
4273
let sem = fs:: read_to_string( root.join ( " .last-semantic" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4274
assert! (
4275
sem.contains ( " \" touched_paths\" :[\" c.txt\" ]" ) & & sem.contains ( " candidate_deps" ) ,
4276
" {sem}"
4277
) ;
6d3eefbc Andy McCall 1 month ago ↶
4278
assert_eq! ( json_str ( " a\" b\\ c\n " ) , " \" a\\ \" b\\ \\ c\\ n\" " ) ;
4279
}
23b217d7 Andy McCall 1 month ago ↶
4280
4281
# [ test ]
4282
fn amend_into_older_change_and_split ( ) {
4283
hermetic ( ) ;
4284
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4285
let root = dir.path ( ) ;
4286
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
4287
repo.create_identity ( " Ada" , " a@b" , IdentityScope:: User)
4288
.unwrap ( ) ;
23b217d7 Andy McCall 1 month ago ↶
4289
repo.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
4290
write ( root, " a.txt" , b " one\n two\n " ) ;
4291
let h0 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4292
write ( root, " a.txt" , b " one\n TWO\n " ) ;
4293
let h1 = repo.commit ( " edit two" , & [ ] ) .unwrap ( ) ;
4294
write ( root, " a.txt" , b " one\n TWO\n three\n " ) ;
4295
let h2 = repo.commit ( " add three" , & [ ] ) .unwrap ( ) ;
4296
let id1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) .change_id;
4297
4298
4299
write ( root, " a.txt" , b " one\n TWO!\n three\n " ) ;
4300
let h1b = repo.amend_into ( & h1, None ) .unwrap ( ) ;
4301
assert_ne! ( h1b, h1) ;
4302
let c1b = repo.store ( ) .get_change ( & h1b) .unwrap ( ) ;
4303
assert_eq! ( c1b.change_id, id1) ;
4304
assert_eq! ( c1b.deps, vec! [ h0] ) ;
75167672 Andy McCall 1 month ago
4305
assert! ( matches! (
4306
repo.verify_change ( & h1b) .unwrap ( ) ,
4307
SignatureStatus:: Valid { .. }
4308
) ) ;
23b217d7 Andy McCall 1 month ago ↶
4309
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
4310
assert_eq! ( heads.len ( ) , 1 ) ;
4311
let top = repo.store ( ) .get_change ( & heads[ 0 ] ) .unwrap ( ) ;
4312
assert_eq! ( top.message, " add three" ) ;
75167672 Andy McCall 1 month ago
4313
assert_eq! (
4314
top.deps.iter ( ) .copied ( ) .collect:: < BTreeSet< _ > > ( ) ,
4315
[ h0, h1b] .into_iter ( ) .collect ( ) ,
4316
" depends on the file's creator and the rewritten line"
4317
) ;
23b217d7 Andy McCall 1 month ago ↶
4318
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
4319
assert! (
4320
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n TWO!\n three\n " )
4321
) ;
4322
assert! (
4323
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
4324
" the working copy is clean after folding"
4325
) ;
23b217d7 Andy McCall 1 month ago ↶
4326
4327
let only = repo.load_state ( & [ h1b] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
4328
assert! (
4329
matches! ( & only.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n TWO!\n " )
4330
) ;
23b217d7 Andy McCall 1 month ago ↶
4331
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 ) ;
4332
4333
4334
write ( root, " a.txt" , b " one\n TWO!\n three!\n " ) ;
4335
let err = repo.amend_into ( & h1b, None ) .unwrap_err ( ) .to_string ( ) ;
4336
assert! ( err.contains ( " introduced after" ) , " {err}" ) ;
75167672 Andy McCall 1 month ago
4337
assert! (
4338
repo.snapshot ( " status" ) .unwrap ( ) .statuses.len ( ) = = 1 ,
4339
" nothing was changed by the refusal"
4340
) ;
23b217d7 Andy McCall 1 month ago ↶
4341
write ( root, " a.txt" , b " one\n TWO!\n three\n " ) ;
4342
4343
4344
write ( root, " b.txt" , b " b\n " ) ;
4345
write ( root, " c.txt" , b " c\n " ) ;
4346
let h3 = repo.commit ( " b and c" , & [ ] ) .unwrap ( ) ;
4347
let id3 = repo.store ( ) .get_change ( & h3) .unwrap ( ) .change_id;
4348
let ( p1, p2) = repo.split ( & h3, & [ " b.txt" .into ( ) ] ) .unwrap ( ) ;
4349
let c_1 = repo.store ( ) .get_change ( & p1) .unwrap ( ) ;
4350
let c_2 = repo.store ( ) .get_change ( & p2) .unwrap ( ) ;
4351
assert! ( c_1 .message.contains ( " part 1: b.txt" ) ) ;
4352
assert_eq! ( c_2 .change_id, id3) ;
4353
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
75167672 Andy McCall 1 month ago
4354
assert! (
4355
heads.contains ( & p1) & & heads.contains ( & p2) ,
4356
" independent parts are both heads"
4357
) ;
4358
let snap = repo
4359
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
4360
.unwrap ( )
4361
.materialize ( ) ;
23b217d7 Andy McCall 1 month ago ↶
4362
assert! ( snap.files.contains_key ( " b.txt" ) & & snap.files.contains_key ( " c.txt" ) ) ;
4363
let only1 = repo.load_state ( & [ p1] ) .unwrap ( ) .materialize ( ) ;
4364
assert! ( only1.files.contains_key ( " b.txt" ) & & ! only1.files.contains_key ( " c.txt" ) ) ;
4365
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4366
assert! ( repo.split ( & p2, & [ " nope.txt" .into ( ) ] ) .is_err ( ) ) ;
4367
let _ = h2;
4368
}
28fc23d7 Andy McCall 1 month ago ↶
4369
4370
# [ test ]
4371
fn identity_lifecycle ( ) {
4372
hermetic ( ) ;
4373
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4374
let root = dir.path ( ) ;
4375
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
4376
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
4377
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
4378
.unwrap ( )
4379
.with_home ( root.join ( " home-a" ) ) ;
28fc23d7 Andy McCall 1 month ago ↶
4380
a.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4381
let ( key_a, _ ) = a
4382
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
4383
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4384
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
4385
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
4386
assert! ( a.policy_init ( ) .unwrap ( ) .applied) ;
4387
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
767ac6cd Andy McCall 17 days ago
4388
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4389
4390
75167672 Andy McCall 1 month ago
4391
let mut e = Repo:: init( & root.join ( " e" ) )
4392
.unwrap ( )
4393
.with_home ( root.join ( " home-e" ) ) ;
28fc23d7 Andy McCall 1 month ago ↶
4394
e.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4395
e.create_identity_with (
4396
" Eve" ,
4397
" [email protected] " ,
4398
IdentityScope:: User,
4399
Some ( Hlc:: now( ) .wall_ms - 1 ) ,
4400
)
4401
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4402
write ( & root.join ( " e" ) , " e.txt" , b " e\n " ) ;
4403
let he = e.commit ( " late" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4404
assert! (
4405
matches! ( e.verify_change ( & he) .unwrap ( ) , SignatureStatus:: Invalid( m) if m.contains ( " expired" ) )
4406
) ;
28fc23d7 Andy McCall 1 month ago ↶
4407
75167672 Andy McCall 1 month ago
4408
let r = e
4409
.renew_identity ( Some ( Hlc:: now( ) .wall_ms + 86_400_000 ) )
4410
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4411
assert_eq! ( e.identity ( & r.new) .unwrap ( ) .previous, Some ( r.old) ) ;
4412
write ( & root.join ( " e" ) , " e.txt" , b " e2\n " ) ;
4413
let he2 = e.commit ( " in time" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4414
assert! (
4415
matches! ( e.verify_change ( & he2) .unwrap ( ) , SignatureStatus:: Valid { key, .. } if key = = r.new)
4416
) ;
28fc23d7 Andy McCall 1 month ago ↶
4417
4418
4419
let r = a.rotate_identity ( ) .unwrap ( ) ;
4420
assert_eq! ( r.old, key_a) ;
4421
assert_eq! ( a.config.author_key, Some ( r.new) ) ;
4422
assert_eq! ( a.identity ( & r.new) .unwrap ( ) .previous, Some ( key_a) ) ;
4423
assert! ( r.policy.as_ref ( ) .unwrap ( ) .applied) ;
4424
let ( _ , sp) = a.current_policy ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4425
assert_eq! (
4426
( sp.policy.serial, sp.policy.roots.clone ( ) ) ,
4427
( 2 , vec! [ r.new] )
4428
) ;
4429
assert! (
4430
! sp.policy.revoked.contains ( & key_a) & & sp.policy.authorizes ( & r.new, " main" ) ,
4431
" superseded, not revoked"
4432
) ;
28fc23d7 Andy McCall 1 month ago ↶
4433
assert! ( sp.policy.succeeds ( & r.new, & key_a) & & ! sp.policy.succeeds ( & key_a, & r.new) ) ;
75167672 Andy McCall 1 month ago
4434
assert! (
4435
matches! ( a.verify_change ( & h1) .unwrap ( ) , SignatureStatus:: Valid { key, revoked: false , .. } if key = = key_a)
4436
) ;
28fc23d7 Andy McCall 1 month ago ↶
4437
write ( & root.join ( " a" ) , " a.txt" , b " a2\n " ) ;
4438
let h2 = a.commit ( " two" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4439
assert! (
4440
matches! ( a.verify_change ( & h2) .unwrap ( ) , SignatureStatus:: Valid { key, revoked: false , .. } if key = = r.new)
4441
) ;
28fc23d7 Andy McCall 1 month ago ↶
4442
4443
let res = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 17 days ago
4444
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4445
assert! ( res.iter ( ) .all ( | x | x.ok ) , " {res:?}" ) ;
4446
assert_eq! ( origin.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 2 ) ;
4447
let key_a = r.new;
4448
4449
4450
75167672 Andy McCall 1 month ago
4451
let mut b = Repo:: clone( & url, & root.join ( " b" ) )
4452
.unwrap ( )
4453
.with_home ( root.join ( " home-b" ) ) ;
4454
let ( key_b, id_b) = b
4455
.create_identity ( " Bob" , " [email protected] " , IdentityScope:: User)
4456
.unwrap ( ) ;
7ba155f2 Andy McCall 1 month ago ↶
4457
let r = a.delegate_add ( id_b.clone ( ) , Scope:: ALL , None ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4458
assert_eq! ( r.old, key_a) ;
4459
let key_a2 = r.new;
75167672 Andy McCall 1 month ago
4460
assert_eq! (
7ba155f2 Andy McCall 1 month ago ↶
4461
a.identity ( & key_a2) .unwrap ( ) .delegates,
4462
vec! [ Delegate:: full( key_b) ]
4463
) ;
4464
assert_eq! (
75167672 Andy McCall 1 month ago
4465
a.identity ( & key_a2) .unwrap ( ) .ed25519_pk,
4466
a.identity ( & key_a) .unwrap ( ) .ed25519_pk
7ba155f2 Andy McCall 1 month ago ↶
4467
) ;
4468
assert! (
4469
a.delegate_add ( id_b.clone ( ) , Scope:: ALL , None ) .is_err ( ) ,
4470
" already a delegate"
75167672 Andy McCall 1 month ago
4471
) ;
28fc23d7 Andy McCall 1 month ago ↶
4472
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | x | x.ok ) ) ;
767ac6cd Andy McCall 17 days ago
4473
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4474
write ( & root.join ( " a" ) , " a.txt" , b " a3\n " ) ;
4475
let h3 = a.commit ( " three" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4476
assert! (
4477
matches! ( a.verify_change ( & h3) .unwrap ( ) , SignatureStatus:: Valid { key, .. } if key = = key_a2) ,
4478
" the same secret signs under the new KeyId"
4479
) ;
28fc23d7 Andy McCall 1 month ago ↶
4480
4481
a.set_user_config ( " core.sign" , " seal" ) .unwrap ( ) ;
4482
write ( & root.join ( " a" ) , " a.txt" , b " a4\n " ) ;
4483
let h4 = a.commit ( " four" , & [ ] ) .unwrap ( ) ;
4484
assert_eq! ( a.verify_change ( & h4) .unwrap ( ) , SignatureStatus:: Unsigned) ;
4485
let ( _ , sk_b) = b.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4486
a.store ( )
4487
.put_signature ( & h4, & sk_b.sign ( Domain:: Change, & key_b, & h4) )
4488
.unwrap ( ) ;
4489
assert! (
4490
matches! ( a.verify_change ( & h4) .unwrap ( ) , SignatureStatus:: Valid { key, .. } if key = = key_b)
4491
) ;
28fc23d7 Andy McCall 1 month ago ↶
4492
a.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
4493
4494
let r = a.delegate_remove ( & key_b) .unwrap ( ) ;
4495
assert! ( a.identity ( & r.new) .unwrap ( ) .delegates.is_empty ( ) ) ;
4496
assert! ( a.delegate_remove ( & key_b) .is_err ( ) ) ;
4497
let key_a3 = r.new;
4498
let res = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 17 days ago
4499
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4500
assert! ( res.iter ( ) .all ( | x | x.ok ) , " {res:?}" ) ;
4501
4502
4503
4504
assert! ( a.policy_root_add ( id_b.clone ( ) ) .unwrap ( ) .applied) ;
4505
assert! ( a.policy_root_add ( id_b.clone ( ) ) .is_err ( ) ) ;
4506
assert! ( a.policy_set_threshold ( 3 ) .is_err ( ) ) ;
4507
assert! ( a.policy_set_threshold ( 2 ) .unwrap ( ) .applied) ;
4508
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | x | x.ok ) ) ;
767ac6cd Andy McCall 17 days ago
4509
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4510
b.pull ( " origin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4511
assert_eq! (
4512
b.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.root_threshold,
4513
2
4514
) ;
4515
let mut c = Repo:: init( & root.join ( " c" ) )
4516
.unwrap ( )
4517
.with_home ( root.join ( " home-c" ) ) ;
4518
let ( key_c, id_c) = c
4519
.create_identity ( " Cy" , " [email protected] " , IdentityScope:: User)
4520
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4521
let serial_before = a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial;
4522
let proposal = a.policy_grant ( id_c, None ) .unwrap ( ) ;
4523
assert! ( ! proposal.applied & & proposal.sigs.len ( ) = = 1 ) ;
4524
assert_eq! ( proposal.policy.serial, serial_before + 1 ) ;
75167672 Andy McCall 1 month ago
4525
assert_eq! (
4526
a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial,
4527
serial_before,
4528
" a proposal changes nothing yet"
4529
) ;
28fc23d7 Andy McCall 1 month ago ↶
4530
4531
let bytes = proposal.signed ( ) .encode ( ) ;
75167672 Andy McCall 1 month ago
4532
let signed = b
4533
.policy_sign ( SignedPolicy:: decode( & bytes) .unwrap ( ) )
4534
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4535
assert! ( signed.applied & & signed.sigs.len ( ) = = 2 ) ;
75167672 Andy McCall 1 month ago
4536
assert! ( b
4537
.current_policy ( )
4538
.unwrap ( )
4539
.unwrap ( )
4540
.1
4541
.policy
4542
.authorizes ( & key_c, " main" ) ) ;
4543
assert! (
4544
b.policy_sign ( signed.signed ( ) ) .is_err ( ) ,
4545
" a stale proposal is refused"
4546
) ;
28fc23d7 Andy McCall 1 month ago ↶
4547
75167672 Andy McCall 1 month ago
4548
assert! ( b
4549
.push ( " origin" , false )
4550
.unwrap ( )
4551
.iter ( )
4552
.any ( | x | x.name = = " policy" & & x.ok ) ) ;
767ac6cd Andy McCall 17 days ago
4553
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4554
a.pull ( " origin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4555
assert_eq! (
4556
a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial,
4557
serial_before + 1
4558
) ;
28fc23d7 Andy McCall 1 month ago ↶
4559
4560
assert! ( a.policy_revoke ( & key_a3) .is_err ( ) ) ;
75167672 Andy McCall 1 month ago
4561
let u = a
4562
.revoke_identity ( & key_c, " left the project" )
4563
.unwrap ( )
4564
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4565
assert! ( ! u.applied) ;
4566
assert! ( a.is_revoked ( & key_c) .unwrap ( ) ) ;
4567
assert! ( ! b.is_revoked ( & key_c) .unwrap ( ) ) ;
4568
let u = b.policy_sign ( u.signed ( ) ) .unwrap ( ) ;
4569
assert! ( u.applied & & u.policy.is_revoked ( & key_c) ) ;
4570
assert! ( b.is_revoked ( & key_c) .unwrap ( ) ) ;
4571
75167672 Andy McCall 1 month ago
4572
assert! (
4573
b.policy_revoke ( & key_a3) .is_err ( ) ,
4574
" would leave one root for a threshold of 2"
4575
) ;
28fc23d7 Andy McCall 1 month ago ↶
4576
4577
let key_c_own = c.config.author_key.unwrap ( ) ;
75167672 Andy McCall 1 month ago
4578
assert! ( c
4579
.revoke_identity ( & key_c_own, " lost laptop" )
4580
.unwrap ( )
4581
.is_none ( ) ) ;
28fc23d7 Andy McCall 1 month ago ↶
4582
assert! ( c.is_revoked ( & key_c_own) .unwrap ( ) ) ;
4583
assert_eq! ( key_c_own, key_c) ;
4584
}
4585
6e723b81 Andy McCall 1 month ago ↶
4586
# [ test ]
4587
fn renames_are_detected_and_tracked_as_moves ( ) {
4588
hermetic ( ) ;
4589
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4590
let root = dir.path ( ) ;
4591
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
4592
write (
4593
root,
4594
" src/old.rs" ,
4595
b " fn a() {}\n fn b() {}\n fn c() {}\n fn d() {}\n " ,
4596
) ;
6e723b81 Andy McCall 1 month ago ↶
4597
write ( root, " img.bin" , b " \x89 PNG\0 \0 \x01 \x02 " ) ;
4598
write ( root, " tiny.txt" , b " x\n " ) ;
4599
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4600
let node_old = repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) .files[ " src/old.rs" ] .node;
4601
let node_img = repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) .files[ " img.bin" ] .node;
4602
4603
4604
4605
fs:: remove_file( root.join ( " src/old.rs" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4606
write (
4607
root,
4608
" lib/new.rs" ,
4609
b " fn a() {}\n fn b() {}\n fn c() {}\n fn e() {}\n " ,
4610
) ;
6e723b81 Andy McCall 1 month ago ↶
4611
fs:: remove_file( root.join ( " img.bin" ) ) .unwrap ( ) ;
4612
write ( root, " assets/img.bin" , b " \x89 PNG\0 \0 \x01 \x02 " ) ;
4613
fs:: remove_file( root.join ( " tiny.txt" ) ) .unwrap ( ) ;
4614
write ( root, " other.txt" , b " completely different\n " ) ;
4615
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4616
let statuses: Vec< ( & str , ChangeStatus) > = snap
4617
.statuses
4618
.iter ( )
4619
.map ( | s | ( s.path.as_str ( ) , s.status) )
4620
.collect ( ) ;
6e723b81 Andy McCall 1 month ago ↶
4621
assert_eq! (
4622
statuses,
75167672 Andy McCall 1 month ago
4623
vec! [
4624
( " assets/img.bin" , ChangeStatus:: Renamed) ,
4625
( " lib/new.rs" , ChangeStatus:: Renamed) ,
4626
( " other.txt" , ChangeStatus:: Added) ,
4627
( " src" , ChangeStatus:: Removed) ,
4628
( " tiny.txt" , ChangeStatus:: Removed)
4629
]
6e723b81 Andy McCall 1 month ago ↶
4630
) ;
4631
let h2 = repo.commit ( " move things" , & [ ] ) .unwrap ( ) ;
4632
let c = repo.store ( ) .get_change ( & h2) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4633
let moves: Vec< _ > = c
4634
.tree_ops
4635
.iter ( )
4636
.filter ( | op | matches! ( op, TreeOp:: Move { .. } ) )
4637
.collect ( ) ;
6e723b81 Andy McCall 1 month ago ↶
4638
assert_eq! ( moves.len ( ) , 2 , " {:?}" , c.tree_ops) ;
75167672 Andy McCall 1 month ago
4639
assert! ( c.tree_ops.iter ( ) .any (
4640
| op | matches! ( op, TreeOp:: Move { node, name, .. } if * node = = node_old & & name = = " new.rs" )
4641
) ) ;
4642
assert! ( c.tree_ops.iter ( ) .any (
4643
| op | matches! ( op, TreeOp:: Move { node, name, .. } if * node = = node_img & & name = = " img.bin" )
4644
) ) ;
6e723b81 Andy McCall 1 month ago ↶
4645
let snap = repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
4646
assert_eq! (
4647
snap.files[ " lib/new.rs" ] .node, node_old,
4648
" the node survives the rename"
4649
) ;
4650
assert! (
4651
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 " )
4652
) ;
6e723b81 Andy McCall 1 month ago ↶
4653
assert! ( ! snap.files.contains_key ( " src/old.rs" ) & & ! snap.files.contains_key ( " src" ) ) ;
75167672 Andy McCall 1 month ago
4654
assert! ( render_change (
4655
& c,
4656
& repo.load_state ( & [ h1] ) .unwrap ( ) ,
4657
& repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( )
4658
)
0912a033 Andy McCall 1 month ago ↶
4659
.contains ( " R src/old.rs -> lib/new.rs" ) ) ;
6e723b81 Andy McCall 1 month ago ↶
4660
4661
let other = tempfile:: tempdir( ) .unwrap ( ) ;
4662
let clone = Repo:: clone( & root.to_string_lossy ( ) , other.path ( ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4663
assert_eq! (
4664
fs:: read( other.path ( ) .join ( " lib/new.rs" ) ) .unwrap ( ) ,
4665
b " fn a() {}\n fn b() {}\n fn c() {}\n fn e() {}\n "
4666
) ;
6e723b81 Andy McCall 1 month ago ↶
4667
assert! ( ! other.path ( ) .join ( " src" ) .exists ( ) ) ;
4668
assert! ( ! clone.snapshot ( " status" ) .unwrap ( ) .changed) ;
4669
4670
assert_eq! ( similarity ( b " a\n b\n " , b " a\n b\n " ) , 100 ) ;
4671
assert_eq! ( similarity ( b " a\n b\n c\n d\n " , b " a\n b\n x\n y\n " ) , 50 ) ;
4672
assert_eq! ( similarity ( b " hello\n " , b " world\n " ) , 0 ) ;
4673
assert_eq! ( similarity ( b " " , b " x\n " ) , 0 ) ;
4674
}
4675
30ea9c1e Andy McCall 1 month ago ↶
4676
# [ test ]
4677
fn git_fetch_and_push_round_trip ( ) {
4678
hermetic ( ) ;
75167672 Andy McCall 1 month ago
4679
if std:: process:: Command:: new( " git" )
4680
.arg ( " --version" )
4681
.output ( )
4682
.is_err ( )
4683
{
30ea9c1e Andy McCall 1 month ago ↶
4684
eprintln! ( " git not installed; skipping" ) ;
4685
return ;
4686
}
4687
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4688
let root = dir.path ( ) ;
4689
let git_in = | d : & Path, args : & [& str ]| -> String {
4690
let out = std:: process:: Command:: new( " git" )
4691
.arg ( " -C" )
4692
.arg ( d)
75167672 Andy McCall 1 month ago
4693
.args ( [
4694
" -c" ,
4695
" user.name=Grace" ,
4696
" -c" ,
4697
" [email protected] " ,
4698
" -c" ,
4699
" commit.gpgsign=false" ,
4700
] )
30ea9c1e Andy McCall 1 month ago ↶
4701
.args ( args)
4702
.output ( )
4703
.unwrap ( ) ;
75167672 Andy McCall 1 month ago
4704
assert! (
4705
out.status.success ( ) ,
4706
" git {args:?}: {}" ,
4707
String :: from_utf8_lossy( & out.stderr)
4708
) ;
30ea9c1e Andy McCall 1 month ago ↶
4709
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
4710
} ;
4711
4712
let remote = root.join ( " remote.git" ) ;
4713
fs:: create_dir_all( & remote) .unwrap ( ) ;
4714
git_in ( & remote, & [ " init" , " -q" , " --bare" , " -b" , " main" ] ) ;
4715
let work = root.join ( " work" ) ;
75167672 Andy McCall 1 month ago
4716
git_in (
4717
root,
4718
& [
4719
" clone" ,
4720
" -q" ,
4721
remote.to_str ( ) .unwrap ( ) ,
4722
work.to_str ( ) .unwrap ( ) ,
4723
] ,
4724
) ;
30ea9c1e Andy McCall 1 month ago ↶
4725
git_in ( & work, & [ " checkout" , " -q" , " -b" , " main" ] ) ;
4726
write ( & work, " README.md" , b " # Project\n " ) ;
4727
write ( & work, " src/lib.rs" , b " pub fn a() {}\n " ) ;
4728
git_in ( & work, & [ " add" , " ." ] ) ;
4729
git_in ( & work, & [ " commit" , " -q" , " -m" , " initial" ] ) ;
4730
write ( & work, " src/lib.rs" , b " pub fn a() {}\n pub fn b() {}\n " ) ;
4731
git_in ( & work, & [ " commit" , " -q" , " -am" , " second" ] ) ;
4732
git_in ( & work, & [ " push" , " -q" , " origin" , " main" ] ) ;
4733
let tip1 = git_in ( & work, & [ " rev-parse" , " HEAD" ] ) ;
4734
4735
4736
let o = root.join ( " oxy" ) ;
4737
let mut repo = Repo:: init( & o) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
4738
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
4739
.unwrap ( ) ;
30ea9c1e Andy McCall 1 month ago ↶
4740
let url = remote.to_string_lossy ( ) .to_string ( ) ;
4741
let r = repo.git_fetch ( & url, " main" ) .unwrap ( ) ;
4742
assert_eq! ( ( r.commits, r.heads) , ( 2 , 1 ) ) ;
75167672 Andy McCall 1 month ago
4743
assert_eq! (
4744
fs:: read( o.join ( " src/lib.rs" ) ) .unwrap ( ) ,
4745
b " pub fn a() {}\n pub fn b() {}\n "
4746
) ;
30ea9c1e Andy McCall 1 month ago ↶
4747
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
75167672 Andy McCall 1 month ago
4748
assert_eq! (
4749
repo.git_fetch ( & url, " main" ) .unwrap ( ) .commits,
4750
0 ,
4751
" nothing new"
4752
) ;
30ea9c1e Andy McCall 1 month ago ↶
4753
4754
75167672 Andy McCall 1 month ago
4755
write (
4756
& o,
4757
" src/lib.rs" ,
4758
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n " ,
4759
) ;
30ea9c1e Andy McCall 1 month ago ↶
4760
write ( & o, " NOTES.md" , b " from oxy\n " ) ;
4761
let h_oxy = repo.commit ( " add c and notes" , & [ ] ) .unwrap ( ) ;
4762
let r = repo.git_push ( & url, " main" ) .unwrap ( ) ;
4763
assert_eq! ( r.exported, 1 , " {r:?}" ) ;
4764
git_in ( & work, & [ " pull" , " -q" , " --ff-only" , " origin" , " main" ] ) ;
75167672 Andy McCall 1 month ago
4765
assert_eq! (
4766
git_in ( & work, & [ " log" , " --format=%s" , " main" ] ) ,
4767
" add c and notes\n second\n initial"
4768
) ;
30ea9c1e Andy McCall 1 month ago ↶
4769
assert_eq! ( git_in ( & work, & [ " rev-parse" , " HEAD~1" ] ) , tip1) ;
4770
assert_eq! ( fs:: read( work.join ( " NOTES.md" ) ) .unwrap ( ) , b " from oxy\n " ) ;
4771
assert_eq! ( git_in ( & work, & [ " log" , " -1" , " --format=%an" , " main" ] ) , " Ada" ) ;
75167672 Andy McCall 1 month ago
4772
assert! ( git_in ( & work, & [ " log" , " -1" , " --format=%B" , " main" ] )
4773
.contains ( & format! ( " Oxy-Hash: {} " , h_oxy.to_hex ( ) ) ) ) ;
4774
assert_eq! (
4775
repo.git_push ( & url, " main" ) .unwrap ( ) .exported,
4776
0 ,
4777
" already pushed"
4778
) ;
30ea9c1e Andy McCall 1 month ago ↶
4779
4780
4781
4782
write ( & work, " README.md" , b " # Project\n \n More.\n " ) ;
4783
git_in ( & work, & [ " commit" , " -q" , " -am" , " readme from git" ] ) ;
4784
git_in ( & work, & [ " push" , " -q" , " origin" , " main" ] ) ;
4785
let tip2 = git_in ( & work, & [ " rev-parse" , " HEAD" ] ) ;
75167672 Andy McCall 1 month ago
4786
assert! ( repo
4787
.git_push ( & url, " main" )
4788
.unwrap_err ( )
4789
.to_string ( )
4790
.contains ( " fetch" ) ) ;
30ea9c1e Andy McCall 1 month ago ↶
4791
let r = repo.git_fetch ( & url, " main" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4792
assert_eq! (
4793
( r.commits, r.heads) ,
4794
( 1 , 2 ) ,
4795
" the Git change and the oxy change commute"
4796
) ;
4797
assert_eq! (
4798
fs:: read( o.join ( " README.md" ) ) .unwrap ( ) ,
4799
b " # Project\n \n More.\n "
4800
) ;
30ea9c1e Andy McCall 1 month ago ↶
4801
assert_eq! ( fs:: read( o.join ( " NOTES.md" ) ) .unwrap ( ) , b " from oxy\n " ) ;
4802
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4803
4804
write ( & o, " NOTES.md" , b " from oxy\n again\n " ) ;
4805
repo.commit ( " notes again" , & [ ] ) .unwrap ( ) ;
4806
assert_eq! ( repo.git_push ( & url, " main" ) .unwrap ( ) .exported, 1 ) ;
4807
git_in ( & work, & [ " pull" , " -q" , " --ff-only" , " origin" , " main" ] ) ;
4808
assert_eq! ( git_in ( & work, & [ " rev-parse" , " HEAD~1" ] ) , tip2) ;
75167672 Andy McCall 1 month ago
4809
assert_eq! (
4810
fs:: read( work.join ( " NOTES.md" ) ) .unwrap ( ) ,
4811
b " from oxy\n again\n "
4812
) ;
4813
assert_eq! (
4814
git_in ( & work, & [ " log" , " --format=%s" , " main" ] )
4815
.lines ( )
4816
.count ( ) ,
4817
5
4818
) ;
30ea9c1e Andy McCall 1 month ago ↶
4819
}
4820
2b872d07 Andy McCall 1 month ago ↶
4821
4822
4823
# [ test ]
4824
# [ ignore ]
4825
fn bench_one_gibibyte_file ( ) {
4826
hermetic ( ) ;
4827
4828
let dir = match std:: env:: var_os( " OXY_BENCH_DIR" ) {
4829
Some ( d) => tempfile:: tempdir_in( d) .unwrap ( ) ,
4830
None => tempfile:: tempdir( ) .unwrap ( ) ,
4831
} ;
4832
let root = dir.path ( ) ;
75167672 Andy McCall 1 month ago
4833
let repo = Repo:: init( & root.join ( " r" ) )
4834
.unwrap ( )
4835
.with_home ( root.join ( " home" ) ) ;
2b872d07 Andy McCall 1 month ago ↶
4836
let size = 1 usize < < 30 ;
4837
let mut data = vec! [ 0 u8 ; size] ;
4838
let mut x = 0x9e3779b97f4a7c15 u64 ;
4839
for chunk in data.chunks_mut ( 8 ) {
4840
x ^ = x < < 13 ;
4841
x ^ = x > > 7 ;
4842
x ^ = x < < 17 ;
4843
let b = x.to_le_bytes ( ) ;
4844
chunk.copy_from_slice ( & b[ .. chunk.len ( ) ] ) ;
4845
}
4846
let t = std:: time:: Instant:: now( ) ;
4847
write ( & root.join ( " r" ) , " big.bin" , & data) ;
4848
eprintln! ( " write 1 GiB to disk: {:.2?}" , t.elapsed ( ) ) ;
4849
let t = std:: time:: Instant:: now( ) ;
4850
let h = repo.commit ( " big" , & [ ] ) .unwrap ( ) ;
4851
eprintln! ( " commit (chunk + hash + store): {:.2?}" , t.elapsed ( ) ) ;
4852
let t = std:: time:: Instant:: now( ) ;
4853
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4854
eprintln! ( " status on a clean 1 GiB working copy: {:.2?}" , t.elapsed ( ) ) ;
4855
let t = std:: time:: Instant:: now( ) ;
4856
let objects = repo.store ( ) .list ( ) .unwrap ( ) .len ( ) ;
4857
eprintln! ( " objects stored: {objects} ({:.2?} to list)" , t.elapsed ( ) ) ;
4858
let t = std:: time:: Instant:: now( ) ;
4859
let g = repo.gc ( true ) .unwrap ( ) ;
4860
eprintln! ( " gc (pack): {:.2?} {g:?}" , t.elapsed ( ) ) ;
4861
let t = std:: time:: Instant:: now( ) ;
4862
let clone = Repo:: clone( & root.join ( " r" ) .to_string_lossy ( ) , & root.join ( " c" ) ) .unwrap ( ) ;
4863
eprintln! ( " clone (fetch + checkout): {:.2?}" , t.elapsed ( ) ) ;
4864
let t = std:: time:: Instant:: now( ) ;
4865
let back = fs:: read( root.join ( " c/big.bin" ) ) .unwrap ( ) ;
4866
assert_eq! ( blake3:: hash( & back) , blake3:: hash( & data) ) ;
4867
eprintln! ( " read back and verify: {:.2?}" , t.elapsed ( ) ) ;
4868
assert_eq! ( clone.bookmark_heads ( ) .unwrap ( ) , vec! [ h] ) ;
4869
let store_bytes: u64 = walkdir_size ( & root.join ( " r/.oxy/objects" ) ) ;
75167672 Andy McCall 1 month ago
4870
eprintln! (
4871
" store size: {:.1} MiB for 1 GiB of data" ,
4872
store_bytes as f64 / ( 1024. 0 * 1024. 0 )
4873
) ;
2b872d07 Andy McCall 1 month ago ↶
4874
}
4875
4876
fn walkdir_size ( p : & Path) -> u64 {
4877
let mut total = 0 ;
4878
if let Ok ( rd) = fs:: read_dir( p) {
4879
for e in rd.flatten ( ) {
4880
let path = e.path ( ) ;
4881
if path.is_dir ( ) {
4882
total + = walkdir_size ( & path) ;
4883
} else if let Ok ( m) = fs:: metadata( & path) {
4884
total + = m.len ( ) ;
4885
}
4886
}
4887
}
4888
total
4889
}
4890
b7f102fc Andy McCall 1 month ago ↶
4891
4892
4893
# [ cfg ( unix ) ]
4894
# [ test ]
4895
fn stat_cache_skips_reading_unchanged_opaque_files ( ) {
4896
use std:: os:: unix:: fs:: PermissionsExt;
4897
hermetic ( ) ;
4898
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4899
let root = dir.path ( ) ;
4900
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4901
let mut big = vec! [ 0 u8 ; 300 * 1024 ] ;
4902
for ( i, b) in big.iter_mut ( ) .enumerate ( ) {
4903
* b = ( i as u32 ) .wrapping_mul ( 2654435761 ) .to_le_bytes ( ) [ 1 ] ;
4904
}
4905
big[ 0 ] = 0 ;
4906
write ( root, " big.bin" , & big) ;
4907
write ( root, " a.txt" , b " text\n " ) ;
4908
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4909
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4910
assert! (
4911
m.entries[ " big.bin" ] .blob.is_some ( ) ,
4912
" opaque files record their blob"
4913
) ;
b7f102fc Andy McCall 1 month ago ↶
4914
assert! ( m.entries[ " a.txt" ] .blob.is_none ( ) ) ;
4915
let blob = m.entries[ " big.bin" ] .blob.unwrap ( ) ;
4916
assert! ( repo.store ( ) .has ( & blob) ) ;
4917
4918
4919
4920
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
75167672 Andy McCall 1 month ago
4921
fs:: File:: options( )
4922
.write ( true )
4923
.open ( root.join ( " big.bin" ) )
4924
.unwrap ( )
4925
.set_modified ( old)
4926
.unwrap ( ) ;
b7f102fc Andy McCall 1 month ago ↶
4927
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4928
4929
fs:: set_permissions( root.join ( " big.bin" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
4930
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4931
assert! (
4932
! snap.changed & & snap.statuses.is_empty ( ) ,
4933
" {:?}" ,
4934
snap.statuses
4935
) ;
b7f102fc Andy McCall 1 month ago ↶
4936
fs:: set_permissions( root.join ( " big.bin" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
4937
4938
let mut big2 = big.clone ( ) ;
4939
big2[ 10 ] ^ = 0xff ;
4940
write ( root, " big.bin" , & big2) ;
4941
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4942
assert_eq! (
4943
snap.statuses
4944
.iter ( )
4945
.map ( | s | ( s.path.as_str ( ) , s.status) )
4946
.collect:: < Vec< _ > > ( ) ,
4947
vec! [ ( " big.bin" , ChangeStatus:: Modified) ]
4948
) ;
b7f102fc Andy McCall 1 month ago ↶
4949
let h2 = repo.commit ( " edit" , & [ ] ) .unwrap ( ) ;
4950
let files = repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
4951
assert_eq! (
4952
files[ " big.bin" ] .content,
4953
FileContent:: Opaque {
4954
blobs: vec! [ Repo:: blob_ref( & big2) ]
4955
}
4956
) ;
b7f102fc Andy McCall 1 month ago ↶
4957
4958
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
4959
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
4960
assert_eq! ( m.entries[ " big.bin" ] .blob, Some ( Repo:: blob_ref( & big) ) ) ;
4961
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4962
}
4963
50a046e4 Andy McCall 1 month ago ↶
4964
# [ test ]
4965
fn status_json_lists_conflicts_with_contributors ( ) {
4966
hermetic ( ) ;
4967
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4968
let root = dir.path ( ) ;
4969
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4970
write ( root, " a.txt" , b " one\n " ) ;
4971
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4972
write ( root, " a.txt" , b " one\n left\n " ) ;
4973
let ha = repo.commit ( " left" , & [ ] ) .unwrap ( ) ;
4974
repo.checkout ( & [ h1] , true , " checkout" ) .unwrap ( ) ;
4975
write ( root, " a.txt" , b " one\n right\n " ) ;
4976
let hb = repo.commit ( " right" , & [ ] ) .unwrap ( ) ;
4977
repo.checkout ( & [ ha, hb] , true , " checkout" ) .unwrap ( ) ;
4978
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4979
assert! ( snap.base_snapshot.is_conflicted ( ) ) ;
4980
let json = repo.status_json ( & snap) .unwrap ( ) ;
4981
let ida = repo.store ( ) .get_change ( & ha) .unwrap ( ) .change_id.to_hex ( ) ;
4982
let idb = repo.store ( ) .get_change ( & hb) .unwrap ( ) .change_id.to_hex ( ) ;
4983
assert! ( json.contains ( " \" conflicted\" :true" ) , " {json}" ) ;
75167672 Andy McCall 1 month ago
4984
assert! (
4985
json.contains ( " \" kind\" :\" text\" ,\" path\" :\" a.txt\" " ) ,
4986
" {json}"
4987
) ;
50a046e4 Andy McCall 1 month ago ↶
4988
assert! ( json.contains ( & ida) & & json.contains ( & idb) , " {json}" ) ;
4989
assert! ( json.contains ( " \" resolved\" :false" ) , " {json}" ) ;
4990
assert! ( json.starts_with ( ' {' ) & & json.ends_with ( ' }' ) ) ;
4991
4992
write ( root, " a.txt" , b " one\n left\n right\n " ) ;
4993
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4994
let json = repo.status_json ( & snap) .unwrap ( ) ;
4995
assert! ( json.contains ( " \" resolved\" :true" ) , " {json}" ) ;
4996
repo.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
4997
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4998
let json = repo.status_json ( & snap) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4999
assert! (
5000
json.contains ( " \" clean\" :true,\" conflicted\" :false,\" paths\" :[],\" conflicts\" :[]" ) ,
5001
" {json}"
5002
) ;
50a046e4 Andy McCall 1 month ago ↶
5003
}
5004
9ac1fbe2 Andy McCall 1 month ago ↶
5005
# [ test ]
5006
fn ignore_rules_do_not_untrack_files ( ) {
5007
hermetic ( ) ;
5008
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5009
let root = dir.path ( ) ;
5010
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5011
write ( root, " src.c" , b " int x;\n " ) ;
5012
write ( root, " build/out.o" , b " \0 obj" ) ;
5013
write ( root, " sub/keep.log" , b " keep\n " ) ;
5014
write ( root, " sub/junk.log" , b " junk\n " ) ;
5015
write ( root, " .oxyignore" , b " /build\n *.log\n !sub/keep.log\n " ) ;
5016
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
5017
let added: Vec< & str > = snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect ( ) ;
75167672 Andy McCall 1 month ago
5018
assert_eq! (
5019
added,
5020
vec! [ " .oxyignore" , " src.c" , " sub/keep.log" ] ,
5021
" anchored, glob, and negated rules"
5022
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
5023
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
5024
5025
write ( root, " .oxyignore" , b " /build\n *.log\n !sub/keep.log\n src.c\n " ) ;
5026
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5027
assert_eq! (
5028
snap.statuses
5029
.iter ( )
5030
.map ( | s | s.path.as_str ( ) )
5031
.collect:: < Vec< _ > > ( ) ,
5032
vec! [ " .oxyignore" ]
5033
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
5034
let h2 = repo.commit ( " ignore src.c" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5035
assert! ( repo
5036
.load_state ( & [ h2] )
5037
.unwrap ( )
5038
.materialize ( )
5039
.files
5040
.contains_key ( " src.c" ) ) ;
9ac1fbe2 Andy McCall 1 month ago ↶
5041
5042
assert_eq! ( repo.quick_dirty ( ) .unwrap ( ) , Some ( false ) ) ;
5043
write ( root, " src.c" , b " int x, y;\n " ) ;
5044
assert_eq! ( repo.quick_dirty ( ) .unwrap ( ) , Some ( true ) ) ;
5045
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5046
assert_eq! (
5047
snap.statuses
5048
.iter ( )
5049
.map ( | s | ( s.path.as_str ( ) , s.status) )
5050
.collect:: < Vec< _ > > ( ) ,
5051
vec! [ ( " src.c" , ChangeStatus:: Modified) ]
5052
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
5053
let h3 = repo.commit ( " edit src.c" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5054
assert! (
5055
matches! ( & repo.load_state ( & [ h3] ) .unwrap ( ) .materialize ( ) .files[ " src.c" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " int x, y;\n " )
5056
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
5057
5058
fs:: remove_file( root.join ( " src.c" ) ) .unwrap ( ) ;
5059
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5060
assert_eq! (
5061
snap.statuses
5062
.iter ( )
5063
.map ( | s | ( s.path.as_str ( ) , s.status) )
5064
.collect:: < Vec< _ > > ( ) ,
5065
vec! [ ( " src.c" , ChangeStatus:: Removed) ]
5066
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
5067
repo.checkout ( & [ h3] , true , " checkout" ) .unwrap ( ) ;
5068
assert_eq! ( fs:: read( root.join ( " src.c" ) ) .unwrap ( ) , b " int x, y;\n " ) ;
5069
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
5070
let _ = h1;
5071
}
5072
b837d2b5 Andy McCall 1 month ago ↶
5073
# [ test ]
5074
fn split_works_on_non_heads_by_node ( ) {
5075
hermetic ( ) ;
5076
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5077
let root = dir.path ( ) ;
5078
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5079
write ( root, " a.txt" , b " one\n " ) ;
5080
write ( root, " junk/x.lock" , b " lock\n " ) ;
5081
let h1 = repo.commit ( " work plus junk" , & [ ] ) .unwrap ( ) ;
5082
write ( root, " a.txt" , b " one\n two\n " ) ;
5083
let h2 = repo.commit ( " more work" , & [ ] ) .unwrap ( ) ;
5084
assert_eq! ( repo.store ( ) .get_change ( & h2) .unwrap ( ) .deps, vec! [ h1] ) ;
5085
let id1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) .change_id;
5086
5087
let ( hj, h1b) = repo.split ( & h1, & [ " junk" .into ( ) ] ) .unwrap ( ) ;
5088
assert_ne! ( h1b, h1) ;
75167672 Andy McCall 1 month ago
5089
assert_eq! (
5090
repo.store ( ) .get_change ( & h1b) .unwrap ( ) .change_id,
5091
id1,
5092
" the rest keeps the ChangeId"
5093
) ;
b837d2b5 Andy McCall 1 month ago ↶
5094
let junk = repo.store ( ) .get_change ( & hj) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5095
assert! ( junk.tree_ops.iter ( ) .all (
5096
| op | matches! ( op, TreeOp:: AddNode { name, .. } if name = = " junk" | | name = = " x.lock" )
5097
) ) ;
b837d2b5 Andy McCall 1 month ago ↶
5098
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5099
assert! (
5100
heads.contains ( & hj) ,
5101
" the split-off part is a head of its own"
5102
) ;
b837d2b5 Andy McCall 1 month ago ↶
5103
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
5104
assert! (
5105
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n " ) ,
5106
" the dependent was rewritten onto the new hash"
5107
) ;
b837d2b5 Andy McCall 1 month ago ↶
5108
assert! ( snap.files.contains_key ( " junk/x.lock" ) ) ;
75167672 Andy McCall 1 month ago
5109
let h2b = heads
5110
.iter ( )
5111
.find ( | h | repo.store ( ) .get_change ( h) .unwrap ( ) .message = = " more work" )
5112
.copied ( )
5113
.unwrap ( ) ;
b837d2b5 Andy McCall 1 month ago ↶
5114
assert_eq! ( repo.store ( ) .get_change ( & h2b) .unwrap ( ) .deps, vec! [ h1b] ) ;
5115
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
5116
5117
repo.abandon ( & hj) .unwrap ( ) ;
5118
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5119
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
5120
assert! ( ! snap.files.contains_key ( " junk/x.lock" ) & & ! snap.files.contains_key ( " junk" ) ) ;
5121
assert! ( snap.files.contains_key ( " a.txt" ) ) ;
5122
5123
write ( root, " junk/y.lock" , b " y\n " ) ;
5124
let hb = repo.commit ( " junk base" , & [ ] ) .unwrap ( ) ;
5125
write ( root, " junk/y.lock" , b " yy\n " ) ;
5126
write ( root, " b.txt" , b " b\n " ) ;
5127
repo.commit ( " touches junk" , & [ ] ) .unwrap ( ) ;
5128
let err = repo.split ( & hb, & [ " junk" .into ( ) ] ) .unwrap_err ( ) .to_string ( ) ;
5129
assert! ( err.contains ( " depends on the split-off paths" ) , " {err}" ) ;
5130
}
5131
7b9809e6 Andy McCall 1 month ago ↶
5132
# [ test ]
5133
fn bookmarks_switch_merge_and_push ( ) {
5134
hermetic ( ) ;
5135
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5136
let root = dir.path ( ) ;
5137
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
5138
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
5139
let mut repo = Repo:: clone( & url, & root.join ( " r" ) )
5140
.unwrap ( )
5141
.with_home ( root.join ( " home" ) ) ;
7b9809e6 Andy McCall 1 month ago ↶
5142
let r = root.join ( " r" ) ;
5143
write ( & r, " a.txt" , b " a\n " ) ;
5144
let h_a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
5145
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 17 days ago
5146
origin.refresh ( ) .unwrap ( ) ;
7b9809e6 Andy McCall 1 month ago ↶
5147
5148
5149
assert! ( repo.bookmark_create ( " bad name.." , None ) .is_err ( ) ) ;
5150
assert! ( repo.bookmark_create ( " -x" , None ) .is_err ( ) ) ;
5151
assert_eq! ( repo.bookmark_create ( " feature" , None ) .unwrap ( ) , vec! [ h_a] ) ;
5152
assert! ( repo.bookmark_create ( " feature" , None ) .is_err ( ) , " exists" ) ;
5153
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
5154
assert_eq! ( repo.config.bookmark, " feature" ) ;
5155
write ( & r, " a.txt" , b " a\n b\n " ) ;
5156
write ( & r, " b.txt" , b " b\n " ) ;
5157
let h_b = repo.commit ( " b on feature" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5158
assert_eq! (
5159
repo.bookmark_heads ( ) .unwrap ( ) ,
5160
vec! [ h_b] ,
5161
" b edits a.txt, so it depends on a"
5162
) ;
5163
assert_eq! (
5164
repo.resolve_heads ( " main" ) .unwrap ( ) ,
5165
vec! [ h_a] ,
5166
" main is untouched"
5167
) ;
5168
assert_eq! (
5169
repo.resolve_change ( " feature" ) .unwrap ( ) ,
5170
h_b,
5171
" a one-head bookmark names its change"
5172
) ;
7b9809e6 Andy McCall 1 month ago ↶
5173
5174
let reopened = Repo:: open( & r) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5175
assert_eq! ( reopened.config.bookmark, " feature" ) ;
5176
5177
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
5178
assert! ( ! r.join ( " b.txt" ) .exists ( ) ) ;
5179
assert_eq! ( fs:: read( r.join ( " a.txt" ) ) .unwrap ( ) , b " a\n " ) ;
5180
write ( & r, " c.txt" , b " c\n " ) ;
5181
let h_c = repo.commit ( " c on main" , & [ ] ) .unwrap ( ) ;
5182
let list = repo.bookmarks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5183
assert_eq! (
5184
list.iter ( )
5185
.map ( | b | ( b.name.as_str ( ) , b.current) )
5186
.collect:: < Vec< _ > > ( ) ,
5187
vec! [ ( " feature" , false ) , ( " main" , true ) ]
5188
) ;
5189
assert_eq! (
5190
list.iter ( ) .find ( | b | b.name = = " main" ) .unwrap ( ) .ahead,
5191
Some ( 1 ) ,
5192
" c is not on origin yet"
5193
) ;
7b9809e6 Andy McCall 1 month ago ↶
5194
5195
let m = repo.merge ( " feature" ) .unwrap ( ) ;
5196
assert! ( m.changed & & m.checked_out) ;
5197
let mut want = vec! [ h_b, h_c] ;
5198
want.sort ( ) ;
5199
assert_eq! ( m.heads, want) ;
5200
assert_eq! ( fs:: read( r.join ( " b.txt" ) ) .unwrap ( ) , b " b\n " ) ;
5201
assert! ( ! repo.merge ( " feature" ) .unwrap ( ) .changed, " already merged" ) ;
5202
assert! ( repo.merge ( " main" ) .is_err ( ) ) ;
5203
5204
assert! ( repo.push_bookmark ( " origin" , " feature" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 17 days ago
5205
origin.refresh ( ) .unwrap ( ) ;
7b9809e6 Andy McCall 1 month ago ↶
5206
assert_eq! ( origin.resolve_heads ( " feature" ) .unwrap ( ) , vec! [ h_b] ) ;
5207
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 17 days ago
5208
origin.refresh ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5209
assert_eq! (
5210
repo.bookmarks ( )
5211
.unwrap ( )
5212
.iter ( )
5213
.find ( | b | b.name = = " main" )
5214
.unwrap ( )
5215
.ahead,
5216
Some ( 0 )
5217
) ;
7b9809e6 Andy McCall 1 month ago ↶
5218
5219
let ws = repo.workspace_add ( & root.join ( " ws" ) , None ) .unwrap ( ) ;
5220
assert_eq! ( ws.config.bookmark, " main" ) ;
5221
let mut ws = ws;
5222
ws.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5223
assert_eq! (
5224
Repo:: open( & r) .unwrap ( ) .config.bookmark,
5225
" main" ,
5226
" the first workspace is unaffected"
5227
) ;
5228
assert! (
5229
repo.bookmark_delete ( " feature" ) .is_err ( ) ,
5230
" current bookmark of another workspace"
5231
) ;
7b9809e6 Andy McCall 1 month ago ↶
5232
ws.bookmark_switch ( " main" , false ) .unwrap ( ) ;
5233
5234
assert_eq! ( repo.bookmark_set ( " feature" , & [ h_a] ) .unwrap ( ) , vec! [ h_a] ) ;
5235
assert! ( repo.bookmark_delete ( " main" ) .is_err ( ) , " current here" ) ;
5236
repo.bookmark_delete ( " feature" ) .unwrap ( ) ;
5237
assert! ( repo.resolve_heads ( " feature" ) .is_err ( ) ) ;
5238
assert! ( repo.bookmark_set ( " feature" , & [ h_a] ) .is_err ( ) ) ;
75167672 Andy McCall 1 month ago
5239
assert! (
5240
repo.checkout ( & [ h_a] , false , " checkout" ) .is_ok ( ) ,
5241
" changes of a deleted bookmark remain"
5242
) ;
7b9809e6 Andy McCall 1 month ago ↶
5243
}
5244
4dee277c Andy McCall 1 month ago ↶
5245
# [ test ]
5246
fn oxygen_subrepos_pin_follow_and_update ( ) {
5247
hermetic ( ) ;
5248
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5249
let root = dir.path ( ) ;
5250
5251
let lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
5252
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
5253
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
5254
.unwrap ( )
5255
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
5256
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
5257
let l1 = lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
5258
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
5259
let _ = lib_origin;
5260
5261
5262
let app_origin = Repo:: init( & root.join ( " app-origin" ) ) .unwrap ( ) ;
5263
let app_url = root.join ( " app-origin" ) .to_string_lossy ( ) .to_string ( ) ;
5264
let _ = app_origin;
5265
let app_dir = root.join ( " app" ) ;
75167672 Andy McCall 1 month ago
5266
let app = Repo:: clone( & app_url, & app_dir)
5267
.unwrap ( )
5268
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
5269
write ( & app_dir, " main.txt" , b " app\n " ) ;
5270
let pin = app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
5271
assert! ( matches! ( & pin, crate :: subrepo:: Pin:: Oxy { heads, .. } if heads = = & vec! [ l1] ) ) ;
5272
assert! ( app_dir.join ( " .oxymodules" ) .exists ( ) & & app_dir.join ( " libs/lib/.oxy" ) .is_dir ( ) ) ;
5273
let snap = app.snapshot ( " status" ) .unwrap ( ) ;
5274
let paths: Vec< & str > = snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect ( ) ;
75167672 Andy McCall 1 month ago
5275
assert_eq! (
5276
paths,
5277
vec! [ " .oxymodules" , " libs/lib" , " main.txt" ] ,
5278
" the nested files are not walked"
5279
) ;
4dee277c Andy McCall 1 month ago ↶
5280
let a1 = app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
5281
let files = app.load_state ( & [ a1] ) .unwrap ( ) .materialize ( ) .files;
5282
assert_eq! ( files[ " libs/lib" ] .kind, NodeKind:: Subrepo) ;
5283
assert! ( ! files.contains_key ( " libs/lib/lib.txt" ) ) ;
5284
assert! ( ! app.snapshot ( " status" ) .unwrap ( ) .changed) ;
5285
let subs = app.subrepos ( ) .unwrap ( ) ;
5286
assert_eq! ( subs.len ( ) , 1 ) ;
5287
assert_eq! ( subs[ 0 ] .actual.as_ref ( ) , Some ( & subs[ 0 ] .pin) ) ;
5288
5289
75167672 Andy McCall 1 month ago
5290
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) )
5291
.unwrap ( )
5292
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
5293
write ( & app_dir.join ( " libs/lib" ) , " lib.txt" , b " v2\n " ) ;
5294
let l2 = nested.commit ( " lib v2" , & [ ] ) .unwrap ( ) ;
5295
assert! ( nested.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
5296
let snap = app.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5297
assert_eq! (
5298
snap.statuses
5299
.iter ( )
5300
.map ( | s | ( s.path.as_str ( ) , s.status) )
5301
.collect:: < Vec< _ > > ( ) ,
5302
vec! [ ( " libs/lib" , ChangeStatus:: Modified) ]
5303
) ;
4dee277c Andy McCall 1 month ago ↶
5304
let a2 = app.commit ( " bump lib" , & [ ] ) .unwrap ( ) ;
1a3b6203 Andy McCall 1 month ago ↶
5305
5306
4dee277c Andy McCall 1 month ago ↶
5307
let r = app.checkout ( & [ a1] , false , " checkout" ) .unwrap ( ) ;
1a3b6203 Andy McCall 1 month ago ↶
5308
assert! (
5309
r.notes.len ( ) = = 1 & & r.notes[ 0 ] .contains ( " keeps 1 newer change" ) ,
5310
" {:?}" ,
5311
r.notes
5312
) ;
4dee277c Andy McCall 1 month ago ↶
5313
assert_eq! ( fs:: read( app_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v1\n " ) ;
1a3b6203 Andy McCall 1 month ago ↶
5314
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) ) .unwrap ( ) ;
5315
assert_eq! ( nested.bookmark_heads ( ) .unwrap ( ) , vec! [ l2] ) ;
5316
assert_eq! ( nested.working_copy ( ) .unwrap ( ) .1. deps, vec! [ l1] ) ;
4dee277c Andy McCall 1 month ago ↶
5317
assert! ( ! app.snapshot ( " status" ) .unwrap ( ) .changed) ;
5318
5319
write ( & app_dir.join ( " libs/lib" ) , " scratch.txt" , b " wip\n " ) ;
5320
let r = app.checkout ( & [ a2] , false , " checkout" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5321
assert! (
5322
r.notes.iter ( ) .any ( | n | n.contains ( " uncommitted" ) ) ,
5323
" {:?}" ,
5324
r.notes
5325
) ;
4dee277c Andy McCall 1 month ago ↶
5326
assert_eq! ( fs:: read( app_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v1\n " ) ;
5327
fs:: remove_file( app_dir.join ( " libs/lib/scratch.txt" ) ) .unwrap ( ) ;
5328
let r = app.submodule_update ( false , & [ ] ) .unwrap ( ) ;
5329
assert_eq! ( r, vec! [ ( " libs/lib" .to_string ( ) , SubrepoSync:: Updated) ] ) ;
5330
assert_eq! ( fs:: read( app_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v2\n " ) ;
5331
let _ = l2;
5332
5333
5334
assert! ( app.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
5335
let clone_dir = root.join ( " app-clone" ) ;
75167672 Andy McCall 1 month ago
5336
let clone = Repo:: clone( & app_url, & clone_dir)
5337
.unwrap ( )
5338
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
5339
assert! ( ! clone_dir.join ( " libs/lib/.oxy" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
5340
assert_eq! (
5341
clone.submodule_update ( false , & [ ] ) .unwrap ( ) ,
5342
vec! [ ( " libs/lib" .to_string ( ) , SubrepoSync:: Missing) ]
5343
) ;
5344
assert_eq! (
5345
clone.submodule_update ( true , & [ ] ) .unwrap ( ) ,
5346
vec! [ ( " libs/lib" .to_string ( ) , SubrepoSync:: Updated) ]
5347
) ;
5348
assert_eq! (
5349
fs:: read( clone_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) ,
5350
b " v2\n "
5351
) ;
4dee277c Andy McCall 1 month ago ↶
5352
assert! ( ! clone.snapshot ( " status" ) .unwrap ( ) .changed) ;
5353
5354
clone.submodule_remove ( " libs/lib" ) .unwrap ( ) ;
5355
let snap = clone.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5356
assert! (
5357
snap.statuses
5358
.iter ( )
5359
.any ( | s | s.path = = " libs/lib" & & s.status = = ChangeStatus:: Removed ) ,
5360
" {:?}" ,
5361
snap.statuses
5362
) ;
4dee277c Andy McCall 1 month ago ↶
5363
let c = clone.commit ( " drop lib" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5364
assert! ( ! clone
5365
.load_state ( & [ c] )
5366
.unwrap ( )
5367
.materialize ( )
5368
.files
5369
.contains_key ( " libs/lib" ) ) ;
4dee277c Andy McCall 1 month ago ↶
5370
assert! ( clone_dir.join ( " libs/lib/lib.txt" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
5371
assert! (
5372
! clone.snapshot ( " status" ) .unwrap ( ) .changed,
5373
" an unregistered nested repository is ignored"
5374
) ;
64dc5197 Andy McCall 7 days ago ↶
5375
5376
5377
5378
5379
let modules = format! ( " [libs/lib]\n url = {lib_url} \n " ) ;
5380
let pinned = | c : ChangeHash| {
5381
clone
5382
.load_state ( & [ c] )
5383
.unwrap ( )
5384
.materialize ( )
5385
.files
5386
.get ( " libs/lib" )
5387
.map ( | n | n.kind )
5388
} ;
5389
write ( & clone_dir, " .oxymodules" , modules.as_bytes ( ) ) ;
5390
let c = clone.commit ( " lib again" , & [ ] ) .unwrap ( ) ;
5391
assert_eq! ( pinned ( c) , Some ( NodeKind:: Subrepo) ) ;
5392
clone.submodule_remove ( " libs/lib" ) .unwrap ( ) ;
5393
let c = clone.commit ( " drop lib again" , & [ ] ) .unwrap ( ) ;
5394
assert_eq! ( pinned ( c) , None ) ;
5395
write ( & clone_dir, " .oxymodules" , modules.as_bytes ( ) ) ;
5396
let c = clone.commit ( " lib back" , & [ ] ) .unwrap ( ) ;
5397
assert_eq! (
5398
pinned ( c) ,
5399
Some ( NodeKind:: Subrepo) ,
5400
" pinned in the change that registers it"
5401
) ;
5402
assert! ( ! clone.snapshot ( " status" ) .unwrap ( ) .changed) ;
4dee277c Andy McCall 1 month ago ↶
5403
}
5404
5405
# [ test ]
5406
fn git_submodules_import_and_export_as_gitlinks ( ) {
5407
hermetic ( ) ;
75167672 Andy McCall 1 month ago
5408
if std:: process:: Command:: new( " git" )
5409
.arg ( " --version" )
5410
.output ( )
5411
.is_err ( )
5412
{
4dee277c Andy McCall 1 month ago ↶
5413
eprintln! ( " git not installed; skipping" ) ;
5414
return ;
5415
}
5416
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5417
let root = dir.path ( ) ;
5418
let git_in = | d : & Path, args : & [& str ]| -> String {
5419
let out = std:: process:: Command:: new( " git" )
5420
.arg ( " -C" )
5421
.arg ( d)
75167672 Andy McCall 1 month ago
5422
.args ( [
5423
" -c" ,
5424
" user.name=Grace" ,
5425
" -c" ,
5426
" [email protected] " ,
5427
" -c" ,
5428
" commit.gpgsign=false" ,
5429
" -c" ,
5430
" protocol.file.allow=always" ,
5431
] )
4dee277c Andy McCall 1 month ago ↶
5432
.args ( args)
5433
.output ( )
5434
.unwrap ( ) ;
75167672 Andy McCall 1 month ago
5435
assert! (
5436
out.status.success ( ) ,
5437
" git {args:?}: {}" ,
5438
String :: from_utf8_lossy( & out.stderr)
5439
) ;
4dee277c Andy McCall 1 month ago ↶
5440
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
5441
} ;
5442
let sub = root.join ( " sub" ) ;
5443
fs:: create_dir_all( & sub) .unwrap ( ) ;
5444
git_in ( & sub, & [ " init" , " -q" , " -b" , " main" ] ) ;
5445
write ( & sub, " s.txt" , b " sub\n " ) ;
5446
git_in ( & sub, & [ " add" , " ." ] ) ;
5447
git_in ( & sub, & [ " commit" , " -q" , " -m" , " sub initial" ] ) ;
5448
let sub_sha = git_in ( & sub, & [ " rev-parse" , " HEAD" ] ) ;
5449
let g = root.join ( " main.git" ) ;
5450
fs:: create_dir_all( & g) .unwrap ( ) ;
5451
git_in ( & g, & [ " init" , " -q" , " -b" , " main" ] ) ;
5452
write ( & g, " README.md" , b " # app\n " ) ;
5453
git_in ( & g, & [ " add" , " ." ] ) ;
5454
git_in ( & g, & [ " commit" , " -q" , " -m" , " initial" ] ) ;
75167672 Andy McCall 1 month ago
5455
git_in (
5456
& g,
5457
& [
5458
" submodule" ,
5459
" add" ,
5460
" -q" ,
5461
sub.to_str ( ) .unwrap ( ) ,
5462
" vendor/sub" ,
5463
] ,
5464
) ;
4dee277c Andy McCall 1 month ago ↶
5465
git_in ( & g, & [ " commit" , " -q" , " -m" , " add submodule" ] ) ;
5466
5467
let dest = root.join ( " oxy" ) ;
5468
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5469
let rep = repo.import_git ( & g, " main" ) .unwrap ( ) ;
5470
assert_eq! ( ( rep.commits, rep.submodules) , ( 2 , 1 ) ) ;
75167672 Andy McCall 1 month ago
5471
let files = repo
5472
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
5473
.unwrap ( )
5474
.materialize ( )
5475
.files;
4dee277c Andy McCall 1 month ago ↶
5476
assert_eq! ( files[ " vendor/sub" ] .kind, NodeKind:: Subrepo) ;
75167672 Andy McCall 1 month ago
5477
let FileContent:: Opaque { blobs } = & files[ " vendor/sub" ] .content else {
5478
panic! ( )
5479
} ;
5480
assert_eq! (
5481
crate :: subrepo:: Pin:: decode( & repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) ) .unwrap ( ) ,
5482
crate :: subrepo:: Pin:: Git {
5483
sha: sub_sha.clone ( ) ,
5484
branch: " HEAD" .into ( )
5485
}
5486
) ;
5487
let modules =
5488
crate :: subrepo:: Modules:: parse( & fs:: read_to_string( dest.join ( " .oxymodules" ) ) .unwrap ( ) ) ;
4dee277c Andy McCall 1 month ago ↶
5489
assert_eq! ( modules.entries[ " vendor/sub" ] .url, sub.to_string_lossy ( ) ) ;
5490
assert! ( modules.entries[ " vendor/sub" ] .git) ;
75167672 Andy McCall 1 month ago
5491
assert! (
5492
files.contains_key ( " .gitmodules" ) ,
5493
" the Git file is kept for round trips"
5494
) ;
5495
assert! (
5496
! dest.join ( " vendor/sub/.git" ) .exists ( ) ,
5497
" not cloned until update --init"
5498
) ;
4dee277c Andy McCall 1 month ago ↶
5499
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
5500
75167672 Andy McCall 1 month ago
5501
assert_eq! (
5502
repo.submodule_update ( true , & [ ] ) .unwrap ( ) ,
5503
vec! [ ( " vendor/sub" .to_string ( ) , SubrepoSync:: Updated) ]
5504
) ;
5505
assert_eq! (
5506
git_in ( & dest.join ( " vendor/sub" ) , & [ " rev-parse" , " HEAD" ] ) ,
5507
sub_sha
5508
) ;
4dee277c Andy McCall 1 month ago ↶
5509
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
5510
5511
let mirror = root.join ( " mirror.git" ) ;
5512
let rep = repo.git_export ( & mirror) .unwrap ( ) ;
5513
assert_eq! ( rep.skipped_subrepos, 0 ) ;
5514
let ls = git_in ( & mirror, & [ " ls-tree" , " main" , " vendor/sub" ] ) ;
75167672 Andy McCall 1 month ago
5515
assert! (
5516
ls.starts_with ( " 160000 commit " ) & & ls.contains ( & sub_sha) ,
5517
" {ls}"
5518
) ;
5519
assert_eq! (
5520
git_in ( & mirror, & [ " ls-tree" , " --name-only" , " main" ] ) ,
5521
" .gitmodules\n README.md\n vendor"
5522
) ;
4dee277c Andy McCall 1 month ago ↶
5523
let orig_tree = git_in ( & g, & [ " rev-parse" , " main^{tree}" ] ) ;
75167672 Andy McCall 1 month ago
5524
assert_eq! (
5525
git_in ( & mirror, & [ " rev-parse" , " main^{tree}" ] ) ,
5526
orig_tree,
5527
" tree round trip"
5528
) ;
4dee277c Andy McCall 1 month ago ↶
5529
}
5530
957fee30 Andy McCall 1 month ago ↶
5531
# [ test ]
5532
fn partial_commits_by_path_and_by_hunk ( ) {
5533
hermetic ( ) ;
5534
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5535
let root = dir.path ( ) ;
5536
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5537
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
5538
write ( root, " b.txt" , b " b\n " ) ;
5539
let h0 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
5540
write ( root, " a.txt" , b " 0\n 1\n 2\n 3\n 4\n " ) ;
5541
write ( root, " b.txt" , b " b\n bb\n " ) ;
5542
write ( root, " c.txt" , b " c\n " ) ;
5543
75167672 Andy McCall 1 month ago
5544
let h1 = repo
5545
.commit_selection ( " a only" , & [ ] , & Selection:: Paths( vec! [ " a.txt" .into ( ) ] ) )
5546
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5547
let c1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5548
assert! (
5549
c1.tree_ops.is_empty ( ) & & c1.edits.iter ( ) .all ( | e | e.node ( ) = = c1.edits[ 0 ] .node ( ) ) ,
5550
" {:?}" ,
5551
c1.tree_ops
5552
) ;
957fee30 Andy McCall 1 month ago ↶
5553
assert_eq! ( c1.deps, vec! [ h0] ) ;
5554
let files = repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
5555
assert! (
5556
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 0\n 1\n 2\n 3\n 4\n " )
5557
) ;
957fee30 Andy McCall 1 month ago ↶
5558
assert! ( matches! ( & files[ " b.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " b\n " ) ) ;
5559
assert! ( ! files.contains_key ( " c.txt" ) ) ;
5560
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5561
assert_eq! (
5562
snap.statuses
5563
.iter ( )
5564
.map ( | s | ( s.path.as_str ( ) , s.status) )
5565
.collect:: < Vec< _ > > ( ) ,
5566
vec! [
5567
( " b.txt" , ChangeStatus:: Modified) ,
5568
( " c.txt" , ChangeStatus:: Added)
5569
]
5570
) ;
5571
assert_eq! (
5572
fs:: read( root.join ( " b.txt" ) ) .unwrap ( ) ,
5573
b " b\n bb\n " ,
5574
" disk untouched"
5575
) ;
957fee30 Andy McCall 1 month ago ↶
5576
5577
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5578
let b_hunk = hunks
5579
.iter ( )
5580
.find ( | h | h.path = = " b.txt" & & h.text.contains ( " +bb" ) )
5581
.unwrap ( )
5582
.index;
5583
let h2 = repo
5584
.commit_selection ( " b only" , & [ ] , & Selection:: Hunks( vec! [ b_hunk] ) )
5585
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5586
let files = repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
5587
assert! (
5588
matches! ( & files[ " b.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " b\n bb\n " )
5589
) ;
957fee30 Andy McCall 1 month ago ↶
5590
assert! ( ! files.contains_key ( " c.txt" ) ) ;
5591
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5592
assert_eq! (
5593
snap.statuses
5594
.iter ( )
5595
.map ( | s | s.path.as_str ( ) )
5596
.collect:: < Vec< _ > > ( ) ,
5597
vec! [ " c.txt" ]
5598
) ;
957fee30 Andy McCall 1 month ago ↶
5599
5600
5601
write ( root, " d/e.txt" , b " e\n " ) ;
5602
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5603
let e_insert = hunks
5604
.iter ( )
5605
.find ( | h | h.path = = " d/e.txt" & & h.text.contains ( " +e" ) )
5606
.unwrap ( )
5607
.index;
5608
let h3 = repo
5609
.commit_selection ( " e only" , & [ ] , & Selection:: Hunks( vec! [ e_insert] ) )
5610
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5611
let c3 = repo.store ( ) .get_change ( & h3) .unwrap ( ) ;
5612
assert_eq! ( c3.tree_ops.len ( ) , 2 , " {:?}" , c3.tree_ops) ;
5613
let files = repo.load_state ( & [ h3] ) .unwrap ( ) .materialize ( ) .files;
5614
assert! ( files.contains_key ( " d/e.txt" ) & & ! files.contains_key ( " c.txt" ) ) ;
d8c086ca Andy McCall 1 month ago ↶
5615
5616
write ( root, " f/g.txt" , b " g\n " ) ;
5617
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5618
let g_add = hunks
5619
.iter ( )
5620
.find ( | h | h.path = = " f/g.txt" & & h.text.starts_with ( " A " ) )
5621
.unwrap ( )
5622
.index;
5623
let h4 = repo
5624
.commit_selection ( " g by addition" , & [ ] , & Selection:: Hunks( vec! [ g_add] ) )
5625
.unwrap ( ) ;
d8c086ca Andy McCall 1 month ago ↶
5626
let files = repo.load_state ( & [ h4] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
5627
assert! (
5628
matches! ( & files[ " f/g.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " g\n " )
5629
) ;
5630
assert! ( ! repo
5631
.snapshot ( " status" )
5632
.unwrap ( )
5633
.statuses
5634
.iter ( )
5635
.any ( | s | s.path = = " f/g.txt" ) ) ;
957fee30 Andy McCall 1 month ago ↶
5636
5637
repo.commit ( " the rest" , & [ ] ) .unwrap ( ) ;
5638
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
75167672 Andy McCall 1 month ago
5639
let files = repo
5640
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
5641
.unwrap ( )
5642
.materialize ( )
5643
.files;
957fee30 Andy McCall 1 month ago ↶
5644
assert! ( files.contains_key ( " c.txt" ) ) ;
5645
}
5646
5647
# [ test ]
5648
fn blame_attributes_lines_to_introducing_changes_across_renames ( ) {
5649
hermetic ( ) ;
5650
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5651
let root = dir.path ( ) ;
5652
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
5653
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
5654
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5655
write ( root, " a.txt" , b " one\n " ) ;
5656
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
5657
write ( root, " a.txt" , b " one\n two\n " ) ;
5658
let h2 = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
5659
let b = repo.blame ( " a.txt" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5660
assert_eq! (
5661
b.iter ( )
5662
.map ( | l | ( l.line, l.hash, l.text.as_slice ( ) ) )
5663
.collect:: < Vec< _ > > ( ) ,
5664
vec! [ ( 1 , h1, b " one\n " .as_slice ( ) ) , ( 2 , h2, b " two\n " .as_slice ( ) ) ]
5665
) ;
957fee30 Andy McCall 1 month ago ↶
5666
assert! ( b.iter ( ) .all ( | l | l.author = = " Ada" ) ) ;
75167672 Andy McCall 1 month ago
5667
assert_eq! (
5668
b[ 0 ] .change_id,
5669
repo.store ( ) .get_change ( & h1) .unwrap ( ) .change_id
5670
) ;
957fee30 Andy McCall 1 month ago ↶
5671
fs:: rename( root.join ( " a.txt" ) , root.join ( " b.txt" ) ) .unwrap ( ) ;
5672
repo.commit ( " rename" , & [ ] ) .unwrap ( ) ;
5673
let b = repo.blame ( " b.txt" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5674
assert_eq! (
5675
b.iter ( ) .map ( | l | l.hash ) .collect:: < Vec< _ > > ( ) ,
5676
vec! [ h1, h2] ,
5677
" the node survives the rename"
5678
) ;
957fee30 Andy McCall 1 month ago ↶
5679
assert! ( repo.blame ( " a.txt" ) .is_err ( ) ) ;
5680
}
5681
5682
# [ test ]
5683
fn bisect_finds_the_first_bad_change ( ) {
5684
hermetic ( ) ;
5685
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5686
let root = dir.path ( ) ;
5687
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5688
let mut content = String :: new( ) ;
5689
let mut hashes = Vec :: new( ) ;
5690
for i in 1 .. = 8 {
5691
content.push_str ( & format! ( " line {i} {} \n " , if i = = 5 { " bug" } else { " " } ) ) ;
5692
write ( root, " f.txt" , content.as_bytes ( ) ) ;
5693
hashes.push ( repo.commit ( & format! ( " step {i} " ) , & [ ] ) .unwrap ( ) ) ;
5694
}
5695
5696
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ hashes[ 7 ] ] ) ;
5697
repo.bisect_start ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5698
assert! ( matches! (
5699
repo.bisect_mark ( Some ( true ) , & [ hashes[ 0 ] ] ) .unwrap ( ) ,
5700
BisectStep:: NeedMarks { good: 1 , bad: 0 }
5701
) ) ;
957fee30 Andy McCall 1 month ago ↶
5702
let step = repo.bisect_mark ( Some ( false ) , & [ hashes[ 7 ] ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5703
assert! (
5704
matches! ( step, BisectStep:: Testing { remaining: 7 , .. } ) ,
5705
" {step:?}"
5706
) ;
957fee30 Andy McCall 1 month ago ↶
5707
75167672 Andy McCall 1 month ago
5708
let step = repo
5709
.bisect_run ( & [ " sh" .into ( ) , " -c" .into ( ) , " ! grep -q bug f.txt" .into ( ) ] )
5710
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5711
assert_eq! ( step, BisectStep:: Found { culprit: hashes[ 4 ] } ) ;
75167672 Andy McCall 1 month ago
5712
assert_eq! (
5713
repo.bisect_status ( ) .unwrap ( ) ,
5714
Some ( BisectStep:: Found { culprit: hashes[ 4 ] } )
5715
) ;
957fee30 Andy McCall 1 month ago ↶
5716
repo.bisect_reset ( ) .unwrap ( ) ;
5717
assert_eq! ( fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) , content) ;
5718
assert! ( repo.bisect_status ( ) .unwrap ( ) .is_none ( ) ) ;
5719
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
5720
5721
repo.bisect_start ( ) .unwrap ( ) ;
5722
repo.bisect_mark ( Some ( true ) , & [ hashes[ 0 ] ] ) .unwrap ( ) ;
5723
let mut step = repo.bisect_mark ( Some ( false ) , & [ hashes[ 7 ] ] ) .unwrap ( ) ;
5724
let mut skipped_once = false ;
5725
let mut guard = 0 ;
5726
while let BisectStep:: Testing { .. } = step {
5727
guard + = 1 ;
5728
assert! ( guard < 20 ) ;
5729
let text = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
5730
if ! skipped_once & & text.contains ( " line 6" ) {
5731
5732
skipped_once = true ;
5733
step = repo.bisect_mark ( None , & [ ] ) .unwrap ( ) ;
5734
continue ;
5735
}
5736
step = repo.bisect_mark ( Some ( ! text.contains ( " bug" ) ) , & [ ] ) .unwrap ( ) ;
5737
}
5738
assert! ( skipped_once) ;
5739
assert_eq! ( step, BisectStep:: Found { culprit: hashes[ 4 ] } ) ;
5740
repo.bisect_reset ( ) .unwrap ( ) ;
5741
5742
repo.bisect_start ( ) .unwrap ( ) ;
5743
repo.bisect_mark ( Some ( true ) , & [ hashes[ 0 ] ] ) .unwrap ( ) ;
5744
let mut step = repo.bisect_mark ( Some ( false ) , & [ hashes[ 7 ] ] ) .unwrap ( ) ;
5745
let mut guard = 0 ;
5746
while let BisectStep:: Testing { heads, .. } = & step {
5747
guard + = 1 ;
5748
assert! ( guard < 20 ) ;
5749
let text = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5750
let is_culprit_state =
5751
heads.contains ( & hashes[ 4 ] ) & & ! heads.iter ( ) .any ( | h | hashes[ 5 .. ] .contains ( h) ) ;
5752
step = if is_culprit_state {
5753
repo.bisect_mark ( None , & [ ] ) .unwrap ( )
5754
} else {
5755
repo.bisect_mark ( Some ( ! text.contains ( " bug" ) ) , & [ ] ) .unwrap ( )
5756
} ;
957fee30 Andy McCall 1 month ago ↶
5757
}
5758
match step {
75167672 Andy McCall 1 month ago
5759
BisectStep:: Inconclusive { candidates } => assert! (
5760
candidates.contains ( & hashes[ 4 ] ) & & candidates.len ( ) < = 2 ,
5761
" {candidates:?}"
5762
) ,
957fee30 Andy McCall 1 month ago ↶
5763
other => panic! ( " {other:?}" ) ,
5764
}
5765
repo.bisect_reset ( ) .unwrap ( ) ;
5766
}
5767
728da0f9 Andy McCall 1 month ago ↶
5768
# [ test ]
5769
fn squash_folds_a_change_into_its_dependency ( ) {
5770
hermetic ( ) ;
5771
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5772
let root = dir.path ( ) ;
5773
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5774
write ( root, " a.txt" , b " 1\n " ) ;
5775
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
5776
write ( root, " a.txt" , b " 1\n 2\n " ) ;
5777
let c = repo.commit ( " C" , & [ ] ) .unwrap ( ) ;
5778
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
5779
let e = repo.commit ( " E" , & [ ] ) .unwrap ( ) ;
5780
write ( root, " b.txt" , b " b\n " ) ;
5781
let b = repo.commit ( " B" , & [ ] ) .unwrap ( ) ;
5782
let id_a = repo.store ( ) .get_change ( & a) .unwrap ( ) .change_id;
5783
let id_e = repo.store ( ) .get_change ( & e) .unwrap ( ) .change_id;
5784
5785
let err = repo.squash ( & e, & a) .unwrap_err ( ) .to_string ( ) ;
5786
assert! ( err.contains ( & c.short ( 12 ) ) , " {err}" ) ;
5787
5788
let a2 = repo.squash ( & c, & a) .unwrap ( ) ;
5789
let a2c = repo.store ( ) .get_change ( & a2) .unwrap ( ) ;
5790
assert_eq! ( a2c.change_id, id_a) ;
5791
assert! ( a2c.deps.is_empty ( ) ) ;
5792
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5793
let st = repo.load_state ( & heads) .unwrap ( ) ;
5794
assert! ( ! st.contains ( & c) & & ! st.contains ( & a) & & ! st.contains ( & e) ) ;
75167672 Andy McCall 1 month ago
5795
let e2 = st
5796
.changes ( )
5797
.find ( | ( _, m ) | m.change_id = = id_e )
5798
.map ( | ( h , _) | * h)
5799
.unwrap ( ) ;
5800
assert_eq! (
5801
repo.store ( ) .get_change ( & e2) .unwrap ( ) .deps,
5802
vec! [ a2] ,
5803
" E now depends on A' alone"
5804
) ;
728da0f9 Andy McCall 1 month ago ↶
5805
let files = st.materialize ( ) .files;
75167672 Andy McCall 1 month ago
5806
assert! (
5807
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 1\n 2\n 3\n " )
5808
) ;
728da0f9 Andy McCall 1 month ago ↶
5809
assert! ( files.contains_key ( " b.txt" ) ) ;
5810
assert! ( st.contains ( & b) ) ;
5811
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
5812
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 , " working copy, B, E', A'" ) ;
5813
5814
repo.undo ( ) .unwrap ( ) ;
5815
repo.undo ( ) .unwrap ( ) ;
5816
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
5817
assert! ( st.contains ( & c) & & st.contains ( & a) & & st.contains ( & e) ) ;
5818
}
5819
5820
# [ test ]
5821
fn rebase_re_expresses_a_change_against_another_base ( ) {
5822
hermetic ( ) ;
5823
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5824
let root = dir.path ( ) ;
5825
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5826
write ( root, " a.txt" , b " 1\n 2\n " ) ;
5827
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
5828
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
5829
let c = repo.commit ( " C: append 3" , & [ ] ) .unwrap ( ) ;
5830
write ( root, " a.txt" , b " 1\n 2\n 3\n 4\n " ) ;
5831
let e = repo.commit ( " E: append 4" , & [ ] ) .unwrap ( ) ;
5832
5833
repo.checkout ( & [ a] , true , " checkout" ) .unwrap ( ) ;
5834
write ( root, " a.txt" , b " 0\n 1\n 2\n " ) ;
5835
let d = repo.commit ( " D: prepend 0" , & [ ] ) .unwrap ( ) ;
5836
let id_c = repo.store ( ) .get_change ( & c) .unwrap ( ) .change_id;
5837
let id_e = repo.store ( ) .get_change ( & e) .unwrap ( ) .change_id;
5838
5839
let c2 = repo.rebase ( & c, & [ d] ) .unwrap ( ) ;
5840
let c2c = repo.store ( ) .get_change ( & c2) .unwrap ( ) ;
5841
assert_eq! ( c2c.change_id, id_c) ;
5842
assert! ( c2c.deps.contains ( & d) , " {:?}" , c2c.deps) ;
5843
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5844
let st = repo.load_state ( & heads) .unwrap ( ) ;
5845
assert! ( ! st.contains ( & c) & & st.contains ( & d) ) ;
75167672 Andy McCall 1 month ago
5846
let e2 = st
5847
.changes ( )
5848
.find ( | ( _, m ) | m.change_id = = id_e )
5849
.map ( | ( h , _) | * h)
5850
.unwrap ( ) ;
728da0f9 Andy McCall 1 month ago ↶
5851
let e2deps = repo.store ( ) .get_change ( & e2) .unwrap ( ) .deps;
75167672 Andy McCall 1 month ago
5852
assert! (
5853
e2deps.contains ( & c2) & & ! e2deps.contains ( & c) & & e2deps.contains ( & a) ,
5854
" E follows C by content and still depends on A, which introduced the file: {e2deps:?}"
5855
) ;
728da0f9 Andy McCall 1 month ago ↶
5856
let files = st.materialize ( ) .files;
75167672 Andy McCall 1 month ago
5857
assert! (
5858
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 0\n 1\n 2\n 3\n 4\n " ) ,
5859
" {:?}" ,
5860
files[ " a.txt" ] .content
5861
) ;
728da0f9 Andy McCall 1 month ago ↶
5862
assert! ( ! files[ " a.txt" ] .is_conflicted ( ) ) ;
5863
5864
assert! ( repo.rebase ( & c2, & [ c2] ) .is_err ( ) ) ;
5865
let err = repo.rebase ( & e2, & [ d] ) .unwrap_err ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
5866
assert! (
5867
err.contains ( " lacks dependencies" ) ,
5868
" E needs C, which D's state does not have: {err}"
5869
) ;
728da0f9 Andy McCall 1 month ago ↶
5870
}
5871
c792033a Andy McCall 1 month ago ↶
5872
# [ test ]
5873
fn unsigned_tags_keep_their_message ( ) {
5874
hermetic ( ) ;
5875
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5876
let root = dir.path ( ) ;
5877
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5878
write ( root, " a.txt" , b " a\n " ) ;
5879
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
5880
assert! ( repo.secret_key ( ) .unwrap ( ) .is_none ( ) ) ;
75167672 Andy McCall 1 month ago
5881
repo.tag_create ( " v1" , & repo.bookmark_heads ( ) .unwrap ( ) , " first release" )
5882
.unwrap ( ) ;
c792033a Andy McCall 1 month ago ↶
5883
let tags = repo.tags ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5884
assert_eq! (
5885
(
5886
tags[ 0 ] .name.as_str ( ) ,
5887
tags[ 0 ] .message.as_str ( ) ,
5888
tags[ 0 ] .signer.as_deref ( )
5889
) ,
5890
( " v1" , " first release" , None )
5891
) ;
5892
assert! (
5893
repo.tag_refs ( ) .unwrap ( ) [ 0 ] .attestation.is_none ( ) ,
5894
" an unsigned attestation is not advertised"
5895
) ;
c792033a Andy McCall 1 month ago ↶
5896
}
387335ff Andy McCall 1 month ago ↶
5897
5898
# [ test ]
5899
fn fsck_passes_a_sound_repository_and_names_each_fault ( ) {
64818938 Andy McCall 1 month ago ↶
5900
5901
5902
if std:: env:: var_os( " OXY_STORE_BACKEND" ) .is_some ( ) {
5903
return ;
5904
}
387335ff Andy McCall 1 month ago ↶
5905
hermetic ( ) ;
5906
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5907
let root = dir.path ( ) ;
5908
let origin_path = root.join ( " origin" ) ;
5909
let origin = Repo:: init( & origin_path) .unwrap ( ) ;
5910
let mut a = Repo:: clone( & origin_path.to_string_lossy ( ) , & root.join ( " a" ) )
5911
.unwrap ( )
5912
.with_home ( root.join ( " home-a" ) ) ;
5913
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
5914
.unwrap ( ) ;
5915
write ( & root.join ( " a" ) , " f.txt" , b " one\n " ) ;
5916
write ( & root.join ( " a" ) , " bin.dat" , b " \0 \x01 \x02 binary" ) ;
5917
let h1 = a.commit ( " first" , & [ ] ) .unwrap ( ) ;
5918
write ( & root.join ( " a" ) , " f.txt" , b " one\n two\n " ) ;
5919
let h2 = a.commit ( " second" , & [ ] ) .unwrap ( ) ;
5920
a.tag_create ( " v1" , & [ h2] , " first release" ) .unwrap ( ) ;
5921
a.checkpoint ( ) .unwrap ( ) ;
5922
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 17 days ago
5923
origin.refresh ( ) .unwrap ( ) ;
387335ff Andy McCall 1 month ago ↶
5924
5925
5926
for repo in [ & a, & origin] {
5927
let rep = repo.fsck ( ) .unwrap ( ) ;
5928
assert_eq! ( rep.problems, vec! [ ] , " {rep:?}" ) ;
5929
5930
assert! ( rep.changes > = 2 & & rep.signed > = 2 , " {rep:?}" ) ;
5931
assert! ( rep.ops > 1 & & rep.objects > 5 ) ;
5932
}
5933
assert! ( origin.fsck ( ) .unwrap ( ) .attestations > = 1 ) ;
5934
5935
5936
let c1 = a.store ( ) .path_for ( & h1) ;
5937
let pristine = fs:: read( & c1) .unwrap ( ) ;
5938
let mut bytes = pristine.clone ( ) ;
5939
* bytes.last_mut ( ) .unwrap ( ) ^ = 1 ;
5940
fs:: write( & c1, & bytes) .unwrap ( ) ;
5941
let rep = a.fsck ( ) .unwrap ( ) ;
5942
assert! ( rep.errors ( ) > = 1 ) ;
5943
assert! (
5944
rep.problems
5945
.iter ( )
5946
.any ( | p | p.object = = Some ( h1) & & p.what.contains ( " unreadable" ) ) ,
5947
" {rep:?}"
5948
) ;
5949
fs:: write( & c1, & pristine) .unwrap ( ) ;
5950
assert_eq! ( a.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
5951
5952
5953
fs:: remove_file( & c1) .unwrap ( ) ;
5954
let rep = a.fsck ( ) .unwrap ( ) ;
5955
assert! (
5956
rep.problems.iter ( ) .any ( | p | p.severity = = Severity:: Error
5957
& & p.object = = Some ( h1)
5958
& & p.what.contains ( " change missing, needed by change" ) ) ,
5959
" {rep:?}"
5960
) ;
5961
fs:: write( & c1, & pristine) .unwrap ( ) ;
5962
5963
5964
let c2 = a.store ( ) .get_change ( & h1) .unwrap ( ) ;
5965
let blob = c2
5966
.edits
5967
.iter ( )
5968
.find_map ( | e | match e {
5969
Edit:: Replace { new: Some ( b) , .. } => Some ( * b) ,
5970
_ => None ,
5971
} )
5972
.expect ( " the binary file is a blob" ) ;
5973
let bp = a.store ( ) .path_for ( & blob) ;
5974
let blob_bytes = fs:: read( & bp) .unwrap ( ) ;
5975
fs:: remove_file( & bp) .unwrap ( ) ;
5976
let rep = a.fsck ( ) .unwrap ( ) ;
5977
assert! (
5978
rep.problems
5979
.iter ( )
5980
.any ( | p | p.object = = Some ( blob) & & p.what.contains ( " blob missing" ) ) ,
5981
" {rep:?}"
5982
) ;
5983
fs:: write( & bp, & blob_bytes) .unwrap ( ) ;
5984
5985
5986
let head = root.join ( " a/.oxy/ops/head" ) ;
5987
let saved = fs:: read( & head) .unwrap ( ) ;
5988
fs:: write( & head, b " not a hash" ) .unwrap ( ) ;
5989
let rep = a.fsck ( ) .unwrap ( ) ;
5990
assert! (
5991
rep.problems.iter ( ) .any ( | p | p.what.contains ( " ops/head" ) ) ,
5992
" {rep:?}"
5993
) ;
5994
fs:: write( & head, saved) .unwrap ( ) ;
5995
assert_eq! ( a.fsck ( ) .unwrap ( ) .problems, vec! [ ] ) ;
5996
}
4dcb8c09 Andy McCall 1 month ago ↶
5997
5998
5999
fn copy_tree ( from : & Path, to : & Path) {
6000
fs:: create_dir_all( to) .unwrap ( ) ;
6001
for e in fs:: read_dir( from) .unwrap ( ) {
6002
let e = e.unwrap ( ) ;
6003
let target = to.join ( e.file_name ( ) ) ;
6004
if e.file_type ( ) .unwrap ( ) .is_dir ( ) {
6005
copy_tree ( & e.path ( ) , & target) ;
6006
} else {
6007
fs:: copy( e.path ( ) , & target) .unwrap ( ) ;
6008
}
6009
}
6010
}
6011
6012
6013
6014
6015
# [ test ]
6016
fn crash_at_every_step_of_commit_and_push_leaves_sound_repositories ( ) {
64818938 Andy McCall 1 month ago ↶
6017
6018
6019
if std:: env:: var_os( " OXY_STORE_BACKEND" ) .is_some ( ) {
6020
return ;
6021
}
4dcb8c09 Andy McCall 1 month ago ↶
6022
use crate :: durable:: failpoint;
6023
hermetic ( ) ;
6024
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6025
let pristine = dir.path ( ) .join ( " pristine" ) ;
6026
let origin_path = pristine.join ( " origin" ) ;
6027
Repo:: init( & origin_path) .unwrap ( ) ;
6028
let mut a = Repo:: clone( & origin_path.to_string_lossy ( ) , & pristine.join ( " a" ) )
6029
.unwrap ( )
6030
.with_home ( dir.path ( ) .join ( " home" ) ) ;
6031
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
6032
.unwrap ( ) ;
6033
write ( & pristine.join ( " a" ) , " f.txt" , b " one\n " ) ;
6034
a.commit ( " first" , & [ ] ) .unwrap ( ) ;
6035
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
6036
drop ( a) ;
6037
6038
type Op = fn ( & Repo) -> Result< ( ) > ;
6039
let edit_and_commit: Op = | r | {
6040
write ( r.root ( ) , " f.txt" , b " one\n two\n " ) ;
6041
write ( r.root ( ) , " big.bin" , & vec! [ 7 u8 ; 300_000 ] ) ;
6042
r.commit ( " second" , & [ ] ) .map ( | _| ( ) )
6043
} ;
6044
let push: Op = | r | {
6045
let res = r.push ( " origin" , false ) ? ;
6046
if res.iter ( ) .all ( | x | x.ok ) {
6047
Ok ( ( ) )
6048
} else {
6049
Err ( RepoError:: Msg( format! ( " {res:?} " ) ) )
6050
}
6051
} ;
6052
6053
for ( name, prepare, op) in [
6054
( " commit" , None , edit_and_commit) ,
6055
( " push" , Some ( edit_and_commit) , push) ,
6056
] {
6057
let mut steps = 0 ;
6058
for n in 1 .. {
6059
let run = dir.path ( ) .join ( format! ( " {name} -{n} " ) ) ;
6060
copy_tree ( & pristine, & run) ;
6061
6062
let open = | p : & Path| {
6063
let mut r = Repo:: open( p) .unwrap ( ) .with_home ( dir.path ( ) .join ( " home" ) ) ;
6064
r.config
6065
.set ( " remote.origin.url" , & run.join ( " origin" ) .to_string_lossy ( ) )
6066
.unwrap ( ) ;
6067
r.save_config ( ) .unwrap ( ) ;
6068
r
6069
} ;
6070
let a = open ( & run.join ( " a" ) ) ;
6071
if let Some ( prep) = prepare {
6072
prep ( & a) .unwrap ( ) ;
6073
}
6074
failpoint:: crash_after( n) ;
6075
let outcome = op ( & a) ;
6076
let crashed = failpoint:: disarm( ) ;
6077
drop ( a) ;
6078
if ! crashed {
6079
outcome.unwrap_or_else ( | e | panic! ( " {name} failed without a crash: {e}" ) ) ;
6080
steps = n - 1 ;
6081
break ;
6082
}
6083
assert! (
6084
outcome.is_err ( ) ,
6085
" {name}: a crash at step {n} was swallowed"
6086
) ;
6087
for repo in [ " a" , " origin" ] {
6088
let r = Repo:: open( & run.join ( repo) ) .unwrap_or_else ( | e | {
6089
panic! ( " {name} crash at step {n}: {repo} no longer opens: {e}" )
6090
} ) ;
6091
let rep = r.fsck ( ) .unwrap ( ) ;
6092
assert_eq! (
6093
rep.errors ( ) ,
6094
0 ,
6095
" {name} crash at step {n}: {repo} fails fsck: {:?}" ,
6096
rep.problems
6097
) ;
6098
}
6099
6100
let a = open ( & run.join ( " a" ) ) ;
6101
6102
let landed = a
6103
.bookmark_heads ( )
6104
.unwrap ( )
6105
.iter ( )
6106
.any ( | h | a.store ( ) .get_change ( h) .unwrap ( ) .message = = " second" ) ;
6107
let again = match ( name, landed) {
6108
( " commit" , true ) => Ok ( ( ) ) ,
6109
( " commit" , false ) => a.commit ( " second" , & [ ] ) .map ( | _| ( ) ) ,
6110
_ => op ( & a) ,
6111
} ;
6112
again.unwrap_or_else ( | e | panic! ( " {name} crash at step {n}: retry failed: {e}" ) ) ;
6113
assert_eq! ( a.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
6114
fs:: remove_dir_all( & run) .unwrap ( ) ;
6115
}
6116
assert! ( steps > = 3 , " {name} has only {steps} durable steps?" ) ;
6117
}
6118
}
19555025 Andy McCall 1 month ago ↶
6119
6120
6121
6122
6123
# [ test ]
6124
fn concurrent_commits_in_two_workspaces_lose_nothing ( ) {
6125
hermetic ( ) ;
6126
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6127
let root = dir.path ( ) ;
6128
let origin = root.join ( " origin" ) ;
6129
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
6130
let main = root.join ( " main" ) ;
6131
let mut repo = Repo:: init( & main) .unwrap ( ) ;
6132
repo.config
6133
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
6134
.unwrap ( ) ;
6135
repo.save_config ( ) .unwrap ( ) ;
6136
write ( & main, " seed.txt" , b " seed\n " ) ;
6137
repo.commit ( " seed" , & [ ] ) .unwrap ( ) ;
6138
let second = root.join ( " second" ) ;
6139
repo.workspace_add ( & second, None ) .unwrap ( ) ;
6140
drop ( repo) ;
6141
6142
const EACH : usize = 15 ;
6143
std:: thread:: scope( | s | {
6144
for ( tag, dir, push) in [ ( " a" , main.clone ( ) , true ) , ( " b" , second.clone ( ) , false ) ] {
6145
s.spawn ( move | | {
6146
let r = Repo:: open( & dir) .unwrap ( ) ;
6147
for i in 0 .. EACH {
6148
write (
6149
& dir,
6150
& format! ( " {tag} -{i} .txt" ) ,
6151
format! ( " {tag} {i} \n " ) .as_bytes ( ) ,
6152
) ;
6153
r.commit ( & format! ( " {tag} {i} " ) , & [ ] ) .unwrap ( ) ;
6154
if push & & i % 5 = = 4 {
6155
6156
let _ = r.push ( " origin" , false ) ;
6157
}
6158
}
6159
} ) ;
6160
}
6161
} ) ;
6162
6163
let repo = Repo:: open( & main) .unwrap ( ) ;
6164
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6165
let state = repo.load_state ( & heads) .unwrap ( ) ;
6166
let messages: BTreeSet< String> = state.changes ( ) .map ( | ( _, m ) | m.message.clone ( ) ) .collect ( ) ;
6167
for tag in [ " a" , " b" ] {
6168
for i in 0 .. EACH {
6169
assert! (
6170
messages.contains ( & format! ( " {tag} {i} " ) ) ,
6171
" lost commit {tag} {i}; have {messages:?}"
6172
) ;
6173
}
6174
}
6175
let rep = repo.fsck ( ) .unwrap ( ) ;
6176
assert_eq! ( rep.errors ( ) , 0 , " {:?}" , rep.problems) ;
6177
assert! ( repo.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
6178
let hosted = Repo:: open( & origin) .unwrap ( ) ;
6179
let hosted_heads = hosted.bookmark_heads ( ) .unwrap ( ) ;
6180
assert_eq! (
6181
hosted.load_state ( & hosted_heads) .unwrap ( ) .len ( ) ,
6182
state.len ( ) ,
6183
" the remote has every commit"
6184
) ;
6185
}
6186
6187
6188
6189
6190
# [ test ]
6191
fn operations_from_the_same_parent_are_merged ( ) {
6192
hermetic ( ) ;
6193
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6194
let root = dir.path ( ) ;
6195
let repo = Repo:: init( root) .unwrap ( ) ;
6196
write ( root, " a.txt" , b " a\n " ) ;
6197
let a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
6198
write ( root, " b.txt" , b " b\n " ) ;
6199
let b = repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
6200
let ( x, op) = repo.head_op ( ) .unwrap ( ) ;
6201
let state = | heads : Vec< ChangeHash> | repo.store ( ) .put_state ( & StateObject:: new( heads) ) .unwrap ( ) ;
6202
6203
6204
let mut one = op.view.clone ( ) ;
6205
one.bookmarks.insert ( " main" .into ( ) , state ( vec! [ a] ) ) ;
6206
one.bookmarks.insert ( " topic" .into ( ) , state ( vec! [ a] ) ) ;
6207
let mut two = op.view.clone ( ) ;
6208
two.bookmarks.insert ( " main" .into ( ) , state ( vec! [ b] ) ) ;
6209
repo.record_op ( & [ x] , " one" , one) .unwrap ( ) ;
6210
let merged = repo.record_op ( & [ x] , " two" , two) .unwrap ( ) ;
6211
6212
let ( head, op) = repo.head_op ( ) .unwrap ( ) ;
6213
assert_eq! ( head, merged) ;
6214
assert_eq! ( op.parents.len ( ) , 2 , " a merge of the two operations" ) ;
6215
assert! ( op.command.contains ( " merged with a concurrent operation" ) ) ;
6216
let mut main = repo
6217
.store ( )
6218
.get_state ( & op.view.bookmarks[ " main" ] )
6219
.unwrap ( )
6220
.heads;
6221
main.sort ( ) ;
6222
let mut want = vec! [ a, b] ;
6223
want.sort ( ) ;
6224
assert_eq! ( main, want, " both sides' heads are kept" ) ;
6225
assert! (
6226
op.view.bookmarks.contains_key ( " topic" ) ,
6227
" a one-sided change survives"
6228
) ;
6229
assert_eq! ( repo.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
6230
}
3e5772cc Andy McCall 1 month ago ↶
6231
6232
# [ test ]
6233
fn failed_requests_come_with_a_next_step ( ) {
6234
use crate :: protocol:: hint_for;
6235
assert! ( hint_for ( " https://h/x/oxy/v1/push: HTTP 401: bad token" )
6236
.unwrap ( )
6237
.contains ( " token" ) ) ;
6238
assert! ( hint_for ( " …: HTTP 403: forbidden" )
6239
.unwrap ( )
6240
.contains ( " repo:write" ) ) ;
6241
assert! ( hint_for ( " …: HTTP 404: no such repository" )
6242
.unwrap ( )
6243
.contains ( " owner and repository name" ) ) ;
6244
assert! ( hint_for ( " invalid peer certificate: UnknownIssuer" )
6245
.unwrap ( )
6246
.contains ( " oxyhub-ca.pem" ) ) ;
6247
assert! ( hint_for ( " Connect error: Connection refused (os error 111)" )
6248
.unwrap ( )
6249
.contains ( " running" ) ) ;
6250
assert! ( hint_for ( " Dns Failed: failed to lookup address information" )
6251
.unwrap ( )
6252
.contains ( " resolve" ) ) ;
6253
assert_eq! ( hint_for ( " something else entirely" ) , None ) ;
6254
}
6255
6256
# [ test ]
6257
fn doctor_names_problems_and_what_to_do ( ) {
6258
hermetic ( ) ;
6259
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6260
let root = dir.path ( ) ;
6261
let origin = root.join ( " origin" ) ;
6262
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
6263
let mut repo = Repo:: init( & root.join ( " r" ) )
6264
.unwrap ( )
6265
.with_home ( root.join ( " home" ) ) ;
6266
let find = | r : & DoctorReport, area : & str , h : Health| {
6267
r.checks
6268
.iter ( )
6269
.find ( | c | c.area = = area & & c.health = = h )
6270
.cloned ( )
6271
} ;
6272
6273
let r = repo.doctor ( true ) ;
6274
assert! ( find ( & r, " identity" , Health:: Warn)
6275
.unwrap ( )
6276
.hint
6277
.unwrap ( )
6278
.contains ( " oxy identity create" ) ) ;
6279
assert! ( find ( & r, " repository" , Health:: Ok) .is_some ( ) ) ;
ad2496f7 Andy McCall 16 days ago ↶
6280
6281
assert! ( find ( & r, " trust" , Health:: Warn)
6282
.unwrap ( )
6283
.hint
6284
.unwrap ( )
6285
.contains ( " oxy policy init" ) ) ;
3e5772cc Andy McCall 1 month ago ↶
6286
6287
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
6288
.unwrap ( ) ;
ad2496f7 Andy McCall 16 days ago ↶
6289
repo.policy_init ( ) .unwrap ( ) ;
6290
let r = repo.doctor ( false ) ;
6291
let trust = find ( & r, " trust" , Health:: Ok) .expect ( " a fresh policy is fine" ) ;
6292
assert! (
6293
trust.message.contains ( " serial 1" ) & & trust.message.contains ( " 1 root" ) ,
6294
" {trust:?}"
6295
) ;
3e5772cc Andy McCall 1 month ago ↶
6296
repo.config.set ( " author.name" , " Ada" ) .unwrap ( ) ;
6297
repo.config.set ( " author.email" , " [email protected] " ) .unwrap ( ) ;
6298
repo.config
6299
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
6300
.unwrap ( ) ;
6301
repo.config
6302
.set ( " remote.down.url" , " http://127.0.0.1:9/a/b" )
6303
.unwrap ( ) ;
6304
repo.config.set ( " remote.down.token" , " t" ) .unwrap ( ) ;
6305
repo.config
6306
.set ( " remote.badca.url" , " https://example.invalid/a/b" )
6307
.unwrap ( ) ;
6308
repo.config
6309
.set (
6310
" remote.badca.ca" ,
6311
& root.join ( " missing.pem" ) .to_string_lossy ( ) ,
6312
)
6313
.unwrap ( ) ;
6314
repo.save_config ( ) .unwrap ( ) ;
6315
6316
let r = repo.doctor ( true ) ;
6317
assert! ( find ( & r, " identity" , Health:: Ok) .is_some ( ) , " {r:?}" ) ;
6318
assert! ( find ( & r, " author" , Health:: Ok) .is_some ( ) , " {r:?}" ) ;
6319
assert! ( find ( & r, " remote origin" , Health:: Ok)
6320
.unwrap ( )
6321
.message
6322
.contains ( " first push binds it" ) ) ;
6323
let down = find ( & r, " remote down" , Health:: Fail) .unwrap ( ) ;
6324
assert! (
6325
down.hint.as_deref ( ) .unwrap_or ( " " ) .contains ( " running" ) ,
6326
" {down:?}"
6327
) ;
6328
let badca = find ( & r, " remote badca" , Health:: Fail) .unwrap ( ) ;
6329
assert! (
6330
badca.hint.unwrap ( ) .contains ( " remote.badca.ca" ) ,
6331
" the remote's own name is in the hint"
6332
) ;
6333
assert_eq! ( r.worst ( ) , Health:: Fail) ;
6334
6335
6336
let r = repo.doctor ( false ) ;
6337
assert! ( find ( & r, " remote down" , Health:: Fail) .is_none ( ) ) ;
6338
assert! ( find ( & r, " remote down" , Health:: Ok)
6339
.unwrap ( )
6340
.message
6341
.contains ( " not contacted" ) ) ;
6342
}
62794ecc Andy McCall 1 month ago ↶
6343
6344
6345
6346
6347
# [ test ]
6348
fn deleting_a_line_after_an_insertion_below_it_is_not_a_conflict ( ) {
6349
hermetic ( ) ;
6350
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6351
let root = dir.path ( ) ;
6352
let repo = Repo:: init( root) .unwrap ( ) ;
6353
write ( root, " f" , b " a\n b\n c\n " ) ;
6354
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6355
write ( root, " f" , b " a\n b\n x\n c\n " ) ;
6356
let insert = repo.commit ( " insert x after b" , & [ ] ) .unwrap ( ) ;
6357
write ( root, " f" , b " a\n x\n c\n " ) ;
6358
let delete = repo.commit ( " delete b" , & [ ] ) .unwrap ( ) ;
6359
assert! (
6360
repo.store ( )
6361
.get_change ( & delete)
6362
.unwrap ( )
6363
.deps
6364
.contains ( & insert) ,
6365
" the deletion depends on the insertion below the deleted line"
6366
) ;
6367
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6368
assert_eq! ( heads, vec! [ delete] ) ;
6369
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
6370
assert! ( ! snap.is_conflicted ( ) ) ;
6371
assert! (
6372
matches! ( & snap.files[ " f" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n x\n c\n " )
6373
) ;
6374
}
6375
6376
6377
6378
# [ test ]
6379
fn a_binary_file_reverted_to_an_earlier_blob_keeps_it ( ) {
6380
hermetic ( ) ;
6381
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6382
let root = dir.path ( ) ;
6383
let repo = Repo:: init( root) .unwrap ( ) ;
6384
write ( root, " bin" , b " \0 A" ) ;
6385
let v1 = repo.commit ( " v1" , & [ ] ) .unwrap ( ) ;
6386
write ( root, " bin" , b " \0 B" ) ;
6387
let v2 = repo.commit ( " v2" , & [ ] ) .unwrap ( ) ;
6388
assert! ( repo.store ( ) .get_change ( & v2) .unwrap ( ) .deps.contains ( & v1) ) ;
6389
write ( root, " bin" , b " \0 A" ) ;
6390
let v3 = repo.commit ( " back to A" , & [ ] ) .unwrap ( ) ;
6391
assert! ( repo.store ( ) .get_change ( & v3) .unwrap ( ) .deps.contains ( & v2) ) ;
6392
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ v3] ) ;
6393
let state = repo.load_state ( & [ v3] ) .unwrap ( ) ;
6394
let snap = state.materialize ( ) ;
6395
let FileContent:: Opaque { blobs } = & snap.files[ " bin" ] .content else {
6396
panic! ( " opaque" )
6397
} ;
6398
assert_eq! ( blobs.len ( ) , 1 , " one live blob, not none and not two" ) ;
6399
assert_eq! ( repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) , b " \0 A" ) ;
6400
}
6401
6402
6403
6404
6405
# [ test ]
6406
fn git_import_resolves_a_binary_merged_in_git_and_verifies ( ) {
6407
hermetic ( ) ;
6408
if std:: process:: Command:: new( " git" )
6409
.arg ( " --version" )
6410
.output ( )
6411
.is_err ( )
6412
{
6413
eprintln! ( " git not installed; skipping" ) ;
6414
return ;
6415
}
6416
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6417
let root = dir.path ( ) ;
6418
let g = root.join ( " src.git" ) ;
6419
fs:: create_dir_all( & g) .unwrap ( ) ;
6420
let git = | args : & [& str ]| -> String {
6421
let out = std:: process:: Command:: new( " git" )
6422
.arg ( " -C" )
6423
.arg ( & g)
6424
.args ( [
6425
" -c" ,
6426
" user.name=Grace" ,
6427
" -c" ,
6428
" [email protected] " ,
6429
" -c" ,
6430
" commit.gpgsign=false" ,
6431
] )
6432
.args ( args)
6433
.output ( )
6434
.unwrap ( ) ;
6435
assert! (
6436
out.status.success ( ) ,
6437
" git {args:?}: {}" ,
6438
String :: from_utf8_lossy( & out.stderr)
6439
) ;
6440
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
6441
} ;
6442
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
6443
write ( & g, " logo.bin" , b " \0 base" ) ;
6444
write ( & g, " notes.txt" , b " a\n b\n c\n " ) ;
6445
git ( & [ " add" , " ." ] ) ;
6446
git ( & [ " commit" , " -qm" , " base" ] ) ;
6447
git ( & [ " checkout" , " -qb" , " side" ] ) ;
6448
write ( & g, " logo.bin" , b " \0 side" ) ;
6449
write ( & g, " notes.txt" , b " a\n b\n x\n c\n " ) ;
6450
git ( & [ " commit" , " -qam" , " side" ] ) ;
6451
git ( & [ " checkout" , " -q" , " main" ] ) ;
6452
write ( & g, " logo.bin" , b " \0 main" ) ;
6453
git ( & [ " commit" , " -qam" , " main" ] ) ;
6454
6455
let _ = std:: process:: Command:: new( " git" )
6456
.arg ( " -C" )
6457
.arg ( & g)
6458
.args ( [
6459
" -c" ,
6460
" user.name=G" ,
6461
" -c" ,
6462
" user.email=g@e" ,
6463
" merge" ,
6464
" -q" ,
6465
" side" ,
6466
] )
6467
.output ( ) ;
6468
write ( & g, " logo.bin" , b " \0 main" ) ;
6469
git ( & [ " add" , " ." ] ) ;
6470
git ( & [ " commit" , " -qm" , " merge side" ] ) ;
6471
6472
write ( & g, " logo.bin" , b " \0 base" ) ;
6473
git ( & [ " commit" , " -qam" , " back to base" ] ) ;
6474
6475
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
6476
let rep = repo.import_git ( & g, " main" ) .unwrap ( ) ;
6477
assert_eq! ( rep.merges, 1 ) ;
6478
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
6479
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
6480
assert_eq! ( v.commits, 5 ) ;
6481
let snap = repo
6482
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
6483
.unwrap ( )
6484
.materialize ( ) ;
6485
assert! ( ! snap.is_conflicted ( ) ) ;
6486
let FileContent:: Opaque { blobs } = & snap.files[ " logo.bin" ] .content else {
6487
panic! ( " opaque" )
6488
} ;
6489
assert_eq! ( repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) , b " \0 base" ) ;
6490
}
c863c7a7 Andy McCall 1 month ago ↶
6491
6492
6493
6494
6495
# [ cfg ( unix ) ]
6496
# [ test ]
6497
fn windows_file_systems_keep_exec_bits_and_symlinks ( ) {
6498
use crate :: portable:: Platform;
6499
use std:: os:: unix:: fs:: PermissionsExt;
6500
hermetic ( ) ;
6501
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6502
let root = dir.path ( ) ;
6503
let repo = Repo:: init( root) .unwrap ( ) ;
6504
write ( root, " run.sh" , b " #!/bin/sh\n " ) ;
6505
fs:: set_permissions( root.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
6506
std:: os:: unix:: fs:: symlink( " run.sh" , root.join ( " link" ) ) .unwrap ( ) ;
6507
repo.commit ( " script and link" , & [ ] ) .unwrap ( ) ;
6508
drop ( repo) ;
6509
6510
6511
fs:: set_permissions( root.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6512
fs:: remove_file( root.join ( " link" ) ) .unwrap ( ) ;
6513
write ( root, " link" , b " run.sh" ) ;
6514
let win = Repo:: open( root) .unwrap ( ) .with_platform ( Platform:: windows( ) ) ;
6515
let s = win.snapshot ( " oxy status" ) .unwrap ( ) ;
6516
assert! (
6517
s.statuses.is_empty ( ) ,
6518
" nothing changed on Windows: {:?}" ,
6519
statuses ( & s)
6520
) ;
6521
6522
write ( root, " run.sh" , b " #!/bin/sh\n echo hi\n " ) ;
6523
write ( root, " link" , b " other" ) ;
6524
let h = win.commit ( " edit on Windows" , & [ ] ) .unwrap ( ) ;
6525
let snap = win.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
6526
assert! ( snap.files[ " run.sh" ] .mode.exec, " the exec bit survives" ) ;
6527
assert_eq! ( snap.files[ " link" ] .kind, NodeKind:: Symlink) ;
6528
6529
let linux = Repo:: open( root) .unwrap ( ) ;
6530
assert! ( ! linux.snapshot ( " oxy status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6531
}
6532
6533
6534
6535
# [ test ]
6536
fn paths_other_platforms_cannot_hold_are_reported ( ) {
6537
use crate :: portable:: Platform;
6538
hermetic ( ) ;
6539
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6540
let root = dir.path ( ) ;
6541
let repo = Repo:: init( root) .unwrap ( ) ;
6542
write ( root, " README.md" , b " a\n " ) ;
6543
write ( root, " readme.md" , b " b\n " ) ;
6544
write ( root, " src/aux.c" , b " int x;\n " ) ;
6545
let h = repo.commit ( " unportable" , & [ ] ) .unwrap ( ) ;
6546
let r = repo.doctor ( false ) ;
6547
let port = r.checks.iter ( ) .find ( | c | c.area = = " portability" ) .unwrap ( ) ;
6548
assert_eq! ( port.health, Health:: Warn) ;
6549
assert! (
6550
port.message.contains ( " differ only in case" ) & & port.message.contains ( " aux.c" ) ,
6551
" {}" ,
6552
port.message
6553
) ;
6554
let mac = Repo:: open( root) .unwrap ( ) .with_platform ( Platform:: macos( ) ) ;
6555
let rep = mac.checkout ( & [ h] , true , " oxy checkout" ) .unwrap ( ) ;
6556
assert! (
6557
rep.notes
6558
.iter ( )
6559
.any ( | n | n.contains ( " README.md and readme.md" ) ) ,
6560
" {:?}" ,
6561
rep.notes
6562
) ;
6563
assert! (
6564
! rep.notes.iter ( ) .any ( | n | n.contains ( " aux.c" ) ) ,
6565
" macOS allows aux.c"
6566
) ;
6567
}
bc489b6c Andy McCall 1 month ago ↶
6568
6569
# [ test ]
6570
fn history_and_blame_of_a_loaded_state_match_the_full_walk ( ) {
6571
hermetic ( ) ;
6572
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6573
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
6574
write ( dir.path ( ) , " a.txt" , b " one\n " ) ;
6575
write ( dir.path ( ) , " other.txt" , b " x\n " ) ;
6576
write ( dir.path ( ) , " bin.dat" , & [ 0 u8 , 1 , 2 , 0 , 255 ] ) ;
6577
repo.snapshot ( " t" ) .unwrap ( ) ;
6578
let c1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
6579
write ( dir.path ( ) , " other.txt" , b " y\n " ) ;
6580
repo.snapshot ( " t" ) .unwrap ( ) ;
6581
repo.commit ( " other only" , & [ ] ) .unwrap ( ) ;
6582
write ( dir.path ( ) , " a.txt" , b " one\n two\n " ) ;
6583
repo.snapshot ( " t" ) .unwrap ( ) ;
6584
let c3 = repo.commit ( " second line" , & [ ] ) .unwrap ( ) ;
6585
write ( dir.path ( ) , " other.txt" , b " z\n " ) ;
6586
repo.snapshot ( " t" ) .unwrap ( ) ;
6587
repo.commit ( " other again" , & [ ] ) .unwrap ( ) ;
6588
6589
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6590
let state = repo.load_state ( & heads) .unwrap ( ) ;
6591
let full: Vec< ChangeHash> = repo
6592
.log_for_path_in ( " a.txt" , & heads)
6593
.unwrap ( )
6594
.iter ( )
6595
.map ( | e | e.hash )
6596
.collect ( ) ;
6597
assert_eq! (
6598
full,
6599
vec! [ c3, c1] ,
6600
" newest first, only changes touching the path"
6601
) ;
6602
let newest: Vec< ChangeHash> = repo
6603
.log_for_path_of ( & state, " a.txt" , Some ( 1 ) )
6604
.unwrap ( )
6605
.iter ( )
6606
.map ( | e | e.hash )
6607
.collect ( ) ;
6608
assert_eq! ( newest, vec! [ c3] , " stops at the first" ) ;
6609
assert_eq! (
6610
repo.log_for_path_of ( & state, " a.txt" , None ) .unwrap ( ) .len ( ) ,
6611
2
6612
) ;
6613
assert! ( repo
6614
.log_for_path_of ( & state, " missing.txt" , Some ( 1 ) )
6615
.is_err ( ) ) ;
6616
assert_eq! (
6617
repo.log_state ( & heads) .unwrap ( ) .len ( ) ,
6618
repo.log_of ( & state) .len ( )
6619
) ;
6620
6621
let tree = state.tree ( ) .view ( ) ;
6622
let via_state = repo.blame_of ( & state, & tree, " a.txt" ) .unwrap ( ) ;
6623
let via_heads = repo.blame_in ( " a.txt" , & heads) .unwrap ( ) ;
6624
assert_eq! (
6625
via_state
6626
.iter ( )
6627
.map ( | l | ( l.hash, l.text.clone ( ) ) )
6628
.collect:: < Vec< _ > > ( ) ,
6629
via_heads
6630
.iter ( )
6631
.map ( | l | ( l.hash, l.text.clone ( ) ) )
6632
.collect:: < Vec< _ > > ( )
6633
) ;
6634
assert_eq! (
6635
via_state.iter ( ) .map ( | l | l.hash ) .collect:: < Vec< _ > > ( ) ,
6636
vec! [ c1, c3]
6637
) ;
6638
assert! ( repo.blame_of ( & state, & tree, " missing.txt" ) .is_err ( ) ) ;
6639
}
696bbf29 Andy McCall 1 month ago ↶
6640
6641
6642
6643
6644
# [ cfg ( unix ) ]
6645
# [ test ]
6646
fn stat_cache_takes_unchanged_text_from_the_base_and_misses_no_edit ( ) {
6647
use std:: os:: unix:: fs:: PermissionsExt;
6648
hermetic ( ) ;
6649
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6650
let root = dir.path ( ) ;
6651
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6652
write ( root, " a.txt" , b " one\n two\n " ) ;
6653
write ( root, " b.txt" , b " bee\n " ) ;
6654
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6655
let age = | p : & str | {
6656
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6657
fs:: File:: options( )
6658
.write ( true )
6659
.open ( root.join ( p) )
6660
.unwrap ( )
6661
.set_modified ( old)
6662
.unwrap ( ) ;
6663
} ;
6664
age ( " a.txt" ) ;
6665
age ( " b.txt" ) ;
6666
assert! (
6667
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
6668
" records the aged stat"
6669
) ;
6670
6671
6672
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6673
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
6674
assert! (
6675
! snap.changed & & snap.statuses.is_empty ( ) ,
6676
" {:?}" ,
6677
snap.statuses
6678
) ;
6679
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6680
6681
6682
6683
write ( root, " b.txt" , b " bee edited\n " ) ;
6684
age ( " b.txt" ) ;
6685
let first = repo.snapshot ( " status" ) .unwrap ( ) ;
6686
assert_eq! (
6687
statuses ( & first) ,
6688
vec! [ ( " b.txt" .to_string ( ) , ChangeStatus:: Modified) ]
6689
) ;
6690
let again = repo.snapshot ( " status" ) .unwrap ( ) ;
6691
assert_eq! (
6692
statuses ( & again) ,
6693
vec! [ ( " b.txt" .to_string ( ) , ChangeStatus:: Modified) ] ,
6694
" still seen once stat-clean"
6695
) ;
6696
assert! ( ! again.changed, " and the working copy hashes the same" ) ;
6697
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6698
assert! (
6699
repo.snapshot ( " status" ) .is_err ( ) ,
6700
" an edited file must be read, never taken from the base"
6701
) ;
6702
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6703
6704
6705
write ( root, " a.txt" , b " one\n TWO\n " ) ;
6706
assert_eq! (
6707
statuses ( & repo.snapshot ( " status" ) .unwrap ( ) ) ,
6708
vec! [
6709
( " a.txt" .to_string ( ) , ChangeStatus:: Modified) ,
6710
( " b.txt" .to_string ( ) , ChangeStatus:: Modified)
6711
]
6712
) ;
6713
6714
repo.commit ( " edits" , & [ ] ) .unwrap ( ) ;
6715
age ( " a.txt" ) ;
6716
age ( " b.txt" ) ;
6717
assert! ( repo.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6718
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6719
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6720
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
6721
assert! ( ! snap.changed & & snap.statuses.is_empty ( ) ) ;
6722
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6723
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6724
}
afaba5f1 Andy McCall 1 month ago ↶
6725
6726
6727
6728
6729
# [ test ]
6730
fn checkpoints_keep_large_graphs_as_blobs_of_their_own ( ) {
6731
hermetic ( ) ;
6732
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6733
let root = dir.path ( ) ;
6734
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6735
let long: String = ( 0 .. 300 )
6736
.map ( | i | format! ( " line {i} of a long file\n " ) )
6737
.collect ( ) ;
6738
write ( root, " long.txt" , long.as_bytes ( ) ) ;
6739
write ( root, " short.txt" , b " short\n " ) ;
6740
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6741
let ( r1, _ , _ ) = repo.checkpoint ( ) .unwrap ( ) ;
6742
let cp1 = crate :: objects:: Checkpoint:: decode(
6743
& repo.store ( ) .get_typed ( & r1, ObjectType:: Checkpoint) .unwrap ( ) ,
6744
)
6745
.unwrap ( ) ;
6746
let graphs1 = repo.checkpoint_graph_blobs ( & cp1) .unwrap ( ) ;
6747
assert_eq! ( graphs1.len ( ) , 1 , " only the long file's graph is a blob" ) ;
6748
6749
6750
repo.gc ( true ) .unwrap ( ) ;
6751
assert! ( repo.store ( ) .has ( & graphs1[ 0 ] ) ) ;
6752
let report = repo.fsck ( ) .unwrap ( ) ;
6753
assert_eq! ( report.errors ( ) , 0 , " {:?}" , report.problems) ;
6754
6755
6756
write ( root, " short.txt" , b " short, edited\n " ) ;
6757
repo.commit ( " edit the short file" , & [ ] ) .unwrap ( ) ;
6758
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6759
let fast = repo.load_state ( & heads) .unwrap ( ) ;
6760
let full = repo.load_state_full ( & heads) .unwrap ( ) ;
6761
assert_eq! ( fast.checkpoint_payload ( ) , full.checkpoint_payload ( ) ) ;
6762
6763
6764
let ( r2, _ , _ ) = repo.checkpoint ( ) .unwrap ( ) ;
6765
let cp2 = crate :: objects:: Checkpoint:: decode(
6766
& repo.store ( ) .get_typed ( & r2, ObjectType:: Checkpoint) .unwrap ( ) ,
6767
)
6768
.unwrap ( ) ;
6769
assert_eq! ( repo.checkpoint_graph_blobs ( & cp2) .unwrap ( ) , graphs1) ;
6770
6771
6772
let bundle = repo.checkpoint_bundle_for ( & heads) .unwrap ( ) ;
6773
assert! ( bundle.objects.iter ( ) .any ( | ( t , body ) | * t = = ObjectType:: Blob
6774
& & crate :: hash:: hash_object( ObjectType:: Blob, body) = = graphs1[ 0 ] ) ) ;
6775
6776
6777
let state = repo.load_state_full ( & heads) .unwrap ( ) ;
6778
let old = repo.store_blob ( & state.checkpoint_payload ( ) ) .unwrap ( ) ;
6779
let cp_old = crate :: objects:: Checkpoint {
6780
heads: cp2.heads.clone ( ) ,
6781
count: cp2.count,
6782
payload: old,
6783
} ;
6784
let r_old = repo
6785
.store ( )
6786
.put ( ObjectType:: Checkpoint, & cp_old.encode ( ) )
6787
.unwrap ( ) ;
6788
repo.install_checkpoint ( " main" , & r_old) .unwrap ( ) ;
6789
assert_eq! (
6790
repo.load_state ( & heads) .unwrap ( ) .checkpoint_payload ( ) ,
6791
full.checkpoint_payload ( )
6792
) ;
6793
assert_eq! ( repo.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
6794
6795
6796
6797
let dir2 = tempfile:: tempdir( ) .unwrap ( ) ;
6798
let root2 = dir2.path ( ) ;
6799
let repo2 = Repo:: init( root2) .unwrap ( ) .with_home ( root2.join ( " home" ) ) ;
6800
write ( root2, " long.txt" , long.as_bytes ( ) ) ;
6801
repo2.commit ( " base" , & [ ] ) .unwrap ( ) ;
6802
let ( r, _ , _ ) = repo2.checkpoint ( ) .unwrap ( ) ;
6803
let cp = crate :: objects:: Checkpoint:: decode(
6804
& repo2.store ( ) .get_typed ( & r, ObjectType:: Checkpoint) .unwrap ( ) ,
6805
)
6806
.unwrap ( ) ;
6807
let graph = repo2.checkpoint_graph_blobs ( & cp) .unwrap ( ) [ 0 ] ;
64818938 Andy McCall 1 month ago ↶
6808
repo2.store ( ) .remove_loose_for_test ( & graph) .unwrap ( ) ;
afaba5f1 Andy McCall 1 month ago ↶
6809
assert! ( repo2.load_state ( & repo2.bookmark_heads ( ) .unwrap ( ) ) .is_err ( ) ) ;
6810
assert! ( repo2.fsck ( ) .unwrap ( ) .errors ( ) > 0 , " fsck reports it" ) ;
6811
}
e3d071ce Andy McCall 1 month ago ↶
6812
6813
6814
6815
6816
# [ test ]
6817
fn identity_is_found_in_the_key_directory_for_a_new_repository ( ) {
6818
hermetic ( ) ;
6819
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6820
let home = dir.path ( ) .join ( " home" ) ;
6821
let mut first = Repo:: init( & dir.path ( ) .join ( " a" ) )
6822
.unwrap ( )
6823
.with_home ( home.clone ( ) ) ;
6824
let ( key, _ ) = first
6825
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
6826
.unwrap ( ) ;
6827
let fresh = Repo:: init( & dir.path ( ) .join ( " b" ) )
6828
.unwrap ( )
6829
.with_home ( home.clone ( ) ) ;
6830
assert! ( ! fresh.store ( ) .has ( & key) , " nothing stored yet" ) ;
6831
let id = fresh.identity ( & key) .unwrap ( ) ;
6832
assert_eq! ( ( id.name.as_str ( ) , id.hash ( ) ) , ( " Ada" , key) ) ;
6833
6834
let keys = fresh.keys_dir.clone ( ) ;
6835
let ( other, _ ) = first
6836
.create_identity ( " Bob" , " [email protected] " , IdentityScope:: User)
6837
.unwrap ( ) ;
6838
fs:: copy(
6839
keys.join ( format! ( " {} .identity" , other.to_hex ( ) ) ) ,
6840
keys.join ( format! ( " {} .identity" , key.to_hex ( ) ) ) ,
6841
)
6842
.unwrap ( ) ;
6843
assert! ( fresh
6844
.identity ( & key)
6845
.unwrap_err ( )
6846
.to_string ( )
6847
.contains ( " does not hold identity" ) ) ;
6848
}
cc7a0056 Andy McCall 1 month ago ↶
6849
6850
6851
6852
6853
6854
# [ cfg ( unix ) ]
6855
# [ test ]
6856
fn status_snapshots_skip_files_unchanged_since_the_manifest ( ) {
6857
use std:: os:: unix:: fs:: PermissionsExt;
6858
hermetic ( ) ;
6859
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6860
let root = dir.path ( ) ;
6861
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6862
for ( p, c) in [ ( " a.txt" , " one\n " ) , ( " b.txt" , " bee\n " ) , ( " d/c.txt" , " sea\n " ) ] {
6863
write ( root, p, c.as_bytes ( ) ) ;
6864
}
6865
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6866
let age = | p : & str | {
6867
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6868
fs:: File:: options( )
6869
.write ( true )
6870
.open ( root.join ( p) )
6871
.unwrap ( )
6872
.set_modified ( old)
6873
.unwrap ( ) ;
6874
} ;
6875
let lock = | p : & str , mode : u32 | {
6876
fs:: set_permissions( root.join ( p) , fs:: Permissions:: from_mode( mode) ) .unwrap ( )
6877
} ;
6878
for p in [ " a.txt" , " b.txt" , " d/c.txt" ] {
6879
age ( p) ;
6880
}
6881
assert! ( repo.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6882
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
6883
assert! (
6884
m.entries.values ( ) .all ( | e | e.graph.is_some ( ) ) ,
6885
" keys recorded"
6886
) ;
6887
6888
6889
6890
lock ( " a.txt" , 0o000 ) ;
6891
lock ( " b.txt" , 0o000 ) ;
6892
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6893
assert! ( ! snap.changed & & snap.statuses.is_empty ( ) ) ;
6894
assert_eq! (
6895
snap.base_snapshot
6896
.unrendered
6897
.iter ( )
6898
.cloned ( )
6899
.collect:: < Vec< _ > > ( ) ,
6900
vec! [ " a.txt" , " b.txt" , " d/c.txt" ]
6901
) ;
6902
assert! ( ! snap.base.is_full ( ) ) ;
6903
assert! ( ! snap.base_snapshot.is_conflicted ( ) ) ;
6904
6905
let full = repo.snapshot ( " status" ) .unwrap ( ) ;
6906
assert! ( full.statuses.is_empty ( ) & & full.base.is_full ( ) ) ;
6907
assert! ( full.base_snapshot.unrendered.is_empty ( ) ) ;
6908
assert! ( matches! ( & full.base_snapshot.files[ " a.txt" ] .content,
6909
FileContent:: Text { bytes, .. } if bytes = = b " one\n " ) ) ;
6910
6911
6912
write ( root, " d/c.txt" , b " sea edited\n " ) ;
6913
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6914
assert_eq! (
6915
statuses ( & snap) ,
6916
vec! [ ( " d/c.txt" .to_string ( ) , ChangeStatus:: Modified) ]
6917
) ;
6918
assert! ( ! snap.base_snapshot.unrendered.contains ( " d/c.txt" ) ) ;
6919
6920
lock ( " b.txt" , 0o100 ) ;
6921
assert_eq! (
6922
statuses ( & repo.snapshot_status ( " status" ) .unwrap ( ) ) ,
6923
vec! [
6924
( " b.txt" .to_string ( ) , ChangeStatus:: ModeChanged) ,
6925
( " d/c.txt" .to_string ( ) , ChangeStatus:: Modified)
6926
]
6927
) ;
6928
lock ( " b.txt" , 0o000 ) ;
6929
6930
6931
6932
repo.commit_selection ( " edit c" , & [ ] , & Selection:: Paths( vec! [ " d/c.txt" .into ( ) ] ) )
6933
.unwrap ( ) ;
6934
age ( " d/c.txt" ) ;
6935
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6936
assert! ( snap.statuses.is_empty ( ) , " {:?}" , snap.statuses) ;
6937
assert! ( snap.base_snapshot.unrendered.contains ( " a.txt" ) ) ;
6938
lock ( " d/c.txt" , 0o000 ) ;
6939
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6940
assert! ( snap.statuses.is_empty ( ) ) ;
6941
assert! ( snap.base_snapshot.unrendered.contains ( " d/c.txt" ) ) ;
6942
for p in [ " a.txt" , " b.txt" , " d/c.txt" ] {
6943
lock ( p, 0o644 ) ;
6944
}
6945
}
6946
6947
6948
6949
# [ cfg ( unix ) ]
6950
# [ test ]
6951
fn a_changed_attributes_file_disables_skipping_unchanged_files ( ) {
6952
hermetic ( ) ;
6953
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6954
let root = dir.path ( ) ;
6955
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6956
write ( root, " a.txt" , b " crlf\r \n " ) ;
6957
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6958
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6959
fs:: File:: options( )
6960
.write ( true )
6961
.open ( root.join ( " a.txt" ) )
6962
.unwrap ( )
6963
.set_modified ( old)
6964
.unwrap ( ) ;
6965
assert! ( repo.snapshot_status ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6966
assert! ( repo
6967
.snapshot_status ( " status" )
6968
.unwrap ( )
6969
.base_snapshot
6970
.unrendered
6971
.contains ( " a.txt" ) ) ;
6972
6973
write ( root, " .oxyattributes" , b " *.txt eol=lf\n " ) ;
6974
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6975
assert_eq! (
6976
statuses ( & snap) ,
6977
vec! [
6978
( " .oxyattributes" .to_string ( ) , ChangeStatus:: Added) ,
6979
( " a.txt" .to_string ( ) , ChangeStatus:: Modified)
6980
]
6981
) ;
6982
}
54cd08a1 Andy McCall 1 month ago ↶
6983
6984
6985
6986
6987
# [ test ]
6988
fn bookmarks_with_slashes_push_and_checkpoint ( ) {
6989
hermetic ( ) ;
6990
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6991
let root = dir.path ( ) ;
6992
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
6993
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
6994
let mut repo = Repo:: clone( & url, & root.join ( " r" ) )
6995
.unwrap ( )
6996
.with_home ( root.join ( " home" ) ) ;
6997
6998
6999
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
7000
.unwrap ( ) ;
7001
let r = root.join ( " r" ) ;
7002
write ( & r, " a.txt" , b " a\n " ) ;
7003
let h_a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
7004
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 17 days ago
7005
origin.refresh ( ) .unwrap ( ) ;
54cd08a1 Andy McCall 1 month ago ↶
7006
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
7007
repo.bookmark_create ( " feature/x" , None ) .unwrap ( ) ;
7008
repo.bookmark_switch ( " feature/x" , false ) .unwrap ( ) ;
7009
write ( & r, " b.txt" , b " b\n " ) ;
7010
let h_b = repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
7011
for name in [ " feature" , " feature/x" ] {
7012
let res = repo.push_bookmark ( " origin" , name, false ) .unwrap ( ) ;
767ac6cd Andy McCall 17 days ago
7013
origin.refresh ( ) .unwrap ( ) ;
54cd08a1 Andy McCall 1 month ago ↶
7014
assert! ( res[ 0 ] .ok, " {name}: {res:?}" ) ;
7015
}
7016
assert_eq! ( origin.resolve_heads ( " feature" ) .unwrap ( ) , vec! [ h_a] ) ;
7017
assert_eq! (
7018
origin.resolve_heads ( " feature/x" ) .unwrap ( ) ,
7019
repo.resolve_heads ( " feature/x" ) .unwrap ( )
7020
) ;
7021
assert! ( origin.resolve_heads ( " feature/x" ) .unwrap ( ) .contains ( & h_b) ) ;
7022
assert! ( origin.latest_ref_attest ( " feature/x" ) .unwrap ( ) .is_some ( ) ) ;
7023
assert! ( origin.latest_ref_attest ( " feature" ) .unwrap ( ) .is_some ( ) ) ;
7024
7025
7026
let ( cp, _ , _ ) = repo.checkpoint ( ) .unwrap ( ) ;
7027
assert_eq! ( repo.latest_checkpoint ( " feature/x" ) .unwrap ( ) .unwrap ( ) .0 , cp) ;
7028
assert! ( repo.latest_checkpoint ( " feature" ) .unwrap ( ) .is_none ( ) ) ;
7029
let heads = repo.resolve_heads ( " feature/x" ) .unwrap ( ) ;
7030
assert! ( repo.load_state_checkpointed ( & heads) .unwrap ( ) .is_some ( ) ) ;
f8c69576 Andy McCall 1 month ago ↶
7031
}
7032
7033
7034
7035
7036
# [ test ]
7037
fn bookmarks_are_deleted_on_a_remote ( ) {
7038
hermetic ( ) ;
7039
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7040
let root = dir.path ( ) ;
7041
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
7042
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
7043
let mut repo = Repo:: clone( & url, & root.join ( " r" ) )
7044
.unwrap ( )
7045
.with_home ( root.join ( " home" ) ) ;
7046
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
7047
.unwrap ( ) ;
7048
let r = root.join ( " r" ) ;
7049
write ( & r, " a.txt" , b " a\n " ) ;
7050
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
7051
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 17 days ago
7052
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
7053
repo.bookmark_create ( " feature/x" , None ) .unwrap ( ) ;
7054
assert! ( repo.push_bookmark ( " origin" , " feature/x" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 17 days ago
7055
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
7056
assert! ( origin.resolve_heads ( " feature/x" ) .is_ok ( ) ) ;
7057
7058
let res = repo.push_delete ( " origin" , " feature/x" ) .unwrap ( ) ;
767ac6cd Andy McCall 17 days ago
7059
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
7060
assert! ( res[ 0 ] .ok, " {res:?}" ) ;
7061
assert! ( ! origin.view ( ) .unwrap ( ) .bookmarks.contains_key ( " feature/x" ) ) ;
7062
let ( _ , att) = origin.latest_ref_attest ( " feature/x" ) .unwrap ( ) .unwrap ( ) ;
7063
assert! (
7064
matches! ( att.attest.body, AttestBody:: Ref { new_state, .. } if new_state.is_zero ( ) ) ,
7065
" the deletion is attested"
7066
) ;
7067
assert! (
7068
repo.view ( ) .unwrap ( ) .bookmarks.contains_key ( " feature/x" ) ,
7069
" the local bookmark stays"
7070
) ;
7071
assert! ( ! repo
7072
.view ( )
7073
.unwrap ( )
7074
.remotes
7075
.get ( " origin" )
7076
.is_some_and ( | m | m.contains_key ( " feature/x" ) ) ) ;
7077
7078
7079
let res = repo.push_bookmark ( " origin" , " feature/x" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 17 days ago
7080
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
7081
assert! ( res[ 0 ] .ok, " {res:?}" ) ;
7082
assert! ( origin.view ( ) .unwrap ( ) .bookmarks.contains_key ( " feature/x" ) ) ;
7083
7084
7085
assert! ( repo.push_delete ( " origin" , " nope" ) .is_err ( ) ) ;
7086
let res = repo.push_delete ( " origin" , " main" ) .unwrap ( ) ;
767ac6cd Andy McCall 17 days ago
7087
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
7088
assert! (
7089
! res[ 0 ] .ok & & res[ 0 ] .reason.contains ( " default bookmark" ) ,
7090
" {res:?}"
7091
) ;
7092
assert! ( origin.view ( ) .unwrap ( ) .bookmarks.contains_key ( " main" ) ) ;
54cd08a1 Andy McCall 1 month ago ↶
7093
}
61e3bce7 Andy McCall 1 month ago ↶
7094
7095
7096
7097
7098
# [ test ]
7099
fn switching_bookmarks_carries_uncommitted_work ( ) {
7100
hermetic ( ) ;
7101
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7102
let root = dir.path ( ) ;
7103
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
7104
write ( root, " a.txt" , b " a\n " ) ;
7105
write ( root, " b.txt" , b " b\n " ) ;
7106
write ( root, " gone.txt" , b " gone\n " ) ;
7107
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7108
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
7109
7110
write ( root, " a.txt" , b " a\n more\n " ) ;
7111
write ( root, " new.txt" , b " new\n " ) ;
7112
fs:: remove_file( root.join ( " gone.txt" ) ) .unwrap ( ) ;
44f1ebe7 Andy McCall 17 days ago ↶
7113
let ( report, carried) = repo.bookmark_switch_carry ( " feature" ) .unwrap ( ) ;
61e3bce7 Andy McCall 1 month ago ↶
7114
assert_eq! ( carried, vec! [ " a.txt" , " gone.txt" , " new.txt" ] ) ;
44f1ebe7 Andy McCall 17 days ago ↶
7115
7116
assert_eq! (
7117
( report.removed, report.removed_paths.len ( ) ) ,
7118
( 0 , 0 ) ,
7119
" {report:?}"
7120
) ;
61e3bce7 Andy McCall 1 month ago ↶
7121
assert_eq! ( repo.config.bookmark, " feature" ) ;
7122
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " a\n more\n " ) ;
7123
assert_eq! ( fs:: read( root.join ( " new.txt" ) ) .unwrap ( ) , b " new\n " ) ;
7124
assert! ( ! root.join ( " gone.txt" ) .exists ( ) ) ;
7125
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
7126
assert_eq! (
7127
statuses ( & snap) ,
7128
vec! [
7129
( " a.txt" .to_string ( ) , ChangeStatus:: Modified) ,
7130
( " gone.txt" .to_string ( ) , ChangeStatus:: Removed) ,
7131
( " new.txt" .to_string ( ) , ChangeStatus:: Added)
7132
]
7133
) ;
7134
repo.commit ( " the work" , & [ ] ) .unwrap ( ) ;
7135
assert! ( repo.resolve_heads ( " main" ) .unwrap ( ) .len ( ) = = 1 ) ;
7136
7137
7138
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
7139
write ( root, " b.txt" , b " b on main\n " ) ;
7140
repo.commit ( " b on main" , & [ ] ) .unwrap ( ) ;
7141
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
7142
write ( root, " b.txt" , b " b edited\n " ) ;
7143
let err = repo.bookmark_switch_carry ( " main" ) .unwrap_err ( ) .to_string ( ) ;
7144
assert! ( err.contains ( " b.txt" ) & & err.contains ( " --discard" ) , " {err}" ) ;
7145
assert_eq! ( repo.config.bookmark, " feature" , " nothing happened" ) ;
7146
assert_eq! ( fs:: read( root.join ( " b.txt" ) ) .unwrap ( ) , b " b edited\n " ) ;
7147
}
1c2bd9ab Andy McCall 1 month ago ↶
7148
7149
7150
7151
7152
# [ test ]
7153
fn merges_move_clean_working_copies_and_checkouts_say_what_blocks_them ( ) {
7154
hermetic ( ) ;
7155
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7156
let root = dir.path ( ) ;
7157
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
7158
write ( root, " a.txt" , b " a\n " ) ;
7159
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
7160
write ( root, " b.txt" , b " b\n " ) ;
7161
repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
7162
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
7163
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
7164
write ( root, " c.txt" , b " c\n " ) ;
7165
repo.commit ( " c on feature" , & [ ] ) .unwrap ( ) ;
7166
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
7167
7168
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
7169
assert! ( ! root.join ( " b.txt" ) .exists ( ) ) ;
7170
let m = repo.merge ( " feature" ) .unwrap ( ) ;
7171
assert! ( m.checked_out, " a clean working copy follows the merge" ) ;
7172
for f in [ " a.txt" , " b.txt" , " c.txt" ] {
7173
assert! ( root.join ( f) .exists ( ) , " {f}" ) ;
7174
}
7175
7176
7177
fs:: remove_file( root.join ( " b.txt" ) ) .unwrap ( ) ;
7178
write ( root, " a.txt" , b " a edited\n " ) ;
7179
let err = repo
7180
.checkout ( & [ h1] , false , " checkout" )
7181
.unwrap_err ( )
7182
.to_string ( ) ;
7183
assert! (
7184
err.contains ( " modified a.txt" ) & & err.contains ( " removed b.txt" ) & & err.contains ( " --carry" ) ,
7185
" {err}"
7186
) ;
fe9586d9 Andy McCall 24 days ago ↶
7187
7188
7189
1c2bd9ab Andy McCall 1 month ago ↶
7190
let ( _ , carried) = repo
7191
.checkout_carry ( & [ h1] , " the first change" , " checkout" )
7192
.unwrap ( ) ;
7193
assert_eq! ( carried, vec! [ " a.txt" ] ) ;
7194
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " a edited\n " ) ;
7195
assert! ( ! root.join ( " b.txt" ) .exists ( ) , " h1 has no b.txt" ) ;
7196
}
e7ac6c2e Andy McCall 1 month ago ↶
7197
7198
7199
7200
7201
# [ test ]
7202
fn a_new_repository_is_healthy_before_its_first_commit ( ) {
7203
hermetic ( ) ;
7204
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7205
let root = dir.path ( ) ;
7206
let home = root.join ( " home" ) ;
7207
let mut first = Repo:: init( & root.join ( " first" ) )
7208
.unwrap ( )
7209
.with_home ( home.clone ( ) ) ;
7210
let ( key, _ ) = first
7211
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
7212
.unwrap ( ) ;
7213
let repo = Repo:: init( & root.join ( " second" ) )
7214
.unwrap ( )
7215
.with_home ( home.clone ( ) ) ;
7216
7217
let keys = repo.keys_dir.clone ( ) ;
7218
let _ = fs:: remove_file( keys.join ( format! ( " {} .identity" , key.to_hex ( ) ) ) ) ;
7219
assert! ( keys.join ( format! ( " {} .secret" , key.to_hex ( ) ) ) .is_file ( ) ) ;
7220
let r = repo.doctor ( true ) ;
7221
let id = r.checks.iter ( ) .find ( | c | c.area = = " identity" ) .unwrap ( ) ;
7222
assert_eq! ( id.health, Health:: Ok, " {}" , id.message) ;
7223
assert! ( id.message.contains ( " first signed change" ) , " {}" , id.message) ;
7224
7225
7226
let lib = root.join ( " lib" ) ;
7227
let l = Repo:: init( & lib) .unwrap ( ) .with_home ( home.clone ( ) ) ;
7228
write ( & lib, " x.txt" , b " x\n " ) ;
7229
l.commit ( " x" , & [ ] ) .unwrap ( ) ;
7230
let second = root.join ( " second" ) ;
7231
write ( & second, " a.txt" , b " a\n " ) ;
7232
repo.submodule_add ( & lib.to_string_lossy ( ) , " deps/lib" , None )
7233
.unwrap ( ) ;
7234
let subs = repo.subrepos ( ) .unwrap ( ) ;
7235
assert_eq! ( subs.len ( ) , 1 ) ;
7236
assert_eq! (
7237
( subs[ 0 ] .path.as_str ( ) , subs[ 0 ] .committed) ,
7238
( " deps/lib" , false )
7239
) ;
7240
assert_eq! ( subs[ 0 ] .actual.as_ref ( ) , Some ( & subs[ 0 ] .pin) ) ;
7241
repo.commit ( " add lib" , & [ ] ) .unwrap ( ) ;
7242
let subs = repo.subrepos ( ) .unwrap ( ) ;
7243
assert! ( subs[ 0 ] .committed, " committed now" ) ;
7244
}
8ab265dc Andy McCall 1 month ago ↶
7245
7246
7247
7248
7249
# [ cfg ( unix ) ]
7250
# [ test ]
7251
fn commits_and_diffs_leave_unchanged_files_alone ( ) {
7252
use std:: os:: unix:: fs:: PermissionsExt;
7253
hermetic ( ) ;
7254
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7255
let root = dir.path ( ) ;
7256
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
7257
write ( root, " big.txt" , b " lots\n of\n lines\n " ) ;
7258
write ( root, " a.txt" , b " one\n two\n " ) ;
7259
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7260
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
7261
for f in [ " big.txt" , " a.txt" ] {
7262
fs:: File:: options( )
7263
.write ( true )
7264
.open ( root.join ( f) )
7265
.unwrap ( )
7266
.set_modified ( old)
7267
.unwrap ( ) ;
7268
}
7269
assert! ( repo.snapshot_status ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
7270
7271
fs:: set_permissions( root.join ( " big.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
7272
7273
write ( root, " a.txt" , b " one\n TWO\n three\n " ) ;
7274
let diff = repo.diff_files ( None ) .unwrap ( ) ;
7275
assert_eq! (
7276
diff.iter ( ) .map ( | f | f.path.as_str ( ) ) .collect:: < Vec< _ > > ( ) ,
7277
[ " a.txt" ]
7278
) ;
7279
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
7280
assert! ( ! hunks.is_empty ( ) & & hunks.iter ( ) .all ( | h | h.path = = " a.txt" ) ) ;
7281
let h = repo.commit ( " edit a" , & [ ] ) .unwrap ( ) ;
7282
7283
7284
let c = repo.store ( ) .get_change ( & h) .unwrap ( ) ;
7285
let full = repo.load_state ( & c.deps) .unwrap ( ) ;
7286
let want = render_change ( & c, & full, & full.materialize ( ) ) ;
7287
assert_eq! ( repo.describe_change ( & h) .unwrap ( ) , want) ;
7288
let ( base, snap) = repo.change_base ( & c) .unwrap ( ) ;
7289
assert! ( ! base.is_full ( ) & & snap.unrendered.contains ( " big.txt" ) ) ;
7290
7291
7292
write ( root, " a.txt" , b " scratch\n " ) ;
7293
assert_eq! ( repo.discard ( & [ " a.txt" .into ( ) ] ) .unwrap ( ) , 1 ) ;
7294
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n TWO\n three\n " ) ;
7295
fs:: set_permissions( root.join ( " big.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
7296
}
f7510b63 Andy McCall 1 month ago ↶
7297
7298
7299
7300
7301
# [ test ]
7302
fn replace_conflicts_are_settled_when_asked ( ) {
7303
hermetic ( ) ;
7304
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7305
let root = dir.path ( ) ;
7306
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
7307
let blob = | n : u8 | {
7308
let mut v = vec! [ 0 u8 ; 64 ] ;
7309
v[ 1 ] = n;
7310
v
7311
} ;
7312
write ( root, " img.bin" , & blob ( 1 ) ) ;
7313
repo.commit ( " add img" , & [ ] ) .unwrap ( ) ;
7314
repo.bookmark_create ( " other" , None ) .unwrap ( ) ;
7315
write ( root, " img.bin" , & blob ( 2 ) ) ;
7316
repo.commit ( " img two" , & [ ] ) .unwrap ( ) ;
7317
repo.bookmark_switch ( " other" , false ) .unwrap ( ) ;
7318
write ( root, " img.bin" , & blob ( 3 ) ) ;
7319
repo.commit ( " img three" , & [ ] ) .unwrap ( ) ;
7320
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
7321
repo.merge ( " other" ) .unwrap ( ) ;
7322
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
7323
let merged = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
7324
assert! (
7325
matches! ( & merged.files[ " img.bin" ] .content, FileContent:: Opaque { blobs } if blobs.len ( ) = = 2 ) ,
7326
" a replace conflict"
7327
) ;
7328
7329
assert! ( repo
7330
.snapshot ( " status" )
7331
.unwrap ( )
7332
.statuses
7333
.iter ( )
7334
.all ( | s | s.path ! = " img.bin" ) ) ;
7335
7336
7337
let author = repo.author ( ) ;
7338
let edits = [ FileEdit {
7339
path: " img.bin" .into ( ) ,
7340
content: Some ( blob ( 3 ) ) ,
7341
} ] ;
7342
let c = repo
7343
.build_change ( & heads, & edits, " keep three" , author)
7344
.unwrap ( ) ;
7345
assert_eq! ( c.kind, ChangeKind:: Resolution) ;
7346
let mut st = repo.load_state ( & heads) .unwrap ( ) ;
7347
for d in & c.deps {
7348
assert! ( st.contains ( d) ) ;
7349
}
7350
assert! (
7351
heads.iter ( ) .all ( | h | c.deps.contains ( h) ) ,
7352
" depends on both contributors"
7353
) ;
7354
st.add ( c) .unwrap ( ) ;
7355
let after = st.materialize ( ) ;
7356
assert! ( ! after.is_conflicted ( ) ) ;
7357
assert_eq! (
7358
after.files[ " img.bin" ] .content,
7359
FileContent:: Opaque {
7360
blobs: vec! [ Repo:: blob_ref( & blob ( 3 ) ) ]
7361
}
7362
) ;
7363
7364
7365
let edits = [ FileEdit {
7366
path: " img.bin" .into ( ) ,
7367
content: Some ( blob ( 9 ) ) ,
7368
} ] ;
7369
let c = repo.build_change ( & heads, & edits, " new" , author) .unwrap ( ) ;
7370
let mut st = repo.load_state ( & heads) .unwrap ( ) ;
7371
st.add ( c) .unwrap ( ) ;
7372
assert_eq! (
7373
st.materialize ( ) .files[ " img.bin" ] .content,
7374
FileContent:: Opaque {
7375
blobs: vec! [ Repo:: blob_ref( & blob ( 9 ) ) ]
7376
}
7377
) ;
7378
}
83d61e80 Andy McCall 1 month ago ↶
7379
7380
7381
7382
7383
# [ test ]
7384
fn line_ranges_have_exact_histories ( ) {
7385
hermetic ( ) ;
7386
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7387
let root = dir.path ( ) ;
7388
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
7389
write ( root, " f.rs" , b " use x;\n fn a() {\n one();\n two();\n }\n " ) ;
7390
let base = repo.commit ( " add f" , & [ ] ) .unwrap ( ) ;
7391
write ( root, " f.rs" , b " use y;\n fn a() {\n one();\n two();\n }\n " ) ;
7392
let outside = repo.commit ( " change the import" , & [ ] ) .unwrap ( ) ;
7393
write ( root, " f.rs" , b " use y;\n fn a() {\n ONE();\n two();\n }\n " ) ;
7394
let inside = repo.commit ( " edit a line of a" , & [ ] ) .unwrap ( ) ;
7395
write ( root, " f.rs" , b " use y;\n fn a() {\n ONE();\n }\n " ) ;
7396
let deleted = repo.commit ( " delete a line of a" , & [ ] ) .unwrap ( ) ;
7397
write ( root, " f.rs" , b " use y;\n fn a() {\n ONE();\n }\n fn b() {}\n " ) ;
7398
let after = repo.commit ( " add b after a" , & [ ] ) .unwrap ( ) ;
7399
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
7400
7401
7402
let hist: Vec< ChangeHash> = repo
7403
.line_history ( & heads, " f.rs" , 2 , 4 )
7404
.unwrap ( )
7405
.iter ( )
7406
.map ( | e | e.hash )
7407
.collect ( ) ;
7408
for ( h, what) in [
7409
( base, " wrote it" ) ,
7410
( inside, " edited a line" ) ,
7411
( deleted, " deleted a line from inside" ) ,
7412
] {
7413
assert! ( hist.contains ( & h) , " {what}" ) ;
7414
}
7415
for ( h, what) in [ ( outside, " an edit above" ) , ( after, " an addition below" ) ] {
7416
assert! ( ! hist.contains ( & h) , " {what}" ) ;
7417
}
7418
assert_eq! ( hist[ 0 ] , deleted, " newest first" ) ;
7419
7420
let one: Vec< ChangeHash> = repo
7421
.line_history ( & heads, " f.rs" , 1 , 1 )
7422
.unwrap ( )
7423
.iter ( )
7424
.map ( | e | e.hash )
7425
.collect ( ) ;
7426
assert! ( one.contains ( & outside) & & ! one.contains ( & inside) ) ;
7427
assert! ( repo.line_history ( & heads, " f.rs" , 3 , 9 ) .is_err ( ) ) ;
7428
assert! ( repo.line_history ( & heads, " f.rs" , 0 , 1 ) .is_err ( ) ) ;
7429
assert! ( repo.line_history ( & heads, " nope.rs" , 1 , 1 ) .is_err ( ) ) ;
7430
}
fc1a466a Andy McCall 1 month ago ↶
7431
7432
# [ test ]
7433
fn receipts_are_kept_with_their_state_and_verified ( ) {
7434
use crate :: receipt:: { Receipt, SignedReceipt} ;
7435
hermetic ( ) ;
7436
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7437
let root = dir.path ( ) ;
7438
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
7439
write ( root, " a.txt" , b " 1\n " ) ;
7440
repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
7441
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
7442
let state = repo
7443
.store ( )
7444
.put_state ( & StateObject:: new( heads.clone ( ) ) )
7445
.unwrap ( ) ;
7446
let out = repo.store_blob ( b " built\n " ) .unwrap ( ) ;
7447
let receipt = Receipt {
7448
job_name: " build" .into ( ) ,
7449
pipeline_hash: ObjectRef( [ 1 ; 32 ] ) ,
7450
scope_tree_hash: ObjectRef( [ 2 ; 32 ] ) ,
7451
inputs: vec! [ ] ,
7452
environment: ObjectRef( [ 3 ; 32 ] ) ,
7453
outputs: vec! [ out] ,
7454
exit_code: 0 ,
7455
duration_ms: 42 ,
7456
} ;
7457
let sk = SecretKey:: generate( ) ;
7458
let runner = sk.identity ( " earth-1" , " runner@earth" , 1 ) ;
7459
let sr = SignedReceipt:: sign(
7460
receipt.clone ( ) ,
7461
repo.id ( ) .unwrap ( ) ,
7462
state,
7463
" demo#1" ,
7464
& runner,
7465
& sk,
7466
) ;
7467
7468
let r = repo.add_receipt ( & sr) .unwrap ( ) ;
7469
let found = repo.receipts ( & heads) .unwrap ( ) ;
7470
assert_eq! ( found.len ( ) , 1 ) ;
7471
assert_eq! ( found[ 0 ] .attest, r) ;
7472
assert_eq! ( found[ 0 ] .signed, sr) ;
7473
assert! ( found[ 0 ] .problem.is_none ( ) & & ! found[ 0 ] .revoked) ;
7474
assert! ( repo.receipts ( & [ ObjectRef( [ 5 ; 32 ] ) ] ) .unwrap ( ) .is_empty ( ) ) ;
7475
7476
7477
let elsewhere = SignedReceipt:: sign( receipt.clone ( ) , [ 0 ; 16 ] , state, " x" , & runner, & sk) ;
7478
assert! ( repo
7479
.add_receipt ( & elsewhere)
7480
.unwrap_err ( )
7481
.to_string ( )
7482
.contains ( " another repository" ) ) ;
7483
let mut forged = sr.clone ( ) ;
7484
forged.receipt.exit_code = 1 ;
7485
assert! ( repo
7486
.add_receipt ( & forged)
7487
.unwrap_err ( )
7488
.to_string ( )
7489
.contains ( " does not verify" ) ) ;
7490
c63fed2e Andy McCall 18 days ago ↶
7491
7492
7493
let hub_sk = SecretKey:: generate( ) ;
7494
let hub = hub_sk.identity ( " oxygenhub.io" , " [email protected] " , 1 ) ;
7495
let job_sk = SecretKey:: generate( ) ;
7496
let job = job_sk.identity ( " job 2 test" , " [email protected] " , 1 ) ;
7497
let mut test_receipt = receipt.clone ( ) ;
7498
test_receipt.job_name = " test" .into ( ) ;
7499
let plain = SignedReceipt:: sign(
7500
test_receipt,
7501
repo.id ( ) .unwrap ( ) ,
7502
state,
7503
" demo#2" ,
7504
& job,
7505
& job_sk,
7506
) ;
7507
let now = plain.attest.hlc.wall_ms;
7508
let cert = crate :: cert:: SignedCertificate:: sign(
7509
crate :: cert:: Certificate {
7510
key: job.clone ( ) ,
7511
issuer: hub.hash ( ) ,
7512
subject: " repo:andy/demo:bookmark:main" .into ( ) ,
7513
claims: vec! [ ] ,
7514
scope: crate :: identity:: Scope:: SEAL ,
7515
not_before: now - 1_000 ,
7516
expires: now + 600_000 ,
a2387d54 Andy McCall 18 days ago ↶
7517
grant: None ,
c63fed2e Andy McCall 18 days ago ↶
7518
} ,
7519
& hub,
7520
& hub_sk,
7521
) ;
7522
let certified = plain.with_certificate ( cert, hub.clone ( ) ) ;
7523
repo.add_receipt ( & certified) .unwrap ( ) ;
7524
let found = repo.receipts ( & heads) .unwrap ( ) ;
7525
let got = found
7526
.iter ( )
7527
.find ( | i | i.signed.receipt.job_name = = " test" )
7528
.unwrap ( ) ;
7529
assert_eq! ( got.signed, certified) ;
7530
assert! ( got.problem.is_none ( ) , " {:?}" , got.problem) ;
7531
7532
7533
fc1a466a Andy McCall 1 month ago ↶
7534
repo.gc ( true ) .unwrap ( ) ;
c63fed2e Andy McCall 18 days ago ↶
7535
let found = repo.receipts ( & heads) .unwrap ( ) ;
7536
assert_eq! ( found.len ( ) , 2 ) ;
7537
let got = found
7538
.iter ( )
7539
.find ( | i | i.signed.receipt.job_name = = " test" )
7540
.unwrap ( ) ;
7541
assert_eq! ( got.signed, certified, " the certificate survives gc" ) ;
7542
assert! ( got.problem.is_none ( ) ) ;
fc1a466a Andy McCall 1 month ago ↶
7543
assert_eq! ( repo.read_blob ( & out) .unwrap ( ) , b " built\n " ) ;
7544
assert! ( repo.fsck ( ) .unwrap ( ) .problems.is_empty ( ) ) ;
dfb23a3a Andy McCall 1 month ago ↶
7545
}
7546
7547
# [ test ]
3a4b3e01 Andy McCall 20 days ago ↶
7548
fn approvals_are_kept_with_their_state_and_survive_gc ( ) {
7549
hermetic ( ) ;
7550
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7551
let root = dir.path ( ) ;
7552
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
7553
write ( root, " a.txt" , b " 1\n " ) ;
7554
repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
7555
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
7556
let state = repo.store ( ) .put_state ( & StateObject:: new( heads) ) .unwrap ( ) ;
7557
let plan = repo.store_blob ( b " the reviewed plan\n " ) .unwrap ( ) ;
7558
let sk = SecretKey:: generate( ) ;
7559
let andy = sk.identity ( " Andy" , " [email protected] " , 1 ) ;
7560
let statement = crate :: approval:: Statement {
7561
repo: repo.id ( ) .unwrap ( ) ,
7562
run: " https://hub.example/andy/infra/flow/8" .into ( ) ,
7563
job: " apply" .into ( ) ,
7564
job_hash: ObjectRef( [ 1 ; 32 ] ) ,
7565
state,
7566
inputs: vec! [ plan] ,
7567
environment: " prod" .into ( ) ,
7568
approve: true ,
7569
} ;
7570
let sa = crate :: approval:: SignedApproval:: sign( & statement, & andy, & sk) ;
7571
let r = repo.add_approval ( & sa) .unwrap ( ) ;
7572
assert_eq! ( repo.store ( ) .approvals_for ( & state) .unwrap ( ) , vec! [ r] ) ;
7573
7574
let elsewhere = crate :: approval:: SignedApproval:: sign(
7575
& crate :: approval:: Statement {
7576
repo: [ 0 ; 16 ] ,
7577
.. statement.clone ( )
7578
} ,
7579
& andy,
7580
& sk,
7581
) ;
7582
assert! ( repo
7583
.add_approval ( & elsewhere)
7584
.unwrap_err ( )
7585
.to_string ( )
7586
.contains ( " another repository" ) ) ;
7587
let mut forged = sa.clone ( ) ;
7588
if let AttestBody:: Approval { environment, .. } = & mut forged.attest.body {
7589
* environment = " dev" .into ( ) ;
7590
}
7591
assert! ( repo
7592
.add_approval ( & forged)
7593
.unwrap_err ( )
7594
.to_string ( )
7595
.contains ( " does not verify" ) ) ;
7596
7597
repo.gc ( true ) .unwrap ( ) ;
7598
assert_eq! ( repo.store ( ) .approvals_for ( & state) .unwrap ( ) , vec! [ r] ) ;
7599
assert_eq! ( repo.store ( ) .get_attest ( & r) .unwrap ( ) , sa.attest) ;
7600
assert_eq! ( repo.store ( ) .signatures ( & r) .unwrap ( ) , sa.sigs) ;
7601
assert! ( repo.fsck ( ) .unwrap ( ) .problems.is_empty ( ) ) ;
7602
}
7603
7604
# [ test ]
dfb23a3a Andy McCall 1 month ago ↶
7605
fn status_leaves_an_unchanged_manifest_alone ( ) {
7606
hermetic ( ) ;
7607
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7608
let root = dir.path ( ) ;
7609
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
7610
write ( root, " a.txt" , b " 1\n " ) ;
7611
write ( root, " b.txt" , b " 2\n " ) ;
7612
repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
7613
7614
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 60 ) ;
7615
for f in [ " a.txt" , " b.txt" ] {
7616
fs:: File:: options( )
7617
.write ( true )
7618
.open ( root.join ( f) )
7619
.unwrap ( )
7620
.set_modified ( old)
7621
.unwrap ( ) ;
7622
}
7623
repo.snapshot_status ( " status" ) .unwrap ( ) ;
7624
let path = repo.manifest_path ( ) ;
7625
let first = fs:: read( & path) .unwrap ( ) ;
7626
repo.snapshot_status ( " status" ) .unwrap ( ) ;
7627
assert_eq! (
7628
fs:: read( & path) .unwrap ( ) ,
7629
first,
7630
" an unchanged tree does not rewrite the manifest"
7631
) ;
7632
7633
write ( root, " a.txt" , b " 1\n 2\n " ) ;
7634
repo.snapshot_status ( " status" ) .unwrap ( ) ;
7635
assert_ne! ( fs:: read( & path) .unwrap ( ) , first) ;
fc1a466a Andy McCall 1 month ago ↶
7636
}
6f8aeb99 Andy McCall 1 month ago ↶
7637
7638
# [ test ]
7639
fn large_text_files_travel_and_survive_gc_as_text_blobs ( ) {
7640
hermetic ( ) ;
7641
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7642
let root = dir.path ( ) ;
7643
let origin_path = root.join ( " origin" ) ;
7644
Repo:: init( & origin_path) .unwrap ( ) ;
7645
let origin_url = origin_path.to_string_lossy ( ) .to_string ( ) ;
7646
let a = Repo:: clone( & origin_url, & root.join ( " a" ) ) .unwrap ( ) ;
7647
let big: String = ( 0 .. 5000 ) .map ( | i | format! ( " line {i} \n " ) ) .collect ( ) ;
7648
write ( & root.join ( " a" ) , " big.txt" , big.as_bytes ( ) ) ;
7649
write ( & root.join ( " a" ) , " small.txt" , b " small\n " ) ;
7650
let h1 = a.commit ( " large" , & [ ] ) .unwrap ( ) ;
7651
let ( _ , body) = a.store ( ) .get ( & h1) .unwrap ( ) ;
7652
assert! ( body.len ( ) < 2048 , " the change is {} bytes" , body.len ( ) ) ;
7653
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7654
7655
7656
let b = Repo:: clone( & origin_url, & root.join ( " b" ) ) .unwrap ( ) ;
7657
assert_eq! ( fs:: read( root.join ( " b/big.txt" ) ) .unwrap ( ) , big.as_bytes ( ) ) ;
7658
7659
let edited = big.replace ( " line 2500\n " , " line 2500, edited\n " ) ;
7660
write ( & root.join ( " b" ) , " big.txt" , edited.as_bytes ( ) ) ;
7661
let h2 = b.commit ( " edit" , & [ ] ) .unwrap ( ) ;
7662
assert! ( b.store ( ) .get_change ( & h2) .unwrap ( ) .edits.len ( ) < = 2 ) ;
7663
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7664
assert! ( a.pull ( " origin" ) .unwrap ( ) .checked_out) ;
7665
assert_eq! ( fs:: read( root.join ( " a/big.txt" ) ) .unwrap ( ) , edited.as_bytes ( ) ) ;
7666
assert_eq! (
7667
a.fsck ( ) .unwrap ( ) .errors ( ) ,
7668
0 ,
7669
" {:?}" ,
7670
a.fsck ( ) .unwrap ( ) .problems
7671
) ;
7672
7673
7674
let mut a = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) ;
7675
a.config.set ( " core.op-retention" , " 0" ) .unwrap ( ) ;
7676
a.save_config ( ) .unwrap ( ) ;
7677
let a = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) ;
7678
a.gc ( true ) .unwrap ( ) ;
7679
let a = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) ;
7680
assert_eq! ( a.store ( ) .get_change ( & h1) .unwrap ( ) .message, " large" ) ;
7681
assert_eq! (
7682
a.fsck ( ) .unwrap ( ) .errors ( ) ,
7683
0 ,
7684
" {:?}" ,
7685
a.fsck ( ) .unwrap ( ) .problems
7686
) ;
7687
7688
let c = Repo:: clone( & root.join ( " a" ) .to_string_lossy ( ) , & root.join ( " c" ) ) .unwrap ( ) ;
7689
assert_eq! ( fs:: read( root.join ( " c/big.txt" ) ) .unwrap ( ) , edited.as_bytes ( ) ) ;
7690
drop ( c) ;
6caacfff Andy McCall 1 month ago ↶
7691
}
7692
7693
# [ test ]
7694
fn operations_that_both_replace_a_working_copy_are_not_merged ( ) {
7695
hermetic ( ) ;
7696
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7697
let root = dir.path ( ) ;
7698
let repo = Repo:: init( root) .unwrap ( ) ;
7699
write ( root, " a.txt" , b " a\n " ) ;
7700
let a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
7701
write ( root, " b.txt" , b " b\n " ) ;
7702
repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
7703
let ( x, op) = repo.head_op ( ) .unwrap ( ) ;
7704
let ws = * op.view.workspaces.keys ( ) .next ( ) .unwrap ( ) ;
7705
let mut one = op.view.clone ( ) ;
7706
one.workspaces.insert ( ws, a) ;
7707
let mut two = op.view.clone ( ) ;
7708
two.workspaces.insert ( ws, ObjectRef( [ 7 ; 32 ] ) ) ;
7709
let first = repo.record_op ( & [ x] , " one" , one) .unwrap ( ) ;
7710
let e = repo.record_op ( & [ x] , " two" , two) .unwrap_err ( ) ;
7711
assert! (
7712
e.to_string ( )
7713
.contains ( " changed this working copy at the same time" ) ,
7714
" {e}"
7715
) ;
7716
assert_eq! (
7717
repo.head_op ( ) .unwrap ( ) .0 ,
7718
first,
7719
" the second is not recorded"
7720
) ;
7721
}
7722
7723
7724
7725
7726
# [ test ]
7727
fn a_snapshot_during_a_switch_never_records_the_switch_as_edits ( ) {
7728
hermetic ( ) ;
7729
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7730
let root = dir.path ( ) .to_path_buf ( ) ;
7731
let mut repo = Repo:: init( & root) .unwrap ( ) ;
7732
write ( & root, " keep.txt" , b " keep\n " ) ;
7733
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7734
repo.bookmark_create ( " small" , None ) .unwrap ( ) ;
7735
for i in 0 .. 300 {
7736
write (
7737
& root,
7738
& format! ( " many/f{i} .txt" ) ,
7739
format! ( " file {i} \n " ) .as_bytes ( ) ,
7740
) ;
7741
}
7742
repo.commit ( " many" , & [ ] ) .unwrap ( ) ;
7743
let stop = std:: sync:: Arc:: new( std:: sync:: atomic:: AtomicBool:: new( false ) ) ;
7744
let watcher = {
7745
let ( root, stop) = ( root.clone ( ) , stop.clone ( ) ) ;
7746
std:: thread:: spawn( move | | {
7747
let r = Repo:: open( & root) .unwrap ( ) ;
7748
let mut ran = 0 ;
7749
while ! stop.load ( std:: sync:: atomic:: Ordering:: Relaxed) {
7750
if r.snapshot_status ( " oxy status" ) .is_ok ( ) {
7751
ran + = 1 ;
7752
}
7753
}
7754
ran
7755
} )
7756
} ;
7757
for i in 0 .. 20 {
7758
let to = if i % 2 = = 0 { " small" } else { " main" } ;
7759
repo.bookmark_switch ( to, false ) .unwrap ( ) ;
7760
}
7761
stop.store ( true , std:: sync:: atomic:: Ordering:: Relaxed) ;
7762
assert! ( watcher.join ( ) .unwrap ( ) > 0 , " the watcher snapshotted" ) ;
7763
7764
let repo = Repo:: open( & root) .unwrap ( ) ;
7765
let s = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7766
assert! ( s.statuses.is_empty ( ) , " {:?}" , statuses ( & s) ) ;
7767
assert! ( s.wc.edits.is_empty ( ) & & s.wc.tree_ops.is_empty ( ) ) ;
7768
assert_eq! ( s.wc.deps, repo.bookmark_heads ( ) .unwrap ( ) ) ;
7769
assert! ( root.join ( " many/f299.txt" ) .exists ( ) ) ;
1a3b6203 Andy McCall 1 month ago ↶
7770
}
7771
7772
7773
7774
7775
# [ test ]
7776
fn a_parent_switch_with_carry_leaves_a_subrepo_ahead_of_its_pin_alone ( ) {
7777
hermetic ( ) ;
7778
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7779
let root = dir.path ( ) ;
7780
Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
7781
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
7782
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) ) .unwrap ( ) ;
7783
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
7784
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
7785
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7786
7787
let app_dir = root.join ( " app" ) ;
7788
let mut app = Repo:: init( & app_dir) .unwrap ( ) ;
7789
write ( & app_dir, " main.txt" , b " app\n " ) ;
7790
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
7791
app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
7792
app.bookmark_create ( " feature" , None ) .unwrap ( ) ;
7793
7794
7795
let nested_dir = app_dir.join ( " libs/lib" ) ;
7796
let nested = Repo:: open( & nested_dir) .unwrap ( ) ;
7797
write ( & nested_dir, " new.txt" , b " new\n " ) ;
7798
let l2 = nested.commit ( " lib: new file" , & [ ] ) .unwrap ( ) ;
7799
drop ( nested) ;
7800
7801
let ( _ , carried) = app.bookmark_switch_carry ( " feature" ) .unwrap ( ) ;
7802
assert! ( carried.contains ( & " libs/lib" .to_string ( ) ) , " {carried:?}" ) ;
7803
let nested = Repo:: open( & nested_dir) .unwrap ( ) ;
7804
assert! (
7805
nested.bookmark_heads ( ) .unwrap ( ) .contains ( & l2) ,
7806
" its bookmark kept its work"
7807
) ;
7808
assert! ( nested_dir.join ( " new.txt" ) .exists ( ) , " and its files" ) ;
7809
let s = app.snapshot ( " status" ) .unwrap ( ) ;
7810
assert_eq! (
7811
statuses ( & s) ,
7812
vec! [ ( " libs/lib" .to_string ( ) , ChangeStatus:: Modified) ]
7813
) ;
6f8aeb99 Andy McCall 1 month ago ↶
7814
}
7d9dab13 Andy McCall 1 month ago ↶
7815
7816
7817
7818
7819
# [ test ]
7820
fn pull_takes_a_remote_move_to_changes_already_here ( ) {
7821
hermetic ( ) ;
7822
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7823
let root = dir.path ( ) ;
7824
let origin_path = root.join ( " origin" ) ;
7825
let origin = Repo:: init( & origin_path) .unwrap ( ) ;
7826
let url = origin_path.to_string_lossy ( ) .to_string ( ) ;
7827
let mut b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) ;
7828
write ( & root.join ( " b" ) , " base.txt" , b " base\n " ) ;
7829
b.commit ( " base" , & [ ] ) .unwrap ( ) ;
7830
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 17 days ago
7831
origin.refresh ( ) .unwrap ( ) ;
7d9dab13 Andy McCall 1 month ago ↶
7832
7833
7834
b.bookmark_create ( " feat" , None ) .unwrap ( ) ;
7835
b.bookmark_switch ( " feat" , false ) .unwrap ( ) ;
7836
write ( & root.join ( " b" ) , " x.txt" , b " x\n " ) ;
7837
let x = b.commit ( " x" , & [ ] ) .unwrap ( ) ;
7838
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 17 days ago
7839
origin.refresh ( ) .unwrap ( ) ;
7d9dab13 Andy McCall 1 month ago ↶
7840
b.bookmark_switch ( " main" , false ) .unwrap ( ) ;
7841
7842
7843
let feat = origin
7844
.store ( )
7845
.get_state ( & origin.view ( ) .unwrap ( ) .bookmarks[ " feat" ] )
7846
.unwrap ( )
7847
.heads;
7848
origin.bookmark_set ( " main" , & feat) .unwrap ( ) ;
7849
7850
let r = b.pull ( " origin" ) .unwrap ( ) ;
7851
assert! ( r.changed, " {r:?}" ) ;
7852
assert! ( r.heads.contains ( & x) | | b.load_state ( & r.heads) .unwrap ( ) .contains ( & x) ) ;
7853
let main = b
7854
.bookmarks ( )
7855
.unwrap ( )
7856
.into_iter ( )
7857
.find ( | m | m.name = = " main" )
7858
.unwrap ( ) ;
7859
assert_eq! ( main.behind, Some ( 0 ) , " {main:?}" ) ;
7860
}
86e1824f Andy McCall 1 month ago ↶
7861
14edbaaf Andy McCall 1 month ago ↶
7862
7863
7864
7865
7866
# [ test ]
7867
fn remote_bookmarks_are_named_switched_to_pulled_and_cloned ( ) {
7868
hermetic ( ) ;
7869
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7870
let root = dir.path ( ) ;
7871
let origin_path = root.join ( " origin" ) ;
7872
let mut origin = Repo:: init( & origin_path) .unwrap ( ) ;
7873
let url = origin_path.to_string_lossy ( ) .to_string ( ) ;
7874
let mut a = Repo:: clone( & url, & root.join ( " a" ) ) .unwrap ( ) ;
7875
write ( & root.join ( " a" ) , " base.txt" , b " base\n " ) ;
7876
a.commit ( " base" , & [ ] ) .unwrap ( ) ;
7877
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7878
a.bookmark_create ( " develop" , None ) .unwrap ( ) ;
7879
a.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
7880
write ( & root.join ( " a" ) , " base.txt" , b " base\n d1\n " ) ;
7881
let d1 = a.commit ( " d1" , & [ ] ) .unwrap ( ) ;
7882
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7883
7884
7885
let c_root = root.join ( " c" ) ;
7886
let mut c = Repo:: clone( & url, & c_root ) .unwrap ( ) ;
7887
assert_eq! ( c.config.bookmark, " main" ) ;
7888
assert! ( ! c.view ( ) .unwrap ( ) .bookmarks.contains_key ( " develop" ) ) ;
7889
assert_eq! ( c.resolve_heads ( " origin/develop" ) .unwrap ( ) , vec! [ d1] ) ;
7890
assert_eq! ( c.resolve_change ( " origin/develop" ) .unwrap ( ) , d1) ;
7891
assert_eq! ( c.remote_only_bookmarks ( ) .unwrap ( ) .len ( ) , 1 ) ;
7892
7893
7894
c.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
7895
assert_eq! ( c.config.bookmark, " develop" ) ;
7896
assert_eq! ( c.bookmark_heads ( ) .unwrap ( ) , vec! [ d1] ) ;
7897
assert_eq! ( fs:: read( c_root .join ( " base.txt" ) ) .unwrap ( ) , b " base\n d1\n " ) ;
7898
assert! ( c.remote_only_bookmarks ( ) .unwrap ( ) .is_empty ( ) ) ;
7899
assert! ( c.bookmark_switch ( " nowhere" , false ) .is_err ( ) ) ;
7900
7901
7902
write ( & root.join ( " a" ) , " base.txt" , b " base\n d1\n d2\n " ) ;
7903
let d2 = a.commit ( " d2" , & [ ] ) .unwrap ( ) ;
7904
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7905
a.bookmark_switch ( " main" , false ) .unwrap ( ) ;
7906
write ( & root.join ( " a" ) , " m.txt" , b " m\n " ) ;
7907
let m = a.commit ( " m" , & [ ] ) .unwrap ( ) ;
7908
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
733ea16a Andy McCall 17 days ago ↶
7909
7910
let f = Repo:: clone( & url, & root.join ( " f" ) ) .unwrap ( ) ;
7911
assert! ( ! f.view ( ) .unwrap ( ) .bookmarks.contains_key ( " develop" ) ) ;
7912
let merged = f.merge ( " origin/develop" ) .unwrap ( ) ;
7913
assert! ( merged.changed, " {merged:?}" ) ;
7914
assert! (
7915
f.load_state_indexed ( & f.bookmark_heads ( ) .unwrap ( ) )
7916
.unwrap ( )
7917
.contains ( & d1) ,
7918
" main holds develop's change"
7919
) ;
7920
assert! ( f
7921
.merge ( " origin/nowhere" )
7922
.unwrap_err ( )
7923
.to_string ( )
7924
.contains ( " no bookmark origin/nowhere" ) ) ;
14edbaaf Andy McCall 1 month ago ↶
7925
let r = c.pull ( " origin" ) .unwrap ( ) ;
7926
assert! ( r.changed & & r.checked_out, " {r:?}" ) ;
7927
assert_eq! ( r.heads, vec! [ d2] ) ;
7928
assert_eq! ( r.others, vec! [ " main" .to_string ( ) ] ) ;
7929
assert! ( c.resolve_heads ( " main" ) .unwrap ( ) .contains ( & m) ) ;
7930
assert_eq! (
7931
fs:: read( c_root .join ( " base.txt" ) ) .unwrap ( ) ,
7932
b " base\n d1\n d2\n "
7933
) ;
7934
let again = c.pull ( " origin" ) .unwrap ( ) ;
7935
assert! ( ! again.changed & & again.others.is_empty ( ) , " {again:?}" ) ;
7936
7937
7938
origin.config.set ( " workspace.bookmark" , " develop" ) .unwrap ( ) ;
7939
origin.save_config ( ) .unwrap ( ) ;
7940
let e = Repo:: clone( & url, & root.join ( " e" ) ) .unwrap ( ) ;
7941
assert_eq! ( e.config.bookmark, " develop" ) ;
7942
assert_eq! ( e.bookmark_heads ( ) .unwrap ( ) , vec! [ d2] ) ;
7943
let reopened = Repo:: open( & root.join ( " e" ) ) .unwrap ( ) ;
7944
assert_eq! ( reopened.config.bookmark, " develop" ) ;
7945
7946
let mut e = reopened;
7947
e.bookmark_switch ( " origin/main" , false ) .unwrap ( ) ;
7948
assert_eq! ( e.config.bookmark, " main" ) ;
7949
assert! ( e.bookmark_heads ( ) .unwrap ( ) .contains ( & m) ) ;
7950
}
7951
98a1620a Andy McCall 1 month ago ↶
7952
7953
7954
7955
7956
# [ test ]
7957
fn operations_can_be_read_restored_and_reverted ( ) {
7958
hermetic ( ) ;
7959
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7960
let root = dir.path ( ) ;
7961
let repo = Repo:: init( root) .unwrap ( ) ;
7962
write ( root, " a.txt" , b " a\n " ) ;
7963
let first = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
7964
write ( root, " a.txt" , b " a\n b\n " ) ;
7965
let second = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
7966
let at_second = repo.head_op ( ) .unwrap ( ) .0. to_hex ( ) ;
7967
repo.bookmark_create ( " keep" , None ) .unwrap ( ) ;
7968
let created = repo.head_op ( ) .unwrap ( ) .0. to_hex ( ) ;
7969
write ( root, " n.txt" , b " n\n " ) ;
7970
let third = repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
7971
7972
7973
let ops = repo.op_log ( ) .unwrap ( ) ;
7974
let changes = repo.view_changes ( & ops[ 1 ] .1. view, & ops[ 0 ] .1. view) .unwrap ( ) ;
7975
assert! (
7976
changes
7977
.iter ( )
7978
.any ( | c | matches! ( c, ViewChange:: BookmarkMoved { name, .. } if name = = " main" ) ) ,
7979
" {changes:?}"
7980
) ;
7981
assert! ( changes
7982
.iter ( )
7983
.any ( | c | matches! ( c, ViewChange:: WorkingCopy { .. } ) ) ) ;
7984
7985
7986
let old = Repo:: open( root) .unwrap ( ) .at_op ( & at_second[ .. 10 ] ) .unwrap ( ) ;
7987
assert_eq! ( old.bookmark_heads ( ) .unwrap ( ) , vec! [ second] ) ;
7988
assert! ( ! old.view ( ) .unwrap ( ) .bookmarks.contains_key ( " keep" ) ) ;
7989
write ( root, " z.txt" , b " z\n " ) ;
7990
assert! ( old.commit ( " refused" , & [ ] ) .is_err ( ) , " --at-op is read-only" ) ;
7991
fs:: remove_file( root.join ( " z.txt" ) ) .unwrap ( ) ;
7992
assert! ( repo.resolve_op ( " zz" ) .is_err ( ) ) ;
7993
assert_eq! ( repo.resolve_op ( " @" ) .unwrap ( ) .0 , repo.head_op ( ) .unwrap ( ) .0 ) ;
7994
7995
7996
repo.op_restore ( & at_second, false ) .unwrap ( ) ;
7997
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ second] ) ;
7998
assert! ( ! root.join ( " n.txt" ) .exists ( ) ) ;
7999
repo.undo ( ) .unwrap ( ) ;
8000
let mut heads = repo.bookmark_heads ( ) .unwrap ( ) ;
8001
heads.sort ( ) ;
8002
let mut want = vec! [ second, third] ;
8003
want.sort ( ) ;
8004
assert_eq! ( heads, want) ;
8005
assert! (
8006
root.join ( " n.txt" ) .exists ( ) ,
8007
" the files came back with the undo"
8008
) ;
8009
8010
8011
let r = repo.op_revert ( & created) .unwrap ( ) ;
8012
assert_eq! ( r.reverted, vec! [ " keep" .to_string ( ) ] ) ;
8013
assert! ( ! repo.view ( ) .unwrap ( ) .bookmarks.contains_key ( " keep" ) ) ;
8014
assert! ( repo
8015
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
8016
.unwrap ( )
8017
.contains ( & third) ) ;
8018
assert! ( repo.op_revert ( & created) .is_err ( ) , " nothing left to revert" ) ;
8019
let _ = first;
8020
}
8021
36041796 Andy McCall 1 month ago ↶
8022
8023
8024
8025
# [ test ]
8026
fn status_with_uncommitted_work_comes_from_the_cache_until_a_file_changes ( ) {
8027
hermetic ( ) ;
8028
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8029
let root = dir.path ( ) ;
8030
let repo = Repo:: init( root) .unwrap ( ) ;
8031
write ( root, " a.txt" , b " a\n " ) ;
8032
write ( root, " b.txt" , b " b\n " ) ;
8033
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
8034
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , Some ( vec! [ ] ) , " clean" ) ;
8035
8036
write ( root, " a.txt" , b " a\n more\n " ) ;
8037
assert_eq! (
8038
repo.status_by_manifest ( ) .unwrap ( ) ,
8039
None ,
8040
" no snapshot of the edit yet"
8041
) ;
8042
let snap = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
8043
assert_eq! ( snap.statuses.len ( ) , 1 ) ;
8044
let cached = repo
8045
.status_by_manifest ( )
8046
.unwrap ( )
8047
.expect ( " answered from the cache" ) ;
8048
assert_eq! ( cached.len ( ) , 1 ) ;
8049
assert_eq! (
8050
( cached[ 0 ] .path.as_str ( ) , cached[ 0 ] .status) ,
8051
( " a.txt" , ChangeStatus:: Modified)
8052
) ;
8053
8054
8055
write ( root, " a.txt" , b " a\n more\n and more\n " ) ;
8056
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None , " edited again" ) ;
8057
repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
8058
assert! ( repo.status_by_manifest ( ) .unwrap ( ) .is_some ( ) ) ;
8059
write ( root, " new.txt" , b " n\n " ) ;
8060
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None , " a file added" ) ;
8061
fs:: remove_file( root.join ( " new.txt" ) ) .unwrap ( ) ;
8062
assert! (
8063
repo.status_by_manifest ( ) .unwrap ( ) .is_some ( ) ,
8064
" back as it was"
8065
) ;
8066
fs:: remove_file( root.join ( " b.txt" ) ) .unwrap ( ) ;
8067
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None , " a file removed" ) ;
8068
write ( root, " b.txt" , b " b\n " ) ;
8069
8070
8071
repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
8072
repo.commit ( " more" , & [ ] ) .unwrap ( ) ;
8073
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , Some ( vec! [ ] ) ) ;
8074
}
8075
77445673 Andy McCall 1 month ago ↶
8076
8077
8078
8079
# [ test ]
8080
fn the_working_copy_diff_from_the_cache_matches_a_snapshot ( ) {
8081
hermetic ( ) ;
8082
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8083
let root = dir.path ( ) ;
8084
let repo = Repo:: init( root) .unwrap ( ) ;
8085
write ( root, " a.txt" , b " one\n two\n three\n " ) ;
8086
write ( root, " gone.txt" , b " bye\n " ) ;
8087
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
8088
write ( root, " a.txt" , b " one\n 2\n three\n four\n " ) ;
8089
write ( root, " new.txt" , b " hello\n " ) ;
8090
fs:: remove_file( root.join ( " gone.txt" ) ) .unwrap ( ) ;
8091
let snap = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
8092
let slow = render_change ( & snap.wc, & snap.base, & snap.base_snapshot) ;
8093
assert! (
8094
repo.status_by_manifest ( ) .unwrap ( ) .is_some ( ) ,
8095
" the cache holds"
8096
) ;
8097
let ( wc, base, snapshot) = repo.working_copy_with_base ( ) .unwrap ( ) ;
8098
assert_eq! ( render_change ( & wc, & base, & snapshot) , slow) ;
8099
assert_eq! ( repo.diff_files ( None ) .unwrap ( ) .len ( ) , 3 ) ;
74b4a3c6 Andy McCall 19 days ago ↶
8100
8101
8102
8103
repo.commit ( " all" , & [ ] ) .unwrap ( ) ;
8104
repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
8105
assert! ( repo.status_by_manifest ( ) .unwrap ( ) .is_some ( ) ) ;
8106
assert! ( repo
8107
.working_copy_with_base_unless_unchanged ( )
8108
.unwrap ( )
8109
.is_none ( ) ) ;
8110
let ( wc, base, snapshot) = repo.working_copy_with_base ( ) .unwrap ( ) ;
8111
assert_eq! ( render_change ( & wc, & base, & snapshot) , " " ) ;
8112
assert! ( repo.diff_files ( None ) .unwrap ( ) .is_empty ( ) ) ;
77445673 Andy McCall 1 month ago ↶
8113
}
8114
1ec3f008 Andy McCall 1 month ago ↶
8115
8116
8117
8118
8119
# [ test ]
8120
fn the_manifest_check_skips_listing_while_no_directory_changed ( ) {
8121
hermetic ( ) ;
8122
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8123
let root = dir.path ( ) ;
8124
let repo = Repo:: init( root) .unwrap ( ) ;
8125
write ( root, " a.txt" , b " a\n " ) ;
8126
write ( root, " sub/deep/b.txt" , b " b\n " ) ;
8127
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
8128
8129
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 60 ) ;
8130
for p in [ " a.txt" , " sub/deep/b.txt" , " sub/deep" , " sub" , " " ] {
8131
fs:: File:: open( root.join ( p) )
8132
.unwrap ( )
8133
.set_modified ( old)
8134
.unwrap ( ) ;
8135
}
8136
repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
8137
repo.write_manifest ( false , false ) .unwrap ( ) ;
8138
let m = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) .2 ;
8139
assert! ( m.dirs.iter ( ) .any ( | ( d , _) | d = = " sub/deep" ) , " {:?}" , m.dirs) ;
8140
assert_eq! (
8141
repo.disk_differs_by_dirs ( & m) ,
8142
Some ( false ) ,
8143
" no listing needed"
8144
) ;
8145
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , Some ( vec! [ ] ) ) ;
8146
8147
8148
write ( root, " sub/deep/b.txt" , b " c\n " ) ;
8149
assert_eq! ( repo.disk_differs_by_dirs ( & m) , Some ( true ) ) ;
8150
write ( root, " sub/deep/b.txt" , b " b\n " ) ;
8151
fs:: File:: open( root.join ( " sub/deep/b.txt" ) )
8152
.unwrap ( )
8153
.set_modified ( old)
8154
.unwrap ( ) ;
8155
assert_eq! ( repo.disk_differs_by_dirs ( & m) , Some ( false ) ) ;
8156
8157
8158
write ( root, " sub/deep/new.txt" , b " n\n " ) ;
8159
assert_eq! ( repo.disk_differs_by_dirs ( & m) , None ) ;
8160
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None ) ;
8161
fs:: remove_file( root.join ( " sub/deep/new.txt" ) ) .unwrap ( ) ;
8162
8163
fs:: remove_file( root.join ( " a.txt" ) ) .unwrap ( ) ;
8164
assert_eq! ( repo.disk_differs_by_dirs ( & m) , None ) ;
8165
}
8166
e226a6cc Andy McCall 1 month ago ↶
8167
8168
8169
8170
# [ test ]
8171
fn a_commit_after_a_status_uses_the_cached_snapshot ( ) {
8172
hermetic ( ) ;
8173
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8174
let root = dir.path ( ) ;
8175
let repo = Repo:: init( root) .unwrap ( ) ;
8176
write ( root, " a.txt" , b " a\n b\n " ) ;
8177
write ( root, " .oxyattributes" , b " *.bin binary\n " ) ;
8178
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
8179
write ( root, " a.txt" , b " a\n B\n " ) ;
8180
write ( root, " new.txt" , b " new\n " ) ;
8181
let snap = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
8182
let cached = repo
8183
.snapshot_from_cache ( )
8184
.unwrap ( )
8185
.expect ( " the cache holds" ) ;
8186
assert_eq! ( cached.wc_hash, snap.wc_hash) ;
8187
assert_eq! ( cached.statuses, snap.statuses) ;
8188
assert! (
8189
! cached.attributes.is_empty ( ) ,
8190
" the .oxyattributes file was read"
8191
) ;
8192
let h = repo.commit ( " edit" , & [ ] ) .unwrap ( ) ;
8193
let c = repo.store ( ) .get_change ( & h) .unwrap ( ) ;
8194
assert_eq! (
8195
c.tree_ops.len ( ) + c.edits.len ( ) ,
8196
snap.wc.tree_ops.len ( ) + snap.wc.edits.len ( )
8197
) ;
8198
assert_eq! (
8199
repo.status_by_manifest ( ) .unwrap ( ) ,
8200
Some ( vec! [ ] ) ,
8201
" clean after the commit"
8202
) ;
8203
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " a\n B\n " ) ;
8204
}
8205
0389c3e8 Andy McCall 1 month ago ↶
8206
8207
8208
8209
8210
# [ test ]
8211
fn a_verified_clean_check_restamps_the_manifest_safely ( ) {
8212
hermetic ( ) ;
8213
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8214
let root = dir.path ( ) ;
8215
let repo = Repo:: init( root) .unwrap ( ) ;
8216
write ( root, " a.txt" , b " one\n " ) ;
8217
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
8218
let before = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) .2. written_ns;
8219
assert! ( repo.clean_by_manifest ( ) .unwrap ( ) ) ;
8220
let after = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) .2. written_ns;
8221
assert! ( after > before, " restamped" ) ;
8222
write ( root, " a.txt" , b " two\n " ) ;
8223
assert! (
8224
! repo.clean_by_manifest ( ) .unwrap ( ) ,
8225
" a same-size rewrite is still seen"
8226
) ;
8227
}
8228
86e1824f Andy McCall 1 month ago ↶
8229
8230
8231
8232
# [ test ]
8233
fn history_only_states_read_text_only_for_the_graphs_they_build ( ) {
8234
hermetic ( ) ;
8235
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8236
let root = dir.path ( ) ;
8237
let repo = Repo:: init( root) .unwrap ( ) ;
8238
let big: String = ( 0 .. 3000 ) .map ( | i | format! ( " big line {i} \n " ) ) .collect ( ) ;
8239
let other: String = ( 0 .. 3000 ) .map ( | i | format! ( " other line {i} \n " ) ) .collect ( ) ;
8240
write ( root, " big.txt" , big.as_bytes ( ) ) ;
8241
write ( root, " other.txt" , other.as_bytes ( ) ) ;
8242
let h = repo.commit ( " two large files" , & [ ] ) .unwrap ( ) ;
8243
write ( root, " big.txt" , format! ( " {big} one more\n " ) .as_bytes ( ) ) ;
8244
let h2 = repo.commit ( " append" , & [ ] ) .unwrap ( ) ;
8245
8246
let full = repo.load_state_full ( & [ h2] ) .unwrap ( ) .materialize ( ) ;
8247
let text = | snap : & crate:: state:: Snapshot, p : & str | match & snap.files[ p] .content {
8248
FileContent:: Text { bytes, .. } => bytes.clone ( ) ,
8249
other => panic! ( " {other:?}" ) ,
8250
} ;
8251
8252
let mut hist = repo.load_state_history ( & [ h2] ) .unwrap ( ) ;
8253
let big_node = full.files[ " big.txt" ] .node;
8254
assert! ( hist.graph_key ( & big_node) .is_some ( ) , " not built yet" ) ;
8255
repo.load_all_graphs ( & mut hist) .unwrap ( ) ;
8256
let rendered = hist.materialize ( ) ;
8257
for p in [ " big.txt" , " other.txt" ] {
8258
assert_eq! ( text ( & rendered, p) , text ( & full, p) , " {p}" ) ;
8259
}
8260
8261
8262
8263
let ( _ , texts) = repo.store ( ) .get_change_lazy ( & h) .unwrap ( ) ;
8264
assert_eq! ( texts.len ( ) , 2 ) ;
8265
let content = | r : & ObjectRef| -> Vec< u8 > {
8266
let blob = crate :: objects:: Blob:: decode( & repo.store ( ) .get ( r) .unwrap ( ) .1 ) .unwrap ( ) ;
8267
blob.chunks
8268
.iter ( )
8269
.flat_map ( | c | repo.store ( ) .get ( c) .unwrap ( ) .1 )
8270
.collect ( )
8271
} ;
8272
let other_text = texts
8273
.iter ( )
a4fad5b0 Andy McCall 1 month ago ↶
8274
.filter_map ( | t | match t {
0f93f49c Andy McCall 1 month ago ↶
8275
crate :: objects:: TextRef:: Blob( r, _ , _ ) => Some ( * r) ,
a4fad5b0 Andy McCall 1 month ago ↶
8276
_ => None ,
8277
} )
86e1824f Andy McCall 1 month ago ↶
8278
.find ( | r | content ( r) .windows ( 12 ) .any ( | w | w = = b " other line 1" ) )
8279
.unwrap ( ) ;
64818938 Andy McCall 1 month ago ↶
8280
repo.store ( ) .remove_loose_for_test ( & other_text) .unwrap ( ) ;
86e1824f Andy McCall 1 month ago ↶
8281
let mut hist = repo.load_state_history ( & [ h2] ) .unwrap ( ) ;
8282
repo.load_graphs_of ( & mut hist, & [ big_node] ) .unwrap ( ) ;
8283
assert! ( repo
8284
.load_graphs_of ( & mut hist, & [ full.files[ " other.txt" ] .node] )
8285
.is_err ( ) ) ;
8286
}
8287
8288
8289
8290
8291
# [ test ]
8292
fn a_commit_leaves_a_manifest_the_next_status_trusts ( ) {
8293
use std:: os:: unix:: fs:: PermissionsExt;
8294
hermetic ( ) ;
8295
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8296
let root = dir.path ( ) ;
8297
let repo = Repo:: init( root) .unwrap ( ) ;
8298
let files = [ " a.txt" , " b.txt" , " d/c.txt" ] ;
8299
for p in files {
8300
write ( root, p, format! ( " {p} \n " ) .as_bytes ( ) ) ;
8301
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
8302
fs:: File:: options( )
8303
.write ( true )
8304
.open ( root.join ( p) )
8305
.unwrap ( )
8306
.set_modified ( old)
8307
.unwrap ( ) ;
8308
}
8309
repo.commit ( " added" , & [ ] ) .unwrap ( ) ;
8310
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
8311
assert! (
8312
m.entries.values ( ) .all ( | e | e.graph.is_some ( ) ) ,
8313
" keys recorded at commit"
8314
) ;
8315
8316
for p in files {
8317
fs:: set_permissions( root.join ( p) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
8318
}
8319
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8320
for p in files {
8321
fs:: set_permissions( root.join ( p) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
8322
}
8323
assert! ( ! snap.changed & & snap.statuses.is_empty ( ) ) ;
8324
assert_eq! ( snap.base_snapshot.unrendered.len ( ) , files.len ( ) ) ;
8325
}
c5ce0367 Andy McCall 1 month ago ↶
8326
8327
8328
8329
# [ test ]
8330
fn checkout_touches_only_what_differs_and_renders_in_batches ( ) {
8331
use std:: os:: unix:: fs:: PermissionsExt;
8332
hermetic ( ) ;
8333
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8334
let root = dir.path ( ) ;
8335
let origin = root.join ( " origin" ) ;
8336
let mut repo = Repo:: init( & origin) .unwrap ( ) ;
8337
8338
let n = 2100 ;
8339
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
8340
for i in 0 .. n {
8341
let p = format! ( " f/{i:04} .txt" ) ;
8342
write ( & origin, & p, format! ( " file {i} \n " ) .as_bytes ( ) ) ;
8343
fs:: File:: options( )
8344
.write ( true )
8345
.open ( origin.join ( & p) )
8346
.unwrap ( )
8347
.set_modified ( old)
8348
.unwrap ( ) ;
8349
}
8350
repo.commit ( " many" , & [ ] ) .unwrap ( ) ;
8351
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
8352
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
8353
write ( & origin, " f/0007.txt" , b " changed\n " ) ;
8354
repo.commit ( " one change" , & [ ] ) .unwrap ( ) ;
8355
8356
8357
for i in 0 .. n {
8358
if i ! = 7 {
8359
let p = origin.join ( format! ( " f/{i:04} .txt" ) ) ;
8360
fs:: set_permissions( & p, fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
8361
}
8362
}
8363
let r = repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
8364
for i in 0 .. n {
8365
let p = origin.join ( format! ( " f/{i:04} .txt" ) ) ;
8366
fs:: set_permissions( & p, fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
8367
}
8368
assert_eq! ( ( r.written, r.removed) , ( 1 , 0 ) , " {r:?}" ) ;
8369
assert_eq! ( fs:: read( origin.join ( " f/0007.txt" ) ) .unwrap ( ) , b " file 7\n " ) ;
8370
assert! ( repo.snapshot_status ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
8371
8372
8373
let c = root.join ( " clone" ) ;
8374
Repo:: clone( & origin.to_string_lossy ( ) , & c) .unwrap ( ) ;
8375
for i in [ 0 , 2047 , 2048 , n - 1 ] {
8376
assert_eq! (
8377
fs:: read( c.join ( format! ( " f/{i:04} .txt" ) ) ) .unwrap ( ) ,
8378
format! ( " file {i} \n " ) .as_bytes ( )
8379
) ;
8380
}
8381
let cloned = Repo:: open( & c) .unwrap ( ) ;
8382
assert! ( cloned
8383
.snapshot_status ( " status" )
8384
.unwrap ( )
8385
.statuses
8386
.is_empty ( ) ) ;
d3cd7ef9 Andy McCall 1 month ago ↶
8387
}
8388
8389
8390
8391
8392
# [ test ]
8393
fn a_racy_manifest_entry_is_checked_by_hash ( ) {
8394
hermetic ( ) ;
8395
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8396
let root = dir.path ( ) ;
8397
let repo = Repo:: init( root) .unwrap ( ) ;
8398
write ( root, " a.txt" , b " one\n " ) ;
8399
repo.commit ( " fresh" , & [ ] ) .unwrap ( ) ;
8400
assert! ( repo.clean_by_manifest ( ) .unwrap ( ) , " fresh but equal" ) ;
8401
let t = fs:: metadata( root.join ( " a.txt" ) )
8402
.unwrap ( )
8403
.modified ( )
8404
.unwrap ( ) ;
8405
write ( root, " a.txt" , b " two\n " ) ;
8406
fs:: File:: options( )
8407
.write ( true )
8408
.open ( root.join ( " a.txt" ) )
8409
.unwrap ( )
8410
.set_modified ( t)
8411
.unwrap ( ) ;
8412
assert! (
8413
! repo.clean_by_manifest ( ) .unwrap ( ) ,
8414
" same size and time, other bytes"
8415
) ;
8416
assert_eq! (
8417
statuses ( & repo.snapshot_status ( " status" ) .unwrap ( ) ) ,
8418
vec! [ ( " a.txt" .to_string ( ) , ChangeStatus:: Modified) ]
8419
) ;
c5ce0367 Andy McCall 1 month ago ↶
8420
}
2c75672f Andy McCall 1 month ago ↶
8421
8422
8423
8424
# [ test ]
8425
fn a_raw_bundle_without_a_changes_text_is_refused ( ) {
8426
hermetic ( ) ;
8427
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8428
let root = dir.path ( ) ;
8429
let a = Repo:: init( & root.join ( " a" ) ) .unwrap ( ) ;
8430
let big: String = ( 0 .. 3000 ) .map ( | i | format! ( " line {i} \n " ) ) .collect ( ) ;
8431
write ( & root.join ( " a" ) , " big.txt" , big.as_bytes ( ) ) ;
8432
let h = a.commit ( " big" , & [ ] ) .unwrap ( ) ;
8433
let bundle = a.bundle_raw_for ( & [ h] , None ) .unwrap ( ) ;
8434
assert_eq! ( bundle.raw_changes.len ( ) , 1 ) ;
8435
assert! ( bundle.changes.is_empty ( ) ) ;
8436
let b = Repo:: init( & root.join ( " b" ) ) .unwrap ( ) ;
8437
let mut without = bundle.clone ( ) ;
8438
without.objects.retain ( | ( ty , _) | * ty ! = ObjectType:: Blob) ;
8439
let e = b.import_bundle ( & without, false ) .unwrap_err ( ) ;
8440
assert! ( e.to_string ( ) .contains ( " came without its text blob" ) , " {e}" ) ;
8441
assert! ( ! b.store ( ) .has ( & h) ) ;
8442
assert_eq! ( b.import_bundle ( & bundle, false ) .unwrap ( ) , vec! [ h] ) ;
8443
assert_eq! ( b.store ( ) .get_change ( & h) .unwrap ( ) .message, " big" ) ;
8444
}
a87c7b9d Andy McCall 1 month ago ↶
8445
8446
8447
8448
8449
8450
# [ test ]
8451
fn git_import_brings_a_directory_back_rather_than_making_a_second ( ) {
8452
hermetic ( ) ;
8453
if std:: process:: Command:: new( " git" )
8454
.arg ( " --version" )
8455
.output ( )
8456
.is_err ( )
8457
{
8458
eprintln! ( " git not installed; skipping" ) ;
8459
return ;
8460
}
8461
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8462
let root = dir.path ( ) ;
8463
let g = root.join ( " src.git" ) ;
8464
fs:: create_dir_all( & g) .unwrap ( ) ;
8465
let git = | args : & [& str ]| -> String {
8466
let out = std:: process:: Command:: new( " git" )
8467
.arg ( " -C" )
8468
.arg ( & g)
8469
.args ( [
8470
" -c" ,
8471
" user.name=Grace" ,
8472
" -c" ,
8473
" [email protected] " ,
8474
" -c" ,
8475
" commit.gpgsign=false" ,
8476
] )
8477
.args ( args)
8478
.output ( )
8479
.unwrap ( ) ;
8480
assert! (
8481
out.status.success ( ) ,
8482
" git {args:?}: {}" ,
8483
String :: from_utf8_lossy( & out.stderr)
8484
) ;
8485
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
8486
} ;
8487
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
8488
write ( & g, " d/a.txt" , b " a\n " ) ;
8489
write ( & g, " c/x.txt" , b " x\n " ) ;
8490
git ( & [ " add" , " ." ] ) ;
8491
git ( & [ " commit" , " -qm" , " base" ] ) ;
8492
8493
git ( & [ " checkout" , " -qb" , " side" ] ) ;
8494
git ( & [ " mv" , " c/x.txt" , " d/x.txt" ] ) ;
8495
git ( & [ " commit" , " -qm" , " x into d" ] ) ;
8496
8497
git ( & [ " checkout" , " -q" , " main" ] ) ;
8498
git ( & [ " rm" , " -q" , " d/a.txt" ] ) ;
8499
git ( & [ " commit" , " -qm" , " d goes" ] ) ;
8500
write ( & g, " d/b.txt" , b " b\n " ) ;
8501
git ( & [ " add" , " ." ] ) ;
8502
git ( & [ " commit" , " -qm" , " d comes back" ] ) ;
8503
git ( & [ " mv" , " c/x.txt" , " d/x.txt" ] ) ;
8504
git ( & [ " commit" , " -qm" , " x into d again" ] ) ;
8505
git ( & [ " merge" , " -q" , " --no-edit" , " side" ] ) ;
8506
8507
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
8508
repo.import_git ( & g, " main" ) .unwrap ( ) ;
8509
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
8510
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
8511
let st = repo
8512
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
8513
.unwrap ( ) ;
8514
let view = st.tree ( ) .view ( ) ;
8515
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
8516
8517
8518
let first = repo
8519
.log ( )
8520
.unwrap ( )
8521
.into_iter ( )
8522
.find ( | e | e.change.message.starts_with ( " base" ) )
8523
.unwrap ( ) ;
8524
let base = repo
8525
.load_state_history ( & [ first.hash] )
8526
.unwrap ( )
8527
.tree ( )
8528
.view ( ) ;
8529
assert_eq! ( view.entries[ " d" ] .node, base.entries[ " d" ] .node) ;
8530
}
8531
8532
8533
8534
8535
# [ test ]
8536
fn git_import_merge_retires_a_placement_under_a_removed_directory ( ) {
8537
hermetic ( ) ;
8538
if std:: process:: Command:: new( " git" )
8539
.arg ( " --version" )
8540
.output ( )
8541
.is_err ( )
8542
{
8543
eprintln! ( " git not installed; skipping" ) ;
8544
return ;
8545
}
8546
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8547
let root = dir.path ( ) ;
8548
let g = root.join ( " src.git" ) ;
8549
fs:: create_dir_all( & g) .unwrap ( ) ;
8550
let run = | args : & [& str ]| {
8551
std:: process:: Command:: new( " git" )
8552
.arg ( " -C" )
8553
.arg ( & g)
8554
.args ( [
8555
" -c" ,
8556
" user.name=Grace" ,
8557
" -c" ,
8558
" [email protected] " ,
8559
" -c" ,
8560
" commit.gpgsign=false" ,
8561
] )
8562
.args ( args)
8563
.output ( )
8564
.unwrap ( )
8565
} ;
8566
let git = | args : & [& str ]| {
8567
let out = run ( args) ;
8568
assert! (
8569
out.status.success ( ) ,
8570
" git {args:?}: {}" ,
8571
String :: from_utf8_lossy( & out.stderr)
8572
) ;
8573
} ;
8574
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
8575
write ( & g, " c/x.txt" , b " x\n " ) ;
8576
write ( & g, " keep.txt" , b " k\n " ) ;
8577
git ( & [ " add" , " ." ] ) ;
8578
git ( & [ " commit" , " -qm" , " base" ] ) ;
8579
8580
8581
git ( & [ " checkout" , " -qb" , " side" ] ) ;
8582
git ( & [ " mv" , " c/x.txt" , " c/y.txt" ] ) ;
8583
git ( & [ " commit" , " -qm" , " rename within c" ] ) ;
8584
git ( & [ " checkout" , " -q" , " main" ] ) ;
8585
fs:: create_dir_all( g.join ( " n" ) ) .unwrap ( ) ;
8586
git ( & [ " mv" , " c/x.txt" , " n/x.txt" ] ) ;
8587
git ( & [ " commit" , " -qm" , " move out of c" ] ) ;
8588
8589
let _ = run ( & [ " merge" , " -q" , " side" ] ) ;
8590
let _ = run ( & [
8591
" rm" ,
8592
" -q" ,
8593
" --cached" ,
8594
" --ignore-unmatch" ,
8595
" c/y.txt" ,
8596
" c/x.txt" ,
8597
] ) ;
8598
let _ = std:: fs:: remove_dir_all( g.join ( " c" ) ) ;
8599
write ( & g, " n/x.txt" , b " x\n " ) ;
8600
git ( & [ " add" , " -A" ] ) ;
8601
git ( & [ " commit" , " -qm" , " merge: keep n/x.txt" ] ) ;
8602
8603
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
8604
repo.import_git ( & g, " main" ) .unwrap ( ) ;
8605
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
8606
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
8607
let st = repo
8608
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
8609
.unwrap ( ) ;
8610
let view = st.tree ( ) .view ( ) ;
8611
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
8612
assert! ( view.entries.contains_key ( " n/x.txt" ) ) ;
8613
}
8614
8615
8616
8617
8618
# [ test ]
8619
fn git_import_merge_leaves_no_orphan_behind ( ) {
8620
hermetic ( ) ;
8621
if std:: process:: Command:: new( " git" )
8622
.arg ( " --version" )
8623
.output ( )
8624
.is_err ( )
8625
{
8626
eprintln! ( " git not installed; skipping" ) ;
8627
return ;
8628
}
8629
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8630
let root = dir.path ( ) ;
8631
let g = root.join ( " src.git" ) ;
8632
fs:: create_dir_all( & g) .unwrap ( ) ;
8633
let git = | args : & [& str ]| {
8634
let out = std:: process:: Command:: new( " git" )
8635
.arg ( " -C" )
8636
.arg ( & g)
8637
.args ( [
8638
" -c" ,
8639
" user.name=Grace" ,
8640
" -c" ,
8641
" [email protected] " ,
8642
" -c" ,
8643
" commit.gpgsign=false" ,
8644
] )
8645
.args ( args)
8646
.output ( )
8647
.unwrap ( ) ;
8648
assert! (
8649
out.status.success ( ) ,
8650
" git {args:?}: {}" ,
8651
String :: from_utf8_lossy( & out.stderr)
8652
) ;
8653
} ;
8654
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
8655
write ( & g, " d/f.txt" , b " f\n " ) ;
8656
write ( & g, " keep.txt" , b " k\n " ) ;
8657
git ( & [ " add" , " ." ] ) ;
8658
git ( & [ " commit" , " -qm" , " base" ] ) ;
8659
git ( & [ " checkout" , " -qb" , " side" ] ) ;
8660
write ( & g, " d/new.txt" , b " new\n " ) ;
8661
git ( & [ " add" , " ." ] ) ;
8662
git ( & [ " commit" , " -qm" , " add into d" ] ) ;
8663
git ( & [ " checkout" , " -q" , " main" ] ) ;
8664
git ( & [ " rm" , " -q" , " d/f.txt" ] ) ;
8665
git ( & [ " commit" , " -qm" , " d goes" ] ) ;
8666
git ( & [ " merge" , " -q" , " --no-edit" , " side" ] ) ;
8667
8668
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
8669
repo.import_git ( & g, " main" ) .unwrap ( ) ;
8670
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
8671
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
8672
let st = repo
8673
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
8674
.unwrap ( ) ;
8675
let view = st.tree ( ) .view ( ) ;
8676
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
8677
assert! ( view.entries.contains_key ( " d/new.txt" ) ) ;
8678
}
d77e1702 Andy McCall 1 month ago ↶
8679
8680
8681
8682
# [ test ]
8683
fn fsck_names_a_missing_text_blob ( ) {
8684
hermetic ( ) ;
8685
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8686
let root = dir.path ( ) ;
8687
let repo = Repo:: init( root) .unwrap ( ) ;
8688
let big: String = ( 0 .. 3000 ) .map ( | i | format! ( " line {i} \n " ) ) .collect ( ) ;
8689
write ( root, " big.txt" , big.as_bytes ( ) ) ;
8690
let h = repo.commit ( " big" , & [ ] ) .unwrap ( ) ;
8691
assert_eq! ( repo.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
8692
let ( _ , texts) = repo.store ( ) .get_change_lazy ( & h) .unwrap ( ) ;
8693
let Some ( crate :: objects:: TextRef:: Blob( b, _ , _ ) ) = texts.first ( ) .copied ( ) else {
8694
panic! ( " a stored text" )
8695
} ;
64818938 Andy McCall 1 month ago ↶
8696
repo.store ( ) .remove_loose_for_test ( & b) .unwrap ( ) ;
d77e1702 Andy McCall 1 month ago ↶
8697
let rep = repo.fsck ( ) .unwrap ( ) ;
8698
assert! ( rep.errors ( ) > 0 ) ;
8699
assert! (
8700
rep.problems.iter ( ) .any ( | p | p.what.contains ( & h.short ( 12 ) ) ) ,
8701
" {:?}" ,
8702
rep.problems
8703
) ;
8704
}
a07f60a3 Andy McCall 1 month ago ↶
8705
8706
8707
8708
8709
# [ test ]
8710
fn a_subrepo_on_the_parents_hub_borrows_its_credentials ( ) {
8711
hermetic ( ) ;
8712
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8713
let mut repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
8714
repo.config
8715
.set (
8716
" remote.origin.url" ,
8717
" https://Hub.example:7343/doomsdayone/metalgear" ,
8718
)
8719
.unwrap ( ) ;
8720
repo.config.set ( " remote.origin.token" , " secret" ) .unwrap ( ) ;
8721
repo.config
8722
.set ( " remote.origin.ca" , " /tmp/hub-ca.pem" )
8723
.unwrap ( ) ;
8724
let same = repo.credentials_for ( " https://hub.example:7343/doomsdayone/rstar-api" ) ;
8725
assert_eq! ( same.token.as_deref ( ) , Some ( " secret" ) ) ;
8726
assert_eq! ( same.ca.as_deref ( ) , Some ( Path:: new( " /tmp/hub-ca.pem" ) ) ) ;
8727
for other in [
8728
" https://elsewhere.example:7343/doomsdayone/rstar-api" ,
8729
" https://hub.example/doomsdayone/rstar-api" ,
8730
" http://hub.example:7343/doomsdayone/rstar-api" ,
8731
] {
8732
let o = repo.credentials_for ( other) ;
8733
assert_ne! ( o.token.as_deref ( ) , Some ( " secret" ) , " {other}" ) ;
8734
}
8735
}
ce14f0d6 Andy McCall 1 month ago ↶
8736
ff1f62fe Andy McCall 1 month ago ↶
8737
8738
8739
ce14f0d6 Andy McCall 1 month ago ↶
8740
# [ test ]
ff1f62fe Andy McCall 1 month ago ↶
8741
fn remotes_use_the_credential_store_and_the_enclosing_checkout ( ) {
8742
hermetic ( ) ;
8743
let home = tempfile:: tempdir( ) .unwrap ( ) ;
8744
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8745
let mut parent = Repo:: init( dir.path ( ) ) .unwrap ( ) .with_home ( home.path ( ) ) ;
8746
parent
8747
.config
8748
.set ( " remote.origin.url" , " https://hub.example/o/parent" )
8749
.unwrap ( ) ;
8750
parent.save_config ( ) .unwrap ( ) ;
8751
assert_eq! ( parent.remote_options ( " origin" ) .token, None ) ;
8752
crate :: credentials:: Credentials:: at( home.path ( ) )
8753
.save ( " https://hub.example" , " stored" , Some ( " andy" ) , None )
8754
.unwrap ( ) ;
8755
assert_eq! (
8756
parent.remote_options ( " origin" ) .token.as_deref ( ) ,
8757
Some ( " stored" )
8758
) ;
8759
8760
let empty_home = tempfile:: tempdir( ) .unwrap ( ) ;
8761
let nested = dir.path ( ) .join ( " deps" ) .join ( " child" ) ;
8762
let mut child = Repo:: init( & nested) .unwrap ( ) .with_home ( empty_home.path ( ) ) ;
8763
child
8764
.config
8765
.set ( " remote.origin.url" , " https://hub.example/o/child" )
8766
.unwrap ( ) ;
8767
child.save_config ( ) .unwrap ( ) ;
8768
assert_eq! ( child.remote_options ( " origin" ) .token, None ) ;
8769
parent
8770
.config
8771
.set ( " remote.origin.token" , " checkout" )
8772
.unwrap ( ) ;
8773
parent.save_config ( ) .unwrap ( ) ;
8774
assert_eq! (
8775
child.remote_options ( " origin" ) .token.as_deref ( ) ,
8776
Some ( " checkout" )
8777
) ;
8778
8779
child
8780
.config
8781
.set ( " remote.origin.url" , " https://other.example/o/child" )
8782
.unwrap ( ) ;
8783
assert_eq! ( child.remote_options ( " origin" ) .token, None ) ;
8784
}
8785
8786
# [ test ]
ce14f0d6 Andy McCall 1 month ago ↶
8787
fn the_metadata_cache_survives_a_torn_tail ( ) {
8788
hermetic ( ) ;
8789
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8790
let root = dir.path ( ) ;
8791
let repo = Repo:: init( root) .unwrap ( ) ;
8792
write ( root, " a.txt" , b " 1\n " ) ;
8793
let h = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8794
let meta = repo.store ( ) .get_change_meta ( & h) .unwrap ( ) ;
8795
let cache = repo.store ( ) .dir ( ) .join ( " metas" ) ;
8796
let len = fs:: metadata( & cache) .unwrap ( ) .len ( ) ;
8797
assert! ( len > 0 , " a change read is cached" ) ;
8798
8799
fs:: OpenOptions:: new( )
8800
.append ( true )
8801
.open ( & cache)
8802
.unwrap ( )
8803
.write_all ( & [ 7 ; 20 ] )
8804
.unwrap ( ) ;
8805
let again = Repo:: open( root) .unwrap ( ) ;
8806
assert_eq! ( again.store ( ) .get_change_meta ( & h) .unwrap ( ) , meta) ;
8807
assert_eq! ( fs:: metadata( & cache) .unwrap ( ) .len ( ) , len) ;
8808
}
f9466021 Andy McCall 1 month ago ↶
8809
8810
# [ test ]
8811
fn a_manifest_that_does_not_check_reads_as_none ( ) {
8812
hermetic ( ) ;
8813
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8814
let root = dir.path ( ) ;
8815
let repo = Repo:: init( root) .unwrap ( ) ;
8816
write ( root, " a.txt" , b " 1\n " ) ;
8817
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8818
let path = repo.manifest_path ( ) ;
8819
let mut bytes = fs:: read( & path) .unwrap ( ) ;
8820
assert! ( Repo:: open( root) .unwrap ( ) .read_manifest ( ) .unwrap ( ) .is_some ( ) ) ;
8821
let last = bytes.len ( ) - 1 ;
8822
bytes[ last] ^ = 1 ;
8823
fs:: write( & path, & bytes) .unwrap ( ) ;
8824
assert! ( Repo:: open( root) .unwrap ( ) .read_manifest ( ) .unwrap ( ) .is_none ( ) ) ;
8825
8826
let again = Repo:: open( root) .unwrap ( ) ;
8827
assert! ( ! again.snapshot ( " status" ) .unwrap ( ) .changed) ;
8828
assert! ( again.read_manifest ( ) .unwrap ( ) .is_some ( ) ) ;
8829
}
8830
8831
# [ test ]
8832
fn a_commit_leaves_a_manifest_that_proves_the_working_copy_clean ( ) {
8833
hermetic ( ) ;
8834
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8835
let root = dir.path ( ) ;
8836
let repo = Repo:: init( root) .unwrap ( ) ;
8837
write ( root, " a.txt" , b " 1\n " ) ;
8838
write ( root, " b.txt" , b " 2\n " ) ;
8839
8840
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 60 ) ;
8841
fs:: File:: options( )
8842
.write ( true )
8843
.open ( root.join ( " b.txt" ) )
8844
.unwrap ( )
8845
.set_modified ( old)
8846
.unwrap ( ) ;
8847
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8848
assert! ( repo.clean_by_manifest ( ) .unwrap ( ) , " after the first commit" ) ;
8849
repo.snapshot_status ( " status" ) .unwrap ( ) ;
8850
write ( root, " a.txt" , b " 1\n 2\n " ) ;
8851
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
8852
assert! (
8853
repo.clean_by_manifest ( ) .unwrap ( ) ,
8854
" after a commit of one file"
8855
) ;
8856
8857
8858
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
8859
repo.snapshot_status ( " status" ) .unwrap ( ) ;
8860
write ( root, " a.txt" , b " 1\n 2\n 3\n 4\n 5\n " ) ;
8861
repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
8862
assert! (
8863
repo.clean_by_manifest ( ) .unwrap ( ) ,
8864
" after an edit a status saw"
8865
) ;
8866
}
e266b7d4 Andy McCall 1 month ago ↶
8867
8868
# [ test ]
8869
fn a_conflicted_rebase_can_be_resolved_in_the_working_copy ( ) {
8870
hermetic ( ) ;
8871
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8872
let root = dir.path ( ) ;
8873
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8874
write ( root, " a.txt" , b " 1\n 2\n " ) ;
8875
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
8876
repo.bookmark_create ( " develop" , Some ( & [ a] ) ) .unwrap ( ) ;
8877
write ( root, " a.txt" , b " 1\n X\n " ) ;
8878
let c = repo.commit ( " C: 2 to X" , & [ ] ) .unwrap ( ) ;
8879
repo.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
8880
write ( root, " a.txt" , b " 1\n Y\n " ) ;
8881
let d = repo.commit ( " D: 2 to Y" , & [ ] ) .unwrap ( ) ;
8882
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
8883
8884
let err = repo.rebase ( & c, & [ d] ) .unwrap_err ( ) ;
8885
assert! ( matches! ( err, RepoError:: Conflicted( _ ) ) , " {err}" ) ;
8886
let r = repo.rebase_into_working_copy ( & c, & [ d] ) .unwrap ( ) ;
8887
assert! ( r.changed & & r.checked_out) ;
8888
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8889
assert! (
8890
snap.base_snapshot.is_conflicted ( ) ,
8891
" the conflict is in the working copy"
8892
) ;
8893
let text = fs:: read_to_string( root.join ( " a.txt" ) ) .unwrap ( ) ;
8894
assert! ( text.contains ( ' X' ) & & text.contains ( ' Y' ) , " {text}" ) ;
8895
8896
write ( root, " a.txt" , b " 1\n XY\n " ) ;
8897
repo.commit ( " Resolve" , & [ ] ) .unwrap ( ) ;
8898
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
8899
assert! ( st.contains ( & c) & & st.contains ( & d) ) ;
8900
let files = st.materialize ( ) .files;
8901
assert! ( ! files[ " a.txt" ] .is_conflicted ( ) ) ;
8902
assert! (
8903
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 1\n XY\n " )
8904
) ;
8905
8906
8907
repo.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
8908
write ( root, " b.txt" , b " b\n " ) ;
8909
let e = repo.commit ( " E" , & [ ] ) .unwrap ( ) ;
8910
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
8911
let err = repo.rebase_into_working_copy ( & e, & [ a] ) .unwrap_err ( ) ;
8912
assert! (
8913
err.to_string ( ) .contains ( " not on the current bookmark" ) ,
8914
" {err}"
8915
) ;
8916
}
d4dbab97 Andy McCall 1 month ago ↶
8917
8918
# [ test ]
8919
fn an_ignore_rule_drops_an_added_file_but_not_a_tracked_one ( ) {
8920
hermetic ( ) ;
8921
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8922
let root = dir.path ( ) ;
8923
let repo = Repo:: init( root) .unwrap ( ) ;
8924
write ( root, " a.txt" , b " a\n " ) ;
8925
write ( root, " kept.log" , b " tracked\n " ) ;
8926
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8927
write ( root, " build.log" , b " made\n " ) ;
8928
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8929
assert! ( snap.statuses.iter ( ) .any ( | s | s.path = = " build.log" ) ) ;
8930
8931
8932
write ( root, " .gitignore" , b " *.log\n " ) ;
8933
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8934
let paths: Vec< & str > = snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect ( ) ;
8935
assert_eq! ( paths, vec! [ " .gitignore" ] , " {paths:?}" ) ;
8936
write ( root, " kept.log" , b " tracked, edited\n " ) ;
8937
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8938
assert! ( snap.statuses.iter ( ) .any ( | s | s.path = = " kept.log" ) ) ;
8939
}
fd280228 Andy McCall 1 month ago ↶
8940
8941
# [ test ]
8942
fn shed_graphs_come_back_the_same ( ) {
8943
hermetic ( ) ;
8944
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8945
let root = dir.path ( ) ;
8946
let repo = Repo:: init( root) .unwrap ( ) ;
8947
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
8948
write ( root, " b.txt" , b " x\n " ) ;
8949
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8950
write ( root, " a.txt" , b " 1\n two\n 3\n 4\n " ) ;
8951
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
8952
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
8953
let mut st = repo.load_state_history ( & heads) .unwrap ( ) ;
8954
let view = st.tree ( ) .view ( ) ;
8955
let nodes: Vec< NodeId> = [ " a.txt" , " b.txt" ]
8956
.iter ( )
8957
.map ( | p | view.entries[ * p] .node )
8958
.collect ( ) ;
8959
repo.load_graphs_of ( & mut st, & nodes) .unwrap ( ) ;
8960
let before: Vec< Vec< u8 > > = nodes.iter ( ) .map ( | n | st.render_file ( n) .0 ) .collect ( ) ;
8961
assert_eq! ( st.built_count ( ) , 2 ) ;
8962
let shed = st.shed_graphs ( | b | repo.store_blob ( b) ) .unwrap ( ) ;
8963
assert_eq! ( ( shed, st.built_count ( ) ) , ( 2 , 0 ) ) ;
8964
8965
write ( root, " a.txt" , b " 1\n two\n 3\n 4\n 5\n " ) ;
8966
let h3 = repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
8967
let ( skeleton, texts) = repo.store ( ) .get_change_lazy ( & h3) .unwrap ( ) ;
8968
st.add_known ( h3, skeleton, texts) ;
8969
repo.load_graphs_of ( & mut st, & nodes) .unwrap ( ) ;
8970
let after: Vec< Vec< u8 > > = nodes.iter ( ) .map ( | n | st.render_file ( n) .0 ) .collect ( ) ;
8971
assert_eq! ( after[ 1 ] , before[ 1 ] ) ;
8972
assert_eq! ( after[ 0 ] , b " 1\n two\n 3\n 4\n 5\n " ) ;
8973
}
360aa74a Andy McCall 1 month ago ↶
8974
8975
# [ test ]
8976
fn many_edited_files_diff_in_parallel ( ) {
8977
8978
8979
8980
hermetic ( ) ;
8981
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8982
let root = dir.path ( ) ;
8983
let repo = Repo:: init( root) .unwrap ( ) ;
8984
let text = | i : usize , round : usize | {
8985
let mut s = String :: new( ) ;
8986
for line in 0 .. 50 {
8987
if round > 0 & & line % 7 = = i % 7 {
8988
s.push_str ( & format! ( " edited {round} {i} {line} \n " ) ) ;
8989
} else {
8990
s.push_str ( & format! ( " file {i} line {line} \n " ) ) ;
8991
}
8992
}
8993
if round > 0 & & i.is_multiple_of ( 3 ) {
8994
s.push_str ( " appended\n " ) ;
8995
}
8996
s
8997
} ;
8998
for i in 0 .. 40 {
8999
write ( root, & format! ( " d/f{i} .txt" ) , text ( i, 0 ) .as_bytes ( ) ) ;
9000
}
9001
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
9002
for i in 0 .. 40 {
9003
write ( root, & format! ( " d/f{i} .txt" ) , text ( i, 1 ) .as_bytes ( ) ) ;
9004
}
9005
let h = repo.commit ( " edit every file" , & [ ] ) .unwrap ( ) ;
9006
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
9007
for i in 0 .. 40 {
9008
let want = text ( i, 1 ) ;
9009
assert! (
9010
matches! ( & snap.files[ & format! ( " d/f{i} .txt" ) ] .content, FileContent:: Text { bytes, .. } if bytes = = want.as_bytes ( ) ) ,
9011
" d/f{i}.txt as committed"
9012
) ;
9013
}
9014
assert! (
9015
repo.commit ( " nothing" , & [ ] ) .is_err ( ) ,
9016
" nothing left to commit"
9017
) ;
9018
}
b35df36b Andy McCall 1 month ago ↶
9019
9020
9021
9022
9023
9024
# [ test ]
9025
fn a_fetched_change_shows_as_behind_and_resolves ( ) {
9026
hermetic ( ) ;
9027
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9028
let root = dir.path ( ) ;
9029
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
9030
drop ( origin) ;
9031
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
9032
let a = Repo:: clone( & url, & root.join ( " a" ) )
9033
.unwrap ( )
9034
.with_home ( root.join ( " home-a" ) ) ;
9035
write ( & root.join ( " a" ) , " f.txt" , b " one\n " ) ;
9036
a.commit ( " one" , & [ ] ) .unwrap ( ) ;
9037
a.push ( " origin" , false ) .unwrap ( ) ;
9038
let b = Repo:: clone( & url, & root.join ( " b" ) )
9039
.unwrap ( )
9040
.with_home ( root.join ( " home-b" ) ) ;
9041
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 0 ) ) ) ;
9042
9043
write ( & root.join ( " a" ) , " f.txt" , b " one\n two\n " ) ;
9044
let h2 = a.commit ( " two" , & [ ] ) .unwrap ( ) ;
9045
a.push ( " origin" , false ) .unwrap ( ) ;
9046
b.fetch ( " origin" ) .unwrap ( ) ;
9047
9048
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 1 ) ) ) ;
9049
assert_eq! ( b.tracking ( " origin" , " nope" ) .unwrap ( ) , None ) ;
9050
assert_eq! (
9051
b.resolve_change ( & format! ( " #{} " , & h2.to_hex ( ) [ .. 12 ] ) )
9052
.unwrap ( ) ,
9053
h2
9054
) ;
9055
assert_eq! (
9056
b.remote_only_location ( & h2) .unwrap ( ) .as_deref ( ) ,
9057
Some ( " origin/main" )
9058
) ;
9059
9060
9061
b.fetch ( " origin" ) .unwrap ( ) ;
9062
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 1 ) ) ) ;
9063
b.pull ( " origin" ) .unwrap ( ) ;
9064
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 0 ) ) ) ;
9065
assert_eq! ( b.remote_only_location ( & h2) .unwrap ( ) , None ) ;
9066
9067
9068
let mut c = Repo:: init( & root.join ( " c" ) ) .unwrap ( ) ;
9069
assert_eq! ( c.default_remote ( ) , " origin" ) ;
9070
c.config.set ( " remote.earth.url" , & url) .unwrap ( ) ;
9071
assert_eq! ( c.default_remote ( ) , " earth" ) ;
9072
c.config.set ( " remote.origin.url" , & url) .unwrap ( ) ;
9073
assert_eq! ( c.default_remote ( ) , " origin" ) ;
9074
}
827281ee Andy McCall 1 month ago ↶
9075
9076
9077
9078
# [ test ]
9079
fn a_clone_from_a_relative_path_records_it_absolute ( ) {
9080
hermetic ( ) ;
9081
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9082
let root = dir.path ( ) ;
9083
Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
9084
let rel = crate :: protocol:: stored_url( " relative/repo" ) ;
9085
assert! ( std:: path:: Path:: new( & rel) .is_absolute ( ) , " {rel}" ) ;
9086
assert_eq! (
9087
crate :: protocol:: stored_url( " https://hub.example/o/n" ) ,
9088
" https://hub.example/o/n"
9089
) ;
9090
assert! ( crate :: protocol:: stored_url( " file://x" ) .starts_with ( " file:///" ) ) ;
8d803563 Andy McCall 1 month ago ↶
9091
assert! ( crate :: protocol:: stored_url( " dir://x" ) .starts_with ( " dir:///" ) ) ;
9092
9093
assert_eq! (
9094
crate :: protocol:: stored_url( " s3://bucket/prefix?profile=p" ) ,
9095
" s3://bucket/prefix?profile=p"
9096
) ;
827281ee Andy McCall 1 month ago ↶
9097
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
9098
let b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) ;
9099
assert_eq! ( b.config.remotes[ " origin" ] , url) ;
9100
b.fetch ( " origin" ) .unwrap ( ) ;
9101
}
a46f3e4f Andy McCall 1 month ago ↶
9102
9103
9104
9105
9106
# [ test ]
9107
fn a_workspace_added_on_a_bookmark_is_on_it_with_its_subrepos ( ) {
9108
hermetic ( ) ;
9109
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9110
let root = dir.path ( ) ;
9111
Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9112
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9113
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9114
.unwrap ( )
9115
.with_home ( root.join ( " home" ) ) ;
9116
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9117
let l1 = lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9118
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9119
9120
let app_dir = root.join ( " app" ) ;
9121
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9122
write ( & app_dir, " main.txt" , b " app\n " ) ;
9123
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9124
app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
9125
9126
app.bookmark_create ( " develop" , None ) .unwrap ( ) ;
9127
app.bookmark_create ( " feature" , None ) .unwrap ( ) ;
9128
let mut app = app;
9129
app.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
9130
assert_eq! ( app.config.bookmark, " feature" ) ;
9131
9132
9133
std:: fs:: rename( root.join ( " lib-origin" ) , root.join ( " lib-origin-gone" ) ) .unwrap ( ) ;
9134
let heads = app.resolve_heads ( " develop" ) .unwrap ( ) ;
9135
let ws = app
9136
.workspace_add_on ( & root.join ( " ws" ) , Some ( & heads) , Some ( " develop" ) )
9137
.unwrap ( ) ;
9138
assert_eq! (
9139
ws.config.bookmark, " develop" ,
9140
" the new workspace is on develop"
9141
) ;
9142
assert_eq! (
9143
app.config.bookmark, " feature" ,
9144
" and this one stays on its own"
9145
) ;
9146
let sub = Repo:: open( & root.join ( " ws/libs/lib" ) ) .unwrap ( ) ;
9147
assert_eq! (
9148
sub.config.remotes[ " origin" ] , lib_url,
9149
" its origin is the subrepo's URL"
9150
) ;
9151
assert_eq! ( sub.bookmark_heads ( ) .unwrap ( ) , vec! [ l1] , " at its pin" ) ;
9152
assert_eq! (
9153
std:: fs:: read( root.join ( " ws/libs/lib/lib.txt" ) ) .unwrap ( ) ,
9154
b " v1\n "
9155
) ;
9156
}
47ce8b8e Andy McCall 1 month ago ↶
9157
9158
9159
9160
9161
# [ test ]
9162
fn revsets_select_changes_by_relation_author_text_date_and_path ( ) {
9163
hermetic ( ) ;
9164
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9165
let root = dir.path ( ) ;
9166
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9167
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
9168
.unwrap ( ) ;
9169
write ( root, " src/a.rs" , b " one\n " ) ;
9170
let a = repo.commit ( " first: add a" , & [ ] ) .unwrap ( ) ;
9171
write ( root, " src/a.rs" , b " one\n two\n " ) ;
9172
let b = repo.commit ( " second: grow a" , & [ ] ) .unwrap ( ) ;
9173
9174
repo.checkout ( & [ a] , false , " checkout a" ) .unwrap ( ) ;
9175
write ( root, " docs/notes.md" , b " notes\n " ) ;
9176
let c = repo.commit ( " third: notes" , & [ ] ) .unwrap ( ) ;
9177
9178
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
9179
.unwrap ( ) ;
9180
fs:: remove_file( root.join ( " src/a.rs" ) ) .unwrap ( ) ;
9181
write ( root, " src/z.rs" , b " one\n two\n " ) ;
9182
let d = repo.commit ( " fourth: rename a to z" , & [ ] ) .unwrap ( ) ;
9183
let wc = repo.working_copy ( ) .unwrap ( ) .0 ;
9184
9185
repo.set_user_config ( " revset.mine" , " author(me) & ::main" )
9186
.unwrap ( ) ;
9187
assert! ( repo.set_user_config ( " revset.bad" , " a &" ) .is_err ( ) ) ;
9188
let set = | src : & str | -> Vec< ChangeHash> {
9189
let mut v = repo.revset ( src) .unwrap ( ) .hashes;
9190
v.sort ( ) ;
9191
v
9192
} ;
9193
let sorted = | hs : & [ChangeHash]| -> Vec< ChangeHash> {
9194
let mut v = hs.to_vec ( ) ;
9195
v.sort ( ) ;
9196
v
9197
} ;
9198
9199
9200
9201
9202
let heads = sorted ( & repo.bookmark_heads ( ) .unwrap ( ) ) ;
9203
let meta = | h : & ChangeHash| repo.store ( ) .get_change_meta ( h) .unwrap ( ) ;
9204
assert! ( heads.contains ( & d) & & heads.contains ( & c) ) ;
9205
9206
assert_eq! ( set ( " main" ) , heads) ;
9207
assert_eq! ( set ( " ::main" ) , sorted ( & [ a, b, c, d] ) ) ;
9208
assert_eq! ( set ( " @" ) , vec! [ wc] ) ;
9209
assert_eq! ( set ( " ::@" ) , sorted ( & [ a, b, c, d, wc] ) ) ;
9210
assert_eq! ( set ( & format! ( " ::#{} " , b.short ( 12 ) ) ) , sorted ( & [ a, b] ) ) ;
9211
assert_eq! ( set ( & format! ( " #{} ::" , a.short ( 12 ) ) ) , sorted ( & [ a, b, d, wc] ) ) ;
9212
assert_eq! (
9213
set ( & format! ( " #{} ::#{} " , a.short ( 12 ) , b.short ( 12 ) ) ) ,
9214
sorted ( & [ a, b] )
9215
) ;
9216
9217
assert_eq! ( set ( & format! ( " #{} ..main" , b.short ( 12 ) ) ) , sorted ( & [ c, d] ) ) ;
9218
assert_eq! ( set ( & format! ( " #{} .." , c.short ( 12 ) ) ) , sorted ( & [ a, b, d, wc] ) ) ;
9219
assert_eq! ( set ( " ..main" ) , sorted ( & [ a, b, c, d] ) ) ;
9220
9221
assert_eq! (
9222
set ( & format! ( " ::main ~ #{} " , d.short ( 12 ) ) ) ,
9223
sorted ( & [ a, b, c] )
9224
) ;
9225
assert_eq! (
9226
set ( & format! ( " ::main & #{} | #{} " , b.short ( 12 ) , c.short ( 12 ) ) ) ,
9227
sorted ( & [ b, c] )
9228
) ;
9229
assert_eq! ( set ( " all() ~ ::main" ) , vec! [ wc] ) ;
9230
9231
let mut head_deps: Vec< ChangeHash> = heads.iter ( ) .flat_map ( | h | meta ( h) .deps) .collect ( ) ;
9232
head_deps.sort ( ) ;
9233
head_deps.dedup ( ) ;
9234
assert_eq! ( set ( " deps(main)" ) , head_deps) ;
9235
let on_a: Vec< ChangeHash> = [ b, c, d]
9236
.into_iter ( )
9237
.filter ( | h | meta ( h) .deps.contains ( & a) )
9238
.collect ( ) ;
9239
assert_eq! ( set ( & format! ( " dependents(#{} )" , a.short ( 12 ) ) ) , sorted ( & on_a) ) ;
9240
assert_eq! ( set ( " heads(::main)" ) , heads) ;
9241
assert_eq! ( set ( " roots(::main)" ) , sorted ( & [ a, c] ) ) ;
9242
assert_eq! (
9243
set ( & format! (
9244
" heads(#{} | #{} | #{} )" ,
9245
a.short ( 12 ) ,
9246
b.short ( 12 ) ,
9247
c.short ( 12 )
9248
) ) ,
9249
sorted ( & [ b, c] )
9250
) ;
9251
assert_eq! ( set ( " latest(::main, 1)" ) , vec! [ d] ) ;
9252
assert_eq! ( set ( " kind(working-copy)" ) , vec! [ wc] ) ;
9253
assert_eq! ( set ( " none()" ) , vec! [ ] ) ;
9254
9255
let key = repo.config.author_key.unwrap ( ) ;
9256
assert_eq! ( set ( " author(ada) & ::main" ) , sorted ( & [ a, b, c, d] ) ) ;
9257
assert_eq! ( set ( " author(\" [email protected] \" ) & ::main" ) .len ( ) , 4 ) ;
9258
assert_eq! ( set ( & format! ( " author({} ) & ::main" , key.short ( 12 ) ) ) .len ( ) , 4 ) ;
9259
assert_eq! ( set ( " author(me) & ::main" ) .len ( ) , 4 ) ;
9260
assert_eq! ( set ( " author(nobody)" ) , vec! [ ] ) ;
9261
assert_eq! ( set ( " description(notes)" ) , vec! [ c] ) ;
9262
assert_eq! ( set ( " description(\" rename a\" )" ) , vec! [ d] ) ;
9263
assert_eq! ( set ( " date(-1h..) & ::main" ) .len ( ) , 4 ) ;
9264
assert_eq! ( set ( " date(..2000-01-01)" ) , vec! [ ] ) ;
9265
9266
assert_eq! ( set ( " touches(\" src/z.rs\" )" ) , sorted ( & [ a, b, d] ) ) ;
9267
assert_eq! ( set ( " touches(\" src/**\" ) & ::main" ) , sorted ( & [ a, b, d] ) ) ;
9268
assert_eq! ( set ( " touches(\" docs/*\" )" ) , vec! [ c] ) ;
9269
assert_eq! ( set ( " touches(\" nothing/*\" )" ) , vec! [ ] ) ;
9270
9271
assert_eq! ( repo.revset ( " ::main" ) .unwrap ( ) .hashes[ 0 ] , d) ;
9272
9273
assert_eq! ( set ( " mine()" ) .len ( ) , 4 ) ;
9274
let e = repo.revset ( " nosuch()" ) .unwrap_err ( ) .to_string ( ) ;
9275
assert! ( e.contains ( " unknown function nosuch()" ) , " {e}" ) ;
9276
let e = repo.revset ( " main &" ) .unwrap_err ( ) .to_string ( ) ;
9277
assert! ( e.contains ( " at character" ) , " {e}" ) ;
9278
let e = repo.revset ( " zzzz" ) .unwrap_err ( ) .to_string ( ) ;
9279
assert! ( e.contains ( " no change matches" ) , " {e}" ) ;
9280
let e = repo
9281
.revset_with_budget ( " ::main" , std:: time:: Duration:: ZERO )
9282
.unwrap_err ( )
9283
.to_string ( ) ;
9284
assert! ( e.contains ( " budget" ) , " {e}" ) ;
9285
9286
let entries = repo.log_revset ( " ::@" ) .unwrap ( ) ;
9287
assert! ( entries[ 0 ] .is_working_copy & & entries.iter ( ) .any ( | e | e.is_head & & e.hash = = d ) ) ;
2c9ddac3 Andy McCall 1 month ago ↶
9288
9289
9290
assert_eq! ( set ( " unsigned() & ::main" ) , sorted ( & [ a, b, c, d] ) ) ;
9291
assert_eq! ( set ( " signed_by(me)" ) , vec! [ ] ) ;
9292
repo.sign_change ( & d) .unwrap ( ) ;
9293
assert_eq! ( set ( " signed_by(me)" ) , vec! [ d] ) ;
9294
assert_eq! ( set ( " signed_by([email protected] )" ) , vec! [ d] ) ;
9295
assert_eq! ( set ( & format! ( " signed_by({} )" , key.short ( 12 ) ) ) , vec! [ d] ) ;
9296
assert_eq! ( set ( " unsigned() & ::main" ) , sorted ( & [ a, b, c] ) ) ;
9297
assert_eq! ( set ( " delegated()" ) , vec! [ ] ) ;
9298
assert_eq! ( set ( " revoked()" ) , vec! [ ] ) ;
9299
assert_eq! ( set ( " attested(tag)" ) , vec! [ ] ) ;
9300
repo.tag_create ( " v1" , & [ b] , " first" ) .unwrap ( ) ;
9301
assert_eq! ( set ( " attested(tag)" ) , sorted ( & [ a, b] ) ) ;
9302
assert_eq! ( set ( " attested(tag) & unsigned()" ) , sorted ( & [ a, b] ) ) ;
9303
assert_eq! ( set ( " attested(receipt)" ) , vec! [ ] ) ;
9304
assert_eq! ( set ( " built(test)" ) , vec! [ ] ) ;
9305
let e = repo.revset ( " attested(seal)" ) .unwrap_err ( ) .to_string ( ) ;
9306
assert! ( e.contains ( " tag, receipt or ref" ) , " {e}" ) ;
9307
repo.revoke_identity ( & key, " test" ) .unwrap ( ) ;
9308
assert_eq! ( set ( " revoked()" ) , vec! [ d] ) ;
9309
assert_eq! ( set ( " signed_by(me) ~ revoked()" ) , vec! [ ] ) ;
543bbf64 Andy McCall 1 month ago ↶
9310
9311
9312
9313
assert_eq! (
9314
set ( & format! ( " independent(#{} , ::@)" , b.short ( 12 ) ) ) ,
9315
sorted ( & [ c, d] )
9316
) ;
9317
assert_eq! (
9318
set ( & format! ( " independent(#{} , ::main)" , a.short ( 12 ) ) ) ,
9319
vec! [ c]
9320
) ;
9321
9322
assert_eq! ( set ( " conflicts()" ) , vec! [ ] ) ;
9323
assert_eq! ( set ( " conflicts(main)" ) , vec! [ ] ) ;
9324
9325
9326
repo.checkout ( & [ a] , true , " checkout a" ) .unwrap ( ) ;
9327
write ( root, " src/a.rs" , b " one\n TWO\n " ) ;
9328
let e = repo
9329
.commit ( " fifth: the other edit of line two" , & [ ] )
9330
.unwrap ( ) ;
9331
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , true , " checkout main" )
9332
.unwrap ( ) ;
9333
let conflicted = repo.revset ( " conflicts()" ) .unwrap ( ) .hashes;
9334
assert! (
9335
conflicted.contains ( & e) & & conflicted.contains ( & b) ,
9336
" {conflicted:?} should hold b and e"
9337
) ;
9338
assert! ( ! conflicted.contains ( & c) ) ;
9339
assert_eq! ( set ( " conflicts() & author(nobody)" ) , vec! [ ] ) ;
47ce8b8e Andy McCall 1 month ago ↶
9340
}
614f55e1 Andy McCall 1 month ago ↶
9341
9342
# [ test ]
9343
fn a_rotation_is_recorded_wherever_the_old_key_was_named ( ) {
9344
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9345
let root = dir.path ( ) ;
9346
let home = root.join ( " home" ) ;
9347
9348
9349
let mut a = Repo:: init( & root.join ( " a" ) ) .unwrap ( ) .with_home ( & home) ;
9350
let ( key, _ ) = a
9351
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
9352
.unwrap ( ) ;
9353
a.config.set ( " author.key" , & key.to_hex ( ) ) .unwrap ( ) ;
9354
a.save_config ( ) .unwrap ( ) ;
9355
let agent = SecretKey:: generate( ) .identity ( " Agent" , " [email protected] " , 1 ) ;
9356
let r = a
9357
.delegate_add ( agent.clone ( ) , crate :: identity:: Scope:: CHANGES , None )
9358
.unwrap ( ) ;
9359
assert_eq! (
9360
read_config_file ( & home.join ( " config.toml" ) ) .unwrap ( ) [ " author.key" ] ,
9361
r.new.to_hex ( ) ,
9362
" the user file follows"
9363
) ;
9364
assert_eq! (
9365
read_config_file ( & root.join ( " a/.oxy/config" ) ) .unwrap ( ) [ " author.key" ] ,
9366
r.new.to_hex ( ) ,
9367
" the pinning repository follows"
9368
) ;
9369
let b = Repo:: init( & root.join ( " b" ) ) .unwrap ( ) .with_home ( & home) ;
9370
assert_eq! (
9371
b.config.author_key,
9372
Some ( r.new) ,
9373
" a checkout without a pin follows the user file"
9374
) ;
9375
9376
let other_home = root.join ( " home2" ) ;
9377
let mut c = Repo:: init( & root.join ( " c" ) ) .unwrap ( ) .with_home ( & other_home) ;
9378
let ( ck, _ ) = c
9379
.create_identity ( " Cy" , " [email protected] " , IdentityScope:: Repo)
9380
.unwrap ( ) ;
9381
assert! ( ! other_home.join ( " config.toml" ) .exists ( ) ) ;
9382
let r2 = c.rotate_identity ( ) .unwrap ( ) ;
9383
assert_ne! ( r2.new, ck) ;
9384
assert_eq! (
9385
read_config_file ( & root.join ( " c/.oxy/config" ) ) .unwrap ( ) [ " author.key" ] ,
9386
r2.new.to_hex ( )
9387
) ;
9388
assert! (
9389
! other_home.join ( " config.toml" ) .exists ( ) ,
9390
" a repository-scoped identity stays repository-scoped"
9391
) ;
9392
9393
let ( d1, d2) = (
9394
SecretKey:: generate( ) .identity ( " D1" , " d1@x" , 1 ) ,
9395
SecretKey:: generate( ) .identity ( " D2" , " d2@x" , 1 ) ,
9396
) ;
9397
let r3 = c
9398
.delegate_add_many ( vec! [ d1.clone ( ) , d2] , crate :: identity:: Scope:: CHANGES , None )
9399
.unwrap ( ) ;
9400
assert_eq! ( r3.identity.delegates.len ( ) , 2 ) ;
9401
assert! ( c
9402
.delegate_add ( d1, crate :: identity:: Scope:: CHANGES , None )
9403
.is_err ( ) ) ;
9404
}
33be2409 Andy McCall 1 month ago ↶
9405
9406
9407
9408
9409
9410
# [ test ]
9411
fn git_import_joins_unrelated_histories_that_add_the_same_file ( ) {
9412
9413
9414
crate :: repo:: git:: set_merge_batch_for_tests( Some ( 1 ) ) ;
9415
hermetic ( ) ;
9416
if std:: process:: Command:: new( " git" )
9417
.arg ( " --version" )
9418
.output ( )
9419
.is_err ( )
9420
{
9421
eprintln! ( " git not installed; skipping" ) ;
9422
return ;
9423
}
9424
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9425
let root = dir.path ( ) ;
9426
let g = root.join ( " src.git" ) ;
9427
fs:: create_dir_all( & g) .unwrap ( ) ;
9428
let git = | args : & [& str ]| -> String {
9429
let out = std:: process:: Command:: new( " git" )
9430
.arg ( " -C" )
9431
.arg ( & g)
9432
.args ( [
9433
" -c" ,
9434
" user.name=Grace" ,
9435
" -c" ,
9436
" [email protected] " ,
9437
" -c" ,
9438
" commit.gpgsign=false" ,
9439
] )
9440
.args ( args)
9441
.output ( )
9442
.unwrap ( ) ;
9443
assert! (
9444
out.status.success ( ) ,
9445
" git {args:?}: {}" ,
9446
String :: from_utf8_lossy( & out.stderr)
9447
) ;
9448
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
9449
} ;
9450
let text = b " /* header */\n const a = 1;\n const b = 2;\n " ;
9451
9452
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
9453
write ( & g, " gulpfile.js" , text) ;
9454
write ( & g, " a.txt" , b " a\n " ) ;
9455
git ( & [ " add" , " ." ] ) ;
9456
git ( & [ " commit" , " -q" , " -m" , " A root" ] ) ;
9457
9458
git ( & [ " checkout" , " -q" , " --orphan" , " fork" ] ) ;
9459
git ( & [ " rm" , " -q" , " -r" , " --cached" , " ." ] ) ;
9460
for f in [ " gulpfile.js" , " a.txt" ] {
9461
let _ = fs:: remove_file( g.join ( f) ) ;
9462
}
9463
write ( & g, " gulpfile.js" , text) ;
9464
write ( & g, " b.txt" , b " b\n " ) ;
9465
git ( & [ " add" , " ." ] ) ;
9466
git ( & [ " commit" , " -q" , " -m" , " B root" ] ) ;
9467
9468
git ( & [ " checkout" , " -q" , " -b" , " one" , " main" ] ) ;
9469
git ( & [
9470
" merge" ,
9471
" -q" ,
9472
" --no-ff" ,
9473
" --allow-unrelated-histories" ,
9474
" -m" ,
9475
" join on one" ,
9476
" fork" ,
9477
] ) ;
9478
git ( & [ " checkout" , " -q" , " -b" , " two" , " main" ] ) ;
9479
write ( & g, " a.txt" , b " a\n two\n " ) ;
9480
git ( & [ " commit" , " -q" , " -am" , " work on two" ] ) ;
9481
git ( & [
9482
" merge" ,
9483
" -q" ,
9484
" --no-ff" ,
9485
" --allow-unrelated-histories" ,
9486
" -m" ,
9487
" join on two" ,
9488
" fork" ,
9489
] ) ;
9490
9491
git ( & [ " checkout" , " -q" , " main" ] ) ;
9492
git ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge one" , " one" ] ) ;
9493
git ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge two" , " two" ] ) ;
9494
write (
9495
& g,
9496
" gulpfile.js" ,
9497
b " /* header */\n const a = 1;\n const b = 3;\n " ,
9498
) ;
9499
git ( & [ " commit" , " -q" , " -am" , " after" ] ) ;
9500
9501
let dest = root.join ( " dest" ) ;
9502
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9503
repo.import_git ( & g, " main" ) .unwrap ( ) ;
9504
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
9505
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
9506
assert_eq! (
9507
fs:: read( dest.join ( " gulpfile.js" ) ) .unwrap ( ) ,
9508
b " /* header */\n const a = 1;\n const b = 3;\n "
9509
) ;
9510
}
9511
9512
9513
9514
9515
# [ test ]
9516
fn git_import_joins_where_each_join_kept_a_different_copy ( ) {
9517
9518
9519
crate :: repo:: git:: set_merge_batch_for_tests( Some ( 1 ) ) ;
9520
hermetic ( ) ;
9521
if std:: process:: Command:: new( " git" )
9522
.arg ( " --version" )
9523
.output ( )
9524
.is_err ( )
9525
{
9526
eprintln! ( " git not installed; skipping" ) ;
9527
return ;
9528
}
9529
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9530
let root = dir.path ( ) ;
9531
let g = root.join ( " src.git" ) ;
9532
fs:: create_dir_all( & g) .unwrap ( ) ;
9533
let run = | args : & [& str ]| {
9534
std:: process:: Command:: new( " git" )
9535
.arg ( " -C" )
9536
.arg ( & g)
9537
.args ( [
9538
" -c" ,
9539
" user.name=Grace" ,
9540
" -c" ,
9541
" [email protected] " ,
9542
" -c" ,
9543
" commit.gpgsign=false" ,
9544
] )
9545
.args ( args)
9546
.output ( )
9547
.unwrap ( )
9548
} ;
9549
let git = | args : & [& str ]| -> String {
9550
let out = run ( args) ;
9551
assert! (
9552
out.status.success ( ) ,
9553
" git {args:?}: {}" ,
9554
String :: from_utf8_lossy( & out.stderr)
9555
) ;
9556
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
9557
} ;
9558
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
9559
write (
9560
& g,
9561
" gulpfile.js" ,
9562
b " /* header */\n const a = 1;\n const b = 2;\n " ,
9563
) ;
9564
git ( & [ " add" , " ." ] ) ;
9565
git ( & [ " commit" , " -q" , " -m" , " A root" ] ) ;
9566
write (
9567
& g,
9568
" gulpfile.js" ,
9569
b " /* header */\n const a = 1;\n \n const b = 2;\n " ,
9570
) ;
9571
git ( & [ " commit" , " -q" , " -am" , " whitespace" ] ) ;
9572
git ( & [ " checkout" , " -q" , " --orphan" , " fork" ] ) ;
9573
git ( & [ " rm" , " -q" , " -r" , " --cached" , " ." ] ) ;
9574
let _ = fs:: remove_file( g.join ( " gulpfile.js" ) ) ;
9575
write (
9576
& g,
9577
" gulpfile.js" ,
9578
b " /* header */\n const a = 1;\n const b = 2;\n const c = 3;\n " ,
9579
) ;
9580
git ( & [ " add" , " ." ] ) ;
9581
git ( & [ " commit" , " -q" , " -m" , " B root" ] ) ;
9582
9583
for ( branch, keep) in [ ( " one" , " ours" ) , ( " two" , " theirs" ) ] {
9584
git ( & [ " checkout" , " -q" , " -b" , branch, " main" ] ) ;
9585
let _ = run ( & [
9586
" merge" ,
9587
" -q" ,
9588
" --no-ff" ,
9589
" --allow-unrelated-histories" ,
9590
" -m" ,
9591
" join" ,
9592
" fork" ,
9593
] ) ;
9594
git ( & [ " checkout" , & format! ( " --{keep} " ) , " gulpfile.js" ] ) ;
9595
git ( & [ " add" , " gulpfile.js" ] ) ;
9596
git ( & [ " commit" , " -q" , " -m" , & format! ( " join on {branch} " ) ] ) ;
9597
}
9598
9599
git ( & [ " checkout" , " -q" , " one" ] ) ;
9600
write (
9601
& g,
9602
" gulpfile.js" ,
9603
b " /* header */\n const a = 1;\n \n const b = 2;\n // tslint\n " ,
9604
) ;
9605
git ( & [ " commit" , " -q" , " -am" , " tslint on one" ] ) ;
9606
git ( & [ " checkout" , " -q" , " two" ] ) ;
9607
write (
9608
& g,
9609
" gulpfile.js" ,
9610
b " /* header */\n const a = 1;\n const b = 2;\n const c = 3;\n // fmt\n " ,
9611
) ;
9612
write ( & g, " extra.txt" , b " x\n " ) ;
9613
write ( & g, " more.txt" , b " m\n " ) ;
9614
git ( & [ " add" , " ." ] ) ;
9615
git ( & [ " commit" , " -q" , " -am" , " fmt on two" ] ) ;
9616
9617
git ( & [ " checkout" , " -q" , " one" ] ) ;
9618
let _ = run ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge two" , " two" ] ) ;
9619
write (
9620
& g,
9621
" gulpfile.js" ,
9622
b " /* header */\n const a = 1;\n const b = 2;\n const c = 3;\n // tslint\n // fmt\n " ,
9623
) ;
9624
git ( & [ " add" , " gulpfile.js" ] ) ;
9625
git ( & [ " commit" , " -q" , " -m" , " merge two into one" ] ) ;
9626
9627
let dest = root.join ( " dest" ) ;
9628
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9629
repo.import_git ( & g, " one" ) .unwrap ( ) ;
9630
let v = repo.verify_git_import ( & g, " one" ) .unwrap ( ) ;
9631
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
9632
}
dd21fd82 Andy McCall 1 month ago ↶
9633
9634
9635
9636
9637
9638
9639
# [ test ]
9640
fn over_a_store_the_operation_head_moves_by_compare_and_swap ( ) {
9641
use crate :: strata:: { MemoryStore, Store as _ } ;
9642
let backend = std:: sync:: Arc:: new( MemoryStore:: new( ) ) ;
9643
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = Some ( backend.clone ( ) ) ) ;
9644
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9645
let a = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9646
assert! (
9647
! dir.path ( ) .join ( " .oxy/ops/head" ) .exists ( ) ,
9648
" the head is not a file"
9649
) ;
9650
let ( h0, op0) = a.head_op ( ) .unwrap ( ) ;
9651
let record = backend
9652
.read_ref ( HEAD_RECORD )
9653
.unwrap ( )
9654
.expect ( " the head is the store's record" ) ;
9655
assert_eq! ( String :: from_utf8( record.0 ) .unwrap ( ) , h0.to_hex ( ) ) ;
9656
9657
let b = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
9658
let state = a.store.put_state ( & StateObject:: new( vec! [ ] ) ) .unwrap ( ) ;
9659
let mut va = op0.view.clone ( ) ;
9660
va.bookmarks.insert ( " from-a" .into ( ) , state) ;
9661
let mut vb = op0.view.clone ( ) ;
9662
vb.bookmarks.insert ( " from-b" .into ( ) , state) ;
9663
let rb = b.record_op ( & [ h0] , " bookmark from-b" , vb) .unwrap ( ) ;
9664
assert_eq! ( b.head_ref ( ) .unwrap ( ) , rb) ;
9665
9666
let ra = a.record_op ( & [ h0] , " bookmark from-a" , va) .unwrap ( ) ;
9667
let ( head, op) = Repo:: open( dir.path ( ) ) .unwrap ( ) .head_op ( ) .unwrap ( ) ;
9668
assert_eq! ( head, ra) ;
9669
assert! (
9670
op.command.ends_with ( " (merged with a concurrent operation)" ) ,
9671
" {}" ,
9672
op.command
9673
) ;
9674
assert_eq! ( op.parents, vec! [ rb, op.parents[ 1 ] ] ) ;
9675
assert! ( op.view.bookmarks.contains_key ( " from-a" ) & & op.view.bookmarks.contains_key ( " from-b" ) ) ;
9676
9677
9678
let ( h, v) = a.read_head ( ) .unwrap ( ) ;
9679
assert! ( b.swap_head ( & rb, v.as_ref ( ) ) .unwrap ( ) ) ;
9680
assert! ( ! a.swap_head ( & h, v.as_ref ( ) ) .unwrap ( ) , " stale" ) ;
767ac6cd Andy McCall 17 days ago
9681
a.refresh ( ) .unwrap ( ) ;
dd21fd82 Andy McCall 1 month ago ↶
9682
assert_eq! ( a.head_ref ( ) .unwrap ( ) , rb) ;
9683
9684
let other = tempfile:: tempdir( ) .unwrap ( ) ;
9685
let err = Repo:: init( other.path ( ) )
9686
.err ( )
9687
.expect ( " the store already holds a log" )
9688
.to_string ( ) ;
9689
assert! (
9690
err.contains ( " already holds a repository's operation log" ) ,
9691
" {err}"
9692
) ;
9693
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = None ) ;
9694
}
d069701a Andy McCall 1 month ago ↶
9695
9696
9697
9698
9699
# [ test ]
9700
fn a_repository_runs_on_a_store_with_a_cache_in_front ( ) {
9701
hermetic ( ) ;
9702
if std:: env:: var_os( " OXY_STORE_BACKEND" ) .is_some ( ) {
9703
return ;
9704
}
9705
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9706
let ( root, bucket) = ( dir.path ( ) .join ( " work" ) , dir.path ( ) .join ( " bucket" ) ) ;
9707
let url = format! ( " dir://{} " , bucket.display ( ) ) ;
9708
let repo = Repo:: init_on( & root, [ 7 ; 16 ] , Some ( & url) ) .unwrap ( ) ;
9709
assert_eq! ( repo.config.storage_url.as_deref ( ) , Some ( url.as_str ( ) ) ) ;
9710
write ( & root, " a.txt" , b " one\n " ) ;
9711
let h1 = repo.commit ( " First" , & [ ] ) .unwrap ( ) ;
9712
write ( & root, " a.txt" , b " one\n two\n " ) ;
9713
let h2 = repo.commit ( " Second" , & [ ] ) .unwrap ( ) ;
9714
assert! (
9715
! root.join ( " .oxy/ops/head" ) .exists ( ) ,
9716
" the head is in the store"
9717
) ;
9718
assert! ( bucket.join ( " ops/head" ) .is_file ( ) ) ;
9719
assert! ( root.join ( " .oxy/cache" ) .is_dir ( ) ) ;
9720
drop ( repo) ;
9721
9722
9723
for drop_cache in [ false , true ] {
9724
if drop_cache {
9725
std:: fs:: remove_dir_all( root.join ( " .oxy/cache" ) ) .unwrap ( ) ;
9726
}
9727
let repo = Repo:: open( & root) .unwrap ( ) ;
9728
let log: Vec< String> = repo
9729
.op_log ( )
9730
.unwrap ( )
9731
.into_iter ( )
9732
.map ( | ( _, op ) | op.command )
9733
.collect ( ) ;
9734
assert! ( log.len ( ) > = 3 , " {log:?}" ) ;
9735
assert_eq! ( repo.store ( ) .get_change ( & h1) .unwrap ( ) .message, " First" ) ;
9736
let heads = repo
9737
.store ( )
9738
.get_state ( & repo.view ( ) .unwrap ( ) .bookmarks[ " main" ] )
9739
.unwrap ( )
9740
.heads;
9741
assert_eq! ( heads, vec! [ h2] ) ;
9742
assert_eq! (
9743
repo.load_state ( & heads) .unwrap ( ) .materialize ( ) .files.len ( ) ,
9744
1
9745
) ;
9746
}
9747
9748
9749
let err = Repo:: init_on( & dir.path ( ) .join ( " other" ) , [ 8 ; 16 ] , Some ( & url) )
9750
.err ( )
9751
.unwrap ( )
9752
.to_string ( ) ;
9753
assert! (
9754
err.contains ( " already holds a repository's operation log" ) ,
9755
" {err}"
9756
) ;
9757
let err = Repo:: init_on( & dir.path ( ) .join ( " third" ) , [ 9 ; 16 ] , Some ( " nope://x" ) )
9758
.err ( )
9759
.unwrap ( )
9760
.to_string ( ) ;
9761
assert! ( err.contains ( " cannot open that kind of store" ) , " {err}" ) ;
9762
}
80677b7a Andy McCall 1 month ago ↶
9763
9764
9765
9766
9767
9768
# [ test ]
9769
fn the_quick_dirty_check_compares_a_subrepo_by_its_pin ( ) {
9770
hermetic ( ) ;
9771
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9772
let root = dir.path ( ) ;
9773
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9774
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9775
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9776
.unwrap ( )
9777
.with_home ( root.join ( " home" ) ) ;
9778
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9779
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9780
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9781
let app_dir = root.join ( " app" ) ;
9782
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9783
write ( & app_dir, " main.txt" , b " app\n " ) ;
9784
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9785
app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
9786
9787
std:: thread:: sleep( std:: time:: Duration:: from_millis( 2100 ) ) ;
9788
let reopen = | | Repo:: open( & app_dir) .unwrap ( ) ;
9789
assert_eq! (
9790
reopen ( ) .quick_dirty ( ) .unwrap ( ) ,
9791
Some ( false ) ,
9792
" clean, as status says"
9793
) ;
9794
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) ) .unwrap ( ) ;
9795
write ( & app_dir.join ( " libs/lib" ) , " lib.txt" , b " v2\n " ) ;
9796
nested.commit ( " lib v2" , & [ ] ) .unwrap ( ) ;
9797
assert_eq! (
9798
reopen ( ) .quick_dirty ( ) .unwrap ( ) ,
9799
Some ( true ) ,
9800
" the subrepo moved"
9801
) ;
9802
reopen ( ) .commit ( " pin lib v2" , & [ ] ) .unwrap ( ) ;
9803
std:: thread:: sleep( std:: time:: Duration:: from_millis( 2100 ) ) ;
9804
assert_eq! (
9805
reopen ( ) .quick_dirty ( ) .unwrap ( ) ,
9806
Some ( false ) ,
9807
" the new pin is committed"
9808
) ;
9809
}
7993d465 Andy McCall 1 month ago ↶
9810
9811
# [ test ]
9812
fn a_module_entry_that_was_never_added_is_reported_as_unpinned ( ) {
9813
9814
hermetic ( ) ;
9815
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9816
let root = dir.path ( ) ;
9817
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9818
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9819
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9820
.unwrap ( )
9821
.with_home ( root.join ( " home" ) ) ;
9822
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9823
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9824
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9825
let app_dir = root.join ( " app" ) ;
9826
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9827
write (
9828
& app_dir,
9829
" .oxymodules" ,
9830
format! ( " [libs/lib]\n url = {lib_url} \n " ) .as_bytes ( ) ,
9831
) ;
9832
let unpinned = app.unpinned_modules ( ) .unwrap ( ) ;
9833
assert_eq! ( unpinned.len ( ) , 1 ) ;
9834
assert_eq! ( unpinned[ 0 ] .0 , " libs/lib" ) ;
9835
assert_eq! ( unpinned[ 0 ] .1. url, lib_url) ;
9836
assert! ( app.submodule_update ( true , & [ ] ) .unwrap ( ) .is_empty ( ) ) ;
9837
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9838
assert! ( app.unpinned_modules ( ) .unwrap ( ) .is_empty ( ) , " added now" ) ;
9839
}
bf3a6781 Andy McCall 1 month ago ↶
9840
9841
# [ test ]
9842
fn the_bookmark_revset_function_names_only_bookmarks ( ) {
9843
9844
hermetic ( ) ;
9845
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9846
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9847
write ( dir.path ( ) , " a.txt" , b " a\n " ) ;
9848
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
9849
write ( dir.path ( ) , " b.txt" , b " b\n " ) ;
9850
let b = repo.commit ( " B" , & [ ] ) .unwrap ( ) ;
9851
let mut heads = repo.revset ( " bookmark(main)" ) .unwrap ( ) .hashes;
9852
heads.sort ( ) ;
9853
let mut want = repo.bookmark_heads ( ) .unwrap ( ) ;
9854
want.sort ( ) ;
9855
assert_eq! ( heads, want) ;
9856
assert! ( heads.contains ( & b) ) ;
9857
assert_eq! ( repo.revset ( " main" ) .unwrap ( ) .hashes.len ( ) , heads.len ( ) ) ;
9858
9859
let prefix = repo.store ( ) .get_change_meta ( & a) .unwrap ( ) .change_id.short ( 8 ) ;
9860
assert! ( repo.revset ( & prefix) .is_ok ( ) ) ;
9861
let e = repo
9862
.revset ( & format! ( " bookmark({prefix} )" ) )
9863
.unwrap_err ( )
9864
.to_string ( ) ;
9865
assert! ( e.contains ( " no such bookmark" ) , " {e}" ) ;
9866
}
72484195 Andy McCall 1 month ago ↶
9867
157d02e9 Andy McCall 10 days ago ↶
9868
9869
9870
72484195 Andy McCall 1 month ago ↶
9871
# [ test ]
157d02e9 Andy McCall 10 days ago ↶
9872
fn a_replace_conflict_is_settled_from_the_working_copy ( ) {
9873
hermetic ( ) ;
9874
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9875
let root = dir.path ( ) ;
9876
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9877
let blob = | n : u8 | {
9878
let mut v = vec! [ 0 u8 ; 64 ] ;
9879
v[ 1 ] = n;
9880
v
9881
} ;
9882
write ( root, " img.bin" , & blob ( 1 ) ) ;
9883
write ( root, " a.txt" , b " a\n " ) ;
9884
repo.commit ( " add img" , & [ ] ) .unwrap ( ) ;
9885
repo.bookmark_create ( " other" , None ) .unwrap ( ) ;
9886
write ( root, " img.bin" , & blob ( 2 ) ) ;
9887
repo.commit ( " img two" , & [ ] ) .unwrap ( ) ;
9888
repo.bookmark_switch ( " other" , false ) .unwrap ( ) ;
9889
write ( root, " img.bin" , & blob ( 3 ) ) ;
9890
repo.commit ( " img three" , & [ ] ) .unwrap ( ) ;
9891
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
9892
repo.merge ( " other" ) .unwrap ( ) ;
9893
9894
9895
write ( root, " img.bin" , & blob ( 9 ) ) ;
9896
write ( root, " a.txt" , b " a\n more\n " ) ;
9897
assert! (
9898
repo.commit ( " edit a" , & [ ] ) .is_err ( ) ,
9899
" refused while conflicted"
9900
) ;
9901
let snap = repo.snapshot ( " oxy resolve" ) .unwrap ( ) ;
9902
assert! (
9903
repo.choose_resolutions ( & snap.base_snapshot, & [ " a.txt" .to_string ( ) ] )
9904
.is_err ( ) ,
9905
" a.txt has no replace conflict"
9906
) ;
9907
repo.choose_resolutions ( & snap.base_snapshot, & [ " img.bin" .to_string ( ) ] )
9908
.unwrap ( ) ;
9909
let snap = repo.snapshot ( " oxy resolve" ) .unwrap ( ) ;
9910
assert! ( snap
9911
.statuses
9912
.iter ( )
9913
.any ( | s | s.path = = " img.bin" & & s.status = = ChangeStatus:: Resolved ) ) ;
9914
let c = repo.commit ( " settle img" , & [ ] ) .unwrap ( ) ;
9915
assert_eq! (
9916
repo.store ( ) .get_change ( & c) .unwrap ( ) .kind,
9917
ChangeKind:: Resolution
9918
) ;
9919
let after = repo
9920
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
9921
.unwrap ( )
9922
.materialize ( ) ;
9923
assert! ( ! after.is_conflicted ( ) ) ;
9924
assert_eq! (
9925
after.files[ " img.bin" ] .content,
9926
FileContent:: Opaque {
9927
blobs: vec! [ Repo:: blob_ref( & blob ( 9 ) ) ]
9928
}
9929
) ;
9930
assert! (
9931
matches! ( & after.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n more\n " )
9932
) ;
9933
9934
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
9935
assert! ( snap.statuses.is_empty ( ) , " {:?}" , snap.statuses) ;
9936
assert! ( repo.resolutions ( ) .unwrap ( ) .is_empty ( ) ) ;
9937
}
9938
9939
9940
# [ test ]
9941
fn a_pin_conflict_is_settled_from_the_working_copy ( ) {
9942
hermetic ( ) ;
9943
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9944
let root = dir.path ( ) ;
9945
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9946
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9947
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9948
.unwrap ( )
9949
.with_home ( root.join ( " home" ) ) ;
9950
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9951
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9952
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9953
let app_dir = root.join ( " app" ) ;
9954
let mut app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9955
write ( & app_dir, " main.txt" , b " app\n " ) ;
9956
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9957
app.commit ( " pin v1" , & [ ] ) .unwrap ( ) ;
9958
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) )
9959
.unwrap ( )
9960
.with_home ( root.join ( " home" ) ) ;
9961
fn pin ( app : & Repo, nested : & Repo, text : & [u8 ], message : & str ) {
9962
write ( nested.root ( ) , " lib.txt" , text) ;
9963
nested.commit ( message, & [ ] ) .unwrap ( ) ;
9964
app.commit ( & format! ( " pin {message} " ) , & [ ] ) .unwrap ( ) ;
9965
}
9966
app.bookmark_create ( " other" , None ) .unwrap ( ) ;
9967
pin ( & app, & nested, b " v2\n " , " v2" ) ;
9968
app.bookmark_switch ( " other" , false ) .unwrap ( ) ;
9969
pin ( & app, & nested, b " v3\n " , " v3" ) ;
9970
app.bookmark_switch ( " main" , false ) .unwrap ( ) ;
9971
app.merge ( " other" ) .unwrap ( ) ;
9972
let merged = app
9973
.load_state ( & app.bookmark_heads ( ) .unwrap ( ) )
9974
.unwrap ( )
9975
.materialize ( ) ;
9976
assert! (
9977
matches! ( & merged.files[ " libs/lib" ] .content, FileContent:: Opaque { blobs } if blobs.len ( ) = = 2 ) ,
9978
" a pin conflict"
9979
) ;
9980
9981
write ( & app_dir.join ( " libs/lib" ) , " lib.txt" , b " v4\n " ) ;
9982
nested.commit ( " v4" , & [ ] ) .unwrap ( ) ;
9983
let snap = app.snapshot ( " oxy resolve" ) .unwrap ( ) ;
9984
app.choose_resolutions ( & snap.base_snapshot, & [ " libs/lib" .to_string ( ) ] )
9985
.unwrap ( ) ;
9986
let c = app.commit ( " settle the pin" , & [ ] ) .unwrap ( ) ;
9987
assert_eq! (
9988
app.store ( ) .get_change ( & c) .unwrap ( ) .kind,
9989
ChangeKind:: Resolution
9990
) ;
9991
let after = app
9992
.load_state ( & app.bookmark_heads ( ) .unwrap ( ) )
9993
.unwrap ( )
9994
.materialize ( ) ;
9995
assert! ( ! after.is_conflicted ( ) ) ;
9996
assert! ( app.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
9997
}
9998
9999
# [ test ]
72484195 Andy McCall 1 month ago ↶
10000
fn carrying_work_over_a_subrepo_already_at_the_target_pin_is_no_conflict ( ) {
10001
10002
10003
hermetic ( ) ;
10004
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10005
let root = dir.path ( ) ;
10006
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
10007
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
10008
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
10009
.unwrap ( )
10010
.with_home ( root.join ( " home" ) ) ;
10011
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
10012
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
10013
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10014
let app_dir = root.join ( " app" ) ;
10015
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
10016
write ( & app_dir, " main.txt" , b " app\n " ) ;
10017
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
10018
10019
fs:: rename( app_dir.join ( " libs/lib" ) , root.join ( " aside" ) ) .unwrap ( ) ;
10020
app.commit ( " initial" , & [ ] ) .unwrap ( ) ;
10021
let stale = app.bookmark_heads ( ) .unwrap ( ) ;
10022
fs:: rename( root.join ( " aside" ) , app_dir.join ( " libs/lib" ) ) .unwrap ( ) ;
10023
app.commit ( " pin lib" , & [ ] ) .unwrap ( ) ;
10024
let newer = app.bookmark_heads ( ) .unwrap ( ) ;
10025
app.checkout ( & stale, false , " test" ) .unwrap ( ) ;
10026
write ( & app_dir, " notes.txt" , b " mine\n " ) ;
10027
let ( _ , carried) = app.checkout_carry ( & newer, " main" , " test" ) .unwrap ( ) ;
10028
assert_eq! ( carried, vec! [ " notes.txt" .to_string ( ) ] ) ;
10029
assert! ( app_dir.join ( " notes.txt" ) .exists ( ) ) ;
10030
}
10031
c8d9059e Andy McCall 10 days ago ↶
10032
10033
72484195 Andy McCall 1 month ago ↶
10034
# [ test ]
c8d9059e Andy McCall 10 days ago ↶
10035
fn a_new_bookmark_sends_only_what_the_remote_lacks ( ) {
10036
hermetic ( ) ;
10037
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10038
let root = dir.path ( ) ;
10039
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
10040
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
10041
let mut one = Repo:: clone( & url, & root.join ( " one" ) )
10042
.unwrap ( )
10043
.with_home ( root.join ( " home" ) ) ;
10044
for n in 0 .. 5 {
10045
write ( & root.join ( " one" ) , " a.txt" , format! ( " {n} \n " ) .as_bytes ( ) ) ;
10046
one.commit ( & format! ( " main {n} " ) , & [ ] ) .unwrap ( ) ;
10047
}
10048
assert! ( one.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10049
one.bookmark_create ( " feature" , None ) .unwrap ( ) ;
10050
one.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
10051
write ( & root.join ( " one" ) , " b.txt" , b " feature\n " ) ;
10052
let added = one.commit ( " feature" , & [ ] ) .unwrap ( ) ;
10053
let heads = one.bookmark_heads ( ) .unwrap ( ) ;
10054
let refs = one
10055
.open_named_remote ( " origin" )
10056
.unwrap ( )
10057
.refs ( )
10058
.unwrap ( )
10059
.refs;
10060
assert! (
10061
refs.iter ( ) .all ( | r | r.name ! = " feature" ) ,
10062
" new to the remote"
10063
) ;
10064
let sent = one
10065
.negotiate ( & one.remote_holds ( & refs, & [ ] ) , & heads)
10066
.unwrap ( ) ;
10067
assert_eq! ( sent, vec! [ added] , " only the change main lacks" ) ;
10068
assert! ( one.push_bookmark ( " origin" , " feature" , false ) .unwrap ( ) [ 0 ] .ok) ;
10069
let two = Repo:: clone( & url, & root.join ( " two" ) ) .unwrap ( ) ;
10070
assert! ( two.store ( ) .has ( & added) ) ;
10071
}
10072
10073
# [ test ]
72484195 Andy McCall 1 month ago ↶
10074
fn a_bookmark_only_behind_its_remote_catches_up ( ) {
10075
10076
hermetic ( ) ;
10077
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10078
let root = dir.path ( ) ;
10079
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
10080
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
10081
let one = Repo:: clone( & url, & root.join ( " one" ) )
10082
.unwrap ( )
10083
.with_home ( root.join ( " home" ) ) ;
10084
write ( & root.join ( " one" ) , " a.txt" , b " a\n " ) ;
10085
one.commit ( " A" , & [ ] ) .unwrap ( ) ;
10086
assert! ( one.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10087
let two = Repo:: clone( & url, & root.join ( " two" ) )
10088
.unwrap ( )
10089
.with_home ( root.join ( " home" ) ) ;
10090
write ( & root.join ( " two" ) , " b.txt" , b " b\n " ) ;
10091
two.commit ( " B" , & [ ] ) .unwrap ( ) ;
10092
assert! ( two.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10093
assert_eq! ( one.catch_up_bookmark ( " main" ) .unwrap ( ) , None , " not fetched" ) ;
10094
one.fetch ( " origin" ) .unwrap ( ) ;
10095
assert_eq! ( one.catch_up_bookmark ( " main" ) .unwrap ( ) , Some ( 1 ) ) ;
10096
assert_eq! ( one.bookmarks ( ) .unwrap ( ) [ 0 ] .behind, Some ( 0 ) ) ;
10097
10098
write ( & root.join ( " one" ) , " c.txt" , b " c\n " ) ;
10099
one.commit ( " C" , & [ ] ) .unwrap ( ) ;
10100
write ( & root.join ( " two" ) , " d.txt" , b " d\n " ) ;
10101
two.commit ( " D" , & [ ] ) .unwrap ( ) ;
10102
assert! ( two.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10103
one.fetch ( " origin" ) .unwrap ( ) ;
10104
assert_eq! ( one.catch_up_bookmark ( " main" ) .unwrap ( ) , None ) ;
10105
}
42ccf504 Andy McCall 27 days ago ↶
10106
10107
# [ test ]
10108
fn a_bookmark_is_compared_with_a_remote_that_has_it_when_there_is_no_origin ( ) {
10109
10110
hermetic ( ) ;
10111
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10112
let root = dir.path ( ) ;
10113
let _up = Repo:: init( & root.join ( " up" ) ) .unwrap ( ) ;
10114
let url = root.join ( " up" ) .to_string_lossy ( ) .to_string ( ) ;
10115
let other = Repo:: clone( & url, & root.join ( " other" ) )
10116
.unwrap ( )
10117
.with_home ( root.join ( " home" ) ) ;
10118
write ( & root.join ( " other" ) , " a.txt" , b " a\n " ) ;
10119
other.commit ( " a" , & [ ] ) .unwrap ( ) ;
10120
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10121
let mut one = Repo:: clone( & url, & root.join ( " one" ) )
10122
.unwrap ( )
10123
.with_home ( root.join ( " home" ) ) ;
10124
one.config.set ( " remote.earth.url" , & url) .unwrap ( ) ;
10125
one.config.set ( " remote.gcs.url" , & url) .unwrap ( ) ;
10126
one.config.set ( " remote.origin.url" , " " ) .unwrap ( ) ;
10127
one.save_config ( ) .unwrap ( ) ;
10128
write ( & root.join ( " other" ) , " b.txt" , b " b\n " ) ;
10129
other.commit ( " b" , & [ ] ) .unwrap ( ) ;
10130
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10131
one.fetch ( " earth" ) .unwrap ( ) ;
10132
let b = one
10133
.bookmarks ( )
10134
.unwrap ( )
10135
.into_iter ( )
10136
.find ( | b | b.name = = " main" )
10137
.unwrap ( ) ;
10138
assert_eq! (
10139
( b.remote.as_deref ( ) , b.behind) ,
10140
( Some ( " earth" ) , Some ( 1 ) ) ,
10141
" a stale origin copy is not used"
10142
) ;
10143
assert_eq! (
10144
one.default_remote ( ) ,
10145
" origin" ,
10146
" no default chosen among several"
10147
) ;
10148
let e = one.pull ( & one.default_remote ( ) ) .unwrap_err ( ) .to_string ( ) ;
10149
assert! ( e.contains ( " earth, gcs" ) & & e.contains ( " core.remote" ) , " {e}" ) ;
10150
one.config.set ( " core.remote" , " earth" ) .unwrap ( ) ;
10151
assert_eq! ( one.default_remote ( ) , " earth" ) ;
10152
}
cbe8499a Andy McCall 27 days ago ↶
10153
10154
# [ test ]
10155
fn a_missing_author_identity_says_how_to_fix_it ( ) {
10156
10157
10158
hermetic ( ) ;
10159
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10160
let mut repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
10161
let missing = KeyId:: from_hex( & " ab" .repeat ( 32 ) ) .unwrap ( ) ;
10162
repo.config.set ( " author.key" , & missing.to_hex ( ) ) .unwrap ( ) ;
10163
let e = repo.identity ( & missing) .unwrap_err ( ) .to_string ( ) ;
10164
assert! (
10165
e.contains ( " this repository has no identity" ) & & e.contains ( " oxy config author.key" ) ,
10166
" {e}"
10167
) ;
10168
assert! ( e.contains ( " this repository's config" ) , " {e}" ) ;
10169
}
f6dccc45 Andy McCall 26 days ago ↶
10170
10171
# [ test ]
10172
fn a_working_copy_behind_its_bookmark_is_flagged_and_pull_brings_it_up ( ) {
10173
10174
10175
10176
hermetic ( ) ;
10177
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10178
let root = dir.path ( ) ;
10179
let _up = Repo:: init( & root.join ( " up" ) ) .unwrap ( ) ;
10180
let url = root.join ( " up" ) .to_string_lossy ( ) .to_string ( ) ;
10181
let other = Repo:: clone( & url, & root.join ( " other" ) )
10182
.unwrap ( )
10183
.with_home ( root.join ( " home" ) ) ;
10184
write ( & root.join ( " other" ) , " a.txt" , b " a\n " ) ;
10185
other.commit ( " a" , & [ ] ) .unwrap ( ) ;
10186
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10187
let one = Repo:: clone( & url, & root.join ( " one" ) )
10188
.unwrap ( )
10189
.with_home ( root.join ( " home" ) ) ;
10190
write ( & root.join ( " other" ) , " b.txt" , b " b\n " ) ;
10191
other.commit ( " b" , & [ ] ) .unwrap ( ) ;
10192
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10193
one.fetch ( " origin" ) .unwrap ( ) ;
10194
10195
let theirs = one.resolve_heads ( " origin/main" ) .unwrap ( ) ;
10196
one.bookmark_set ( " main" , & theirs) .unwrap ( ) ;
10197
assert! ( ! root.join ( " one/b.txt" ) .exists ( ) ) ;
10198
assert_eq! ( one.working_copy_behind ( ) .unwrap ( ) , Some ( 1 ) ) ;
10199
let r = one.pull ( " origin" ) .unwrap ( ) ;
10200
assert! (
10201
! r.changed & & r.checked_out,
10202
" nothing new, but the working copy caught up"
10203
) ;
10204
assert! ( root.join ( " one/b.txt" ) .exists ( ) ) ;
10205
assert_eq! ( one.working_copy_behind ( ) .unwrap ( ) , None ) ;
10206
}
170bb436 Andy McCall 26 days ago ↶
10207
10208
# [ test ]
10209
fn touched_nodes_cache_answers_as_the_changes_do ( ) {
10210
hermetic ( ) ;
10211
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10212
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
10213
write ( dir.path ( ) , " a.txt" , b " one\n " ) ;
10214
write ( dir.path ( ) , " b.txt" , b " x\n " ) ;
10215
repo.snapshot ( " t" ) .unwrap ( ) ;
10216
let c1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
10217
write ( dir.path ( ) , " b.txt" , b " y\n " ) ;
10218
repo.snapshot ( " t" ) .unwrap ( ) ;
10219
repo.commit ( " b only" , & [ ] ) .unwrap ( ) ;
10220
fs:: rename( dir.path ( ) .join ( " a.txt" ) , dir.path ( ) .join ( " c.txt" ) ) .unwrap ( ) ;
10221
write ( dir.path ( ) , " c.txt" , b " one\n two\n " ) ;
10222
repo.snapshot ( " t" ) .unwrap ( ) ;
10223
let c3 = repo.commit ( " rename and edit" , & [ ] ) .unwrap ( ) ;
10224
10225
write ( dir.path ( ) , " c.txt" , b " one\n two\n three\n " ) ;
10226
repo.snapshot ( " t" ) .unwrap ( ) ;
10227
10228
let path = dir.path ( ) .join ( " .oxy/index/touched" ) ;
10229
let hashes = | es : Vec< LogEntry> | es.iter ( ) .map ( | e | e.hash ) .collect:: < Vec< _ > > ( ) ;
10230
let cold = hashes ( repo.log_for_path ( " c.txt" ) .unwrap ( ) ) ;
10231
assert! ( path.exists ( ) , " the first query writes the cache" ) ;
10232
let warm = hashes ( repo.log_for_path ( " c.txt" ) .unwrap ( ) ) ;
10233
assert_eq! ( cold, warm, " the cache answers as the changes do" ) ;
10234
let ( wc, _ ) = repo.working_copy ( ) .unwrap ( ) ;
10235
assert_eq! (
10236
cold,
10237
vec! [ wc, c3, c1] ,
10238
" renames followed, the working copy included"
10239
) ;
10240
10241
10242
let cached = super :: touched:: read_touched_for_test( & fs:: read( & path) .unwrap ( ) ) .unwrap ( ) ;
10243
assert! ( ! cached.contains ( & wc) , " a working-copy change is not cached" ) ;
10244
assert! ( cached.contains ( & c1) & & cached.contains ( & c3) ) ;
10245
10246
10247
let mut bytes = fs:: read( & path) .unwrap ( ) ;
10248
let last = bytes.len ( ) - 40 ;
10249
bytes[ last] ^ = 1 ;
10250
fs:: write( & path, & bytes) .unwrap ( ) ;
10251
assert_eq! ( hashes ( repo.log_for_path ( " c.txt" ) .unwrap ( ) ) , cold) ;
10252
let touches = repo.log_revset ( " touches(\" c.txt\" ) ~ @" ) .unwrap ( ) ;
10253
assert_eq! ( hashes ( touches) , vec! [ c3, c1] ) ;
10254
}
e8e6e0ca Andy McCall 26 days ago ↶
10255
10256
10257
10258
10259
10260
# [ test ]
10261
fn git_import_removes_every_copy_of_a_file_at_one_place ( ) {
10262
hermetic ( ) ;
10263
if std:: process:: Command:: new( " git" )
10264
.arg ( " --version" )
10265
.output ( )
10266
.is_err ( )
10267
{
10268
eprintln! ( " git not installed; skipping" ) ;
10269
return ;
10270
}
10271
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10272
let root = dir.path ( ) ;
10273
let g = root.join ( " src.git" ) ;
10274
fs:: create_dir_all( & g) .unwrap ( ) ;
10275
let git = | args : & [& str ]| {
10276
let out = std:: process:: Command:: new( " git" )
10277
.arg ( " -C" )
10278
.arg ( & g)
10279
.args ( [
10280
" -c" ,
10281
" user.name=Grace" ,
10282
" -c" ,
10283
" [email protected] " ,
10284
" -c" ,
10285
" commit.gpgsign=false" ,
10286
] )
10287
.args ( args)
10288
.output ( )
10289
.unwrap ( ) ;
10290
assert! (
10291
out.status.success ( ) ,
10292
" git {args:?}: {}" ,
10293
String :: from_utf8_lossy( & out.stderr)
10294
) ;
10295
} ;
10296
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
10297
write ( & g, " d/list.txt" , b " one\n two\n three\n four\n " ) ;
10298
write ( & g, " keep.txt" , b " k\n " ) ;
10299
git ( & [ " add" , " ." ] ) ;
10300
git ( & [ " commit" , " -qm" , " base" ] ) ;
10301
fs:: create_dir_all( g.join ( " d/sub" ) ) .unwrap ( ) ;
10302
git ( & [ " mv" , " d/list.txt" , " d/sub/list.txt" ] ) ;
10303
git ( & [ " commit" , " -qm" , " into sub" ] ) ;
10304
git ( & [ " checkout" , " -qb" , " side" ] ) ;
10305
git ( & [ " mv" , " d/sub/list.txt" , " d/list.txt" ] ) ;
10306
git ( & [ " commit" , " -qm" , " back, on side" ] ) ;
10307
git ( & [ " checkout" , " -q" , " main" ] ) ;
10308
git ( & [ " mv" , " d/sub/list.txt" , " d/list.txt" ] ) ;
10309
git ( & [ " commit" , " -qm" , " back, on main" ] ) ;
10310
git ( & [ " merge" , " -q" , " --no-edit" , " side" ] ) ;
10311
git ( & [ " rm" , " -rq" , " d" ] ) ;
10312
git ( & [ " commit" , " -qm" , " d goes" ] ) ;
10313
10314
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
10315
repo.import_git ( & g, " main" ) .unwrap ( ) ;
10316
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
10317
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
10318
let st = repo
10319
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
10320
.unwrap ( ) ;
10321
let view = st.tree ( ) .view ( ) ;
10322
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
10323
assert_eq! ( view.entries.keys ( ) .collect:: < Vec< _ > > ( ) , vec! [ " keep.txt" ] ) ;
10324
}
5552e69c Andy McCall 26 days ago ↶
10325
10326
10327
10328
10329
# [ test ]
10330
fn a_damaged_file_history_in_the_state_index_is_rebuilt ( ) {
10331
hermetic ( ) ;
10332
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10333
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
10334
for i in 0 .. 6 {
10335
write (
10336
dir.path ( ) ,
10337
& format! ( " f{i} .txt" ) ,
10338
format! ( " one {i} \n " ) .as_bytes ( ) ,
10339
) ;
10340
}
10341
repo.snapshot ( " t" ) .unwrap ( ) ;
10342
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
10343
for i in 0 .. 6 {
10344
write (
10345
dir.path ( ) ,
10346
& format! ( " f{i} .txt" ) ,
10347
format! ( " one {i} \n two\n " ) .as_bytes ( ) ,
10348
) ;
10349
}
10350
repo.snapshot ( " t" ) .unwrap ( ) ;
10351
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
10352
let want: Vec< Vec< u8 > > = ( 0 .. 6 )
10353
.map ( | i | format! ( " one {i} \n two\n " ) .into_bytes ( ) )
10354
.collect ( ) ;
10355
10356
10357
let ( _ , wc) = repo.working_copy ( ) .unwrap ( ) ;
10358
let base = repo.load_state_history ( & wc.deps) .unwrap ( ) ;
10359
repo.write_state_index ( & wc.deps, & base) ;
10360
for ( i, w) in want.iter ( ) .enumerate ( ) {
10361
assert_eq! ( & repo.file_at ( & format! ( " f{i} .txt" ) , None ) .unwrap ( ) , w) ;
10362
}
10363
let index = repo.state_indexes ( ) ;
10364
assert! ( ! index.is_empty ( ) , " a state index was written" ) ;
10365
10366
let mut bytes = fs:: read( & index[ 0 ] ) .unwrap ( ) ;
10367
let last = bytes.len ( ) - 1 ;
10368
bytes[ last] ^ = 0x40 ;
10369
fs:: write( & index[ 0 ] , & bytes) .unwrap ( ) ;
10370
10371
let reads: Vec< _ > = ( 0 .. 6 )
10372
.map ( | i | repo.file_at ( & format! ( " f{i} .txt" ) , None ) )
10373
.collect ( ) ;
10374
let damaged: Vec< String> = reads
10375
.iter ( )
10376
.filter_map ( | r | r.as_ref ( ) .err ( ) .map ( | e | e.to_string ( ) ) )
10377
.collect ( ) ;
10378
assert_eq! ( damaged.len ( ) , 1 , " {damaged:?}" ) ;
10379
assert! (
10380
damaged[ 0 ] .contains ( " state index was damaged" )
10381
& & damaged[ 0 ] .contains ( " run the command again" ) ,
10382
" {}" ,
10383
damaged[ 0 ]
10384
) ;
10385
10386
for ( i, w) in want.iter ( ) .enumerate ( ) {
10387
assert_eq! ( & repo.file_at ( & format! ( " f{i} .txt" ) , None ) .unwrap ( ) , w) ;
10388
}
10389
}
cb8b8082 Andy McCall 26 days ago ↶
10390
10391
10392
10393
10394
10395
# [ test ]
10396
fn a_long_push_goes_in_parts ( ) {
10397
hermetic ( ) ;
10398
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10399
let root = dir.path ( ) ;
10400
let origin = root.join ( " origin" ) ;
10401
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
10402
let main = root.join ( " main" ) ;
10403
let mut repo = Repo:: init( & main) .unwrap ( ) ;
10404
repo.config
10405
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
10406
.unwrap ( ) ;
10407
10408
repo.config.set ( " push.batch-bytes" , " 1" ) .unwrap ( ) ;
10409
repo.save_config ( ) .unwrap ( ) ;
10410
for i in 0 .. 6 {
10411
write (
10412
& main,
10413
& format! ( " f{i} .txt" ) ,
10414
format! ( " file {i} \n " ) .as_bytes ( ) ,
10415
) ;
10416
repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ;
10417
}
10418
let mut parts = Vec :: new( ) ;
10419
let results = repo
10420
.push_bookmark_with ( " origin" , " main" , false , & mut | m| parts.push ( m.to_string ( ) ) )
10421
.unwrap ( ) ;
10422
assert! (
10423
results.iter ( ) .any ( | r | r.ok & & r.name = = " main" ) ,
10424
" {results:?}"
10425
) ;
10426
assert_eq! ( parts.len ( ) , 6 , " {parts:?}" ) ;
10427
assert_eq! ( parts[ 0 ] , " part 1 of 6: 1 changes" ) ;
10428
10429
let remote = Repo:: open( & origin) .unwrap ( ) ;
10430
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
10431
assert_eq! ( remote.bookmark_heads ( ) .unwrap ( ) , heads) ;
10432
assert_eq! (
10433
remote.log_state ( & heads) .unwrap ( ) .len ( ) ,
10434
repo.log_state ( & heads) .unwrap ( ) .len ( )
10435
) ;
10436
10437
let got = remote
10438
.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ ] )
10439
.unwrap ( ) ;
10440
assert_eq! ( got.len ( ) , 1 ) ;
10441
assert! ( got[ 0 ] .ok & & got[ 0 ] .name = = OBJECTS_RESULT , " {got:?}" ) ;
10442
}
d6e0d5b8 Andy McCall 25 days ago ↶
10443
d3b27090 Andy McCall 11 days ago ↶
10444
10445
10446
10447
10448
# [ test ]
10449
fn a_heavy_change_sends_its_blobs_in_parts ( ) {
10450
hermetic ( ) ;
10451
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10452
let root = dir.path ( ) ;
10453
let origin = root.join ( " origin" ) ;
10454
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
10455
let main = root.join ( " main" ) ;
10456
let mut repo = Repo:: init( & main) .unwrap ( ) ;
10457
repo.config
10458
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
10459
.unwrap ( ) ;
10460
repo.config.set ( " push.batch-bytes" , " 50000" ) .unwrap ( ) ;
10461
repo.save_config ( ) .unwrap ( ) ;
10462
let mut x: u64 = 0x9e3779b97f4a7c15 ;
10463
let mut noise = | n : usize | -> Vec< u8 > {
10464
( 0 .. n)
10465
.map ( | _| {
10466
x ^ = x < < 13 ;
10467
x ^ = x > > 7 ;
10468
x ^ = x < < 17 ;
10469
( x > > 24 ) as u8
10470
} )
10471
.collect ( )
10472
} ;
10473
let files: Vec< Vec< u8 > > = ( 0 .. 4 ) .map ( | _| noise ( 40_000 ) ) .collect ( ) ;
10474
for ( i, f) in files.iter ( ) .enumerate ( ) {
10475
write ( & main, & format! ( " bin{i} .dat" ) , f) ;
10476
}
10477
repo.commit ( " four blobs" , & [ ] ) .unwrap ( ) ;
10478
let mut parts = Vec :: new( ) ;
10479
let results = repo
10480
.push_bookmark_with ( " origin" , " main" , false , & mut | m| parts.push ( m.to_string ( ) ) )
10481
.unwrap ( ) ;
10482
assert! (
10483
results.iter ( ) .any ( | r | r.ok & & r.name = = " main" ) ,
10484
" {results:?}"
10485
) ;
10486
assert! (
10487
parts.len ( ) > = 3 & & parts[ 0 ] .starts_with ( " objects 1 of" ) ,
10488
" {parts:?}"
10489
) ;
10490
let back = Repo:: clone( & origin.to_string_lossy ( ) , & root.join ( " back" ) ) .unwrap ( ) ;
10491
for ( i, f) in files.iter ( ) .enumerate ( ) {
10492
assert_eq! ( & fs:: read( root.join ( format! ( " back/bin{i} .dat" ) ) ) .unwrap ( ) , f) ;
10493
}
10494
drop ( back) ;
10495
}
10496
570e3531 Andy McCall 11 days ago ↶
10497
10498
10499
10500
10501
# [ test ]
10502
fn a_replacement_does_not_send_the_old_content_again ( ) {
10503
hermetic ( ) ;
10504
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10505
let root = dir.path ( ) ;
10506
let origin = root.join ( " origin" ) ;
10507
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
10508
let main = root.join ( " main" ) ;
10509
let mut repo = Repo:: init( & main) .unwrap ( ) ;
10510
repo.config
10511
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
10512
.unwrap ( ) ;
10513
repo.save_config ( ) .unwrap ( ) ;
10514
let blobs = | b : & Bundle| {
10515
b.objects
10516
.iter ( )
10517
.filter ( | ( ty , _) | * ty = = ObjectType:: Blob)
10518
.count ( )
10519
} ;
10520
write ( & main, " bin.dat" , b " \0 \x01 first binary" ) ;
10521
write ( & main, " gone.dat" , b " \0 \x02 to be removed" ) ;
10522
let added = repo.commit ( " add" , & [ ] ) .unwrap ( ) ;
10523
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10524
write ( & main, " bin.dat" , b " \0 \x01 second binary" ) ;
10525
fs:: remove_file( main.join ( " gone.dat" ) ) .unwrap ( ) ;
10526
let changed = repo.commit ( " replace and remove" , & [ ] ) .unwrap ( ) ;
10527
assert_eq! ( blobs ( & repo.bundle_for ( & [ changed] ) .unwrap ( ) ) , 1 ) ;
10528
assert_eq! ( blobs ( & repo.bundle_for ( & [ added, changed] ) .unwrap ( ) ) , 3 ) ;
10529
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10530
let origin = Repo:: open( & origin) .unwrap ( ) ;
10531
assert_eq! ( origin.fsck ( ) .unwrap ( ) .problems, vec! [ ] ) ;
10532
let back = Repo:: clone( & origin.root ( ) .to_string_lossy ( ) , & root.join ( " back" ) ) .unwrap ( ) ;
10533
assert_eq! ( back.fsck ( ) .unwrap ( ) .problems, vec! [ ] ) ;
10534
assert_eq! (
10535
fs:: read( root.join ( " back/bin.dat" ) ) .unwrap ( ) ,
10536
b " \0 \x01 second binary"
10537
) ;
10538
assert! ( ! root.join ( " back/gone.dat" ) .exists ( ) ) ;
10539
}
10540
d6e0d5b8 Andy McCall 25 days ago ↶
10541
10542
10543
10544
# [ test ]
10545
fn git_import_claims_your_own_commits ( ) {
10546
hermetic ( ) ;
10547
if std:: process:: Command:: new( " git" )
10548
.arg ( " --version" )
10549
.output ( )
10550
.is_err ( )
10551
{
10552
eprintln! ( " git not installed; skipping" ) ;
10553
return ;
10554
}
10555
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10556
let root = dir.path ( ) ;
10557
let g = root.join ( " src.git" ) ;
10558
fs:: create_dir_all( & g) .unwrap ( ) ;
10559
let git = | who : & str , args : & [& str ]| {
10560
let ( name, email) = who.split_once ( ' ' ) .unwrap ( ) ;
10561
let out = std:: process:: Command:: new( " git" )
10562
.arg ( " -C" )
10563
.arg ( & g)
10564
.args ( [
10565
" -c" ,
10566
& format! ( " user.name={name} " ) ,
10567
" -c" ,
10568
& format! ( " user.email={email} " ) ,
10569
" -c" ,
10570
" commit.gpgsign=false" ,
10571
] )
10572
.args ( args)
10573
.output ( )
10574
.unwrap ( ) ;
10575
assert! (
10576
out.status.success ( ) ,
10577
" git {args:?}: {}" ,
10578
String :: from_utf8_lossy( & out.stderr)
10579
) ;
10580
} ;
10581
git ( " Grace [email protected] " , & [ " init" , " -q" , " -b" , " main" ] ) ;
10582
write ( & g, " a.txt" , b " a\n " ) ;
10583
git ( " Grace [email protected] " , & [ " add" , " ." ] ) ;
10584
git ( " Grace [email protected] " , & [ " commit" , " -qm" , " mine" ] ) ;
10585
write ( & g, " b.txt" , b " b\n " ) ;
10586
git ( " Ada [email protected] " , & [ " add" , " ." ] ) ;
10587
git ( " Ada [email protected] " , & [ " commit" , " -qm" , " someone else's" ] ) ;
10588
10589
let mut repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
10590
let ( key, _ ) = repo
10591
.create_identity ( " Grace" , " [email protected] " , IdentityScope:: Repo)
10592
.unwrap ( ) ;
10593
repo.config.set ( " git.claim" , " true" ) .unwrap ( ) ;
10594
repo.save_config ( ) .unwrap ( ) ;
10595
let repo = Repo:: open( & root.join ( " o" ) ) .unwrap ( ) ;
10596
repo.import_git ( & g, " main" ) .unwrap ( ) ;
10597
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
10598
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
10599
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
10600
let by_message: std:: collections:: HashMap< String, KeyId> = repo
10601
.log_state ( & heads)
10602
.unwrap ( )
10603
.into_iter ( )
10604
.map ( | e | {
10605
(
10606
e.change.message.lines ( ) .next ( ) .unwrap_or ( " " ) .to_string ( ) ,
10607
e.change.author,
10608
)
10609
} )
10610
.collect ( ) ;
10611
assert_eq! ( by_message[ " mine" ] , key, " her own commit, any case of email" ) ;
10612
assert_eq! ( by_message[ " someone else's" ] , ObjectRef:: ZERO , " not hers" ) ;
10613
}
34fad4a9 Andy McCall 25 days ago ↶
10614
10615
10616
10617
# [ test ]
10618
fn removing_a_remote_leaves_nothing_of_it ( ) {
10619
hermetic ( ) ;
10620
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10621
let origin = dir.path ( ) .join ( " origin" ) ;
10622
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
10623
let main = dir.path ( ) .join ( " main" ) ;
10624
let mut repo = Repo:: init( & main) .unwrap ( ) ;
10625
repo.config
10626
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
10627
.unwrap ( ) ;
10628
repo.config
10629
.set ( " remote.origin.token" , " secret-ish" )
10630
.unwrap ( ) ;
10631
repo.config
10632
.set ( " remote.keep.url" , & origin.to_string_lossy ( ) )
10633
.unwrap ( ) ;
10634
repo.save_config ( ) .unwrap ( ) ;
10635
write ( & main, " a.txt" , b " a\n " ) ;
10636
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10637
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10638
assert! (
10639
repo.view ( ) .unwrap ( ) .remotes.contains_key ( " origin" ) ,
10640
" a tracking bookmark"
10641
) ;
10642
10643
repo.remove_remote ( " origin" ) .unwrap ( ) ;
10644
let again = Repo:: open( & main) .unwrap ( ) ;
10645
assert! (
10646
! again
10647
.config
10648
.repo_layer ( )
10649
.keys ( )
10650
.any ( | k | k.starts_with ( " remote.origin." ) ) ,
10651
" {:?}" ,
10652
again.config.repo_layer ( )
10653
) ;
10654
assert! ( ! again.config.remotes.contains_key ( " origin" ) ) ;
10655
assert! (
10656
! again.view ( ) .unwrap ( ) .remotes.contains_key ( " origin" ) ,
10657
" its tracking bookmarks go"
10658
) ;
10659
assert! (
10660
again.config.remotes.contains_key ( " keep" ) ,
10661
" another remote stays"
10662
) ;
10663
let mut again = again;
10664
assert! (
10665
again.remove_remote ( " origin" ) .is_err ( ) ,
10666
" a second removal is refused"
10667
) ;
10668
}
9d580f21 Andy McCall 25 days ago ↶
10669
10670
10671
10672
10673
# [ test ]
10674
fn gc_keeps_the_blobs_a_host_names ( ) {
10675
hermetic ( ) ;
10676
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10677
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
10678
write ( dir.path ( ) , " a.txt" , b " a\n " ) ;
10679
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10680
10681
let body: Vec< u8 > = ( 0 .. 400_000 u32 )
10682
.flat_map ( | i | ( i.wrapping_mul ( 2654435761 ) ) .to_le_bytes ( ) )
10683
.collect ( ) ;
10684
let kept = repo.store_blob ( & body) .unwrap ( ) ;
10685
let lost = repo.store_blob ( b " an artifact nobody names" ) .unwrap ( ) ;
10686
repo.gc_keeping ( true , & [ kept] , & mut | _ | { } ) .unwrap ( ) ;
10687
assert_eq! ( repo.read_blob ( & kept) .unwrap ( ) , body, " named: kept whole" ) ;
10688
assert! ( ! repo.store ( ) .has ( & lost) , " not named: pruned" ) ;
10689
repo.gc ( true ) .unwrap ( ) ;
10690
assert! ( ! repo.store ( ) .has ( & kept) , " a plain gc prunes it" ) ;
10691
}
530a0fc6 Andy McCall 24 days ago ↶
10692
10693
10694
10695
10696
# [ test ]
10697
fn undo_is_scoped_to_its_workspace ( ) {
10698
hermetic ( ) ;
10699
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10700
let root = dir.path ( ) .join ( " main" ) ;
10701
let a = Repo:: init( & root) .unwrap ( ) ;
10702
write ( & root, " base.txt" , b " base\n " ) ;
10703
let base = a.commit ( " base" , & [ ] ) .unwrap ( ) ;
10704
a.bookmark_create ( " feature" , None ) .unwrap ( ) ;
10705
let ws_dir = dir.path ( ) .join ( " ws" ) ;
10706
let heads = a.resolve_heads ( " feature" ) .unwrap ( ) ;
10707
let b = a
10708
.workspace_add_on ( & ws_dir, Some ( & heads) , Some ( " feature" ) )
10709
.unwrap ( ) ;
10710
10711
10712
write ( & root, " a.txt" , b " a\n " ) ;
10713
let ca = a.commit ( " from a" , & [ ] ) .unwrap ( ) ;
10714
write ( & ws_dir, " b.txt" , b " b\n " ) ;
10715
let cb = b.commit ( " from b" , & [ ] ) .unwrap ( ) ;
10716
10717
10718
let u = a.undo ( ) .unwrap ( ) ;
10719
assert_eq! ( u.command, " oxy commit -m \" from a\" " ) ;
10720
assert_eq! ( u.others_kept, 1 , " B's newer commit is left" ) ;
10721
assert! ( ! u.changes.is_empty ( ) ) ;
10722
let view = a.view ( ) .unwrap ( ) ;
10723
10724
assert_eq! ( a.heads_of ( & view, " main" ) .unwrap ( ) , vec! [ base] ) ;
10725
assert! (
10726
a.heads_of ( & view, " feature" ) .unwrap ( ) .contains ( & cb) ,
10727
" B's commit stands"
10728
) ;
10729
assert! (
10730
a.store ( ) .get_change ( & ca) .is_ok ( ) ,
10731
" the change itself is still in the store"
10732
) ;
10733
10734
assert! ( ! root.join ( " a.txt" ) .exists ( ) ) ;
10735
assert! ( a.snapshot ( " oxy status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
10736
assert! ( ws_dir.join ( " b.txt" ) .exists ( ) ) ;
10737
10738
10739
let u = b.undo ( ) .unwrap ( ) ;
10740
assert_eq! ( u.command, " oxy commit -m \" from b\" " ) ;
10741
assert! ( ! b
10742
.heads_of ( & b.view ( ) .unwrap ( ) , " feature" )
10743
.unwrap ( )
10744
.contains ( & cb) ) ;
10745
assert_eq! (
10746
a.heads_of ( & a.view ( ) .unwrap ( ) , " main" ) .unwrap ( ) ,
10747
vec! [ base] ,
10748
" A's undo stands"
10749
) ;
10750
10751
10752
10753
let e = a.undo ( ) .unwrap_err ( ) .to_string ( ) ;
10754
assert! ( e.contains ( " the workspace at" ) & & e.contains ( " ws" ) , " {e}" ) ;
10755
}
10756
10757
10758
10759
# [ test ]
10760
fn undo_on_a_shared_bookmark_keeps_the_other_workspaces_head ( ) {
10761
hermetic ( ) ;
10762
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10763
let root = dir.path ( ) .join ( " main" ) ;
10764
let a = Repo:: init( & root) .unwrap ( ) ;
10765
write ( & root, " base.txt" , b " base\n " ) ;
10766
a.commit ( " base" , & [ ] ) .unwrap ( ) ;
10767
let ws_dir = dir.path ( ) .join ( " ws" ) ;
10768
let b = a.workspace_add ( & ws_dir, None ) .unwrap ( ) ;
10769
write ( & root, " a.txt" , b " a\n " ) ;
10770
let ca = a.commit ( " from a" , & [ ] ) .unwrap ( ) ;
10771
write ( & ws_dir, " b.txt" , b " b\n " ) ;
10772
let cb = b.commit ( " from b" , & [ ] ) .unwrap ( ) ;
10773
let u = a.undo ( ) .unwrap ( ) ;
10774
assert_eq! ( u.command, " oxy commit -m \" from a\" " ) ;
10775
let heads = a.heads_of ( & a.view ( ) .unwrap ( ) , " main" ) .unwrap ( ) ;
10776
assert! ( heads.contains ( & cb) & & ! heads.contains ( & ca) , " {heads:?}" ) ;
10777
}
10778
10779
10780
10781
# [ test ]
10782
fn undo_refuses_to_take_back_another_workspaces_work ( ) {
10783
hermetic ( ) ;
10784
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10785
let root = dir.path ( ) .join ( " main" ) ;
10786
let a = Repo:: init( & root) .unwrap ( ) ;
10787
write ( & root, " base.txt" , b " base\n " ) ;
10788
a.commit ( " base" , & [ ] ) .unwrap ( ) ;
10789
let ws_dir = dir.path ( ) .join ( " ws" ) ;
10790
let b = a.workspace_add ( & ws_dir, None ) .unwrap ( ) ;
10791
10792
write ( & root, " a.txt" , b " a\n " ) ;
10793
a.commit ( " from a" , & [ ] ) .unwrap ( ) ;
767ac6cd Andy McCall 17 days ago
10794
b.refresh ( ) .unwrap ( ) ;
530a0fc6 Andy McCall 24 days ago ↶
10795
let heads = b.resolve_heads ( " main" ) .unwrap ( ) ;
10796
b.checkout ( & heads, false , " oxy checkout" ) .unwrap ( ) ;
10797
write ( & ws_dir, " a.txt" , b " a, then b\n " ) ;
10798
b.commit ( " from b" , & [ ] ) .unwrap ( ) ;
10799
let e = a.undo ( ) .unwrap_err ( ) .to_string ( ) ;
10800
assert! (
10801
e.contains ( " bookmark main" ) & & e.contains ( " builds on" ) ,
10802
" {e}"
10803
) ;
10804
assert! ( e.contains ( " oxy op restore" ) , " {e}" ) ;
10805
}
10806
10807
10808
10809
# [ test ]
10810
fn undo_says_when_the_commit_was_pushed ( ) {
10811
hermetic ( ) ;
10812
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10813
let root = dir.path ( ) ;
10814
Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
10815
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
10816
let repo = Repo:: clone( & url, & root.join ( " work" ) )
10817
.unwrap ( )
10818
.with_home ( root.join ( " home" ) ) ;
10819
write ( & root.join ( " work" ) , " a.txt" , b " a\n " ) ;
10820
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10821
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10822
10823
let u = repo.undo ( ) .unwrap ( ) ;
10824
assert_eq! ( u.command, " oxy commit -m \" a\" " ) ;
10825
assert_eq! ( u.pushed, vec! [ ( " main" .to_string ( ) , " origin" .to_string ( ) ) ] ) ;
10826
}
b689779e Andy McCall 24 days ago ↶
10827
10828
10829
10830
10831
# [ test ]
10832
fn large_new_files_are_left_out_of_snapshots ( ) {
10833
hermetic ( ) ;
10834
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10835
let root = dir.path ( ) ;
10836
let mut repo = Repo:: init( root) .unwrap ( ) ;
10837
repo.config.max_new_file = 1024 ;
10838
write ( root, " small.txt" , b " small\n " ) ;
10839
write ( root, " big.bin" , & vec! [ 0 u8 ; 4096 ] ) ;
10840
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10841
let paths: Vec< _ > = s.statuses.iter ( ) .map ( | p | p.path.as_str ( ) ) .collect ( ) ;
10842
assert_eq! ( paths, [ " small.txt" ] ) ;
10843
assert_eq! ( repo.skipped_large ( ) , vec! [ ( " big.bin" .to_string ( ) , 4096 ) ] ) ;
10844
repo.commit ( " small" , & [ ] ) .unwrap ( ) ;
10845
10846
10847
write ( root, " small.txt" , & vec! [ b ' x' ; 4096 ] ) ;
10848
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10849
assert! ( s.statuses.iter ( ) .any ( | p | p.path = = " small.txt" ) ) ;
10850
10851
repo.config.max_new_file = 0 ;
10852
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10853
assert! ( s.statuses.iter ( ) .any ( | p | p.path = = " big.bin" ) ) ;
10854
repo.config.max_new_file = 1024 ;
10855
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10856
assert! (
10857
s.statuses.iter ( ) .any ( | p | p.path = = " big.bin" ) ,
10858
" already in the working copy"
10859
) ;
10860
assert! ( repo.skipped_large ( ) .is_empty ( ) ) ;
10861
}
10862
10863
10864
10865
# [ test ]
10866
fn gc_prunes_what_only_expired_snapshots_held ( ) {
10867
hermetic ( ) ;
10868
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10869
let root = dir.path ( ) ;
10870
let mut repo = Repo:: init( root) .unwrap ( ) ;
10871
repo.config.gc_grace_days = 0 ;
10872
write ( root, " a.txt" , b " a\n " ) ;
10873
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10874
let image: Vec< u8 > = ( 0 .. 200_000 u32 ) .map ( | i | ( i % 7 ) as u8 ) .collect ( ) ;
10875
write ( root, " image.tar" , & image) ;
10876
repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10877
std:: fs:: remove_file( root.join ( " image.tar" ) ) .unwrap ( ) ;
10878
repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10879
let blob = Repo:: blob_ref( & image) ;
10880
assert! ( repo.store ( ) .has ( & blob) ) ;
10881
repo.gc ( true ) .unwrap ( ) ;
10882
assert! ( repo.store ( ) .has ( & blob) , " a recent snapshot keeps it" ) ;
10883
repo.config.snapshot_retention_days = 0 ;
10884
repo.gc ( true ) .unwrap ( ) ;
10885
assert! ( ! repo.store ( ) .has ( & blob) , " an expired snapshot does not" ) ;
10886
assert_eq! (
10887
repo.file_at ( " a.txt" , None ) .unwrap ( ) ,
10888
b " a\n " ,
10889
" committed files stay"
10890
) ;
fe9586d9 Andy McCall 24 days ago ↶
10891
}
10892
10893
10894
10895
10896
# [ test ]
10897
fn carrying_work_the_target_already_has_is_no_conflict ( ) {
10898
hermetic ( ) ;
10899
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10900
let root = dir.path ( ) ;
10901
let mut repo = Repo:: init( root) .unwrap ( ) ;
10902
write ( root, " a.txt" , b " one\n " ) ;
10903
write ( root, " b.txt" , b " b\n " ) ;
10904
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
10905
repo.bookmark_create ( " landed" , None ) .unwrap ( ) ;
10906
10907
repo.bookmark_switch ( " landed" , false ) .unwrap ( ) ;
10908
write ( root, " a.txt" , b " two\n " ) ;
10909
repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
10910
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
10911
assert_eq! ( std:: fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n " ) ;
10912
10913
write ( root, " a.txt" , b " two\n " ) ;
10914
write ( root, " b.txt" , b " b, edited\n " ) ;
10915
let ( _ , carried) = repo.bookmark_switch_carry ( " landed" ) .unwrap ( ) ;
10916
assert_eq! (
10917
carried,
10918
vec! [ " b.txt" .to_string ( ) ] ,
10919
" only the work landed lacks is carried"
10920
) ;
10921
assert_eq! ( std:: fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " two\n " ) ;
10922
assert_eq! ( std:: fs:: read( root.join ( " b.txt" ) ) .unwrap ( ) , b " b, edited\n " ) ;
10923
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10924
let paths: Vec< _ > = s.statuses.iter ( ) .map ( | p | p.path.as_str ( ) ) .collect ( ) ;
10925
assert_eq! ( paths, [ " b.txt" ] ) ;
10926
10927
10928
repo.bookmark_switch ( " main" , true ) .unwrap ( ) ;
10929
write ( root, " a.txt" , b " three\n " ) ;
10930
let e = repo
10931
.bookmark_switch_carry ( " landed" )
10932
.unwrap_err ( )
10933
.to_string ( ) ;
10934
assert! ( e.contains ( " a.txt" ) & & e.contains ( " --discard" ) , " {e}" ) ;
b689779e Andy McCall 24 days ago ↶
10935
}
aa4939e0 Andy McCall 24 days ago ↶
10936
10937
10938
10939
# [ test ]
10940
fn a_full_hash_resolves_without_its_hash_sign ( ) {
10941
hermetic ( ) ;
10942
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10943
let root = dir.path ( ) ;
10944
let repo = Repo:: init( root) .unwrap ( ) ;
10945
write ( root, " a.txt" , b " a\n " ) ;
10946
let h = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10947
assert_eq! ( repo.resolve_change ( & h.to_hex ( ) ) .unwrap ( ) , h) ;
10948
assert_eq! ( repo.resolve_change ( & format! ( " #{} " , h.to_hex ( ) ) ) .unwrap ( ) , h) ;
10949
assert! ( repo
10950
.resolve_change ( & " 0" .repeat ( 64 ) )
10951
.unwrap_err ( )
10952
.to_string ( )
10953
.contains ( " no change matches" ) ) ;
10954
}
f2f91ceb Andy McCall 23 days ago ↶
10955
10956
# [ test ]
10957
fn a_pin_change_moves_only_the_pin ( ) {
10958
hermetic ( ) ;
10959
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10960
let root = dir.path ( ) ;
10961
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
10962
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
10963
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
10964
.unwrap ( )
10965
.with_home ( root.join ( " home" ) ) ;
10966
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
10967
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
10968
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10969
let _app_origin = Repo:: init( & root.join ( " app-origin" ) ) .unwrap ( ) ;
10970
let app_dir = root.join ( " app" ) ;
10971
let app = Repo:: clone( & root.join ( " app-origin" ) .to_string_lossy ( ) , & app_dir)
10972
.unwrap ( )
10973
.with_home ( root.join ( " home" ) ) ;
10974
write ( & app_dir, " main.txt" , b " app\n " ) ;
10975
let pin1 = app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
10976
let a1 = app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
10977
10978
write ( & root.join ( " lib" ) , " more.txt" , b " v2\n " ) ;
10979
let l2 = lib.commit ( " lib v2" , & [ ] ) .unwrap ( ) ;
10980
let crate :: subrepo:: Pin:: Oxy { repo, bookmark, .. } = pin1 else {
10981
panic! ( " an Oxygen pin" )
10982
} ;
10983
let pin2 = crate :: subrepo:: Pin:: Oxy {
10984
repo,
10985
heads: vec! [ l2] ,
10986
bookmark,
10987
} ;
10988
let author = app.store ( ) .get_change ( & a1) .unwrap ( ) .author;
10989
let change = app
10990
.build_pin_change ( & [ a1] , " libs/lib" , & pin2, " Move lib to v2" , author)
10991
.unwrap ( ) ;
10992
let mut state = app.load_state ( & [ a1] ) .unwrap ( ) ;
10993
state.add ( change) .unwrap ( ) ;
10994
let files = state.materialize ( ) .files;
10995
assert_eq! ( files[ " libs/lib" ] .kind, NodeKind:: Subrepo) ;
10996
let FileContent:: Opaque { blobs } = & files[ " libs/lib" ] .content else {
10997
panic! ( " a pin is opaque" )
10998
} ;
10999
let read = crate :: subrepo:: Pin:: decode( & app.read_blob ( & blobs[ 0 ] ) .unwrap ( ) ) .unwrap ( ) ;
11000
assert_eq! ( read, pin2, " the new pin" ) ;
11001
assert! (
11002
matches! ( & files[ " main.txt" ] .content, FileContent:: Text { bytes, .. } if bytes.as_slice ( ) = = b " app\n " ) ,
11003
" nothing else changes"
11004
) ;
11005
11006
let e = app.build_pin_change ( & [ a1] , " main.txt" , & pin2, " x" , author) ;
11007
assert! ( format! ( " {:?} " , e.unwrap_err ( ) ) .contains ( " not a subrepo" ) ) ;
11008
let e = app.build_pin_change ( & [ a1] , " libs/none" , & pin2, " x" , author) ;
11009
assert! ( format! ( " {:?} " , e.unwrap_err ( ) ) .contains ( " does not exist" ) ) ;
11010
}
767ac6cd Andy McCall 17 days ago
11011
b5e98094 Andy McCall 9 days ago ↶
11012
11013
11014
11015
# [ test ]
11016
fn a_subrepo_at_its_pinned_state_on_another_bookmark_is_unmodified ( ) {
11017
hermetic ( ) ;
11018
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
11019
let root = dir.path ( ) ;
11020
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
11021
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
11022
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
11023
.unwrap ( )
11024
.with_home ( root.join ( " home" ) ) ;
11025
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
11026
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
11027
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
11028
let _app_origin = Repo:: init( & root.join ( " app-origin" ) ) .unwrap ( ) ;
11029
let app_dir = root.join ( " app" ) ;
11030
let mut app = Repo:: clone( & root.join ( " app-origin" ) .to_string_lossy ( ) , & app_dir)
11031
.unwrap ( )
11032
.with_home ( root.join ( " home" ) ) ;
11033
write ( & app_dir, " main.txt" , b " app\n " ) ;
11034
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
11035
let a1 = app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
11036
11037
11038
let mut nested = Repo:: open( & app_dir.join ( " libs/lib" ) )
11039
.unwrap ( )
11040
.with_home ( root.join ( " home" ) ) ;
11041
let heads = nested.bookmark_heads ( ) .unwrap ( ) ;
11042
nested.bookmark_create ( " feature" , Some ( & heads) ) .unwrap ( ) ;
11043
nested.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
11044
let snap = app.snapshot ( " status" ) .unwrap ( ) ;
11045
assert! (
11046
! snap.changed,
11047
" only the bookmark differs: {:?}" ,
11048
snap.statuses
11049
) ;
11050
app.bookmark_create ( " other" , Some ( & [ a1] ) ) .unwrap ( ) ;
11051
app.bookmark_switch ( " other" , false ) .unwrap ( ) ;
11052
11053
11054
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) )
11055
.unwrap ( )
11056
.with_home ( root.join ( " home" ) ) ;
11057
write ( & app_dir.join ( " libs/lib" ) , " lib.txt" , b " v2\n " ) ;
11058
nested.commit ( " lib v2" , & [ ] ) .unwrap ( ) ;
11059
let snap = app.snapshot ( " status" ) .unwrap ( ) ;
11060
assert_eq! (
11061
snap.statuses
11062
.iter ( )
11063
.map ( | s | ( s.path.as_str ( ) , s.status) )
11064
.collect:: < Vec< _ > > ( ) ,
11065
vec! [ ( " libs/lib" , ChangeStatus:: Modified) ]
11066
) ;
11067
}
11068
767ac6cd Andy McCall 17 days ago
11069
11070
11071
11072
11073
11074
# [ test ]
11075
fn one_handle_keeps_one_head_until_it_refreshes ( ) {
11076
hermetic ( ) ;
11077
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
11078
let root = dir.path ( ) ;
11079
let writer = Repo:: init( root) .unwrap ( ) ;
11080
write ( root, " a.txt" , b " one\n " ) ;
11081
let h1 = writer.commit ( " one" , & [ ] ) .unwrap ( ) ;
11082
let reader = Repo:: open( root) .unwrap ( ) ;
11083
let before = reader.view ( ) .unwrap ( ) ;
11084
assert_eq! ( reader.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
11085
write ( root, " a.txt" , b " two\n " ) ;
11086
let h2 = writer.commit ( " two" , & [ ] ) .unwrap ( ) ;
11087
assert_eq! (
11088
writer.bookmark_heads ( ) .unwrap ( ) ,
11089
vec! [ h2] ,
11090
" its own operation"
11091
) ;
11092
assert_eq! ( reader.view ( ) .unwrap ( ) , before, " the head it read first" ) ;
11093
assert_eq! ( reader.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
11094
reader.refresh ( ) .unwrap ( ) ;
11095
assert_eq! ( reader.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
11096
11097
11098
let stale = Repo:: open( root) .unwrap ( ) ;
11099
assert_eq! ( stale.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
11100
write ( root, " a.txt" , b " three\n " ) ;
11101
let h3 = writer.commit ( " three" , & [ ] ) .unwrap ( ) ;
11102
stale.bookmark_create ( " feature" , Some ( & [ h1] ) ) .unwrap ( ) ;
11103
let view = stale.view ( ) .unwrap ( ) ;
11104
assert! ( view.bookmarks.contains_key ( " feature" ) ) ;
11105
assert_eq! (
11106
stale.bookmark_heads ( ) .unwrap ( ) ,
11107
vec! [ h3] ,
11108
" merged, not lost"
11109
) ;
11110
let fresh = Repo:: open( root) .unwrap ( ) ;
11111
assert_eq! ( fresh.view ( ) .unwrap ( ) , view) ;
11112
}
11113
11114
11115
# [ test ]
11116
fn over_a_store_one_handle_keeps_one_head ( ) {
11117
hermetic ( ) ;
11118
let backend = std:: sync:: Arc:: new( crate :: strata:: MemoryStore:: new( ) ) ;
11119
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = Some ( backend.clone ( ) ) ) ;
11120
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
11121
let a = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
11122
let ( h0, _ ) = a.head_op ( ) .unwrap ( ) ;
11123
let b = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
11124
assert_eq! ( b.head_op ( ) .unwrap ( ) .0 , h0) ;
11125
let state = a.store.put_state ( & StateObject:: new( vec! [ ] ) ) .unwrap ( ) ;
11126
let mut view = a.view ( ) .unwrap ( ) ;
11127
view.bookmarks.insert ( " x" .into ( ) , state) ;
11128
let r = a.record_op ( & [ h0] , " bookmark x" , view) .unwrap ( ) ;
11129
assert_eq! ( a.head_op ( ) .unwrap ( ) .0 , r) ;
11130
assert_eq! ( b.head_op ( ) .unwrap ( ) .0 , h0, " b keeps its head" ) ;
11131
b.refresh ( ) .unwrap ( ) ;
11132
assert_eq! ( b.head_op ( ) .unwrap ( ) .0 , r) ;
11133
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = None ) ;
11134
}
11135
11136
11137
11138
11139
# [ test ]
11140
fn gc_retires_and_a_later_gc_deletes_after_the_hour ( ) {
11141
hermetic ( ) ;
11142
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
11143
let root = dir.path ( ) ;
11144
let repo = Repo:: init( root) .unwrap ( ) ;
11145
let mut heads = Vec :: new( ) ;
11146
for i in 0 .. 4 {
11147
write ( root, & format! ( " f{i} .txt" ) , format! ( " {i} \n " ) .as_bytes ( ) ) ;
11148
heads.push ( repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ) ;
11149
}
11150
let reader = Repo:: open( root) .unwrap ( ) ;
11151
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 5 ) ;
11152
let r = repo.gc ( true ) .unwrap ( ) ;
11153
assert_eq! ( r.retired, crate :: store:: RetiredReport:: default( ) ) ;
11154
assert_eq! ( repo.store ( ) .loose_count ( ) .unwrap ( ) , 0 ) ;
11155
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 5 , " read on from what it had" ) ;
11156
11157
let r = repo.gc ( true ) .unwrap ( ) ;
11158
assert_eq! ( ( r.retired.packs, r.retired.loose) , ( 0 , 0 ) ) ;
11159
assert! ( r.retired.kept > 0 , " {r:?}" ) ;
11160
write ( root, " f9.txt" , b " 9\n " ) ;
11161
heads.push ( repo.commit ( " c9" , & [ ] ) .unwrap ( ) ) ;
11162
crate :: store:: reader_tests:: age_retired(
11163
repo.store ( ) ,
11164
crate :: store:: RETIRED_FOR + std:: time:: Duration:: from_secs( 60 ) ,
11165
) ;
11166
let r = repo.gc ( true ) .unwrap ( ) ;
11167
assert! ( r.retired.packs > = 1 & & r.retired.loose > 0 , " {r:?}" ) ;
11168
assert_eq! ( r.retired.kept, 0 , " {r:?}" ) ;
11169
let r = repo.gc ( true ) .unwrap ( ) ;
11170
assert_eq! ( ( r.retired.packs, r.retired.loose) , ( 0 , 0 ) ) ;
11171
assert! ( r.retired.kept > 0 , " what the last gc retired is young" ) ;
11172
11173
11174
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 5 ) ;
11175
for h in & heads[ .. 4 ] {
11176
reader.store ( ) .get_change ( h) .unwrap ( ) ;
11177
}
11178
reader.refresh ( ) .unwrap ( ) ;
11179
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 6 ) ;
11180
let fresh = Repo:: open( root) .unwrap ( ) ;
11181
assert_eq! (
11182
fresh.store ( ) .pack_count ( ) ,
11183
1 ,
11184
" retired packs are not loaded"
11185
) ;
11186
}
11187
11188
11189
11190
# [ test ]
11191
fn a_repository_opens_when_a_listed_index_is_gone ( ) {
11192
hermetic ( ) ;
11193
let backend = crate :: store:: reader_tests:: racing( true ) ;
11194
let dyn_backend: std:: sync:: Arc< crate:: store:: Backend> = backend.clone ( ) ;
11195
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = Some ( dyn_backend) ) ;
11196
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
11197
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
11198
write ( dir.path ( ) , " a.txt" , b " a\n " ) ;
11199
let h = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
11200
repo.gc ( true ) .unwrap ( ) ;
11201
let again = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
11202
assert_eq! ( again.store ( ) .pack_count ( ) , 1 ) ;
11203
assert_eq! ( again.bookmark_heads ( ) .unwrap ( ) , vec! [ h] ) ;
11204
11205
11206
use crate :: strata:: Store as _ ;
11207
let idx = backend
11208
.list ( " pack-" )
11209
.unwrap ( )
11210
.into_iter ( )
11211
.map ( | e | e.key )
11212
.find ( | k | k.ends_with ( " .idx" ) & & k ! = " pack-0000.idx" )
11213
.unwrap ( ) ;
11214
let stem = idx.trim_end_matches ( " .idx" ) ;
11215
for ext in [ " pack" , " idx" ] {
11216
let bytes = backend.get ( & format! ( " {stem} .{ext} " ) ) .unwrap ( ) ;
11217
backend
11218
.put_new ( & format! ( " pack-copy.{ext} " ) , & bytes)
11219
.unwrap ( ) ;
11220
}
11221
* backend.doomed.lock ( ) .unwrap ( ) = Some ( " pack-copy.idx" .into ( ) ) ;
11222
let again = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
11223
assert_eq! ( again.store ( ) .pack_count ( ) , 1 ) ;
11224
assert_eq! ( again.bookmark_heads ( ) .unwrap ( ) , vec! [ h] ) ;
11225
assert! ( ! backend.has ( " pack-copy.idx" ) .unwrap ( ) ) ;
11226
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = None ) ;
11227
}
2452b440 Andy McCall 9 days ago ↶
11228
11229
11230
11231
11232
11233
# [ test ]
11234
fn author_for_follows_the_key_the_hub_named ( ) {
11235
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
11236
let home = dir.path ( ) .join ( " home" ) ;
11237
fs:: create_dir_all( home.join ( " keys" ) ) .unwrap ( ) ;
11238
let user = home.join ( " config.toml" ) ;
11239
let repo_cfg = dir.path ( ) .join ( " config" ) ;
11240
let ( old, new, pin) = (
11241
KeyId:: from_hex( & " a" .repeat ( 64 ) ) .unwrap ( ) ,
11242
KeyId:: from_hex( & " b" .repeat ( 64 ) ) .unwrap ( ) ,
11243
KeyId:: from_hex( & " c" .repeat ( 64 ) ) .unwrap ( ) ,
11244
) ;
11245
fs:: write( & user, format! ( " author.key = \" {} \" \n " , old.to_hex ( ) ) ) .unwrap ( ) ;
11246
fs:: write(
11247
home.join ( " keys" ) .join ( super :: config:: AUTHOR_FOR_FILE ) ,
11248
format! ( " {} \n " , new.to_hex ( ) ) ,
11249
)
11250
.unwrap ( ) ;
11251
fs:: write( & repo_cfg, " " ) .unwrap ( ) ;
11252
assert_eq! (
11253
Config:: load( Some ( & user) , & repo_cfg) .unwrap ( ) .author_key,
11254
Some ( old) ,
11255
" no author.for: the note is ignored"
11256
) ;
11257
fs:: write(
11258
& user,
11259
format! (
11260
" author.key = \" {} \" \n author.for = \" andymccall\" \n " ,
11261
old.to_hex ( )
11262
) ,
11263
)
11264
.unwrap ( ) ;
11265
assert_eq! (
11266
Config:: load( Some ( & user) , & repo_cfg) .unwrap ( ) .author_key,
11267
Some ( new)
11268
) ;
11269
fs:: write( & repo_cfg, format! ( " author.key = \" {} \" \n " , pin.to_hex ( ) ) ) .unwrap ( ) ;
11270
assert_eq! (
11271
Config:: load( Some ( & user) , & repo_cfg) .unwrap ( ) .author_key,
11272
Some ( pin) ,
11273
" a repository's pin wins"
11274
) ;
11275
}
6f4226bf Andy McCall 4 days ago ↶
11276
11277
# [ test ]
11278
fn git_export_graph_is_deterministic_and_merges_heads ( ) {
11279
hermetic ( ) ;
11280
if std:: process:: Command:: new( " git" )
11281
.arg ( " --version" )
11282
.output ( )
11283
.is_err ( )
11284
{
11285
eprintln! ( " git not installed; skipping" ) ;
11286
return ;
11287
}
11288
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
11289
let root = dir.path ( ) ;
11290
let mut repo = Repo:: init( & root.join ( " r" ) )
11291
.unwrap ( )
11292
.with_home ( root.join ( " home" ) ) ;
11293
repo.create_identity ( " Ada Lovelace" , " [email protected] " , IdentityScope:: User)
11294
.unwrap ( ) ;
11295
let r = root.join ( " r" ) ;
11296
write ( & r, " README.md" , b " hello\n " ) ;
11297
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
11298
write ( & r, " README.md" , b " hello\n world\n " ) ;
11299
write ( & r, " src/main.rs" , b " fn main() {}\n " ) ;
11300
let h2 = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
11301
repo.tag_create ( " v1" , & [ h2] , " the first release" ) .unwrap ( ) ;
11302
11303
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
11304
write ( & r, " other.txt" , b " o\n " ) ;
11305
let h3 = repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
11306
repo.bookmark_create ( " gone" , Some ( & [ h1] ) ) .unwrap ( ) ;
11307
11308
let git = | git_dir : & Path, args : & [& str ]| -> String {
11309
let out = std:: process:: Command:: new( " git" )
11310
.arg ( " -C" )
11311
.arg ( git_dir)
11312
.args ( args)
11313
.output ( )
11314
.unwrap ( ) ;
11315
assert! (
11316
out.status.success ( ) ,
11317
" git {args:?}: {}" ,
11318
String :: from_utf8_lossy( & out.stderr)
11319
) ;
11320
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
11321
} ;
11322
let a = root.join ( " a.git" ) ;
11323
let rep = repo.git_export_graph ( & a) .unwrap ( ) ;
11324
assert_eq! ( ( rep.commits, rep.tags) , ( 3 , 1 ) , " {rep:?}" ) ;
11325
assert_eq! ( rep.branches, 2 , " main and gone: {rep:?}" ) ;
11326
11327
11328
11329
let by_hash = | hash : ChangeHash| {
11330
git (
11331
& a,
11332
& [
11333
" log" ,
11334
" --all" ,
11335
" --format=%H" ,
11336
& format! ( " --grep=Oxy-Hash: {} " , hash.to_hex ( ) ) ,
11337
] ,
11338
)
11339
} ;
11340
let ( c1, c2, c3) = ( by_hash ( h1) , by_hash ( h2) , by_hash ( h3) ) ;
11341
for ( h, c) in [ ( h1, & c1) , ( h2, & c2) , ( h3, & c3) ] {
11342
let mut want: Vec< String> = repo
11343
.store ( )
11344
.get_change_meta ( & h)
11345
.unwrap ( )
11346
.deps
11347
.iter ( )
11348
.map ( | d | by_hash ( * d) )
11349
.collect ( ) ;
11350
want.sort ( ) ;
11351
let mut have: Vec< String> = git ( & a, & [ " rev-list" , " --parents" , " -1" , c] )
11352
.split ( ' ' )
11353
.skip ( 1 )
11354
.map ( String :: from)
11355
.collect ( ) ;
11356
have.sort ( ) ;
11357
assert_eq! ( have, want, " {h:?}'s commit's parents" ) ;
11358
}
11359
assert! (
11360
! repo.store ( ) .get_change_meta ( & h2) .unwrap ( ) .deps.is_empty ( ) ,
11361
" second edits first's README"
11362
) ;
11363
assert_eq! (
11364
git ( & a, & [ " show" , & format! ( " {c2} :src/main.rs" ) ] ) ,
11365
" fn main() {}"
11366
) ;
11367
assert! (
11368
git ( & a, & [ " ls-tree" , " --name-only" , & c3] )
11369
.lines ( )
11370
.all ( | l | l ! = " src" ) ,
11371
" a change's tree is its own closure, not the bookmark's"
11372
) ;
11373
assert_eq! (
11374
git ( & a, & [ " log" , " -1" , " --format=%an <%ae>" , & c1] ) ,
11375
" Ada Lovelace <[email protected] >"
11376
) ;
11377
11378
11379
let main = git ( & a, & [ " rev-parse" , " main" ] ) ;
11380
let mut parents: Vec< String> = git ( & a, & [ " rev-list" , " --parents" , " -1" , " main" ] )
11381
.split ( ' ' )
11382
.skip ( 1 )
11383
.map ( String :: from)
11384
.collect ( ) ;
11385
parents.sort ( ) ;
11386
let mut want = vec! [ c2.clone ( ) , c3.clone ( ) ] ;
11387
want.sort ( ) ;
11388
assert_eq! ( parents, want, " the merge's parents are the heads" ) ;
11389
assert_eq! (
11390
git ( & a, & [ " ls-tree" , " --name-only" , " main" ] ) ,
11391
" README.md\n other.txt\n src"
11392
) ;
11393
assert! ( git ( & a, & [ " log" , " -1" , " --format=%B" , " main" ] ) .contains ( " Merge of 2 heads" ) ) ;
11394
assert_eq! (
11395
git ( & a, & [ " rev-parse" , " v1^{commit}" ] ) ,
11396
c2,
11397
" the tag is its head's commit"
11398
) ;
11399
assert_eq! ( git ( & a, & [ " symbolic-ref" , " HEAD" ] ) , " refs/heads/main" ) ;
11400
11401
11402
repo.bookmark_delete ( " gone" ) .unwrap ( ) ;
11403
let rep = repo.git_export_graph ( & a) .unwrap ( ) ;
11404
assert_eq! ( rep.commits, 0 , " {rep:?}" ) ;
11405
assert! (
11406
git ( & a, & [ " for-each-ref" , " --format=%(refname)" , " refs/heads" ] )
11407
.lines ( )
11408
.all ( | r | r ! = " refs/heads/gone" )
11409
) ;
11410
assert_eq! (
11411
git ( & a, & [ " rev-parse" , " main" ] ) ,
11412
main,
11413
" the merge is made once"
11414
) ;
11415
11416
11417
let other = Repo:: clone( r.to_str ( ) .unwrap ( ) , & root.join ( " clone" ) )
11418
.unwrap ( )
11419
.with_home ( root.join ( " home" ) ) ;
11420
let b = root.join ( " b.git" ) ;
11421
other.git_export_graph ( & b) .unwrap ( ) ;
11422
assert_eq! (
11423
git ( & b, & [ " rev-parse" , " main" ] ) ,
11424
main,
11425
" deterministic across repositories"
11426
) ;
11427
assert_eq! ( git ( & b, & [ " rev-parse" , " v1^{commit}" ] ) , c2) ;
11428
}
2f0b4b06 Andy McCall 4 days ago ↶
11429
11430
# [ test ]
11431
fn a_fetch_of_many_bookmarks_takes_shared_changes_once ( ) {
11432
11433
11434
11435
11436
hermetic ( ) ;
11437
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
11438
let root = dir.path ( ) ;
11439
drop ( Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ) ;
11440
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
11441
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
11442
.unwrap ( )
11443
.with_home ( root.join ( " home-a" ) ) ;
11444
let w = root.join ( " a" ) ;
11445
write ( & w, " f.txt" , b " one\n " ) ;
11446
a.commit ( " one" , & [ ] ) .unwrap ( ) ;
11447
write ( & w, " f.txt" , b " one\n two\n " ) ;
11448
a.commit ( " two" , & [ ] ) .unwrap ( ) ;
11449
a.push ( " origin" , false ) .unwrap ( ) ;
11450
11451
for i in 0 .. 5 {
11452
a.bookmark_create ( & format! ( " feat{i} " ) , None ) .unwrap ( ) ;
11453
a.bookmark_switch ( & format! ( " feat{i} " ) , false ) .unwrap ( ) ;
11454
write ( & w, & format! ( " feat{i} .txt" ) , format! ( " {i} \n " ) .as_bytes ( ) ) ;
11455
a.commit ( & format! ( " feat {i} " ) , & [ ] ) .unwrap ( ) ;
11456
a.push ( " origin" , false ) .unwrap ( ) ;
11457
a.bookmark_switch ( " main" , false ) .unwrap ( ) ;
11458
}
11459
11460
let b = Repo:: clone( & url, & root.join ( " b" ) )
11461
.unwrap ( )
11462
.with_home ( root.join ( " home-b" ) ) ;
11463
let report = b.fetch ( " origin" ) .unwrap ( ) ;
11464
assert_eq! ( report.refs.len ( ) , 6 , " every bookmark: {:?}" , report.refs) ;
11465
assert! (
11466
report.refs.iter ( ) .all ( | ( _, _, n ) | * n = = 0 ) ,
11467
" the clone brought them all: {:?}" ,
11468
report.refs
11469
) ;
11470
11471
11472
a.bookmark_switch ( " feat3" , false ) .unwrap ( ) ;
11473
write ( & w, " feat3.txt" , b " 3\n more\n " ) ;
11474
a.commit ( " feat 3 again" , & [ ] ) .unwrap ( ) ;
11475
a.push ( " origin" , false ) .unwrap ( ) ;
11476
let fetches = | | crate :: protocol:: LOCAL_FETCHES .with ( | n | n.get ( ) ) ;
11477
let at = fetches ( ) ;
11478
let report = b.fetch ( " origin" ) .unwrap ( ) ;
11479
assert_eq! ( fetches ( ) - at, 1 , " one bundle for all six bookmarks" ) ;
11480
let counts: Vec< ( String, usize ) > = report
11481
.refs
11482
.iter ( )
11483
.map ( | ( n , _, c ) | ( n.clone ( ) , * c) )
11484
.collect ( ) ;
11485
assert! ( counts.contains ( & ( " feat3" .to_string ( ) , 1 ) ) , " {counts:?}" ) ;
11486
assert_eq! (
11487
counts.iter ( ) .filter ( | ( _, c ) | * c > 0 ) .count ( ) ,
11488
1 ,
11489
" only feat3 had anything new: {counts:?}"
11490
) ;
11491
11492
11493
let at = fetches ( ) ;
11494
let report = b.fetch ( " origin" ) .unwrap ( ) ;
11495
assert_eq! ( fetches ( ) - at, 0 , " nothing to fetch, no round trip" ) ;
11496
assert_eq! ( report.refs.len ( ) , 6 ) ;
11497
assert! (
11498
report.refs.iter ( ) .all ( | ( _, _, n ) | * n = = 0 ) ,
11499
" {:?}" ,
11500
report.refs
11501
) ;
11502
}
aded4110 Andy McCall 1 day ago ↶
11503
11504
# [ test ]
11505
fn a_stale_pinned_key_cannot_fork_the_identity ( ) {
11506
11507
11508
11509
hermetic ( ) ;
11510
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
11511
let root = dir.path ( ) ;
11512
let home = root.join ( " home" ) ;
11513
let mut a = Repo:: init( & root.join ( " a" ) ) .unwrap ( ) .with_home ( & home) ;
11514
let ( first, _ ) = a
11515
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
11516
.unwrap ( ) ;
11517
let r = a.rotate_identity ( ) .unwrap ( ) ;
11518
assert_eq! ( ( r.old, a.config.author_key) , ( first, Some ( r.new) ) ) ;
11519
assert_eq! ( a.successors ( & first) .unwrap ( ) , vec! [ r.new] ) ;
11520
assert_eq! ( a.newest ( & first) .unwrap ( ) , r.new) ;
11521
11522
11523
let mut b = Repo:: init( & root.join ( " b" ) ) .unwrap ( ) .with_home ( & home) ;
11524
b.config.set ( " author.key" , & first.to_hex ( ) ) .unwrap ( ) ;
11525
b.save_config ( ) .unwrap ( ) ;
11526
let mut b = Repo:: open( & root.join ( " b" ) ) .unwrap ( ) .with_home ( & home) ;
11527
assert_eq! ( b.config.author_key, Some ( first) ) ;
11528
for e in [
11529
b.rotate_identity ( ) .unwrap_err ( ) .to_string ( ) ,
11530
b.renew_identity ( None ) .unwrap_err ( ) .to_string ( ) ,
11531
] {
11532
assert! ( e.contains ( " fork" ) & & e.contains ( & r.new.short ( 12 ) ) , " {e}" ) ;
11533
assert! (
11534
e.contains ( & format! ( " oxy config author.key {} " , r.new.to_hex ( ) ) ) ,
11535
" {e}"
11536
) ;
11537
}
11538
assert_eq! (
11539
b.successors ( & first) .unwrap ( ) ,
11540
vec! [ r.new] ,
11541
" nothing was made"
11542
) ;
11543
11544
11545
b.config.set ( " author.key" , & r.new.to_hex ( ) ) .unwrap ( ) ;
11546
b.save_config ( ) .unwrap ( ) ;
11547
let mut b = Repo:: open( & root.join ( " b" ) ) .unwrap ( ) .with_home ( & home) ;
11548
let r2 = b.rotate_identity ( ) .unwrap ( ) ;
11549
assert_eq! ( r2.old, r.new) ;
11550
assert_eq! ( b.newest ( & first) .unwrap ( ) , r2.new) ;
11551
}