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
238
# [ test ]
98286406 Andy McCall 1 month ago ↶
239
fn concurrent_edits_conflict_and_can_be_resolved ( ) {
240
hermetic ( ) ;
241
let ( dir, repo, h1, h2, h3) = conflicted_repo ( ) ;
242
let root = dir.path ( ) ;
243
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
244
assert_eq! ( heads, [ h2, h3] .into_iter ( ) .collect ( ) ) ;
245
let st = repo.load_state ( & [ h2, h3] ) .unwrap ( ) ;
246
assert! ( st.materialize ( ) .is_conflicted ( ) ) ;
247
248
75167672 Andy McCall 1 month ago
249
let r = repo
250
.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
251
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
252
assert! ( r.conflicted) ;
253
let marked = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
254
assert! (
255
marked.starts_with ( " a\n <<<<<<< oxy conflict 1 of 1" ) ,
256
" {marked}"
257
) ;
258
assert! (
259
marked.contains ( " B\n " ) & & marked.contains ( " X\n " ) & & marked.ends_with ( " end\n c\n " ) ,
260
" {marked}"
261
) ;
98286406 Andy McCall 1 month ago ↶
262
assert! ( has_conflict_markers ( marked.as_bytes ( ) ) ) ;
263
264
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
265
assert! ( s.statuses.is_empty ( ) ) ;
266
assert! ( ! s.resolves) ;
267
268
269
write ( root, " f.txt" , marked.replace ( " B\n " , " BB\n " ) .as_bytes ( ) ) ;
270
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
271
assert_eq! (
272
statuses ( & s) ,
273
vec! [ ( " f.txt" .into ( ) , ChangeStatus:: ConflictEdited) ]
274
) ;
98286406 Andy McCall 1 month ago ↶
275
assert! ( repo.commit ( " no" , & [ ] ) .is_err ( ) ) ;
276
277
278
write ( root, " f.txt" , b " a\n X\n c\n d\n " ) ;
279
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
280
assert_eq! ( statuses ( & s) , vec! [ ( " f.txt" .into ( ) , ChangeStatus:: Resolved) ] ) ;
281
assert! ( s.resolves) ;
282
let h4 = repo.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
283
let c4 = repo.store ( ) .get_change ( & h4) .unwrap ( ) ;
284
assert_eq! ( c4.kind, ChangeKind:: Resolution) ;
285
let mut want = vec! [ h1, h2, h3] ;
286
want.sort ( ) ;
75167672 Andy McCall 1 month ago
287
assert_eq! (
288
c4.deps, want,
289
" a resolution depends on exactly the contributors and what it references"
290
) ;
98286406 Andy McCall 1 month ago ↶
291
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h4] ) ;
292
let snap = repo.load_state ( & [ h4] ) .unwrap ( ) .materialize ( ) ;
293
assert! ( ! snap.is_conflicted ( ) ) ;
75167672 Andy McCall 1 month ago
294
assert! (
295
matches! ( & snap.files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n X\n c\n d\n " )
296
) ;
98286406 Andy McCall 1 month ago ↶
297
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
298
299
300
let mut st = State:: new( ) ;
301
for h in [ h4, h3, h1, h2] {
302
st.add_unchecked ( repo.store ( ) .get_change ( & h) .unwrap ( ) ) ;
303
}
304
st.validate ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
305
assert! (
306
matches! ( & st.materialize ( ) .files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n X\n c\n d\n " )
307
) ;
98286406 Andy McCall 1 month ago ↶
308
}
309
69953b50 Andy McCall 17 days ago ↶
310
311
312
98286406 Andy McCall 1 month ago ↶
313
# [ test ]
69953b50 Andy McCall 17 days ago ↶
314
fn resolutions_and_amends_take_paths ( ) {
315
hermetic ( ) ;
316
let ( dir, repo, _h1, _h2, _h3) = conflicted_repo ( ) ;
317
let root = dir.path ( ) ;
318
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
319
.unwrap ( ) ;
320
321
write ( root, " f.txt" , b " a\n X\n c\n " ) ;
322
write ( root, " notes.txt" , b " pending\n " ) ;
323
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
324
assert! ( s.resolves) ;
325
let err = repo
326
.commit_selection_partial ( " resolve" , & [ ] , & Selection:: Paths( vec! [ " notes.txt" .into ( ) ] ) )
327
.unwrap_err ( )
328
.to_string ( ) ;
329
assert! (
330
err.contains ( " takes every file it resolves" ) & & err.contains ( " f.txt" ) ,
331
" {err}"
332
) ;
333
let ( h4, partial) = repo
334
.commit_selection_partial ( " resolve" , & [ ] , & Selection:: Paths( vec! [ " f.txt" .into ( ) ] ) )
335
.unwrap ( ) ;
336
assert! ( partial) ;
337
assert_eq! (
338
repo.store ( ) .get_change ( & h4) .unwrap ( ) .kind,
339
ChangeKind:: Resolution
340
) ;
341
assert! ( ! repo
342
.load_state ( & [ h4] )
343
.unwrap ( )
344
.materialize ( )
345
.is_conflicted ( ) ) ;
346
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
347
assert_eq! (
348
statuses ( & s) ,
349
vec! [ ( " notes.txt" .into ( ) , ChangeStatus:: Added) ] ,
350
" the other work waits"
351
) ;
352
353
354
355
let h5 = repo.commit ( " notes" , & [ h4] ) .unwrap ( ) ;
356
let id5 = repo.store ( ) .get_change ( & h5) .unwrap ( ) .change_id;
357
write ( root, " notes.txt" , b " pending\n more\n " ) ;
358
write ( root, " other.txt" , b " later\n " ) ;
359
let h6 = repo
360
.amend_paths ( Some ( " notes, amended" ) , & [ " notes.txt" .into ( ) ] )
361
.unwrap ( ) ;
362
let c6 = repo.store ( ) .get_change ( & h6) .unwrap ( ) ;
363
assert_eq! ( ( c6.change_id, c6.message.as_str ( ) ) , ( id5, " notes, amended" ) ) ;
364
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h6] ) ;
365
let snap6 = repo.load_state ( & [ h6] ) .unwrap ( ) .materialize ( ) ;
366
assert! (
367
matches! ( & snap6.files[ " notes.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " pending\n more\n " )
368
) ;
369
assert! (
370
! snap6.files.contains_key ( " other.txt" ) ,
371
" other.txt stays out"
372
) ;
373
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
374
assert_eq! (
375
statuses ( & s) ,
376
vec! [ ( " other.txt" .into ( ) , ChangeStatus:: Added) ]
377
) ;
378
}
379
380
# [ test ]
98286406 Andy McCall 1 month ago ↶
381
fn resolution_may_keep_both_sides_or_neither ( ) {
382
hermetic ( ) ;
383
let ( dir, repo, _h1, _h2, _h3) = conflicted_repo ( ) ;
384
let root = dir.path ( ) ;
75167672 Andy McCall 1 month ago
385
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
386
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
387
388
write ( root, " f.txt" , b " a\n X\n B\n c\n " ) ;
389
let h = repo.commit ( " both" , & [ ] ) .unwrap ( ) ;
390
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
391
assert! ( ! snap.is_conflicted ( ) ) ;
75167672 Andy McCall 1 month ago
392
assert! (
393
matches! ( & snap.files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n X\n B\n c\n " )
394
) ;
98286406 Andy McCall 1 month ago ↶
395
396
repo.undo ( ) .unwrap ( ) ;
397
write ( root, " f.txt" , b " a\n c\n " ) ;
398
let h = repo.commit ( " neither" , & [ ] ) .unwrap ( ) ;
399
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
400
assert! ( ! snap.is_conflicted ( ) ) ;
75167672 Andy McCall 1 month ago
401
assert! (
402
matches! ( & snap.files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n c\n " )
403
) ;
98286406 Andy McCall 1 month ago ↶
404
}
405
406
# [ test ]
407
fn name_conflict_resolved_by_removing_a_candidate ( ) {
408
hermetic ( ) ;
409
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
410
let root = dir.path ( ) ;
411
let repo = Repo:: init( root) .unwrap ( ) ;
412
write ( root, " keep.txt" , b " k\n " ) ;
413
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
414
write ( root, " new.txt" , b " one\n " ) ;
415
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
416
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
417
write ( root, " new.txt" , b " two\n " ) ;
418
repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
419
let r = repo
420
.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
421
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
422
assert! ( r.conflicted) ;
75167672 Andy McCall 1 month ago
423
assert! (
424
root.join ( " new.txt.oxy-conflict-1" ) .exists ( )
425
& & root.join ( " new.txt.oxy-conflict-2" ) .exists ( )
426
) ;
98286406 Andy McCall 1 month ago ↶
427
assert! ( ! root.join ( " new.txt" ) .exists ( ) ) ;
428
429
75167672 Andy McCall 1 month ago
430
let ( keep, drop) = if fs:: read( root.join ( " new.txt.oxy-conflict-1" ) ) .unwrap ( ) = = b " two\n " {
431
( 1 , 2 )
432
} else {
433
( 2 , 1 )
434
} ;
98286406 Andy McCall 1 month ago ↶
435
fs:: remove_file( root.join ( format! ( " new.txt.oxy-conflict-{drop} " ) ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
436
fs:: rename(
437
root.join ( format! ( " new.txt.oxy-conflict-{keep} " ) ) ,
438
root.join ( " new.txt" ) ,
439
)
440
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
441
let h = repo.commit ( " pick two" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
442
assert_eq! (
443
repo.store ( ) .get_change ( & h) .unwrap ( ) .kind,
444
ChangeKind:: Resolution
445
) ;
98286406 Andy McCall 1 month ago ↶
446
447
448
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
449
assert_eq! ( heads, [ h1, h] .into_iter ( ) .collect ( ) ) ;
75167672 Andy McCall 1 month ago
450
let snap = repo
451
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
452
.unwrap ( )
453
.materialize ( ) ;
98286406 Andy McCall 1 month ago ↶
454
assert! ( ! snap.is_conflicted ( ) , " {:?}" , snap.tree.conflicts) ;
75167672 Andy McCall 1 month ago
455
assert_eq! (
456
snap.files.keys ( ) .cloned ( ) .collect:: < Vec< _ > > ( ) ,
457
[ " keep.txt" , " new.txt" ]
458
) ;
459
assert! (
460
matches! ( & snap.files[ " new.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " two\n " )
461
) ;
98286406 Andy McCall 1 month ago ↶
462
}
463
c2e1d090 Andy McCall 17 days ago
464
465
466
467
fn merged_additions (
468
side : impl Fn( &Path , bool ) ,
469
) -> ( tempfile:: TempDir, Repo, Vec< ChangeHash> , [ChangeHash; 3]) {
470
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
471
let root = dir.path ( ) .to_path_buf ( ) ;
472
let mut repo = Repo:: init( & root) .unwrap ( ) ;
473
write ( & root, " keep.txt" , b " k\n " ) ;
474
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
475
repo.bookmark_create ( " side" , None ) .unwrap ( ) ;
476
side ( & root, false ) ;
477
let ha = repo.commit ( " add on main" , & [ ] ) .unwrap ( ) ;
478
repo.bookmark_switch ( " side" , false ) .unwrap ( ) ;
479
side ( & root, true ) ;
480
let hb = repo.commit ( " add on side" , & [ ] ) .unwrap ( ) ;
481
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
482
let m = repo.merge ( " side" ) .unwrap ( ) ;
483
assert! ( m.changed & & m.checked_out) ;
484
( dir, repo, m.heads, [ h1, ha, hb] )
485
}
486
487
fn conflict_copies ( root : & Path) -> Vec< String> {
488
let mut out: Vec< String> = fs:: read_dir( root)
489
.unwrap ( )
490
.map ( | e | e.unwrap ( ) .file_name ( ) .to_string_lossy ( ) .into_owned ( ) )
491
.filter ( | n | n.contains ( " .oxy-conflict-" ) )
492
.collect ( ) ;
493
out.sort ( ) ;
494
out
495
}
496
497
498
499
500
# [ test ]
501
fn identical_additions_unify_on_merge ( ) {
502
hermetic ( ) ;
503
let ( dir, repo, heads, [ h1, ha, hb] ) =
504
merged_additions ( | root , _| write ( root, " x.txt" , b " same\n lines\n " ) ) ;
505
let root = dir.path ( ) ;
506
507
let st = repo.load_state ( & heads) .unwrap ( ) ;
508
assert! ( st
509
.tree ( )
510
.view ( )
511
.conflicts
512
.iter ( )
513
.any ( | c | matches! ( c, crate :: tree:: TreeConflict:: Name { .. } ) ) ) ;
514
let snap = st.materialize ( ) ;
515
assert! ( ! snap.is_conflicted ( ) , " {:?}" , snap.tree.conflicts) ;
516
assert_eq! (
517
snap.files.keys ( ) .cloned ( ) .collect:: < Vec< _ > > ( ) ,
518
[ " keep.txt" , " x.txt" ]
519
) ;
520
assert! (
521
matches! ( & snap.files[ " x.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " same\n lines\n " )
522
) ;
523
assert_eq! ( snap.tree.unified.len ( ) , 1 ) ;
524
525
assert_eq! ( fs:: read( root.join ( " x.txt" ) ) .unwrap ( ) , b " same\n lines\n " ) ;
526
assert! (
527
conflict_copies ( root) .is_empty ( ) ,
528
" {:?}" ,
529
conflict_copies ( root)
530
) ;
531
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
532
assert! ( s.statuses.is_empty ( ) , " {:?}" , statuses ( & s) ) ;
533
assert! ( ! s.changed & & ! s.resolves) ;
534
assert! ( ! s.base_snapshot.is_conflicted ( ) ) ;
535
assert! ( s.wc.tree_ops.is_empty ( ) & & s.wc.edits.is_empty ( ) ) ;
536
let json = repo.status_json ( & s) .unwrap ( ) ;
537
assert! ( json.contains ( " \" conflicted\" :false" ) , " {json}" ) ;
538
assert! ( repo.commit ( " nothing" , & [ ] ) .is_err ( ) ) ;
539
assert_eq! ( repo.file_at ( " x.txt" , None ) .unwrap ( ) , b " same\n lines\n " ) ;
540
541
let mut peer = State:: new( ) ;
542
for h in [ hb, ha, h1] {
543
peer.add_unchecked ( repo.store ( ) .get_change ( & h) .unwrap ( ) ) ;
544
}
545
peer.validate ( ) .unwrap ( ) ;
546
let ps = peer.materialize ( ) ;
547
assert! ( ! ps.is_conflicted ( ) ) ;
548
assert_eq! ( ps.tree.entries[ " x.txt" ] , snap.tree.entries[ " x.txt" ] ) ;
549
assert_eq! ( ps.tree.unified, snap.tree.unified) ;
550
551
let log: Vec< ChangeHash> = repo
552
.log_for_path ( " x.txt" )
553
.unwrap ( )
554
.iter ( )
555
.map ( | e | e.hash )
556
.collect ( ) ;
557
assert! ( log.contains ( & ha) & & log.contains ( & hb) , " {log:?}" ) ;
558
559
560
write ( root, " x.txt" , b " same\n edited\n " ) ;
561
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
562
assert_eq! ( statuses ( & s) , vec! [ ( " x.txt" .into ( ) , ChangeStatus:: Modified) ] ) ;
563
let he = repo.commit ( " edit x" , & [ ] ) .unwrap ( ) ;
564
let c = repo.store ( ) .get_change ( & he) .unwrap ( ) ;
565
assert_eq! ( c.kind, ChangeKind:: Normal) ;
566
let ( dup, _ ) = snap.tree.unified.iter ( ) .next ( ) .unwrap ( ) ;
567
assert! (
568
c.tree_ops
569
.iter ( )
570
.any ( | o | matches! ( o, TreeOp:: RemoveNode { node, .. } if node = = dup) ) ,
571
" {:?}" ,
572
c.tree_ops
573
) ;
574
let d = repo.describe_change ( & he) .unwrap ( ) ;
575
assert! ( ! d.contains ( " D x.txt" ) , " {d}" ) ;
576
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
577
assert! (
578
st.tree ( ) .view ( ) .conflicts.is_empty ( ) ,
579
" nothing left to unify"
580
) ;
581
let snap = st.materialize ( ) ;
582
assert! ( ! snap.is_conflicted ( ) ) ;
583
assert! (
584
matches! ( & snap.files[ " x.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " same\n edited\n " )
585
) ;
586
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
587
588
589
fs:: remove_file( root.join ( " x.txt" ) ) .unwrap ( ) ;
590
repo.commit ( " drop x" , & [ ] ) .unwrap ( ) ;
591
let snap = repo
592
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
593
.unwrap ( )
594
.materialize ( ) ;
595
assert! ( ! snap.is_conflicted ( ) , " {:?}" , snap.tree.conflicts) ;
596
assert_eq! ( snap.files.keys ( ) .cloned ( ) .collect:: < Vec< _ > > ( ) , [ " keep.txt" ] ) ;
597
assert! ( ! root.join ( " x.txt" ) .exists ( ) ) ;
598
}
599
600
601
602
# [ test ]
603
fn unified_additions_stay_one_when_removed_renamed_or_committed_alone ( ) {
604
hermetic ( ) ;
605
let add = | root : & Path, _: bool | write ( root, " x.txt" , b " same\n " ) ;
606
for how in [ " remove" , " rename" , " path" , " hunk" ] {
607
let ( dir, repo, _ , _ ) = merged_additions ( add) ;
608
let root = dir.path ( ) ;
609
match how {
610
" remove" => fs:: remove_file( root.join ( " x.txt" ) ) .unwrap ( ) ,
611
" rename" => fs:: rename( root.join ( " x.txt" ) , root.join ( " y.txt" ) ) .unwrap ( ) ,
612
_ => {
613
write ( root, " x.txt" , b " same\n more\n " ) ;
614
write ( root, " other.txt" , b " o\n " ) ;
615
}
616
}
617
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
618
let h = match how {
619
" path" => repo
620
.commit_selection ( " x alone" , & [ ] , & Selection:: Paths( vec! [ " x.txt" .into ( ) ] ) )
621
.unwrap ( ) ,
622
" hunk" => {
623
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
624
625
assert! ( hunks.iter ( ) .all ( | h | ! h.text.starts_with ( " D " ) ) , " {hunks:?}" ) ;
626
let x: Vec< usize > = hunks
627
.iter ( )
628
.filter ( | h | h.path = = " x.txt" )
629
.map ( | h | h.index )
630
.collect ( ) ;
631
assert! ( ! x.is_empty ( ) ) ;
632
repo.commit_selection ( " x hunks" , & [ ] , & Selection:: Hunks( x) )
633
.unwrap ( )
634
}
635
_ => repo.commit ( how, & [ ] ) .unwrap ( ) ,
636
} ;
637
assert! ( ! s.base_snapshot.is_conflicted ( ) ) ;
638
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
639
assert! ( heads.contains ( & h) ) ;
640
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
641
assert! ( ! snap.is_conflicted ( ) , " {how}: {:?}" , snap.tree.conflicts) ;
642
let want: & [ & str ] = match how {
643
" remove" => & [ " keep.txt" ] ,
644
" rename" => & [ " keep.txt" , " y.txt" ] ,
645
_ => & [ " keep.txt" , " x.txt" ] ,
646
} ;
647
assert_eq! (
648
snap.files.keys ( ) .cloned ( ) .collect:: < Vec< _ > > ( ) ,
649
want,
650
" {how}"
651
) ;
652
assert! ( conflict_copies ( root) .is_empty ( ) , " {how}" ) ;
653
}
654
}
655
656
657
658
# [ test ]
659
fn additions_that_differ_still_conflict ( ) {
660
hermetic ( ) ;
661
# [ cfg ( unix ) ]
662
use std:: os:: unix:: fs:: PermissionsExt;
663
let content =
664
| root : & Path, side : bool | write ( root, " x.txt" , if side { b " two\n " } else { b " one\n " } ) ;
665
let mode = | root : & Path, side : bool | {
666
write ( root, " x.txt" , b " same\n " ) ;
667
# [ cfg ( unix ) ]
668
if side {
669
fs:: set_permissions( root.join ( " x.txt" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
670
}
671
} ;
672
let check = | what : & str , side : & dyn Fn( & Path, bool ) | {
673
let ( dir, repo, heads, _ ) = merged_additions ( side) ;
674
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
675
assert! ( snap.tree.unified.is_empty ( ) , " {what}" ) ;
676
assert! (
677
snap.tree.conflicts.iter ( ) .any (
678
| c | matches! ( c, crate :: tree:: TreeConflict:: Name { name, .. } if name = = " x.txt" )
679
) ,
680
" {what}: {:?}" ,
681
snap.tree.conflicts
682
) ;
683
assert_eq! (
684
conflict_copies ( dir.path ( ) ) ,
685
[ " x.txt.oxy-conflict-1" , " x.txt.oxy-conflict-2" ] ,
686
" {what}"
687
) ;
688
assert! ( ! dir.path ( ) .join ( " x.txt" ) .exists ( ) , " {what}" ) ;
689
} ;
690
check ( " content" , & content) ;
691
# [ cfg ( unix ) ]
692
check ( " mode" , & mode) ;
693
# [ cfg ( not ( unix ) ) ]
694
let _ = mode;
695
}
696
697
698
699
700
# [ test ]
701
fn the_same_subrepo_added_on_two_bookmarks_unifies ( ) {
702
hermetic ( ) ;
703
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
704
let root = dir.path ( ) ;
705
let lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
706
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
707
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
708
.unwrap ( )
709
.with_home ( root.join ( " home" ) ) ;
710
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
711
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
712
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
713
let _ = lib_origin;
714
715
let app_dir = root.join ( " app" ) ;
716
let mut app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
717
write ( & app_dir, " main.txt" , b " app\n " ) ;
718
app.commit ( " base" , & [ ] ) .unwrap ( ) ;
719
app.bookmark_create ( " side" , None ) .unwrap ( ) ;
720
app.submodule_add ( & lib_url, " rstar-api" , None ) .unwrap ( ) ;
721
let modules = fs:: read( app_dir.join ( " .oxymodules" ) ) .unwrap ( ) ;
722
app.commit ( " rstar-api on main" , & [ ] ) .unwrap ( ) ;
723
724
725
app.bookmark_switch ( " side" , false ) .unwrap ( ) ;
726
assert! ( app_dir.join ( " rstar-api/.oxy" ) .is_dir ( ) ) ;
727
write ( & app_dir, " .oxymodules" , & modules) ;
728
let s = app.snapshot ( " status" ) .unwrap ( ) ;
729
assert_eq! (
730
statuses ( & s) ,
731
vec! [
732
( " .oxymodules" .into ( ) , ChangeStatus:: Added) ,
733
( " rstar-api" .into ( ) , ChangeStatus:: Added)
734
]
735
) ;
736
app.commit ( " rstar-api on side" , & [ ] ) .unwrap ( ) ;
737
app.bookmark_switch ( " main" , false ) .unwrap ( ) ;
738
739
let m = app.merge ( " side" ) .unwrap ( ) ;
740
assert! ( m.changed & & m.checked_out) ;
741
let snap = app.load_state ( & m.heads) .unwrap ( ) .materialize ( ) ;
742
assert! ( ! snap.is_conflicted ( ) , " {:?}" , snap.tree.conflicts) ;
743
assert_eq! ( snap.tree.unified.len ( ) , 2 ) ;
744
assert_eq! ( snap.files[ " rstar-api" ] .kind, NodeKind:: Subrepo) ;
745
assert! ( conflict_copies ( & app_dir) .is_empty ( ) ) ;
746
let s = app.snapshot ( " status" ) .unwrap ( ) ;
747
assert! ( s.statuses.is_empty ( ) , " {:?}" , statuses ( & s) ) ;
748
assert! ( ! s.base_snapshot.is_conflicted ( ) ) ;
749
assert_eq! (
750
fs:: read_to_string( app_dir.join ( " rstar-api/lib.txt" ) ) .unwrap ( ) ,
751
" v1\n "
752
) ;
753
}
754
98286406 Andy McCall 1 month ago ↶
755
# [ test ]
756
fn identities_and_signed_commits ( ) {
757
hermetic ( ) ;
758
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
759
let root = dir.path ( ) ;
760
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
761
write ( root, " a.txt" , b " a\n " ) ;
762
let h0 = repo.commit ( " unsigned" , & [ ] ) .unwrap ( ) ;
763
assert_eq! ( repo.verify_change ( & h0) .unwrap ( ) , SignatureStatus:: Unsigned) ;
75167672 Andy McCall 1 month ago
764
assert_eq! (
765
repo.store ( ) .get_change ( & h0) .unwrap ( ) .author,
766
ObjectRef:: ZERO
767
) ;
98286406 Andy McCall 1 month ago ↶
768
75167672 Andy McCall 1 month ago
769
let ( key, id) = repo
770
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
771
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
772
assert_eq! ( id.hash ( ) , key) ;
773
assert_eq! ( repo.config.author_key, Some ( key) ) ;
774
775
let reopened = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
776
assert_eq! ( reopened.config.author_key, Some ( key) ) ;
777
assert_eq! ( reopened.secret_key ( ) .unwrap ( ) .unwrap ( ) .0 , key) ;
778
779
780
write ( root, " a.txt" , b " a\n b\n " ) ;
781
let h1 = repo.commit ( " authored" , & [ ] ) .unwrap ( ) ;
782
assert_eq! ( repo.store ( ) .get_change ( & h1) .unwrap ( ) .author, key) ;
783
assert_eq! ( repo.verify_change ( & h1) .unwrap ( ) , SignatureStatus:: Unsigned) ;
784
785
repo.sign_change ( & h1) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
786
assert! (
787
matches! ( repo.verify_change ( & h1) .unwrap ( ) , SignatureStatus:: Valid { key: k, .. } if k = = key)
788
) ;
98286406 Andy McCall 1 month ago ↶
789
790
repo.config.set ( " core.sign" , " change" ) .unwrap ( ) ;
791
repo.save_config ( ) .unwrap ( ) ;
792
write ( root, " a.txt" , b " a\n b\n c\n " ) ;
793
let h2 = repo.commit ( " signed" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
794
assert_eq! (
795
repo.verify_change ( & h2) .unwrap ( ) ,
796
SignatureStatus:: Valid {
797
key,
798
name: " Ada" .into ( ) ,
799
email: " [email protected] " .into ( ) ,
800
sealed: false ,
801
revoked: false
802
}
803
) ;
98286406 Andy McCall 1 month ago ↶
804
05e71ed6 Andy McCall 1 month ago ↶
805
806
807
98286406 Andy McCall 1 month ago ↶
808
let other = SecretKey:: generate( ) ;
809
let other_id = other.identity ( " Mallory" , " [email protected] " , 0 ) ;
810
let other_key = repo.store ( ) .put_identity ( & other_id) .unwrap ( ) ;
05e71ed6 Andy McCall 1 month ago ↶
811
write ( root, " a.txt" , b " a\n b\n c\n d\n " ) ;
812
repo.config.set ( " core.sign" , " seal" ) .unwrap ( ) ;
813
let h3 = repo.commit ( " ada's, unsigned" , & [ ] ) .unwrap ( ) ;
814
let forged = other.sign ( Domain:: Change, & other_key, & h3) ;
815
repo.store ( ) .put_signature ( & h3, & forged) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
816
assert! (
05e71ed6 Andy McCall 1 month ago ↶
817
matches! ( repo.verify_change ( & h3) .unwrap ( ) , SignatureStatus:: Invalid( m) if m.contains ( " neither the author" ) )
75167672 Andy McCall 1 month ago
818
) ;
05e71ed6 Andy McCall 1 month ago ↶
819
let adopted = other.sign ( Domain:: Change, & other_key, & h0) ;
820
repo.store ( ) .put_signature ( & h0, & adopted) .unwrap ( ) ;
821
assert! (
822
matches! ( repo.verify_change ( & h0) .unwrap ( ) , SignatureStatus:: Valid { name, .. } if name = = " Mallory" )
823
) ;
98286406 Andy McCall 1 month ago ↶
824
825
826
let mut sig = repo.store ( ) .signatures ( & h2) .unwrap ( ) .remove ( 0 ) ;
827
sig.ed[ 0 ] ^ = 1 ;
828
repo.store ( ) .put_signature ( & h2, & sig) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
829
assert! ( matches! (
830
repo.verify_change ( & h2) .unwrap ( ) ,
831
SignatureStatus:: Invalid( _ )
832
) ) ;
98286406 Andy McCall 1 month ago ↶
833
834
835
let no_keys = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " nowhere" ) ) ;
836
write ( root, " a.txt" , b " z\n " ) ;
837
assert! ( no_keys.commit ( " cannot sign" , & [ ] ) .is_err ( ) ) ;
838
}
839
840
# [ test ]
841
fn checkout_moves_the_working_copy ( ) {
842
hermetic ( ) ;
843
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
844
let root = dir.path ( ) ;
845
let repo = Repo:: init( root) .unwrap ( ) ;
846
write ( root, " a.txt" , b " one\n " ) ;
847
write ( root, " sub/deep/b.txt" , b " b\n " ) ;
848
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
849
write ( root, " a.txt" , b " two\n " ) ;
850
fs:: remove_file( root.join ( " sub/deep/b.txt" ) ) .unwrap ( ) ;
851
write ( root, " bin.dat" , & [ 0 , 1 , 2 , 3 ] ) ;
852
let h2 = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
853
854
let r = repo.checkout ( & [ h1] , false , " oxy checkout" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
855
assert_eq! (
856
r,
857
CheckoutReport {
858
written: 2 ,
859
removed: 1 ,
9b723f69 Andy McCall 24 days ago ↶
860
removed_paths: vec! [ " bin.dat" .into ( ) ] ,
75167672 Andy McCall 1 month ago
861
conflicted: false ,
d3cd7ef9 Andy McCall 1 month ago ↶
862
notes: vec! [ ] ,
863
lacks: 1 ,
75167672 Andy McCall 1 month ago
864
}
865
) ;
98286406 Andy McCall 1 month ago ↶
866
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n " ) ;
867
assert_eq! ( fs:: read( root.join ( " sub/deep/b.txt" ) ) .unwrap ( ) , b " b\n " ) ;
868
assert! ( ! root.join ( " bin.dat" ) .exists ( ) ) ;
869
let ( _ , wc) = repo.working_copy ( ) .unwrap ( ) ;
870
assert_eq! ( wc.deps, vec! [ h1] ) ;
75167672 Andy McCall 1 month ago
871
assert! (
872
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
873
" checkout leaves a clean working copy"
874
) ;
98286406 Andy McCall 1 month ago ↶
875
876
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
877
878
879
write ( root, " a.txt" , b " dirty\n " ) ;
880
assert! ( repo.checkout ( & [ h2] , false , " oxy checkout" ) .is_err ( ) ) ;
75167672 Andy McCall 1 month ago
881
let r = repo
882
.checkout ( & [ h2] , true , " oxy checkout --discard" )
883
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
884
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " two\n " ) ;
885
assert_eq! ( fs:: read( root.join ( " bin.dat" ) ) .unwrap ( ) , [ 0 , 1 , 2 , 3 ] ) ;
886
assert! ( ! root.join ( " sub" ) .exists ( ) , " empty directories are pruned" ) ;
887
assert_eq! ( r.removed, 1 ) ;
888
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
889
890
891
# [ cfg ( unix ) ]
892
{
893
fs:: remove_dir_all( root.join ( " sub" ) ) .ok ( ) ;
894
std:: os:: unix:: fs:: symlink( " /tmp" , root.join ( " sub" ) ) .unwrap ( ) ;
895
assert! ( repo.checkout ( & [ h1] , true , " oxy checkout" ) .is_err ( ) ) ;
896
}
897
}
898
899
# [ test ]
900
fn large_files_are_chunked_and_read_back ( ) {
901
hermetic ( ) ;
902
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
903
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
904
let mut big = vec! [ 0 u8 ; 700 * 1024 ] ;
905
for ( i, b) in big.iter_mut ( ) .enumerate ( ) {
906
* b = ( i as u32 ) .wrapping_mul ( 2654435761 ) .to_le_bytes ( ) [ 0 ] ;
907
}
908
let r = repo.store_blob ( & big) .unwrap ( ) ;
909
let blob = Blob:: decode( & repo.store ( ) .get_typed ( & r, ObjectType:: Blob) .unwrap ( ) ) .unwrap ( ) ;
910
assert! ( blob.chunks.len ( ) > = 3 , " {}" , blob.chunks.len ( ) ) ;
911
assert_eq! ( repo.read_blob ( & r) .unwrap ( ) , big) ;
912
assert_eq! ( Repo:: blob_ref( & big) , r) ;
913
}
914
915
# [ test ]
916
fn clone_push_pull_with_seals_and_attestations ( ) {
917
hermetic ( ) ;
918
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
919
let root = dir.path ( ) ;
920
let origin_path = root.join ( " origin" ) ;
921
let origin = Repo:: init( & origin_path) .unwrap ( ) ;
922
let origin_url = origin_path.to_string_lossy ( ) .to_string ( ) ;
923
924
75167672 Andy McCall 1 month ago
925
let mut a = Repo:: clone( & origin_url, & root.join ( " a" ) )
926
.unwrap ( )
927
.with_home ( root.join ( " home-a" ) ) ;
98286406 Andy McCall 1 month ago ↶
928
assert_eq! ( a.id ( ) .unwrap ( ) , origin.id ( ) .unwrap ( ) ) ;
75167672 Andy McCall 1 month ago
929
let ( key_a, _ ) = a
930
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
931
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
932
write ( & root.join ( " a" ) , " f.txt" , b " a\n " ) ;
933
let h1 = a.commit ( " first" , & [ ] ) .unwrap ( ) ;
934
let results = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
935
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
936
assert! ( results[ 0 ] .ok, " {}" , results[ 0 ] .reason) ;
937
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
938
75167672 Andy McCall 1 month ago
939
assert! (
940
matches! ( origin.authenticate_change ( & h1) .unwrap ( ) , SignatureStatus:: Valid { key, sealed: true , .. } if key = = key_a)
941
) ;
942
assert_eq! (
943
origin.verify_change ( & h1) .unwrap ( ) ,
944
SignatureStatus:: Unsigned
945
) ;
f8c69576 Andy McCall 1 month ago ↶
946
let serial_of = | att : & SignedAttest| match att.attest.body {
947
AttestBody:: Ref { serial, .. } => serial,
948
_ => unreachable! ( ) ,
949
} ;
98286406 Andy McCall 1 month ago ↶
950
let ( _ , att) = origin.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
951
952
953
let first = serial_of ( & att) ;
954
assert! ( first > 1_000_000_000_000 , " {first}" ) ;
98286406 Andy McCall 1 month ago ↶
955
let again = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
956
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
957
assert! ( again[ 0 ] .ok & & again[ 0 ] .reason.contains ( " up to date" ) ) ;
958
let ( _ , att) = origin.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
959
assert_eq! ( serial_of ( & att) , first, " a no-op push does not attest again" ) ;
98286406 Andy McCall 1 month ago ↶
960
961
962
let b = Repo:: clone( & origin_url, & root.join ( " b" ) ) .unwrap ( ) ;
963
assert_eq! ( fs:: read( root.join ( " b/f.txt" ) ) .unwrap ( ) , b " a\n " ) ;
964
assert_eq! ( b.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
75167672 Andy McCall 1 month ago
965
assert! ( matches! (
966
b.authenticate_change ( & h1) .unwrap ( ) ,
967
SignatureStatus:: Valid { sealed: true , .. }
968
) ) ;
98286406 Andy McCall 1 month ago ↶
969
970
971
write ( & root.join ( " b" ) , " f.txt" , b " a\n b\n " ) ;
972
let h2 = b.commit ( " second" , & [ ] ) .unwrap ( ) ;
973
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
974
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
975
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
976
977
978
write ( & root.join ( " a" ) , " g.txt" , b " g\n " ) ;
979
let h3 = a.commit ( " third" , & [ ] ) .unwrap ( ) ;
980
assert! ( a.push ( " origin" , false ) .is_err ( ) ) ;
981
let pr = a.pull ( " origin" ) .unwrap ( ) ;
982
assert! ( pr.changed & & pr.checked_out) ;
983
let mut want = vec! [ h2, h3] ;
984
want.sort ( ) ;
985
assert_eq! ( pr.heads, want) ;
986
assert_eq! ( fs:: read( root.join ( " a/f.txt" ) ) .unwrap ( ) , b " a\n b\n " ) ;
987
assert_eq! ( fs:: read( root.join ( " a/g.txt" ) ) .unwrap ( ) , b " g\n " ) ;
988
let results = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
989
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
990
assert! ( results[ 0 ] .ok, " {}" , results[ 0 ] .reason) ;
991
let mut origin_heads = origin.bookmark_heads ( ) .unwrap ( ) ;
992
origin_heads.sort ( ) ;
993
assert_eq! ( origin_heads, want) ;
994
let ( _ , att) = origin.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
995
assert_eq! ( serial_of ( & att) , first + 1 ) ;
98286406 Andy McCall 1 month ago ↶
996
997
998
let pr = b.pull ( " origin" ) .unwrap ( ) ;
999
assert_eq! ( pr.heads, want) ;
1000
assert_eq! ( fs:: read( root.join ( " b/g.txt" ) ) .unwrap ( ) , b " g\n " ) ;
1001
assert! ( ! b.snapshot ( " status" ) .unwrap ( ) .changed) ;
1002
1003
1004
write ( & root.join ( " a" ) , " f.txt" , b " a\n A\n " ) ;
1005
a.commit ( " a-edit" , & [ ] ) .unwrap ( ) ;
1006
a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
1007
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1008
write ( & root.join ( " b" ) , " f.txt" , b " a\n B\n " ) ;
1009
b.commit ( " b-edit" , & [ ] ) .unwrap ( ) ;
1010
let pr = b.pull ( " origin" ) .unwrap ( ) ;
1011
assert! ( pr.changed & & pr.checked_out) ;
1012
let text = fs:: read_to_string( root.join ( " b/f.txt" ) ) .unwrap ( ) ;
1013
assert! ( text.contains ( " <<<<<<< oxy conflict" ) , " {text}" ) ;
1014
write ( & root.join ( " b" ) , " f.txt" , b " a\n AB\n " ) ;
1015
let hr = b.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1016
assert_eq! (
1017
b.store ( ) .get_change ( & hr) .unwrap ( ) .kind,
1018
ChangeKind:: Resolution
1019
) ;
98286406 Andy McCall 1 month ago ↶
1020
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
1021
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1022
a.pull ( " origin" ) .unwrap ( ) ;
1023
assert_eq! ( fs:: read( root.join ( " a/f.txt" ) ) .unwrap ( ) , b " a\n AB\n " ) ;
1024
1025
1026
let mut want = vec! [ hr, h3] ;
1027
want.sort ( ) ;
1028
assert_eq! ( a.bookmark_heads ( ) .unwrap ( ) , want) ;
1029
assert! ( ! a.load_state ( & want) .unwrap ( ) .materialize ( ) .is_conflicted ( ) ) ;
1030
}
1031
1032
# [ test ]
1033
fn receive_push_rejects_bad_updates ( ) {
1034
hermetic ( ) ;
1035
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1036
let root = dir.path ( ) ;
1037
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1038
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
1039
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1040
.unwrap ( )
1041
.with_home ( root.join ( " home" ) ) ;
1042
let ( key, _ ) = a
1043
.create_identity ( " Ada" , " a@b" , IdentityScope:: Repo)
1044
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1045
write ( & root.join ( " a" ) , " f.txt" , b " x\n " ) ;
1046
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
1047
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
1048
let empty = origin.store ( ) .put_state ( & StateObject:: new( vec! [ ] ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1049
let s1 = origin
1050
.store ( )
1051
.put_state ( & StateObject:: new( vec! [ h1] ) )
1052
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1053
1054
75167672 Andy McCall 1 month ago
1055
let ghost = origin
1056
.store ( )
1057
.put_state ( & StateObject:: new( vec! [ ObjectRef( [ 9 ; 32 ] ) ] ) )
1058
.unwrap ( ) ;
1059
let r = origin
1060
.receive_push (
1061
Bundle:: default( ) ,
1062
vec! [ RefUpdate {
1063
name: " main" .into ( ) ,
1064
old_state: s1,
1065
new_state: ghost,
1066
force: false ,
1067
attestation: None ,
1068
} ] ,
1069
None ,
1070
vec! [ ] ,
1071
)
1072
.unwrap ( ) ;
1073
assert! (
1074
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " missing" ) ,
1075
" {}" ,
1076
r[ 0 ] .reason
1077
) ;
98286406 Andy McCall 1 month ago ↶
1078
1079
75167672 Andy McCall 1 month ago
1080
let r = origin
1081
.receive_push (
1082
Bundle:: default( ) ,
1083
vec! [ RefUpdate {
1084
name: " main" .into ( ) ,
1085
old_state: empty,
1086
new_state: s1,
1087
force: false ,
1088
attestation: None ,
1089
} ] ,
1090
None ,
1091
vec! [ ] ,
1092
)
1093
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1094
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " stale" ) , " {}" , r[ 0 ] .reason) ;
1095
1096
75167672 Andy McCall 1 month ago
1097
let r = origin
1098
.receive_push (
1099
Bundle:: default( ) ,
1100
vec! [ RefUpdate {
1101
name: " main" .into ( ) ,
1102
old_state: s1,
1103
new_state: empty,
1104
force: false ,
1105
attestation: None ,
1106
} ] ,
1107
None ,
1108
vec! [ ] ,
1109
)
1110
.unwrap ( ) ;
1111
assert! (
1112
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " fast-forward" ) ,
1113
" {}" ,
1114
r[ 0 ] .reason
1115
) ;
1116
let r = origin
1117
.receive_push (
1118
Bundle:: default( ) ,
1119
vec! [ RefUpdate {
1120
name: " main" .into ( ) ,
1121
old_state: s1,
1122
new_state: empty,
1123
force: true ,
1124
attestation: None ,
1125
} ] ,
1126
None ,
1127
vec! [ ] ,
1128
)
1129
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1130
assert! ( r[ 0 ] .ok) ;
1131
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ ] ) ;
1132
1133
75167672 Andy McCall 1 month ago
1134
let sa = a
1135
.sign_attest ( & Attest {
1136
repo: a.id ( ) .unwrap ( ) ,
1137
hlc: Hlc:: now( ) ,
1138
body: AttestBody:: Ref {
1139
name: " main" .into ( ) ,
1140
old_state: empty,
1141
new_state: s1,
1142
serial: 1 ,
1143
} ,
1144
} )
1145
.unwrap ( ) ;
1146
let r = origin
1147
.receive_push (
1148
Bundle:: default( ) ,
1149
vec! [ RefUpdate {
1150
name: " main" .into ( ) ,
1151
old_state: empty,
1152
new_state: s1,
1153
force: false ,
1154
attestation: Some ( sa.clone ( ) ) ,
1155
} ] ,
1156
None ,
1157
vec! [ ] ,
1158
)
1159
.unwrap ( ) ;
1160
assert! (
1161
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " serial" ) ,
1162
" {}" ,
1163
r[ 0 ] .reason
1164
) ;
98286406 Andy McCall 1 month ago ↶
1165
75167672 Andy McCall 1 month ago
1166
let sa2 = a
1167
.sign_attest ( & Attest {
1168
repo: a.id ( ) .unwrap ( ) ,
1169
hlc: Hlc:: now( ) ,
1170
body: AttestBody:: Ref {
1171
name: " main" .into ( ) ,
1172
old_state: s1,
1173
new_state: empty,
1174
serial: 5 ,
1175
} ,
1176
} )
1177
.unwrap ( ) ;
1178
let r = origin
1179
.receive_push (
1180
Bundle:: default( ) ,
1181
vec! [ RefUpdate {
1182
name: " main" .into ( ) ,
1183
old_state: empty,
1184
new_state: s1,
1185
force: false ,
1186
attestation: Some ( sa2) ,
1187
} ] ,
1188
None ,
1189
vec! [ ] ,
1190
)
1191
.unwrap ( ) ;
1192
assert! (
1193
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " describe" ) ,
1194
" {}" ,
1195
r[ 0 ] .reason
1196
) ;
98286406 Andy McCall 1 month ago ↶
1197
let _ = key;
1198
}
1199
1200
# [ test ]
1201
fn user_config_supplies_defaults_and_clones_sign ( ) {
1202
hermetic ( ) ;
1203
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1204
let root = dir.path ( ) ;
1205
let home = root.join ( " home" ) ;
1206
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1207
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1208
1209
1210
let mut a = Repo:: init( & root.join ( " a" ) ) .unwrap ( ) .with_home ( & home) ;
75167672 Andy McCall 1 month ago
1211
let ( key, _ ) = a
1212
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1213
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1214
assert! ( ! a.config.repo_layer ( ) .contains_key ( " author.key" ) ) ;
1215
assert_eq! ( a.config.author_key, Some ( key) ) ;
1216
assert! ( home.join ( " config.toml" ) .exists ( ) ) ;
1217
a.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
1218
assert! ( a.config.sign_changes) ;
1219
1220
1221
a.config.set ( " core.sign" , " seal" ) .unwrap ( ) ;
1222
a.save_config ( ) .unwrap ( ) ;
1223
let again = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) .with_home ( & home) ;
1224
assert! ( ! again.config.sign_changes) ;
1225
assert_eq! ( again.config.author_key, Some ( key) ) ;
75167672 Andy McCall 1 month ago
1226
assert_eq! (
1227
read_config_file ( & root.join ( " a/.oxy/config" ) )
1228
.unwrap ( )
1229
.get ( " core.sign" )
1230
.map ( String :: as_str) ,
1231
Some ( " seal" )
1232
) ;
98286406 Andy McCall 1 month ago ↶
1233
1234
1235
1236
let b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) .with_home ( & home) ;
1237
assert! ( b.config.sign_changes) ;
1238
write ( & root.join ( " b" ) , " f.txt" , b " f\n " ) ;
1239
let h = b.commit ( " signed from a clone" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1240
assert! (
1241
matches! ( b.verify_change ( & h) .unwrap ( ) , SignatureStatus:: Valid { key: k, .. } if k = = key)
1242
) ;
98286406 Andy McCall 1 month ago ↶
1243
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
75167672 Andy McCall 1 month ago
1244
assert! ( matches! (
1245
origin.verify_change ( & h) .unwrap ( ) ,
1246
SignatureStatus:: Valid { .. }
1247
) ) ;
98286406 Andy McCall 1 month ago ↶
1248
}
1249
1250
# [ test ]
1251
fn format1_repositories_are_migrated_on_open ( ) {
1252
hermetic ( ) ;
1253
use crate :: oplog:: { Format1Op, Format1View} ;
1254
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1255
let root = dir.path ( ) ;
1256
let repo = Repo:: init( root) .unwrap ( ) ;
1257
write ( root, " a.txt" , b " a\n " ) ;
1258
let h1 = repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
1259
let ( wc_hash, _ ) = repo.working_copy ( ) .unwrap ( ) ;
1260
1261
let mut v1 = Format1View:: default( ) ;
1262
v1.bookmarks.insert ( " main" .into ( ) , vec! [ ] ) ;
1263
v1.workspaces.insert ( repo.workspace ( ) , wc_hash) ;
75167672 Andy McCall 1 month ago
1264
let init = Format1Op {
1265
parents: vec! [ ] ,
1266
hlc: Hlc:: default( ) ,
1267
command: " oxy init" .into ( ) ,
1268
view: v1.clone ( ) ,
1269
} ;
98286406 Andy McCall 1 month ago ↶
1270
let init_ref = repo.store ( ) .put ( ObjectType:: Op, & init.encode ( ) ) .unwrap ( ) ;
1271
v1.bookmarks.insert ( " main" .into ( ) , vec! [ h1] ) ;
75167672 Andy McCall 1 month ago
1272
let commit = Format1Op {
1273
parents: vec! [ init_ref] ,
1274
hlc: Hlc:: default( ) ,
1275
command: " oxy commit" .into ( ) ,
1276
view: v1,
1277
} ;
98286406 Andy McCall 1 month ago ↶
1278
let commit_ref = repo.store ( ) .put ( ObjectType:: Op, & commit.encode ( ) ) .unwrap ( ) ;
1279
write_atomic ( & root.join ( " .oxy/ops/head" ) , commit_ref.to_hex ( ) .as_bytes ( ) ) .unwrap ( ) ;
1280
write_atomic ( & root.join ( " .oxy/format" ) , FORMAT1_LINE .as_bytes ( ) ) .unwrap ( ) ;
1281
drop ( repo) ;
1282
1283
let repo = Repo:: open( root) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1284
assert_eq! (
1285
fs:: read_to_string( root.join ( " .oxy/format" ) ) .unwrap ( ) ,
1286
FORMAT_LINE
1287
) ;
98286406 Andy McCall 1 month ago ↶
1288
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
1289
let ops = repo.op_log ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1290
assert_eq! (
1291
ops.iter ( )
1292
.map ( | ( _, o ) | o.command.as_str ( ) )
1293
.collect:: < Vec< _ > > ( ) ,
1294
[ " oxy commit" , " oxy init" ]
1295
) ;
98286406 Andy McCall 1 month ago ↶
1296
assert_eq! ( repo.heads_of ( & ops[ 1 ] .1. view, " main" ) .unwrap ( ) , vec! [ ] ) ;
1297
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
1298
1299
assert_eq! ( Repo:: open( root) .unwrap ( ) .op_log ( ) .unwrap ( ) .len ( ) , 2 ) ;
1300
}
1301
1302
# [ test ]
1303
fn undo_skips_snapshot_operations ( ) {
1304
hermetic ( ) ;
1305
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1306
let root = dir.path ( ) ;
1307
let repo = Repo:: init( root) .unwrap ( ) ;
1308
write ( root, " a.txt" , b " a\n " ) ;
1309
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
1310
1311
write ( root, " b.txt" , b " b\n " ) ;
1312
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
1313
assert! ( s.changed) ;
1314
assert! ( repo.head_op ( ) .unwrap ( ) .1. is_snapshot ( ) ) ;
1315
assert_eq! ( repo.last_command ( ) .unwrap ( ) .command, " oxy commit -m \" a\" " ) ;
1316
1317
repo.undo ( ) .unwrap ( ) ;
1318
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ ] ) ;
1319
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
1320
let paths: Vec< _ > = s.statuses.iter ( ) .map ( | p | p.path.as_str ( ) ) .collect ( ) ;
75167672 Andy McCall 1 month ago
1321
assert_eq! (
1322
paths,
1323
[ " a.txt" , " b.txt" ] ,
1324
" both files are back in the working copy"
1325
) ;
98286406 Andy McCall 1 month ago ↶
1326
assert! ( repo.store ( ) .get_change ( & h1) .unwrap ( ) .deps.is_empty ( ) ) ;
1327
1328
assert_eq! ( repo.last_command ( ) .unwrap ( ) .command, " oxy init" ) ;
1329
assert! ( repo.undo ( ) .is_err ( ) ) ;
1330
}
1331
1332
# [ test ]
1333
fn independent_changes_commute_and_make_multiple_heads ( ) {
1334
hermetic ( ) ;
1335
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1336
let root = dir.path ( ) ;
1337
let repo = Repo:: init( root) .unwrap ( ) ;
1338
write ( root, " a.txt" , b " a\n " ) ;
1339
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
1340
write ( root, " b.txt" , b " b\n " ) ;
1341
let h2 = repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
1342
let c2 = repo.store ( ) .get_change ( & h2) .unwrap ( ) ;
1343
assert! ( c2.deps.is_empty ( ) , " b.txt touches nothing from h1" ) ;
1344
let mut heads = repo.bookmark_heads ( ) .unwrap ( ) ;
1345
heads.sort ( ) ;
1346
let mut want = vec! [ h1, h2] ;
1347
want.sort ( ) ;
1348
assert_eq! ( heads, want) ;
1349
1350
write ( root, " a.txt" , b " a\n a2\n " ) ;
1351
let h3 = repo.commit ( " a2" , & [ ] ) .unwrap ( ) ;
1352
assert_eq! ( repo.store ( ) .get_change ( & h3) .unwrap ( ) .deps, vec! [ h1] ) ;
1353
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
1354
assert_eq! ( heads, [ h2, h3] .into_iter ( ) .collect ( ) ) ;
1355
1356
write ( root, " c.txt" , b " c\n " ) ;
1357
let h4 = repo.commit ( " c" , & [ h2] ) .unwrap ( ) ;
1358
assert_eq! ( repo.store ( ) .get_change ( & h4) .unwrap ( ) .deps, vec! [ h2] ) ;
1359
}
1360
1361
# [ test ]
1362
fn mode_change_and_text_binary_switch ( ) {
1363
hermetic ( ) ;
1364
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1365
let root = dir.path ( ) ;
1366
let repo = Repo:: init( root) .unwrap ( ) ;
1367
write ( root, " run.sh" , b " #!/bin/sh\n " ) ;
1368
repo.commit ( " add script" , & [ ] ) .unwrap ( ) ;
1369
# [ cfg ( unix ) ]
1370
{
1371
use std:: os:: unix:: fs:: PermissionsExt;
1372
fs:: set_permissions( root.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
1373
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1374
assert_eq! (
1375
statuses ( & s) ,
1376
vec! [ ( " run.sh" .into ( ) , ChangeStatus:: ModeChanged) ]
1377
) ;
98286406 Andy McCall 1 month ago ↶
1378
let h = repo.commit ( " exec" , & [ ] ) .unwrap ( ) ;
1379
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1380
assert! ( snap.files[ " run.sh" ] .mode.exec) ;
1381
}
1382
1383
write ( root, " run.sh" , b " \0 binary" ) ;
1384
let h = repo.commit ( " binary" , & [ ] ) .unwrap ( ) ;
1385
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1386
assert! ( snap.files[ " run.sh" ] .mode.opaque) ;
1387
write ( root, " run.sh" , b " text again\n " ) ;
1388
let h = repo.commit ( " text" , & [ ] ) .unwrap ( ) ;
1389
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1390
assert! ( ! snap.files[ " run.sh" ] .mode.opaque) ;
75167672 Andy McCall 1 month ago
1391
assert! (
1392
matches! ( & snap.files[ " run.sh" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " text again\n " )
1393
) ;
98286406 Andy McCall 1 month ago ↶
1394
assert! ( ! snap.is_conflicted ( ) ) ;
1395
}
83e0677b Andy McCall 1 month ago ↶
1396
6eae06b6 Andy McCall 27 days ago ↶
1397
1398
1399
# [ cfg ( unix ) ]
1400
# [ test ]
1401
fn a_binary_file_changed_and_made_executable_at_once_keeps_both ( ) {
1402
use std:: os:: unix:: fs:: PermissionsExt;
1403
hermetic ( ) ;
1404
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1405
let root = dir.path ( ) ;
1406
let repo = Repo:: init( root) .unwrap ( ) ;
1407
write ( root, " tool.exe" , b " MZ\0 one" ) ;
1408
repo.commit ( " add" , & [ ] ) .unwrap ( ) ;
1409
write ( root, " tool.exe" , b " MZ\0 two" ) ;
1410
fs:: set_permissions( root.join ( " tool.exe" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
1411
let h = repo.commit ( " update and +x" , & [ ] ) .unwrap ( ) ;
1412
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1413
let f = & snap.files[ " tool.exe" ] ;
1414
assert! ( f.mode.opaque & & f.mode.exec, " {:?}" , f.mode) ;
1415
assert! (
1416
repo.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ,
1417
" nothing left to commit"
1418
) ;
1419
}
1420
f0a517d2 Andy McCall 18 days ago ↶
1421
1422
1423
1424
83e0677b Andy McCall 1 month ago ↶
1425
# [ test ]
f0a517d2 Andy McCall 18 days ago ↶
1426
fn a_certified_key_pushes_under_a_subject_grant ( ) {
1427
use crate :: cert:: { Certificate, SignedCertificate} ;
1428
use crate :: identity:: Scope;
1429
hermetic ( ) ;
1430
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1431
let root = dir.path ( ) ;
1432
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1433
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1434
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1435
.unwrap ( )
1436
.with_home ( root.join ( " home-a" ) ) ;
1437
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1438
.unwrap ( ) ;
1439
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1440
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1441
a.policy_init ( ) .unwrap ( ) ;
1442
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1443
let hub_sk = SecretKey:: generate( ) ;
1444
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1445
a.policy_grant_subject (
1446
" agent:claude@ada" ,
1447
hub.clone ( ) ,
1448
Scope:: CHANGES | Scope:: REFS ,
1449
None ,
1450
)
1451
.unwrap ( ) ;
1452
assert! ( a
1453
.push ( " origin" , false )
1454
.unwrap ( )
1455
.iter ( )
1456
.any ( | r | r.name = = " policy" & & r.ok ) ) ;
1457
1458
let now = Hlc:: now( ) .wall_ms;
1459
1460
1461
let agent = | name : & str , subject : & str , from : u64 , to : u64 | {
1462
let mut r = Repo:: clone( & url, & root.join ( name) )
1463
.unwrap ( )
1464
.with_home ( root.join ( format! ( " home-{name} " ) ) ) ;
1465
let ( _ , id) = r
1466
.create_identity ( name, " [email protected] " , IdentityScope:: User)
1467
.unwrap ( ) ;
1468
let cert = SignedCertificate:: sign(
1469
Certificate {
1470
key: id,
1471
issuer: hub.hash ( ) ,
1472
subject: subject.into ( ) ,
1473
claims: vec! [ ] ,
1474
scope: Scope:: CHANGES | Scope:: REFS ,
1475
not_before: from,
1476
expires: to,
a2387d54 Andy McCall 18 days ago ↶
1477
grant: None ,
f0a517d2 Andy McCall 18 days ago ↶
1478
} ,
1479
& hub,
1480
& hub_sk,
1481
) ;
1482
r.store ( ) .put_identity ( & hub) .unwrap ( ) ;
1483
r.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1484
write ( & root.join ( name) , & format! ( " {name} .txt" ) , b " agent work\n " ) ;
1485
r.commit ( " work" , & [ ] ) .unwrap ( ) ;
1486
r
1487
} ;
1488
1489
let claude = agent ( " claude" , " agent:claude@ada" , now - 60_000 , now + 600_000 ) ;
1490
let r = claude.push ( " origin" , false ) .unwrap ( ) ;
1491
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
1492
1493
a.pull ( " origin" ) .unwrap ( ) ;
1494
assert! ( a.bookmark_heads ( ) .unwrap ( ) .iter ( ) .any ( | h | a
1495
.store ( )
1496
.get_change ( h)
1497
.map ( | c | c.message = = " work" )
1498
.unwrap_or ( false ) ) ) ;
1499
f53cbe7a Andy McCall 18 days ago ↶
1500
1501
1502
1503
1504
let host = Repo:: open( & root.join ( " origin" ) )
1505
.unwrap ( )
1506
.with_acceptor ( hub.clone ( ) , hub_sk.clone ( ) ) ;
1507
let ( attest_ref, sa) = host.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
1508
host.record_acceptance ( & attest_ref) .unwrap ( ) ;
1509
let served = host.remote_refs ( ) .unwrap ( ) ;
1510
let acc = served
1511
.refs
1512
.iter ( )
1513
.find ( | r | r.name = = " main" )
1514
.unwrap ( )
1515
.accepted
1516
.clone ( )
1517
.expect ( " served with the refs" ) ;
1518
let signer = sa.sigs[ 0 ] .key;
1519
assert_eq! (
1520
a.certified_time ( & signer, & sa, Some ( & acc) ) ,
1521
acc.attest.hlc.wall_ms
1522
) ;
1523
assert_eq! ( a.certified_time ( & signer, & sa, None ) , sa.attest.hlc.wall_ms) ;
1524
let stranger_sk = SecretKey:: generate( ) ;
1525
let stranger = stranger_sk.identity ( " stranger" , " s@x" , 1 ) ;
1526
let mut forged = acc.clone ( ) ;
1527
forged.sigs = vec! [ stranger_sk.sign ( Domain:: Attest, & stranger.hash ( ) , & acc.attest.hash ( ) ) ] ;
1528
assert_eq! (
1529
a.certified_time ( & signer, & sa, Some ( & forged) ) ,
1530
sa.attest.hlc.wall_ms,
1531
" not the issuer's"
1532
) ;
1533
let mut elsewhere = acc.clone ( ) ;
1534
elsewhere.attest.body = AttestBody:: Accepted {
1535
attest: ObjectRef( [ 7 ; 32 ] ) ,
1536
} ;
1537
elsewhere.sigs = vec! [ hub_sk.sign ( Domain:: Attest, & hub.hash ( ) , & elsewhere.attest.hash ( ) ) ] ;
1538
assert_eq! (
1539
a.certified_time ( & signer, & sa, Some ( & elsewhere) ) ,
1540
sa.attest.hlc.wall_ms,
1541
" another attestation's"
1542
) ;
1543
a.pull ( " origin" ) .unwrap ( ) ;
1544
1545
host.gc ( true ) .unwrap ( ) ;
1546
assert! ( host.acceptance_of ( & attest_ref) .unwrap ( ) .is_some ( ) ) ;
1547
f0a517d2 Andy McCall 18 days ago ↶
1548
let late = agent ( " late" , " agent:claude@ada" , now - 1_200_000 , now - 600_000 ) ;
1549
let r = late.push ( " origin" , false ) .unwrap ( ) ;
1550
assert! (
1551
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " may not update" ) ,
1552
" an expired certificate: {r:?}"
1553
) ;
1554
let other = agent ( " other" , " agent:mallory@ada" , now - 60_000 , now + 600_000 ) ;
1555
let r = other.push ( " origin" , false ) .unwrap ( ) ;
1556
assert! (
1557
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " may not update" ) ,
1558
" a subject not granted: {r:?}"
1559
) ;
1560
}
1561
d8524a9f Andy McCall 16 days ago ↶
1562
1563
1564
1565
1566
# [ test ]
1567
fn a_tag_signed_by_an_earlier_short_lived_key_is_pushed ( ) {
1568
use crate :: cert:: { Certificate, SignedCertificate} ;
1569
use crate :: identity:: Scope;
1570
hermetic ( ) ;
1571
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1572
let root = dir.path ( ) ;
1573
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1574
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1575
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1576
.unwrap ( )
1577
.with_home ( root.join ( " home-a" ) ) ;
1578
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1579
.unwrap ( ) ;
1580
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1581
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1582
a.policy_init ( ) .unwrap ( ) ;
1583
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1584
let hub_sk = SecretKey:: generate( ) ;
1585
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1586
a.policy_grant_subject (
1587
" agent:claude@ada" ,
1588
hub.clone ( ) ,
1589
Scope:: CHANGES | Scope:: REFS ,
1590
Some ( " *" ) ,
1591
)
1592
.unwrap ( ) ;
1593
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1594
1595
let now = Hlc:: now( ) .wall_ms;
1596
let mut agent = Repo:: clone( & url, & root.join ( " agent" ) )
1597
.unwrap ( )
1598
.with_home ( root.join ( " home-agent" ) ) ;
1599
agent.store ( ) .put_identity ( & hub) .unwrap ( ) ;
1600
let certify = | agent : & Repo, id | {
1601
let cert = SignedCertificate:: sign(
1602
Certificate {
1603
key: id,
1604
issuer: hub.hash ( ) ,
1605
subject: " agent:claude@ada" .into ( ) ,
1606
claims: vec! [ ] ,
1607
scope: Scope:: CHANGES | Scope:: REFS ,
1608
not_before: now - 60_000 ,
1609
expires: now + 600_000 ,
1610
grant: None ,
1611
} ,
1612
& hub,
1613
& hub_sk,
1614
) ;
1615
agent.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1616
} ;
1617
1618
let ( first, first_id) = agent
1619
.create_identity ( " claude" , " [email protected] " , IdentityScope:: User)
1620
.unwrap ( ) ;
1621
certify ( & agent, first_id) ;
1622
let heads = agent.bookmark_heads ( ) .unwrap ( ) ;
1623
agent.tag_create ( " v1" , & heads, " first" ) .unwrap ( ) ;
1624
1625
let ( second, second_id) = agent
1626
.create_identity ( " claude" , " [email protected] " , IdentityScope:: User)
1627
.unwrap ( ) ;
1628
assert_ne! ( first, second) ;
1629
certify ( & agent, second_id) ;
1630
agent
1631
.set_user_config ( " author.signer" , & second.to_hex ( ) )
1632
.unwrap ( ) ;
1633
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
1ec6fdbc Andy McCall 16 days ago ↶
1634
assert! (
1635
host.store ( ) .get_identity ( & first) .is_err ( ) ,
1636
" the host has never seen the tag's key"
1637
) ;
d8524a9f Andy McCall 16 days ago ↶
1638
let r = agent.push ( " origin" , false ) .unwrap ( ) ;
1ec6fdbc Andy McCall 16 days ago ↶
1639
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
1640
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
1641
assert! (
1642
host.tag_refs ( ) .unwrap ( ) .iter ( ) .any ( | t | t.name = = " v1" ) ,
1643
" the tag arrived"
1644
) ;
1645
}
1646
1647
1648
1649
1650
1651
1652
1653
# [ test ]
1654
fn a_short_lived_key_s_tag_is_fetched_at_its_issuer_s_acceptance ( ) {
1655
use crate :: cert:: { Certificate, SignedCertificate} ;
1656
use crate :: identity:: Scope;
1657
hermetic ( ) ;
1658
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1659
let root = dir.path ( ) ;
1660
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1661
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1662
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1663
.unwrap ( )
1664
.with_home ( root.join ( " home-a" ) ) ;
1665
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1666
.unwrap ( ) ;
1667
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1668
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1669
a.policy_init ( ) .unwrap ( ) ;
1670
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1671
let hub_sk = SecretKey:: generate( ) ;
1672
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1673
a.policy_grant_subject (
1674
" agent:claude@ada" ,
1675
hub.clone ( ) ,
1676
Scope:: CHANGES | Scope:: REFS ,
1677
Some ( " *" ) ,
1678
)
1679
.unwrap ( ) ;
1680
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1681
1682
let now = Hlc:: now( ) .wall_ms;
1683
1684
let agent = | name : & str , from : u64 , to : u64 | {
1685
let mut r = Repo:: clone( & url, & root.join ( name) )
1686
.unwrap ( )
1687
.with_home ( root.join ( format! ( " home-{name} " ) ) ) ;
1688
r.store ( ) .put_identity ( & hub) .unwrap ( ) ;
1689
let ( _ , id) = r
1690
.create_identity ( name, " [email protected] " , IdentityScope:: User)
1691
.unwrap ( ) ;
1692
let cert = SignedCertificate:: sign(
1693
Certificate {
1694
key: id,
1695
issuer: hub.hash ( ) ,
1696
subject: " agent:claude@ada" .into ( ) ,
1697
claims: vec! [ ] ,
1698
scope: Scope:: CHANGES | Scope:: REFS ,
1699
not_before: from,
1700
expires: to,
1701
grant: None ,
1702
} ,
1703
& hub,
1704
& hub_sk,
1705
) ;
1706
r.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1707
r
1708
} ;
1709
1710
1711
1712
1713
let claude = agent ( " claude" , now - 60_000 , now + 600_000 ) ;
1714
let heads = claude.bookmark_heads ( ) .unwrap ( ) ;
1715
claude.tag_create ( " v1" , & heads, " one" ) .unwrap ( ) ;
1716
let r = claude.push ( " origin" , false ) .unwrap ( ) ;
d8524a9f Andy McCall 16 days ago ↶
1717
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
1ec6fdbc Andy McCall 16 days ago ↶
1718
let b = Repo:: clone( & url, & root.join ( " b" ) )
1719
.unwrap ( )
1720
.with_home ( root.join ( " home-b" ) ) ;
1721
let rep = b.fetch ( " origin" ) .unwrap ( ) ;
1722
assert! (
1723
rep.skipped
1724
.iter ( )
1725
.any ( | ( n , why ) | n = = " tags/v1" & & why.contains ( " no time to check its certificate at" ) ) ,
1726
" {rep:?}"
1727
) ;
1728
assert_eq! ( b.tag_heads ( " v1" ) .unwrap ( ) , None ) ;
1729
1730
1731
1732
let host = Repo:: open( & root.join ( " origin" ) )
1733
.unwrap ( )
1734
.with_acceptor ( hub.clone ( ) , hub_sk.clone ( ) ) ;
1735
let ( v1, _ ) = host.tag_attest ( " v1" ) .unwrap ( ) .unwrap ( ) ;
1736
host.record_acceptance ( & v1) .unwrap ( ) ;
1737
let served = host.tag_refs ( ) .unwrap ( ) ;
1738
assert! ( served
1739
.iter ( )
1740
.any ( | t | t.name = = " v1" & & t.accepted.is_some ( ) ) ) ;
1741
let rep = b.fetch ( " origin" ) .unwrap ( ) ;
1742
assert! ( rep.skipped.is_empty ( ) , " {rep:?}" ) ;
1743
assert_eq! ( b.tag_heads ( " v1" ) .unwrap ( ) , Some ( heads.clone ( ) ) ) ;
1744
1745
host.gc ( true ) .unwrap ( ) ;
1746
assert! ( host.acceptance_of ( & v1) .unwrap ( ) .is_some ( ) ) ;
1747
1748
1749
1750
let late = agent ( " late" , now - 7_200_000 , now - 3_600_000 ) ;
1751
late.tag_create ( " v2" , & heads, " two" ) .unwrap ( ) ;
1752
let r = late.push ( " origin" , false ) .unwrap ( ) ;
1753
assert! (
1754
r.iter ( )
1755
.any ( | x | x.name = = " tags/v2" & & ! x.ok & & x.reason.contains ( " may not create" ) ) ,
1756
" {r:?}"
1757
) ;
1758
1759
1760
1761
let ( v2, _ ) = late.tag_attest ( " v2" ) .unwrap ( ) .unwrap ( ) ;
1762
let acc = Attest {
1763
repo: late.id ( ) .unwrap ( ) ,
1764
hlc: Hlc {
1765
wall_ms: now - 5_400_000 ,
1766
logical: 0 ,
1767
} ,
1768
body: AttestBody:: Accepted { attest: v2 } ,
1769
} ;
1770
let sig = hub_sk.sign ( Domain:: Attest, & hub.hash ( ) , & acc.hash ( ) ) ;
1771
1772
let stranger_sk = SecretKey:: generate( ) ;
1773
let stranger = stranger_sk.identity ( " stranger" , " [email protected] " , 1 ) ;
1774
let forged = stranger_sk.sign ( Domain:: Attest, & stranger.hash ( ) , & acc.hash ( ) ) ;
1775
late.keep_acceptance (
1776
& v2,
1777
& SignedAttest {
1778
attest: acc.clone ( ) ,
1779
sigs: vec! [ forged] ,
1780
} ,
1781
)
1782
.unwrap ( ) ;
1783
let r = late.push ( " origin" , false ) .unwrap ( ) ;
1784
assert! (
1785
r.iter ( ) .any ( | x | x.name = = " tags/v2" & & ! x.ok ) ,
1786
" not the issuer's: {r:?}"
1787
) ;
1788
let theirs = SignedAttest {
1789
attest: acc,
1790
sigs: vec! [ sig] ,
1791
} ;
1792
1793
late.keep_acceptance ( & v2, & theirs) .unwrap ( ) ;
1794
let r = late.push ( " origin" , false ) .unwrap ( ) ;
1795
1796
1797
assert! ( r.iter ( ) .any ( | x | x.name = = " tags/v2" & & x.ok ) , " {r:?}" ) ;
d8524a9f Andy McCall 16 days ago ↶
1798
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
1ec6fdbc Andy McCall 16 days ago ↶
1799
let ( hv2, _ ) = host.tag_attest ( " v2" ) .unwrap ( ) .unwrap ( ) ;
1800
assert_eq! ( hv2, v2) ;
1801
assert! ( host
1802
.store ( )
1803
.index_get_pub ( " accepted" , & v2)
1804
.unwrap ( )
1805
.contains ( & theirs.attest.hash ( ) ) ) ;
1806
let c = Repo:: clone( & url, & root.join ( " c" ) )
1807
.unwrap ( )
1808
.with_home ( root.join ( " home-c" ) ) ;
1809
assert_eq! ( c.tag_heads ( " v2" ) .unwrap ( ) , Some ( heads.clone ( ) ) ) ;
1810
assert_eq! ( c.tag_heads ( " v1" ) .unwrap ( ) , Some ( heads) ) ;
d8524a9f Andy McCall 16 days ago ↶
1811
}
1812
d36ba2f7 Andy McCall 17 days ago ↶
1813
1814
1815
1816
1817
1818
# [ test ]
1819
fn a_bookmark_moved_by_a_certified_key_alone_is_fetched ( ) {
1820
use crate :: cert:: { Certificate, SignedCertificate} ;
1821
use crate :: identity:: Scope;
1822
hermetic ( ) ;
1823
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1824
let root = dir.path ( ) ;
1825
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1826
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1827
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1828
.unwrap ( )
1829
.with_home ( root.join ( " home-a" ) ) ;
1830
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1831
.unwrap ( ) ;
1832
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1833
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1834
a.policy_init ( ) .unwrap ( ) ;
1835
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1836
let hub_sk = SecretKey:: generate( ) ;
1837
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1838
a.policy_grant_subject (
1839
" agent:claude@ada" ,
1840
hub.clone ( ) ,
1841
Scope:: CHANGES | Scope:: REFS ,
1842
None ,
1843
)
1844
.unwrap ( ) ;
1845
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1846
1847
let now = Hlc:: now( ) .wall_ms;
1848
let mut agent = Repo:: clone( & url, & root.join ( " agent" ) )
1849
.unwrap ( )
1850
.with_home ( root.join ( " home-agent" ) ) ;
1851
let ( key, id) = agent
1852
.create_identity ( " claude" , " [email protected] " , IdentityScope:: User)
1853
.unwrap ( ) ;
1854
let cert = SignedCertificate:: sign(
1855
Certificate {
1856
key: id,
1857
issuer: hub.hash ( ) ,
1858
subject: " agent:claude@ada" .into ( ) ,
1859
claims: vec! [ ] ,
1860
scope: Scope:: CHANGES | Scope:: REFS ,
1861
not_before: now - 60_000 ,
1862
expires: now + 600_000 ,
1863
grant: None ,
1864
} ,
1865
& hub,
1866
& hub_sk,
1867
) ;
1868
agent.store ( ) .put_identity ( & hub) .unwrap ( ) ;
1869
agent.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1870
let heads = agent.bookmark_heads ( ) .unwrap ( ) ;
1871
agent.bookmark_create ( " agent-work" , Some ( & heads) ) .unwrap ( ) ;
1872
let r = agent.push_bookmark ( " origin" , " agent-work" , false ) .unwrap ( ) ;
1873
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
1874
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
1875
let ( _ , sa) = host.latest_ref_attest ( " agent-work" ) .unwrap ( ) .unwrap ( ) ;
1876
assert_eq! ( sa.sigs[ 0 ] .key, key, " the certified key alone moved it" ) ;
1877
1878
1879
let bundle = host.bundle_raw_for ( & [ ] , None ) .unwrap ( ) ;
1880
assert! ( bundle.changes.is_empty ( ) & & bundle.raw_changes.is_empty ( ) ) ;
1881
assert_eq! ( bundle.certificates.len ( ) , 1 ) ;
1882
for k in [ key, hub.hash ( ) ] {
1883
assert! ( bundle
1884
.objects
1885
.iter ( )
1886
.any ( | ( t , b ) | * t = = ObjectType:: Identity & & hash_object ( * t, b) = = k) ) ;
1887
}
1888
1889
let c = Repo:: clone( & url, & root.join ( " c" ) ) .unwrap ( ) ;
1890
let tracked = c.view ( ) .unwrap ( ) .remotes[ " origin" ] .clone ( ) ;
1891
assert_eq! (
1892
tracked.get ( " agent-work" ) ,
1893
host.view ( ) .unwrap ( ) .bookmarks.get ( " agent-work" )
1894
) ;
1895
assert! ( c.store ( ) .certificate_of ( & key) .unwrap ( ) .is_some ( ) ) ;
1896
}
1897
028545df Andy McCall 17 days ago ↶
1898
1899
1900
1901
1902
1903
1904
# [ test ]
1905
fn a_fetched_attestation_is_judged_at_its_integrated_time ( ) {
1906
use super :: transparency:: LogCheck;
1907
use crate :: cert:: { Certificate, SignedCertificate} ;
1908
use crate :: identity:: Scope;
1909
use crate :: protocol:: { Integration, Logged, RefsResponse, RemoteRef} ;
1910
use crate :: tlog:: { self , Checkpoint, Entry, Hash, Integrated, Note} ;
1911
hermetic ( ) ;
1912
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1913
let root = dir.path ( ) ;
1914
let mut a = Repo:: init( & root.join ( " a" ) )
1915
.unwrap ( )
1916
.with_home ( root.join ( " home-a" ) ) ;
1917
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1918
.unwrap ( ) ;
1919
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1920
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1921
a.policy_init ( ) .unwrap ( ) ;
1922
let issuer_sk = SecretKey:: generate( ) ;
1923
let issuer = issuer_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1924
a.policy_grant_subject (
1925
" agent:claude@ada" ,
1926
issuer.clone ( ) ,
1927
Scope:: CHANGES | Scope:: REFS ,
1928
None ,
1929
)
1930
.unwrap ( ) ;
1931
let p = a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy;
1932
1933
1934
let now = Hlc:: now( ) .wall_ms;
1935
let t0 = now - 2 * 3_600_000 ;
1936
let agent_sk = SecretKey:: generate( ) ;
1937
let agent = agent_sk.identity ( " claude" , " [email protected] " , t0) ;
1938
let cert = SignedCertificate:: sign(
1939
Certificate {
1940
key: agent.clone ( ) ,
1941
issuer: issuer.hash ( ) ,
1942
subject: " agent:claude@ada" .into ( ) ,
1943
claims: vec! [ ] ,
1944
scope: Scope:: CHANGES | Scope:: REFS ,
1945
not_before: t0,
1946
expires: t0 + 600_000 ,
1947
grant: None ,
1948
} ,
1949
& issuer,
1950
& issuer_sk,
1951
) ;
1952
a.store ( ) .put_identity ( & agent) .unwrap ( ) ;
1953
a.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1954
let signer = agent.hash ( ) ;
1955
let attested = | claimed : u64 | {
1956
let attest = Attest {
1957
repo: a.id ( ) .unwrap ( ) ,
1958
hlc: Hlc {
1959
wall_ms: claimed,
1960
logical: 0 ,
1961
} ,
1962
body: AttestBody:: Ref {
1963
name: " main" .into ( ) ,
1964
old_state: ObjectRef( [ 1 ; 32 ] ) ,
1965
new_state: ObjectRef( [ 2 ; 32 ] ) ,
1966
serial: 1 ,
1967
} ,
1968
} ;
1969
let sig = agent_sk.sign ( Domain:: Attest, & signer, & attest.hash ( ) ) ;
1970
SignedAttest {
1971
attest,
1972
sigs: vec! [ sig] ,
1973
}
1974
} ;
1975
1976
1977
1978
let log_sk = SecretKey:: generate( ) ;
1979
let log_key = log_sk.identity ( " hub.example" , " [email protected] " , 1 ) ;
1980
let origin = " hub.example/log" ;
1981
let hub = " https://hub.example" ;
1982
let url = " https://hub.example/ada/a" ;
1983
let w_sk = SecretKey:: generate( ) ;
1984
let w = w_sk.identity ( " w" , " [email protected] " , 1 ) ;
1985
let stranger_sk = SecretKey:: generate( ) ;
1986
let stranger = stranger_sk.identity ( " s" , " [email protected] " , 1 ) ;
1987
let sa = attested ( t0 + 300_000 ) ;
1988
let entry_of = | i : u64 | {
1989
let ( kind, object) = if i = = 3 {
1990
( " ref" , sa.attest.hash ( ) )
1991
} else {
1992
( " seal" , ObjectRef( [ i as u8 ; 32 ] ) )
1993
} ;
1994
Entry {
1995
kind: kind.into ( ) ,
1996
repo: sa.attest.repo,
1997
object,
1998
signers: vec! [ signer] ,
1999
received: t0 + 300_000 ,
2000
}
2001
.encode ( )
2002
} ;
2003
let mut stored: Vec< Hash> = Vec :: new( ) ;
2004
for i in 0 .. 8 {
2005
let add = tlog:: stored_hashes_for_leaf( i, tlog:: leaf_hash( & entry_of ( i) ) , & stored) .unwrap ( ) ;
2006
stored.extend ( add) ;
2007
}
2008
let note_of = | n : u64 , cosigners : & [( & str , & Identity, & SecretKey, u64 ) ]| {
2009
let mut note = Checkpoint {
2010
origin: origin.into ( ) ,
2011
size: n,
2012
root: tlog:: tree_hash( n, & stored) .unwrap ( ) ,
2013
extensions: vec! [ ] ,
2014
}
2015
.sign ( & log_key, & log_sk)
2016
.unwrap ( ) ;
2017
for ( name, id, sk, t) in cosigners {
2018
note.sigs
2019
.extend ( tlog:: cosign( & note.text, name, id, sk, * t) .unwrap ( ) ) ;
2020
}
2021
note.encode ( )
2022
} ;
2023
let latest = note_of ( 8 , & [ ] ) ;
2024
let witnessed_at = ( t0 + 360_000 ) / 1000 ;
2025
let cosigned = note_of (
2026
4 ,
2027
& [
2028
( " w.example" , & w, & w_sk, witnessed_at) ,
2029
( " s.example" , & stranger, & stranger_sk, 1 ) ,
2030
] ,
2031
) ;
2032
let answer = | integration : Vec< Integration> , witnesses : Vec< ( String, Vec< u8 > ) > | RefsResponse {
2033
refs: vec! [ RemoteRef {
2034
name: " main" .into ( ) ,
2035
state: ObjectRef( [ 2 ; 32 ] ) ,
2036
heads: vec! [ ] ,
2037
attestation: Some ( sa.clone ( ) ) ,
2038
last_serial: None ,
2039
accepted: None ,
2040
logged: Some ( Logged {
2041
index: 3 ,
2042
entry: entry_of ( 3 ) ,
2043
proof: tlog:: prove_inclusion( 8 , 3 , & stored) .unwrap ( ) ,
2044
} ) ,
2045
integration,
2046
} ] ,
2047
log: Some ( latest.clone ( ) ) ,
2048
witnesses,
2049
.. RefsResponse:: default( )
2050
} ;
2051
let proof4 = tlog:: prove_inclusion( 4 , 3 , & stored) .unwrap ( ) ;
2052
let served = vec! [ Integration {
2053
note: cosigned.clone ( ) ,
2054
proof: proof4.clone ( ) ,
2055
} ] ;
2056
let listed = vec! [ ( " w.example" .to_string ( ) , w.encode ( ) ) ] ;
2057
2058
a.remember_log ( hub, & log_key, & Note:: parse( & latest) .unwrap ( ) )
2059
.unwrap ( ) ;
2060
2061
2062
2063
let resp = answer ( served.clone ( ) , listed.clone ( ) ) ;
2064
let lc = a.check_log ( url, None , & resp) .unwrap ( ) ;
2065
assert! ( lc.notes.is_empty ( ) , " {:?}" , lc.notes) ;
2066
let t = lc.time_of ( " main" ) .unwrap ( ) ;
2067
assert_eq! ( ( t.time, t.witness.as_str ( ) ) , ( witnessed_at, " w.example" ) ) ;
2068
2069
2070
assert! ( cert.cert.expires < now) ;
2071
let mut notes = Vec :: new( ) ;
2072
a.authorize_in_time ( & p, " origin" , & resp.refs[ 0 ] , & sa, & signer, & lc, & mut notes)
2073
.unwrap ( ) ;
2074
assert! ( notes.is_empty ( ) , " {notes:?}" ) ;
2075
2076
2077
2078
let lonely = vec! [ Integration {
2079
note: note_of ( 4 , & [ ( " s.example" , & stranger, & stranger_sk, 1 ) ] ) ,
2080
proof: proof4.clone ( ) ,
2081
} ] ;
2082
let resp = answer ( lonely, vec! [ ] ) ;
2083
let lc = a.check_log ( url, None , & resp) .unwrap ( ) ;
2084
assert! ( lc
2085
.time_of ( " main" )
2086
.unwrap_err ( )
2087
.contains ( " no witness this user trusts" ) ) ;
2088
let mut notes = Vec :: new( ) ;
2089
a.authorize_in_time ( & p, " origin" , & resp.refs[ 0 ] , & sa, & signer, & lc, & mut notes)
2090
.unwrap ( ) ;
2091
assert! (
2092
notes.len ( ) = = 1 & & notes[ 0 ] .contains ( " not at a witnessed time" ) ,
2093
" {notes:?}"
2094
) ;
2095
2096
2097
2098
let resp = answer (
2099
served.clone ( ) ,
2100
vec! [ ( " w.example" .into ( ) , stranger.encode ( ) ) ] ,
2101
) ;
2102
let lc = a.check_log ( url, None , & resp) .unwrap ( ) ;
2103
assert! (
2104
lc.notes.iter ( ) .any ( | n | n.contains ( " another key" ) ) ,
2105
" {:?}" ,
2106
lc.notes
2107
) ;
2108
assert! (
2109
lc.time_of ( " main" ) .is_err ( ) ,
2110
" w's cosignature is by its old key"
2111
) ;
2112
a.check_log ( url, None , & answer ( served.clone ( ) , listed.clone ( ) ) )
2113
.unwrap ( ) ;
2114
2115
2116
2117
let mut old = answer ( vec! [ ] , vec! [ ] ) ;
2118
old.log = None ;
2119
old.refs[ 0 ] .logged = None ;
2120
let lc = a.check_log ( url, None , & old) .unwrap ( ) ;
2121
assert! ( ! lc.kept & & lc.notes.is_empty ( ) ) ;
2122
let mut notes = Vec :: new( ) ;
2123
a.authorize_in_time ( & p, " origin" , & old.refs[ 0 ] , & sa, & signer, & lc, & mut notes)
2124
.unwrap ( ) ;
2125
assert! ( notes.is_empty ( ) , " {notes:?}" ) ;
2126
2127
2128
let at = | secs_after_t0 : u64 | {
2129
let mut times = std:: collections:: HashMap:: new( ) ;
2130
times.insert (
2131
" main" .to_string ( ) ,
2132
Ok ( Integrated {
2133
time: ( t0 + secs_after_t0 * 1000 ) / 1000 ,
2134
witness: " w.example" .into ( ) ,
2135
size: 4 ,
2136
} ) ,
2137
) ;
2138
LogCheck {
2139
kept: true ,
2140
times,
2141
.. LogCheck:: default( )
2142
}
2143
} ;
2144
let judged = | sa : & SignedAttest, lc : & LogCheck| {
2145
let mut notes = Vec :: new( ) ;
2146
a.authorize_in_time ( & p, " origin" , & old.refs[ 0 ] , sa, & signer, lc, & mut notes)
2147
.map ( | _| notes )
2148
} ;
2149
2150
2151
2152
let notes = judged ( & sa, & at ( 3600 ) ) .unwrap ( ) ;
2153
assert! (
2154
notes
2155
.iter ( )
2156
.any ( | n | n.contains ( " had lapsed when witness w.example" ) )
2157
& & notes
2158
.iter ( )
2159
.any ( | n | n.contains ( " 55 minutes before witness" ) ) ,
2160
" {notes:?}"
2161
) ;
2162
2163
2164
2165
let fast = attested ( now) ;
2166
let notes = judged ( & fast, & at ( 400 ) ) .unwrap ( ) ;
2167
assert! (
2168
notes.len ( ) = = 1 & & notes[ 0 ] .contains ( " after witness w.example saw it" ) ,
2169
" {notes:?}"
2170
) ;
2171
2172
assert! ( judged ( & fast, & at ( 3600 ) ) .is_err ( ) ) ;
2173
2174
assert! ( judged ( & sa, & at ( 500 ) ) .unwrap ( ) .is_empty ( ) ) ;
2175
}
2176
a2387d54 Andy McCall 18 days ago ↶
2177
2178
2179
2180
45a6f5ec Andy McCall 11 days ago ↶
2181
2182
2183
f0a517d2 Andy McCall 18 days ago ↶
2184
# [ test ]
45a6f5ec Andy McCall 11 days ago ↶
2185
fn a_grant_outlives_its_owner_s_rotation ( ) {
2186
use crate :: cert:: { Certificate, Grant, SignedCertificate, SignedGrant} ;
2187
use crate :: identity:: Scope;
2188
hermetic ( ) ;
2189
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2190
let root = dir.path ( ) ;
2191
let mut a = Repo:: init( & root.join ( " a" ) )
2192
.unwrap ( )
2193
.with_home ( root.join ( " home-a" ) ) ;
2194
let ( old, _ ) = a
2195
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
2196
.unwrap ( ) ;
2197
let ( _ , old_sk) = a.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
2198
let old_id = a.identity ( & old) .unwrap ( ) ;
2199
let hub_sk = SecretKey:: generate( ) ;
2200
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
2201
let now = Hlc:: now( ) .wall_ms;
2202
let grant = | from : u64 | {
2203
SignedGrant:: sign(
2204
Grant {
2205
grantor: old,
2206
subject: " agent:claude@ada" .into ( ) ,
2207
issuer: hub.hash ( ) ,
2208
scope: Scope:: CHANGES | Scope:: SEAL ,
2209
not_before: from,
2210
expires: now + 86_400_000 ,
2211
} ,
2212
& old_id,
2213
& old_sk,
2214
)
2215
} ;
2216
let before = grant ( now - 60_000 ) ;
2217
let after = grant ( now + 60_000 ) ;
2218
let rot = a.renew_identity ( None ) .unwrap ( ) ;
2219
assert_ne! ( rot.new, old) ;
2220
let agent = | name : & str , g : SignedGrant| {
2221
let mut r = Repo:: init( & root.join ( name) )
2222
.unwrap ( )
2223
.with_home ( root.join ( format! ( " home-{name} " ) ) ) ;
2224
let ( key, id) = r
2225
.create_identity ( name, " [email protected] " , IdentityScope:: User)
2226
.unwrap ( ) ;
2227
for i in [ & old_id, & rot.identity, & hub] {
2228
r.store ( ) .put_identity ( i) .unwrap ( ) ;
2229
}
2230
r.set_user_config ( " author.key" , & rot.new.to_hex ( ) ) .unwrap ( ) ;
2231
r.set_user_config ( " author.signer" , & key.to_hex ( ) ) .unwrap ( ) ;
2232
let cert = SignedCertificate:: sign(
2233
Certificate {
2234
key: id,
2235
issuer: hub.hash ( ) ,
2236
subject: " agent:claude@ada" .into ( ) ,
2237
claims: vec! [ ] ,
2238
scope: Scope:: CHANGES | Scope:: REFS | Scope:: SEAL ,
2239
not_before: now - 60_000 ,
2240
expires: now + 600_000 ,
2241
grant: Some ( g) ,
2242
} ,
2243
& hub,
2244
& hub_sk,
2245
) ;
2246
r.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
2247
write ( & root.join ( name) , " work.txt" , b " agent work\n " ) ;
2248
let h = r.commit ( " work" , & [ ] ) .unwrap ( ) ;
2249
assert_eq! ( r.store ( ) .get_change_meta ( & h) .unwrap ( ) .author, rot.new) ;
2250
r.seal ( & [ h] ) .unwrap ( ) ;
2251
( r, h)
2252
} ;
2253
let ( r, h) = agent ( " kept" , before) ;
2254
assert! (
2255
matches! (
2256
r.authenticate_change ( & h) .unwrap ( ) ,
2257
SignatureStatus:: Valid { .. }
2258
) ,
2259
" {:?}" ,
2260
r.authenticate_change ( & h) .unwrap ( )
2261
) ;
2262
let ( r, h) = agent ( " late" , after) ;
2263
assert! (
2264
matches! ( r.authenticate_change ( & h) .unwrap ( ) , SignatureStatus:: Invalid( e) if e.contains ( " neither" ) ) ,
2265
" a grant the old key made after the rotation: {:?}" ,
2266
r.authenticate_change ( & h) .unwrap ( )
2267
) ;
2268
}
2269
2270
# [ test ]
a2387d54 Andy McCall 18 days ago ↶
2271
fn a_certified_key_acts_for_its_author_under_a_grant ( ) {
2272
use crate :: cert:: { Certificate, Grant, SignedCertificate, SignedGrant} ;
2273
use crate :: identity:: { Identity, Scope} ;
2274
hermetic ( ) ;
2275
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2276
let root = dir.path ( ) ;
2277
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
2278
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
2279
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
2280
.unwrap ( )
2281
.with_home ( root.join ( " home-a" ) ) ;
2282
let ( ada, _ ) = a
2283
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
2284
.unwrap ( ) ;
2285
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
2286
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
2287
a.policy_init ( ) .unwrap ( ) ;
2288
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
2289
let hub_sk = SecretKey:: generate( ) ;
2290
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
2291
a.policy_grant_subject (
2292
" agent:claude@ada" ,
2293
hub.clone ( ) ,
2294
Scope:: CHANGES | Scope:: REFS ,
2295
None ,
2296
)
2297
.unwrap ( ) ;
2298
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
2299
let ( _ , ada_sk) = a.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
2300
let ada_id = a.identity ( & ada) .unwrap ( ) ;
2301
2302
let now = Hlc:: now( ) .wall_ms;
2303
let grant = | by : & Identity, sk : & SecretKey, from : u64 , to : u64 | {
2304
SignedGrant:: sign(
2305
Grant {
2306
grantor: by.hash ( ) ,
2307
subject: " agent:claude@ada" .into ( ) ,
2308
issuer: hub.hash ( ) ,
2309
scope: Scope:: CHANGES | Scope:: SEAL ,
2310
not_before: from,
2311
expires: to,
2312
} ,
2313
by,
2314
sk,
2315
)
2316
} ;
2317
2318
2319
let agent = | name : & str , g : Option< SignedGrant> | {
2320
let mut r = Repo:: clone( & url, & root.join ( name) )
2321
.unwrap ( )
2322
.with_home ( root.join ( format! ( " home-{name} " ) ) ) ;
2323
let ( key, id) = r
2324
.create_identity ( name, " [email protected] " , IdentityScope:: User)
2325
.unwrap ( ) ;
2326
r.set_user_config ( " author.key" , & ada.to_hex ( ) ) .unwrap ( ) ;
2327
r.set_user_config ( " author.signer" , & key.to_hex ( ) ) .unwrap ( ) ;
2328
let cert = SignedCertificate:: sign(
2329
Certificate {
2330
key: id,
2331
issuer: hub.hash ( ) ,
2332
subject: " agent:claude@ada" .into ( ) ,
2333
claims: vec! [ ] ,
2334
scope: Scope:: CHANGES | Scope:: REFS | Scope:: SEAL ,
2335
not_before: now - 60_000 ,
2336
expires: now + 600_000 ,
2337
grant: g,
2338
} ,
2339
& hub,
2340
& hub_sk,
2341
) ;
2342
r.store ( ) .put_identity ( & hub) .unwrap ( ) ;
2343
r.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
2344
write ( & root.join ( name) , & format! ( " {name} .txt" ) , b " agent work\n " ) ;
2345
let h = r.commit ( " work" , & [ ] ) .unwrap ( ) ;
2346
assert_eq! ( r.store ( ) .get_change_meta ( & h) .unwrap ( ) .author, ada) ;
2347
( r, key, h)
2348
} ;
2349
2350
let ( claude, key, h) = agent (
2351
" claude" ,
2352
Some ( grant ( & ada_id, & ada_sk, now - 60_000 , now + 86_400_000 ) ) ,
2353
) ;
2354
let r = claude.push ( " origin" , false ) .unwrap ( ) ;
2355
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
2356
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
68dcf21f Andy McCall 18 days ago ↶
2357
assert! (
2358
matches! (
2359
host.authenticate_change ( & h) .unwrap ( ) ,
2360
SignatureStatus:: Valid { key: k, sealed: true , name, .. } if k = = key & & name = = " agent:claude@ada"
2361
) ,
2362
" named by its certificate's subject"
2363
) ;
a2387d54 Andy McCall 18 days ago ↶
2364
a.pull ( " origin" ) .unwrap ( ) ;
2365
assert! ( matches! (
2366
a.authenticate_change ( & h) .unwrap ( ) ,
2367
SignatureStatus:: Valid { key: k, .. } if k = = key
2368
) ) ;
2369
2370
let ( none, _ , _ ) = agent ( " none" , None ) ;
2371
let r = none.push ( " origin" , false ) .unwrap ( ) ;
2372
assert! ( r.iter ( ) .all ( | x | ! x.ok) , " no grant: {r:?}" ) ;
2373
let mallory_sk = SecretKey:: generate( ) ;
2374
let mallory = mallory_sk.identity ( " Mallory" , " [email protected] " , 1 ) ;
2375
let ( stranger, _ , _ ) = agent (
2376
" stranger" ,
2377
Some ( grant ( & mallory, & mallory_sk, now - 60_000 , now + 600_000 ) ) ,
2378
) ;
2379
let r = stranger.push ( " origin" , false ) .unwrap ( ) ;
2380
assert! ( r.iter ( ) .all ( | x | ! x.ok) , " a stranger's grant: {r:?}" ) ;
2381
let ( ended, _ , eh) = agent (
2382
" ended" ,
2383
Some ( grant ( & ada_id, & ada_sk, now - 7_200_000 , now - 3_600_000 ) ) ,
2384
) ;
2385
let r = ended.push ( " origin" , false ) .unwrap ( ) ;
2386
assert! ( r.iter ( ) .all ( | x | ! x.ok) , " an ended grant: {r:?}" ) ;
2387
assert! ( matches! (
2388
ended.authenticate_change ( & eh) .unwrap ( ) ,
2389
SignatureStatus:: Invalid( e) if e.contains ( " grant did not cover" )
2390
) ) ;
2391
}
2392
2393
# [ test ]
83e0677b Andy McCall 1 month ago ↶
2394
fn trust_policy_governs_pushes_and_fetches ( ) {
2395
hermetic ( ) ;
2396
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2397
let root = dir.path ( ) ;
2398
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
2399
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
2400
2401
75167672 Andy McCall 1 month ago
2402
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
2403
.unwrap ( )
2404
.with_home ( root.join ( " home-a" ) ) ;
2405
let ( key_a, _ ) = a
2406
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
2407
.unwrap ( ) ;
2408
let mut b = Repo:: clone( & url, & root.join ( " b" ) )
2409
.unwrap ( )
2410
.with_home ( root.join ( " home-b" ) ) ;
83e0677b Andy McCall 1 month ago ↶
2411
write ( & root.join ( " b" ) , " b.txt" , b " b\n " ) ;
2412
b.commit ( " unsigned before policy" , & [ ] ) .unwrap ( ) ;
2413
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
2414
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2415
2416
2417
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
2418
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
2419
let sp = a.policy_init ( ) .unwrap ( ) ;
2420
assert_eq! ( sp.policy.serial, 1 ) ;
2421
a.pull ( " origin" ) .unwrap ( ) ;
2422
let results = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
2423
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2424
assert! ( results.iter ( ) .all ( | r | r.ok ) , " {results:?}" ) ;
2425
assert_eq! ( origin.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 1 ) ;
2426
2427
2428
b.pull ( " origin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2429
assert! (
2430
b.current_policy ( ) .unwrap ( ) .is_some ( ) ,
2431
" the policy arrived with the fetch"
2432
) ;
83e0677b Andy McCall 1 month ago ↶
2433
assert! ( b.push ( " origin" , false ) .is_err ( ) ) ;
2434
75167672 Andy McCall 1 month ago
2435
let ( key_b, id_b) = b
2436
.create_identity ( " Bob" , " [email protected] " , IdentityScope:: User)
2437
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2438
write ( & root.join ( " b" ) , " b.txt" , b " b2\n " ) ;
2439
let h_b2 = b.commit ( " b2 as Bob" , & [ ] ) .unwrap ( ) ;
2440
assert_eq! ( b.store ( ) .get_change ( & h_b2) .unwrap ( ) .author, key_b) ;
2441
let r = b.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
2442
origin.refresh ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2443
assert! (
2444
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " may not update" ) ,
2445
" {:?}" ,
2446
r
2447
) ;
83e0677b Andy McCall 1 month ago ↶
2448
2449
2450
let sp = a.policy_grant ( id_b.clone ( ) , None ) .unwrap ( ) ;
2451
assert_eq! ( sp.policy.serial, 2 ) ;
2452
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
2453
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2454
assert! ( r.iter ( ) .any ( | x | x.name = = " policy" & & x.ok ) , " {r:?}" ) ;
2455
75167672 Andy McCall 1 month ago
2456
assert! ( b
2457
.push ( " origin" , false )
2458
.unwrap_or_else ( | e | vec! [ RefResult {
2459
name: " main" .into ( ) ,
2460
ok: false ,
2461
reason: e.to_string ( )
2462
} ] )
2463
.iter ( )
2464
.all ( | x | ! x.ok) ) ;
83e0677b Andy McCall 1 month ago ↶
2465
b.pull ( " origin" ) .unwrap ( ) ;
2466
assert_eq! ( b.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 2 ) ;
2467
let r = b.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
2468
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2469
assert! ( r[ 0 ] .ok, " {:?}" , r) ;
2470
2471
let heads = origin.bookmark_heads ( ) .unwrap ( ) ;
2472
for h in & heads {
2473
let c = origin.store ( ) .get_change ( h) .unwrap ( ) ;
2474
if c.author = = key_b {
75167672 Andy McCall 1 month ago
2475
origin
2476
.change_allowed (
2477
h,
2478
& origin.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy,
2479
" main" ,
2480
)
2481
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2482
}
2483
}
2484
2485
2486
let mut forged = b.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.clone ( ) ;
2487
forged.serial = 3 ;
2488
forged.roots = vec! [ key_b] ;
2489
let fh = forged.hash ( ) ;
2490
let ( _ , sk_b) = b.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2491
let forged = SignedPolicy {
2492
policy: forged,
2493
sigs: vec! [ sk_b.sign ( Domain:: Policy, & key_b, & fh) ] ,
2494
} ;
2495
let r = origin
2496
.receive_push ( Bundle:: default( ) , vec! [ ] , Some ( forged) , vec! [ ] )
2497
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2498
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " root" ) , " {:?}" , r) ;
2499
let stale = a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1 ;
2500
let mut old = stale.clone ( ) ;
2501
old.policy.serial = 1 ;
75167672 Andy McCall 1 month ago
2502
let r = origin
2503
.receive_push ( Bundle:: default( ) , vec! [ ] , Some ( old) , vec! [ ] )
2504
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2505
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " serial" ) , " {:?}" , r) ;
2506
2507
2508
a.pull ( " origin" ) .unwrap ( ) ;
2509
a.policy_revoke ( & key_b) .unwrap ( ) ;
2510
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
2511
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2512
assert! ( r.iter ( ) .any ( | x | x.name = = " policy" & & x.ok ) , " {r:?}" ) ;
2513
b.pull ( " origin" ) .unwrap ( ) ;
2514
write ( & root.join ( " b" ) , " b.txt" , b " b3\n " ) ;
2515
b.commit ( " b3" , & [ ] ) .unwrap ( ) ;
2516
let r = b.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
2517
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2518
assert! ( ! r[ 0 ] .ok, " {:?}" , r) ;
2519
2520
75167672 Andy McCall 1 month ago
2521
let c = Repo:: clone( & url, & root.join ( " c" ) )
2522
.unwrap ( )
2523
.with_home ( root.join ( " home-c" ) ) ;
83e0677b Andy McCall 1 month ago ↶
2524
assert_eq! ( c.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 3 ) ;
75167672 Andy McCall 1 month ago
2525
assert! ( c
2526
.current_policy ( )
2527
.unwrap ( )
2528
.unwrap ( )
2529
.1
2530
.policy
2531
.roots
2532
.contains ( & key_a) ) ;
83e0677b Andy McCall 1 month ago ↶
2533
assert! ( ! c.snapshot ( " status" ) .unwrap ( ) .changed) ;
2534
}
d02e545d Andy McCall 1 month ago ↶
2535
9ae4d15a Andy McCall 1 month ago ↶
2536
d02e545d Andy McCall 1 month ago ↶
2537
# [ test ]
9ae4d15a Andy McCall 1 month ago ↶
2538
fn gc_reports_its_phases ( ) {
2539
hermetic ( ) ;
2540
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2541
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
2542
for i in 0 .. 3 {
2543
write (
2544
dir.path ( ) ,
2545
& format! ( " f{i} .txt" ) ,
2546
format! ( " {i} \n " ) .as_bytes ( ) ,
2547
) ;
2548
repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ;
2549
}
2550
let mut phases = Vec :: new( ) ;
2551
repo.gc_with ( true , & mut | p| phases.push ( p.to_string ( ) ) )
2552
.unwrap ( ) ;
2553
assert_eq! ( phases[ 0 ] , " finding what is reachable" , " {phases:?}" ) ;
2554
assert! (
2555
phases.iter ( ) .any ( | p | p.starts_with ( " pruning" ) ) ,
2556
" {phases:?}"
2557
) ;
2558
assert! (
2559
phases.iter ( ) .any ( | p | p.starts_with ( " packing 0 of " ) ) ,
2560
" {phases:?}"
2561
) ;
2562
assert! (
2563
phases
2564
.iter ( )
2565
.any ( | p | p.starts_with ( " writing the index of " ) ) ,
2566
" {phases:?}"
2567
) ;
2568
}
2569
2570
# [ test ]
d02e545d Andy McCall 1 month ago ↶
2571
fn gc_packs_objects_and_history_still_works ( ) {
2572
hermetic ( ) ;
2573
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2574
let root = dir.path ( ) ;
2575
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
2576
repo.create_identity ( " Ada" , " a@b" , IdentityScope:: User)
2577
.unwrap ( ) ;
d02e545d Andy McCall 1 month ago ↶
2578
repo.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
2579
write ( root, " a.txt" , b " one\n " ) ;
2580
let h1 = repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
2581
write ( root, " a.txt" , b " two\n " ) ;
2582
write ( root, " big.bin" , & vec! [ 0 u8 ; 100_000 ] ) ;
2583
let h2 = repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
ba160f60 Andy McCall 1 month ago ↶
2584
2585
for i in 0 .. 5 {
2586
write ( root, " a.txt" , format! ( " two\n {i} \n " ) .as_bytes ( ) ) ;
2587
repo.snapshot ( " status" ) .unwrap ( ) ;
2588
}
2589
write ( root, " a.txt" , b " two\n " ) ;
2590
repo.snapshot ( " status" ) .unwrap ( ) ;
d02e545d Andy McCall 1 month ago ↶
2591
let before = repo.store ( ) .list ( ) .unwrap ( ) ;
2592
assert! ( repo.store ( ) .loose_count ( ) .unwrap ( ) > 0 ) ;
ba160f60 Andy McCall 1 month ago ↶
2593
2594
2595
let r = repo.gc ( true ) .unwrap ( ) ;
2596
assert_eq! ( r.packed.objects, before.len ( ) ) ;
2597
assert_eq! ( r.pruned.objects, 0 ) ;
d02e545d Andy McCall 1 month ago ↶
2598
assert_eq! ( repo.store ( ) .loose_count ( ) .unwrap ( ) , 0 ) ;
2599
assert_eq! ( repo.store ( ) .list ( ) .unwrap ( ) , before) ;
ba160f60 Andy McCall 1 month ago ↶
2600
2601
2602
2603
repo.config.set ( " core.op-retention" , " 0" ) .unwrap ( ) ;
2604
repo.save_config ( ) .unwrap ( ) ;
21b2aec2 Andy McCall 1 month ago ↶
2605
let mut repo = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
ba160f60 Andy McCall 1 month ago ↶
2606
let r = repo.gc ( true ) .unwrap ( ) ;
2607
assert! ( r.pruned.objects > = 5 , " {r:?}" ) ;
2608
let after = repo.store ( ) .list ( ) .unwrap ( ) ;
2609
assert! ( after.len ( ) < before.len ( ) ) ;
2610
assert! ( after.contains ( & h1) & & after.contains ( & h2) ) ;
2611
for ( _ , op) in repo.op_log ( ) .unwrap ( ) {
2612
assert! ( ! op.command.is_empty ( ) , " operation objects are all kept" ) ;
2613
}
2614
d02e545d Andy McCall 1 month ago ↶
2615
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
75167672 Andy McCall 1 month ago
2616
assert! ( matches! (
2617
repo.verify_change ( & h2) .unwrap ( ) ,
2618
SignatureStatus:: Valid { .. }
2619
) ) ;
d02e545d Andy McCall 1 month ago ↶
2620
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 3 ) ;
2621
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
2622
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n " ) ;
2623
repo.checkout ( & [ h2] , false , " checkout" ) .unwrap ( ) ;
2624
assert_eq! ( fs:: read( root.join ( " big.bin" ) ) .unwrap ( ) .len ( ) , 100_000 ) ;
2625
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
ba160f60 Andy McCall 1 month ago ↶
2626
d02e545d Andy McCall 1 month ago ↶
2627
write ( root, " a.txt" , b " three\n " ) ;
2628
repo.commit ( " three" , & [ ] ) .unwrap ( ) ;
ba160f60 Andy McCall 1 month ago ↶
2629
let r = repo.gc ( false ) .unwrap ( ) ;
2630
assert_eq! ( r.pruned.objects, 0 ) ;
2631
assert! ( r.pruned.deferred > 0 ) ;
2632
let r = repo.gc ( true ) .unwrap ( ) ;
2633
assert! ( r.pruned.objects > 0 ) ;
2634
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 ) ;
21b2aec2 Andy McCall 1 month ago ↶
2635
2636
2637
repo.config.set ( " core.op-retention" , " 90" ) .unwrap ( ) ;
2638
repo.save_config ( ) .unwrap ( ) ;
2639
let repo = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2640
let r = repo.gc ( false ) .unwrap ( ) ;
2641
assert_eq! ( r.pruned.objects, 0 ) ;
2642
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 ) ;
d02e545d Andy McCall 1 month ago ↶
2643
}
614541c3 Andy McCall 1 month ago ↶
2644
2645
# [ test ]
2646
fn describe_amend_abandon_heads ( ) {
2647
hermetic ( ) ;
2648
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2649
let root = dir.path ( ) ;
2650
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
2651
repo.create_identity ( " Ada" , " a@b" , IdentityScope:: User)
2652
.unwrap ( ) ;
614541c3 Andy McCall 1 month ago ↶
2653
repo.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
2654
write ( root, " a.txt" , b " one\n " ) ;
2655
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
2656
write ( root, " a.txt" , b " one\n two\n " ) ;
2657
let h2 = repo.commit ( " secnod" , & [ ] ) .unwrap ( ) ;
2658
let id2 = repo.store ( ) .get_change ( & h2) .unwrap ( ) .change_id;
2659
2660
2661
let h2b = repo.describe ( & h2, " second" ) .unwrap ( ) ;
2662
assert_ne! ( h2b, h2) ;
2663
let c = repo.store ( ) .get_change ( & h2b) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2664
assert_eq! (
2665
( c.change_id, c.message.as_str ( ) , c.deps.clone ( ) ) ,
2666
( id2, " second" , vec! [ h1] )
2667
) ;
614541c3 Andy McCall 1 month ago ↶
2668
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2b] ) ;
75167672 Andy McCall 1 month ago
2669
assert! ( matches! (
2670
repo.verify_change ( & h2b) .unwrap ( ) ,
2671
SignatureStatus:: Valid { .. }
2672
) ) ;
614541c3 Andy McCall 1 month ago ↶
2673
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
2674
assert! ( s.statuses.is_empty ( ) , " {:?}" , s.statuses) ;
75167672 Andy McCall 1 month ago
2675
assert_eq! (
2676
repo.resolve_change ( & id2.short ( 8 ) ) .unwrap ( ) ,
2677
h2b,
2678
" the old object is no longer reachable from the log"
2679
) ;
c07e0e7d Andy McCall 1 month ago ↶
2680
2681
let h1b = repo.describe ( & h1, " first, described" ) .unwrap ( ) ;
2682
assert_ne! ( h1b, h1) ;
2683
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
2684
assert_eq! ( heads.len ( ) , 1 ) ;
2685
let top = repo.store ( ) .get_change ( & heads[ 0 ] ) .unwrap ( ) ;
2686
assert_eq! ( ( top.change_id, top.deps.clone ( ) ) , ( id2, vec! [ h1b] ) ) ;
75167672 Andy McCall 1 month ago
2687
assert! (
2688
matches! (
2689
repo.verify_change ( & heads[ 0 ] ) .unwrap ( ) ,
2690
SignatureStatus:: Valid { .. }
2691
) ,
2692
" rewritten own change is re-signed"
2693
) ;
c07e0e7d Andy McCall 1 month ago ↶
2694
let ( _ , wc) = repo.working_copy ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2695
assert_eq! (
2696
wc.deps,
2697
vec! [ heads[ 0 ] ] ,
2698
" the working copy follows the rewrite"
2699
) ;
c07e0e7d Andy McCall 1 month ago ↶
2700
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
2701
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
2702
assert! (
2703
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n " )
2704
) ;
614541c3 Andy McCall 1 month ago ↶
2705
2706
2707
write ( root, " a.txt" , b " one\n two\n three\n " ) ;
2708
write ( root, " b.txt" , b " b\n " ) ;
2709
let h2c = repo.amend ( None ) .unwrap ( ) ;
2710
let c = repo.store ( ) .get_change ( & h2c) .unwrap ( ) ;
2711
assert_eq! ( c.change_id, id2) ;
2712
assert_eq! ( c.message, " second" ) ;
2713
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2c] ) ;
75167672 Andy McCall 1 month ago
2714
assert_eq! (
2715
repo.log ( ) .unwrap ( ) .len ( ) ,
2716
3 ,
2717
" working copy, amended head, first"
2718
) ;
614541c3 Andy McCall 1 month ago ↶
2719
let snap = repo.load_state ( & [ h2c] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
2720
assert! (
2721
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n three\n " )
2722
) ;
614541c3 Andy McCall 1 month ago ↶
2723
assert! ( snap.files.contains_key ( " b.txt" ) ) ;
2724
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
75167672 Andy McCall 1 month ago
2725
assert! (
2726
repo.amend ( None ) .is_err ( ) ,
2727
" clean working copy and no message"
2728
) ;
614541c3 Andy McCall 1 month ago ↶
2729
let h2d = repo.amend ( Some ( " second, amended" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2730
assert_eq! (
2731
repo.store ( ) .get_change ( & h2d) .unwrap ( ) .message,
2732
" second, amended"
2733
) ;
614541c3 Andy McCall 1 month ago ↶
2734
2735
2736
repo.abandon ( & h2d) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2737
assert_eq! (
2738
repo.bookmark_heads ( ) .unwrap ( ) ,
2739
vec! [ h1b] ,
2740
" the described first change is the head again"
2741
) ;
614541c3 Andy McCall 1 month ago ↶
2742
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2743
let paths: Vec< _ > = s
2744
.statuses
2745
.iter ( )
2746
.map ( | p | ( p.path.as_str ( ) , p.status) )
2747
.collect ( ) ;
2748
assert_eq! (
2749
paths,
2750
vec! [
2751
( " a.txt" , ChangeStatus:: Modified) ,
2752
( " b.txt" , ChangeStatus:: Added)
2753
]
2754
) ;
614541c3 Andy McCall 1 month ago ↶
2755
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n two\n three\n " ) ;
2756
let h3 = repo.commit ( " recommitted" , & [ ] ) .unwrap ( ) ;
2757
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h3] ) ;
2758
2759
repo.undo ( ) .unwrap ( ) ;
2760
repo.undo ( ) .unwrap ( ) ;
2761
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2d] ) ;
c07e0e7d Andy McCall 1 month ago ↶
2762
6706bdcf Andy McCall 1 month ago ↶
2763
2764
2765
c07e0e7d Andy McCall 1 month ago ↶
2766
let base = repo.bookmark_heads ( ) .unwrap ( ) [ 0 ] ;
2767
let base_change = repo.store ( ) .get_change ( & base) .unwrap ( ) ;
2768
let h1_now = base_change.deps[ 0 ] ;
6706bdcf Andy McCall 1 month ago ↶
2769
let notes = repo.abandon ( & h1_now) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2770
assert! (
2771
! notes.is_empty ( ) ,
2772
" the dependent's edit of a.txt was reified"
2773
) ;
6706bdcf Andy McCall 1 month ago ↶
2774
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
2775
let after = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
2776
assert! (
2777
! after.files.contains_key ( " a.txt" ) & & after.files.contains_key ( " b.txt" ) ,
2778
" {:?}" ,
2779
after.files.keys ( )
2780
) ;
6706bdcf Andy McCall 1 month ago ↶
2781
repo.undo ( ) .unwrap ( ) ;
2782
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ base] ) ;
c07e0e7d Andy McCall 1 month ago ↶
2783
write ( root, " c.txt" , b " c\n " ) ;
2784
let hc = repo.commit ( " c" , & [ ] ) .unwrap ( ) ;
2785
write ( root, " d.txt" , b " d\n " ) ;
2786
let hd = repo.commit ( " d" , & [ hc] ) .unwrap ( ) ;
2787
assert_eq! ( repo.store ( ) .get_change ( & hd) .unwrap ( ) .deps, vec! [ hc] ) ;
2788
repo.abandon ( & hc) .unwrap ( ) ;
2789
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
2790
assert! ( ! heads.contains ( & hc) ) ;
75167672 Andy McCall 1 month ago
2791
let d_now = heads
2792
.iter ( )
2793
.find ( | h | repo.store ( ) .get_change ( h) .unwrap ( ) .message = = " d" )
2794
.copied ( )
2795
.unwrap ( ) ;
2796
assert! (
2797
repo.store ( ) .get_change ( & d_now) .unwrap ( ) .deps.is_empty ( ) ,
2798
" d inherited c's (empty) dependencies"
2799
) ;
c07e0e7d Andy McCall 1 month ago ↶
2800
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2801
assert_eq! (
2802
s.statuses
2803
.iter ( )
2804
.map ( | p | p.path.as_str ( ) )
2805
.collect:: < Vec< _ > > ( ) ,
2806
vec! [ " c.txt" ] ,
2807
" c's content is now uncommitted work"
2808
) ;
614541c3 Andy McCall 1 month ago ↶
2809
}
79ab8528 Andy McCall 1 month ago ↶
2810
2811
# [ test ]
2812
fn git_export_mirrors_history ( ) {
2813
hermetic ( ) ;
75167672 Andy McCall 1 month ago
2814
if std:: process:: Command:: new( " git" )
2815
.arg ( " --version" )
2816
.output ( )
2817
.is_err ( )
2818
{
79ab8528 Andy McCall 1 month ago ↶
2819
eprintln! ( " git not installed; skipping" ) ;
2820
return ;
2821
}
2822
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2823
let root = dir.path ( ) ;
75167672 Andy McCall 1 month ago
2824
let mut repo = Repo:: init( & root.join ( " r" ) )
2825
.unwrap ( )
2826
.with_home ( root.join ( " home" ) ) ;
2827
repo.create_identity ( " Ada Lovelace" , " [email protected] " , IdentityScope:: User)
2828
.unwrap ( ) ;
79ab8528 Andy McCall 1 month ago ↶
2829
let r = root.join ( " r" ) ;
2830
write ( & r, " README.md" , b " hello\n " ) ;
2831
write ( & r, " src/main.rs" , b " fn main() {}\n " ) ;
2832
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
2833
write ( & r, " src/main.rs" , b " fn main() { run(); }\n " ) ;
2834
write ( & r, " logo.png" , b " \x89 PNG\0 \0 " ) ;
2835
let h2 = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
2836
2837
let git_dir = root.join ( " mirror.git" ) ;
2838
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
2839
assert_eq! ( ( rep.exported, rep.skipped, rep.heads) , ( 2 , 0 , 1 ) ) ;
2840
let git = | args : & [& str ]| -> String {
75167672 Andy McCall 1 month ago
2841
let out = std:: process:: Command:: new( " git" )
2842
.arg ( " -C" )
2843
.arg ( & git_dir)
2844
.args ( args)
2845
.output ( )
2846
.unwrap ( ) ;
2847
assert! (
2848
out.status.success ( ) ,
2849
" {}" ,
2850
String :: from_utf8_lossy( & out.stderr)
2851
) ;
79ab8528 Andy McCall 1 month ago ↶
2852
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
2853
} ;
2854
assert_eq! ( git ( & [ " log" , " --format=%s" , " main" ] ) , " second\n first" ) ;
2855
assert_eq! ( git ( & [ " show" , " main:src/main.rs" ] ) , " fn main() { run(); }" ) ;
2856
assert_eq! ( git ( & [ " show" , " main:README.md" ] ) , " hello" ) ;
2857
let body = git ( & [ " log" , " -1" , " --format=%B" , " main" ] ) ;
75167672 Andy McCall 1 month ago
2858
assert! (
2859
body.contains ( & format! ( " Oxy-Hash: {} " , h2.to_hex ( ) ) ) ,
2860
" {body}"
2861
) ;
79ab8528 Andy McCall 1 month ago ↶
2862
assert! ( body.contains ( " Change-Id: " ) , " {body}" ) ;
75167672 Andy McCall 1 month ago
2863
assert_eq! (
2864
git ( & [ " log" , " -1" , " --format=%an <%ae>" , " main" ] ) ,
2865
" Ada Lovelace <[email protected] >"
2866
) ;
2867
assert_eq! (
2868
git ( & [ " ls-tree" , " --name-only" , " main" ] ) ,
2869
" README.md\n logo.png\n src"
2870
) ;
79ab8528 Andy McCall 1 month ago ↶
2871
2872
2873
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
2874
assert_eq! ( ( rep.exported, rep.skipped) , ( 0 , 2 ) ) ;
2875
2876
2877
2878
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
2879
write ( & r, " other.txt" , b " o\n " ) ;
2880
let h3 = repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
2881
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
2882
assert_eq! ( ( rep.exported, rep.heads) , ( 1 , 2 ) ) ;
2883
assert_eq! ( git ( & [ " log" , " --format=%s" , " main" ] ) , " third\n second\n first" ) ;
2884
let parents = git ( & [ " rev-list" , " --parents" , " -1" , " main" ] ) ;
2885
assert_eq! ( parents.split ( ' ' ) .count ( ) , 2 , " one parent: {parents}" ) ;
2886
assert_eq! ( git ( & [ " show" , " main:other.txt" ] ) , " o" ) ;
2887
assert_eq! ( git ( & [ " show" , " main:src/main.rs" ] ) , " fn main() { run(); }" ) ;
75167672 Andy McCall 1 month ago
2888
assert_eq! (
2889
git ( & [ " ls-tree" , " --name-only" , " main" ] ) ,
2890
" README.md\n logo.png\n other.txt\n src"
2891
) ;
79ab8528 Andy McCall 1 month ago ↶
2892
let body = git ( & [ " log" , " -1" , " --format=%B" , " main" ] ) ;
75167672 Andy McCall 1 month ago
2893
assert! (
2894
body.contains ( & format! ( " Oxy-Hash: {} " , h3.to_hex ( ) ) ) & & body.contains ( " Oxy-State: " ) ,
2895
" {body}"
2896
) ;
79ab8528 Andy McCall 1 month ago ↶
2897
2898
repo.describe ( & h3, " third, described" ) .unwrap ( ) ;
2899
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
2900
assert_eq! ( rep.exported, 1 ) ;
75167672 Andy McCall 1 month ago
2901
assert_eq! (
2902
git ( & [ " log" , " -1" , " --format=%s" , " main" ] ) ,
2903
" third, described"
2904
) ;
79ab8528 Andy McCall 1 month ago ↶
2905
assert_eq! ( git ( & [ " log" , " --format=%s" , " main" ] ) .lines ( ) .count ( ) , 4 ) ;
2906
let _ = h2;
2907
}
c3069537 Andy McCall 1 month ago ↶
2908
2909
# [ test ]
2910
fn git_import_reproduces_trees ( ) {
c3d2ef61 Andy McCall 1 month ago ↶
2911
git_import_reproduces_trees_with ( None ) ;
2912
}
2913
2914
2915
2916
# [ test ]
2917
fn git_import_reproduces_trees_in_batches ( ) {
2918
git_import_reproduces_trees_with ( Some ( 1 ) ) ;
2919
}
2920
2921
fn git_import_reproduces_trees_with ( batch : Option< usize > ) {
2922
crate :: repo:: git:: set_merge_batch_for_tests( batch) ;
c3069537 Andy McCall 1 month ago ↶
2923
hermetic ( ) ;
75167672 Andy McCall 1 month ago
2924
if std:: process:: Command:: new( " git" )
2925
.arg ( " --version" )
2926
.output ( )
2927
.is_err ( )
2928
{
c3069537 Andy McCall 1 month ago ↶
2929
eprintln! ( " git not installed; skipping" ) ;
2930
return ;
2931
}
2932
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2933
let root = dir.path ( ) ;
2934
let g = root.join ( " src.git" ) ;
2935
fs:: create_dir_all( & g) .unwrap ( ) ;
2936
let git = | args : & [& str ]| -> String {
2937
let out = std:: process:: Command:: new( " git" )
2938
.arg ( " -C" )
2939
.arg ( & g)
75167672 Andy McCall 1 month ago
2940
.args ( [
2941
" -c" ,
2942
" user.name=Grace" ,
2943
" -c" ,
2944
" [email protected] " ,
2945
" -c" ,
2946
" commit.gpgsign=false" ,
2947
] )
c3069537 Andy McCall 1 month ago ↶
2948
.args ( args)
2949
.output ( )
2950
.unwrap ( ) ;
75167672 Andy McCall 1 month ago
2951
assert! (
2952
out.status.success ( ) ,
2953
" git {args:?}: {}" ,
2954
String :: from_utf8_lossy( & out.stderr)
2955
) ;
c3069537 Andy McCall 1 month ago ↶
2956
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
2957
} ;
2958
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
2959
write ( & g, " README.md" , b " # Hi\n " ) ;
2960
write ( & g, " src/lib.rs" , b " pub fn a() {}\n " ) ;
2961
git ( & [ " add" , " ." ] ) ;
2962
git ( & [ " commit" , " -q" , " -m" , " initial" ] ) ;
2963
write ( & g, " src/lib.rs" , b " pub fn a() {}\n pub fn b() {}\n " ) ;
2964
write ( & g, " run.sh" , b " #!/bin/sh\n " ) ;
2965
# [ cfg ( unix ) ]
2966
{
2967
use std:: os:: unix:: fs:: PermissionsExt;
2968
fs:: set_permissions( g.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
2969
}
2970
write ( & g, " logo.png" , b " \x89 PNG\0 \0 \x01 " ) ;
2971
git ( & [ " add" , " ." ] ) ;
75167672 Andy McCall 1 month ago
2972
git ( & [
2973
" commit" ,
2974
" -q" ,
2975
" -m" ,
2976
" second\n \n Change-Id: 0123456789abcdef0123456789abcdef" ,
2977
] ) ;
c3069537 Andy McCall 1 month ago ↶
2978
2979
git ( & [ " checkout" , " -q" , " -b" , " topic" , " HEAD~1" ] ) ;
2980
write ( & g, " README.md" , b " # Hi\n \n More.\n " ) ;
2981
git ( & [ " commit" , " -q" , " -am" , " readme" ] ) ;
2982
git ( & [ " checkout" , " -q" , " main" ] ) ;
2983
git ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge topic" , " topic" ] ) ;
2984
2985
2986
let initial = git ( & [ " rev-list" , " --max-parents=0" , " HEAD" ] ) ;
2987
git ( & [ " checkout" , " -q" , " -b" , " clash" , & initial] ) ;
2988
write ( & g, " src/lib.rs" , b " pub fn a() {}\n pub fn c() {}\n " ) ;
2989
git ( & [ " commit" , " -q" , " -am" , " clash" ] ) ;
2990
git ( & [ " checkout" , " -q" , " main" ] ) ;
7df9007b Andy McCall 1 month ago ↶
2991
2992
2993
75167672 Andy McCall 1 month ago
2994
let _ = std:: process:: Command:: new( " git" )
2995
.arg ( " -C" )
2996
.arg ( & g)
7df9007b Andy McCall 1 month ago ↶
2997
.args ( [
2998
" -c" ,
2999
" user.name=Grace" ,
3000
" -c" ,
3001
" [email protected] " ,
3002
" merge" ,
3003
" -q" ,
3004
" clash" ,
3005
] )
75167672 Andy McCall 1 month ago
3006
.output ( ) ;
7df9007b Andy McCall 1 month ago ↶
3007
assert! (
3008
g.join ( " .git/MERGE_HEAD" ) .exists ( ) ,
3009
" the clash merge stopped at its conflict"
3010
) ;
75167672 Andy McCall 1 month ago
3011
write (
3012
& g,
3013
" src/lib.rs" ,
3014
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n " ,
3015
) ;
c3069537 Andy McCall 1 month ago ↶
3016
git ( & [ " add" , " ." ] ) ;
3017
git ( & [ " commit" , " -q" , " -m" , " resolve clash" ] ) ;
3018
git ( & [ " rm" , " -q" , " logo.png" ] ) ;
3019
git ( & [ " commit" , " -q" , " -m" , " drop logo" ] ) ;
6e723b81 Andy McCall 1 month ago ↶
3020
3021
git ( & [ " mv" , " src/lib.rs" , " src/lib2.rs" ] ) ;
75167672 Andy McCall 1 month ago
3022
write (
3023
& g,
3024
" src/lib2.rs" ,
3025
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n pub fn d() {}\n " ,
3026
) ;
6e723b81 Andy McCall 1 month ago ↶
3027
git ( & [ " add" , " ." ] ) ;
3028
git ( & [ " commit" , " -q" , " -m" , " rename lib" ] ) ;
958fb27d Andy McCall 1 month ago ↶
3029
3030
3031
3032
git ( & [ " checkout" , " -q" , " -b" , " discarded" ] ) ;
3033
write ( & g, " run.sh" , b " #!/bin/sh\n echo discarded\n " ) ;
3034
git ( & [ " commit" , " -q" , " -am" , " discarded edit" ] ) ;
3035
git ( & [ " checkout" , " -q" , " main" ] ) ;
314db9b4 Andy McCall 1 month ago ↶
3036
git ( & [
3037
" merge" ,
3038
" -q" ,
3039
" -s" ,
3040
" ours" ,
3041
" -m" ,
3042
" merge discarded, keeping ours" ,
3043
" discarded" ,
3044
] ) ;
c3069537 Andy McCall 1 month ago ↶
3045
let expected_count: usize = git ( & [ " rev-list" , " --count" , " main" ] ) .parse ( ) .unwrap ( ) ;
3046
3047
let dest = root.join ( " oxy" ) ;
3048
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
3049
let rep = repo.import_git ( & g, " main" ) .unwrap ( ) ;
3050
assert_eq! ( rep.commits, expected_count) ;
958fb27d Andy McCall 1 month ago ↶
3051
assert_eq! ( rep.merges, 3 ) ;
314db9b4 Andy McCall 1 month ago ↶
3052
assert_eq! (
3053
fs:: read( dest.join ( " run.sh" ) ) .unwrap ( ) ,
3054
b " #!/bin/sh\n " ,
3055
" the ours merge kept main's run.sh"
3056
) ;
958fb27d Andy McCall 1 month ago ↶
3057
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
3058
assert_eq! ( v.commits, expected_count, " every commit's tree reproduced" ) ;
3059
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
c3069537 Andy McCall 1 month ago ↶
3060
75167672 Andy McCall 1 month ago
3061
assert_eq! (
3062
fs:: read( dest.join ( " src/lib2.rs" ) ) .unwrap ( ) ,
3063
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n pub fn d() {}\n "
3064
) ;
6e723b81 Andy McCall 1 month ago ↶
3065
assert! ( ! dest.join ( " src/lib.rs" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
3066
assert_eq! (
3067
fs:: read( dest.join ( " README.md" ) ) .unwrap ( ) ,
3068
b " # Hi\n \n More.\n "
3069
) ;
c3069537 Andy McCall 1 month ago ↶
3070
assert! ( ! dest.join ( " logo.png" ) .exists ( ) ) ;
3071
# [ cfg ( unix ) ]
3072
{
3073
use std:: os:: unix:: fs:: PermissionsExt;
75167672 Andy McCall 1 month ago
3074
assert! (
3075
fs:: metadata( dest.join ( " run.sh" ) )
3076
.unwrap ( )
3077
.permissions ( )
3078
.mode ( )
3079
& 0o111
3080
! = 0
3081
) ;
c3069537 Andy McCall 1 month ago ↶
3082
}
75167672 Andy McCall 1 month ago
3083
assert! (
3084
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
3085
" clean after import"
3086
) ;
c3069537 Andy McCall 1 month ago ↶
3087
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
3088
let st = repo.load_state ( & heads) .unwrap ( ) ;
3089
assert! ( ! st.materialize ( ) .is_conflicted ( ) ) ;
3090
assert_eq! ( st.len ( ) , expected_count) ;
75167672 Andy McCall 1 month ago
3091
let tip = repo
3092
.log ( )
3093
.unwrap ( )
3094
.into_iter ( )
3095
.find ( | e | e.change.message.starts_with ( " rename lib" ) )
3096
.unwrap ( ) ;
6e723b81 Andy McCall 1 month ago ↶
3097
let c = repo.store ( ) .get_change ( & tip.hash) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3098
assert! (
3099
c.tree_ops
3100
.iter ( )
3101
.any ( | op | matches! ( op, TreeOp:: Move { name, .. } if name = = " lib2.rs" ) ) ,
3102
" {:?}" ,
3103
c.tree_ops
3104
) ;
3105
assert! ( ! c
3106
.tree_ops
3107
.iter ( )
3108
.any ( | op | matches! ( op, TreeOp:: AddNode { .. } ) ) ) ;
c3069537 Andy McCall 1 month ago ↶
3109
3110
let log = repo.log ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3111
let second = log
3112
.iter ( )
3113
.find ( | e | e.change.message.starts_with ( " second" ) )
3114
.unwrap ( ) ;
3115
assert_eq! (
3116
second.change.change_id,
3117
ChangeId:: from_hex( " 0123456789abcdef0123456789abcdef" ) .unwrap ( )
3118
) ;
3119
assert! ( second
3120
.change
3121
.message
3122
.contains ( " Git-Author: Grace <[email protected] >" ) ) ;
3123
let resolve = log
3124
.iter ( )
3125
.find ( | e | e.change.message.starts_with ( " resolve clash" ) )
3126
.unwrap ( ) ;
3127
assert_eq! (
3128
resolve.change.kind,
3129
ChangeKind:: Resolution,
3130
" a conflicting Git merge becomes a resolution"
3131
) ;
c3069537 Andy McCall 1 month ago ↶
3132
75167672 Andy McCall 1 month ago
3133
let repo2 = Repo:: init( & root.join ( " oxy2" ) )
3134
.unwrap ( )
3135
.with_home ( root.join ( " home" ) ) ;
c3069537 Andy McCall 1 month ago ↶
3136
repo2.import_git ( & g, " main" ) .unwrap ( ) ;
dfb23a3a Andy McCall 1 month ago ↶
3137
3138
3139
assert_eq! (
3140
repo2.bookmark_heads ( ) .unwrap ( ) ,
3141
repo.bookmark_heads ( ) .unwrap ( )
3142
) ;
75167672 Andy McCall 1 month ago
3143
let ids: BTreeSet< ChangeId> = repo
3144
.log ( )
3145
.unwrap ( )
3146
.iter ( )
3147
.filter ( | e | ! e.is_working_copy)
3148
.map ( | e | e.change.change_id )
3149
.collect ( ) ;
3150
let ids2: BTreeSet< ChangeId> = repo2
3151
.log ( )
3152
.unwrap ( )
3153
.iter ( )
3154
.filter ( | e | ! e.is_working_copy)
3155
.map ( | e | e.change.change_id )
3156
.collect ( ) ;
c3069537 Andy McCall 1 month ago ↶
3157
assert_eq! ( ids, ids2) ;
3158
3159
let mirror = root.join ( " mirror.git" ) ;
3160
repo.git_export ( & mirror) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3161
let out = std:: process:: Command:: new( " git" )
3162
.arg ( " -C" )
3163
.arg ( & mirror)
3164
.args ( [ " show" , " main:src/lib2.rs" ] )
3165
.output ( )
3166
.unwrap ( ) ;
3167
assert_eq! (
3168
String :: from_utf8_lossy( & out.stdout) ,
3169
" pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n pub fn d() {}\n "
3170
) ;
3171
let out = std:: process:: Command:: new( " git" )
3172
.arg ( " -C" )
3173
.arg ( & mirror)
3174
.args ( [ " show" , " main:src/lib.rs" ] )
3175
.output ( )
3176
.unwrap ( ) ;
3177
assert! (
3178
! out.status.success ( ) ,
3179
" the old path is gone from the export"
3180
) ;
c3069537 Andy McCall 1 month ago ↶
3181
}
252e8cb2 Andy McCall 1 month ago ↶
3182
3183
# [ test ]
3184
fn checkpoints_accelerate_loading_and_stay_correct ( ) {
3185
hermetic ( ) ;
3186
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3187
let root = dir.path ( ) ;
3188
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
3189
repo.config.set ( " core.checkpoint-interval" , " 5" ) .unwrap ( ) ;
3190
repo.save_config ( ) .unwrap ( ) ;
3191
let repo = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
3192
let mut hashes = Vec :: new( ) ;
3193
for i in 0 .. 12 {
75167672 Andy McCall 1 month ago
3194
write (
3195
root,
3196
" a.txt" ,
3197
format! (
3198
" {} \n " ,
3199
( 0 .. = i)
3200
.map ( | k | k.to_string ( ) )
3201
.collect:: < Vec< _ > > ( )
3202
.join ( " \n " )
3203
)
3204
.as_bytes ( ) ,
3205
) ;
252e8cb2 Andy McCall 1 month ago ↶
3206
write ( root, & format! ( " f{i} .txt" ) , b " x\n " ) ;
3207
hashes.push ( repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ) ;
3208
}
75167672 Andy McCall 1 month ago
3209
let ( _ , cp) = repo
3210
.latest_checkpoint ( " main" )
3211
.unwrap ( )
3212
.expect ( " automatic checkpoint" ) ;
252e8cb2 Andy McCall 1 month ago ↶
3213
assert! ( cp.count > = 5 & & cp.count < = 12 , " {}" , cp.count) ;
3214
3215
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
3216
let fast = repo.load_state ( & heads) .unwrap ( ) ;
3217
let full = repo.load_state_full ( & heads) .unwrap ( ) ;
3218
assert_eq! ( fast.len ( ) , full.len ( ) ) ;
3219
assert_eq! ( fast.heads ( ) , full.heads ( ) ) ;
3220
let ( a, b) = ( fast.materialize ( ) , full.materialize ( ) ) ;
75167672 Andy McCall 1 month ago
3221
assert_eq! (
3222
a.files.keys ( ) .collect:: < Vec< _ > > ( ) ,
3223
b.files.keys ( ) .collect:: < Vec< _ > > ( )
3224
) ;
252e8cb2 Andy McCall 1 month ago ↶
3225
for ( p, f) in & a.files {
3226
assert_eq! ( f.content, b.files[ p] .content, " {p}" ) ;
3227
}
3228
assert! ( repo.load_state_checkpointed ( & heads) .unwrap ( ) .is_some ( ) ) ;
3229
75167672 Andy McCall 1 month ago
3230
assert! ( repo
3231
.load_state_checkpointed ( & [ hashes[ 0 ] ] )
3232
.unwrap ( )
3233
.is_none ( ) ) ;
252e8cb2 Andy McCall 1 month ago ↶
3234
assert_eq! ( repo.load_state ( & [ hashes[ 0 ] ] ) .unwrap ( ) .len ( ) , 1 ) ;
3235
3236
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
3237
let h = repo.describe ( & hashes[ 3 ] , " c3 described" ) .unwrap ( ) ;
3238
assert_eq! ( repo.store ( ) .get_change ( & h) .unwrap ( ) .message, " c3 described" ) ;
3239
let ( r, n, bytes) = repo.checkpoint ( ) .unwrap ( ) ;
3240
assert! ( n > = 12 & & bytes > 0 ) ;
3241
assert_eq! ( repo.latest_checkpoint ( " main" ) .unwrap ( ) .unwrap ( ) .0 , r) ;
3242
write ( root, " a.txt" , b " after\n " ) ;
3243
let h_after = repo.commit ( " after" , & [ ] ) .unwrap ( ) ;
3244
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
3245
assert! ( st.contains ( & h_after) ) ;
75167672 Andy McCall 1 month ago
3246
assert! (
3247
matches! ( & st.materialize ( ) .files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " after\n " )
3248
) ;
252e8cb2 Andy McCall 1 month ago ↶
3249
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 14 ) ;
3250
3251
repo.gc ( true ) .unwrap ( ) ;
3252
assert! ( repo.latest_checkpoint ( " main" ) .unwrap ( ) .is_some ( ) ) ;
75167672 Andy McCall 1 month ago
3253
assert! ( repo
3254
.load_state_checkpointed ( & repo.bookmark_heads ( ) .unwrap ( ) )
3255
.unwrap ( )
3256
.is_some ( ) ) ;
252e8cb2 Andy McCall 1 month ago ↶
3257
}
135212de Andy McCall 1 month ago ↶
3258
3259
# [ test ]
3260
fn quick_status_for_prompts ( ) {
3261
hermetic ( ) ;
3262
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3263
let root = dir.path ( ) ;
3264
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
3265
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
3266
let a = Repo:: clone( & url, & root.join ( " a" ) )
3267
.unwrap ( )
3268
.with_home ( root.join ( " home" ) ) ;
135212de Andy McCall 1 month ago ↶
3269
let ops_before = a.op_log ( ) .unwrap ( ) .len ( ) ;
3270
let info = a.quick_status ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3271
assert_eq! (
3272
(
3273
info.bookmark.as_str ( ) ,
3274
info.heads,
3275
info.dirty,
3276
info.conflicted,
3277
info.ahead,
3278
info.behind
3279
) ,
3280
( " main" , 0 , false , false , Some ( 0 ) , Some ( 0 ) )
3281
) ;
135212de Andy McCall 1 month ago ↶
3282
write ( & root.join ( " a" ) , " f.txt" , b " f\n " ) ;
3283
assert! ( a.quick_status ( ) .unwrap ( ) .dirty, " a new file is dirty" ) ;
75167672 Andy McCall 1 month ago
3284
assert_eq! (
3285
a.op_log ( ) .unwrap ( ) .len ( ) ,
3286
ops_before,
3287
" quick_status records nothing"
3288
) ;
135212de Andy McCall 1 month ago ↶
3289
3290
a.snapshot ( " status" ) .unwrap ( ) ;
3291
assert! ( a.quick_status ( ) .unwrap ( ) .dirty) ;
3292
a.commit ( " f" , & [ ] ) .unwrap ( ) ;
3293
let info = a.quick_status ( ) .unwrap ( ) ;
3294
assert! ( ! info.dirty, " clean right after a commit" ) ;
3295
assert_eq! ( ( info.ahead, info.behind) , ( Some ( 1 ) , Some ( 0 ) ) ) ;
75167672 Andy McCall 1 month ago
3296
assert_eq! (
3297
info.format ( " %b %c%d%x%A%D" ) ,
3298
format! ( " main {} +1" , info.change_id)
3299
) ;
135212de Andy McCall 1 month ago ↶
3300
assert_eq! ( info.format ( " [%h heads] %a/%B %%" ) , " [1 heads] 1/0 %" ) ;
3301
3302
3303
let p = root.join ( " a/f.txt" ) ;
3304
let content = fs:: read( & p) .unwrap ( ) ;
3305
fs:: write( & p, & content) .unwrap ( ) ;
3306
assert! ( ! a.quick_status ( ) .unwrap ( ) .dirty) ;
3307
fs:: write( & p, b " g\n " ) .unwrap ( ) ;
3308
assert! ( a.quick_status ( ) .unwrap ( ) .dirty) ;
3309
fs:: write( & p, b " f\n " ) .unwrap ( ) ;
3310
3311
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
3312
assert_eq! ( a.quick_status ( ) .unwrap ( ) .ahead, Some ( 0 ) ) ;
75167672 Andy McCall 1 month ago
3313
let b = Repo:: clone( & url, & root.join ( " b" ) )
3314
.unwrap ( )
3315
.with_home ( root.join ( " home-b" ) ) ;
135212de Andy McCall 1 month ago ↶
3316
write ( & root.join ( " b" ) , " g.txt" , b " g\n " ) ;
3317
b.commit ( " g" , & [ ] ) .unwrap ( ) ;
3318
b.push ( " origin" , false ) .unwrap ( ) ;
3319
a.fetch ( " origin" ) .unwrap ( ) ;
3320
let info = a.quick_status ( ) .unwrap ( ) ;
3321
assert_eq! ( ( info.ahead, info.behind) , ( Some ( 0 ) , Some ( 1 ) ) ) ;
3322
assert_eq! ( info.format ( " %A%D" ) , " -1" ) ;
3323
let _ = origin;
3324
}
b3c5a59a Andy McCall 1 month ago ↶
3325
3326
# [ test ]
3327
fn lazy_blobs_are_fetched_on_first_read ( ) {
3328
hermetic ( ) ;
3329
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3330
let root = dir.path ( ) ;
3331
let origin_dir = root.join ( " origin" ) ;
75167672 Andy McCall 1 month ago
3332
let origin = Repo:: init( & origin_dir)
3333
.unwrap ( )
3334
.with_home ( root.join ( " home" ) ) ;
b3c5a59a Andy McCall 1 month ago ↶
3335
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
3336
let big: Vec< u8 > = ( 0 .. 200_000 u32 ) .map ( | i | ( i % 251 ) as u8 ) .collect ( ) ;
3337
write ( & origin_dir, " big.bin" , & big) ;
3338
write ( & origin_dir, " small.bin" , & [ 0 u8 , 1 , 2 ] ) ;
3339
write ( & origin_dir, " t.txt" , b " t\n " ) ;
3340
let h = origin.commit ( " blobs" , & [ ] ) .unwrap ( ) ;
3341
let big_ref = Repo:: blob_ref( & big) ;
3342
3343
75167672 Andy McCall 1 month ago
3344
let a = Repo:: init_with_id( & root.join ( " a" ) , origin.id ( ) .unwrap ( ) )
3345
.unwrap ( )
3346
.with_home ( root.join ( " home" ) ) ;
b3c5a59a Andy McCall 1 month ago ↶
3347
let a = {
3348
let mut a = a;
3349
a.config.set ( " remote.origin.url" , & url) .unwrap ( ) ;
3350
a.config.set ( " filter.max-blob" , " 100K" ) .unwrap ( ) ;
3351
a.save_config ( ) .unwrap ( ) ;
3352
a
3353
} ;
3354
assert_eq! ( a.config.max_blob, Some ( 100 * 1024 ) ) ;
3355
a.fetch ( " origin" ) .unwrap ( ) ;
3356
assert! ( a.store ( ) .has ( & h) ) ;
3357
assert! ( ! a.store ( ) .has ( & big_ref) , " withheld" ) ;
75167672 Andy McCall 1 month ago
3358
assert! (
3359
a.store ( ) .has ( & Repo:: blob_ref( & [ 0 u8 , 1 , 2 ] ) ) ,
3360
" small blob came along"
3361
) ;
b3c5a59a Andy McCall 1 month ago ↶
3362
assert_eq! ( a.lazy_remote ( & big_ref) .as_deref ( ) , Some ( " origin" ) ) ;
3363
3364
3365
let ( op_ref, op) = a.head_op ( ) .unwrap ( ) ;
3366
let mut view = op.view.clone ( ) ;
75167672 Andy McCall 1 month ago
3367
view.bookmarks.insert (
3368
" main" .into ( ) ,
3369
a.store ( ) .put_state ( & StateObject:: new( vec! [ h] ) ) .unwrap ( ) ,
3370
) ;
b3c5a59a Andy McCall 1 month ago ↶
3371
a.record_op ( & [ op_ref] , " adopt" , view) .unwrap ( ) ;
3372
3373
a.checkout ( & [ h] , true , " checkout" ) .unwrap ( ) ;
3374
assert! ( a.store ( ) .has ( & big_ref) , " fetched on first read" ) ;
3375
assert_eq! ( fs:: read( root.join ( " a/big.bin" ) ) .unwrap ( ) , big) ;
3376
assert! ( a.lazy_remote ( & big_ref) .is_none ( ) , " no longer registered" ) ;
3377
write ( & root.join ( " a" ) , " t.txt" , b " t2\n " ) ;
3378
a.commit ( " text edit" , & [ ] ) .unwrap ( ) ;
3379
3380
7421309f Andy McCall 1 month ago ↶
3381
let b = Repo:: clone_with( & url, & root.join ( " b" ) , Some ( 1024 ) , & [ ] ) .unwrap ( ) ;
b3c5a59a Andy McCall 1 month ago ↶
3382
assert_eq! ( b.config.max_blob, Some ( 1024 ) ) ;
3383
assert_eq! ( fs:: read( root.join ( " b/big.bin" ) ) .unwrap ( ) , big) ;
3384
assert_eq! ( parse_size ( " 2M" ) .unwrap ( ) , 2 < < 20 ) ;
3385
assert! ( parse_size ( " lots" ) .is_err ( ) ) ;
3386
}
7421309f Andy McCall 1 month ago ↶
3387
3388
# [ test ]
3389
fn sparse_checkout_materializes_only_chosen_paths ( ) {
3390
hermetic ( ) ;
3391
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3392
let root = dir.path ( ) ;
3393
let origin_dir = root.join ( " origin" ) ;
75167672 Andy McCall 1 month ago
3394
let origin = Repo:: init( & origin_dir)
3395
.unwrap ( )
3396
.with_home ( root.join ( " home" ) ) ;
7421309f Andy McCall 1 month ago ↶
3397
write ( & origin_dir, " src/main.rs" , b " fn main() {}\n " ) ;
3398
write ( & origin_dir, " src/lib.rs" , b " pub fn a() {}\n " ) ;
3399
write ( & origin_dir, " docs/guide.md" , b " # guide\n " ) ;
3400
let logo: Vec< u8 > = ( 0 .. 50_000 u32 ) .map ( | i | ( i % 7 ) as u8 ) .collect ( ) ;
3401
write ( & origin_dir, " docs/logo.png" , & logo) ;
3402
write ( & origin_dir, " README.md" , b " # top\n " ) ;
3403
let h1 = origin.commit ( " tree" , & [ ] ) .unwrap ( ) ;
3404
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
3405
75167672 Andy McCall 1 month ago
3406
let a = Repo:: clone_with( & url, & root.join ( " a" ) , None , & [ " src" .into ( ) ] )
3407
.unwrap ( )
3408
.with_home ( root.join ( " home" ) ) ;
7421309f Andy McCall 1 month ago ↶
3409
assert_eq! ( a.config.sparse_paths, vec! [ " src" ] ) ;
3410
assert_eq! ( a.config.max_blob, Some ( 0 ) , " sparse implies lazy blobs" ) ;
3411
assert! ( root.join ( " a/src/main.rs" ) .exists ( ) ) ;
3412
assert! ( ! root.join ( " a/docs" ) .exists ( ) & & ! root.join ( " a/README.md" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
3413
assert! (
3414
! a.store ( ) .has ( & Repo:: blob_ref( & logo) ) ,
3415
" out-of-scope blob never fetched"
3416
) ;
3417
assert! (
3418
! a.snapshot ( " status" ) .unwrap ( ) .changed,
3419
" out-of-scope files are not removals"
3420
) ;
05e71ed6 Andy McCall 1 month ago ↶
3421
assert! ( ! a.quick_status ( ) .unwrap ( ) .dirty) ;
7421309f Andy McCall 1 month ago ↶
3422
3423
75167672 Andy McCall 1 month ago
3424
write (
3425
& root.join ( " a" ) ,
3426
" src/lib.rs" ,
3427
b " pub fn a() {}\n pub fn b() {}\n " ,
3428
) ;
7421309f Andy McCall 1 month ago ↶
3429
let h2 = a.commit ( " edit lib" , & [ ] ) .unwrap ( ) ;
3430
let st = a.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
3431
assert! (
3432
st.files.contains_key ( " docs/guide.md" ) ,
3433
" the state still has everything"
3434
) ;
7421309f Andy McCall 1 month ago ↶
3435
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
3436
origin.refresh ( ) .unwrap ( ) ;
7421309f Andy McCall 1 month ago ↶
3437
3438
write ( & root.join ( " a" ) , " docs/new.md" , b " ignored\n " ) ;
3439
assert! ( a.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
3440
3441
3442
let mut a = a;
3443
a.config.set ( " filter.paths" , " src,docs" ) .unwrap ( ) ;
3444
a.save_config ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3445
let r = a
3446
.checkout ( & a.bookmark_heads ( ) .unwrap ( ) , true , " widen" )
3447
.unwrap ( ) ;
7421309f Andy McCall 1 month ago ↶
3448
assert! ( r.written > = 2 ) ;
75167672 Andy McCall 1 month ago
3449
assert_eq! (
3450
fs:: read( root.join ( " a/docs/logo.png" ) ) .unwrap ( ) ,
3451
logo,
3452
" now fetched lazily"
3453
) ;
7421309f Andy McCall 1 month ago ↶
3454
assert! ( ! root.join ( " a/README.md" ) .exists ( ) ) ;
3455
3456
origin.pull ( " origin" ) .err ( ) ;
75167672 Andy McCall 1 month ago
3457
let full = origin
3458
.load_state ( & origin.bookmark_heads ( ) .unwrap ( ) )
3459
.unwrap ( )
3460
.materialize ( ) ;
3461
assert! (
3462
matches! ( & full.files[ " src/lib.rs" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " pub fn a() {}\n pub fn b() {}\n " )
3463
) ;
7421309f Andy McCall 1 month ago ↶
3464
assert! ( full.files.contains_key ( " README.md" ) ) ;
3465
let _ = h1;
3466
}
2863456a Andy McCall 1 month ago ↶
3467
7bd805ff Andy McCall 11 days ago ↶
3468
3469
3470
3471
2863456a Andy McCall 1 month ago ↶
3472
# [ test ]
7bd805ff Andy McCall 11 days ago ↶
3473
fn a_depth_0_clone_brings_its_binary_files ( ) {
3474
hermetic ( ) ;
3475
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3476
let root = dir.path ( ) ;
3477
let origin_dir = root.join ( " origin" ) ;
3478
let origin = Repo:: init( & origin_dir)
3479
.unwrap ( )
3480
.with_home ( root.join ( " home" ) ) ;
3481
let png: Vec< u8 > = ( 0 .. 4000 u32 )
3482
.map ( | i | ( i * 7 % 251 ) as u8 )
3483
.chain ( [ 0 , 0xff ] )
3484
.collect ( ) ;
3485
3486
3487
write ( & origin_dir, " logo.png" , & png) ;
3488
write ( & origin_dir, " a.txt" , b " first\n " ) ;
3489
origin.commit ( " logo" , & [ ] ) .unwrap ( ) ;
3490
write ( & origin_dir, " a.txt" , b " later\n " ) ;
3491
origin.commit ( " later" , & [ ] ) .unwrap ( ) ;
3492
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) .len ( ) , 1 ) ;
3493
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
3494
Repo:: clone_shallow( & url, & root.join ( " a" ) , 0 , None , & [ ] ) .unwrap ( ) ;
3495
assert_eq! ( fs:: read( root.join ( " a/logo.png" ) ) .unwrap ( ) , png) ;
3496
let lazy = Repo:: clone_shallow( & url, & root.join ( " b" ) , 0 , Some ( 0 ) , & [ ] ) .unwrap ( ) ;
3497
assert_eq! ( fs:: read( root.join ( " b/a.txt" ) ) .unwrap ( ) , b " later\n " ) ;
3498
let blob = {
3499
let heads = lazy.bookmark_heads ( ) .unwrap ( ) ;
3500
let st = lazy.load_state_history ( & heads) .unwrap ( ) ;
3501
st.opaque_nodes ( )
3502
.iter ( )
3503
.flat_map ( | n | st.live_blobs ( n) )
3504
.next ( )
3505
.unwrap ( )
3506
} ;
3507
assert_eq! ( lazy.read_blob ( & blob) .unwrap ( ) , png, " fetched on read" ) ;
f3811eb2 Andy McCall 11 days ago ↶
3508
3509
3510
let mut a = Repo:: open( & root.join ( " a" ) )
3511
.unwrap ( )
3512
.with_home ( root.join ( " home" ) ) ;
3513
let head = origin.bookmark_heads ( ) .unwrap ( ) [ 0 ] ;
3514
assert! ( ! a.store ( ) .has ( & head) , " no change fetched" ) ;
3515
assert! ( a.boundary ( ) .contains ( & head) ) ;
3516
assert_eq! (
3517
a.log ( )
3518
.unwrap ( )
3519
.iter ( )
3520
.filter ( | e | ! e.is_working_copy)
3521
.count ( ) ,
3522
2 ,
3523
" the log from the checkpoint"
3524
) ;
3525
write ( & root.join ( " a" ) , " a.txt" , b " later\n mine\n " ) ;
3526
a.config.set ( " remote.origin.url" , & url) .unwrap ( ) ;
3527
a.save_config ( ) .unwrap ( ) ;
3528
let mine = a.commit ( " mine" , & [ ] ) .unwrap ( ) ;
3529
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
3530
origin.refresh ( ) .unwrap ( ) ;
3531
assert! ( origin.store ( ) .has ( & mine) ) ;
3532
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ mine] ) ;
3533
3534
origin
3535
.checkout ( & origin.bookmark_heads ( ) .unwrap ( ) , false , " co" )
3536
.unwrap ( ) ;
3537
write ( & origin_dir, " a.txt" , b " later\n mine\n theirs\n " ) ;
3538
origin.snapshot ( " t" ) .unwrap ( ) ;
3539
origin.commit ( " theirs" , & [ ] ) .unwrap ( ) ;
3540
a.pull ( " origin" ) .unwrap ( ) ;
3541
assert_eq! (
3542
fs:: read( root.join ( " a/a.txt" ) ) .unwrap ( ) ,
3543
b " later\n mine\n theirs\n "
3544
) ;
7bd805ff Andy McCall 11 days ago ↶
3545
}
3546
3547
# [ test ]
2863456a Andy McCall 1 month ago ↶
3548
fn shallow_clone_and_deepen ( ) {
3549
hermetic ( ) ;
3550
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3551
let root = dir.path ( ) ;
3552
let origin_dir = root.join ( " origin" ) ;
75167672 Andy McCall 1 month ago
3553
let origin = Repo:: init( & origin_dir)
3554
.unwrap ( )
3555
.with_home ( root.join ( " home" ) ) ;
2863456a Andy McCall 1 month ago ↶
3556
let mut hashes = Vec :: new( ) ;
3557
for i in 0 .. 8 {
75167672 Andy McCall 1 month ago
3558
write (
3559
& origin_dir,
3560
" a.txt" ,
3561
format! (
3562
" {} \n " ,
3563
( 0 .. = i)
3564
.map ( | k | k.to_string ( ) )
3565
.collect:: < Vec< _ > > ( )
3566
.join ( " \n " )
3567
)
3568
.as_bytes ( ) ,
3569
) ;
2863456a Andy McCall 1 month ago ↶
3570
hashes.push ( origin.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ) ;
3571
}
3572
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
3573
let a = Repo:: clone_shallow( & url, & root.join ( " a" ) , 3 , None , & [ ] )
3574
.unwrap ( )
3575
.with_home ( root.join ( " home" ) ) ;
2863456a Andy McCall 1 month ago ↶
3576
assert! ( a.is_shallow ( ) ) ;
75167672 Andy McCall 1 month ago
3577
assert_eq! (
3578
fs:: read_to_string( root.join ( " a/a.txt" ) ) .unwrap ( ) ,
3579
" 0\n 1\n 2\n 3\n 4\n 5\n 6\n 7\n "
3580
) ;
3581
assert! (
3582
a.store ( ) .has ( & hashes[ 7 ] ) & & a.store ( ) .has ( & hashes[ 5 ] ) ,
3583
" three levels present"
3584
) ;
2863456a Andy McCall 1 month ago ↶
3585
assert! ( ! a.store ( ) .has ( & hashes[ 4 ] ) , " beyond the depth is absent" ) ;
3586
assert_eq! ( a.boundary ( ) , [ hashes[ 4 ] ] .into_iter ( ) .collect ( ) ) ;
75167672 Andy McCall 1 month ago
3587
assert_eq! (
3588
a.log ( ) .unwrap ( ) .len ( ) ,
3589
9 ,
3590
" the log shows every change from the checkpoint's metadata"
3591
) ;
2863456a Andy McCall 1 month ago ↶
3592
assert! ( ! a.snapshot ( " status" ) .unwrap ( ) .changed) ;
3593
assert_eq! ( a.quick_status ( ) .unwrap ( ) .heads, 1 ) ;
3594
3595
3596
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 " ) ;
3597
let h_new = a.commit ( " c8" , & [ ] ) .unwrap ( ) ;
3598
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
3599
origin.refresh ( ) .unwrap ( ) ;
2863456a Andy McCall 1 month ago ↶
3600
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ h_new] ) ;
3601
3602
write ( & origin_dir, " b.txt" , b " b\n " ) ;
3603
origin.pull ( " origin" ) .err ( ) ;
3604
let ( _ , wc) = origin.working_copy ( ) .unwrap ( ) ;
3605
let _ = wc;
75167672 Andy McCall 1 month ago
3606
origin
3607
.checkout ( & origin.bookmark_heads ( ) .unwrap ( ) , true , " co" )
3608
.unwrap ( ) ;
2863456a Andy McCall 1 month ago ↶
3609
write ( & origin_dir, " b.txt" , b " b\n " ) ;
3610
let h_b = origin.commit ( " b" , & [ ] ) .unwrap ( ) ;
3611
let pr = a.pull ( " origin" ) .unwrap ( ) ;
3612
assert! ( pr.changed) ;
3613
assert! ( a.store ( ) .has ( & h_b) ) ;
3614
assert! ( root.join ( " a/b.txt" ) .exists ( ) ) ;
3615
3616
3617
3618
3619
assert! ( a.deepen ( " origin" , 2 ) .unwrap ( ) > = 2 ) ;
3620
assert! ( a.store ( ) .has ( & hashes[ 3 ] ) ) ;
3621
assert! ( a.is_shallow ( ) ) ;
3622
a.deepen ( " origin" , 100 ) .unwrap ( ) ;
3623
assert! ( ! a.is_shallow ( ) , " boundary reached the root" ) ;
3624
assert! ( a.store ( ) .has ( & hashes[ 0 ] ) ) ;
75167672 Andy McCall 1 month ago
3625
assert_eq! (
3626
a.load_state_full ( & a.bookmark_heads ( ) .unwrap ( ) )
3627
.unwrap ( )
3628
.len ( ) ,
3629
10
3630
) ;
2863456a Andy McCall 1 month ago ↶
3631
}
132b884b Andy McCall 1 month ago ↶
3632
3633
# [ test ]
b63fc7a7 Andy McCall 11 days ago ↶
3634
fn a_tag_is_withdrawn_by_a_signed_withdrawal ( ) {
3635
hermetic ( ) ;
3636
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3637
let root = dir.path ( ) ;
3638
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
3639
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
3640
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
3641
.unwrap ( )
3642
.with_home ( root.join ( " home-a" ) ) ;
3643
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
3644
.unwrap ( ) ;
3645
write ( & root.join ( " a" ) , " f.txt" , b " 1\n " ) ;
3646
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
3647
a.tag_create ( " v1" , & [ h1] , " first release" ) .unwrap ( ) ;
3648
a.tag_create ( " v2" , & [ h1] , " " ) .unwrap ( ) ;
3649
a.policy_init ( ) .unwrap ( ) ;
3650
a.push ( " origin" , false ) .unwrap ( ) ;
3651
origin.refresh ( ) .unwrap ( ) ;
3652
let original = a
3653
.tag_refs ( )
3654
.unwrap ( )
3655
.into_iter ( )
3656
.find ( | t | t.name = = " v1" )
3657
.unwrap ( ) ;
3658
let b = Repo:: clone( & url, & root.join ( " b" ) )
3659
.unwrap ( )
3660
.with_home ( root.join ( " home-b" ) ) ;
3661
assert_eq! ( b.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
3662
3663
3664
let mut c = Repo:: clone( & url, & root.join ( " c" ) )
3665
.unwrap ( )
3666
.with_home ( root.join ( " home-c" ) ) ;
3667
c.create_identity ( " Cy" , " [email protected] " , IdentityScope:: User)
3668
.unwrap ( ) ;
3669
let r = c.push_delete ( " origin" , " tags/v1" ) .unwrap ( ) ;
3670
assert! (
3671
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " may not withdraw" ) ,
3672
" {r:?}"
3673
) ;
3674
assert_eq! ( c.tag_heads ( " v1" ) .unwrap ( ) , None , " withdrawn there first" ) ;
3675
origin.refresh ( ) .unwrap ( ) ;
3676
assert_eq! ( origin.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
3677
3678
3679
let r = a.push_delete ( " origin" , " tags/v1" ) .unwrap ( ) ;
3680
assert! ( r[ 0 ] .ok & & r[ 0 ] .reason = = " withdrawn" , " {r:?}" ) ;
3681
origin.refresh ( ) .unwrap ( ) ;
3682
assert_eq! ( origin.tag_heads ( " v1" ) .unwrap ( ) , None ) ;
3683
assert_eq! ( a.tag_heads ( " v1" ) .unwrap ( ) , None ) ;
3684
assert! ( origin.tag_withdrawal ( " v1" ) .unwrap ( ) .is_some ( ) ) ;
3685
assert! ( origin.tag_attest ( " v1" ) .unwrap ( ) .is_some ( ) , " kept beside it" ) ;
3686
let refs = origin.remote_refs ( ) .unwrap ( ) ;
3687
assert! ( refs.tags.iter ( ) .all ( | t | t.name ! = " v1" ) ) ;
3688
assert_eq! ( refs.withdrawn.len ( ) , 1 ) ;
3689
let r = a.push_delete ( " origin" , " tags/v1" ) .unwrap ( ) ;
3690
assert! ( r[ 0 ] .ok & & r[ 0 ] .reason = = " already withdrawn" , " {r:?}" ) ;
3691
3692
3693
let rep = b.fetch ( " origin" ) .unwrap ( ) ;
3694
assert_eq! ( rep.withdrawn_tags, vec! [ " v1" .to_string ( ) ] ) ;
3695
assert_eq! ( b.tag_heads ( " v1" ) .unwrap ( ) , None ) ;
3696
assert! ( b.tag_create ( " v1" , & [ h1] , " " ) .is_err ( ) ) ;
3697
let r = origin
3698
.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ original] )
3699
.unwrap ( ) ;
3700
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " withdrawn" ) , " {r:?}" ) ;
3701
3702
3703
a.tag_delete ( " v2" , " tagged too early" ) .unwrap ( ) ;
3704
let r = a.push ( " origin" , false ) .unwrap ( ) ;
3705
assert! (
3706
r.iter ( )
3707
.any ( | x | x.name = = " tags/v2" & & x.ok & & x.reason = = " withdrawn" ) ,
3708
" {r:?}"
3709
) ;
3710
origin.refresh ( ) .unwrap ( ) ;
3711
assert_eq! ( origin.tag_heads ( " v2" ) .unwrap ( ) , None ) ;
3712
let ( _ , w) = origin.tag_withdrawal ( " v2" ) .unwrap ( ) .unwrap ( ) ;
3713
assert! (
3714
matches! ( & w.attest.body, AttestBody:: Untag { reason, .. } if reason = = " tagged too early" )
3715
) ;
3716
}
3717
3718
# [ test ]
132b884b Andy McCall 1 month ago ↶
3719
fn tags_are_immutable_named_states_and_travel ( ) {
3720
hermetic ( ) ;
3721
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3722
let root = dir.path ( ) ;
3723
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
3724
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
3725
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
3726
.unwrap ( )
3727
.with_home ( root.join ( " home-a" ) ) ;
3728
let ( key_a, _ ) = a
3729
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
3730
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3731
write ( & root.join ( " a" ) , " f.txt" , b " 1\n " ) ;
3732
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
3733
write ( & root.join ( " a" ) , " f.txt" , b " 1\n 2\n " ) ;
3734
let h2 = a.commit ( " two" , & [ ] ) .unwrap ( ) ;
3735
3736
let t = a.tag_create ( " v1" , & [ h1] , " first release" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3737
assert_eq! (
3738
( t.heads.clone ( ) , t.signer.as_deref ( ) ) ,
3739
( vec! [ h1] , Some ( " Ada" ) )
3740
) ;
132b884b Andy McCall 1 month ago ↶
3741
assert! ( a.tag_create ( " v1" , & [ h2] , " " ) .is_err ( ) , " immutable" ) ;
3742
assert! ( a.tag_create ( " bad/name" , & [ h2] , " " ) .is_err ( ) ) ;
3743
assert_eq! ( a.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
3744
assert_eq! ( a.resolve_heads ( " v1" ) .unwrap ( ) , vec! [ h1] ) ;
3745
assert_eq! ( a.resolve_heads ( " main" ) .unwrap ( ) , vec! [ h2] ) ;
3746
let ( _ , sa) = a.tag_attest ( " v1" ) .unwrap ( ) .unwrap ( ) ;
3747
assert_eq! ( sa.sigs[ 0 ] .key, key_a) ;
ee130dd9 Andy McCall 15 days ago ↶
3748
3749
3750
let mut refs = a.remote_refs ( ) .unwrap ( ) ;
3751
a.refs_for_client ( & mut refs, None ) ;
3752
assert_eq! ( refs.tags.len ( ) , 1 ) ;
132b884b Andy McCall 1 month ago ↶
3753
75167672 Andy McCall 1 month ago
3754
a.checkout ( & a.resolve_heads ( " v1" ) .unwrap ( ) , false , " co" )
3755
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3756
assert_eq! ( fs:: read( root.join ( " a/f.txt" ) ) .unwrap ( ) , b " 1\n " ) ;
75167672 Andy McCall 1 month ago
3757
a.checkout ( & a.bookmark_heads ( ) .unwrap ( ) , false , " co" )
3758
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3759
3760
3761
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
3762
origin.refresh ( ) .unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3763
assert! ( r.iter ( ) .any ( | x | x.name = = " tags/v1" & & x.ok ) , " {r:?}" ) ;
3764
assert_eq! ( origin.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
75167672 Andy McCall 1 month ago
3765
let mut forged = a
3766
.tag_refs ( )
3767
.unwrap ( )
3768
.into_iter ( )
3769
.find ( | t | t.name = = " v1" )
3770
.unwrap ( ) ;
3771
forged.state = origin
3772
.store ( )
3773
.put_state ( & StateObject:: new( vec! [ h2] ) )
3774
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3775
forged.heads = vec! [ h2] ;
3776
forged.attestation = None ;
75167672 Andy McCall 1 month ago
3777
let r = origin
3778
.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ forged] )
3779
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3780
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " immutable" ) , " {r:?}" ) ;
3781
3782
75167672 Andy McCall 1 month ago
3783
let b = Repo:: clone( & url, & root.join ( " b" ) )
3784
.unwrap ( )
3785
.with_home ( root.join ( " home-b" ) ) ;
132b884b Andy McCall 1 month ago ↶
3786
assert_eq! ( b.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
3787
assert! ( b.tags ( ) .unwrap ( ) [ 0 ] .signer.as_deref ( ) = = Some ( " Ada" ) ) ;
3788
a.tag_create ( " v2" , & [ h2] , " second" ) .unwrap ( ) ;
3789
a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
3790
origin.refresh ( ) .unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3791
let rep = b.fetch ( " origin" ) .unwrap ( ) ;
3792
assert_eq! ( rep.new_tags, 1 ) ;
3793
assert_eq! ( b.tag_heads ( " v2" ) .unwrap ( ) , Some ( vec! [ h2] ) ) ;
3794
3795
3796
a.policy_init ( ) .unwrap ( ) ;
3797
a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
3798
origin.refresh ( ) .unwrap ( ) ;
1ec6fdbc Andy McCall 16 days ago ↶
3799
let unattested = crate :: protocol:: TagRef:: new(
3800
" v3" .into ( ) ,
3801
origin
75167672 Andy McCall 1 month ago
3802
.store ( )
3803
.put_state ( & StateObject:: new( vec! [ h2] ) )
3804
.unwrap ( ) ,
1ec6fdbc Andy McCall 16 days ago ↶
3805
vec! [ h2] ,
3806
None ,
3807
) ;
75167672 Andy McCall 1 month ago
3808
let r = origin
3809
.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ unattested] )
3810
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3811
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " attestation" ) , " {r:?}" ) ;
3812
a.tag_create ( " v3" , & [ h2] , " " ) .unwrap ( ) ;
3813
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
3814
origin.refresh ( ) .unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3815
assert! ( r.iter ( ) .any ( | x | x.name = = " tags/v3" & & x.ok ) , " {r:?}" ) ;
3816
assert! ( a.gc ( true ) .is_ok ( ) ) ;
3817
assert_eq! ( a.tags ( ) .unwrap ( ) .len ( ) , 3 , " tags survive gc" ) ;
e5d9b051 Andy McCall 19 days ago ↶
3818
3819
3820
3821
3822
let rot = a.rotate_identity ( ) .unwrap ( ) ;
3823
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
3824
origin.refresh ( ) .unwrap ( ) ;
e5d9b051 Andy McCall 19 days ago ↶
3825
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
3826
let c = Repo:: clone( & url, & root.join ( " c" ) )
3827
.unwrap ( )
3828
.with_home ( root.join ( " home-c" ) ) ;
3829
assert_eq! ( c.tag_heads ( " v3" ) .unwrap ( ) , Some ( vec! [ h2] ) ) ;
3830
let ( _ , sp) = c.current_policy ( ) .unwrap ( ) .unwrap ( ) ;
3831
assert! ( ! sp.policy.authorizes ( & key_a, " tags/v9" ) ) ;
3832
let made = c.identity ( & rot.new) .unwrap ( ) .created;
3833
assert! ( c.predecessor_authorized ( & sp.policy, & key_a, " tags/v9" , made - 1 ) ) ;
3834
assert! ( ! c.predecessor_authorized ( & sp.policy, & key_a, " tags/v9" , made) ) ;
3835
assert! ( ! c.predecessor_authorized ( & sp.policy, & rot.new, " tags/v9" , 0 ) ) ;
132b884b Andy McCall 1 month ago ↶
3836
}
4e88e774 Andy McCall 1 month ago ↶
3837
3838
# [ test ]
3839
fn multiple_workspaces_share_a_repository ( ) {
3840
hermetic ( ) ;
3841
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3842
let root = dir.path ( ) ;
3843
let main_dir = root.join ( " main" ) ;
3844
let repo = Repo:: init( & main_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
3845
write ( & main_dir, " a.txt" , b " a\n " ) ;
3846
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
3847
assert_eq! ( repo.workspaces ( ) .unwrap ( ) .len ( ) , 1 ) ;
3848
3849
3850
let ws_dir = root.join ( " ws2" ) ;
4476cdbb Andy McCall 11 days ago ↶
3851
let ops = repo.op_log ( ) .unwrap ( ) .len ( ) ;
4e88e774 Andy McCall 1 month ago ↶
3852
let ws = repo.workspace_add ( & ws_dir, None ) .unwrap ( ) ;
4476cdbb Andy McCall 11 days ago ↶
3853
assert_eq! (
3854
ws.op_log ( ) .unwrap ( ) .len ( ) ,
3855
ops + 2 ,
3856
" the add and its checkout, and no snapshot finding the tree deleted (oxygen#137)"
3857
) ;
4e88e774 Andy McCall 1 month ago ↶
3858
assert! ( ws_dir.join ( " .oxy" ) .is_file ( ) ) ;
3859
assert_eq! ( fs:: read( ws_dir.join ( " a.txt" ) ) .unwrap ( ) , b " a\n " ) ;
3860
assert_ne! ( ws.workspace ( ) , repo.workspace ( ) ) ;
3861
let list = repo.workspaces ( ) .unwrap ( ) ;
3862
assert_eq! ( list.len ( ) , 2 ) ;
75167672 Andy McCall 1 month ago
3863
assert! ( list
3864
.iter ( )
3865
.any ( | w | w.id = = ws.workspace ( ) & & w.path = = ws_dir.canonicalize ( ) .unwrap ( ) & & ! w.current ) ) ;
4e88e774 Andy McCall 1 month ago ↶
3866
assert! ( ! ws.snapshot ( " status" ) .unwrap ( ) .changed) ;
3867
3868
3869
write ( & ws_dir, " b.txt" , b " b\n " ) ;
3870
let s = ws.snapshot ( " status" ) .unwrap ( ) ;
3871
assert_eq! ( s.statuses.len ( ) , 1 ) ;
75167672 Andy McCall 1 month ago
3872
assert! (
3873
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
3874
" the first workspace is untouched"
3875
) ;
4e88e774 Andy McCall 1 month ago ↶
3876
let h2 = ws.commit ( " b from ws2" , & [ ] ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
3877
repo.refresh ( ) .unwrap ( ) ;
4e88e774 Andy McCall 1 month ago ↶
3878
let mut heads = repo.bookmark_heads ( ) .unwrap ( ) ;
3879
heads.sort ( ) ;
3880
let mut want = vec! [ h1, h2] ;
3881
want.sort ( ) ;
3882
assert_eq! ( heads, want, " both workspaces see the bookmark move" ) ;
3883
assert! ( ! main_dir.join ( " b.txt" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
3884
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " co" )
3885
.unwrap ( ) ;
4e88e774 Andy McCall 1 month ago ↶
3886
assert! ( main_dir.join ( " b.txt" ) .exists ( ) ) ;
3887
3888
3889
let reopened = Repo:: open( & ws_dir) .unwrap ( ) ;
3890
assert_eq! ( reopened.workspace ( ) , ws.workspace ( ) ) ;
3891
assert_eq! ( reopened.id ( ) .unwrap ( ) , repo.id ( ) .unwrap ( ) ) ;
3892
assert_eq! ( reopened.quick_status ( ) .unwrap ( ) .heads, 2 ) ;
3893
3894
3895
assert! ( repo.workspace_add ( & main_dir.join ( " inner" ) , None ) .is_err ( ) ) ;
3896
assert! ( repo.workspace_forget ( & repo.workspace ( ) ) .is_err ( ) ) ;
3897
repo.workspace_forget ( & ws.workspace ( ) ) .unwrap ( ) ;
3898
assert_eq! ( repo.workspaces ( ) .unwrap ( ) .len ( ) , 1 ) ;
3899
assert! ( ws_dir.join ( " b.txt" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
3900
assert! (
3901
ws.snapshot ( " status" ) .is_err ( ) ,
3902
" a forgotten workspace is no longer in the view"
3903
) ;
4e88e774 Andy McCall 1 month ago ↶
3904
6caacfff Andy McCall 1 month ago ↶
3905
3906
4e88e774 Andy McCall 1 month ago ↶
3907
let lock = repo.lock_snapshot ( ) .unwrap ( ) ;
6caacfff Andy McCall 1 month ago ↶
3908
let again = repo.lock_working_copy ( ) .unwrap ( ) ;
3909
drop ( again) ;
4e88e774 Andy McCall 1 month ago ↶
3910
drop ( lock) ;
75167672 Andy McCall 1 month ago
3911
let lock_path = main_dir
3912
.join ( " .oxy/workspaces" )
3913
.join ( repo.workspace ( ) .to_hex ( ) )
3914
.join ( " lock" ) ;
4e88e774 Andy McCall 1 month ago ↶
3915
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 120 ) ;
6caacfff Andy McCall 1 month ago ↶
3916
let age = | p : & Path| fs:: File:: open( p) .unwrap ( ) .set_modified ( old) .unwrap ( ) ;
3917
fs:: write( & lock_path, std:: process:: id( ) .to_string ( ) ) .unwrap ( ) ;
3918
age ( & lock_path) ;
3919
assert! (
3920
! super :: workspace:: lock_is_stale( & lock_path) ,
3921
" a live process keeps its lock"
3922
) ;
3923
fs:: write( & lock_path, " 4294967295" ) .unwrap ( ) ;
3924
age ( & lock_path) ;
4e88e774 Andy McCall 1 month ago ↶
3925
assert! ( repo.lock_snapshot ( ) .is_ok ( ) , " stale lock taken over" ) ;
3926
}
cdb5beba Andy McCall 1 month ago ↶
3927
3928
# [ test ]
3929
fn attributes_binary_eol_nocompress_lockable ( ) {
3930
hermetic ( ) ;
3931
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3932
let root = dir.path ( ) ;
3933
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
3934
write (
3935
root,
3936
" .oxyattributes" ,
3937
b " *.dat binary nocompress\n *.txt eol=crlf\n assets/** lockable\n " ,
3938
) ;
cdb5beba Andy McCall 1 month ago ↶
3939
write ( root, " plain.dat" , b " no nul bytes here\n " ) ;
3940
write ( root, " notes.txt" , b " one\r \n two\r \n " ) ;
3941
write ( root, " assets/model.bin" , b " \0 model" ) ;
3942
let h = repo.commit ( " attrs" , & [ ] ) .unwrap ( ) ;
3943
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
3944
assert! (
3945
snap.files[ " plain.dat" ] .mode.opaque,
3946
" binary attribute forces opaque without NUL bytes"
3947
) ;
3948
assert! (
3949
matches! ( & snap.files[ " notes.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n " ) ,
3950
" stored with LF"
3951
) ;
3952
assert! (
3953
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
3954
" CRLF on disk equals LF in the state"
3955
) ;
cdb5beba Andy McCall 1 month ago ↶
3956
3957
let r = repo.gc ( true ) .unwrap ( ) ;
3958
assert! ( r.packed.objects > 0 ) ;
3959
3960
fs:: remove_file( root.join ( " notes.txt" ) ) .unwrap ( ) ;
3961
repo.checkout ( & [ h] , true , " co" ) .unwrap ( ) ;
3962
assert_eq! ( fs:: read( root.join ( " notes.txt" ) ) .unwrap ( ) , b " one\r \n two\r \n " ) ;
75167672 Andy McCall 1 month ago
3963
assert! ( fs:: metadata( root.join ( " assets/model.bin" ) )
3964
.unwrap ( )
3965
.permissions ( )
3966
.readonly ( ) ) ;
cdb5beba Andy McCall 1 month ago ↶
3967
assert! ( repo.lock_path ( " plain.dat" ) .is_err ( ) , " not lockable" ) ;
3968
repo.lock_path ( " assets/model.bin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3969
assert! ( ! fs:: metadata( root.join ( " assets/model.bin" ) )
3970
.unwrap ( )
3971
.permissions ( )
3972
.readonly ( ) ) ;
cdb5beba Andy McCall 1 month ago ↶
3973
assert! ( repo.is_locked ( " assets/model.bin" ) ) ;
3974
write ( root, " assets/model.bin" , b " \0 model2" ) ;
3975
repo.commit ( " edit model" , & [ ] ) .unwrap ( ) ;
3976
repo.unlock_path ( " assets/model.bin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3977
assert! ( fs:: metadata( root.join ( " assets/model.bin" ) )
3978
.unwrap ( )
3979
.permissions ( )
3980
.readonly ( ) ) ;
cdb5beba Andy McCall 1 month ago ↶
3981
3982
repo.checkout ( & [ h] , true , " co" ) .unwrap ( ) ;
3983
assert_eq! ( fs:: read( root.join ( " assets/model.bin" ) ) .unwrap ( ) , b " \0 model" ) ;
3984
}
6d3eefbc Andy McCall 1 month ago ↶
3985
3986
# [ cfg ( unix ) ]
3987
# [ test ]
3988
fn hooks_can_veto_observe_and_add_dependencies ( ) {
3989
use std:: os:: unix:: fs:: PermissionsExt;
3990
hermetic ( ) ;
3991
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3992
let root = dir.path ( ) ;
3993
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
3994
let hooks = root.join ( " .oxy/hooks" ) ;
3995
fs:: create_dir_all( & hooks) .unwrap ( ) ;
3996
let hook = | name : & str , body : & str | {
3997
let p = hooks.join ( name) ;
3998
fs:: write( & p, format! ( " #!/bin/sh\n {body} \n " ) ) .unwrap ( ) ;
3999
fs:: set_permissions( & p, fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
4000
} ;
4001
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" ) ;
4002
hook ( " post-commit" , " cat > \" $OXY_ROOT/.last-post-commit\" " ) ;
4003
hook ( " post-checkout" , " cat > \" $OXY_ROOT/.last-post-checkout\" " ) ;
4004
hook ( " pre-push" , " echo pushing blocked >&2; exit 3" ) ;
4005
write ( root, " .oxyignore" , b " .last-*\n " ) ;
4006
write ( root, " a.txt" , b " a\n " ) ;
4007
let err = repo.commit ( " WIP: nope" , & [ ] ) .unwrap_err ( ) .to_string ( ) ;
4008
assert! ( err.contains ( " pre-commit refused: no WIP commits" ) , " {err}" ) ;
4009
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
4010
let pre = fs:: read_to_string( root.join ( " .last-pre-commit" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4011
assert! (
4012
pre.contains ( " \" event\" :\" pre-commit\" " )
4013
& & pre.contains ( " \" path\" :\" a.txt\" " )
4014
& & pre.contains ( " \" message\" :\" first\" " ) ,
4015
" {pre}"
4016
) ;
6d3eefbc Andy McCall 1 month ago ↶
4017
let post = fs:: read_to_string( root.join ( " .last-post-commit" ) ) .unwrap ( ) ;
4018
assert! ( post.contains ( & h1.to_hex ( ) ) , " {post}" ) ;
4019
repo.checkout ( & [ h1] , false , " co" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4020
assert! ( fs:: read_to_string( root.join ( " .last-post-checkout" ) )
4021
.unwrap ( )
4022
.contains ( " post-checkout" ) ) ;
6d3eefbc Andy McCall 1 month ago ↶
4023
repo.config.set ( " remote.origin.url" , " /nowhere" ) .unwrap ( ) ;
4024
repo.save_config ( ) .unwrap ( ) ;
4025
let err = repo.push ( " origin" , false ) .unwrap_err ( ) .to_string ( ) ;
4026
assert! ( err.contains ( " pre-push refused: pushing blocked" ) , " {err}" ) ;
4027
4028
4029
write ( root, " b.txt" , b " b\n " ) ;
75167672 Andy McCall 1 month ago
4030
hook (
4031
" semantic-deps" ,
4032
& format! (
4033
" cat > \" $OXY_ROOT/.last-semantic\" ; echo '{{ \" deps\" : [\" {} \" ]}} '" ,
4034
h1.to_hex ( )
4035
) ,
4036
) ;
6d3eefbc Andy McCall 1 month ago ↶
4037
let h2 = repo.commit ( " b without hook" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4038
assert! (
4039
repo.store ( ) .get_change ( & h2) .unwrap ( ) .deps.is_empty ( ) ,
4040
" hook disabled by default"
4041
) ;
6d3eefbc Andy McCall 1 month ago ↶
4042
repo.config.set ( " hooks.semantic-deps" , " true" ) .unwrap ( ) ;
4043
repo.save_config ( ) .unwrap ( ) ;
4044
write ( root, " c.txt" , b " c\n " ) ;
4045
let h3 = repo.commit ( " c with hook" , & [ ] ) .unwrap ( ) ;
4046
assert_eq! ( repo.store ( ) .get_change ( & h3) .unwrap ( ) .deps, vec! [ h1] ) ;
4047
let sem = fs:: read_to_string( root.join ( " .last-semantic" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4048
assert! (
4049
sem.contains ( " \" touched_paths\" :[\" c.txt\" ]" ) & & sem.contains ( " candidate_deps" ) ,
4050
" {sem}"
4051
) ;
6d3eefbc Andy McCall 1 month ago ↶
4052
assert_eq! ( json_str ( " a\" b\\ c\n " ) , " \" a\\ \" b\\ \\ c\\ n\" " ) ;
4053
}
23b217d7 Andy McCall 1 month ago ↶
4054
4055
# [ test ]
4056
fn amend_into_older_change_and_split ( ) {
4057
hermetic ( ) ;
4058
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4059
let root = dir.path ( ) ;
4060
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
4061
repo.create_identity ( " Ada" , " a@b" , IdentityScope:: User)
4062
.unwrap ( ) ;
23b217d7 Andy McCall 1 month ago ↶
4063
repo.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
4064
write ( root, " a.txt" , b " one\n two\n " ) ;
4065
let h0 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4066
write ( root, " a.txt" , b " one\n TWO\n " ) ;
4067
let h1 = repo.commit ( " edit two" , & [ ] ) .unwrap ( ) ;
4068
write ( root, " a.txt" , b " one\n TWO\n three\n " ) ;
4069
let h2 = repo.commit ( " add three" , & [ ] ) .unwrap ( ) ;
4070
let id1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) .change_id;
4071
4072
4073
write ( root, " a.txt" , b " one\n TWO!\n three\n " ) ;
4074
let h1b = repo.amend_into ( & h1, None ) .unwrap ( ) ;
4075
assert_ne! ( h1b, h1) ;
4076
let c1b = repo.store ( ) .get_change ( & h1b) .unwrap ( ) ;
4077
assert_eq! ( c1b.change_id, id1) ;
4078
assert_eq! ( c1b.deps, vec! [ h0] ) ;
75167672 Andy McCall 1 month ago
4079
assert! ( matches! (
4080
repo.verify_change ( & h1b) .unwrap ( ) ,
4081
SignatureStatus:: Valid { .. }
4082
) ) ;
23b217d7 Andy McCall 1 month ago ↶
4083
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
4084
assert_eq! ( heads.len ( ) , 1 ) ;
4085
let top = repo.store ( ) .get_change ( & heads[ 0 ] ) .unwrap ( ) ;
4086
assert_eq! ( top.message, " add three" ) ;
75167672 Andy McCall 1 month ago
4087
assert_eq! (
4088
top.deps.iter ( ) .copied ( ) .collect:: < BTreeSet< _ > > ( ) ,
4089
[ h0, h1b] .into_iter ( ) .collect ( ) ,
4090
" depends on the file's creator and the rewritten line"
4091
) ;
23b217d7 Andy McCall 1 month ago ↶
4092
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
4093
assert! (
4094
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n TWO!\n three\n " )
4095
) ;
4096
assert! (
4097
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
4098
" the working copy is clean after folding"
4099
) ;
23b217d7 Andy McCall 1 month ago ↶
4100
4101
let only = repo.load_state ( & [ h1b] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
4102
assert! (
4103
matches! ( & only.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n TWO!\n " )
4104
) ;
23b217d7 Andy McCall 1 month ago ↶
4105
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 ) ;
4106
4107
4108
write ( root, " a.txt" , b " one\n TWO!\n three!\n " ) ;
4109
let err = repo.amend_into ( & h1b, None ) .unwrap_err ( ) .to_string ( ) ;
4110
assert! ( err.contains ( " introduced after" ) , " {err}" ) ;
75167672 Andy McCall 1 month ago
4111
assert! (
4112
repo.snapshot ( " status" ) .unwrap ( ) .statuses.len ( ) = = 1 ,
4113
" nothing was changed by the refusal"
4114
) ;
23b217d7 Andy McCall 1 month ago ↶
4115
write ( root, " a.txt" , b " one\n TWO!\n three\n " ) ;
4116
4117
4118
write ( root, " b.txt" , b " b\n " ) ;
4119
write ( root, " c.txt" , b " c\n " ) ;
4120
let h3 = repo.commit ( " b and c" , & [ ] ) .unwrap ( ) ;
4121
let id3 = repo.store ( ) .get_change ( & h3) .unwrap ( ) .change_id;
4122
let ( p1, p2) = repo.split ( & h3, & [ " b.txt" .into ( ) ] ) .unwrap ( ) ;
4123
let c_1 = repo.store ( ) .get_change ( & p1) .unwrap ( ) ;
4124
let c_2 = repo.store ( ) .get_change ( & p2) .unwrap ( ) ;
4125
assert! ( c_1 .message.contains ( " part 1: b.txt" ) ) ;
4126
assert_eq! ( c_2 .change_id, id3) ;
4127
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
75167672 Andy McCall 1 month ago
4128
assert! (
4129
heads.contains ( & p1) & & heads.contains ( & p2) ,
4130
" independent parts are both heads"
4131
) ;
4132
let snap = repo
4133
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
4134
.unwrap ( )
4135
.materialize ( ) ;
23b217d7 Andy McCall 1 month ago ↶
4136
assert! ( snap.files.contains_key ( " b.txt" ) & & snap.files.contains_key ( " c.txt" ) ) ;
4137
let only1 = repo.load_state ( & [ p1] ) .unwrap ( ) .materialize ( ) ;
4138
assert! ( only1.files.contains_key ( " b.txt" ) & & ! only1.files.contains_key ( " c.txt" ) ) ;
4139
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4140
assert! ( repo.split ( & p2, & [ " nope.txt" .into ( ) ] ) .is_err ( ) ) ;
4141
let _ = h2;
4142
}
28fc23d7 Andy McCall 1 month ago ↶
4143
4144
# [ test ]
4145
fn identity_lifecycle ( ) {
4146
hermetic ( ) ;
4147
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4148
let root = dir.path ( ) ;
4149
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
4150
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
4151
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
4152
.unwrap ( )
4153
.with_home ( root.join ( " home-a" ) ) ;
28fc23d7 Andy McCall 1 month ago ↶
4154
a.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4155
let ( key_a, _ ) = a
4156
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
4157
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4158
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
4159
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
4160
assert! ( a.policy_init ( ) .unwrap ( ) .applied) ;
4161
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
767ac6cd Andy McCall 18 days ago
4162
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4163
4164
75167672 Andy McCall 1 month ago
4165
let mut e = Repo:: init( & root.join ( " e" ) )
4166
.unwrap ( )
4167
.with_home ( root.join ( " home-e" ) ) ;
28fc23d7 Andy McCall 1 month ago ↶
4168
e.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4169
e.create_identity_with (
4170
" Eve" ,
4171
" [email protected] " ,
4172
IdentityScope:: User,
4173
Some ( Hlc:: now( ) .wall_ms - 1 ) ,
4174
)
4175
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4176
write ( & root.join ( " e" ) , " e.txt" , b " e\n " ) ;
4177
let he = e.commit ( " late" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4178
assert! (
4179
matches! ( e.verify_change ( & he) .unwrap ( ) , SignatureStatus:: Invalid( m) if m.contains ( " expired" ) )
4180
) ;
28fc23d7 Andy McCall 1 month ago ↶
4181
75167672 Andy McCall 1 month ago
4182
let r = e
4183
.renew_identity ( Some ( Hlc:: now( ) .wall_ms + 86_400_000 ) )
4184
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4185
assert_eq! ( e.identity ( & r.new) .unwrap ( ) .previous, Some ( r.old) ) ;
4186
write ( & root.join ( " e" ) , " e.txt" , b " e2\n " ) ;
4187
let he2 = e.commit ( " in time" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4188
assert! (
4189
matches! ( e.verify_change ( & he2) .unwrap ( ) , SignatureStatus:: Valid { key, .. } if key = = r.new)
4190
) ;
28fc23d7 Andy McCall 1 month ago ↶
4191
4192
4193
let r = a.rotate_identity ( ) .unwrap ( ) ;
4194
assert_eq! ( r.old, key_a) ;
4195
assert_eq! ( a.config.author_key, Some ( r.new) ) ;
4196
assert_eq! ( a.identity ( & r.new) .unwrap ( ) .previous, Some ( key_a) ) ;
4197
assert! ( r.policy.as_ref ( ) .unwrap ( ) .applied) ;
4198
let ( _ , sp) = a.current_policy ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4199
assert_eq! (
4200
( sp.policy.serial, sp.policy.roots.clone ( ) ) ,
4201
( 2 , vec! [ r.new] )
4202
) ;
4203
assert! (
4204
! sp.policy.revoked.contains ( & key_a) & & sp.policy.authorizes ( & r.new, " main" ) ,
4205
" superseded, not revoked"
4206
) ;
28fc23d7 Andy McCall 1 month ago ↶
4207
assert! ( sp.policy.succeeds ( & r.new, & key_a) & & ! sp.policy.succeeds ( & key_a, & r.new) ) ;
75167672 Andy McCall 1 month ago
4208
assert! (
4209
matches! ( a.verify_change ( & h1) .unwrap ( ) , SignatureStatus:: Valid { key, revoked: false , .. } if key = = key_a)
4210
) ;
28fc23d7 Andy McCall 1 month ago ↶
4211
write ( & root.join ( " a" ) , " a.txt" , b " a2\n " ) ;
4212
let h2 = a.commit ( " two" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4213
assert! (
4214
matches! ( a.verify_change ( & h2) .unwrap ( ) , SignatureStatus:: Valid { key, revoked: false , .. } if key = = r.new)
4215
) ;
28fc23d7 Andy McCall 1 month ago ↶
4216
4217
let res = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
4218
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4219
assert! ( res.iter ( ) .all ( | x | x.ok ) , " {res:?}" ) ;
4220
assert_eq! ( origin.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 2 ) ;
4221
let key_a = r.new;
4222
4223
4224
75167672 Andy McCall 1 month ago
4225
let mut b = Repo:: clone( & url, & root.join ( " b" ) )
4226
.unwrap ( )
4227
.with_home ( root.join ( " home-b" ) ) ;
4228
let ( key_b, id_b) = b
4229
.create_identity ( " Bob" , " [email protected] " , IdentityScope:: User)
4230
.unwrap ( ) ;
7ba155f2 Andy McCall 1 month ago ↶
4231
let r = a.delegate_add ( id_b.clone ( ) , Scope:: ALL , None ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4232
assert_eq! ( r.old, key_a) ;
4233
let key_a2 = r.new;
75167672 Andy McCall 1 month ago
4234
assert_eq! (
7ba155f2 Andy McCall 1 month ago ↶
4235
a.identity ( & key_a2) .unwrap ( ) .delegates,
4236
vec! [ Delegate:: full( key_b) ]
4237
) ;
4238
assert_eq! (
75167672 Andy McCall 1 month ago
4239
a.identity ( & key_a2) .unwrap ( ) .ed25519_pk,
4240
a.identity ( & key_a) .unwrap ( ) .ed25519_pk
7ba155f2 Andy McCall 1 month ago ↶
4241
) ;
4242
assert! (
4243
a.delegate_add ( id_b.clone ( ) , Scope:: ALL , None ) .is_err ( ) ,
4244
" already a delegate"
75167672 Andy McCall 1 month ago
4245
) ;
28fc23d7 Andy McCall 1 month ago ↶
4246
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | x | x.ok ) ) ;
767ac6cd Andy McCall 18 days ago
4247
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4248
write ( & root.join ( " a" ) , " a.txt" , b " a3\n " ) ;
4249
let h3 = a.commit ( " three" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4250
assert! (
4251
matches! ( a.verify_change ( & h3) .unwrap ( ) , SignatureStatus:: Valid { key, .. } if key = = key_a2) ,
4252
" the same secret signs under the new KeyId"
4253
) ;
28fc23d7 Andy McCall 1 month ago ↶
4254
4255
a.set_user_config ( " core.sign" , " seal" ) .unwrap ( ) ;
4256
write ( & root.join ( " a" ) , " a.txt" , b " a4\n " ) ;
4257
let h4 = a.commit ( " four" , & [ ] ) .unwrap ( ) ;
4258
assert_eq! ( a.verify_change ( & h4) .unwrap ( ) , SignatureStatus:: Unsigned) ;
4259
let ( _ , sk_b) = b.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4260
a.store ( )
4261
.put_signature ( & h4, & sk_b.sign ( Domain:: Change, & key_b, & h4) )
4262
.unwrap ( ) ;
4263
assert! (
4264
matches! ( a.verify_change ( & h4) .unwrap ( ) , SignatureStatus:: Valid { key, .. } if key = = key_b)
4265
) ;
28fc23d7 Andy McCall 1 month ago ↶
4266
a.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
4267
4268
let r = a.delegate_remove ( & key_b) .unwrap ( ) ;
4269
assert! ( a.identity ( & r.new) .unwrap ( ) .delegates.is_empty ( ) ) ;
4270
assert! ( a.delegate_remove ( & key_b) .is_err ( ) ) ;
4271
let key_a3 = r.new;
4272
let res = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
4273
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4274
assert! ( res.iter ( ) .all ( | x | x.ok ) , " {res:?}" ) ;
4275
4276
4277
4278
assert! ( a.policy_root_add ( id_b.clone ( ) ) .unwrap ( ) .applied) ;
4279
assert! ( a.policy_root_add ( id_b.clone ( ) ) .is_err ( ) ) ;
4280
assert! ( a.policy_set_threshold ( 3 ) .is_err ( ) ) ;
4281
assert! ( a.policy_set_threshold ( 2 ) .unwrap ( ) .applied) ;
4282
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | x | x.ok ) ) ;
767ac6cd Andy McCall 18 days ago
4283
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4284
b.pull ( " origin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4285
assert_eq! (
4286
b.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.root_threshold,
4287
2
4288
) ;
4289
let mut c = Repo:: init( & root.join ( " c" ) )
4290
.unwrap ( )
4291
.with_home ( root.join ( " home-c" ) ) ;
4292
let ( key_c, id_c) = c
4293
.create_identity ( " Cy" , " [email protected] " , IdentityScope:: User)
4294
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4295
let serial_before = a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial;
4296
let proposal = a.policy_grant ( id_c, None ) .unwrap ( ) ;
4297
assert! ( ! proposal.applied & & proposal.sigs.len ( ) = = 1 ) ;
4298
assert_eq! ( proposal.policy.serial, serial_before + 1 ) ;
75167672 Andy McCall 1 month ago
4299
assert_eq! (
4300
a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial,
4301
serial_before,
4302
" a proposal changes nothing yet"
4303
) ;
28fc23d7 Andy McCall 1 month ago ↶
4304
4305
let bytes = proposal.signed ( ) .encode ( ) ;
75167672 Andy McCall 1 month ago
4306
let signed = b
4307
.policy_sign ( SignedPolicy:: decode( & bytes) .unwrap ( ) )
4308
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4309
assert! ( signed.applied & & signed.sigs.len ( ) = = 2 ) ;
75167672 Andy McCall 1 month ago
4310
assert! ( b
4311
.current_policy ( )
4312
.unwrap ( )
4313
.unwrap ( )
4314
.1
4315
.policy
4316
.authorizes ( & key_c, " main" ) ) ;
4317
assert! (
4318
b.policy_sign ( signed.signed ( ) ) .is_err ( ) ,
4319
" a stale proposal is refused"
4320
) ;
28fc23d7 Andy McCall 1 month ago ↶
4321
75167672 Andy McCall 1 month ago
4322
assert! ( b
4323
.push ( " origin" , false )
4324
.unwrap ( )
4325
.iter ( )
4326
.any ( | x | x.name = = " policy" & & x.ok ) ) ;
767ac6cd Andy McCall 18 days ago
4327
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4328
a.pull ( " origin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4329
assert_eq! (
4330
a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial,
4331
serial_before + 1
4332
) ;
28fc23d7 Andy McCall 1 month ago ↶
4333
4334
assert! ( a.policy_revoke ( & key_a3) .is_err ( ) ) ;
75167672 Andy McCall 1 month ago
4335
let u = a
4336
.revoke_identity ( & key_c, " left the project" )
4337
.unwrap ( )
4338
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4339
assert! ( ! u.applied) ;
4340
assert! ( a.is_revoked ( & key_c) .unwrap ( ) ) ;
4341
assert! ( ! b.is_revoked ( & key_c) .unwrap ( ) ) ;
4342
let u = b.policy_sign ( u.signed ( ) ) .unwrap ( ) ;
4343
assert! ( u.applied & & u.policy.is_revoked ( & key_c) ) ;
4344
assert! ( b.is_revoked ( & key_c) .unwrap ( ) ) ;
4345
75167672 Andy McCall 1 month ago
4346
assert! (
4347
b.policy_revoke ( & key_a3) .is_err ( ) ,
4348
" would leave one root for a threshold of 2"
4349
) ;
28fc23d7 Andy McCall 1 month ago ↶
4350
4351
let key_c_own = c.config.author_key.unwrap ( ) ;
75167672 Andy McCall 1 month ago
4352
assert! ( c
4353
.revoke_identity ( & key_c_own, " lost laptop" )
4354
.unwrap ( )
4355
.is_none ( ) ) ;
28fc23d7 Andy McCall 1 month ago ↶
4356
assert! ( c.is_revoked ( & key_c_own) .unwrap ( ) ) ;
4357
assert_eq! ( key_c_own, key_c) ;
4358
}
4359
6e723b81 Andy McCall 1 month ago ↶
4360
# [ test ]
4361
fn renames_are_detected_and_tracked_as_moves ( ) {
4362
hermetic ( ) ;
4363
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4364
let root = dir.path ( ) ;
4365
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
4366
write (
4367
root,
4368
" src/old.rs" ,
4369
b " fn a() {}\n fn b() {}\n fn c() {}\n fn d() {}\n " ,
4370
) ;
6e723b81 Andy McCall 1 month ago ↶
4371
write ( root, " img.bin" , b " \x89 PNG\0 \0 \x01 \x02 " ) ;
4372
write ( root, " tiny.txt" , b " x\n " ) ;
4373
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4374
let node_old = repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) .files[ " src/old.rs" ] .node;
4375
let node_img = repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) .files[ " img.bin" ] .node;
4376
4377
4378
4379
fs:: remove_file( root.join ( " src/old.rs" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4380
write (
4381
root,
4382
" lib/new.rs" ,
4383
b " fn a() {}\n fn b() {}\n fn c() {}\n fn e() {}\n " ,
4384
) ;
6e723b81 Andy McCall 1 month ago ↶
4385
fs:: remove_file( root.join ( " img.bin" ) ) .unwrap ( ) ;
4386
write ( root, " assets/img.bin" , b " \x89 PNG\0 \0 \x01 \x02 " ) ;
4387
fs:: remove_file( root.join ( " tiny.txt" ) ) .unwrap ( ) ;
4388
write ( root, " other.txt" , b " completely different\n " ) ;
4389
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4390
let statuses: Vec< ( & str , ChangeStatus) > = snap
4391
.statuses
4392
.iter ( )
4393
.map ( | s | ( s.path.as_str ( ) , s.status) )
4394
.collect ( ) ;
6e723b81 Andy McCall 1 month ago ↶
4395
assert_eq! (
4396
statuses,
75167672 Andy McCall 1 month ago
4397
vec! [
4398
( " assets/img.bin" , ChangeStatus:: Renamed) ,
4399
( " lib/new.rs" , ChangeStatus:: Renamed) ,
4400
( " other.txt" , ChangeStatus:: Added) ,
4401
( " src" , ChangeStatus:: Removed) ,
4402
( " tiny.txt" , ChangeStatus:: Removed)
4403
]
6e723b81 Andy McCall 1 month ago ↶
4404
) ;
4405
let h2 = repo.commit ( " move things" , & [ ] ) .unwrap ( ) ;
4406
let c = repo.store ( ) .get_change ( & h2) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4407
let moves: Vec< _ > = c
4408
.tree_ops
4409
.iter ( )
4410
.filter ( | op | matches! ( op, TreeOp:: Move { .. } ) )
4411
.collect ( ) ;
6e723b81 Andy McCall 1 month ago ↶
4412
assert_eq! ( moves.len ( ) , 2 , " {:?}" , c.tree_ops) ;
75167672 Andy McCall 1 month ago
4413
assert! ( c.tree_ops.iter ( ) .any (
4414
| op | matches! ( op, TreeOp:: Move { node, name, .. } if * node = = node_old & & name = = " new.rs" )
4415
) ) ;
4416
assert! ( c.tree_ops.iter ( ) .any (
4417
| op | matches! ( op, TreeOp:: Move { node, name, .. } if * node = = node_img & & name = = " img.bin" )
4418
) ) ;
6e723b81 Andy McCall 1 month ago ↶
4419
let snap = repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
4420
assert_eq! (
4421
snap.files[ " lib/new.rs" ] .node, node_old,
4422
" the node survives the rename"
4423
) ;
4424
assert! (
4425
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 " )
4426
) ;
6e723b81 Andy McCall 1 month ago ↶
4427
assert! ( ! snap.files.contains_key ( " src/old.rs" ) & & ! snap.files.contains_key ( " src" ) ) ;
75167672 Andy McCall 1 month ago
4428
assert! ( render_change (
4429
& c,
4430
& repo.load_state ( & [ h1] ) .unwrap ( ) ,
4431
& repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( )
4432
)
0912a033 Andy McCall 1 month ago ↶
4433
.contains ( " R src/old.rs -> lib/new.rs" ) ) ;
6e723b81 Andy McCall 1 month ago ↶
4434
4435
let other = tempfile:: tempdir( ) .unwrap ( ) ;
4436
let clone = Repo:: clone( & root.to_string_lossy ( ) , other.path ( ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4437
assert_eq! (
4438
fs:: read( other.path ( ) .join ( " lib/new.rs" ) ) .unwrap ( ) ,
4439
b " fn a() {}\n fn b() {}\n fn c() {}\n fn e() {}\n "
4440
) ;
6e723b81 Andy McCall 1 month ago ↶
4441
assert! ( ! other.path ( ) .join ( " src" ) .exists ( ) ) ;
4442
assert! ( ! clone.snapshot ( " status" ) .unwrap ( ) .changed) ;
4443
4444
assert_eq! ( similarity ( b " a\n b\n " , b " a\n b\n " ) , 100 ) ;
4445
assert_eq! ( similarity ( b " a\n b\n c\n d\n " , b " a\n b\n x\n y\n " ) , 50 ) ;
4446
assert_eq! ( similarity ( b " hello\n " , b " world\n " ) , 0 ) ;
4447
assert_eq! ( similarity ( b " " , b " x\n " ) , 0 ) ;
4448
}
4449
30ea9c1e Andy McCall 1 month ago ↶
4450
# [ test ]
4451
fn git_fetch_and_push_round_trip ( ) {
4452
hermetic ( ) ;
75167672 Andy McCall 1 month ago
4453
if std:: process:: Command:: new( " git" )
4454
.arg ( " --version" )
4455
.output ( )
4456
.is_err ( )
4457
{
30ea9c1e Andy McCall 1 month ago ↶
4458
eprintln! ( " git not installed; skipping" ) ;
4459
return ;
4460
}
4461
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4462
let root = dir.path ( ) ;
4463
let git_in = | d : & Path, args : & [& str ]| -> String {
4464
let out = std:: process:: Command:: new( " git" )
4465
.arg ( " -C" )
4466
.arg ( d)
75167672 Andy McCall 1 month ago
4467
.args ( [
4468
" -c" ,
4469
" user.name=Grace" ,
4470
" -c" ,
4471
" [email protected] " ,
4472
" -c" ,
4473
" commit.gpgsign=false" ,
4474
] )
30ea9c1e Andy McCall 1 month ago ↶
4475
.args ( args)
4476
.output ( )
4477
.unwrap ( ) ;
75167672 Andy McCall 1 month ago
4478
assert! (
4479
out.status.success ( ) ,
4480
" git {args:?}: {}" ,
4481
String :: from_utf8_lossy( & out.stderr)
4482
) ;
30ea9c1e Andy McCall 1 month ago ↶
4483
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
4484
} ;
4485
4486
let remote = root.join ( " remote.git" ) ;
4487
fs:: create_dir_all( & remote) .unwrap ( ) ;
4488
git_in ( & remote, & [ " init" , " -q" , " --bare" , " -b" , " main" ] ) ;
4489
let work = root.join ( " work" ) ;
75167672 Andy McCall 1 month ago
4490
git_in (
4491
root,
4492
& [
4493
" clone" ,
4494
" -q" ,
4495
remote.to_str ( ) .unwrap ( ) ,
4496
work.to_str ( ) .unwrap ( ) ,
4497
] ,
4498
) ;
30ea9c1e Andy McCall 1 month ago ↶
4499
git_in ( & work, & [ " checkout" , " -q" , " -b" , " main" ] ) ;
4500
write ( & work, " README.md" , b " # Project\n " ) ;
4501
write ( & work, " src/lib.rs" , b " pub fn a() {}\n " ) ;
4502
git_in ( & work, & [ " add" , " ." ] ) ;
4503
git_in ( & work, & [ " commit" , " -q" , " -m" , " initial" ] ) ;
4504
write ( & work, " src/lib.rs" , b " pub fn a() {}\n pub fn b() {}\n " ) ;
4505
git_in ( & work, & [ " commit" , " -q" , " -am" , " second" ] ) ;
4506
git_in ( & work, & [ " push" , " -q" , " origin" , " main" ] ) ;
4507
let tip1 = git_in ( & work, & [ " rev-parse" , " HEAD" ] ) ;
4508
4509
4510
let o = root.join ( " oxy" ) ;
4511
let mut repo = Repo:: init( & o) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
4512
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
4513
.unwrap ( ) ;
30ea9c1e Andy McCall 1 month ago ↶
4514
let url = remote.to_string_lossy ( ) .to_string ( ) ;
4515
let r = repo.git_fetch ( & url, " main" ) .unwrap ( ) ;
4516
assert_eq! ( ( r.commits, r.heads) , ( 2 , 1 ) ) ;
75167672 Andy McCall 1 month ago
4517
assert_eq! (
4518
fs:: read( o.join ( " src/lib.rs" ) ) .unwrap ( ) ,
4519
b " pub fn a() {}\n pub fn b() {}\n "
4520
) ;
30ea9c1e Andy McCall 1 month ago ↶
4521
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
75167672 Andy McCall 1 month ago
4522
assert_eq! (
4523
repo.git_fetch ( & url, " main" ) .unwrap ( ) .commits,
4524
0 ,
4525
" nothing new"
4526
) ;
30ea9c1e Andy McCall 1 month ago ↶
4527
4528
75167672 Andy McCall 1 month ago
4529
write (
4530
& o,
4531
" src/lib.rs" ,
4532
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n " ,
4533
) ;
30ea9c1e Andy McCall 1 month ago ↶
4534
write ( & o, " NOTES.md" , b " from oxy\n " ) ;
4535
let h_oxy = repo.commit ( " add c and notes" , & [ ] ) .unwrap ( ) ;
4536
let r = repo.git_push ( & url, " main" ) .unwrap ( ) ;
4537
assert_eq! ( r.exported, 1 , " {r:?}" ) ;
4538
git_in ( & work, & [ " pull" , " -q" , " --ff-only" , " origin" , " main" ] ) ;
75167672 Andy McCall 1 month ago
4539
assert_eq! (
4540
git_in ( & work, & [ " log" , " --format=%s" , " main" ] ) ,
4541
" add c and notes\n second\n initial"
4542
) ;
30ea9c1e Andy McCall 1 month ago ↶
4543
assert_eq! ( git_in ( & work, & [ " rev-parse" , " HEAD~1" ] ) , tip1) ;
4544
assert_eq! ( fs:: read( work.join ( " NOTES.md" ) ) .unwrap ( ) , b " from oxy\n " ) ;
4545
assert_eq! ( git_in ( & work, & [ " log" , " -1" , " --format=%an" , " main" ] ) , " Ada" ) ;
75167672 Andy McCall 1 month ago
4546
assert! ( git_in ( & work, & [ " log" , " -1" , " --format=%B" , " main" ] )
4547
.contains ( & format! ( " Oxy-Hash: {} " , h_oxy.to_hex ( ) ) ) ) ;
4548
assert_eq! (
4549
repo.git_push ( & url, " main" ) .unwrap ( ) .exported,
4550
0 ,
4551
" already pushed"
4552
) ;
30ea9c1e Andy McCall 1 month ago ↶
4553
4554
4555
4556
write ( & work, " README.md" , b " # Project\n \n More.\n " ) ;
4557
git_in ( & work, & [ " commit" , " -q" , " -am" , " readme from git" ] ) ;
4558
git_in ( & work, & [ " push" , " -q" , " origin" , " main" ] ) ;
4559
let tip2 = git_in ( & work, & [ " rev-parse" , " HEAD" ] ) ;
75167672 Andy McCall 1 month ago
4560
assert! ( repo
4561
.git_push ( & url, " main" )
4562
.unwrap_err ( )
4563
.to_string ( )
4564
.contains ( " fetch" ) ) ;
30ea9c1e Andy McCall 1 month ago ↶
4565
let r = repo.git_fetch ( & url, " main" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4566
assert_eq! (
4567
( r.commits, r.heads) ,
4568
( 1 , 2 ) ,
4569
" the Git change and the oxy change commute"
4570
) ;
4571
assert_eq! (
4572
fs:: read( o.join ( " README.md" ) ) .unwrap ( ) ,
4573
b " # Project\n \n More.\n "
4574
) ;
30ea9c1e Andy McCall 1 month ago ↶
4575
assert_eq! ( fs:: read( o.join ( " NOTES.md" ) ) .unwrap ( ) , b " from oxy\n " ) ;
4576
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4577
4578
write ( & o, " NOTES.md" , b " from oxy\n again\n " ) ;
4579
repo.commit ( " notes again" , & [ ] ) .unwrap ( ) ;
4580
assert_eq! ( repo.git_push ( & url, " main" ) .unwrap ( ) .exported, 1 ) ;
4581
git_in ( & work, & [ " pull" , " -q" , " --ff-only" , " origin" , " main" ] ) ;
4582
assert_eq! ( git_in ( & work, & [ " rev-parse" , " HEAD~1" ] ) , tip2) ;
75167672 Andy McCall 1 month ago
4583
assert_eq! (
4584
fs:: read( work.join ( " NOTES.md" ) ) .unwrap ( ) ,
4585
b " from oxy\n again\n "
4586
) ;
4587
assert_eq! (
4588
git_in ( & work, & [ " log" , " --format=%s" , " main" ] )
4589
.lines ( )
4590
.count ( ) ,
4591
5
4592
) ;
30ea9c1e Andy McCall 1 month ago ↶
4593
}
4594
2b872d07 Andy McCall 1 month ago ↶
4595
4596
4597
# [ test ]
4598
# [ ignore ]
4599
fn bench_one_gibibyte_file ( ) {
4600
hermetic ( ) ;
4601
4602
let dir = match std:: env:: var_os( " OXY_BENCH_DIR" ) {
4603
Some ( d) => tempfile:: tempdir_in( d) .unwrap ( ) ,
4604
None => tempfile:: tempdir( ) .unwrap ( ) ,
4605
} ;
4606
let root = dir.path ( ) ;
75167672 Andy McCall 1 month ago
4607
let repo = Repo:: init( & root.join ( " r" ) )
4608
.unwrap ( )
4609
.with_home ( root.join ( " home" ) ) ;
2b872d07 Andy McCall 1 month ago ↶
4610
let size = 1 usize < < 30 ;
4611
let mut data = vec! [ 0 u8 ; size] ;
4612
let mut x = 0x9e3779b97f4a7c15 u64 ;
4613
for chunk in data.chunks_mut ( 8 ) {
4614
x ^ = x < < 13 ;
4615
x ^ = x > > 7 ;
4616
x ^ = x < < 17 ;
4617
let b = x.to_le_bytes ( ) ;
4618
chunk.copy_from_slice ( & b[ .. chunk.len ( ) ] ) ;
4619
}
4620
let t = std:: time:: Instant:: now( ) ;
4621
write ( & root.join ( " r" ) , " big.bin" , & data) ;
4622
eprintln! ( " write 1 GiB to disk: {:.2?}" , t.elapsed ( ) ) ;
4623
let t = std:: time:: Instant:: now( ) ;
4624
let h = repo.commit ( " big" , & [ ] ) .unwrap ( ) ;
4625
eprintln! ( " commit (chunk + hash + store): {:.2?}" , t.elapsed ( ) ) ;
4626
let t = std:: time:: Instant:: now( ) ;
4627
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4628
eprintln! ( " status on a clean 1 GiB working copy: {:.2?}" , t.elapsed ( ) ) ;
4629
let t = std:: time:: Instant:: now( ) ;
4630
let objects = repo.store ( ) .list ( ) .unwrap ( ) .len ( ) ;
4631
eprintln! ( " objects stored: {objects} ({:.2?} to list)" , t.elapsed ( ) ) ;
4632
let t = std:: time:: Instant:: now( ) ;
4633
let g = repo.gc ( true ) .unwrap ( ) ;
4634
eprintln! ( " gc (pack): {:.2?} {g:?}" , t.elapsed ( ) ) ;
4635
let t = std:: time:: Instant:: now( ) ;
4636
let clone = Repo:: clone( & root.join ( " r" ) .to_string_lossy ( ) , & root.join ( " c" ) ) .unwrap ( ) ;
4637
eprintln! ( " clone (fetch + checkout): {:.2?}" , t.elapsed ( ) ) ;
4638
let t = std:: time:: Instant:: now( ) ;
4639
let back = fs:: read( root.join ( " c/big.bin" ) ) .unwrap ( ) ;
4640
assert_eq! ( blake3:: hash( & back) , blake3:: hash( & data) ) ;
4641
eprintln! ( " read back and verify: {:.2?}" , t.elapsed ( ) ) ;
4642
assert_eq! ( clone.bookmark_heads ( ) .unwrap ( ) , vec! [ h] ) ;
4643
let store_bytes: u64 = walkdir_size ( & root.join ( " r/.oxy/objects" ) ) ;
75167672 Andy McCall 1 month ago
4644
eprintln! (
4645
" store size: {:.1} MiB for 1 GiB of data" ,
4646
store_bytes as f64 / ( 1024. 0 * 1024. 0 )
4647
) ;
2b872d07 Andy McCall 1 month ago ↶
4648
}
4649
4650
fn walkdir_size ( p : & Path) -> u64 {
4651
let mut total = 0 ;
4652
if let Ok ( rd) = fs:: read_dir( p) {
4653
for e in rd.flatten ( ) {
4654
let path = e.path ( ) ;
4655
if path.is_dir ( ) {
4656
total + = walkdir_size ( & path) ;
4657
} else if let Ok ( m) = fs:: metadata( & path) {
4658
total + = m.len ( ) ;
4659
}
4660
}
4661
}
4662
total
4663
}
4664
b7f102fc Andy McCall 1 month ago ↶
4665
4666
4667
# [ cfg ( unix ) ]
4668
# [ test ]
4669
fn stat_cache_skips_reading_unchanged_opaque_files ( ) {
4670
use std:: os:: unix:: fs:: PermissionsExt;
4671
hermetic ( ) ;
4672
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4673
let root = dir.path ( ) ;
4674
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4675
let mut big = vec! [ 0 u8 ; 300 * 1024 ] ;
4676
for ( i, b) in big.iter_mut ( ) .enumerate ( ) {
4677
* b = ( i as u32 ) .wrapping_mul ( 2654435761 ) .to_le_bytes ( ) [ 1 ] ;
4678
}
4679
big[ 0 ] = 0 ;
4680
write ( root, " big.bin" , & big) ;
4681
write ( root, " a.txt" , b " text\n " ) ;
4682
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4683
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4684
assert! (
4685
m.entries[ " big.bin" ] .blob.is_some ( ) ,
4686
" opaque files record their blob"
4687
) ;
b7f102fc Andy McCall 1 month ago ↶
4688
assert! ( m.entries[ " a.txt" ] .blob.is_none ( ) ) ;
4689
let blob = m.entries[ " big.bin" ] .blob.unwrap ( ) ;
4690
assert! ( repo.store ( ) .has ( & blob) ) ;
4691
4692
4693
4694
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
75167672 Andy McCall 1 month ago
4695
fs:: File:: options( )
4696
.write ( true )
4697
.open ( root.join ( " big.bin" ) )
4698
.unwrap ( )
4699
.set_modified ( old)
4700
.unwrap ( ) ;
b7f102fc Andy McCall 1 month ago ↶
4701
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4702
4703
fs:: set_permissions( root.join ( " big.bin" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
4704
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4705
assert! (
4706
! snap.changed & & snap.statuses.is_empty ( ) ,
4707
" {:?}" ,
4708
snap.statuses
4709
) ;
b7f102fc Andy McCall 1 month ago ↶
4710
fs:: set_permissions( root.join ( " big.bin" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
4711
4712
let mut big2 = big.clone ( ) ;
4713
big2[ 10 ] ^ = 0xff ;
4714
write ( root, " big.bin" , & big2) ;
4715
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4716
assert_eq! (
4717
snap.statuses
4718
.iter ( )
4719
.map ( | s | ( s.path.as_str ( ) , s.status) )
4720
.collect:: < Vec< _ > > ( ) ,
4721
vec! [ ( " big.bin" , ChangeStatus:: Modified) ]
4722
) ;
b7f102fc Andy McCall 1 month ago ↶
4723
let h2 = repo.commit ( " edit" , & [ ] ) .unwrap ( ) ;
4724
let files = repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
4725
assert_eq! (
4726
files[ " big.bin" ] .content,
4727
FileContent:: Opaque {
4728
blobs: vec! [ Repo:: blob_ref( & big2) ]
4729
}
4730
) ;
b7f102fc Andy McCall 1 month ago ↶
4731
4732
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
4733
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
4734
assert_eq! ( m.entries[ " big.bin" ] .blob, Some ( Repo:: blob_ref( & big) ) ) ;
4735
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4736
}
4737
50a046e4 Andy McCall 1 month ago ↶
4738
# [ test ]
4739
fn status_json_lists_conflicts_with_contributors ( ) {
4740
hermetic ( ) ;
4741
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4742
let root = dir.path ( ) ;
4743
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4744
write ( root, " a.txt" , b " one\n " ) ;
4745
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4746
write ( root, " a.txt" , b " one\n left\n " ) ;
4747
let ha = repo.commit ( " left" , & [ ] ) .unwrap ( ) ;
4748
repo.checkout ( & [ h1] , true , " checkout" ) .unwrap ( ) ;
4749
write ( root, " a.txt" , b " one\n right\n " ) ;
4750
let hb = repo.commit ( " right" , & [ ] ) .unwrap ( ) ;
4751
repo.checkout ( & [ ha, hb] , true , " checkout" ) .unwrap ( ) ;
4752
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4753
assert! ( snap.base_snapshot.is_conflicted ( ) ) ;
4754
let json = repo.status_json ( & snap) .unwrap ( ) ;
4755
let ida = repo.store ( ) .get_change ( & ha) .unwrap ( ) .change_id.to_hex ( ) ;
4756
let idb = repo.store ( ) .get_change ( & hb) .unwrap ( ) .change_id.to_hex ( ) ;
4757
assert! ( json.contains ( " \" conflicted\" :true" ) , " {json}" ) ;
75167672 Andy McCall 1 month ago
4758
assert! (
4759
json.contains ( " \" kind\" :\" text\" ,\" path\" :\" a.txt\" " ) ,
4760
" {json}"
4761
) ;
50a046e4 Andy McCall 1 month ago ↶
4762
assert! ( json.contains ( & ida) & & json.contains ( & idb) , " {json}" ) ;
4763
assert! ( json.contains ( " \" resolved\" :false" ) , " {json}" ) ;
4764
assert! ( json.starts_with ( ' {' ) & & json.ends_with ( ' }' ) ) ;
4765
4766
write ( root, " a.txt" , b " one\n left\n right\n " ) ;
4767
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4768
let json = repo.status_json ( & snap) .unwrap ( ) ;
4769
assert! ( json.contains ( " \" resolved\" :true" ) , " {json}" ) ;
4770
repo.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
4771
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4772
let json = repo.status_json ( & snap) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4773
assert! (
4774
json.contains ( " \" clean\" :true,\" conflicted\" :false,\" paths\" :[],\" conflicts\" :[]" ) ,
4775
" {json}"
4776
) ;
50a046e4 Andy McCall 1 month ago ↶
4777
}
4778
9ac1fbe2 Andy McCall 1 month ago ↶
4779
# [ test ]
4780
fn ignore_rules_do_not_untrack_files ( ) {
4781
hermetic ( ) ;
4782
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4783
let root = dir.path ( ) ;
4784
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4785
write ( root, " src.c" , b " int x;\n " ) ;
4786
write ( root, " build/out.o" , b " \0 obj" ) ;
4787
write ( root, " sub/keep.log" , b " keep\n " ) ;
4788
write ( root, " sub/junk.log" , b " junk\n " ) ;
4789
write ( root, " .oxyignore" , b " /build\n *.log\n !sub/keep.log\n " ) ;
4790
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4791
let added: Vec< & str > = snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect ( ) ;
75167672 Andy McCall 1 month ago
4792
assert_eq! (
4793
added,
4794
vec! [ " .oxyignore" , " src.c" , " sub/keep.log" ] ,
4795
" anchored, glob, and negated rules"
4796
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4797
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4798
4799
write ( root, " .oxyignore" , b " /build\n *.log\n !sub/keep.log\n src.c\n " ) ;
4800
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4801
assert_eq! (
4802
snap.statuses
4803
.iter ( )
4804
.map ( | s | s.path.as_str ( ) )
4805
.collect:: < Vec< _ > > ( ) ,
4806
vec! [ " .oxyignore" ]
4807
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4808
let h2 = repo.commit ( " ignore src.c" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4809
assert! ( repo
4810
.load_state ( & [ h2] )
4811
.unwrap ( )
4812
.materialize ( )
4813
.files
4814
.contains_key ( " src.c" ) ) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4815
4816
assert_eq! ( repo.quick_dirty ( ) .unwrap ( ) , Some ( false ) ) ;
4817
write ( root, " src.c" , b " int x, y;\n " ) ;
4818
assert_eq! ( repo.quick_dirty ( ) .unwrap ( ) , Some ( true ) ) ;
4819
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4820
assert_eq! (
4821
snap.statuses
4822
.iter ( )
4823
.map ( | s | ( s.path.as_str ( ) , s.status) )
4824
.collect:: < Vec< _ > > ( ) ,
4825
vec! [ ( " src.c" , ChangeStatus:: Modified) ]
4826
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4827
let h3 = repo.commit ( " edit src.c" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4828
assert! (
4829
matches! ( & repo.load_state ( & [ h3] ) .unwrap ( ) .materialize ( ) .files[ " src.c" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " int x, y;\n " )
4830
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4831
4832
fs:: remove_file( root.join ( " src.c" ) ) .unwrap ( ) ;
4833
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4834
assert_eq! (
4835
snap.statuses
4836
.iter ( )
4837
.map ( | s | ( s.path.as_str ( ) , s.status) )
4838
.collect:: < Vec< _ > > ( ) ,
4839
vec! [ ( " src.c" , ChangeStatus:: Removed) ]
4840
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4841
repo.checkout ( & [ h3] , true , " checkout" ) .unwrap ( ) ;
4842
assert_eq! ( fs:: read( root.join ( " src.c" ) ) .unwrap ( ) , b " int x, y;\n " ) ;
4843
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4844
let _ = h1;
4845
}
4846
b837d2b5 Andy McCall 1 month ago ↶
4847
# [ test ]
4848
fn split_works_on_non_heads_by_node ( ) {
4849
hermetic ( ) ;
4850
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4851
let root = dir.path ( ) ;
4852
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4853
write ( root, " a.txt" , b " one\n " ) ;
4854
write ( root, " junk/x.lock" , b " lock\n " ) ;
4855
let h1 = repo.commit ( " work plus junk" , & [ ] ) .unwrap ( ) ;
4856
write ( root, " a.txt" , b " one\n two\n " ) ;
4857
let h2 = repo.commit ( " more work" , & [ ] ) .unwrap ( ) ;
4858
assert_eq! ( repo.store ( ) .get_change ( & h2) .unwrap ( ) .deps, vec! [ h1] ) ;
4859
let id1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) .change_id;
4860
4861
let ( hj, h1b) = repo.split ( & h1, & [ " junk" .into ( ) ] ) .unwrap ( ) ;
4862
assert_ne! ( h1b, h1) ;
75167672 Andy McCall 1 month ago
4863
assert_eq! (
4864
repo.store ( ) .get_change ( & h1b) .unwrap ( ) .change_id,
4865
id1,
4866
" the rest keeps the ChangeId"
4867
) ;
b837d2b5 Andy McCall 1 month ago ↶
4868
let junk = repo.store ( ) .get_change ( & hj) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4869
assert! ( junk.tree_ops.iter ( ) .all (
4870
| op | matches! ( op, TreeOp:: AddNode { name, .. } if name = = " junk" | | name = = " x.lock" )
4871
) ) ;
b837d2b5 Andy McCall 1 month ago ↶
4872
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4873
assert! (
4874
heads.contains ( & hj) ,
4875
" the split-off part is a head of its own"
4876
) ;
b837d2b5 Andy McCall 1 month ago ↶
4877
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
4878
assert! (
4879
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n " ) ,
4880
" the dependent was rewritten onto the new hash"
4881
) ;
b837d2b5 Andy McCall 1 month ago ↶
4882
assert! ( snap.files.contains_key ( " junk/x.lock" ) ) ;
75167672 Andy McCall 1 month ago
4883
let h2b = heads
4884
.iter ( )
4885
.find ( | h | repo.store ( ) .get_change ( h) .unwrap ( ) .message = = " more work" )
4886
.copied ( )
4887
.unwrap ( ) ;
b837d2b5 Andy McCall 1 month ago ↶
4888
assert_eq! ( repo.store ( ) .get_change ( & h2b) .unwrap ( ) .deps, vec! [ h1b] ) ;
4889
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4890
4891
repo.abandon ( & hj) .unwrap ( ) ;
4892
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
4893
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
4894
assert! ( ! snap.files.contains_key ( " junk/x.lock" ) & & ! snap.files.contains_key ( " junk" ) ) ;
4895
assert! ( snap.files.contains_key ( " a.txt" ) ) ;
4896
4897
write ( root, " junk/y.lock" , b " y\n " ) ;
4898
let hb = repo.commit ( " junk base" , & [ ] ) .unwrap ( ) ;
4899
write ( root, " junk/y.lock" , b " yy\n " ) ;
4900
write ( root, " b.txt" , b " b\n " ) ;
4901
repo.commit ( " touches junk" , & [ ] ) .unwrap ( ) ;
4902
let err = repo.split ( & hb, & [ " junk" .into ( ) ] ) .unwrap_err ( ) .to_string ( ) ;
4903
assert! ( err.contains ( " depends on the split-off paths" ) , " {err}" ) ;
4904
}
4905
7b9809e6 Andy McCall 1 month ago ↶
4906
# [ test ]
4907
fn bookmarks_switch_merge_and_push ( ) {
4908
hermetic ( ) ;
4909
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4910
let root = dir.path ( ) ;
4911
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
4912
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
4913
let mut repo = Repo:: clone( & url, & root.join ( " r" ) )
4914
.unwrap ( )
4915
.with_home ( root.join ( " home" ) ) ;
7b9809e6 Andy McCall 1 month ago ↶
4916
let r = root.join ( " r" ) ;
4917
write ( & r, " a.txt" , b " a\n " ) ;
4918
let h_a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
4919
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
4920
origin.refresh ( ) .unwrap ( ) ;
7b9809e6 Andy McCall 1 month ago ↶
4921
4922
4923
assert! ( repo.bookmark_create ( " bad name.." , None ) .is_err ( ) ) ;
4924
assert! ( repo.bookmark_create ( " -x" , None ) .is_err ( ) ) ;
4925
assert_eq! ( repo.bookmark_create ( " feature" , None ) .unwrap ( ) , vec! [ h_a] ) ;
4926
assert! ( repo.bookmark_create ( " feature" , None ) .is_err ( ) , " exists" ) ;
4927
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
4928
assert_eq! ( repo.config.bookmark, " feature" ) ;
4929
write ( & r, " a.txt" , b " a\n b\n " ) ;
4930
write ( & r, " b.txt" , b " b\n " ) ;
4931
let h_b = repo.commit ( " b on feature" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4932
assert_eq! (
4933
repo.bookmark_heads ( ) .unwrap ( ) ,
4934
vec! [ h_b] ,
4935
" b edits a.txt, so it depends on a"
4936
) ;
4937
assert_eq! (
4938
repo.resolve_heads ( " main" ) .unwrap ( ) ,
4939
vec! [ h_a] ,
4940
" main is untouched"
4941
) ;
4942
assert_eq! (
4943
repo.resolve_change ( " feature" ) .unwrap ( ) ,
4944
h_b,
4945
" a one-head bookmark names its change"
4946
) ;
7b9809e6 Andy McCall 1 month ago ↶
4947
4948
let reopened = Repo:: open( & r) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4949
assert_eq! ( reopened.config.bookmark, " feature" ) ;
4950
4951
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
4952
assert! ( ! r.join ( " b.txt" ) .exists ( ) ) ;
4953
assert_eq! ( fs:: read( r.join ( " a.txt" ) ) .unwrap ( ) , b " a\n " ) ;
4954
write ( & r, " c.txt" , b " c\n " ) ;
4955
let h_c = repo.commit ( " c on main" , & [ ] ) .unwrap ( ) ;
4956
let list = repo.bookmarks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4957
assert_eq! (
4958
list.iter ( )
4959
.map ( | b | ( b.name.as_str ( ) , b.current) )
4960
.collect:: < Vec< _ > > ( ) ,
4961
vec! [ ( " feature" , false ) , ( " main" , true ) ]
4962
) ;
4963
assert_eq! (
4964
list.iter ( ) .find ( | b | b.name = = " main" ) .unwrap ( ) .ahead,
4965
Some ( 1 ) ,
4966
" c is not on origin yet"
4967
) ;
7b9809e6 Andy McCall 1 month ago ↶
4968
4969
let m = repo.merge ( " feature" ) .unwrap ( ) ;
4970
assert! ( m.changed & & m.checked_out) ;
4971
let mut want = vec! [ h_b, h_c] ;
4972
want.sort ( ) ;
4973
assert_eq! ( m.heads, want) ;
4974
assert_eq! ( fs:: read( r.join ( " b.txt" ) ) .unwrap ( ) , b " b\n " ) ;
4975
assert! ( ! repo.merge ( " feature" ) .unwrap ( ) .changed, " already merged" ) ;
4976
assert! ( repo.merge ( " main" ) .is_err ( ) ) ;
4977
4978
assert! ( repo.push_bookmark ( " origin" , " feature" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
4979
origin.refresh ( ) .unwrap ( ) ;
7b9809e6 Andy McCall 1 month ago ↶
4980
assert_eq! ( origin.resolve_heads ( " feature" ) .unwrap ( ) , vec! [ h_b] ) ;
4981
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
4982
origin.refresh ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4983
assert_eq! (
4984
repo.bookmarks ( )
4985
.unwrap ( )
4986
.iter ( )
4987
.find ( | b | b.name = = " main" )
4988
.unwrap ( )
4989
.ahead,
4990
Some ( 0 )
4991
) ;
7b9809e6 Andy McCall 1 month ago ↶
4992
4993
let ws = repo.workspace_add ( & root.join ( " ws" ) , None ) .unwrap ( ) ;
4994
assert_eq! ( ws.config.bookmark, " main" ) ;
4995
let mut ws = ws;
4996
ws.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4997
assert_eq! (
4998
Repo:: open( & r) .unwrap ( ) .config.bookmark,
4999
" main" ,
5000
" the first workspace is unaffected"
5001
) ;
5002
assert! (
5003
repo.bookmark_delete ( " feature" ) .is_err ( ) ,
5004
" current bookmark of another workspace"
5005
) ;
7b9809e6 Andy McCall 1 month ago ↶
5006
ws.bookmark_switch ( " main" , false ) .unwrap ( ) ;
5007
5008
assert_eq! ( repo.bookmark_set ( " feature" , & [ h_a] ) .unwrap ( ) , vec! [ h_a] ) ;
5009
assert! ( repo.bookmark_delete ( " main" ) .is_err ( ) , " current here" ) ;
5010
repo.bookmark_delete ( " feature" ) .unwrap ( ) ;
5011
assert! ( repo.resolve_heads ( " feature" ) .is_err ( ) ) ;
5012
assert! ( repo.bookmark_set ( " feature" , & [ h_a] ) .is_err ( ) ) ;
75167672 Andy McCall 1 month ago
5013
assert! (
5014
repo.checkout ( & [ h_a] , false , " checkout" ) .is_ok ( ) ,
5015
" changes of a deleted bookmark remain"
5016
) ;
7b9809e6 Andy McCall 1 month ago ↶
5017
}
5018
4dee277c Andy McCall 1 month ago ↶
5019
# [ test ]
5020
fn oxygen_subrepos_pin_follow_and_update ( ) {
5021
hermetic ( ) ;
5022
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5023
let root = dir.path ( ) ;
5024
5025
let lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
5026
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
5027
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
5028
.unwrap ( )
5029
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
5030
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
5031
let l1 = lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
5032
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
5033
let _ = lib_origin;
5034
5035
5036
let app_origin = Repo:: init( & root.join ( " app-origin" ) ) .unwrap ( ) ;
5037
let app_url = root.join ( " app-origin" ) .to_string_lossy ( ) .to_string ( ) ;
5038
let _ = app_origin;
5039
let app_dir = root.join ( " app" ) ;
75167672 Andy McCall 1 month ago
5040
let app = Repo:: clone( & app_url, & app_dir)
5041
.unwrap ( )
5042
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
5043
write ( & app_dir, " main.txt" , b " app\n " ) ;
5044
let pin = app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
5045
assert! ( matches! ( & pin, crate :: subrepo:: Pin:: Oxy { heads, .. } if heads = = & vec! [ l1] ) ) ;
5046
assert! ( app_dir.join ( " .oxymodules" ) .exists ( ) & & app_dir.join ( " libs/lib/.oxy" ) .is_dir ( ) ) ;
5047
let snap = app.snapshot ( " status" ) .unwrap ( ) ;
5048
let paths: Vec< & str > = snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect ( ) ;
75167672 Andy McCall 1 month ago
5049
assert_eq! (
5050
paths,
5051
vec! [ " .oxymodules" , " libs/lib" , " main.txt" ] ,
5052
" the nested files are not walked"
5053
) ;
4dee277c Andy McCall 1 month ago ↶
5054
let a1 = app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
5055
let files = app.load_state ( & [ a1] ) .unwrap ( ) .materialize ( ) .files;
5056
assert_eq! ( files[ " libs/lib" ] .kind, NodeKind:: Subrepo) ;
5057
assert! ( ! files.contains_key ( " libs/lib/lib.txt" ) ) ;
5058
assert! ( ! app.snapshot ( " status" ) .unwrap ( ) .changed) ;
5059
let subs = app.subrepos ( ) .unwrap ( ) ;
5060
assert_eq! ( subs.len ( ) , 1 ) ;
5061
assert_eq! ( subs[ 0 ] .actual.as_ref ( ) , Some ( & subs[ 0 ] .pin) ) ;
5062
5063
75167672 Andy McCall 1 month ago
5064
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) )
5065
.unwrap ( )
5066
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
5067
write ( & app_dir.join ( " libs/lib" ) , " lib.txt" , b " v2\n " ) ;
5068
let l2 = nested.commit ( " lib v2" , & [ ] ) .unwrap ( ) ;
5069
assert! ( nested.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
5070
let snap = app.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5071
assert_eq! (
5072
snap.statuses
5073
.iter ( )
5074
.map ( | s | ( s.path.as_str ( ) , s.status) )
5075
.collect:: < Vec< _ > > ( ) ,
5076
vec! [ ( " libs/lib" , ChangeStatus:: Modified) ]
5077
) ;
4dee277c Andy McCall 1 month ago ↶
5078
let a2 = app.commit ( " bump lib" , & [ ] ) .unwrap ( ) ;
1a3b6203 Andy McCall 1 month ago ↶
5079
5080
4dee277c Andy McCall 1 month ago ↶
5081
let r = app.checkout ( & [ a1] , false , " checkout" ) .unwrap ( ) ;
1a3b6203 Andy McCall 1 month ago ↶
5082
assert! (
5083
r.notes.len ( ) = = 1 & & r.notes[ 0 ] .contains ( " keeps 1 newer change" ) ,
5084
" {:?}" ,
5085
r.notes
5086
) ;
4dee277c Andy McCall 1 month ago ↶
5087
assert_eq! ( fs:: read( app_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v1\n " ) ;
1a3b6203 Andy McCall 1 month ago ↶
5088
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) ) .unwrap ( ) ;
5089
assert_eq! ( nested.bookmark_heads ( ) .unwrap ( ) , vec! [ l2] ) ;
5090
assert_eq! ( nested.working_copy ( ) .unwrap ( ) .1. deps, vec! [ l1] ) ;
4dee277c Andy McCall 1 month ago ↶
5091
assert! ( ! app.snapshot ( " status" ) .unwrap ( ) .changed) ;
5092
5093
write ( & app_dir.join ( " libs/lib" ) , " scratch.txt" , b " wip\n " ) ;
5094
let r = app.checkout ( & [ a2] , false , " checkout" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5095
assert! (
5096
r.notes.iter ( ) .any ( | n | n.contains ( " uncommitted" ) ) ,
5097
" {:?}" ,
5098
r.notes
5099
) ;
4dee277c Andy McCall 1 month ago ↶
5100
assert_eq! ( fs:: read( app_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v1\n " ) ;
5101
fs:: remove_file( app_dir.join ( " libs/lib/scratch.txt" ) ) .unwrap ( ) ;
5102
let r = app.submodule_update ( false , & [ ] ) .unwrap ( ) ;
5103
assert_eq! ( r, vec! [ ( " libs/lib" .to_string ( ) , SubrepoSync:: Updated) ] ) ;
5104
assert_eq! ( fs:: read( app_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v2\n " ) ;
5105
let _ = l2;
5106
5107
5108
assert! ( app.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
5109
let clone_dir = root.join ( " app-clone" ) ;
75167672 Andy McCall 1 month ago
5110
let clone = Repo:: clone( & app_url, & clone_dir)
5111
.unwrap ( )
5112
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
5113
assert! ( ! clone_dir.join ( " libs/lib/.oxy" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
5114
assert_eq! (
5115
clone.submodule_update ( false , & [ ] ) .unwrap ( ) ,
5116
vec! [ ( " libs/lib" .to_string ( ) , SubrepoSync:: Missing) ]
5117
) ;
5118
assert_eq! (
5119
clone.submodule_update ( true , & [ ] ) .unwrap ( ) ,
5120
vec! [ ( " libs/lib" .to_string ( ) , SubrepoSync:: Updated) ]
5121
) ;
5122
assert_eq! (
5123
fs:: read( clone_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) ,
5124
b " v2\n "
5125
) ;
4dee277c Andy McCall 1 month ago ↶
5126
assert! ( ! clone.snapshot ( " status" ) .unwrap ( ) .changed) ;
5127
5128
clone.submodule_remove ( " libs/lib" ) .unwrap ( ) ;
5129
let snap = clone.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5130
assert! (
5131
snap.statuses
5132
.iter ( )
5133
.any ( | s | s.path = = " libs/lib" & & s.status = = ChangeStatus:: Removed ) ,
5134
" {:?}" ,
5135
snap.statuses
5136
) ;
4dee277c Andy McCall 1 month ago ↶
5137
let c = clone.commit ( " drop lib" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5138
assert! ( ! clone
5139
.load_state ( & [ c] )
5140
.unwrap ( )
5141
.materialize ( )
5142
.files
5143
.contains_key ( " libs/lib" ) ) ;
4dee277c Andy McCall 1 month ago ↶
5144
assert! ( clone_dir.join ( " libs/lib/lib.txt" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
5145
assert! (
5146
! clone.snapshot ( " status" ) .unwrap ( ) .changed,
5147
" an unregistered nested repository is ignored"
5148
) ;
4dee277c Andy McCall 1 month ago ↶
5149
}
5150
5151
# [ test ]
5152
fn git_submodules_import_and_export_as_gitlinks ( ) {
5153
hermetic ( ) ;
75167672 Andy McCall 1 month ago
5154
if std:: process:: Command:: new( " git" )
5155
.arg ( " --version" )
5156
.output ( )
5157
.is_err ( )
5158
{
4dee277c Andy McCall 1 month ago ↶
5159
eprintln! ( " git not installed; skipping" ) ;
5160
return ;
5161
}
5162
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5163
let root = dir.path ( ) ;
5164
let git_in = | d : & Path, args : & [& str ]| -> String {
5165
let out = std:: process:: Command:: new( " git" )
5166
.arg ( " -C" )
5167
.arg ( d)
75167672 Andy McCall 1 month ago
5168
.args ( [
5169
" -c" ,
5170
" user.name=Grace" ,
5171
" -c" ,
5172
" [email protected] " ,
5173
" -c" ,
5174
" commit.gpgsign=false" ,
5175
" -c" ,
5176
" protocol.file.allow=always" ,
5177
] )
4dee277c Andy McCall 1 month ago ↶
5178
.args ( args)
5179
.output ( )
5180
.unwrap ( ) ;
75167672 Andy McCall 1 month ago
5181
assert! (
5182
out.status.success ( ) ,
5183
" git {args:?}: {}" ,
5184
String :: from_utf8_lossy( & out.stderr)
5185
) ;
4dee277c Andy McCall 1 month ago ↶
5186
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
5187
} ;
5188
let sub = root.join ( " sub" ) ;
5189
fs:: create_dir_all( & sub) .unwrap ( ) ;
5190
git_in ( & sub, & [ " init" , " -q" , " -b" , " main" ] ) ;
5191
write ( & sub, " s.txt" , b " sub\n " ) ;
5192
git_in ( & sub, & [ " add" , " ." ] ) ;
5193
git_in ( & sub, & [ " commit" , " -q" , " -m" , " sub initial" ] ) ;
5194
let sub_sha = git_in ( & sub, & [ " rev-parse" , " HEAD" ] ) ;
5195
let g = root.join ( " main.git" ) ;
5196
fs:: create_dir_all( & g) .unwrap ( ) ;
5197
git_in ( & g, & [ " init" , " -q" , " -b" , " main" ] ) ;
5198
write ( & g, " README.md" , b " # app\n " ) ;
5199
git_in ( & g, & [ " add" , " ." ] ) ;
5200
git_in ( & g, & [ " commit" , " -q" , " -m" , " initial" ] ) ;
75167672 Andy McCall 1 month ago
5201
git_in (
5202
& g,
5203
& [
5204
" submodule" ,
5205
" add" ,
5206
" -q" ,
5207
sub.to_str ( ) .unwrap ( ) ,
5208
" vendor/sub" ,
5209
] ,
5210
) ;
4dee277c Andy McCall 1 month ago ↶
5211
git_in ( & g, & [ " commit" , " -q" , " -m" , " add submodule" ] ) ;
5212
5213
let dest = root.join ( " oxy" ) ;
5214
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5215
let rep = repo.import_git ( & g, " main" ) .unwrap ( ) ;
5216
assert_eq! ( ( rep.commits, rep.submodules) , ( 2 , 1 ) ) ;
75167672 Andy McCall 1 month ago
5217
let files = repo
5218
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
5219
.unwrap ( )
5220
.materialize ( )
5221
.files;
4dee277c Andy McCall 1 month ago ↶
5222
assert_eq! ( files[ " vendor/sub" ] .kind, NodeKind:: Subrepo) ;
75167672 Andy McCall 1 month ago
5223
let FileContent:: Opaque { blobs } = & files[ " vendor/sub" ] .content else {
5224
panic! ( )
5225
} ;
5226
assert_eq! (
5227
crate :: subrepo:: Pin:: decode( & repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) ) .unwrap ( ) ,
5228
crate :: subrepo:: Pin:: Git {
5229
sha: sub_sha.clone ( ) ,
5230
branch: " HEAD" .into ( )
5231
}
5232
) ;
5233
let modules =
5234
crate :: subrepo:: Modules:: parse( & fs:: read_to_string( dest.join ( " .oxymodules" ) ) .unwrap ( ) ) ;
4dee277c Andy McCall 1 month ago ↶
5235
assert_eq! ( modules.entries[ " vendor/sub" ] .url, sub.to_string_lossy ( ) ) ;
5236
assert! ( modules.entries[ " vendor/sub" ] .git) ;
75167672 Andy McCall 1 month ago
5237
assert! (
5238
files.contains_key ( " .gitmodules" ) ,
5239
" the Git file is kept for round trips"
5240
) ;
5241
assert! (
5242
! dest.join ( " vendor/sub/.git" ) .exists ( ) ,
5243
" not cloned until update --init"
5244
) ;
4dee277c Andy McCall 1 month ago ↶
5245
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
5246
75167672 Andy McCall 1 month ago
5247
assert_eq! (
5248
repo.submodule_update ( true , & [ ] ) .unwrap ( ) ,
5249
vec! [ ( " vendor/sub" .to_string ( ) , SubrepoSync:: Updated) ]
5250
) ;
5251
assert_eq! (
5252
git_in ( & dest.join ( " vendor/sub" ) , & [ " rev-parse" , " HEAD" ] ) ,
5253
sub_sha
5254
) ;
4dee277c Andy McCall 1 month ago ↶
5255
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
5256
5257
let mirror = root.join ( " mirror.git" ) ;
5258
let rep = repo.git_export ( & mirror) .unwrap ( ) ;
5259
assert_eq! ( rep.skipped_subrepos, 0 ) ;
5260
let ls = git_in ( & mirror, & [ " ls-tree" , " main" , " vendor/sub" ] ) ;
75167672 Andy McCall 1 month ago
5261
assert! (
5262
ls.starts_with ( " 160000 commit " ) & & ls.contains ( & sub_sha) ,
5263
" {ls}"
5264
) ;
5265
assert_eq! (
5266
git_in ( & mirror, & [ " ls-tree" , " --name-only" , " main" ] ) ,
5267
" .gitmodules\n README.md\n vendor"
5268
) ;
4dee277c Andy McCall 1 month ago ↶
5269
let orig_tree = git_in ( & g, & [ " rev-parse" , " main^{tree}" ] ) ;
75167672 Andy McCall 1 month ago
5270
assert_eq! (
5271
git_in ( & mirror, & [ " rev-parse" , " main^{tree}" ] ) ,
5272
orig_tree,
5273
" tree round trip"
5274
) ;
4dee277c Andy McCall 1 month ago ↶
5275
}
5276
957fee30 Andy McCall 1 month ago ↶
5277
# [ test ]
5278
fn partial_commits_by_path_and_by_hunk ( ) {
5279
hermetic ( ) ;
5280
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5281
let root = dir.path ( ) ;
5282
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5283
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
5284
write ( root, " b.txt" , b " b\n " ) ;
5285
let h0 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
5286
write ( root, " a.txt" , b " 0\n 1\n 2\n 3\n 4\n " ) ;
5287
write ( root, " b.txt" , b " b\n bb\n " ) ;
5288
write ( root, " c.txt" , b " c\n " ) ;
5289
75167672 Andy McCall 1 month ago
5290
let h1 = repo
5291
.commit_selection ( " a only" , & [ ] , & Selection:: Paths( vec! [ " a.txt" .into ( ) ] ) )
5292
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5293
let c1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5294
assert! (
5295
c1.tree_ops.is_empty ( ) & & c1.edits.iter ( ) .all ( | e | e.node ( ) = = c1.edits[ 0 ] .node ( ) ) ,
5296
" {:?}" ,
5297
c1.tree_ops
5298
) ;
957fee30 Andy McCall 1 month ago ↶
5299
assert_eq! ( c1.deps, vec! [ h0] ) ;
5300
let files = repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
5301
assert! (
5302
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 0\n 1\n 2\n 3\n 4\n " )
5303
) ;
957fee30 Andy McCall 1 month ago ↶
5304
assert! ( matches! ( & files[ " b.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " b\n " ) ) ;
5305
assert! ( ! files.contains_key ( " c.txt" ) ) ;
5306
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5307
assert_eq! (
5308
snap.statuses
5309
.iter ( )
5310
.map ( | s | ( s.path.as_str ( ) , s.status) )
5311
.collect:: < Vec< _ > > ( ) ,
5312
vec! [
5313
( " b.txt" , ChangeStatus:: Modified) ,
5314
( " c.txt" , ChangeStatus:: Added)
5315
]
5316
) ;
5317
assert_eq! (
5318
fs:: read( root.join ( " b.txt" ) ) .unwrap ( ) ,
5319
b " b\n bb\n " ,
5320
" disk untouched"
5321
) ;
957fee30 Andy McCall 1 month ago ↶
5322
5323
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5324
let b_hunk = hunks
5325
.iter ( )
5326
.find ( | h | h.path = = " b.txt" & & h.text.contains ( " +bb" ) )
5327
.unwrap ( )
5328
.index;
5329
let h2 = repo
5330
.commit_selection ( " b only" , & [ ] , & Selection:: Hunks( vec! [ b_hunk] ) )
5331
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5332
let files = repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
5333
assert! (
5334
matches! ( & files[ " b.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " b\n bb\n " )
5335
) ;
957fee30 Andy McCall 1 month ago ↶
5336
assert! ( ! files.contains_key ( " c.txt" ) ) ;
5337
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5338
assert_eq! (
5339
snap.statuses
5340
.iter ( )
5341
.map ( | s | s.path.as_str ( ) )
5342
.collect:: < Vec< _ > > ( ) ,
5343
vec! [ " c.txt" ]
5344
) ;
957fee30 Andy McCall 1 month ago ↶
5345
5346
5347
write ( root, " d/e.txt" , b " e\n " ) ;
5348
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5349
let e_insert = hunks
5350
.iter ( )
5351
.find ( | h | h.path = = " d/e.txt" & & h.text.contains ( " +e" ) )
5352
.unwrap ( )
5353
.index;
5354
let h3 = repo
5355
.commit_selection ( " e only" , & [ ] , & Selection:: Hunks( vec! [ e_insert] ) )
5356
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5357
let c3 = repo.store ( ) .get_change ( & h3) .unwrap ( ) ;
5358
assert_eq! ( c3.tree_ops.len ( ) , 2 , " {:?}" , c3.tree_ops) ;
5359
let files = repo.load_state ( & [ h3] ) .unwrap ( ) .materialize ( ) .files;
5360
assert! ( files.contains_key ( " d/e.txt" ) & & ! files.contains_key ( " c.txt" ) ) ;
d8c086ca Andy McCall 1 month ago ↶
5361
5362
write ( root, " f/g.txt" , b " g\n " ) ;
5363
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5364
let g_add = hunks
5365
.iter ( )
5366
.find ( | h | h.path = = " f/g.txt" & & h.text.starts_with ( " A " ) )
5367
.unwrap ( )
5368
.index;
5369
let h4 = repo
5370
.commit_selection ( " g by addition" , & [ ] , & Selection:: Hunks( vec! [ g_add] ) )
5371
.unwrap ( ) ;
d8c086ca Andy McCall 1 month ago ↶
5372
let files = repo.load_state ( & [ h4] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
5373
assert! (
5374
matches! ( & files[ " f/g.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " g\n " )
5375
) ;
5376
assert! ( ! repo
5377
.snapshot ( " status" )
5378
.unwrap ( )
5379
.statuses
5380
.iter ( )
5381
.any ( | s | s.path = = " f/g.txt" ) ) ;
957fee30 Andy McCall 1 month ago ↶
5382
5383
repo.commit ( " the rest" , & [ ] ) .unwrap ( ) ;
5384
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
75167672 Andy McCall 1 month ago
5385
let files = repo
5386
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
5387
.unwrap ( )
5388
.materialize ( )
5389
.files;
957fee30 Andy McCall 1 month ago ↶
5390
assert! ( files.contains_key ( " c.txt" ) ) ;
5391
}
5392
5393
# [ test ]
5394
fn blame_attributes_lines_to_introducing_changes_across_renames ( ) {
5395
hermetic ( ) ;
5396
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5397
let root = dir.path ( ) ;
5398
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
5399
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
5400
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5401
write ( root, " a.txt" , b " one\n " ) ;
5402
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
5403
write ( root, " a.txt" , b " one\n two\n " ) ;
5404
let h2 = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
5405
let b = repo.blame ( " a.txt" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5406
assert_eq! (
5407
b.iter ( )
5408
.map ( | l | ( l.line, l.hash, l.text.as_slice ( ) ) )
5409
.collect:: < Vec< _ > > ( ) ,
5410
vec! [ ( 1 , h1, b " one\n " .as_slice ( ) ) , ( 2 , h2, b " two\n " .as_slice ( ) ) ]
5411
) ;
957fee30 Andy McCall 1 month ago ↶
5412
assert! ( b.iter ( ) .all ( | l | l.author = = " Ada" ) ) ;
75167672 Andy McCall 1 month ago
5413
assert_eq! (
5414
b[ 0 ] .change_id,
5415
repo.store ( ) .get_change ( & h1) .unwrap ( ) .change_id
5416
) ;
957fee30 Andy McCall 1 month ago ↶
5417
fs:: rename( root.join ( " a.txt" ) , root.join ( " b.txt" ) ) .unwrap ( ) ;
5418
repo.commit ( " rename" , & [ ] ) .unwrap ( ) ;
5419
let b = repo.blame ( " b.txt" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5420
assert_eq! (
5421
b.iter ( ) .map ( | l | l.hash ) .collect:: < Vec< _ > > ( ) ,
5422
vec! [ h1, h2] ,
5423
" the node survives the rename"
5424
) ;
957fee30 Andy McCall 1 month ago ↶
5425
assert! ( repo.blame ( " a.txt" ) .is_err ( ) ) ;
5426
}
5427
5428
# [ test ]
5429
fn bisect_finds_the_first_bad_change ( ) {
5430
hermetic ( ) ;
5431
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5432
let root = dir.path ( ) ;
5433
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5434
let mut content = String :: new( ) ;
5435
let mut hashes = Vec :: new( ) ;
5436
for i in 1 .. = 8 {
5437
content.push_str ( & format! ( " line {i} {} \n " , if i = = 5 { " bug" } else { " " } ) ) ;
5438
write ( root, " f.txt" , content.as_bytes ( ) ) ;
5439
hashes.push ( repo.commit ( & format! ( " step {i} " ) , & [ ] ) .unwrap ( ) ) ;
5440
}
5441
5442
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ hashes[ 7 ] ] ) ;
5443
repo.bisect_start ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5444
assert! ( matches! (
5445
repo.bisect_mark ( Some ( true ) , & [ hashes[ 0 ] ] ) .unwrap ( ) ,
5446
BisectStep:: NeedMarks { good: 1 , bad: 0 }
5447
) ) ;
957fee30 Andy McCall 1 month ago ↶
5448
let step = repo.bisect_mark ( Some ( false ) , & [ hashes[ 7 ] ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5449
assert! (
5450
matches! ( step, BisectStep:: Testing { remaining: 7 , .. } ) ,
5451
" {step:?}"
5452
) ;
957fee30 Andy McCall 1 month ago ↶
5453
75167672 Andy McCall 1 month ago
5454
let step = repo
5455
.bisect_run ( & [ " sh" .into ( ) , " -c" .into ( ) , " ! grep -q bug f.txt" .into ( ) ] )
5456
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5457
assert_eq! ( step, BisectStep:: Found { culprit: hashes[ 4 ] } ) ;
75167672 Andy McCall 1 month ago
5458
assert_eq! (
5459
repo.bisect_status ( ) .unwrap ( ) ,
5460
Some ( BisectStep:: Found { culprit: hashes[ 4 ] } )
5461
) ;
957fee30 Andy McCall 1 month ago ↶
5462
repo.bisect_reset ( ) .unwrap ( ) ;
5463
assert_eq! ( fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) , content) ;
5464
assert! ( repo.bisect_status ( ) .unwrap ( ) .is_none ( ) ) ;
5465
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
5466
5467
repo.bisect_start ( ) .unwrap ( ) ;
5468
repo.bisect_mark ( Some ( true ) , & [ hashes[ 0 ] ] ) .unwrap ( ) ;
5469
let mut step = repo.bisect_mark ( Some ( false ) , & [ hashes[ 7 ] ] ) .unwrap ( ) ;
5470
let mut skipped_once = false ;
5471
let mut guard = 0 ;
5472
while let BisectStep:: Testing { .. } = step {
5473
guard + = 1 ;
5474
assert! ( guard < 20 ) ;
5475
let text = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
5476
if ! skipped_once & & text.contains ( " line 6" ) {
5477
5478
skipped_once = true ;
5479
step = repo.bisect_mark ( None , & [ ] ) .unwrap ( ) ;
5480
continue ;
5481
}
5482
step = repo.bisect_mark ( Some ( ! text.contains ( " bug" ) ) , & [ ] ) .unwrap ( ) ;
5483
}
5484
assert! ( skipped_once) ;
5485
assert_eq! ( step, BisectStep:: Found { culprit: hashes[ 4 ] } ) ;
5486
repo.bisect_reset ( ) .unwrap ( ) ;
5487
5488
repo.bisect_start ( ) .unwrap ( ) ;
5489
repo.bisect_mark ( Some ( true ) , & [ hashes[ 0 ] ] ) .unwrap ( ) ;
5490
let mut step = repo.bisect_mark ( Some ( false ) , & [ hashes[ 7 ] ] ) .unwrap ( ) ;
5491
let mut guard = 0 ;
5492
while let BisectStep:: Testing { heads, .. } = & step {
5493
guard + = 1 ;
5494
assert! ( guard < 20 ) ;
5495
let text = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5496
let is_culprit_state =
5497
heads.contains ( & hashes[ 4 ] ) & & ! heads.iter ( ) .any ( | h | hashes[ 5 .. ] .contains ( h) ) ;
5498
step = if is_culprit_state {
5499
repo.bisect_mark ( None , & [ ] ) .unwrap ( )
5500
} else {
5501
repo.bisect_mark ( Some ( ! text.contains ( " bug" ) ) , & [ ] ) .unwrap ( )
5502
} ;
957fee30 Andy McCall 1 month ago ↶
5503
}
5504
match step {
75167672 Andy McCall 1 month ago
5505
BisectStep:: Inconclusive { candidates } => assert! (
5506
candidates.contains ( & hashes[ 4 ] ) & & candidates.len ( ) < = 2 ,
5507
" {candidates:?}"
5508
) ,
957fee30 Andy McCall 1 month ago ↶
5509
other => panic! ( " {other:?}" ) ,
5510
}
5511
repo.bisect_reset ( ) .unwrap ( ) ;
5512
}
5513
728da0f9 Andy McCall 1 month ago ↶
5514
# [ test ]
5515
fn squash_folds_a_change_into_its_dependency ( ) {
5516
hermetic ( ) ;
5517
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5518
let root = dir.path ( ) ;
5519
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5520
write ( root, " a.txt" , b " 1\n " ) ;
5521
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
5522
write ( root, " a.txt" , b " 1\n 2\n " ) ;
5523
let c = repo.commit ( " C" , & [ ] ) .unwrap ( ) ;
5524
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
5525
let e = repo.commit ( " E" , & [ ] ) .unwrap ( ) ;
5526
write ( root, " b.txt" , b " b\n " ) ;
5527
let b = repo.commit ( " B" , & [ ] ) .unwrap ( ) ;
5528
let id_a = repo.store ( ) .get_change ( & a) .unwrap ( ) .change_id;
5529
let id_e = repo.store ( ) .get_change ( & e) .unwrap ( ) .change_id;
5530
5531
let err = repo.squash ( & e, & a) .unwrap_err ( ) .to_string ( ) ;
5532
assert! ( err.contains ( & c.short ( 12 ) ) , " {err}" ) ;
5533
5534
let a2 = repo.squash ( & c, & a) .unwrap ( ) ;
5535
let a2c = repo.store ( ) .get_change ( & a2) .unwrap ( ) ;
5536
assert_eq! ( a2c.change_id, id_a) ;
5537
assert! ( a2c.deps.is_empty ( ) ) ;
5538
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5539
let st = repo.load_state ( & heads) .unwrap ( ) ;
5540
assert! ( ! st.contains ( & c) & & ! st.contains ( & a) & & ! st.contains ( & e) ) ;
75167672 Andy McCall 1 month ago
5541
let e2 = st
5542
.changes ( )
5543
.find ( | ( _, m ) | m.change_id = = id_e )
5544
.map ( | ( h , _) | * h)
5545
.unwrap ( ) ;
5546
assert_eq! (
5547
repo.store ( ) .get_change ( & e2) .unwrap ( ) .deps,
5548
vec! [ a2] ,
5549
" E now depends on A' alone"
5550
) ;
728da0f9 Andy McCall 1 month ago ↶
5551
let files = st.materialize ( ) .files;
75167672 Andy McCall 1 month ago
5552
assert! (
5553
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 1\n 2\n 3\n " )
5554
) ;
728da0f9 Andy McCall 1 month ago ↶
5555
assert! ( files.contains_key ( " b.txt" ) ) ;
5556
assert! ( st.contains ( & b) ) ;
5557
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
5558
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 , " working copy, B, E', A'" ) ;
5559
5560
repo.undo ( ) .unwrap ( ) ;
5561
repo.undo ( ) .unwrap ( ) ;
5562
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
5563
assert! ( st.contains ( & c) & & st.contains ( & a) & & st.contains ( & e) ) ;
5564
}
5565
5566
# [ test ]
5567
fn rebase_re_expresses_a_change_against_another_base ( ) {
5568
hermetic ( ) ;
5569
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5570
let root = dir.path ( ) ;
5571
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5572
write ( root, " a.txt" , b " 1\n 2\n " ) ;
5573
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
5574
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
5575
let c = repo.commit ( " C: append 3" , & [ ] ) .unwrap ( ) ;
5576
write ( root, " a.txt" , b " 1\n 2\n 3\n 4\n " ) ;
5577
let e = repo.commit ( " E: append 4" , & [ ] ) .unwrap ( ) ;
5578
5579
repo.checkout ( & [ a] , true , " checkout" ) .unwrap ( ) ;
5580
write ( root, " a.txt" , b " 0\n 1\n 2\n " ) ;
5581
let d = repo.commit ( " D: prepend 0" , & [ ] ) .unwrap ( ) ;
5582
let id_c = repo.store ( ) .get_change ( & c) .unwrap ( ) .change_id;
5583
let id_e = repo.store ( ) .get_change ( & e) .unwrap ( ) .change_id;
5584
5585
let c2 = repo.rebase ( & c, & [ d] ) .unwrap ( ) ;
5586
let c2c = repo.store ( ) .get_change ( & c2) .unwrap ( ) ;
5587
assert_eq! ( c2c.change_id, id_c) ;
5588
assert! ( c2c.deps.contains ( & d) , " {:?}" , c2c.deps) ;
5589
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5590
let st = repo.load_state ( & heads) .unwrap ( ) ;
5591
assert! ( ! st.contains ( & c) & & st.contains ( & d) ) ;
75167672 Andy McCall 1 month ago
5592
let e2 = st
5593
.changes ( )
5594
.find ( | ( _, m ) | m.change_id = = id_e )
5595
.map ( | ( h , _) | * h)
5596
.unwrap ( ) ;
728da0f9 Andy McCall 1 month ago ↶
5597
let e2deps = repo.store ( ) .get_change ( & e2) .unwrap ( ) .deps;
75167672 Andy McCall 1 month ago
5598
assert! (
5599
e2deps.contains ( & c2) & & ! e2deps.contains ( & c) & & e2deps.contains ( & a) ,
5600
" E follows C by content and still depends on A, which introduced the file: {e2deps:?}"
5601
) ;
728da0f9 Andy McCall 1 month ago ↶
5602
let files = st.materialize ( ) .files;
75167672 Andy McCall 1 month ago
5603
assert! (
5604
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 0\n 1\n 2\n 3\n 4\n " ) ,
5605
" {:?}" ,
5606
files[ " a.txt" ] .content
5607
) ;
728da0f9 Andy McCall 1 month ago ↶
5608
assert! ( ! files[ " a.txt" ] .is_conflicted ( ) ) ;
5609
5610
assert! ( repo.rebase ( & c2, & [ c2] ) .is_err ( ) ) ;
5611
let err = repo.rebase ( & e2, & [ d] ) .unwrap_err ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
5612
assert! (
5613
err.contains ( " lacks dependencies" ) ,
5614
" E needs C, which D's state does not have: {err}"
5615
) ;
728da0f9 Andy McCall 1 month ago ↶
5616
}
5617
c792033a Andy McCall 1 month ago ↶
5618
# [ test ]
5619
fn unsigned_tags_keep_their_message ( ) {
5620
hermetic ( ) ;
5621
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5622
let root = dir.path ( ) ;
5623
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5624
write ( root, " a.txt" , b " a\n " ) ;
5625
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
5626
assert! ( repo.secret_key ( ) .unwrap ( ) .is_none ( ) ) ;
75167672 Andy McCall 1 month ago
5627
repo.tag_create ( " v1" , & repo.bookmark_heads ( ) .unwrap ( ) , " first release" )
5628
.unwrap ( ) ;
c792033a Andy McCall 1 month ago ↶
5629
let tags = repo.tags ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5630
assert_eq! (
5631
(
5632
tags[ 0 ] .name.as_str ( ) ,
5633
tags[ 0 ] .message.as_str ( ) ,
5634
tags[ 0 ] .signer.as_deref ( )
5635
) ,
5636
( " v1" , " first release" , None )
5637
) ;
5638
assert! (
5639
repo.tag_refs ( ) .unwrap ( ) [ 0 ] .attestation.is_none ( ) ,
5640
" an unsigned attestation is not advertised"
5641
) ;
c792033a Andy McCall 1 month ago ↶
5642
}
387335ff Andy McCall 1 month ago ↶
5643
5644
# [ test ]
5645
fn fsck_passes_a_sound_repository_and_names_each_fault ( ) {
64818938 Andy McCall 1 month ago ↶
5646
5647
5648
if std:: env:: var_os( " OXY_STORE_BACKEND" ) .is_some ( ) {
5649
return ;
5650
}
387335ff Andy McCall 1 month ago ↶
5651
hermetic ( ) ;
5652
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5653
let root = dir.path ( ) ;
5654
let origin_path = root.join ( " origin" ) ;
5655
let origin = Repo:: init( & origin_path) .unwrap ( ) ;
5656
let mut a = Repo:: clone( & origin_path.to_string_lossy ( ) , & root.join ( " a" ) )
5657
.unwrap ( )
5658
.with_home ( root.join ( " home-a" ) ) ;
5659
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
5660
.unwrap ( ) ;
5661
write ( & root.join ( " a" ) , " f.txt" , b " one\n " ) ;
5662
write ( & root.join ( " a" ) , " bin.dat" , b " \0 \x01 \x02 binary" ) ;
5663
let h1 = a.commit ( " first" , & [ ] ) .unwrap ( ) ;
5664
write ( & root.join ( " a" ) , " f.txt" , b " one\n two\n " ) ;
5665
let h2 = a.commit ( " second" , & [ ] ) .unwrap ( ) ;
5666
a.tag_create ( " v1" , & [ h2] , " first release" ) .unwrap ( ) ;
5667
a.checkpoint ( ) .unwrap ( ) ;
5668
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
5669
origin.refresh ( ) .unwrap ( ) ;
387335ff Andy McCall 1 month ago ↶
5670
5671
5672
for repo in [ & a, & origin] {
5673
let rep = repo.fsck ( ) .unwrap ( ) ;
5674
assert_eq! ( rep.problems, vec! [ ] , " {rep:?}" ) ;
5675
5676
assert! ( rep.changes > = 2 & & rep.signed > = 2 , " {rep:?}" ) ;
5677
assert! ( rep.ops > 1 & & rep.objects > 5 ) ;
5678
}
5679
assert! ( origin.fsck ( ) .unwrap ( ) .attestations > = 1 ) ;
5680
5681
5682
let c1 = a.store ( ) .path_for ( & h1) ;
5683
let pristine = fs:: read( & c1) .unwrap ( ) ;
5684
let mut bytes = pristine.clone ( ) ;
5685
* bytes.last_mut ( ) .unwrap ( ) ^ = 1 ;
5686
fs:: write( & c1, & bytes) .unwrap ( ) ;
5687
let rep = a.fsck ( ) .unwrap ( ) ;
5688
assert! ( rep.errors ( ) > = 1 ) ;
5689
assert! (
5690
rep.problems
5691
.iter ( )
5692
.any ( | p | p.object = = Some ( h1) & & p.what.contains ( " unreadable" ) ) ,
5693
" {rep:?}"
5694
) ;
5695
fs:: write( & c1, & pristine) .unwrap ( ) ;
5696
assert_eq! ( a.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
5697
5698
5699
fs:: remove_file( & c1) .unwrap ( ) ;
5700
let rep = a.fsck ( ) .unwrap ( ) ;
5701
assert! (
5702
rep.problems.iter ( ) .any ( | p | p.severity = = Severity:: Error
5703
& & p.object = = Some ( h1)
5704
& & p.what.contains ( " change missing, needed by change" ) ) ,
5705
" {rep:?}"
5706
) ;
5707
fs:: write( & c1, & pristine) .unwrap ( ) ;
5708
5709
5710
let c2 = a.store ( ) .get_change ( & h1) .unwrap ( ) ;
5711
let blob = c2
5712
.edits
5713
.iter ( )
5714
.find_map ( | e | match e {
5715
Edit:: Replace { new: Some ( b) , .. } => Some ( * b) ,
5716
_ => None ,
5717
} )
5718
.expect ( " the binary file is a blob" ) ;
5719
let bp = a.store ( ) .path_for ( & blob) ;
5720
let blob_bytes = fs:: read( & bp) .unwrap ( ) ;
5721
fs:: remove_file( & bp) .unwrap ( ) ;
5722
let rep = a.fsck ( ) .unwrap ( ) ;
5723
assert! (
5724
rep.problems
5725
.iter ( )
5726
.any ( | p | p.object = = Some ( blob) & & p.what.contains ( " blob missing" ) ) ,
5727
" {rep:?}"
5728
) ;
5729
fs:: write( & bp, & blob_bytes) .unwrap ( ) ;
5730
5731
5732
let head = root.join ( " a/.oxy/ops/head" ) ;
5733
let saved = fs:: read( & head) .unwrap ( ) ;
5734
fs:: write( & head, b " not a hash" ) .unwrap ( ) ;
5735
let rep = a.fsck ( ) .unwrap ( ) ;
5736
assert! (
5737
rep.problems.iter ( ) .any ( | p | p.what.contains ( " ops/head" ) ) ,
5738
" {rep:?}"
5739
) ;
5740
fs:: write( & head, saved) .unwrap ( ) ;
5741
assert_eq! ( a.fsck ( ) .unwrap ( ) .problems, vec! [ ] ) ;
5742
}
4dcb8c09 Andy McCall 1 month ago ↶
5743
5744
5745
fn copy_tree ( from : & Path, to : & Path) {
5746
fs:: create_dir_all( to) .unwrap ( ) ;
5747
for e in fs:: read_dir( from) .unwrap ( ) {
5748
let e = e.unwrap ( ) ;
5749
let target = to.join ( e.file_name ( ) ) ;
5750
if e.file_type ( ) .unwrap ( ) .is_dir ( ) {
5751
copy_tree ( & e.path ( ) , & target) ;
5752
} else {
5753
fs:: copy( e.path ( ) , & target) .unwrap ( ) ;
5754
}
5755
}
5756
}
5757
5758
5759
5760
5761
# [ test ]
5762
fn crash_at_every_step_of_commit_and_push_leaves_sound_repositories ( ) {
64818938 Andy McCall 1 month ago ↶
5763
5764
5765
if std:: env:: var_os( " OXY_STORE_BACKEND" ) .is_some ( ) {
5766
return ;
5767
}
4dcb8c09 Andy McCall 1 month ago ↶
5768
use crate :: durable:: failpoint;
5769
hermetic ( ) ;
5770
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5771
let pristine = dir.path ( ) .join ( " pristine" ) ;
5772
let origin_path = pristine.join ( " origin" ) ;
5773
Repo:: init( & origin_path) .unwrap ( ) ;
5774
let mut a = Repo:: clone( & origin_path.to_string_lossy ( ) , & pristine.join ( " a" ) )
5775
.unwrap ( )
5776
.with_home ( dir.path ( ) .join ( " home" ) ) ;
5777
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
5778
.unwrap ( ) ;
5779
write ( & pristine.join ( " a" ) , " f.txt" , b " one\n " ) ;
5780
a.commit ( " first" , & [ ] ) .unwrap ( ) ;
5781
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
5782
drop ( a) ;
5783
5784
type Op = fn ( & Repo) -> Result< ( ) > ;
5785
let edit_and_commit: Op = | r | {
5786
write ( r.root ( ) , " f.txt" , b " one\n two\n " ) ;
5787
write ( r.root ( ) , " big.bin" , & vec! [ 7 u8 ; 300_000 ] ) ;
5788
r.commit ( " second" , & [ ] ) .map ( | _| ( ) )
5789
} ;
5790
let push: Op = | r | {
5791
let res = r.push ( " origin" , false ) ? ;
5792
if res.iter ( ) .all ( | x | x.ok ) {
5793
Ok ( ( ) )
5794
} else {
5795
Err ( RepoError:: Msg( format! ( " {res:?} " ) ) )
5796
}
5797
} ;
5798
5799
for ( name, prepare, op) in [
5800
( " commit" , None , edit_and_commit) ,
5801
( " push" , Some ( edit_and_commit) , push) ,
5802
] {
5803
let mut steps = 0 ;
5804
for n in 1 .. {
5805
let run = dir.path ( ) .join ( format! ( " {name} -{n} " ) ) ;
5806
copy_tree ( & pristine, & run) ;
5807
5808
let open = | p : & Path| {
5809
let mut r = Repo:: open( p) .unwrap ( ) .with_home ( dir.path ( ) .join ( " home" ) ) ;
5810
r.config
5811
.set ( " remote.origin.url" , & run.join ( " origin" ) .to_string_lossy ( ) )
5812
.unwrap ( ) ;
5813
r.save_config ( ) .unwrap ( ) ;
5814
r
5815
} ;
5816
let a = open ( & run.join ( " a" ) ) ;
5817
if let Some ( prep) = prepare {
5818
prep ( & a) .unwrap ( ) ;
5819
}
5820
failpoint:: crash_after( n) ;
5821
let outcome = op ( & a) ;
5822
let crashed = failpoint:: disarm( ) ;
5823
drop ( a) ;
5824
if ! crashed {
5825
outcome.unwrap_or_else ( | e | panic! ( " {name} failed without a crash: {e}" ) ) ;
5826
steps = n - 1 ;
5827
break ;
5828
}
5829
assert! (
5830
outcome.is_err ( ) ,
5831
" {name}: a crash at step {n} was swallowed"
5832
) ;
5833
for repo in [ " a" , " origin" ] {
5834
let r = Repo:: open( & run.join ( repo) ) .unwrap_or_else ( | e | {
5835
panic! ( " {name} crash at step {n}: {repo} no longer opens: {e}" )
5836
} ) ;
5837
let rep = r.fsck ( ) .unwrap ( ) ;
5838
assert_eq! (
5839
rep.errors ( ) ,
5840
0 ,
5841
" {name} crash at step {n}: {repo} fails fsck: {:?}" ,
5842
rep.problems
5843
) ;
5844
}
5845
5846
let a = open ( & run.join ( " a" ) ) ;
5847
5848
let landed = a
5849
.bookmark_heads ( )
5850
.unwrap ( )
5851
.iter ( )
5852
.any ( | h | a.store ( ) .get_change ( h) .unwrap ( ) .message = = " second" ) ;
5853
let again = match ( name, landed) {
5854
( " commit" , true ) => Ok ( ( ) ) ,
5855
( " commit" , false ) => a.commit ( " second" , & [ ] ) .map ( | _| ( ) ) ,
5856
_ => op ( & a) ,
5857
} ;
5858
again.unwrap_or_else ( | e | panic! ( " {name} crash at step {n}: retry failed: {e}" ) ) ;
5859
assert_eq! ( a.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
5860
fs:: remove_dir_all( & run) .unwrap ( ) ;
5861
}
5862
assert! ( steps > = 3 , " {name} has only {steps} durable steps?" ) ;
5863
}
5864
}
19555025 Andy McCall 1 month ago ↶
5865
5866
5867
5868
5869
# [ test ]
5870
fn concurrent_commits_in_two_workspaces_lose_nothing ( ) {
5871
hermetic ( ) ;
5872
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5873
let root = dir.path ( ) ;
5874
let origin = root.join ( " origin" ) ;
5875
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
5876
let main = root.join ( " main" ) ;
5877
let mut repo = Repo:: init( & main) .unwrap ( ) ;
5878
repo.config
5879
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
5880
.unwrap ( ) ;
5881
repo.save_config ( ) .unwrap ( ) ;
5882
write ( & main, " seed.txt" , b " seed\n " ) ;
5883
repo.commit ( " seed" , & [ ] ) .unwrap ( ) ;
5884
let second = root.join ( " second" ) ;
5885
repo.workspace_add ( & second, None ) .unwrap ( ) ;
5886
drop ( repo) ;
5887
5888
const EACH : usize = 15 ;
5889
std:: thread:: scope( | s | {
5890
for ( tag, dir, push) in [ ( " a" , main.clone ( ) , true ) , ( " b" , second.clone ( ) , false ) ] {
5891
s.spawn ( move | | {
5892
let r = Repo:: open( & dir) .unwrap ( ) ;
5893
for i in 0 .. EACH {
5894
write (
5895
& dir,
5896
& format! ( " {tag} -{i} .txt" ) ,
5897
format! ( " {tag} {i} \n " ) .as_bytes ( ) ,
5898
) ;
5899
r.commit ( & format! ( " {tag} {i} " ) , & [ ] ) .unwrap ( ) ;
5900
if push & & i % 5 = = 4 {
5901
5902
let _ = r.push ( " origin" , false ) ;
5903
}
5904
}
5905
} ) ;
5906
}
5907
} ) ;
5908
5909
let repo = Repo:: open( & main) .unwrap ( ) ;
5910
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5911
let state = repo.load_state ( & heads) .unwrap ( ) ;
5912
let messages: BTreeSet< String> = state.changes ( ) .map ( | ( _, m ) | m.message.clone ( ) ) .collect ( ) ;
5913
for tag in [ " a" , " b" ] {
5914
for i in 0 .. EACH {
5915
assert! (
5916
messages.contains ( & format! ( " {tag} {i} " ) ) ,
5917
" lost commit {tag} {i}; have {messages:?}"
5918
) ;
5919
}
5920
}
5921
let rep = repo.fsck ( ) .unwrap ( ) ;
5922
assert_eq! ( rep.errors ( ) , 0 , " {:?}" , rep.problems) ;
5923
assert! ( repo.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
5924
let hosted = Repo:: open( & origin) .unwrap ( ) ;
5925
let hosted_heads = hosted.bookmark_heads ( ) .unwrap ( ) ;
5926
assert_eq! (
5927
hosted.load_state ( & hosted_heads) .unwrap ( ) .len ( ) ,
5928
state.len ( ) ,
5929
" the remote has every commit"
5930
) ;
5931
}
5932
5933
5934
5935
5936
# [ test ]
5937
fn operations_from_the_same_parent_are_merged ( ) {
5938
hermetic ( ) ;
5939
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5940
let root = dir.path ( ) ;
5941
let repo = Repo:: init( root) .unwrap ( ) ;
5942
write ( root, " a.txt" , b " a\n " ) ;
5943
let a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
5944
write ( root, " b.txt" , b " b\n " ) ;
5945
let b = repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
5946
let ( x, op) = repo.head_op ( ) .unwrap ( ) ;
5947
let state = | heads : Vec< ChangeHash> | repo.store ( ) .put_state ( & StateObject:: new( heads) ) .unwrap ( ) ;
5948
5949
5950
let mut one = op.view.clone ( ) ;
5951
one.bookmarks.insert ( " main" .into ( ) , state ( vec! [ a] ) ) ;
5952
one.bookmarks.insert ( " topic" .into ( ) , state ( vec! [ a] ) ) ;
5953
let mut two = op.view.clone ( ) ;
5954
two.bookmarks.insert ( " main" .into ( ) , state ( vec! [ b] ) ) ;
5955
repo.record_op ( & [ x] , " one" , one) .unwrap ( ) ;
5956
let merged = repo.record_op ( & [ x] , " two" , two) .unwrap ( ) ;
5957
5958
let ( head, op) = repo.head_op ( ) .unwrap ( ) ;
5959
assert_eq! ( head, merged) ;
5960
assert_eq! ( op.parents.len ( ) , 2 , " a merge of the two operations" ) ;
5961
assert! ( op.command.contains ( " merged with a concurrent operation" ) ) ;
5962
let mut main = repo
5963
.store ( )
5964
.get_state ( & op.view.bookmarks[ " main" ] )
5965
.unwrap ( )
5966
.heads;
5967
main.sort ( ) ;
5968
let mut want = vec! [ a, b] ;
5969
want.sort ( ) ;
5970
assert_eq! ( main, want, " both sides' heads are kept" ) ;
5971
assert! (
5972
op.view.bookmarks.contains_key ( " topic" ) ,
5973
" a one-sided change survives"
5974
) ;
5975
assert_eq! ( repo.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
5976
}
3e5772cc Andy McCall 1 month ago ↶
5977
5978
# [ test ]
5979
fn failed_requests_come_with_a_next_step ( ) {
5980
use crate :: protocol:: hint_for;
5981
assert! ( hint_for ( " https://h/x/oxy/v1/push: HTTP 401: bad token" )
5982
.unwrap ( )
5983
.contains ( " token" ) ) ;
5984
assert! ( hint_for ( " …: HTTP 403: forbidden" )
5985
.unwrap ( )
5986
.contains ( " repo:write" ) ) ;
5987
assert! ( hint_for ( " …: HTTP 404: no such repository" )
5988
.unwrap ( )
5989
.contains ( " owner and repository name" ) ) ;
5990
assert! ( hint_for ( " invalid peer certificate: UnknownIssuer" )
5991
.unwrap ( )
5992
.contains ( " oxyhub-ca.pem" ) ) ;
5993
assert! ( hint_for ( " Connect error: Connection refused (os error 111)" )
5994
.unwrap ( )
5995
.contains ( " running" ) ) ;
5996
assert! ( hint_for ( " Dns Failed: failed to lookup address information" )
5997
.unwrap ( )
5998
.contains ( " resolve" ) ) ;
5999
assert_eq! ( hint_for ( " something else entirely" ) , None ) ;
6000
}
6001
6002
# [ test ]
6003
fn doctor_names_problems_and_what_to_do ( ) {
6004
hermetic ( ) ;
6005
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6006
let root = dir.path ( ) ;
6007
let origin = root.join ( " origin" ) ;
6008
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
6009
let mut repo = Repo:: init( & root.join ( " r" ) )
6010
.unwrap ( )
6011
.with_home ( root.join ( " home" ) ) ;
6012
let find = | r : & DoctorReport, area : & str , h : Health| {
6013
r.checks
6014
.iter ( )
6015
.find ( | c | c.area = = area & & c.health = = h )
6016
.cloned ( )
6017
} ;
6018
6019
let r = repo.doctor ( true ) ;
6020
assert! ( find ( & r, " identity" , Health:: Warn)
6021
.unwrap ( )
6022
.hint
6023
.unwrap ( )
6024
.contains ( " oxy identity create" ) ) ;
6025
assert! ( find ( & r, " repository" , Health:: Ok) .is_some ( ) ) ;
ad2496f7 Andy McCall 17 days ago ↶
6026
6027
assert! ( find ( & r, " trust" , Health:: Warn)
6028
.unwrap ( )
6029
.hint
6030
.unwrap ( )
6031
.contains ( " oxy policy init" ) ) ;
3e5772cc Andy McCall 1 month ago ↶
6032
6033
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
6034
.unwrap ( ) ;
ad2496f7 Andy McCall 17 days ago ↶
6035
repo.policy_init ( ) .unwrap ( ) ;
6036
let r = repo.doctor ( false ) ;
6037
let trust = find ( & r, " trust" , Health:: Ok) .expect ( " a fresh policy is fine" ) ;
6038
assert! (
6039
trust.message.contains ( " serial 1" ) & & trust.message.contains ( " 1 root" ) ,
6040
" {trust:?}"
6041
) ;
3e5772cc Andy McCall 1 month ago ↶
6042
repo.config.set ( " author.name" , " Ada" ) .unwrap ( ) ;
6043
repo.config.set ( " author.email" , " [email protected] " ) .unwrap ( ) ;
6044
repo.config
6045
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
6046
.unwrap ( ) ;
6047
repo.config
6048
.set ( " remote.down.url" , " http://127.0.0.1:9/a/b" )
6049
.unwrap ( ) ;
6050
repo.config.set ( " remote.down.token" , " t" ) .unwrap ( ) ;
6051
repo.config
6052
.set ( " remote.badca.url" , " https://example.invalid/a/b" )
6053
.unwrap ( ) ;
6054
repo.config
6055
.set (
6056
" remote.badca.ca" ,
6057
& root.join ( " missing.pem" ) .to_string_lossy ( ) ,
6058
)
6059
.unwrap ( ) ;
6060
repo.save_config ( ) .unwrap ( ) ;
6061
6062
let r = repo.doctor ( true ) ;
6063
assert! ( find ( & r, " identity" , Health:: Ok) .is_some ( ) , " {r:?}" ) ;
6064
assert! ( find ( & r, " author" , Health:: Ok) .is_some ( ) , " {r:?}" ) ;
6065
assert! ( find ( & r, " remote origin" , Health:: Ok)
6066
.unwrap ( )
6067
.message
6068
.contains ( " first push binds it" ) ) ;
6069
let down = find ( & r, " remote down" , Health:: Fail) .unwrap ( ) ;
6070
assert! (
6071
down.hint.as_deref ( ) .unwrap_or ( " " ) .contains ( " running" ) ,
6072
" {down:?}"
6073
) ;
6074
let badca = find ( & r, " remote badca" , Health:: Fail) .unwrap ( ) ;
6075
assert! (
6076
badca.hint.unwrap ( ) .contains ( " remote.badca.ca" ) ,
6077
" the remote's own name is in the hint"
6078
) ;
6079
assert_eq! ( r.worst ( ) , Health:: Fail) ;
6080
6081
6082
let r = repo.doctor ( false ) ;
6083
assert! ( find ( & r, " remote down" , Health:: Fail) .is_none ( ) ) ;
6084
assert! ( find ( & r, " remote down" , Health:: Ok)
6085
.unwrap ( )
6086
.message
6087
.contains ( " not contacted" ) ) ;
6088
}
62794ecc Andy McCall 1 month ago ↶
6089
6090
6091
6092
6093
# [ test ]
6094
fn deleting_a_line_after_an_insertion_below_it_is_not_a_conflict ( ) {
6095
hermetic ( ) ;
6096
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6097
let root = dir.path ( ) ;
6098
let repo = Repo:: init( root) .unwrap ( ) ;
6099
write ( root, " f" , b " a\n b\n c\n " ) ;
6100
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6101
write ( root, " f" , b " a\n b\n x\n c\n " ) ;
6102
let insert = repo.commit ( " insert x after b" , & [ ] ) .unwrap ( ) ;
6103
write ( root, " f" , b " a\n x\n c\n " ) ;
6104
let delete = repo.commit ( " delete b" , & [ ] ) .unwrap ( ) ;
6105
assert! (
6106
repo.store ( )
6107
.get_change ( & delete)
6108
.unwrap ( )
6109
.deps
6110
.contains ( & insert) ,
6111
" the deletion depends on the insertion below the deleted line"
6112
) ;
6113
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6114
assert_eq! ( heads, vec! [ delete] ) ;
6115
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
6116
assert! ( ! snap.is_conflicted ( ) ) ;
6117
assert! (
6118
matches! ( & snap.files[ " f" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n x\n c\n " )
6119
) ;
6120
}
6121
6122
6123
6124
# [ test ]
6125
fn a_binary_file_reverted_to_an_earlier_blob_keeps_it ( ) {
6126
hermetic ( ) ;
6127
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6128
let root = dir.path ( ) ;
6129
let repo = Repo:: init( root) .unwrap ( ) ;
6130
write ( root, " bin" , b " \0 A" ) ;
6131
let v1 = repo.commit ( " v1" , & [ ] ) .unwrap ( ) ;
6132
write ( root, " bin" , b " \0 B" ) ;
6133
let v2 = repo.commit ( " v2" , & [ ] ) .unwrap ( ) ;
6134
assert! ( repo.store ( ) .get_change ( & v2) .unwrap ( ) .deps.contains ( & v1) ) ;
6135
write ( root, " bin" , b " \0 A" ) ;
6136
let v3 = repo.commit ( " back to A" , & [ ] ) .unwrap ( ) ;
6137
assert! ( repo.store ( ) .get_change ( & v3) .unwrap ( ) .deps.contains ( & v2) ) ;
6138
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ v3] ) ;
6139
let state = repo.load_state ( & [ v3] ) .unwrap ( ) ;
6140
let snap = state.materialize ( ) ;
6141
let FileContent:: Opaque { blobs } = & snap.files[ " bin" ] .content else {
6142
panic! ( " opaque" )
6143
} ;
6144
assert_eq! ( blobs.len ( ) , 1 , " one live blob, not none and not two" ) ;
6145
assert_eq! ( repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) , b " \0 A" ) ;
6146
}
6147
6148
6149
6150
6151
# [ test ]
6152
fn git_import_resolves_a_binary_merged_in_git_and_verifies ( ) {
6153
hermetic ( ) ;
6154
if std:: process:: Command:: new( " git" )
6155
.arg ( " --version" )
6156
.output ( )
6157
.is_err ( )
6158
{
6159
eprintln! ( " git not installed; skipping" ) ;
6160
return ;
6161
}
6162
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6163
let root = dir.path ( ) ;
6164
let g = root.join ( " src.git" ) ;
6165
fs:: create_dir_all( & g) .unwrap ( ) ;
6166
let git = | args : & [& str ]| -> String {
6167
let out = std:: process:: Command:: new( " git" )
6168
.arg ( " -C" )
6169
.arg ( & g)
6170
.args ( [
6171
" -c" ,
6172
" user.name=Grace" ,
6173
" -c" ,
6174
" [email protected] " ,
6175
" -c" ,
6176
" commit.gpgsign=false" ,
6177
] )
6178
.args ( args)
6179
.output ( )
6180
.unwrap ( ) ;
6181
assert! (
6182
out.status.success ( ) ,
6183
" git {args:?}: {}" ,
6184
String :: from_utf8_lossy( & out.stderr)
6185
) ;
6186
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
6187
} ;
6188
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
6189
write ( & g, " logo.bin" , b " \0 base" ) ;
6190
write ( & g, " notes.txt" , b " a\n b\n c\n " ) ;
6191
git ( & [ " add" , " ." ] ) ;
6192
git ( & [ " commit" , " -qm" , " base" ] ) ;
6193
git ( & [ " checkout" , " -qb" , " side" ] ) ;
6194
write ( & g, " logo.bin" , b " \0 side" ) ;
6195
write ( & g, " notes.txt" , b " a\n b\n x\n c\n " ) ;
6196
git ( & [ " commit" , " -qam" , " side" ] ) ;
6197
git ( & [ " checkout" , " -q" , " main" ] ) ;
6198
write ( & g, " logo.bin" , b " \0 main" ) ;
6199
git ( & [ " commit" , " -qam" , " main" ] ) ;
6200
6201
let _ = std:: process:: Command:: new( " git" )
6202
.arg ( " -C" )
6203
.arg ( & g)
6204
.args ( [
6205
" -c" ,
6206
" user.name=G" ,
6207
" -c" ,
6208
" user.email=g@e" ,
6209
" merge" ,
6210
" -q" ,
6211
" side" ,
6212
] )
6213
.output ( ) ;
6214
write ( & g, " logo.bin" , b " \0 main" ) ;
6215
git ( & [ " add" , " ." ] ) ;
6216
git ( & [ " commit" , " -qm" , " merge side" ] ) ;
6217
6218
write ( & g, " logo.bin" , b " \0 base" ) ;
6219
git ( & [ " commit" , " -qam" , " back to base" ] ) ;
6220
6221
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
6222
let rep = repo.import_git ( & g, " main" ) .unwrap ( ) ;
6223
assert_eq! ( rep.merges, 1 ) ;
6224
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
6225
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
6226
assert_eq! ( v.commits, 5 ) ;
6227
let snap = repo
6228
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
6229
.unwrap ( )
6230
.materialize ( ) ;
6231
assert! ( ! snap.is_conflicted ( ) ) ;
6232
let FileContent:: Opaque { blobs } = & snap.files[ " logo.bin" ] .content else {
6233
panic! ( " opaque" )
6234
} ;
6235
assert_eq! ( repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) , b " \0 base" ) ;
6236
}
c863c7a7 Andy McCall 1 month ago ↶
6237
6238
6239
6240
6241
# [ cfg ( unix ) ]
6242
# [ test ]
6243
fn windows_file_systems_keep_exec_bits_and_symlinks ( ) {
6244
use crate :: portable:: Platform;
6245
use std:: os:: unix:: fs:: PermissionsExt;
6246
hermetic ( ) ;
6247
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6248
let root = dir.path ( ) ;
6249
let repo = Repo:: init( root) .unwrap ( ) ;
6250
write ( root, " run.sh" , b " #!/bin/sh\n " ) ;
6251
fs:: set_permissions( root.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
6252
std:: os:: unix:: fs:: symlink( " run.sh" , root.join ( " link" ) ) .unwrap ( ) ;
6253
repo.commit ( " script and link" , & [ ] ) .unwrap ( ) ;
6254
drop ( repo) ;
6255
6256
6257
fs:: set_permissions( root.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6258
fs:: remove_file( root.join ( " link" ) ) .unwrap ( ) ;
6259
write ( root, " link" , b " run.sh" ) ;
6260
let win = Repo:: open( root) .unwrap ( ) .with_platform ( Platform:: windows( ) ) ;
6261
let s = win.snapshot ( " oxy status" ) .unwrap ( ) ;
6262
assert! (
6263
s.statuses.is_empty ( ) ,
6264
" nothing changed on Windows: {:?}" ,
6265
statuses ( & s)
6266
) ;
6267
6268
write ( root, " run.sh" , b " #!/bin/sh\n echo hi\n " ) ;
6269
write ( root, " link" , b " other" ) ;
6270
let h = win.commit ( " edit on Windows" , & [ ] ) .unwrap ( ) ;
6271
let snap = win.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
6272
assert! ( snap.files[ " run.sh" ] .mode.exec, " the exec bit survives" ) ;
6273
assert_eq! ( snap.files[ " link" ] .kind, NodeKind:: Symlink) ;
6274
6275
let linux = Repo:: open( root) .unwrap ( ) ;
6276
assert! ( ! linux.snapshot ( " oxy status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6277
}
6278
6279
6280
6281
# [ test ]
6282
fn paths_other_platforms_cannot_hold_are_reported ( ) {
6283
use crate :: portable:: Platform;
6284
hermetic ( ) ;
6285
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6286
let root = dir.path ( ) ;
6287
let repo = Repo:: init( root) .unwrap ( ) ;
6288
write ( root, " README.md" , b " a\n " ) ;
6289
write ( root, " readme.md" , b " b\n " ) ;
6290
write ( root, " src/aux.c" , b " int x;\n " ) ;
6291
let h = repo.commit ( " unportable" , & [ ] ) .unwrap ( ) ;
6292
let r = repo.doctor ( false ) ;
6293
let port = r.checks.iter ( ) .find ( | c | c.area = = " portability" ) .unwrap ( ) ;
6294
assert_eq! ( port.health, Health:: Warn) ;
6295
assert! (
6296
port.message.contains ( " differ only in case" ) & & port.message.contains ( " aux.c" ) ,
6297
" {}" ,
6298
port.message
6299
) ;
6300
let mac = Repo:: open( root) .unwrap ( ) .with_platform ( Platform:: macos( ) ) ;
6301
let rep = mac.checkout ( & [ h] , true , " oxy checkout" ) .unwrap ( ) ;
6302
assert! (
6303
rep.notes
6304
.iter ( )
6305
.any ( | n | n.contains ( " README.md and readme.md" ) ) ,
6306
" {:?}" ,
6307
rep.notes
6308
) ;
6309
assert! (
6310
! rep.notes.iter ( ) .any ( | n | n.contains ( " aux.c" ) ) ,
6311
" macOS allows aux.c"
6312
) ;
6313
}
bc489b6c Andy McCall 1 month ago ↶
6314
6315
# [ test ]
6316
fn history_and_blame_of_a_loaded_state_match_the_full_walk ( ) {
6317
hermetic ( ) ;
6318
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6319
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
6320
write ( dir.path ( ) , " a.txt" , b " one\n " ) ;
6321
write ( dir.path ( ) , " other.txt" , b " x\n " ) ;
6322
write ( dir.path ( ) , " bin.dat" , & [ 0 u8 , 1 , 2 , 0 , 255 ] ) ;
6323
repo.snapshot ( " t" ) .unwrap ( ) ;
6324
let c1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
6325
write ( dir.path ( ) , " other.txt" , b " y\n " ) ;
6326
repo.snapshot ( " t" ) .unwrap ( ) ;
6327
repo.commit ( " other only" , & [ ] ) .unwrap ( ) ;
6328
write ( dir.path ( ) , " a.txt" , b " one\n two\n " ) ;
6329
repo.snapshot ( " t" ) .unwrap ( ) ;
6330
let c3 = repo.commit ( " second line" , & [ ] ) .unwrap ( ) ;
6331
write ( dir.path ( ) , " other.txt" , b " z\n " ) ;
6332
repo.snapshot ( " t" ) .unwrap ( ) ;
6333
repo.commit ( " other again" , & [ ] ) .unwrap ( ) ;
6334
6335
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6336
let state = repo.load_state ( & heads) .unwrap ( ) ;
6337
let full: Vec< ChangeHash> = repo
6338
.log_for_path_in ( " a.txt" , & heads)
6339
.unwrap ( )
6340
.iter ( )
6341
.map ( | e | e.hash )
6342
.collect ( ) ;
6343
assert_eq! (
6344
full,
6345
vec! [ c3, c1] ,
6346
" newest first, only changes touching the path"
6347
) ;
6348
let newest: Vec< ChangeHash> = repo
6349
.log_for_path_of ( & state, " a.txt" , Some ( 1 ) )
6350
.unwrap ( )
6351
.iter ( )
6352
.map ( | e | e.hash )
6353
.collect ( ) ;
6354
assert_eq! ( newest, vec! [ c3] , " stops at the first" ) ;
6355
assert_eq! (
6356
repo.log_for_path_of ( & state, " a.txt" , None ) .unwrap ( ) .len ( ) ,
6357
2
6358
) ;
6359
assert! ( repo
6360
.log_for_path_of ( & state, " missing.txt" , Some ( 1 ) )
6361
.is_err ( ) ) ;
6362
assert_eq! (
6363
repo.log_state ( & heads) .unwrap ( ) .len ( ) ,
6364
repo.log_of ( & state) .len ( )
6365
) ;
6366
6367
let tree = state.tree ( ) .view ( ) ;
6368
let via_state = repo.blame_of ( & state, & tree, " a.txt" ) .unwrap ( ) ;
6369
let via_heads = repo.blame_in ( " a.txt" , & heads) .unwrap ( ) ;
6370
assert_eq! (
6371
via_state
6372
.iter ( )
6373
.map ( | l | ( l.hash, l.text.clone ( ) ) )
6374
.collect:: < Vec< _ > > ( ) ,
6375
via_heads
6376
.iter ( )
6377
.map ( | l | ( l.hash, l.text.clone ( ) ) )
6378
.collect:: < Vec< _ > > ( )
6379
) ;
6380
assert_eq! (
6381
via_state.iter ( ) .map ( | l | l.hash ) .collect:: < Vec< _ > > ( ) ,
6382
vec! [ c1, c3]
6383
) ;
6384
assert! ( repo.blame_of ( & state, & tree, " missing.txt" ) .is_err ( ) ) ;
6385
}
696bbf29 Andy McCall 1 month ago ↶
6386
6387
6388
6389
6390
# [ cfg ( unix ) ]
6391
# [ test ]
6392
fn stat_cache_takes_unchanged_text_from_the_base_and_misses_no_edit ( ) {
6393
use std:: os:: unix:: fs:: PermissionsExt;
6394
hermetic ( ) ;
6395
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6396
let root = dir.path ( ) ;
6397
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6398
write ( root, " a.txt" , b " one\n two\n " ) ;
6399
write ( root, " b.txt" , b " bee\n " ) ;
6400
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6401
let age = | p : & str | {
6402
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6403
fs:: File:: options( )
6404
.write ( true )
6405
.open ( root.join ( p) )
6406
.unwrap ( )
6407
.set_modified ( old)
6408
.unwrap ( ) ;
6409
} ;
6410
age ( " a.txt" ) ;
6411
age ( " b.txt" ) ;
6412
assert! (
6413
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
6414
" records the aged stat"
6415
) ;
6416
6417
6418
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6419
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
6420
assert! (
6421
! snap.changed & & snap.statuses.is_empty ( ) ,
6422
" {:?}" ,
6423
snap.statuses
6424
) ;
6425
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6426
6427
6428
6429
write ( root, " b.txt" , b " bee edited\n " ) ;
6430
age ( " b.txt" ) ;
6431
let first = repo.snapshot ( " status" ) .unwrap ( ) ;
6432
assert_eq! (
6433
statuses ( & first) ,
6434
vec! [ ( " b.txt" .to_string ( ) , ChangeStatus:: Modified) ]
6435
) ;
6436
let again = repo.snapshot ( " status" ) .unwrap ( ) ;
6437
assert_eq! (
6438
statuses ( & again) ,
6439
vec! [ ( " b.txt" .to_string ( ) , ChangeStatus:: Modified) ] ,
6440
" still seen once stat-clean"
6441
) ;
6442
assert! ( ! again.changed, " and the working copy hashes the same" ) ;
6443
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6444
assert! (
6445
repo.snapshot ( " status" ) .is_err ( ) ,
6446
" an edited file must be read, never taken from the base"
6447
) ;
6448
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6449
6450
6451
write ( root, " a.txt" , b " one\n TWO\n " ) ;
6452
assert_eq! (
6453
statuses ( & repo.snapshot ( " status" ) .unwrap ( ) ) ,
6454
vec! [
6455
( " a.txt" .to_string ( ) , ChangeStatus:: Modified) ,
6456
( " b.txt" .to_string ( ) , ChangeStatus:: Modified)
6457
]
6458
) ;
6459
6460
repo.commit ( " edits" , & [ ] ) .unwrap ( ) ;
6461
age ( " a.txt" ) ;
6462
age ( " b.txt" ) ;
6463
assert! ( repo.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6464
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6465
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6466
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
6467
assert! ( ! snap.changed & & snap.statuses.is_empty ( ) ) ;
6468
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6469
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6470
}
afaba5f1 Andy McCall 1 month ago ↶
6471
6472
6473
6474
6475
# [ test ]
6476
fn checkpoints_keep_large_graphs_as_blobs_of_their_own ( ) {
6477
hermetic ( ) ;
6478
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6479
let root = dir.path ( ) ;
6480
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6481
let long: String = ( 0 .. 300 )
6482
.map ( | i | format! ( " line {i} of a long file\n " ) )
6483
.collect ( ) ;
6484
write ( root, " long.txt" , long.as_bytes ( ) ) ;
6485
write ( root, " short.txt" , b " short\n " ) ;
6486
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6487
let ( r1, _ , _ ) = repo.checkpoint ( ) .unwrap ( ) ;
6488
let cp1 = crate :: objects:: Checkpoint:: decode(
6489
& repo.store ( ) .get_typed ( & r1, ObjectType:: Checkpoint) .unwrap ( ) ,
6490
)
6491
.unwrap ( ) ;
6492
let graphs1 = repo.checkpoint_graph_blobs ( & cp1) .unwrap ( ) ;
6493
assert_eq! ( graphs1.len ( ) , 1 , " only the long file's graph is a blob" ) ;
6494
6495
6496
repo.gc ( true ) .unwrap ( ) ;
6497
assert! ( repo.store ( ) .has ( & graphs1[ 0 ] ) ) ;
6498
let report = repo.fsck ( ) .unwrap ( ) ;
6499
assert_eq! ( report.errors ( ) , 0 , " {:?}" , report.problems) ;
6500
6501
6502
write ( root, " short.txt" , b " short, edited\n " ) ;
6503
repo.commit ( " edit the short file" , & [ ] ) .unwrap ( ) ;
6504
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6505
let fast = repo.load_state ( & heads) .unwrap ( ) ;
6506
let full = repo.load_state_full ( & heads) .unwrap ( ) ;
6507
assert_eq! ( fast.checkpoint_payload ( ) , full.checkpoint_payload ( ) ) ;
6508
6509
6510
let ( r2, _ , _ ) = repo.checkpoint ( ) .unwrap ( ) ;
6511
let cp2 = crate :: objects:: Checkpoint:: decode(
6512
& repo.store ( ) .get_typed ( & r2, ObjectType:: Checkpoint) .unwrap ( ) ,
6513
)
6514
.unwrap ( ) ;
6515
assert_eq! ( repo.checkpoint_graph_blobs ( & cp2) .unwrap ( ) , graphs1) ;
6516
6517
6518
let bundle = repo.checkpoint_bundle_for ( & heads) .unwrap ( ) ;
6519
assert! ( bundle.objects.iter ( ) .any ( | ( t , body ) | * t = = ObjectType:: Blob
6520
& & crate :: hash:: hash_object( ObjectType:: Blob, body) = = graphs1[ 0 ] ) ) ;
6521
6522
6523
let state = repo.load_state_full ( & heads) .unwrap ( ) ;
6524
let old = repo.store_blob ( & state.checkpoint_payload ( ) ) .unwrap ( ) ;
6525
let cp_old = crate :: objects:: Checkpoint {
6526
heads: cp2.heads.clone ( ) ,
6527
count: cp2.count,
6528
payload: old,
6529
} ;
6530
let r_old = repo
6531
.store ( )
6532
.put ( ObjectType:: Checkpoint, & cp_old.encode ( ) )
6533
.unwrap ( ) ;
6534
repo.install_checkpoint ( " main" , & r_old) .unwrap ( ) ;
6535
assert_eq! (
6536
repo.load_state ( & heads) .unwrap ( ) .checkpoint_payload ( ) ,
6537
full.checkpoint_payload ( )
6538
) ;
6539
assert_eq! ( repo.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
6540
6541
6542
6543
let dir2 = tempfile:: tempdir( ) .unwrap ( ) ;
6544
let root2 = dir2.path ( ) ;
6545
let repo2 = Repo:: init( root2) .unwrap ( ) .with_home ( root2.join ( " home" ) ) ;
6546
write ( root2, " long.txt" , long.as_bytes ( ) ) ;
6547
repo2.commit ( " base" , & [ ] ) .unwrap ( ) ;
6548
let ( r, _ , _ ) = repo2.checkpoint ( ) .unwrap ( ) ;
6549
let cp = crate :: objects:: Checkpoint:: decode(
6550
& repo2.store ( ) .get_typed ( & r, ObjectType:: Checkpoint) .unwrap ( ) ,
6551
)
6552
.unwrap ( ) ;
6553
let graph = repo2.checkpoint_graph_blobs ( & cp) .unwrap ( ) [ 0 ] ;
64818938 Andy McCall 1 month ago ↶
6554
repo2.store ( ) .remove_loose_for_test ( & graph) .unwrap ( ) ;
afaba5f1 Andy McCall 1 month ago ↶
6555
assert! ( repo2.load_state ( & repo2.bookmark_heads ( ) .unwrap ( ) ) .is_err ( ) ) ;
6556
assert! ( repo2.fsck ( ) .unwrap ( ) .errors ( ) > 0 , " fsck reports it" ) ;
6557
}
e3d071ce Andy McCall 1 month ago ↶
6558
6559
6560
6561
6562
# [ test ]
6563
fn identity_is_found_in_the_key_directory_for_a_new_repository ( ) {
6564
hermetic ( ) ;
6565
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6566
let home = dir.path ( ) .join ( " home" ) ;
6567
let mut first = Repo:: init( & dir.path ( ) .join ( " a" ) )
6568
.unwrap ( )
6569
.with_home ( home.clone ( ) ) ;
6570
let ( key, _ ) = first
6571
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
6572
.unwrap ( ) ;
6573
let fresh = Repo:: init( & dir.path ( ) .join ( " b" ) )
6574
.unwrap ( )
6575
.with_home ( home.clone ( ) ) ;
6576
assert! ( ! fresh.store ( ) .has ( & key) , " nothing stored yet" ) ;
6577
let id = fresh.identity ( & key) .unwrap ( ) ;
6578
assert_eq! ( ( id.name.as_str ( ) , id.hash ( ) ) , ( " Ada" , key) ) ;
6579
6580
let keys = fresh.keys_dir.clone ( ) ;
6581
let ( other, _ ) = first
6582
.create_identity ( " Bob" , " [email protected] " , IdentityScope:: User)
6583
.unwrap ( ) ;
6584
fs:: copy(
6585
keys.join ( format! ( " {} .identity" , other.to_hex ( ) ) ) ,
6586
keys.join ( format! ( " {} .identity" , key.to_hex ( ) ) ) ,
6587
)
6588
.unwrap ( ) ;
6589
assert! ( fresh
6590
.identity ( & key)
6591
.unwrap_err ( )
6592
.to_string ( )
6593
.contains ( " does not hold identity" ) ) ;
6594
}
cc7a0056 Andy McCall 1 month ago ↶
6595
6596
6597
6598
6599
6600
# [ cfg ( unix ) ]
6601
# [ test ]
6602
fn status_snapshots_skip_files_unchanged_since_the_manifest ( ) {
6603
use std:: os:: unix:: fs:: PermissionsExt;
6604
hermetic ( ) ;
6605
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6606
let root = dir.path ( ) ;
6607
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6608
for ( p, c) in [ ( " a.txt" , " one\n " ) , ( " b.txt" , " bee\n " ) , ( " d/c.txt" , " sea\n " ) ] {
6609
write ( root, p, c.as_bytes ( ) ) ;
6610
}
6611
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6612
let age = | p : & str | {
6613
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6614
fs:: File:: options( )
6615
.write ( true )
6616
.open ( root.join ( p) )
6617
.unwrap ( )
6618
.set_modified ( old)
6619
.unwrap ( ) ;
6620
} ;
6621
let lock = | p : & str , mode : u32 | {
6622
fs:: set_permissions( root.join ( p) , fs:: Permissions:: from_mode( mode) ) .unwrap ( )
6623
} ;
6624
for p in [ " a.txt" , " b.txt" , " d/c.txt" ] {
6625
age ( p) ;
6626
}
6627
assert! ( repo.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6628
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
6629
assert! (
6630
m.entries.values ( ) .all ( | e | e.graph.is_some ( ) ) ,
6631
" keys recorded"
6632
) ;
6633
6634
6635
6636
lock ( " a.txt" , 0o000 ) ;
6637
lock ( " b.txt" , 0o000 ) ;
6638
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6639
assert! ( ! snap.changed & & snap.statuses.is_empty ( ) ) ;
6640
assert_eq! (
6641
snap.base_snapshot
6642
.unrendered
6643
.iter ( )
6644
.cloned ( )
6645
.collect:: < Vec< _ > > ( ) ,
6646
vec! [ " a.txt" , " b.txt" , " d/c.txt" ]
6647
) ;
6648
assert! ( ! snap.base.is_full ( ) ) ;
6649
assert! ( ! snap.base_snapshot.is_conflicted ( ) ) ;
6650
6651
let full = repo.snapshot ( " status" ) .unwrap ( ) ;
6652
assert! ( full.statuses.is_empty ( ) & & full.base.is_full ( ) ) ;
6653
assert! ( full.base_snapshot.unrendered.is_empty ( ) ) ;
6654
assert! ( matches! ( & full.base_snapshot.files[ " a.txt" ] .content,
6655
FileContent:: Text { bytes, .. } if bytes = = b " one\n " ) ) ;
6656
6657
6658
write ( root, " d/c.txt" , b " sea edited\n " ) ;
6659
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6660
assert_eq! (
6661
statuses ( & snap) ,
6662
vec! [ ( " d/c.txt" .to_string ( ) , ChangeStatus:: Modified) ]
6663
) ;
6664
assert! ( ! snap.base_snapshot.unrendered.contains ( " d/c.txt" ) ) ;
6665
6666
lock ( " b.txt" , 0o100 ) ;
6667
assert_eq! (
6668
statuses ( & repo.snapshot_status ( " status" ) .unwrap ( ) ) ,
6669
vec! [
6670
( " b.txt" .to_string ( ) , ChangeStatus:: ModeChanged) ,
6671
( " d/c.txt" .to_string ( ) , ChangeStatus:: Modified)
6672
]
6673
) ;
6674
lock ( " b.txt" , 0o000 ) ;
6675
6676
6677
6678
repo.commit_selection ( " edit c" , & [ ] , & Selection:: Paths( vec! [ " d/c.txt" .into ( ) ] ) )
6679
.unwrap ( ) ;
6680
age ( " d/c.txt" ) ;
6681
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6682
assert! ( snap.statuses.is_empty ( ) , " {:?}" , snap.statuses) ;
6683
assert! ( snap.base_snapshot.unrendered.contains ( " a.txt" ) ) ;
6684
lock ( " d/c.txt" , 0o000 ) ;
6685
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6686
assert! ( snap.statuses.is_empty ( ) ) ;
6687
assert! ( snap.base_snapshot.unrendered.contains ( " d/c.txt" ) ) ;
6688
for p in [ " a.txt" , " b.txt" , " d/c.txt" ] {
6689
lock ( p, 0o644 ) ;
6690
}
6691
}
6692
6693
6694
6695
# [ cfg ( unix ) ]
6696
# [ test ]
6697
fn a_changed_attributes_file_disables_skipping_unchanged_files ( ) {
6698
hermetic ( ) ;
6699
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6700
let root = dir.path ( ) ;
6701
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6702
write ( root, " a.txt" , b " crlf\r \n " ) ;
6703
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6704
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6705
fs:: File:: options( )
6706
.write ( true )
6707
.open ( root.join ( " a.txt" ) )
6708
.unwrap ( )
6709
.set_modified ( old)
6710
.unwrap ( ) ;
6711
assert! ( repo.snapshot_status ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6712
assert! ( repo
6713
.snapshot_status ( " status" )
6714
.unwrap ( )
6715
.base_snapshot
6716
.unrendered
6717
.contains ( " a.txt" ) ) ;
6718
6719
write ( root, " .oxyattributes" , b " *.txt eol=lf\n " ) ;
6720
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6721
assert_eq! (
6722
statuses ( & snap) ,
6723
vec! [
6724
( " .oxyattributes" .to_string ( ) , ChangeStatus:: Added) ,
6725
( " a.txt" .to_string ( ) , ChangeStatus:: Modified)
6726
]
6727
) ;
6728
}
54cd08a1 Andy McCall 1 month ago ↶
6729
6730
6731
6732
6733
# [ test ]
6734
fn bookmarks_with_slashes_push_and_checkpoint ( ) {
6735
hermetic ( ) ;
6736
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6737
let root = dir.path ( ) ;
6738
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
6739
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
6740
let mut repo = Repo:: clone( & url, & root.join ( " r" ) )
6741
.unwrap ( )
6742
.with_home ( root.join ( " home" ) ) ;
6743
6744
6745
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
6746
.unwrap ( ) ;
6747
let r = root.join ( " r" ) ;
6748
write ( & r, " a.txt" , b " a\n " ) ;
6749
let h_a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
6750
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
6751
origin.refresh ( ) .unwrap ( ) ;
54cd08a1 Andy McCall 1 month ago ↶
6752
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
6753
repo.bookmark_create ( " feature/x" , None ) .unwrap ( ) ;
6754
repo.bookmark_switch ( " feature/x" , false ) .unwrap ( ) ;
6755
write ( & r, " b.txt" , b " b\n " ) ;
6756
let h_b = repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
6757
for name in [ " feature" , " feature/x" ] {
6758
let res = repo.push_bookmark ( " origin" , name, false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
6759
origin.refresh ( ) .unwrap ( ) ;
54cd08a1 Andy McCall 1 month ago ↶
6760
assert! ( res[ 0 ] .ok, " {name}: {res:?}" ) ;
6761
}
6762
assert_eq! ( origin.resolve_heads ( " feature" ) .unwrap ( ) , vec! [ h_a] ) ;
6763
assert_eq! (
6764
origin.resolve_heads ( " feature/x" ) .unwrap ( ) ,
6765
repo.resolve_heads ( " feature/x" ) .unwrap ( )
6766
) ;
6767
assert! ( origin.resolve_heads ( " feature/x" ) .unwrap ( ) .contains ( & h_b) ) ;
6768
assert! ( origin.latest_ref_attest ( " feature/x" ) .unwrap ( ) .is_some ( ) ) ;
6769
assert! ( origin.latest_ref_attest ( " feature" ) .unwrap ( ) .is_some ( ) ) ;
6770
6771
6772
let ( cp, _ , _ ) = repo.checkpoint ( ) .unwrap ( ) ;
6773
assert_eq! ( repo.latest_checkpoint ( " feature/x" ) .unwrap ( ) .unwrap ( ) .0 , cp) ;
6774
assert! ( repo.latest_checkpoint ( " feature" ) .unwrap ( ) .is_none ( ) ) ;
6775
let heads = repo.resolve_heads ( " feature/x" ) .unwrap ( ) ;
6776
assert! ( repo.load_state_checkpointed ( & heads) .unwrap ( ) .is_some ( ) ) ;
f8c69576 Andy McCall 1 month ago ↶
6777
}
6778
6779
6780
6781
6782
# [ test ]
6783
fn bookmarks_are_deleted_on_a_remote ( ) {
6784
hermetic ( ) ;
6785
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6786
let root = dir.path ( ) ;
6787
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
6788
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
6789
let mut repo = Repo:: clone( & url, & root.join ( " r" ) )
6790
.unwrap ( )
6791
.with_home ( root.join ( " home" ) ) ;
6792
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
6793
.unwrap ( ) ;
6794
let r = root.join ( " r" ) ;
6795
write ( & r, " a.txt" , b " a\n " ) ;
6796
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
6797
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
6798
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6799
repo.bookmark_create ( " feature/x" , None ) .unwrap ( ) ;
6800
assert! ( repo.push_bookmark ( " origin" , " feature/x" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
6801
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6802
assert! ( origin.resolve_heads ( " feature/x" ) .is_ok ( ) ) ;
6803
6804
let res = repo.push_delete ( " origin" , " feature/x" ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
6805
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6806
assert! ( res[ 0 ] .ok, " {res:?}" ) ;
6807
assert! ( ! origin.view ( ) .unwrap ( ) .bookmarks.contains_key ( " feature/x" ) ) ;
6808
let ( _ , att) = origin.latest_ref_attest ( " feature/x" ) .unwrap ( ) .unwrap ( ) ;
6809
assert! (
6810
matches! ( att.attest.body, AttestBody:: Ref { new_state, .. } if new_state.is_zero ( ) ) ,
6811
" the deletion is attested"
6812
) ;
6813
assert! (
6814
repo.view ( ) .unwrap ( ) .bookmarks.contains_key ( " feature/x" ) ,
6815
" the local bookmark stays"
6816
) ;
6817
assert! ( ! repo
6818
.view ( )
6819
.unwrap ( )
6820
.remotes
6821
.get ( " origin" )
6822
.is_some_and ( | m | m.contains_key ( " feature/x" ) ) ) ;
6823
6824
6825
let res = repo.push_bookmark ( " origin" , " feature/x" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
6826
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6827
assert! ( res[ 0 ] .ok, " {res:?}" ) ;
6828
assert! ( origin.view ( ) .unwrap ( ) .bookmarks.contains_key ( " feature/x" ) ) ;
6829
6830
6831
assert! ( repo.push_delete ( " origin" , " nope" ) .is_err ( ) ) ;
6832
let res = repo.push_delete ( " origin" , " main" ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
6833
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6834
assert! (
6835
! res[ 0 ] .ok & & res[ 0 ] .reason.contains ( " default bookmark" ) ,
6836
" {res:?}"
6837
) ;
6838
assert! ( origin.view ( ) .unwrap ( ) .bookmarks.contains_key ( " main" ) ) ;
54cd08a1 Andy McCall 1 month ago ↶
6839
}
61e3bce7 Andy McCall 1 month ago ↶
6840
6841
6842
6843
6844
# [ test ]
6845
fn switching_bookmarks_carries_uncommitted_work ( ) {
6846
hermetic ( ) ;
6847
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6848
let root = dir.path ( ) ;
6849
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6850
write ( root, " a.txt" , b " a\n " ) ;
6851
write ( root, " b.txt" , b " b\n " ) ;
6852
write ( root, " gone.txt" , b " gone\n " ) ;
6853
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6854
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
6855
6856
write ( root, " a.txt" , b " a\n more\n " ) ;
6857
write ( root, " new.txt" , b " new\n " ) ;
6858
fs:: remove_file( root.join ( " gone.txt" ) ) .unwrap ( ) ;
44f1ebe7 Andy McCall 17 days ago ↶
6859
let ( report, carried) = repo.bookmark_switch_carry ( " feature" ) .unwrap ( ) ;
61e3bce7 Andy McCall 1 month ago ↶
6860
assert_eq! ( carried, vec! [ " a.txt" , " gone.txt" , " new.txt" ] ) ;
44f1ebe7 Andy McCall 17 days ago ↶
6861
6862
assert_eq! (
6863
( report.removed, report.removed_paths.len ( ) ) ,
6864
( 0 , 0 ) ,
6865
" {report:?}"
6866
) ;
61e3bce7 Andy McCall 1 month ago ↶
6867
assert_eq! ( repo.config.bookmark, " feature" ) ;
6868
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " a\n more\n " ) ;
6869
assert_eq! ( fs:: read( root.join ( " new.txt" ) ) .unwrap ( ) , b " new\n " ) ;
6870
assert! ( ! root.join ( " gone.txt" ) .exists ( ) ) ;
6871
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
6872
assert_eq! (
6873
statuses ( & snap) ,
6874
vec! [
6875
( " a.txt" .to_string ( ) , ChangeStatus:: Modified) ,
6876
( " gone.txt" .to_string ( ) , ChangeStatus:: Removed) ,
6877
( " new.txt" .to_string ( ) , ChangeStatus:: Added)
6878
]
6879
) ;
6880
repo.commit ( " the work" , & [ ] ) .unwrap ( ) ;
6881
assert! ( repo.resolve_heads ( " main" ) .unwrap ( ) .len ( ) = = 1 ) ;
6882
6883
6884
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
6885
write ( root, " b.txt" , b " b on main\n " ) ;
6886
repo.commit ( " b on main" , & [ ] ) .unwrap ( ) ;
6887
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
6888
write ( root, " b.txt" , b " b edited\n " ) ;
6889
let err = repo.bookmark_switch_carry ( " main" ) .unwrap_err ( ) .to_string ( ) ;
6890
assert! ( err.contains ( " b.txt" ) & & err.contains ( " --discard" ) , " {err}" ) ;
6891
assert_eq! ( repo.config.bookmark, " feature" , " nothing happened" ) ;
6892
assert_eq! ( fs:: read( root.join ( " b.txt" ) ) .unwrap ( ) , b " b edited\n " ) ;
6893
}
1c2bd9ab Andy McCall 1 month ago ↶
6894
6895
6896
6897
6898
# [ test ]
6899
fn merges_move_clean_working_copies_and_checkouts_say_what_blocks_them ( ) {
6900
hermetic ( ) ;
6901
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6902
let root = dir.path ( ) ;
6903
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6904
write ( root, " a.txt" , b " a\n " ) ;
6905
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
6906
write ( root, " b.txt" , b " b\n " ) ;
6907
repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
6908
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
6909
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
6910
write ( root, " c.txt" , b " c\n " ) ;
6911
repo.commit ( " c on feature" , & [ ] ) .unwrap ( ) ;
6912
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
6913
6914
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
6915
assert! ( ! root.join ( " b.txt" ) .exists ( ) ) ;
6916
let m = repo.merge ( " feature" ) .unwrap ( ) ;
6917
assert! ( m.checked_out, " a clean working copy follows the merge" ) ;
6918
for f in [ " a.txt" , " b.txt" , " c.txt" ] {
6919
assert! ( root.join ( f) .exists ( ) , " {f}" ) ;
6920
}
6921
6922
6923
fs:: remove_file( root.join ( " b.txt" ) ) .unwrap ( ) ;
6924
write ( root, " a.txt" , b " a edited\n " ) ;
6925
let err = repo
6926
.checkout ( & [ h1] , false , " checkout" )
6927
.unwrap_err ( )
6928
.to_string ( ) ;
6929
assert! (
6930
err.contains ( " modified a.txt" ) & & err.contains ( " removed b.txt" ) & & err.contains ( " --carry" ) ,
6931
" {err}"
6932
) ;
fe9586d9 Andy McCall 24 days ago ↶
6933
6934
6935
1c2bd9ab Andy McCall 1 month ago ↶
6936
let ( _ , carried) = repo
6937
.checkout_carry ( & [ h1] , " the first change" , " checkout" )
6938
.unwrap ( ) ;
6939
assert_eq! ( carried, vec! [ " a.txt" ] ) ;
6940
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " a edited\n " ) ;
6941
assert! ( ! root.join ( " b.txt" ) .exists ( ) , " h1 has no b.txt" ) ;
6942
}
e7ac6c2e Andy McCall 1 month ago ↶
6943
6944
6945
6946
6947
# [ test ]
6948
fn a_new_repository_is_healthy_before_its_first_commit ( ) {
6949
hermetic ( ) ;
6950
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6951
let root = dir.path ( ) ;
6952
let home = root.join ( " home" ) ;
6953
let mut first = Repo:: init( & root.join ( " first" ) )
6954
.unwrap ( )
6955
.with_home ( home.clone ( ) ) ;
6956
let ( key, _ ) = first
6957
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
6958
.unwrap ( ) ;
6959
let repo = Repo:: init( & root.join ( " second" ) )
6960
.unwrap ( )
6961
.with_home ( home.clone ( ) ) ;
6962
6963
let keys = repo.keys_dir.clone ( ) ;
6964
let _ = fs:: remove_file( keys.join ( format! ( " {} .identity" , key.to_hex ( ) ) ) ) ;
6965
assert! ( keys.join ( format! ( " {} .secret" , key.to_hex ( ) ) ) .is_file ( ) ) ;
6966
let r = repo.doctor ( true ) ;
6967
let id = r.checks.iter ( ) .find ( | c | c.area = = " identity" ) .unwrap ( ) ;
6968
assert_eq! ( id.health, Health:: Ok, " {}" , id.message) ;
6969
assert! ( id.message.contains ( " first signed change" ) , " {}" , id.message) ;
6970
6971
6972
let lib = root.join ( " lib" ) ;
6973
let l = Repo:: init( & lib) .unwrap ( ) .with_home ( home.clone ( ) ) ;
6974
write ( & lib, " x.txt" , b " x\n " ) ;
6975
l.commit ( " x" , & [ ] ) .unwrap ( ) ;
6976
let second = root.join ( " second" ) ;
6977
write ( & second, " a.txt" , b " a\n " ) ;
6978
repo.submodule_add ( & lib.to_string_lossy ( ) , " deps/lib" , None )
6979
.unwrap ( ) ;
6980
let subs = repo.subrepos ( ) .unwrap ( ) ;
6981
assert_eq! ( subs.len ( ) , 1 ) ;
6982
assert_eq! (
6983
( subs[ 0 ] .path.as_str ( ) , subs[ 0 ] .committed) ,
6984
( " deps/lib" , false )
6985
) ;
6986
assert_eq! ( subs[ 0 ] .actual.as_ref ( ) , Some ( & subs[ 0 ] .pin) ) ;
6987
repo.commit ( " add lib" , & [ ] ) .unwrap ( ) ;
6988
let subs = repo.subrepos ( ) .unwrap ( ) ;
6989
assert! ( subs[ 0 ] .committed, " committed now" ) ;
6990
}
8ab265dc Andy McCall 1 month ago ↶
6991
6992
6993
6994
6995
# [ cfg ( unix ) ]
6996
# [ test ]
6997
fn commits_and_diffs_leave_unchanged_files_alone ( ) {
6998
use std:: os:: unix:: fs:: PermissionsExt;
6999
hermetic ( ) ;
7000
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7001
let root = dir.path ( ) ;
7002
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
7003
write ( root, " big.txt" , b " lots\n of\n lines\n " ) ;
7004
write ( root, " a.txt" , b " one\n two\n " ) ;
7005
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7006
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
7007
for f in [ " big.txt" , " a.txt" ] {
7008
fs:: File:: options( )
7009
.write ( true )
7010
.open ( root.join ( f) )
7011
.unwrap ( )
7012
.set_modified ( old)
7013
.unwrap ( ) ;
7014
}
7015
assert! ( repo.snapshot_status ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
7016
7017
fs:: set_permissions( root.join ( " big.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
7018
7019
write ( root, " a.txt" , b " one\n TWO\n three\n " ) ;
7020
let diff = repo.diff_files ( None ) .unwrap ( ) ;
7021
assert_eq! (
7022
diff.iter ( ) .map ( | f | f.path.as_str ( ) ) .collect:: < Vec< _ > > ( ) ,
7023
[ " a.txt" ]
7024
) ;
7025
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
7026
assert! ( ! hunks.is_empty ( ) & & hunks.iter ( ) .all ( | h | h.path = = " a.txt" ) ) ;
7027
let h = repo.commit ( " edit a" , & [ ] ) .unwrap ( ) ;
7028
7029
7030
let c = repo.store ( ) .get_change ( & h) .unwrap ( ) ;
7031
let full = repo.load_state ( & c.deps) .unwrap ( ) ;
7032
let want = render_change ( & c, & full, & full.materialize ( ) ) ;
7033
assert_eq! ( repo.describe_change ( & h) .unwrap ( ) , want) ;
7034
let ( base, snap) = repo.change_base ( & c) .unwrap ( ) ;
7035
assert! ( ! base.is_full ( ) & & snap.unrendered.contains ( " big.txt" ) ) ;
7036
7037
7038
write ( root, " a.txt" , b " scratch\n " ) ;
7039
assert_eq! ( repo.discard ( & [ " a.txt" .into ( ) ] ) .unwrap ( ) , 1 ) ;
7040
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n TWO\n three\n " ) ;
7041
fs:: set_permissions( root.join ( " big.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
7042
}
f7510b63 Andy McCall 1 month ago ↶
7043
7044
7045
7046
7047
# [ test ]
7048
fn replace_conflicts_are_settled_when_asked ( ) {
7049
hermetic ( ) ;
7050
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7051
let root = dir.path ( ) ;
7052
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
7053
let blob = | n : u8 | {
7054
let mut v = vec! [ 0 u8 ; 64 ] ;
7055
v[ 1 ] = n;
7056
v
7057
} ;
7058
write ( root, " img.bin" , & blob ( 1 ) ) ;
7059
repo.commit ( " add img" , & [ ] ) .unwrap ( ) ;
7060
repo.bookmark_create ( " other" , None ) .unwrap ( ) ;
7061
write ( root, " img.bin" , & blob ( 2 ) ) ;
7062
repo.commit ( " img two" , & [ ] ) .unwrap ( ) ;
7063
repo.bookmark_switch ( " other" , false ) .unwrap ( ) ;
7064
write ( root, " img.bin" , & blob ( 3 ) ) ;
7065
repo.commit ( " img three" , & [ ] ) .unwrap ( ) ;
7066
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
7067
repo.merge ( " other" ) .unwrap ( ) ;
7068
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
7069
let merged = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
7070
assert! (
7071
matches! ( & merged.files[ " img.bin" ] .content, FileContent:: Opaque { blobs } if blobs.len ( ) = = 2 ) ,
7072
" a replace conflict"
7073
) ;
7074
7075
assert! ( repo
7076
.snapshot ( " status" )
7077
.unwrap ( )
7078
.statuses
7079
.iter ( )
7080
.all ( | s | s.path ! = " img.bin" ) ) ;
7081
7082
7083
let author = repo.author ( ) ;
7084
let edits = [ FileEdit {
7085
path: " img.bin" .into ( ) ,
7086
content: Some ( blob ( 3 ) ) ,
7087
} ] ;
7088
let c = repo
7089
.build_change ( & heads, & edits, " keep three" , author)
7090
.unwrap ( ) ;
7091
assert_eq! ( c.kind, ChangeKind:: Resolution) ;
7092
let mut st = repo.load_state ( & heads) .unwrap ( ) ;
7093
for d in & c.deps {
7094
assert! ( st.contains ( d) ) ;
7095
}
7096
assert! (
7097
heads.iter ( ) .all ( | h | c.deps.contains ( h) ) ,
7098
" depends on both contributors"
7099
) ;
7100
st.add ( c) .unwrap ( ) ;
7101
let after = st.materialize ( ) ;
7102
assert! ( ! after.is_conflicted ( ) ) ;
7103
assert_eq! (
7104
after.files[ " img.bin" ] .content,
7105
FileContent:: Opaque {
7106
blobs: vec! [ Repo:: blob_ref( & blob ( 3 ) ) ]
7107
}
7108
) ;
7109
7110
7111
let edits = [ FileEdit {
7112
path: " img.bin" .into ( ) ,
7113
content: Some ( blob ( 9 ) ) ,
7114
} ] ;
7115
let c = repo.build_change ( & heads, & edits, " new" , author) .unwrap ( ) ;
7116
let mut st = repo.load_state ( & heads) .unwrap ( ) ;
7117
st.add ( c) .unwrap ( ) ;
7118
assert_eq! (
7119
st.materialize ( ) .files[ " img.bin" ] .content,
7120
FileContent:: Opaque {
7121
blobs: vec! [ Repo:: blob_ref( & blob ( 9 ) ) ]
7122
}
7123
) ;
7124
}
83d61e80 Andy McCall 1 month ago ↶
7125
7126
7127
7128
7129
# [ test ]
7130
fn line_ranges_have_exact_histories ( ) {
7131
hermetic ( ) ;
7132
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7133
let root = dir.path ( ) ;
7134
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
7135
write ( root, " f.rs" , b " use x;\n fn a() {\n one();\n two();\n }\n " ) ;
7136
let base = repo.commit ( " add f" , & [ ] ) .unwrap ( ) ;
7137
write ( root, " f.rs" , b " use y;\n fn a() {\n one();\n two();\n }\n " ) ;
7138
let outside = repo.commit ( " change the import" , & [ ] ) .unwrap ( ) ;
7139
write ( root, " f.rs" , b " use y;\n fn a() {\n ONE();\n two();\n }\n " ) ;
7140
let inside = repo.commit ( " edit a line of a" , & [ ] ) .unwrap ( ) ;
7141
write ( root, " f.rs" , b " use y;\n fn a() {\n ONE();\n }\n " ) ;
7142
let deleted = repo.commit ( " delete a line of a" , & [ ] ) .unwrap ( ) ;
7143
write ( root, " f.rs" , b " use y;\n fn a() {\n ONE();\n }\n fn b() {}\n " ) ;
7144
let after = repo.commit ( " add b after a" , & [ ] ) .unwrap ( ) ;
7145
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
7146
7147
7148
let hist: Vec< ChangeHash> = repo
7149
.line_history ( & heads, " f.rs" , 2 , 4 )
7150
.unwrap ( )
7151
.iter ( )
7152
.map ( | e | e.hash )
7153
.collect ( ) ;
7154
for ( h, what) in [
7155
( base, " wrote it" ) ,
7156
( inside, " edited a line" ) ,
7157
( deleted, " deleted a line from inside" ) ,
7158
] {
7159
assert! ( hist.contains ( & h) , " {what}" ) ;
7160
}
7161
for ( h, what) in [ ( outside, " an edit above" ) , ( after, " an addition below" ) ] {
7162
assert! ( ! hist.contains ( & h) , " {what}" ) ;
7163
}
7164
assert_eq! ( hist[ 0 ] , deleted, " newest first" ) ;
7165
7166
let one: Vec< ChangeHash> = repo
7167
.line_history ( & heads, " f.rs" , 1 , 1 )
7168
.unwrap ( )
7169
.iter ( )
7170
.map ( | e | e.hash )
7171
.collect ( ) ;
7172
assert! ( one.contains ( & outside) & & ! one.contains ( & inside) ) ;
7173
assert! ( repo.line_history ( & heads, " f.rs" , 3 , 9 ) .is_err ( ) ) ;
7174
assert! ( repo.line_history ( & heads, " f.rs" , 0 , 1 ) .is_err ( ) ) ;
7175
assert! ( repo.line_history ( & heads, " nope.rs" , 1 , 1 ) .is_err ( ) ) ;
7176
}
fc1a466a Andy McCall 1 month ago ↶
7177
7178
# [ test ]
7179
fn receipts_are_kept_with_their_state_and_verified ( ) {
7180
use crate :: receipt:: { Receipt, SignedReceipt} ;
7181
hermetic ( ) ;
7182
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7183
let root = dir.path ( ) ;
7184
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
7185
write ( root, " a.txt" , b " 1\n " ) ;
7186
repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
7187
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
7188
let state = repo
7189
.store ( )
7190
.put_state ( & StateObject:: new( heads.clone ( ) ) )
7191
.unwrap ( ) ;
7192
let out = repo.store_blob ( b " built\n " ) .unwrap ( ) ;
7193
let receipt = Receipt {
7194
job_name: " build" .into ( ) ,
7195
pipeline_hash: ObjectRef( [ 1 ; 32 ] ) ,
7196
scope_tree_hash: ObjectRef( [ 2 ; 32 ] ) ,
7197
inputs: vec! [ ] ,
7198
environment: ObjectRef( [ 3 ; 32 ] ) ,
7199
outputs: vec! [ out] ,
7200
exit_code: 0 ,
7201
duration_ms: 42 ,
7202
} ;
7203
let sk = SecretKey:: generate( ) ;
7204
let runner = sk.identity ( " earth-1" , " runner@earth" , 1 ) ;
7205
let sr = SignedReceipt:: sign(
7206
receipt.clone ( ) ,
7207
repo.id ( ) .unwrap ( ) ,
7208
state,
7209
" demo#1" ,
7210
& runner,
7211
& sk,
7212
) ;
7213
7214
let r = repo.add_receipt ( & sr) .unwrap ( ) ;
7215
let found = repo.receipts ( & heads) .unwrap ( ) ;
7216
assert_eq! ( found.len ( ) , 1 ) ;
7217
assert_eq! ( found[ 0 ] .attest, r) ;
7218
assert_eq! ( found[ 0 ] .signed, sr) ;
7219
assert! ( found[ 0 ] .problem.is_none ( ) & & ! found[ 0 ] .revoked) ;
7220
assert! ( repo.receipts ( & [ ObjectRef( [ 5 ; 32 ] ) ] ) .unwrap ( ) .is_empty ( ) ) ;
7221
7222
7223
let elsewhere = SignedReceipt:: sign( receipt.clone ( ) , [ 0 ; 16 ] , state, " x" , & runner, & sk) ;
7224
assert! ( repo
7225
.add_receipt ( & elsewhere)
7226
.unwrap_err ( )
7227
.to_string ( )
7228
.contains ( " another repository" ) ) ;
7229
let mut forged = sr.clone ( ) ;
7230
forged.receipt.exit_code = 1 ;
7231
assert! ( repo
7232
.add_receipt ( & forged)
7233
.unwrap_err ( )
7234
.to_string ( )
7235
.contains ( " does not verify" ) ) ;
7236
c63fed2e Andy McCall 19 days ago ↶
7237
7238
7239
let hub_sk = SecretKey:: generate( ) ;
7240
let hub = hub_sk.identity ( " oxygenhub.io" , " [email protected] " , 1 ) ;
7241
let job_sk = SecretKey:: generate( ) ;
7242
let job = job_sk.identity ( " job 2 test" , " [email protected] " , 1 ) ;
7243
let mut test_receipt = receipt.clone ( ) ;
7244
test_receipt.job_name = " test" .into ( ) ;
7245
let plain = SignedReceipt:: sign(
7246
test_receipt,
7247
repo.id ( ) .unwrap ( ) ,
7248
state,
7249
" demo#2" ,
7250
& job,
7251
& job_sk,
7252
) ;
7253
let now = plain.attest.hlc.wall_ms;
7254
let cert = crate :: cert:: SignedCertificate:: sign(
7255
crate :: cert:: Certificate {
7256
key: job.clone ( ) ,
7257
issuer: hub.hash ( ) ,
7258
subject: " repo:andy/demo:bookmark:main" .into ( ) ,
7259
claims: vec! [ ] ,
7260
scope: crate :: identity:: Scope:: SEAL ,
7261
not_before: now - 1_000 ,
7262
expires: now + 600_000 ,
a2387d54 Andy McCall 18 days ago ↶
7263
grant: None ,
c63fed2e Andy McCall 19 days ago ↶
7264
} ,
7265
& hub,
7266
& hub_sk,
7267
) ;
7268
let certified = plain.with_certificate ( cert, hub.clone ( ) ) ;
7269
repo.add_receipt ( & certified) .unwrap ( ) ;
7270
let found = repo.receipts ( & heads) .unwrap ( ) ;
7271
let got = found
7272
.iter ( )
7273
.find ( | i | i.signed.receipt.job_name = = " test" )
7274
.unwrap ( ) ;
7275
assert_eq! ( got.signed, certified) ;
7276
assert! ( got.problem.is_none ( ) , " {:?}" , got.problem) ;
7277
7278
7279
fc1a466a Andy McCall 1 month ago ↶
7280
repo.gc ( true ) .unwrap ( ) ;
c63fed2e Andy McCall 19 days ago ↶
7281
let found = repo.receipts ( & heads) .unwrap ( ) ;
7282
assert_eq! ( found.len ( ) , 2 ) ;
7283
let got = found
7284
.iter ( )
7285
.find ( | i | i.signed.receipt.job_name = = " test" )
7286
.unwrap ( ) ;
7287
assert_eq! ( got.signed, certified, " the certificate survives gc" ) ;
7288
assert! ( got.problem.is_none ( ) ) ;
fc1a466a Andy McCall 1 month ago ↶
7289
assert_eq! ( repo.read_blob ( & out) .unwrap ( ) , b " built\n " ) ;
7290
assert! ( repo.fsck ( ) .unwrap ( ) .problems.is_empty ( ) ) ;
dfb23a3a Andy McCall 1 month ago ↶
7291
}
7292
7293
# [ test ]
3a4b3e01 Andy McCall 20 days ago ↶
7294
fn approvals_are_kept_with_their_state_and_survive_gc ( ) {
7295
hermetic ( ) ;
7296
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7297
let root = dir.path ( ) ;
7298
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
7299
write ( root, " a.txt" , b " 1\n " ) ;
7300
repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
7301
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
7302
let state = repo.store ( ) .put_state ( & StateObject:: new( heads) ) .unwrap ( ) ;
7303
let plan = repo.store_blob ( b " the reviewed plan\n " ) .unwrap ( ) ;
7304
let sk = SecretKey:: generate( ) ;
7305
let andy = sk.identity ( " Andy" , " [email protected] " , 1 ) ;
7306
let statement = crate :: approval:: Statement {
7307
repo: repo.id ( ) .unwrap ( ) ,
7308
run: " https://hub.example/andy/infra/flow/8" .into ( ) ,
7309
job: " apply" .into ( ) ,
7310
job_hash: ObjectRef( [ 1 ; 32 ] ) ,
7311
state,
7312
inputs: vec! [ plan] ,
7313
environment: " prod" .into ( ) ,
7314
approve: true ,
7315
} ;
7316
let sa = crate :: approval:: SignedApproval:: sign( & statement, & andy, & sk) ;
7317
let r = repo.add_approval ( & sa) .unwrap ( ) ;
7318
assert_eq! ( repo.store ( ) .approvals_for ( & state) .unwrap ( ) , vec! [ r] ) ;
7319
7320
let elsewhere = crate :: approval:: SignedApproval:: sign(
7321
& crate :: approval:: Statement {
7322
repo: [ 0 ; 16 ] ,
7323
.. statement.clone ( )
7324
} ,
7325
& andy,
7326
& sk,
7327
) ;
7328
assert! ( repo
7329
.add_approval ( & elsewhere)
7330
.unwrap_err ( )
7331
.to_string ( )
7332
.contains ( " another repository" ) ) ;
7333
let mut forged = sa.clone ( ) ;
7334
if let AttestBody:: Approval { environment, .. } = & mut forged.attest.body {
7335
* environment = " dev" .into ( ) ;
7336
}
7337
assert! ( repo
7338
.add_approval ( & forged)
7339
.unwrap_err ( )
7340
.to_string ( )
7341
.contains ( " does not verify" ) ) ;
7342
7343
repo.gc ( true ) .unwrap ( ) ;
7344
assert_eq! ( repo.store ( ) .approvals_for ( & state) .unwrap ( ) , vec! [ r] ) ;
7345
assert_eq! ( repo.store ( ) .get_attest ( & r) .unwrap ( ) , sa.attest) ;
7346
assert_eq! ( repo.store ( ) .signatures ( & r) .unwrap ( ) , sa.sigs) ;
7347
assert! ( repo.fsck ( ) .unwrap ( ) .problems.is_empty ( ) ) ;
7348
}
7349
7350
# [ test ]
dfb23a3a Andy McCall 1 month ago ↶
7351
fn status_leaves_an_unchanged_manifest_alone ( ) {
7352
hermetic ( ) ;
7353
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7354
let root = dir.path ( ) ;
7355
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
7356
write ( root, " a.txt" , b " 1\n " ) ;
7357
write ( root, " b.txt" , b " 2\n " ) ;
7358
repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
7359
7360
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 60 ) ;
7361
for f in [ " a.txt" , " b.txt" ] {
7362
fs:: File:: options( )
7363
.write ( true )
7364
.open ( root.join ( f) )
7365
.unwrap ( )
7366
.set_modified ( old)
7367
.unwrap ( ) ;
7368
}
7369
repo.snapshot_status ( " status" ) .unwrap ( ) ;
7370
let path = repo.manifest_path ( ) ;
7371
let first = fs:: read( & path) .unwrap ( ) ;
7372
repo.snapshot_status ( " status" ) .unwrap ( ) ;
7373
assert_eq! (
7374
fs:: read( & path) .unwrap ( ) ,
7375
first,
7376
" an unchanged tree does not rewrite the manifest"
7377
) ;
7378
7379
write ( root, " a.txt" , b " 1\n 2\n " ) ;
7380
repo.snapshot_status ( " status" ) .unwrap ( ) ;
7381
assert_ne! ( fs:: read( & path) .unwrap ( ) , first) ;
fc1a466a Andy McCall 1 month ago ↶
7382
}
6f8aeb99 Andy McCall 1 month ago ↶
7383
7384
# [ test ]
7385
fn large_text_files_travel_and_survive_gc_as_text_blobs ( ) {
7386
hermetic ( ) ;
7387
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7388
let root = dir.path ( ) ;
7389
let origin_path = root.join ( " origin" ) ;
7390
Repo:: init( & origin_path) .unwrap ( ) ;
7391
let origin_url = origin_path.to_string_lossy ( ) .to_string ( ) ;
7392
let a = Repo:: clone( & origin_url, & root.join ( " a" ) ) .unwrap ( ) ;
7393
let big: String = ( 0 .. 5000 ) .map ( | i | format! ( " line {i} \n " ) ) .collect ( ) ;
7394
write ( & root.join ( " a" ) , " big.txt" , big.as_bytes ( ) ) ;
7395
write ( & root.join ( " a" ) , " small.txt" , b " small\n " ) ;
7396
let h1 = a.commit ( " large" , & [ ] ) .unwrap ( ) ;
7397
let ( _ , body) = a.store ( ) .get ( & h1) .unwrap ( ) ;
7398
assert! ( body.len ( ) < 2048 , " the change is {} bytes" , body.len ( ) ) ;
7399
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7400
7401
7402
let b = Repo:: clone( & origin_url, & root.join ( " b" ) ) .unwrap ( ) ;
7403
assert_eq! ( fs:: read( root.join ( " b/big.txt" ) ) .unwrap ( ) , big.as_bytes ( ) ) ;
7404
7405
let edited = big.replace ( " line 2500\n " , " line 2500, edited\n " ) ;
7406
write ( & root.join ( " b" ) , " big.txt" , edited.as_bytes ( ) ) ;
7407
let h2 = b.commit ( " edit" , & [ ] ) .unwrap ( ) ;
7408
assert! ( b.store ( ) .get_change ( & h2) .unwrap ( ) .edits.len ( ) < = 2 ) ;
7409
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7410
assert! ( a.pull ( " origin" ) .unwrap ( ) .checked_out) ;
7411
assert_eq! ( fs:: read( root.join ( " a/big.txt" ) ) .unwrap ( ) , edited.as_bytes ( ) ) ;
7412
assert_eq! (
7413
a.fsck ( ) .unwrap ( ) .errors ( ) ,
7414
0 ,
7415
" {:?}" ,
7416
a.fsck ( ) .unwrap ( ) .problems
7417
) ;
7418
7419
7420
let mut a = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) ;
7421
a.config.set ( " core.op-retention" , " 0" ) .unwrap ( ) ;
7422
a.save_config ( ) .unwrap ( ) ;
7423
let a = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) ;
7424
a.gc ( true ) .unwrap ( ) ;
7425
let a = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) ;
7426
assert_eq! ( a.store ( ) .get_change ( & h1) .unwrap ( ) .message, " large" ) ;
7427
assert_eq! (
7428
a.fsck ( ) .unwrap ( ) .errors ( ) ,
7429
0 ,
7430
" {:?}" ,
7431
a.fsck ( ) .unwrap ( ) .problems
7432
) ;
7433
7434
let c = Repo:: clone( & root.join ( " a" ) .to_string_lossy ( ) , & root.join ( " c" ) ) .unwrap ( ) ;
7435
assert_eq! ( fs:: read( root.join ( " c/big.txt" ) ) .unwrap ( ) , edited.as_bytes ( ) ) ;
7436
drop ( c) ;
6caacfff Andy McCall 1 month ago ↶
7437
}
7438
7439
# [ test ]
7440
fn operations_that_both_replace_a_working_copy_are_not_merged ( ) {
7441
hermetic ( ) ;
7442
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7443
let root = dir.path ( ) ;
7444
let repo = Repo:: init( root) .unwrap ( ) ;
7445
write ( root, " a.txt" , b " a\n " ) ;
7446
let a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
7447
write ( root, " b.txt" , b " b\n " ) ;
7448
repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
7449
let ( x, op) = repo.head_op ( ) .unwrap ( ) ;
7450
let ws = * op.view.workspaces.keys ( ) .next ( ) .unwrap ( ) ;
7451
let mut one = op.view.clone ( ) ;
7452
one.workspaces.insert ( ws, a) ;
7453
let mut two = op.view.clone ( ) ;
7454
two.workspaces.insert ( ws, ObjectRef( [ 7 ; 32 ] ) ) ;
7455
let first = repo.record_op ( & [ x] , " one" , one) .unwrap ( ) ;
7456
let e = repo.record_op ( & [ x] , " two" , two) .unwrap_err ( ) ;
7457
assert! (
7458
e.to_string ( )
7459
.contains ( " changed this working copy at the same time" ) ,
7460
" {e}"
7461
) ;
7462
assert_eq! (
7463
repo.head_op ( ) .unwrap ( ) .0 ,
7464
first,
7465
" the second is not recorded"
7466
) ;
7467
}
7468
7469
7470
7471
7472
# [ test ]
7473
fn a_snapshot_during_a_switch_never_records_the_switch_as_edits ( ) {
7474
hermetic ( ) ;
7475
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7476
let root = dir.path ( ) .to_path_buf ( ) ;
7477
let mut repo = Repo:: init( & root) .unwrap ( ) ;
7478
write ( & root, " keep.txt" , b " keep\n " ) ;
7479
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7480
repo.bookmark_create ( " small" , None ) .unwrap ( ) ;
7481
for i in 0 .. 300 {
7482
write (
7483
& root,
7484
& format! ( " many/f{i} .txt" ) ,
7485
format! ( " file {i} \n " ) .as_bytes ( ) ,
7486
) ;
7487
}
7488
repo.commit ( " many" , & [ ] ) .unwrap ( ) ;
7489
let stop = std:: sync:: Arc:: new( std:: sync:: atomic:: AtomicBool:: new( false ) ) ;
7490
let watcher = {
7491
let ( root, stop) = ( root.clone ( ) , stop.clone ( ) ) ;
7492
std:: thread:: spawn( move | | {
7493
let r = Repo:: open( & root) .unwrap ( ) ;
7494
let mut ran = 0 ;
7495
while ! stop.load ( std:: sync:: atomic:: Ordering:: Relaxed) {
7496
if r.snapshot_status ( " oxy status" ) .is_ok ( ) {
7497
ran + = 1 ;
7498
}
7499
}
7500
ran
7501
} )
7502
} ;
7503
for i in 0 .. 20 {
7504
let to = if i % 2 = = 0 { " small" } else { " main" } ;
7505
repo.bookmark_switch ( to, false ) .unwrap ( ) ;
7506
}
7507
stop.store ( true , std:: sync:: atomic:: Ordering:: Relaxed) ;
7508
assert! ( watcher.join ( ) .unwrap ( ) > 0 , " the watcher snapshotted" ) ;
7509
7510
let repo = Repo:: open( & root) .unwrap ( ) ;
7511
let s = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7512
assert! ( s.statuses.is_empty ( ) , " {:?}" , statuses ( & s) ) ;
7513
assert! ( s.wc.edits.is_empty ( ) & & s.wc.tree_ops.is_empty ( ) ) ;
7514
assert_eq! ( s.wc.deps, repo.bookmark_heads ( ) .unwrap ( ) ) ;
7515
assert! ( root.join ( " many/f299.txt" ) .exists ( ) ) ;
1a3b6203 Andy McCall 1 month ago ↶
7516
}
7517
7518
7519
7520
7521
# [ test ]
7522
fn a_parent_switch_with_carry_leaves_a_subrepo_ahead_of_its_pin_alone ( ) {
7523
hermetic ( ) ;
7524
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7525
let root = dir.path ( ) ;
7526
Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
7527
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
7528
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) ) .unwrap ( ) ;
7529
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
7530
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
7531
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7532
7533
let app_dir = root.join ( " app" ) ;
7534
let mut app = Repo:: init( & app_dir) .unwrap ( ) ;
7535
write ( & app_dir, " main.txt" , b " app\n " ) ;
7536
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
7537
app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
7538
app.bookmark_create ( " feature" , None ) .unwrap ( ) ;
7539
7540
7541
let nested_dir = app_dir.join ( " libs/lib" ) ;
7542
let nested = Repo:: open( & nested_dir) .unwrap ( ) ;
7543
write ( & nested_dir, " new.txt" , b " new\n " ) ;
7544
let l2 = nested.commit ( " lib: new file" , & [ ] ) .unwrap ( ) ;
7545
drop ( nested) ;
7546
7547
let ( _ , carried) = app.bookmark_switch_carry ( " feature" ) .unwrap ( ) ;
7548
assert! ( carried.contains ( & " libs/lib" .to_string ( ) ) , " {carried:?}" ) ;
7549
let nested = Repo:: open( & nested_dir) .unwrap ( ) ;
7550
assert! (
7551
nested.bookmark_heads ( ) .unwrap ( ) .contains ( & l2) ,
7552
" its bookmark kept its work"
7553
) ;
7554
assert! ( nested_dir.join ( " new.txt" ) .exists ( ) , " and its files" ) ;
7555
let s = app.snapshot ( " status" ) .unwrap ( ) ;
7556
assert_eq! (
7557
statuses ( & s) ,
7558
vec! [ ( " libs/lib" .to_string ( ) , ChangeStatus:: Modified) ]
7559
) ;
6f8aeb99 Andy McCall 1 month ago ↶
7560
}
7d9dab13 Andy McCall 1 month ago ↶
7561
7562
7563
7564
7565
# [ test ]
7566
fn pull_takes_a_remote_move_to_changes_already_here ( ) {
7567
hermetic ( ) ;
7568
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7569
let root = dir.path ( ) ;
7570
let origin_path = root.join ( " origin" ) ;
7571
let origin = Repo:: init( & origin_path) .unwrap ( ) ;
7572
let url = origin_path.to_string_lossy ( ) .to_string ( ) ;
7573
let mut b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) ;
7574
write ( & root.join ( " b" ) , " base.txt" , b " base\n " ) ;
7575
b.commit ( " base" , & [ ] ) .unwrap ( ) ;
7576
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
7577
origin.refresh ( ) .unwrap ( ) ;
7d9dab13 Andy McCall 1 month ago ↶
7578
7579
7580
b.bookmark_create ( " feat" , None ) .unwrap ( ) ;
7581
b.bookmark_switch ( " feat" , false ) .unwrap ( ) ;
7582
write ( & root.join ( " b" ) , " x.txt" , b " x\n " ) ;
7583
let x = b.commit ( " x" , & [ ] ) .unwrap ( ) ;
7584
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
7585
origin.refresh ( ) .unwrap ( ) ;
7d9dab13 Andy McCall 1 month ago ↶
7586
b.bookmark_switch ( " main" , false ) .unwrap ( ) ;
7587
7588
7589
let feat = origin
7590
.store ( )
7591
.get_state ( & origin.view ( ) .unwrap ( ) .bookmarks[ " feat" ] )
7592
.unwrap ( )
7593
.heads;
7594
origin.bookmark_set ( " main" , & feat) .unwrap ( ) ;
7595
7596
let r = b.pull ( " origin" ) .unwrap ( ) ;
7597
assert! ( r.changed, " {r:?}" ) ;
7598
assert! ( r.heads.contains ( & x) | | b.load_state ( & r.heads) .unwrap ( ) .contains ( & x) ) ;
7599
let main = b
7600
.bookmarks ( )
7601
.unwrap ( )
7602
.into_iter ( )
7603
.find ( | m | m.name = = " main" )
7604
.unwrap ( ) ;
7605
assert_eq! ( main.behind, Some ( 0 ) , " {main:?}" ) ;
7606
}
86e1824f Andy McCall 1 month ago ↶
7607
14edbaaf Andy McCall 1 month ago ↶
7608
7609
7610
7611
7612
# [ test ]
7613
fn remote_bookmarks_are_named_switched_to_pulled_and_cloned ( ) {
7614
hermetic ( ) ;
7615
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7616
let root = dir.path ( ) ;
7617
let origin_path = root.join ( " origin" ) ;
7618
let mut origin = Repo:: init( & origin_path) .unwrap ( ) ;
7619
let url = origin_path.to_string_lossy ( ) .to_string ( ) ;
7620
let mut a = Repo:: clone( & url, & root.join ( " a" ) ) .unwrap ( ) ;
7621
write ( & root.join ( " a" ) , " base.txt" , b " base\n " ) ;
7622
a.commit ( " base" , & [ ] ) .unwrap ( ) ;
7623
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7624
a.bookmark_create ( " develop" , None ) .unwrap ( ) ;
7625
a.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
7626
write ( & root.join ( " a" ) , " base.txt" , b " base\n d1\n " ) ;
7627
let d1 = a.commit ( " d1" , & [ ] ) .unwrap ( ) ;
7628
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7629
7630
7631
let c_root = root.join ( " c" ) ;
7632
let mut c = Repo:: clone( & url, & c_root ) .unwrap ( ) ;
7633
assert_eq! ( c.config.bookmark, " main" ) ;
7634
assert! ( ! c.view ( ) .unwrap ( ) .bookmarks.contains_key ( " develop" ) ) ;
7635
assert_eq! ( c.resolve_heads ( " origin/develop" ) .unwrap ( ) , vec! [ d1] ) ;
7636
assert_eq! ( c.resolve_change ( " origin/develop" ) .unwrap ( ) , d1) ;
7637
assert_eq! ( c.remote_only_bookmarks ( ) .unwrap ( ) .len ( ) , 1 ) ;
7638
7639
7640
c.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
7641
assert_eq! ( c.config.bookmark, " develop" ) ;
7642
assert_eq! ( c.bookmark_heads ( ) .unwrap ( ) , vec! [ d1] ) ;
7643
assert_eq! ( fs:: read( c_root .join ( " base.txt" ) ) .unwrap ( ) , b " base\n d1\n " ) ;
7644
assert! ( c.remote_only_bookmarks ( ) .unwrap ( ) .is_empty ( ) ) ;
7645
assert! ( c.bookmark_switch ( " nowhere" , false ) .is_err ( ) ) ;
7646
7647
7648
write ( & root.join ( " a" ) , " base.txt" , b " base\n d1\n d2\n " ) ;
7649
let d2 = a.commit ( " d2" , & [ ] ) .unwrap ( ) ;
7650
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7651
a.bookmark_switch ( " main" , false ) .unwrap ( ) ;
7652
write ( & root.join ( " a" ) , " m.txt" , b " m\n " ) ;
7653
let m = a.commit ( " m" , & [ ] ) .unwrap ( ) ;
7654
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
733ea16a Andy McCall 17 days ago ↶
7655
7656
let f = Repo:: clone( & url, & root.join ( " f" ) ) .unwrap ( ) ;
7657
assert! ( ! f.view ( ) .unwrap ( ) .bookmarks.contains_key ( " develop" ) ) ;
7658
let merged = f.merge ( " origin/develop" ) .unwrap ( ) ;
7659
assert! ( merged.changed, " {merged:?}" ) ;
7660
assert! (
7661
f.load_state_indexed ( & f.bookmark_heads ( ) .unwrap ( ) )
7662
.unwrap ( )
7663
.contains ( & d1) ,
7664
" main holds develop's change"
7665
) ;
7666
assert! ( f
7667
.merge ( " origin/nowhere" )
7668
.unwrap_err ( )
7669
.to_string ( )
7670
.contains ( " no bookmark origin/nowhere" ) ) ;
14edbaaf Andy McCall 1 month ago ↶
7671
let r = c.pull ( " origin" ) .unwrap ( ) ;
7672
assert! ( r.changed & & r.checked_out, " {r:?}" ) ;
7673
assert_eq! ( r.heads, vec! [ d2] ) ;
7674
assert_eq! ( r.others, vec! [ " main" .to_string ( ) ] ) ;
7675
assert! ( c.resolve_heads ( " main" ) .unwrap ( ) .contains ( & m) ) ;
7676
assert_eq! (
7677
fs:: read( c_root .join ( " base.txt" ) ) .unwrap ( ) ,
7678
b " base\n d1\n d2\n "
7679
) ;
7680
let again = c.pull ( " origin" ) .unwrap ( ) ;
7681
assert! ( ! again.changed & & again.others.is_empty ( ) , " {again:?}" ) ;
7682
7683
7684
origin.config.set ( " workspace.bookmark" , " develop" ) .unwrap ( ) ;
7685
origin.save_config ( ) .unwrap ( ) ;
7686
let e = Repo:: clone( & url, & root.join ( " e" ) ) .unwrap ( ) ;
7687
assert_eq! ( e.config.bookmark, " develop" ) ;
7688
assert_eq! ( e.bookmark_heads ( ) .unwrap ( ) , vec! [ d2] ) ;
7689
let reopened = Repo:: open( & root.join ( " e" ) ) .unwrap ( ) ;
7690
assert_eq! ( reopened.config.bookmark, " develop" ) ;
7691
7692
let mut e = reopened;
7693
e.bookmark_switch ( " origin/main" , false ) .unwrap ( ) ;
7694
assert_eq! ( e.config.bookmark, " main" ) ;
7695
assert! ( e.bookmark_heads ( ) .unwrap ( ) .contains ( & m) ) ;
7696
}
7697
98a1620a Andy McCall 1 month ago ↶
7698
7699
7700
7701
7702
# [ test ]
7703
fn operations_can_be_read_restored_and_reverted ( ) {
7704
hermetic ( ) ;
7705
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7706
let root = dir.path ( ) ;
7707
let repo = Repo:: init( root) .unwrap ( ) ;
7708
write ( root, " a.txt" , b " a\n " ) ;
7709
let first = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
7710
write ( root, " a.txt" , b " a\n b\n " ) ;
7711
let second = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
7712
let at_second = repo.head_op ( ) .unwrap ( ) .0. to_hex ( ) ;
7713
repo.bookmark_create ( " keep" , None ) .unwrap ( ) ;
7714
let created = repo.head_op ( ) .unwrap ( ) .0. to_hex ( ) ;
7715
write ( root, " n.txt" , b " n\n " ) ;
7716
let third = repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
7717
7718
7719
let ops = repo.op_log ( ) .unwrap ( ) ;
7720
let changes = repo.view_changes ( & ops[ 1 ] .1. view, & ops[ 0 ] .1. view) .unwrap ( ) ;
7721
assert! (
7722
changes
7723
.iter ( )
7724
.any ( | c | matches! ( c, ViewChange:: BookmarkMoved { name, .. } if name = = " main" ) ) ,
7725
" {changes:?}"
7726
) ;
7727
assert! ( changes
7728
.iter ( )
7729
.any ( | c | matches! ( c, ViewChange:: WorkingCopy { .. } ) ) ) ;
7730
7731
7732
let old = Repo:: open( root) .unwrap ( ) .at_op ( & at_second[ .. 10 ] ) .unwrap ( ) ;
7733
assert_eq! ( old.bookmark_heads ( ) .unwrap ( ) , vec! [ second] ) ;
7734
assert! ( ! old.view ( ) .unwrap ( ) .bookmarks.contains_key ( " keep" ) ) ;
7735
write ( root, " z.txt" , b " z\n " ) ;
7736
assert! ( old.commit ( " refused" , & [ ] ) .is_err ( ) , " --at-op is read-only" ) ;
7737
fs:: remove_file( root.join ( " z.txt" ) ) .unwrap ( ) ;
7738
assert! ( repo.resolve_op ( " zz" ) .is_err ( ) ) ;
7739
assert_eq! ( repo.resolve_op ( " @" ) .unwrap ( ) .0 , repo.head_op ( ) .unwrap ( ) .0 ) ;
7740
7741
7742
repo.op_restore ( & at_second, false ) .unwrap ( ) ;
7743
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ second] ) ;
7744
assert! ( ! root.join ( " n.txt" ) .exists ( ) ) ;
7745
repo.undo ( ) .unwrap ( ) ;
7746
let mut heads = repo.bookmark_heads ( ) .unwrap ( ) ;
7747
heads.sort ( ) ;
7748
let mut want = vec! [ second, third] ;
7749
want.sort ( ) ;
7750
assert_eq! ( heads, want) ;
7751
assert! (
7752
root.join ( " n.txt" ) .exists ( ) ,
7753
" the files came back with the undo"
7754
) ;
7755
7756
7757
let r = repo.op_revert ( & created) .unwrap ( ) ;
7758
assert_eq! ( r.reverted, vec! [ " keep" .to_string ( ) ] ) ;
7759
assert! ( ! repo.view ( ) .unwrap ( ) .bookmarks.contains_key ( " keep" ) ) ;
7760
assert! ( repo
7761
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
7762
.unwrap ( )
7763
.contains ( & third) ) ;
7764
assert! ( repo.op_revert ( & created) .is_err ( ) , " nothing left to revert" ) ;
7765
let _ = first;
7766
}
7767
36041796 Andy McCall 1 month ago ↶
7768
7769
7770
7771
# [ test ]
7772
fn status_with_uncommitted_work_comes_from_the_cache_until_a_file_changes ( ) {
7773
hermetic ( ) ;
7774
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7775
let root = dir.path ( ) ;
7776
let repo = Repo:: init( root) .unwrap ( ) ;
7777
write ( root, " a.txt" , b " a\n " ) ;
7778
write ( root, " b.txt" , b " b\n " ) ;
7779
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7780
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , Some ( vec! [ ] ) , " clean" ) ;
7781
7782
write ( root, " a.txt" , b " a\n more\n " ) ;
7783
assert_eq! (
7784
repo.status_by_manifest ( ) .unwrap ( ) ,
7785
None ,
7786
" no snapshot of the edit yet"
7787
) ;
7788
let snap = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7789
assert_eq! ( snap.statuses.len ( ) , 1 ) ;
7790
let cached = repo
7791
.status_by_manifest ( )
7792
.unwrap ( )
7793
.expect ( " answered from the cache" ) ;
7794
assert_eq! ( cached.len ( ) , 1 ) ;
7795
assert_eq! (
7796
( cached[ 0 ] .path.as_str ( ) , cached[ 0 ] .status) ,
7797
( " a.txt" , ChangeStatus:: Modified)
7798
) ;
7799
7800
7801
write ( root, " a.txt" , b " a\n more\n and more\n " ) ;
7802
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None , " edited again" ) ;
7803
repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7804
assert! ( repo.status_by_manifest ( ) .unwrap ( ) .is_some ( ) ) ;
7805
write ( root, " new.txt" , b " n\n " ) ;
7806
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None , " a file added" ) ;
7807
fs:: remove_file( root.join ( " new.txt" ) ) .unwrap ( ) ;
7808
assert! (
7809
repo.status_by_manifest ( ) .unwrap ( ) .is_some ( ) ,
7810
" back as it was"
7811
) ;
7812
fs:: remove_file( root.join ( " b.txt" ) ) .unwrap ( ) ;
7813
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None , " a file removed" ) ;
7814
write ( root, " b.txt" , b " b\n " ) ;
7815
7816
7817
repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7818
repo.commit ( " more" , & [ ] ) .unwrap ( ) ;
7819
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , Some ( vec! [ ] ) ) ;
7820
}
7821
77445673 Andy McCall 1 month ago ↶
7822
7823
7824
7825
# [ test ]
7826
fn the_working_copy_diff_from_the_cache_matches_a_snapshot ( ) {
7827
hermetic ( ) ;
7828
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7829
let root = dir.path ( ) ;
7830
let repo = Repo:: init( root) .unwrap ( ) ;
7831
write ( root, " a.txt" , b " one\n two\n three\n " ) ;
7832
write ( root, " gone.txt" , b " bye\n " ) ;
7833
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7834
write ( root, " a.txt" , b " one\n 2\n three\n four\n " ) ;
7835
write ( root, " new.txt" , b " hello\n " ) ;
7836
fs:: remove_file( root.join ( " gone.txt" ) ) .unwrap ( ) ;
7837
let snap = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7838
let slow = render_change ( & snap.wc, & snap.base, & snap.base_snapshot) ;
7839
assert! (
7840
repo.status_by_manifest ( ) .unwrap ( ) .is_some ( ) ,
7841
" the cache holds"
7842
) ;
7843
let ( wc, base, snapshot) = repo.working_copy_with_base ( ) .unwrap ( ) ;
7844
assert_eq! ( render_change ( & wc, & base, & snapshot) , slow) ;
7845
assert_eq! ( repo.diff_files ( None ) .unwrap ( ) .len ( ) , 3 ) ;
74b4a3c6 Andy McCall 19 days ago ↶
7846
7847
7848
7849
repo.commit ( " all" , & [ ] ) .unwrap ( ) ;
7850
repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7851
assert! ( repo.status_by_manifest ( ) .unwrap ( ) .is_some ( ) ) ;
7852
assert! ( repo
7853
.working_copy_with_base_unless_unchanged ( )
7854
.unwrap ( )
7855
.is_none ( ) ) ;
7856
let ( wc, base, snapshot) = repo.working_copy_with_base ( ) .unwrap ( ) ;
7857
assert_eq! ( render_change ( & wc, & base, & snapshot) , " " ) ;
7858
assert! ( repo.diff_files ( None ) .unwrap ( ) .is_empty ( ) ) ;
77445673 Andy McCall 1 month ago ↶
7859
}
7860
1ec3f008 Andy McCall 1 month ago ↶
7861
7862
7863
7864
7865
# [ test ]
7866
fn the_manifest_check_skips_listing_while_no_directory_changed ( ) {
7867
hermetic ( ) ;
7868
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7869
let root = dir.path ( ) ;
7870
let repo = Repo:: init( root) .unwrap ( ) ;
7871
write ( root, " a.txt" , b " a\n " ) ;
7872
write ( root, " sub/deep/b.txt" , b " b\n " ) ;
7873
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7874
7875
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 60 ) ;
7876
for p in [ " a.txt" , " sub/deep/b.txt" , " sub/deep" , " sub" , " " ] {
7877
fs:: File:: open( root.join ( p) )
7878
.unwrap ( )
7879
.set_modified ( old)
7880
.unwrap ( ) ;
7881
}
7882
repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7883
repo.write_manifest ( false , false ) .unwrap ( ) ;
7884
let m = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) .2 ;
7885
assert! ( m.dirs.iter ( ) .any ( | ( d , _) | d = = " sub/deep" ) , " {:?}" , m.dirs) ;
7886
assert_eq! (
7887
repo.disk_differs_by_dirs ( & m) ,
7888
Some ( false ) ,
7889
" no listing needed"
7890
) ;
7891
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , Some ( vec! [ ] ) ) ;
7892
7893
7894
write ( root, " sub/deep/b.txt" , b " c\n " ) ;
7895
assert_eq! ( repo.disk_differs_by_dirs ( & m) , Some ( true ) ) ;
7896
write ( root, " sub/deep/b.txt" , b " b\n " ) ;
7897
fs:: File:: open( root.join ( " sub/deep/b.txt" ) )
7898
.unwrap ( )
7899
.set_modified ( old)
7900
.unwrap ( ) ;
7901
assert_eq! ( repo.disk_differs_by_dirs ( & m) , Some ( false ) ) ;
7902
7903
7904
write ( root, " sub/deep/new.txt" , b " n\n " ) ;
7905
assert_eq! ( repo.disk_differs_by_dirs ( & m) , None ) ;
7906
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None ) ;
7907
fs:: remove_file( root.join ( " sub/deep/new.txt" ) ) .unwrap ( ) ;
7908
7909
fs:: remove_file( root.join ( " a.txt" ) ) .unwrap ( ) ;
7910
assert_eq! ( repo.disk_differs_by_dirs ( & m) , None ) ;
7911
}
7912
e226a6cc Andy McCall 1 month ago ↶
7913
7914
7915
7916
# [ test ]
7917
fn a_commit_after_a_status_uses_the_cached_snapshot ( ) {
7918
hermetic ( ) ;
7919
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7920
let root = dir.path ( ) ;
7921
let repo = Repo:: init( root) .unwrap ( ) ;
7922
write ( root, " a.txt" , b " a\n b\n " ) ;
7923
write ( root, " .oxyattributes" , b " *.bin binary\n " ) ;
7924
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7925
write ( root, " a.txt" , b " a\n B\n " ) ;
7926
write ( root, " new.txt" , b " new\n " ) ;
7927
let snap = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7928
let cached = repo
7929
.snapshot_from_cache ( )
7930
.unwrap ( )
7931
.expect ( " the cache holds" ) ;
7932
assert_eq! ( cached.wc_hash, snap.wc_hash) ;
7933
assert_eq! ( cached.statuses, snap.statuses) ;
7934
assert! (
7935
! cached.attributes.is_empty ( ) ,
7936
" the .oxyattributes file was read"
7937
) ;
7938
let h = repo.commit ( " edit" , & [ ] ) .unwrap ( ) ;
7939
let c = repo.store ( ) .get_change ( & h) .unwrap ( ) ;
7940
assert_eq! (
7941
c.tree_ops.len ( ) + c.edits.len ( ) ,
7942
snap.wc.tree_ops.len ( ) + snap.wc.edits.len ( )
7943
) ;
7944
assert_eq! (
7945
repo.status_by_manifest ( ) .unwrap ( ) ,
7946
Some ( vec! [ ] ) ,
7947
" clean after the commit"
7948
) ;
7949
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " a\n B\n " ) ;
7950
}
7951
0389c3e8 Andy McCall 1 month ago ↶
7952
7953
7954
7955
7956
# [ test ]
7957
fn a_verified_clean_check_restamps_the_manifest_safely ( ) {
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 " one\n " ) ;
7963
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
7964
let before = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) .2. written_ns;
7965
assert! ( repo.clean_by_manifest ( ) .unwrap ( ) ) ;
7966
let after = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) .2. written_ns;
7967
assert! ( after > before, " restamped" ) ;
7968
write ( root, " a.txt" , b " two\n " ) ;
7969
assert! (
7970
! repo.clean_by_manifest ( ) .unwrap ( ) ,
7971
" a same-size rewrite is still seen"
7972
) ;
7973
}
7974
86e1824f Andy McCall 1 month ago ↶
7975
7976
7977
7978
# [ test ]
7979
fn history_only_states_read_text_only_for_the_graphs_they_build ( ) {
7980
hermetic ( ) ;
7981
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7982
let root = dir.path ( ) ;
7983
let repo = Repo:: init( root) .unwrap ( ) ;
7984
let big: String = ( 0 .. 3000 ) .map ( | i | format! ( " big line {i} \n " ) ) .collect ( ) ;
7985
let other: String = ( 0 .. 3000 ) .map ( | i | format! ( " other line {i} \n " ) ) .collect ( ) ;
7986
write ( root, " big.txt" , big.as_bytes ( ) ) ;
7987
write ( root, " other.txt" , other.as_bytes ( ) ) ;
7988
let h = repo.commit ( " two large files" , & [ ] ) .unwrap ( ) ;
7989
write ( root, " big.txt" , format! ( " {big} one more\n " ) .as_bytes ( ) ) ;
7990
let h2 = repo.commit ( " append" , & [ ] ) .unwrap ( ) ;
7991
7992
let full = repo.load_state_full ( & [ h2] ) .unwrap ( ) .materialize ( ) ;
7993
let text = | snap : & crate:: state:: Snapshot, p : & str | match & snap.files[ p] .content {
7994
FileContent:: Text { bytes, .. } => bytes.clone ( ) ,
7995
other => panic! ( " {other:?}" ) ,
7996
} ;
7997
7998
let mut hist = repo.load_state_history ( & [ h2] ) .unwrap ( ) ;
7999
let big_node = full.files[ " big.txt" ] .node;
8000
assert! ( hist.graph_key ( & big_node) .is_some ( ) , " not built yet" ) ;
8001
repo.load_all_graphs ( & mut hist) .unwrap ( ) ;
8002
let rendered = hist.materialize ( ) ;
8003
for p in [ " big.txt" , " other.txt" ] {
8004
assert_eq! ( text ( & rendered, p) , text ( & full, p) , " {p}" ) ;
8005
}
8006
8007
8008
8009
let ( _ , texts) = repo.store ( ) .get_change_lazy ( & h) .unwrap ( ) ;
8010
assert_eq! ( texts.len ( ) , 2 ) ;
8011
let content = | r : & ObjectRef| -> Vec< u8 > {
8012
let blob = crate :: objects:: Blob:: decode( & repo.store ( ) .get ( r) .unwrap ( ) .1 ) .unwrap ( ) ;
8013
blob.chunks
8014
.iter ( )
8015
.flat_map ( | c | repo.store ( ) .get ( c) .unwrap ( ) .1 )
8016
.collect ( )
8017
} ;
8018
let other_text = texts
8019
.iter ( )
a4fad5b0 Andy McCall 1 month ago ↶
8020
.filter_map ( | t | match t {
0f93f49c Andy McCall 1 month ago ↶
8021
crate :: objects:: TextRef:: Blob( r, _ , _ ) => Some ( * r) ,
a4fad5b0 Andy McCall 1 month ago ↶
8022
_ => None ,
8023
} )
86e1824f Andy McCall 1 month ago ↶
8024
.find ( | r | content ( r) .windows ( 12 ) .any ( | w | w = = b " other line 1" ) )
8025
.unwrap ( ) ;
64818938 Andy McCall 1 month ago ↶
8026
repo.store ( ) .remove_loose_for_test ( & other_text) .unwrap ( ) ;
86e1824f Andy McCall 1 month ago ↶
8027
let mut hist = repo.load_state_history ( & [ h2] ) .unwrap ( ) ;
8028
repo.load_graphs_of ( & mut hist, & [ big_node] ) .unwrap ( ) ;
8029
assert! ( repo
8030
.load_graphs_of ( & mut hist, & [ full.files[ " other.txt" ] .node] )
8031
.is_err ( ) ) ;
8032
}
8033
8034
8035
8036
8037
# [ test ]
8038
fn a_commit_leaves_a_manifest_the_next_status_trusts ( ) {
8039
use std:: os:: unix:: fs:: PermissionsExt;
8040
hermetic ( ) ;
8041
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8042
let root = dir.path ( ) ;
8043
let repo = Repo:: init( root) .unwrap ( ) ;
8044
let files = [ " a.txt" , " b.txt" , " d/c.txt" ] ;
8045
for p in files {
8046
write ( root, p, format! ( " {p} \n " ) .as_bytes ( ) ) ;
8047
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
8048
fs:: File:: options( )
8049
.write ( true )
8050
.open ( root.join ( p) )
8051
.unwrap ( )
8052
.set_modified ( old)
8053
.unwrap ( ) ;
8054
}
8055
repo.commit ( " added" , & [ ] ) .unwrap ( ) ;
8056
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
8057
assert! (
8058
m.entries.values ( ) .all ( | e | e.graph.is_some ( ) ) ,
8059
" keys recorded at commit"
8060
) ;
8061
8062
for p in files {
8063
fs:: set_permissions( root.join ( p) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
8064
}
8065
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8066
for p in files {
8067
fs:: set_permissions( root.join ( p) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
8068
}
8069
assert! ( ! snap.changed & & snap.statuses.is_empty ( ) ) ;
8070
assert_eq! ( snap.base_snapshot.unrendered.len ( ) , files.len ( ) ) ;
8071
}
c5ce0367 Andy McCall 1 month ago ↶
8072
8073
8074
8075
# [ test ]
8076
fn checkout_touches_only_what_differs_and_renders_in_batches ( ) {
8077
use std:: os:: unix:: fs:: PermissionsExt;
8078
hermetic ( ) ;
8079
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8080
let root = dir.path ( ) ;
8081
let origin = root.join ( " origin" ) ;
8082
let mut repo = Repo:: init( & origin) .unwrap ( ) ;
8083
8084
let n = 2100 ;
8085
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
8086
for i in 0 .. n {
8087
let p = format! ( " f/{i:04} .txt" ) ;
8088
write ( & origin, & p, format! ( " file {i} \n " ) .as_bytes ( ) ) ;
8089
fs:: File:: options( )
8090
.write ( true )
8091
.open ( origin.join ( & p) )
8092
.unwrap ( )
8093
.set_modified ( old)
8094
.unwrap ( ) ;
8095
}
8096
repo.commit ( " many" , & [ ] ) .unwrap ( ) ;
8097
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
8098
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
8099
write ( & origin, " f/0007.txt" , b " changed\n " ) ;
8100
repo.commit ( " one change" , & [ ] ) .unwrap ( ) ;
8101
8102
8103
for i in 0 .. n {
8104
if i ! = 7 {
8105
let p = origin.join ( format! ( " f/{i:04} .txt" ) ) ;
8106
fs:: set_permissions( & p, fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
8107
}
8108
}
8109
let r = repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
8110
for i in 0 .. n {
8111
let p = origin.join ( format! ( " f/{i:04} .txt" ) ) ;
8112
fs:: set_permissions( & p, fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
8113
}
8114
assert_eq! ( ( r.written, r.removed) , ( 1 , 0 ) , " {r:?}" ) ;
8115
assert_eq! ( fs:: read( origin.join ( " f/0007.txt" ) ) .unwrap ( ) , b " file 7\n " ) ;
8116
assert! ( repo.snapshot_status ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
8117
8118
8119
let c = root.join ( " clone" ) ;
8120
Repo:: clone( & origin.to_string_lossy ( ) , & c) .unwrap ( ) ;
8121
for i in [ 0 , 2047 , 2048 , n - 1 ] {
8122
assert_eq! (
8123
fs:: read( c.join ( format! ( " f/{i:04} .txt" ) ) ) .unwrap ( ) ,
8124
format! ( " file {i} \n " ) .as_bytes ( )
8125
) ;
8126
}
8127
let cloned = Repo:: open( & c) .unwrap ( ) ;
8128
assert! ( cloned
8129
.snapshot_status ( " status" )
8130
.unwrap ( )
8131
.statuses
8132
.is_empty ( ) ) ;
d3cd7ef9 Andy McCall 1 month ago ↶
8133
}
8134
8135
8136
8137
8138
# [ test ]
8139
fn a_racy_manifest_entry_is_checked_by_hash ( ) {
8140
hermetic ( ) ;
8141
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8142
let root = dir.path ( ) ;
8143
let repo = Repo:: init( root) .unwrap ( ) ;
8144
write ( root, " a.txt" , b " one\n " ) ;
8145
repo.commit ( " fresh" , & [ ] ) .unwrap ( ) ;
8146
assert! ( repo.clean_by_manifest ( ) .unwrap ( ) , " fresh but equal" ) ;
8147
let t = fs:: metadata( root.join ( " a.txt" ) )
8148
.unwrap ( )
8149
.modified ( )
8150
.unwrap ( ) ;
8151
write ( root, " a.txt" , b " two\n " ) ;
8152
fs:: File:: options( )
8153
.write ( true )
8154
.open ( root.join ( " a.txt" ) )
8155
.unwrap ( )
8156
.set_modified ( t)
8157
.unwrap ( ) ;
8158
assert! (
8159
! repo.clean_by_manifest ( ) .unwrap ( ) ,
8160
" same size and time, other bytes"
8161
) ;
8162
assert_eq! (
8163
statuses ( & repo.snapshot_status ( " status" ) .unwrap ( ) ) ,
8164
vec! [ ( " a.txt" .to_string ( ) , ChangeStatus:: Modified) ]
8165
) ;
c5ce0367 Andy McCall 1 month ago ↶
8166
}
2c75672f Andy McCall 1 month ago ↶
8167
8168
8169
8170
# [ test ]
8171
fn a_raw_bundle_without_a_changes_text_is_refused ( ) {
8172
hermetic ( ) ;
8173
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8174
let root = dir.path ( ) ;
8175
let a = Repo:: init( & root.join ( " a" ) ) .unwrap ( ) ;
8176
let big: String = ( 0 .. 3000 ) .map ( | i | format! ( " line {i} \n " ) ) .collect ( ) ;
8177
write ( & root.join ( " a" ) , " big.txt" , big.as_bytes ( ) ) ;
8178
let h = a.commit ( " big" , & [ ] ) .unwrap ( ) ;
8179
let bundle = a.bundle_raw_for ( & [ h] , None ) .unwrap ( ) ;
8180
assert_eq! ( bundle.raw_changes.len ( ) , 1 ) ;
8181
assert! ( bundle.changes.is_empty ( ) ) ;
8182
let b = Repo:: init( & root.join ( " b" ) ) .unwrap ( ) ;
8183
let mut without = bundle.clone ( ) ;
8184
without.objects.retain ( | ( ty , _) | * ty ! = ObjectType:: Blob) ;
8185
let e = b.import_bundle ( & without, false ) .unwrap_err ( ) ;
8186
assert! ( e.to_string ( ) .contains ( " came without its text blob" ) , " {e}" ) ;
8187
assert! ( ! b.store ( ) .has ( & h) ) ;
8188
assert_eq! ( b.import_bundle ( & bundle, false ) .unwrap ( ) , vec! [ h] ) ;
8189
assert_eq! ( b.store ( ) .get_change ( & h) .unwrap ( ) .message, " big" ) ;
8190
}
a87c7b9d Andy McCall 1 month ago ↶
8191
8192
8193
8194
8195
8196
# [ test ]
8197
fn git_import_brings_a_directory_back_rather_than_making_a_second ( ) {
8198
hermetic ( ) ;
8199
if std:: process:: Command:: new( " git" )
8200
.arg ( " --version" )
8201
.output ( )
8202
.is_err ( )
8203
{
8204
eprintln! ( " git not installed; skipping" ) ;
8205
return ;
8206
}
8207
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8208
let root = dir.path ( ) ;
8209
let g = root.join ( " src.git" ) ;
8210
fs:: create_dir_all( & g) .unwrap ( ) ;
8211
let git = | args : & [& str ]| -> String {
8212
let out = std:: process:: Command:: new( " git" )
8213
.arg ( " -C" )
8214
.arg ( & g)
8215
.args ( [
8216
" -c" ,
8217
" user.name=Grace" ,
8218
" -c" ,
8219
" [email protected] " ,
8220
" -c" ,
8221
" commit.gpgsign=false" ,
8222
] )
8223
.args ( args)
8224
.output ( )
8225
.unwrap ( ) ;
8226
assert! (
8227
out.status.success ( ) ,
8228
" git {args:?}: {}" ,
8229
String :: from_utf8_lossy( & out.stderr)
8230
) ;
8231
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
8232
} ;
8233
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
8234
write ( & g, " d/a.txt" , b " a\n " ) ;
8235
write ( & g, " c/x.txt" , b " x\n " ) ;
8236
git ( & [ " add" , " ." ] ) ;
8237
git ( & [ " commit" , " -qm" , " base" ] ) ;
8238
8239
git ( & [ " checkout" , " -qb" , " side" ] ) ;
8240
git ( & [ " mv" , " c/x.txt" , " d/x.txt" ] ) ;
8241
git ( & [ " commit" , " -qm" , " x into d" ] ) ;
8242
8243
git ( & [ " checkout" , " -q" , " main" ] ) ;
8244
git ( & [ " rm" , " -q" , " d/a.txt" ] ) ;
8245
git ( & [ " commit" , " -qm" , " d goes" ] ) ;
8246
write ( & g, " d/b.txt" , b " b\n " ) ;
8247
git ( & [ " add" , " ." ] ) ;
8248
git ( & [ " commit" , " -qm" , " d comes back" ] ) ;
8249
git ( & [ " mv" , " c/x.txt" , " d/x.txt" ] ) ;
8250
git ( & [ " commit" , " -qm" , " x into d again" ] ) ;
8251
git ( & [ " merge" , " -q" , " --no-edit" , " side" ] ) ;
8252
8253
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
8254
repo.import_git ( & g, " main" ) .unwrap ( ) ;
8255
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
8256
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
8257
let st = repo
8258
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
8259
.unwrap ( ) ;
8260
let view = st.tree ( ) .view ( ) ;
8261
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
8262
8263
8264
let first = repo
8265
.log ( )
8266
.unwrap ( )
8267
.into_iter ( )
8268
.find ( | e | e.change.message.starts_with ( " base" ) )
8269
.unwrap ( ) ;
8270
let base = repo
8271
.load_state_history ( & [ first.hash] )
8272
.unwrap ( )
8273
.tree ( )
8274
.view ( ) ;
8275
assert_eq! ( view.entries[ " d" ] .node, base.entries[ " d" ] .node) ;
8276
}
8277
8278
8279
8280
8281
# [ test ]
8282
fn git_import_merge_retires_a_placement_under_a_removed_directory ( ) {
8283
hermetic ( ) ;
8284
if std:: process:: Command:: new( " git" )
8285
.arg ( " --version" )
8286
.output ( )
8287
.is_err ( )
8288
{
8289
eprintln! ( " git not installed; skipping" ) ;
8290
return ;
8291
}
8292
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8293
let root = dir.path ( ) ;
8294
let g = root.join ( " src.git" ) ;
8295
fs:: create_dir_all( & g) .unwrap ( ) ;
8296
let run = | args : & [& str ]| {
8297
std:: process:: Command:: new( " git" )
8298
.arg ( " -C" )
8299
.arg ( & g)
8300
.args ( [
8301
" -c" ,
8302
" user.name=Grace" ,
8303
" -c" ,
8304
" [email protected] " ,
8305
" -c" ,
8306
" commit.gpgsign=false" ,
8307
] )
8308
.args ( args)
8309
.output ( )
8310
.unwrap ( )
8311
} ;
8312
let git = | args : & [& str ]| {
8313
let out = run ( args) ;
8314
assert! (
8315
out.status.success ( ) ,
8316
" git {args:?}: {}" ,
8317
String :: from_utf8_lossy( & out.stderr)
8318
) ;
8319
} ;
8320
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
8321
write ( & g, " c/x.txt" , b " x\n " ) ;
8322
write ( & g, " keep.txt" , b " k\n " ) ;
8323
git ( & [ " add" , " ." ] ) ;
8324
git ( & [ " commit" , " -qm" , " base" ] ) ;
8325
8326
8327
git ( & [ " checkout" , " -qb" , " side" ] ) ;
8328
git ( & [ " mv" , " c/x.txt" , " c/y.txt" ] ) ;
8329
git ( & [ " commit" , " -qm" , " rename within c" ] ) ;
8330
git ( & [ " checkout" , " -q" , " main" ] ) ;
8331
fs:: create_dir_all( g.join ( " n" ) ) .unwrap ( ) ;
8332
git ( & [ " mv" , " c/x.txt" , " n/x.txt" ] ) ;
8333
git ( & [ " commit" , " -qm" , " move out of c" ] ) ;
8334
8335
let _ = run ( & [ " merge" , " -q" , " side" ] ) ;
8336
let _ = run ( & [
8337
" rm" ,
8338
" -q" ,
8339
" --cached" ,
8340
" --ignore-unmatch" ,
8341
" c/y.txt" ,
8342
" c/x.txt" ,
8343
] ) ;
8344
let _ = std:: fs:: remove_dir_all( g.join ( " c" ) ) ;
8345
write ( & g, " n/x.txt" , b " x\n " ) ;
8346
git ( & [ " add" , " -A" ] ) ;
8347
git ( & [ " commit" , " -qm" , " merge: keep n/x.txt" ] ) ;
8348
8349
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
8350
repo.import_git ( & g, " main" ) .unwrap ( ) ;
8351
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
8352
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
8353
let st = repo
8354
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
8355
.unwrap ( ) ;
8356
let view = st.tree ( ) .view ( ) ;
8357
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
8358
assert! ( view.entries.contains_key ( " n/x.txt" ) ) ;
8359
}
8360
8361
8362
8363
8364
# [ test ]
8365
fn git_import_merge_leaves_no_orphan_behind ( ) {
8366
hermetic ( ) ;
8367
if std:: process:: Command:: new( " git" )
8368
.arg ( " --version" )
8369
.output ( )
8370
.is_err ( )
8371
{
8372
eprintln! ( " git not installed; skipping" ) ;
8373
return ;
8374
}
8375
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8376
let root = dir.path ( ) ;
8377
let g = root.join ( " src.git" ) ;
8378
fs:: create_dir_all( & g) .unwrap ( ) ;
8379
let git = | args : & [& str ]| {
8380
let out = std:: process:: Command:: new( " git" )
8381
.arg ( " -C" )
8382
.arg ( & g)
8383
.args ( [
8384
" -c" ,
8385
" user.name=Grace" ,
8386
" -c" ,
8387
" [email protected] " ,
8388
" -c" ,
8389
" commit.gpgsign=false" ,
8390
] )
8391
.args ( args)
8392
.output ( )
8393
.unwrap ( ) ;
8394
assert! (
8395
out.status.success ( ) ,
8396
" git {args:?}: {}" ,
8397
String :: from_utf8_lossy( & out.stderr)
8398
) ;
8399
} ;
8400
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
8401
write ( & g, " d/f.txt" , b " f\n " ) ;
8402
write ( & g, " keep.txt" , b " k\n " ) ;
8403
git ( & [ " add" , " ." ] ) ;
8404
git ( & [ " commit" , " -qm" , " base" ] ) ;
8405
git ( & [ " checkout" , " -qb" , " side" ] ) ;
8406
write ( & g, " d/new.txt" , b " new\n " ) ;
8407
git ( & [ " add" , " ." ] ) ;
8408
git ( & [ " commit" , " -qm" , " add into d" ] ) ;
8409
git ( & [ " checkout" , " -q" , " main" ] ) ;
8410
git ( & [ " rm" , " -q" , " d/f.txt" ] ) ;
8411
git ( & [ " commit" , " -qm" , " d goes" ] ) ;
8412
git ( & [ " merge" , " -q" , " --no-edit" , " side" ] ) ;
8413
8414
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
8415
repo.import_git ( & g, " main" ) .unwrap ( ) ;
8416
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
8417
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
8418
let st = repo
8419
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
8420
.unwrap ( ) ;
8421
let view = st.tree ( ) .view ( ) ;
8422
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
8423
assert! ( view.entries.contains_key ( " d/new.txt" ) ) ;
8424
}
d77e1702 Andy McCall 1 month ago ↶
8425
8426
8427
8428
# [ test ]
8429
fn fsck_names_a_missing_text_blob ( ) {
8430
hermetic ( ) ;
8431
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8432
let root = dir.path ( ) ;
8433
let repo = Repo:: init( root) .unwrap ( ) ;
8434
let big: String = ( 0 .. 3000 ) .map ( | i | format! ( " line {i} \n " ) ) .collect ( ) ;
8435
write ( root, " big.txt" , big.as_bytes ( ) ) ;
8436
let h = repo.commit ( " big" , & [ ] ) .unwrap ( ) ;
8437
assert_eq! ( repo.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
8438
let ( _ , texts) = repo.store ( ) .get_change_lazy ( & h) .unwrap ( ) ;
8439
let Some ( crate :: objects:: TextRef:: Blob( b, _ , _ ) ) = texts.first ( ) .copied ( ) else {
8440
panic! ( " a stored text" )
8441
} ;
64818938 Andy McCall 1 month ago ↶
8442
repo.store ( ) .remove_loose_for_test ( & b) .unwrap ( ) ;
d77e1702 Andy McCall 1 month ago ↶
8443
let rep = repo.fsck ( ) .unwrap ( ) ;
8444
assert! ( rep.errors ( ) > 0 ) ;
8445
assert! (
8446
rep.problems.iter ( ) .any ( | p | p.what.contains ( & h.short ( 12 ) ) ) ,
8447
" {:?}" ,
8448
rep.problems
8449
) ;
8450
}
a07f60a3 Andy McCall 1 month ago ↶
8451
8452
8453
8454
8455
# [ test ]
8456
fn a_subrepo_on_the_parents_hub_borrows_its_credentials ( ) {
8457
hermetic ( ) ;
8458
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8459
let mut repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
8460
repo.config
8461
.set (
8462
" remote.origin.url" ,
8463
" https://Hub.example:7343/doomsdayone/metalgear" ,
8464
)
8465
.unwrap ( ) ;
8466
repo.config.set ( " remote.origin.token" , " secret" ) .unwrap ( ) ;
8467
repo.config
8468
.set ( " remote.origin.ca" , " /tmp/hub-ca.pem" )
8469
.unwrap ( ) ;
8470
let same = repo.credentials_for ( " https://hub.example:7343/doomsdayone/rstar-api" ) ;
8471
assert_eq! ( same.token.as_deref ( ) , Some ( " secret" ) ) ;
8472
assert_eq! ( same.ca.as_deref ( ) , Some ( Path:: new( " /tmp/hub-ca.pem" ) ) ) ;
8473
for other in [
8474
" https://elsewhere.example:7343/doomsdayone/rstar-api" ,
8475
" https://hub.example/doomsdayone/rstar-api" ,
8476
" http://hub.example:7343/doomsdayone/rstar-api" ,
8477
] {
8478
let o = repo.credentials_for ( other) ;
8479
assert_ne! ( o.token.as_deref ( ) , Some ( " secret" ) , " {other}" ) ;
8480
}
8481
}
ce14f0d6 Andy McCall 1 month ago ↶
8482
ff1f62fe Andy McCall 1 month ago ↶
8483
8484
8485
ce14f0d6 Andy McCall 1 month ago ↶
8486
# [ test ]
ff1f62fe Andy McCall 1 month ago ↶
8487
fn remotes_use_the_credential_store_and_the_enclosing_checkout ( ) {
8488
hermetic ( ) ;
8489
let home = tempfile:: tempdir( ) .unwrap ( ) ;
8490
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8491
let mut parent = Repo:: init( dir.path ( ) ) .unwrap ( ) .with_home ( home.path ( ) ) ;
8492
parent
8493
.config
8494
.set ( " remote.origin.url" , " https://hub.example/o/parent" )
8495
.unwrap ( ) ;
8496
parent.save_config ( ) .unwrap ( ) ;
8497
assert_eq! ( parent.remote_options ( " origin" ) .token, None ) ;
8498
crate :: credentials:: Credentials:: at( home.path ( ) )
8499
.save ( " https://hub.example" , " stored" , Some ( " andy" ) , None )
8500
.unwrap ( ) ;
8501
assert_eq! (
8502
parent.remote_options ( " origin" ) .token.as_deref ( ) ,
8503
Some ( " stored" )
8504
) ;
8505
8506
let empty_home = tempfile:: tempdir( ) .unwrap ( ) ;
8507
let nested = dir.path ( ) .join ( " deps" ) .join ( " child" ) ;
8508
let mut child = Repo:: init( & nested) .unwrap ( ) .with_home ( empty_home.path ( ) ) ;
8509
child
8510
.config
8511
.set ( " remote.origin.url" , " https://hub.example/o/child" )
8512
.unwrap ( ) ;
8513
child.save_config ( ) .unwrap ( ) ;
8514
assert_eq! ( child.remote_options ( " origin" ) .token, None ) ;
8515
parent
8516
.config
8517
.set ( " remote.origin.token" , " checkout" )
8518
.unwrap ( ) ;
8519
parent.save_config ( ) .unwrap ( ) ;
8520
assert_eq! (
8521
child.remote_options ( " origin" ) .token.as_deref ( ) ,
8522
Some ( " checkout" )
8523
) ;
8524
8525
child
8526
.config
8527
.set ( " remote.origin.url" , " https://other.example/o/child" )
8528
.unwrap ( ) ;
8529
assert_eq! ( child.remote_options ( " origin" ) .token, None ) ;
8530
}
8531
8532
# [ test ]
ce14f0d6 Andy McCall 1 month ago ↶
8533
fn the_metadata_cache_survives_a_torn_tail ( ) {
8534
hermetic ( ) ;
8535
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8536
let root = dir.path ( ) ;
8537
let repo = Repo:: init( root) .unwrap ( ) ;
8538
write ( root, " a.txt" , b " 1\n " ) ;
8539
let h = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8540
let meta = repo.store ( ) .get_change_meta ( & h) .unwrap ( ) ;
8541
let cache = repo.store ( ) .dir ( ) .join ( " metas" ) ;
8542
let len = fs:: metadata( & cache) .unwrap ( ) .len ( ) ;
8543
assert! ( len > 0 , " a change read is cached" ) ;
8544
8545
fs:: OpenOptions:: new( )
8546
.append ( true )
8547
.open ( & cache)
8548
.unwrap ( )
8549
.write_all ( & [ 7 ; 20 ] )
8550
.unwrap ( ) ;
8551
let again = Repo:: open( root) .unwrap ( ) ;
8552
assert_eq! ( again.store ( ) .get_change_meta ( & h) .unwrap ( ) , meta) ;
8553
assert_eq! ( fs:: metadata( & cache) .unwrap ( ) .len ( ) , len) ;
8554
}
f9466021 Andy McCall 1 month ago ↶
8555
8556
# [ test ]
8557
fn a_manifest_that_does_not_check_reads_as_none ( ) {
8558
hermetic ( ) ;
8559
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8560
let root = dir.path ( ) ;
8561
let repo = Repo:: init( root) .unwrap ( ) ;
8562
write ( root, " a.txt" , b " 1\n " ) ;
8563
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8564
let path = repo.manifest_path ( ) ;
8565
let mut bytes = fs:: read( & path) .unwrap ( ) ;
8566
assert! ( Repo:: open( root) .unwrap ( ) .read_manifest ( ) .unwrap ( ) .is_some ( ) ) ;
8567
let last = bytes.len ( ) - 1 ;
8568
bytes[ last] ^ = 1 ;
8569
fs:: write( & path, & bytes) .unwrap ( ) ;
8570
assert! ( Repo:: open( root) .unwrap ( ) .read_manifest ( ) .unwrap ( ) .is_none ( ) ) ;
8571
8572
let again = Repo:: open( root) .unwrap ( ) ;
8573
assert! ( ! again.snapshot ( " status" ) .unwrap ( ) .changed) ;
8574
assert! ( again.read_manifest ( ) .unwrap ( ) .is_some ( ) ) ;
8575
}
8576
8577
# [ test ]
8578
fn a_commit_leaves_a_manifest_that_proves_the_working_copy_clean ( ) {
8579
hermetic ( ) ;
8580
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8581
let root = dir.path ( ) ;
8582
let repo = Repo:: init( root) .unwrap ( ) ;
8583
write ( root, " a.txt" , b " 1\n " ) ;
8584
write ( root, " b.txt" , b " 2\n " ) ;
8585
8586
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 60 ) ;
8587
fs:: File:: options( )
8588
.write ( true )
8589
.open ( root.join ( " b.txt" ) )
8590
.unwrap ( )
8591
.set_modified ( old)
8592
.unwrap ( ) ;
8593
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8594
assert! ( repo.clean_by_manifest ( ) .unwrap ( ) , " after the first commit" ) ;
8595
repo.snapshot_status ( " status" ) .unwrap ( ) ;
8596
write ( root, " a.txt" , b " 1\n 2\n " ) ;
8597
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
8598
assert! (
8599
repo.clean_by_manifest ( ) .unwrap ( ) ,
8600
" after a commit of one file"
8601
) ;
8602
8603
8604
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
8605
repo.snapshot_status ( " status" ) .unwrap ( ) ;
8606
write ( root, " a.txt" , b " 1\n 2\n 3\n 4\n 5\n " ) ;
8607
repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
8608
assert! (
8609
repo.clean_by_manifest ( ) .unwrap ( ) ,
8610
" after an edit a status saw"
8611
) ;
8612
}
e266b7d4 Andy McCall 1 month ago ↶
8613
8614
# [ test ]
8615
fn a_conflicted_rebase_can_be_resolved_in_the_working_copy ( ) {
8616
hermetic ( ) ;
8617
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8618
let root = dir.path ( ) ;
8619
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8620
write ( root, " a.txt" , b " 1\n 2\n " ) ;
8621
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
8622
repo.bookmark_create ( " develop" , Some ( & [ a] ) ) .unwrap ( ) ;
8623
write ( root, " a.txt" , b " 1\n X\n " ) ;
8624
let c = repo.commit ( " C: 2 to X" , & [ ] ) .unwrap ( ) ;
8625
repo.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
8626
write ( root, " a.txt" , b " 1\n Y\n " ) ;
8627
let d = repo.commit ( " D: 2 to Y" , & [ ] ) .unwrap ( ) ;
8628
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
8629
8630
let err = repo.rebase ( & c, & [ d] ) .unwrap_err ( ) ;
8631
assert! ( matches! ( err, RepoError:: Conflicted( _ ) ) , " {err}" ) ;
8632
let r = repo.rebase_into_working_copy ( & c, & [ d] ) .unwrap ( ) ;
8633
assert! ( r.changed & & r.checked_out) ;
8634
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8635
assert! (
8636
snap.base_snapshot.is_conflicted ( ) ,
8637
" the conflict is in the working copy"
8638
) ;
8639
let text = fs:: read_to_string( root.join ( " a.txt" ) ) .unwrap ( ) ;
8640
assert! ( text.contains ( ' X' ) & & text.contains ( ' Y' ) , " {text}" ) ;
8641
8642
write ( root, " a.txt" , b " 1\n XY\n " ) ;
8643
repo.commit ( " Resolve" , & [ ] ) .unwrap ( ) ;
8644
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
8645
assert! ( st.contains ( & c) & & st.contains ( & d) ) ;
8646
let files = st.materialize ( ) .files;
8647
assert! ( ! files[ " a.txt" ] .is_conflicted ( ) ) ;
8648
assert! (
8649
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 1\n XY\n " )
8650
) ;
8651
8652
8653
repo.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
8654
write ( root, " b.txt" , b " b\n " ) ;
8655
let e = repo.commit ( " E" , & [ ] ) .unwrap ( ) ;
8656
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
8657
let err = repo.rebase_into_working_copy ( & e, & [ a] ) .unwrap_err ( ) ;
8658
assert! (
8659
err.to_string ( ) .contains ( " not on the current bookmark" ) ,
8660
" {err}"
8661
) ;
8662
}
d4dbab97 Andy McCall 1 month ago ↶
8663
8664
# [ test ]
8665
fn an_ignore_rule_drops_an_added_file_but_not_a_tracked_one ( ) {
8666
hermetic ( ) ;
8667
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8668
let root = dir.path ( ) ;
8669
let repo = Repo:: init( root) .unwrap ( ) ;
8670
write ( root, " a.txt" , b " a\n " ) ;
8671
write ( root, " kept.log" , b " tracked\n " ) ;
8672
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8673
write ( root, " build.log" , b " made\n " ) ;
8674
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8675
assert! ( snap.statuses.iter ( ) .any ( | s | s.path = = " build.log" ) ) ;
8676
8677
8678
write ( root, " .gitignore" , b " *.log\n " ) ;
8679
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8680
let paths: Vec< & str > = snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect ( ) ;
8681
assert_eq! ( paths, vec! [ " .gitignore" ] , " {paths:?}" ) ;
8682
write ( root, " kept.log" , b " tracked, edited\n " ) ;
8683
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8684
assert! ( snap.statuses.iter ( ) .any ( | s | s.path = = " kept.log" ) ) ;
8685
}
fd280228 Andy McCall 1 month ago ↶
8686
8687
# [ test ]
8688
fn shed_graphs_come_back_the_same ( ) {
8689
hermetic ( ) ;
8690
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8691
let root = dir.path ( ) ;
8692
let repo = Repo:: init( root) .unwrap ( ) ;
8693
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
8694
write ( root, " b.txt" , b " x\n " ) ;
8695
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8696
write ( root, " a.txt" , b " 1\n two\n 3\n 4\n " ) ;
8697
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
8698
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
8699
let mut st = repo.load_state_history ( & heads) .unwrap ( ) ;
8700
let view = st.tree ( ) .view ( ) ;
8701
let nodes: Vec< NodeId> = [ " a.txt" , " b.txt" ]
8702
.iter ( )
8703
.map ( | p | view.entries[ * p] .node )
8704
.collect ( ) ;
8705
repo.load_graphs_of ( & mut st, & nodes) .unwrap ( ) ;
8706
let before: Vec< Vec< u8 > > = nodes.iter ( ) .map ( | n | st.render_file ( n) .0 ) .collect ( ) ;
8707
assert_eq! ( st.built_count ( ) , 2 ) ;
8708
let shed = st.shed_graphs ( | b | repo.store_blob ( b) ) .unwrap ( ) ;
8709
assert_eq! ( ( shed, st.built_count ( ) ) , ( 2 , 0 ) ) ;
8710
8711
write ( root, " a.txt" , b " 1\n two\n 3\n 4\n 5\n " ) ;
8712
let h3 = repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
8713
let ( skeleton, texts) = repo.store ( ) .get_change_lazy ( & h3) .unwrap ( ) ;
8714
st.add_known ( h3, skeleton, texts) ;
8715
repo.load_graphs_of ( & mut st, & nodes) .unwrap ( ) ;
8716
let after: Vec< Vec< u8 > > = nodes.iter ( ) .map ( | n | st.render_file ( n) .0 ) .collect ( ) ;
8717
assert_eq! ( after[ 1 ] , before[ 1 ] ) ;
8718
assert_eq! ( after[ 0 ] , b " 1\n two\n 3\n 4\n 5\n " ) ;
8719
}
360aa74a Andy McCall 1 month ago ↶
8720
8721
# [ test ]
8722
fn many_edited_files_diff_in_parallel ( ) {
8723
8724
8725
8726
hermetic ( ) ;
8727
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8728
let root = dir.path ( ) ;
8729
let repo = Repo:: init( root) .unwrap ( ) ;
8730
let text = | i : usize , round : usize | {
8731
let mut s = String :: new( ) ;
8732
for line in 0 .. 50 {
8733
if round > 0 & & line % 7 = = i % 7 {
8734
s.push_str ( & format! ( " edited {round} {i} {line} \n " ) ) ;
8735
} else {
8736
s.push_str ( & format! ( " file {i} line {line} \n " ) ) ;
8737
}
8738
}
8739
if round > 0 & & i.is_multiple_of ( 3 ) {
8740
s.push_str ( " appended\n " ) ;
8741
}
8742
s
8743
} ;
8744
for i in 0 .. 40 {
8745
write ( root, & format! ( " d/f{i} .txt" ) , text ( i, 0 ) .as_bytes ( ) ) ;
8746
}
8747
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8748
for i in 0 .. 40 {
8749
write ( root, & format! ( " d/f{i} .txt" ) , text ( i, 1 ) .as_bytes ( ) ) ;
8750
}
8751
let h = repo.commit ( " edit every file" , & [ ] ) .unwrap ( ) ;
8752
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
8753
for i in 0 .. 40 {
8754
let want = text ( i, 1 ) ;
8755
assert! (
8756
matches! ( & snap.files[ & format! ( " d/f{i} .txt" ) ] .content, FileContent:: Text { bytes, .. } if bytes = = want.as_bytes ( ) ) ,
8757
" d/f{i}.txt as committed"
8758
) ;
8759
}
8760
assert! (
8761
repo.commit ( " nothing" , & [ ] ) .is_err ( ) ,
8762
" nothing left to commit"
8763
) ;
8764
}
b35df36b Andy McCall 1 month ago ↶
8765
8766
8767
8768
8769
8770
# [ test ]
8771
fn a_fetched_change_shows_as_behind_and_resolves ( ) {
8772
hermetic ( ) ;
8773
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8774
let root = dir.path ( ) ;
8775
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
8776
drop ( origin) ;
8777
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
8778
let a = Repo:: clone( & url, & root.join ( " a" ) )
8779
.unwrap ( )
8780
.with_home ( root.join ( " home-a" ) ) ;
8781
write ( & root.join ( " a" ) , " f.txt" , b " one\n " ) ;
8782
a.commit ( " one" , & [ ] ) .unwrap ( ) ;
8783
a.push ( " origin" , false ) .unwrap ( ) ;
8784
let b = Repo:: clone( & url, & root.join ( " b" ) )
8785
.unwrap ( )
8786
.with_home ( root.join ( " home-b" ) ) ;
8787
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 0 ) ) ) ;
8788
8789
write ( & root.join ( " a" ) , " f.txt" , b " one\n two\n " ) ;
8790
let h2 = a.commit ( " two" , & [ ] ) .unwrap ( ) ;
8791
a.push ( " origin" , false ) .unwrap ( ) ;
8792
b.fetch ( " origin" ) .unwrap ( ) ;
8793
8794
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 1 ) ) ) ;
8795
assert_eq! ( b.tracking ( " origin" , " nope" ) .unwrap ( ) , None ) ;
8796
assert_eq! (
8797
b.resolve_change ( & format! ( " #{} " , & h2.to_hex ( ) [ .. 12 ] ) )
8798
.unwrap ( ) ,
8799
h2
8800
) ;
8801
assert_eq! (
8802
b.remote_only_location ( & h2) .unwrap ( ) .as_deref ( ) ,
8803
Some ( " origin/main" )
8804
) ;
8805
8806
8807
b.fetch ( " origin" ) .unwrap ( ) ;
8808
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 1 ) ) ) ;
8809
b.pull ( " origin" ) .unwrap ( ) ;
8810
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 0 ) ) ) ;
8811
assert_eq! ( b.remote_only_location ( & h2) .unwrap ( ) , None ) ;
8812
8813
8814
let mut c = Repo:: init( & root.join ( " c" ) ) .unwrap ( ) ;
8815
assert_eq! ( c.default_remote ( ) , " origin" ) ;
8816
c.config.set ( " remote.earth.url" , & url) .unwrap ( ) ;
8817
assert_eq! ( c.default_remote ( ) , " earth" ) ;
8818
c.config.set ( " remote.origin.url" , & url) .unwrap ( ) ;
8819
assert_eq! ( c.default_remote ( ) , " origin" ) ;
8820
}
827281ee Andy McCall 1 month ago ↶
8821
8822
8823
8824
# [ test ]
8825
fn a_clone_from_a_relative_path_records_it_absolute ( ) {
8826
hermetic ( ) ;
8827
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8828
let root = dir.path ( ) ;
8829
Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
8830
let rel = crate :: protocol:: stored_url( " relative/repo" ) ;
8831
assert! ( std:: path:: Path:: new( & rel) .is_absolute ( ) , " {rel}" ) ;
8832
assert_eq! (
8833
crate :: protocol:: stored_url( " https://hub.example/o/n" ) ,
8834
" https://hub.example/o/n"
8835
) ;
8836
assert! ( crate :: protocol:: stored_url( " file://x" ) .starts_with ( " file:///" ) ) ;
8d803563 Andy McCall 1 month ago ↶
8837
assert! ( crate :: protocol:: stored_url( " dir://x" ) .starts_with ( " dir:///" ) ) ;
8838
8839
assert_eq! (
8840
crate :: protocol:: stored_url( " s3://bucket/prefix?profile=p" ) ,
8841
" s3://bucket/prefix?profile=p"
8842
) ;
827281ee Andy McCall 1 month ago ↶
8843
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
8844
let b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) ;
8845
assert_eq! ( b.config.remotes[ " origin" ] , url) ;
8846
b.fetch ( " origin" ) .unwrap ( ) ;
8847
}
a46f3e4f Andy McCall 1 month ago ↶
8848
8849
8850
8851
8852
# [ test ]
8853
fn a_workspace_added_on_a_bookmark_is_on_it_with_its_subrepos ( ) {
8854
hermetic ( ) ;
8855
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8856
let root = dir.path ( ) ;
8857
Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
8858
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
8859
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
8860
.unwrap ( )
8861
.with_home ( root.join ( " home" ) ) ;
8862
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
8863
let l1 = lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
8864
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
8865
8866
let app_dir = root.join ( " app" ) ;
8867
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8868
write ( & app_dir, " main.txt" , b " app\n " ) ;
8869
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
8870
app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
8871
8872
app.bookmark_create ( " develop" , None ) .unwrap ( ) ;
8873
app.bookmark_create ( " feature" , None ) .unwrap ( ) ;
8874
let mut app = app;
8875
app.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
8876
assert_eq! ( app.config.bookmark, " feature" ) ;
8877
8878
8879
std:: fs:: rename( root.join ( " lib-origin" ) , root.join ( " lib-origin-gone" ) ) .unwrap ( ) ;
8880
let heads = app.resolve_heads ( " develop" ) .unwrap ( ) ;
8881
let ws = app
8882
.workspace_add_on ( & root.join ( " ws" ) , Some ( & heads) , Some ( " develop" ) )
8883
.unwrap ( ) ;
8884
assert_eq! (
8885
ws.config.bookmark, " develop" ,
8886
" the new workspace is on develop"
8887
) ;
8888
assert_eq! (
8889
app.config.bookmark, " feature" ,
8890
" and this one stays on its own"
8891
) ;
8892
let sub = Repo:: open( & root.join ( " ws/libs/lib" ) ) .unwrap ( ) ;
8893
assert_eq! (
8894
sub.config.remotes[ " origin" ] , lib_url,
8895
" its origin is the subrepo's URL"
8896
) ;
8897
assert_eq! ( sub.bookmark_heads ( ) .unwrap ( ) , vec! [ l1] , " at its pin" ) ;
8898
assert_eq! (
8899
std:: fs:: read( root.join ( " ws/libs/lib/lib.txt" ) ) .unwrap ( ) ,
8900
b " v1\n "
8901
) ;
8902
}
47ce8b8e Andy McCall 1 month ago ↶
8903
8904
8905
8906
8907
# [ test ]
8908
fn revsets_select_changes_by_relation_author_text_date_and_path ( ) {
8909
hermetic ( ) ;
8910
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8911
let root = dir.path ( ) ;
8912
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8913
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
8914
.unwrap ( ) ;
8915
write ( root, " src/a.rs" , b " one\n " ) ;
8916
let a = repo.commit ( " first: add a" , & [ ] ) .unwrap ( ) ;
8917
write ( root, " src/a.rs" , b " one\n two\n " ) ;
8918
let b = repo.commit ( " second: grow a" , & [ ] ) .unwrap ( ) ;
8919
8920
repo.checkout ( & [ a] , false , " checkout a" ) .unwrap ( ) ;
8921
write ( root, " docs/notes.md" , b " notes\n " ) ;
8922
let c = repo.commit ( " third: notes" , & [ ] ) .unwrap ( ) ;
8923
8924
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
8925
.unwrap ( ) ;
8926
fs:: remove_file( root.join ( " src/a.rs" ) ) .unwrap ( ) ;
8927
write ( root, " src/z.rs" , b " one\n two\n " ) ;
8928
let d = repo.commit ( " fourth: rename a to z" , & [ ] ) .unwrap ( ) ;
8929
let wc = repo.working_copy ( ) .unwrap ( ) .0 ;
8930
8931
repo.set_user_config ( " revset.mine" , " author(me) & ::main" )
8932
.unwrap ( ) ;
8933
assert! ( repo.set_user_config ( " revset.bad" , " a &" ) .is_err ( ) ) ;
8934
let set = | src : & str | -> Vec< ChangeHash> {
8935
let mut v = repo.revset ( src) .unwrap ( ) .hashes;
8936
v.sort ( ) ;
8937
v
8938
} ;
8939
let sorted = | hs : & [ChangeHash]| -> Vec< ChangeHash> {
8940
let mut v = hs.to_vec ( ) ;
8941
v.sort ( ) ;
8942
v
8943
} ;
8944
8945
8946
8947
8948
let heads = sorted ( & repo.bookmark_heads ( ) .unwrap ( ) ) ;
8949
let meta = | h : & ChangeHash| repo.store ( ) .get_change_meta ( h) .unwrap ( ) ;
8950
assert! ( heads.contains ( & d) & & heads.contains ( & c) ) ;
8951
8952
assert_eq! ( set ( " main" ) , heads) ;
8953
assert_eq! ( set ( " ::main" ) , sorted ( & [ a, b, c, d] ) ) ;
8954
assert_eq! ( set ( " @" ) , vec! [ wc] ) ;
8955
assert_eq! ( set ( " ::@" ) , sorted ( & [ a, b, c, d, wc] ) ) ;
8956
assert_eq! ( set ( & format! ( " ::#{} " , b.short ( 12 ) ) ) , sorted ( & [ a, b] ) ) ;
8957
assert_eq! ( set ( & format! ( " #{} ::" , a.short ( 12 ) ) ) , sorted ( & [ a, b, d, wc] ) ) ;
8958
assert_eq! (
8959
set ( & format! ( " #{} ::#{} " , a.short ( 12 ) , b.short ( 12 ) ) ) ,
8960
sorted ( & [ a, b] )
8961
) ;
8962
8963
assert_eq! ( set ( & format! ( " #{} ..main" , b.short ( 12 ) ) ) , sorted ( & [ c, d] ) ) ;
8964
assert_eq! ( set ( & format! ( " #{} .." , c.short ( 12 ) ) ) , sorted ( & [ a, b, d, wc] ) ) ;
8965
assert_eq! ( set ( " ..main" ) , sorted ( & [ a, b, c, d] ) ) ;
8966
8967
assert_eq! (
8968
set ( & format! ( " ::main ~ #{} " , d.short ( 12 ) ) ) ,
8969
sorted ( & [ a, b, c] )
8970
) ;
8971
assert_eq! (
8972
set ( & format! ( " ::main & #{} | #{} " , b.short ( 12 ) , c.short ( 12 ) ) ) ,
8973
sorted ( & [ b, c] )
8974
) ;
8975
assert_eq! ( set ( " all() ~ ::main" ) , vec! [ wc] ) ;
8976
8977
let mut head_deps: Vec< ChangeHash> = heads.iter ( ) .flat_map ( | h | meta ( h) .deps) .collect ( ) ;
8978
head_deps.sort ( ) ;
8979
head_deps.dedup ( ) ;
8980
assert_eq! ( set ( " deps(main)" ) , head_deps) ;
8981
let on_a: Vec< ChangeHash> = [ b, c, d]
8982
.into_iter ( )
8983
.filter ( | h | meta ( h) .deps.contains ( & a) )
8984
.collect ( ) ;
8985
assert_eq! ( set ( & format! ( " dependents(#{} )" , a.short ( 12 ) ) ) , sorted ( & on_a) ) ;
8986
assert_eq! ( set ( " heads(::main)" ) , heads) ;
8987
assert_eq! ( set ( " roots(::main)" ) , sorted ( & [ a, c] ) ) ;
8988
assert_eq! (
8989
set ( & format! (
8990
" heads(#{} | #{} | #{} )" ,
8991
a.short ( 12 ) ,
8992
b.short ( 12 ) ,
8993
c.short ( 12 )
8994
) ) ,
8995
sorted ( & [ b, c] )
8996
) ;
8997
assert_eq! ( set ( " latest(::main, 1)" ) , vec! [ d] ) ;
8998
assert_eq! ( set ( " kind(working-copy)" ) , vec! [ wc] ) ;
8999
assert_eq! ( set ( " none()" ) , vec! [ ] ) ;
9000
9001
let key = repo.config.author_key.unwrap ( ) ;
9002
assert_eq! ( set ( " author(ada) & ::main" ) , sorted ( & [ a, b, c, d] ) ) ;
9003
assert_eq! ( set ( " author(\" [email protected] \" ) & ::main" ) .len ( ) , 4 ) ;
9004
assert_eq! ( set ( & format! ( " author({} ) & ::main" , key.short ( 12 ) ) ) .len ( ) , 4 ) ;
9005
assert_eq! ( set ( " author(me) & ::main" ) .len ( ) , 4 ) ;
9006
assert_eq! ( set ( " author(nobody)" ) , vec! [ ] ) ;
9007
assert_eq! ( set ( " description(notes)" ) , vec! [ c] ) ;
9008
assert_eq! ( set ( " description(\" rename a\" )" ) , vec! [ d] ) ;
9009
assert_eq! ( set ( " date(-1h..) & ::main" ) .len ( ) , 4 ) ;
9010
assert_eq! ( set ( " date(..2000-01-01)" ) , vec! [ ] ) ;
9011
9012
assert_eq! ( set ( " touches(\" src/z.rs\" )" ) , sorted ( & [ a, b, d] ) ) ;
9013
assert_eq! ( set ( " touches(\" src/**\" ) & ::main" ) , sorted ( & [ a, b, d] ) ) ;
9014
assert_eq! ( set ( " touches(\" docs/*\" )" ) , vec! [ c] ) ;
9015
assert_eq! ( set ( " touches(\" nothing/*\" )" ) , vec! [ ] ) ;
9016
9017
assert_eq! ( repo.revset ( " ::main" ) .unwrap ( ) .hashes[ 0 ] , d) ;
9018
9019
assert_eq! ( set ( " mine()" ) .len ( ) , 4 ) ;
9020
let e = repo.revset ( " nosuch()" ) .unwrap_err ( ) .to_string ( ) ;
9021
assert! ( e.contains ( " unknown function nosuch()" ) , " {e}" ) ;
9022
let e = repo.revset ( " main &" ) .unwrap_err ( ) .to_string ( ) ;
9023
assert! ( e.contains ( " at character" ) , " {e}" ) ;
9024
let e = repo.revset ( " zzzz" ) .unwrap_err ( ) .to_string ( ) ;
9025
assert! ( e.contains ( " no change matches" ) , " {e}" ) ;
9026
let e = repo
9027
.revset_with_budget ( " ::main" , std:: time:: Duration:: ZERO )
9028
.unwrap_err ( )
9029
.to_string ( ) ;
9030
assert! ( e.contains ( " budget" ) , " {e}" ) ;
9031
9032
let entries = repo.log_revset ( " ::@" ) .unwrap ( ) ;
9033
assert! ( entries[ 0 ] .is_working_copy & & entries.iter ( ) .any ( | e | e.is_head & & e.hash = = d ) ) ;
2c9ddac3 Andy McCall 1 month ago ↶
9034
9035
9036
assert_eq! ( set ( " unsigned() & ::main" ) , sorted ( & [ a, b, c, d] ) ) ;
9037
assert_eq! ( set ( " signed_by(me)" ) , vec! [ ] ) ;
9038
repo.sign_change ( & d) .unwrap ( ) ;
9039
assert_eq! ( set ( " signed_by(me)" ) , vec! [ d] ) ;
9040
assert_eq! ( set ( " signed_by([email protected] )" ) , vec! [ d] ) ;
9041
assert_eq! ( set ( & format! ( " signed_by({} )" , key.short ( 12 ) ) ) , vec! [ d] ) ;
9042
assert_eq! ( set ( " unsigned() & ::main" ) , sorted ( & [ a, b, c] ) ) ;
9043
assert_eq! ( set ( " delegated()" ) , vec! [ ] ) ;
9044
assert_eq! ( set ( " revoked()" ) , vec! [ ] ) ;
9045
assert_eq! ( set ( " attested(tag)" ) , vec! [ ] ) ;
9046
repo.tag_create ( " v1" , & [ b] , " first" ) .unwrap ( ) ;
9047
assert_eq! ( set ( " attested(tag)" ) , sorted ( & [ a, b] ) ) ;
9048
assert_eq! ( set ( " attested(tag) & unsigned()" ) , sorted ( & [ a, b] ) ) ;
9049
assert_eq! ( set ( " attested(receipt)" ) , vec! [ ] ) ;
9050
assert_eq! ( set ( " built(test)" ) , vec! [ ] ) ;
9051
let e = repo.revset ( " attested(seal)" ) .unwrap_err ( ) .to_string ( ) ;
9052
assert! ( e.contains ( " tag, receipt or ref" ) , " {e}" ) ;
9053
repo.revoke_identity ( & key, " test" ) .unwrap ( ) ;
9054
assert_eq! ( set ( " revoked()" ) , vec! [ d] ) ;
9055
assert_eq! ( set ( " signed_by(me) ~ revoked()" ) , vec! [ ] ) ;
543bbf64 Andy McCall 1 month ago ↶
9056
9057
9058
9059
assert_eq! (
9060
set ( & format! ( " independent(#{} , ::@)" , b.short ( 12 ) ) ) ,
9061
sorted ( & [ c, d] )
9062
) ;
9063
assert_eq! (
9064
set ( & format! ( " independent(#{} , ::main)" , a.short ( 12 ) ) ) ,
9065
vec! [ c]
9066
) ;
9067
9068
assert_eq! ( set ( " conflicts()" ) , vec! [ ] ) ;
9069
assert_eq! ( set ( " conflicts(main)" ) , vec! [ ] ) ;
9070
9071
9072
repo.checkout ( & [ a] , true , " checkout a" ) .unwrap ( ) ;
9073
write ( root, " src/a.rs" , b " one\n TWO\n " ) ;
9074
let e = repo
9075
.commit ( " fifth: the other edit of line two" , & [ ] )
9076
.unwrap ( ) ;
9077
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , true , " checkout main" )
9078
.unwrap ( ) ;
9079
let conflicted = repo.revset ( " conflicts()" ) .unwrap ( ) .hashes;
9080
assert! (
9081
conflicted.contains ( & e) & & conflicted.contains ( & b) ,
9082
" {conflicted:?} should hold b and e"
9083
) ;
9084
assert! ( ! conflicted.contains ( & c) ) ;
9085
assert_eq! ( set ( " conflicts() & author(nobody)" ) , vec! [ ] ) ;
47ce8b8e Andy McCall 1 month ago ↶
9086
}
614f55e1 Andy McCall 1 month ago ↶
9087
9088
# [ test ]
9089
fn a_rotation_is_recorded_wherever_the_old_key_was_named ( ) {
9090
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9091
let root = dir.path ( ) ;
9092
let home = root.join ( " home" ) ;
9093
9094
9095
let mut a = Repo:: init( & root.join ( " a" ) ) .unwrap ( ) .with_home ( & home) ;
9096
let ( key, _ ) = a
9097
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
9098
.unwrap ( ) ;
9099
a.config.set ( " author.key" , & key.to_hex ( ) ) .unwrap ( ) ;
9100
a.save_config ( ) .unwrap ( ) ;
9101
let agent = SecretKey:: generate( ) .identity ( " Agent" , " [email protected] " , 1 ) ;
9102
let r = a
9103
.delegate_add ( agent.clone ( ) , crate :: identity:: Scope:: CHANGES , None )
9104
.unwrap ( ) ;
9105
assert_eq! (
9106
read_config_file ( & home.join ( " config.toml" ) ) .unwrap ( ) [ " author.key" ] ,
9107
r.new.to_hex ( ) ,
9108
" the user file follows"
9109
) ;
9110
assert_eq! (
9111
read_config_file ( & root.join ( " a/.oxy/config" ) ) .unwrap ( ) [ " author.key" ] ,
9112
r.new.to_hex ( ) ,
9113
" the pinning repository follows"
9114
) ;
9115
let b = Repo:: init( & root.join ( " b" ) ) .unwrap ( ) .with_home ( & home) ;
9116
assert_eq! (
9117
b.config.author_key,
9118
Some ( r.new) ,
9119
" a checkout without a pin follows the user file"
9120
) ;
9121
9122
let other_home = root.join ( " home2" ) ;
9123
let mut c = Repo:: init( & root.join ( " c" ) ) .unwrap ( ) .with_home ( & other_home) ;
9124
let ( ck, _ ) = c
9125
.create_identity ( " Cy" , " [email protected] " , IdentityScope:: Repo)
9126
.unwrap ( ) ;
9127
assert! ( ! other_home.join ( " config.toml" ) .exists ( ) ) ;
9128
let r2 = c.rotate_identity ( ) .unwrap ( ) ;
9129
assert_ne! ( r2.new, ck) ;
9130
assert_eq! (
9131
read_config_file ( & root.join ( " c/.oxy/config" ) ) .unwrap ( ) [ " author.key" ] ,
9132
r2.new.to_hex ( )
9133
) ;
9134
assert! (
9135
! other_home.join ( " config.toml" ) .exists ( ) ,
9136
" a repository-scoped identity stays repository-scoped"
9137
) ;
9138
9139
let ( d1, d2) = (
9140
SecretKey:: generate( ) .identity ( " D1" , " d1@x" , 1 ) ,
9141
SecretKey:: generate( ) .identity ( " D2" , " d2@x" , 1 ) ,
9142
) ;
9143
let r3 = c
9144
.delegate_add_many ( vec! [ d1.clone ( ) , d2] , crate :: identity:: Scope:: CHANGES , None )
9145
.unwrap ( ) ;
9146
assert_eq! ( r3.identity.delegates.len ( ) , 2 ) ;
9147
assert! ( c
9148
.delegate_add ( d1, crate :: identity:: Scope:: CHANGES , None )
9149
.is_err ( ) ) ;
9150
}
33be2409 Andy McCall 1 month ago ↶
9151
9152
9153
9154
9155
9156
# [ test ]
9157
fn git_import_joins_unrelated_histories_that_add_the_same_file ( ) {
9158
9159
9160
crate :: repo:: git:: set_merge_batch_for_tests( Some ( 1 ) ) ;
9161
hermetic ( ) ;
9162
if std:: process:: Command:: new( " git" )
9163
.arg ( " --version" )
9164
.output ( )
9165
.is_err ( )
9166
{
9167
eprintln! ( " git not installed; skipping" ) ;
9168
return ;
9169
}
9170
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9171
let root = dir.path ( ) ;
9172
let g = root.join ( " src.git" ) ;
9173
fs:: create_dir_all( & g) .unwrap ( ) ;
9174
let git = | args : & [& str ]| -> String {
9175
let out = std:: process:: Command:: new( " git" )
9176
.arg ( " -C" )
9177
.arg ( & g)
9178
.args ( [
9179
" -c" ,
9180
" user.name=Grace" ,
9181
" -c" ,
9182
" [email protected] " ,
9183
" -c" ,
9184
" commit.gpgsign=false" ,
9185
] )
9186
.args ( args)
9187
.output ( )
9188
.unwrap ( ) ;
9189
assert! (
9190
out.status.success ( ) ,
9191
" git {args:?}: {}" ,
9192
String :: from_utf8_lossy( & out.stderr)
9193
) ;
9194
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
9195
} ;
9196
let text = b " /* header */\n const a = 1;\n const b = 2;\n " ;
9197
9198
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
9199
write ( & g, " gulpfile.js" , text) ;
9200
write ( & g, " a.txt" , b " a\n " ) ;
9201
git ( & [ " add" , " ." ] ) ;
9202
git ( & [ " commit" , " -q" , " -m" , " A root" ] ) ;
9203
9204
git ( & [ " checkout" , " -q" , " --orphan" , " fork" ] ) ;
9205
git ( & [ " rm" , " -q" , " -r" , " --cached" , " ." ] ) ;
9206
for f in [ " gulpfile.js" , " a.txt" ] {
9207
let _ = fs:: remove_file( g.join ( f) ) ;
9208
}
9209
write ( & g, " gulpfile.js" , text) ;
9210
write ( & g, " b.txt" , b " b\n " ) ;
9211
git ( & [ " add" , " ." ] ) ;
9212
git ( & [ " commit" , " -q" , " -m" , " B root" ] ) ;
9213
9214
git ( & [ " checkout" , " -q" , " -b" , " one" , " main" ] ) ;
9215
git ( & [
9216
" merge" ,
9217
" -q" ,
9218
" --no-ff" ,
9219
" --allow-unrelated-histories" ,
9220
" -m" ,
9221
" join on one" ,
9222
" fork" ,
9223
] ) ;
9224
git ( & [ " checkout" , " -q" , " -b" , " two" , " main" ] ) ;
9225
write ( & g, " a.txt" , b " a\n two\n " ) ;
9226
git ( & [ " commit" , " -q" , " -am" , " work on two" ] ) ;
9227
git ( & [
9228
" merge" ,
9229
" -q" ,
9230
" --no-ff" ,
9231
" --allow-unrelated-histories" ,
9232
" -m" ,
9233
" join on two" ,
9234
" fork" ,
9235
] ) ;
9236
9237
git ( & [ " checkout" , " -q" , " main" ] ) ;
9238
git ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge one" , " one" ] ) ;
9239
git ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge two" , " two" ] ) ;
9240
write (
9241
& g,
9242
" gulpfile.js" ,
9243
b " /* header */\n const a = 1;\n const b = 3;\n " ,
9244
) ;
9245
git ( & [ " commit" , " -q" , " -am" , " after" ] ) ;
9246
9247
let dest = root.join ( " dest" ) ;
9248
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9249
repo.import_git ( & g, " main" ) .unwrap ( ) ;
9250
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
9251
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
9252
assert_eq! (
9253
fs:: read( dest.join ( " gulpfile.js" ) ) .unwrap ( ) ,
9254
b " /* header */\n const a = 1;\n const b = 3;\n "
9255
) ;
9256
}
9257
9258
9259
9260
9261
# [ test ]
9262
fn git_import_joins_where_each_join_kept_a_different_copy ( ) {
9263
9264
9265
crate :: repo:: git:: set_merge_batch_for_tests( Some ( 1 ) ) ;
9266
hermetic ( ) ;
9267
if std:: process:: Command:: new( " git" )
9268
.arg ( " --version" )
9269
.output ( )
9270
.is_err ( )
9271
{
9272
eprintln! ( " git not installed; skipping" ) ;
9273
return ;
9274
}
9275
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9276
let root = dir.path ( ) ;
9277
let g = root.join ( " src.git" ) ;
9278
fs:: create_dir_all( & g) .unwrap ( ) ;
9279
let run = | args : & [& str ]| {
9280
std:: process:: Command:: new( " git" )
9281
.arg ( " -C" )
9282
.arg ( & g)
9283
.args ( [
9284
" -c" ,
9285
" user.name=Grace" ,
9286
" -c" ,
9287
" [email protected] " ,
9288
" -c" ,
9289
" commit.gpgsign=false" ,
9290
] )
9291
.args ( args)
9292
.output ( )
9293
.unwrap ( )
9294
} ;
9295
let git = | args : & [& str ]| -> String {
9296
let out = run ( args) ;
9297
assert! (
9298
out.status.success ( ) ,
9299
" git {args:?}: {}" ,
9300
String :: from_utf8_lossy( & out.stderr)
9301
) ;
9302
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
9303
} ;
9304
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
9305
write (
9306
& g,
9307
" gulpfile.js" ,
9308
b " /* header */\n const a = 1;\n const b = 2;\n " ,
9309
) ;
9310
git ( & [ " add" , " ." ] ) ;
9311
git ( & [ " commit" , " -q" , " -m" , " A root" ] ) ;
9312
write (
9313
& g,
9314
" gulpfile.js" ,
9315
b " /* header */\n const a = 1;\n \n const b = 2;\n " ,
9316
) ;
9317
git ( & [ " commit" , " -q" , " -am" , " whitespace" ] ) ;
9318
git ( & [ " checkout" , " -q" , " --orphan" , " fork" ] ) ;
9319
git ( & [ " rm" , " -q" , " -r" , " --cached" , " ." ] ) ;
9320
let _ = fs:: remove_file( g.join ( " gulpfile.js" ) ) ;
9321
write (
9322
& g,
9323
" gulpfile.js" ,
9324
b " /* header */\n const a = 1;\n const b = 2;\n const c = 3;\n " ,
9325
) ;
9326
git ( & [ " add" , " ." ] ) ;
9327
git ( & [ " commit" , " -q" , " -m" , " B root" ] ) ;
9328
9329
for ( branch, keep) in [ ( " one" , " ours" ) , ( " two" , " theirs" ) ] {
9330
git ( & [ " checkout" , " -q" , " -b" , branch, " main" ] ) ;
9331
let _ = run ( & [
9332
" merge" ,
9333
" -q" ,
9334
" --no-ff" ,
9335
" --allow-unrelated-histories" ,
9336
" -m" ,
9337
" join" ,
9338
" fork" ,
9339
] ) ;
9340
git ( & [ " checkout" , & format! ( " --{keep} " ) , " gulpfile.js" ] ) ;
9341
git ( & [ " add" , " gulpfile.js" ] ) ;
9342
git ( & [ " commit" , " -q" , " -m" , & format! ( " join on {branch} " ) ] ) ;
9343
}
9344
9345
git ( & [ " checkout" , " -q" , " one" ] ) ;
9346
write (
9347
& g,
9348
" gulpfile.js" ,
9349
b " /* header */\n const a = 1;\n \n const b = 2;\n // tslint\n " ,
9350
) ;
9351
git ( & [ " commit" , " -q" , " -am" , " tslint on one" ] ) ;
9352
git ( & [ " checkout" , " -q" , " two" ] ) ;
9353
write (
9354
& g,
9355
" gulpfile.js" ,
9356
b " /* header */\n const a = 1;\n const b = 2;\n const c = 3;\n // fmt\n " ,
9357
) ;
9358
write ( & g, " extra.txt" , b " x\n " ) ;
9359
write ( & g, " more.txt" , b " m\n " ) ;
9360
git ( & [ " add" , " ." ] ) ;
9361
git ( & [ " commit" , " -q" , " -am" , " fmt on two" ] ) ;
9362
9363
git ( & [ " checkout" , " -q" , " one" ] ) ;
9364
let _ = run ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge two" , " two" ] ) ;
9365
write (
9366
& g,
9367
" gulpfile.js" ,
9368
b " /* header */\n const a = 1;\n const b = 2;\n const c = 3;\n // tslint\n // fmt\n " ,
9369
) ;
9370
git ( & [ " add" , " gulpfile.js" ] ) ;
9371
git ( & [ " commit" , " -q" , " -m" , " merge two into one" ] ) ;
9372
9373
let dest = root.join ( " dest" ) ;
9374
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9375
repo.import_git ( & g, " one" ) .unwrap ( ) ;
9376
let v = repo.verify_git_import ( & g, " one" ) .unwrap ( ) ;
9377
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
9378
}
dd21fd82 Andy McCall 1 month ago ↶
9379
9380
9381
9382
9383
9384
9385
# [ test ]
9386
fn over_a_store_the_operation_head_moves_by_compare_and_swap ( ) {
9387
use crate :: strata:: { MemoryStore, Store as _ } ;
9388
let backend = std:: sync:: Arc:: new( MemoryStore:: new( ) ) ;
9389
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = Some ( backend.clone ( ) ) ) ;
9390
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9391
let a = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9392
assert! (
9393
! dir.path ( ) .join ( " .oxy/ops/head" ) .exists ( ) ,
9394
" the head is not a file"
9395
) ;
9396
let ( h0, op0) = a.head_op ( ) .unwrap ( ) ;
9397
let record = backend
9398
.read_ref ( HEAD_RECORD )
9399
.unwrap ( )
9400
.expect ( " the head is the store's record" ) ;
9401
assert_eq! ( String :: from_utf8( record.0 ) .unwrap ( ) , h0.to_hex ( ) ) ;
9402
9403
let b = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
9404
let state = a.store.put_state ( & StateObject:: new( vec! [ ] ) ) .unwrap ( ) ;
9405
let mut va = op0.view.clone ( ) ;
9406
va.bookmarks.insert ( " from-a" .into ( ) , state) ;
9407
let mut vb = op0.view.clone ( ) ;
9408
vb.bookmarks.insert ( " from-b" .into ( ) , state) ;
9409
let rb = b.record_op ( & [ h0] , " bookmark from-b" , vb) .unwrap ( ) ;
9410
assert_eq! ( b.head_ref ( ) .unwrap ( ) , rb) ;
9411
9412
let ra = a.record_op ( & [ h0] , " bookmark from-a" , va) .unwrap ( ) ;
9413
let ( head, op) = Repo:: open( dir.path ( ) ) .unwrap ( ) .head_op ( ) .unwrap ( ) ;
9414
assert_eq! ( head, ra) ;
9415
assert! (
9416
op.command.ends_with ( " (merged with a concurrent operation)" ) ,
9417
" {}" ,
9418
op.command
9419
) ;
9420
assert_eq! ( op.parents, vec! [ rb, op.parents[ 1 ] ] ) ;
9421
assert! ( op.view.bookmarks.contains_key ( " from-a" ) & & op.view.bookmarks.contains_key ( " from-b" ) ) ;
9422
9423
9424
let ( h, v) = a.read_head ( ) .unwrap ( ) ;
9425
assert! ( b.swap_head ( & rb, v.as_ref ( ) ) .unwrap ( ) ) ;
9426
assert! ( ! a.swap_head ( & h, v.as_ref ( ) ) .unwrap ( ) , " stale" ) ;
767ac6cd Andy McCall 18 days ago
9427
a.refresh ( ) .unwrap ( ) ;
dd21fd82 Andy McCall 1 month ago ↶
9428
assert_eq! ( a.head_ref ( ) .unwrap ( ) , rb) ;
9429
9430
let other = tempfile:: tempdir( ) .unwrap ( ) ;
9431
let err = Repo:: init( other.path ( ) )
9432
.err ( )
9433
.expect ( " the store already holds a log" )
9434
.to_string ( ) ;
9435
assert! (
9436
err.contains ( " already holds a repository's operation log" ) ,
9437
" {err}"
9438
) ;
9439
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = None ) ;
9440
}
d069701a Andy McCall 1 month ago ↶
9441
9442
9443
9444
9445
# [ test ]
9446
fn a_repository_runs_on_a_store_with_a_cache_in_front ( ) {
9447
hermetic ( ) ;
9448
if std:: env:: var_os( " OXY_STORE_BACKEND" ) .is_some ( ) {
9449
return ;
9450
}
9451
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9452
let ( root, bucket) = ( dir.path ( ) .join ( " work" ) , dir.path ( ) .join ( " bucket" ) ) ;
9453
let url = format! ( " dir://{} " , bucket.display ( ) ) ;
9454
let repo = Repo:: init_on( & root, [ 7 ; 16 ] , Some ( & url) ) .unwrap ( ) ;
9455
assert_eq! ( repo.config.storage_url.as_deref ( ) , Some ( url.as_str ( ) ) ) ;
9456
write ( & root, " a.txt" , b " one\n " ) ;
9457
let h1 = repo.commit ( " First" , & [ ] ) .unwrap ( ) ;
9458
write ( & root, " a.txt" , b " one\n two\n " ) ;
9459
let h2 = repo.commit ( " Second" , & [ ] ) .unwrap ( ) ;
9460
assert! (
9461
! root.join ( " .oxy/ops/head" ) .exists ( ) ,
9462
" the head is in the store"
9463
) ;
9464
assert! ( bucket.join ( " ops/head" ) .is_file ( ) ) ;
9465
assert! ( root.join ( " .oxy/cache" ) .is_dir ( ) ) ;
9466
drop ( repo) ;
9467
9468
9469
for drop_cache in [ false , true ] {
9470
if drop_cache {
9471
std:: fs:: remove_dir_all( root.join ( " .oxy/cache" ) ) .unwrap ( ) ;
9472
}
9473
let repo = Repo:: open( & root) .unwrap ( ) ;
9474
let log: Vec< String> = repo
9475
.op_log ( )
9476
.unwrap ( )
9477
.into_iter ( )
9478
.map ( | ( _, op ) | op.command )
9479
.collect ( ) ;
9480
assert! ( log.len ( ) > = 3 , " {log:?}" ) ;
9481
assert_eq! ( repo.store ( ) .get_change ( & h1) .unwrap ( ) .message, " First" ) ;
9482
let heads = repo
9483
.store ( )
9484
.get_state ( & repo.view ( ) .unwrap ( ) .bookmarks[ " main" ] )
9485
.unwrap ( )
9486
.heads;
9487
assert_eq! ( heads, vec! [ h2] ) ;
9488
assert_eq! (
9489
repo.load_state ( & heads) .unwrap ( ) .materialize ( ) .files.len ( ) ,
9490
1
9491
) ;
9492
}
9493
9494
9495
let err = Repo:: init_on( & dir.path ( ) .join ( " other" ) , [ 8 ; 16 ] , Some ( & url) )
9496
.err ( )
9497
.unwrap ( )
9498
.to_string ( ) ;
9499
assert! (
9500
err.contains ( " already holds a repository's operation log" ) ,
9501
" {err}"
9502
) ;
9503
let err = Repo:: init_on( & dir.path ( ) .join ( " third" ) , [ 9 ; 16 ] , Some ( " nope://x" ) )
9504
.err ( )
9505
.unwrap ( )
9506
.to_string ( ) ;
9507
assert! ( err.contains ( " cannot open that kind of store" ) , " {err}" ) ;
9508
}
80677b7a Andy McCall 1 month ago ↶
9509
9510
9511
9512
9513
9514
# [ test ]
9515
fn the_quick_dirty_check_compares_a_subrepo_by_its_pin ( ) {
9516
hermetic ( ) ;
9517
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9518
let root = dir.path ( ) ;
9519
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9520
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9521
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9522
.unwrap ( )
9523
.with_home ( root.join ( " home" ) ) ;
9524
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9525
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9526
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9527
let app_dir = root.join ( " app" ) ;
9528
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9529
write ( & app_dir, " main.txt" , b " app\n " ) ;
9530
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9531
app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
9532
9533
std:: thread:: sleep( std:: time:: Duration:: from_millis( 2100 ) ) ;
9534
let reopen = | | Repo:: open( & app_dir) .unwrap ( ) ;
9535
assert_eq! (
9536
reopen ( ) .quick_dirty ( ) .unwrap ( ) ,
9537
Some ( false ) ,
9538
" clean, as status says"
9539
) ;
9540
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) ) .unwrap ( ) ;
9541
write ( & app_dir.join ( " libs/lib" ) , " lib.txt" , b " v2\n " ) ;
9542
nested.commit ( " lib v2" , & [ ] ) .unwrap ( ) ;
9543
assert_eq! (
9544
reopen ( ) .quick_dirty ( ) .unwrap ( ) ,
9545
Some ( true ) ,
9546
" the subrepo moved"
9547
) ;
9548
reopen ( ) .commit ( " pin lib v2" , & [ ] ) .unwrap ( ) ;
9549
std:: thread:: sleep( std:: time:: Duration:: from_millis( 2100 ) ) ;
9550
assert_eq! (
9551
reopen ( ) .quick_dirty ( ) .unwrap ( ) ,
9552
Some ( false ) ,
9553
" the new pin is committed"
9554
) ;
9555
}
7993d465 Andy McCall 1 month ago ↶
9556
9557
# [ test ]
9558
fn a_module_entry_that_was_never_added_is_reported_as_unpinned ( ) {
9559
9560
hermetic ( ) ;
9561
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9562
let root = dir.path ( ) ;
9563
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9564
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9565
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9566
.unwrap ( )
9567
.with_home ( root.join ( " home" ) ) ;
9568
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9569
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9570
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9571
let app_dir = root.join ( " app" ) ;
9572
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9573
write (
9574
& app_dir,
9575
" .oxymodules" ,
9576
format! ( " [libs/lib]\n url = {lib_url} \n " ) .as_bytes ( ) ,
9577
) ;
9578
let unpinned = app.unpinned_modules ( ) .unwrap ( ) ;
9579
assert_eq! ( unpinned.len ( ) , 1 ) ;
9580
assert_eq! ( unpinned[ 0 ] .0 , " libs/lib" ) ;
9581
assert_eq! ( unpinned[ 0 ] .1. url, lib_url) ;
9582
assert! ( app.submodule_update ( true , & [ ] ) .unwrap ( ) .is_empty ( ) ) ;
9583
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9584
assert! ( app.unpinned_modules ( ) .unwrap ( ) .is_empty ( ) , " added now" ) ;
9585
}
bf3a6781 Andy McCall 1 month ago ↶
9586
9587
# [ test ]
9588
fn the_bookmark_revset_function_names_only_bookmarks ( ) {
9589
9590
hermetic ( ) ;
9591
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9592
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9593
write ( dir.path ( ) , " a.txt" , b " a\n " ) ;
9594
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
9595
write ( dir.path ( ) , " b.txt" , b " b\n " ) ;
9596
let b = repo.commit ( " B" , & [ ] ) .unwrap ( ) ;
9597
let mut heads = repo.revset ( " bookmark(main)" ) .unwrap ( ) .hashes;
9598
heads.sort ( ) ;
9599
let mut want = repo.bookmark_heads ( ) .unwrap ( ) ;
9600
want.sort ( ) ;
9601
assert_eq! ( heads, want) ;
9602
assert! ( heads.contains ( & b) ) ;
9603
assert_eq! ( repo.revset ( " main" ) .unwrap ( ) .hashes.len ( ) , heads.len ( ) ) ;
9604
9605
let prefix = repo.store ( ) .get_change_meta ( & a) .unwrap ( ) .change_id.short ( 8 ) ;
9606
assert! ( repo.revset ( & prefix) .is_ok ( ) ) ;
9607
let e = repo
9608
.revset ( & format! ( " bookmark({prefix} )" ) )
9609
.unwrap_err ( )
9610
.to_string ( ) ;
9611
assert! ( e.contains ( " no such bookmark" ) , " {e}" ) ;
9612
}
72484195 Andy McCall 1 month ago ↶
9613
157d02e9 Andy McCall 10 days ago ↶
9614
9615
9616
72484195 Andy McCall 1 month ago ↶
9617
# [ test ]
157d02e9 Andy McCall 10 days ago ↶
9618
fn a_replace_conflict_is_settled_from_the_working_copy ( ) {
9619
hermetic ( ) ;
9620
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9621
let root = dir.path ( ) ;
9622
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9623
let blob = | n : u8 | {
9624
let mut v = vec! [ 0 u8 ; 64 ] ;
9625
v[ 1 ] = n;
9626
v
9627
} ;
9628
write ( root, " img.bin" , & blob ( 1 ) ) ;
9629
write ( root, " a.txt" , b " a\n " ) ;
9630
repo.commit ( " add img" , & [ ] ) .unwrap ( ) ;
9631
repo.bookmark_create ( " other" , None ) .unwrap ( ) ;
9632
write ( root, " img.bin" , & blob ( 2 ) ) ;
9633
repo.commit ( " img two" , & [ ] ) .unwrap ( ) ;
9634
repo.bookmark_switch ( " other" , false ) .unwrap ( ) ;
9635
write ( root, " img.bin" , & blob ( 3 ) ) ;
9636
repo.commit ( " img three" , & [ ] ) .unwrap ( ) ;
9637
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
9638
repo.merge ( " other" ) .unwrap ( ) ;
9639
9640
9641
write ( root, " img.bin" , & blob ( 9 ) ) ;
9642
write ( root, " a.txt" , b " a\n more\n " ) ;
9643
assert! (
9644
repo.commit ( " edit a" , & [ ] ) .is_err ( ) ,
9645
" refused while conflicted"
9646
) ;
9647
let snap = repo.snapshot ( " oxy resolve" ) .unwrap ( ) ;
9648
assert! (
9649
repo.choose_resolutions ( & snap.base_snapshot, & [ " a.txt" .to_string ( ) ] )
9650
.is_err ( ) ,
9651
" a.txt has no replace conflict"
9652
) ;
9653
repo.choose_resolutions ( & snap.base_snapshot, & [ " img.bin" .to_string ( ) ] )
9654
.unwrap ( ) ;
9655
let snap = repo.snapshot ( " oxy resolve" ) .unwrap ( ) ;
9656
assert! ( snap
9657
.statuses
9658
.iter ( )
9659
.any ( | s | s.path = = " img.bin" & & s.status = = ChangeStatus:: Resolved ) ) ;
9660
let c = repo.commit ( " settle img" , & [ ] ) .unwrap ( ) ;
9661
assert_eq! (
9662
repo.store ( ) .get_change ( & c) .unwrap ( ) .kind,
9663
ChangeKind:: Resolution
9664
) ;
9665
let after = repo
9666
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
9667
.unwrap ( )
9668
.materialize ( ) ;
9669
assert! ( ! after.is_conflicted ( ) ) ;
9670
assert_eq! (
9671
after.files[ " img.bin" ] .content,
9672
FileContent:: Opaque {
9673
blobs: vec! [ Repo:: blob_ref( & blob ( 9 ) ) ]
9674
}
9675
) ;
9676
assert! (
9677
matches! ( & after.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n more\n " )
9678
) ;
9679
9680
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
9681
assert! ( snap.statuses.is_empty ( ) , " {:?}" , snap.statuses) ;
9682
assert! ( repo.resolutions ( ) .unwrap ( ) .is_empty ( ) ) ;
9683
}
9684
9685
9686
# [ test ]
9687
fn a_pin_conflict_is_settled_from_the_working_copy ( ) {
9688
hermetic ( ) ;
9689
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9690
let root = dir.path ( ) ;
9691
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9692
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9693
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9694
.unwrap ( )
9695
.with_home ( root.join ( " home" ) ) ;
9696
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9697
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9698
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9699
let app_dir = root.join ( " app" ) ;
9700
let mut app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9701
write ( & app_dir, " main.txt" , b " app\n " ) ;
9702
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9703
app.commit ( " pin v1" , & [ ] ) .unwrap ( ) ;
9704
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) )
9705
.unwrap ( )
9706
.with_home ( root.join ( " home" ) ) ;
9707
fn pin ( app : & Repo, nested : & Repo, text : & [u8 ], message : & str ) {
9708
write ( nested.root ( ) , " lib.txt" , text) ;
9709
nested.commit ( message, & [ ] ) .unwrap ( ) ;
9710
app.commit ( & format! ( " pin {message} " ) , & [ ] ) .unwrap ( ) ;
9711
}
9712
app.bookmark_create ( " other" , None ) .unwrap ( ) ;
9713
pin ( & app, & nested, b " v2\n " , " v2" ) ;
9714
app.bookmark_switch ( " other" , false ) .unwrap ( ) ;
9715
pin ( & app, & nested, b " v3\n " , " v3" ) ;
9716
app.bookmark_switch ( " main" , false ) .unwrap ( ) ;
9717
app.merge ( " other" ) .unwrap ( ) ;
9718
let merged = app
9719
.load_state ( & app.bookmark_heads ( ) .unwrap ( ) )
9720
.unwrap ( )
9721
.materialize ( ) ;
9722
assert! (
9723
matches! ( & merged.files[ " libs/lib" ] .content, FileContent:: Opaque { blobs } if blobs.len ( ) = = 2 ) ,
9724
" a pin conflict"
9725
) ;
9726
9727
write ( & app_dir.join ( " libs/lib" ) , " lib.txt" , b " v4\n " ) ;
9728
nested.commit ( " v4" , & [ ] ) .unwrap ( ) ;
9729
let snap = app.snapshot ( " oxy resolve" ) .unwrap ( ) ;
9730
app.choose_resolutions ( & snap.base_snapshot, & [ " libs/lib" .to_string ( ) ] )
9731
.unwrap ( ) ;
9732
let c = app.commit ( " settle the pin" , & [ ] ) .unwrap ( ) ;
9733
assert_eq! (
9734
app.store ( ) .get_change ( & c) .unwrap ( ) .kind,
9735
ChangeKind:: Resolution
9736
) ;
9737
let after = app
9738
.load_state ( & app.bookmark_heads ( ) .unwrap ( ) )
9739
.unwrap ( )
9740
.materialize ( ) ;
9741
assert! ( ! after.is_conflicted ( ) ) ;
9742
assert! ( app.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
9743
}
9744
9745
# [ test ]
72484195 Andy McCall 1 month ago ↶
9746
fn carrying_work_over_a_subrepo_already_at_the_target_pin_is_no_conflict ( ) {
9747
9748
9749
hermetic ( ) ;
9750
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9751
let root = dir.path ( ) ;
9752
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9753
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9754
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9755
.unwrap ( )
9756
.with_home ( root.join ( " home" ) ) ;
9757
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9758
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9759
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9760
let app_dir = root.join ( " app" ) ;
9761
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9762
write ( & app_dir, " main.txt" , b " app\n " ) ;
9763
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9764
9765
fs:: rename( app_dir.join ( " libs/lib" ) , root.join ( " aside" ) ) .unwrap ( ) ;
9766
app.commit ( " initial" , & [ ] ) .unwrap ( ) ;
9767
let stale = app.bookmark_heads ( ) .unwrap ( ) ;
9768
fs:: rename( root.join ( " aside" ) , app_dir.join ( " libs/lib" ) ) .unwrap ( ) ;
9769
app.commit ( " pin lib" , & [ ] ) .unwrap ( ) ;
9770
let newer = app.bookmark_heads ( ) .unwrap ( ) ;
9771
app.checkout ( & stale, false , " test" ) .unwrap ( ) ;
9772
write ( & app_dir, " notes.txt" , b " mine\n " ) ;
9773
let ( _ , carried) = app.checkout_carry ( & newer, " main" , " test" ) .unwrap ( ) ;
9774
assert_eq! ( carried, vec! [ " notes.txt" .to_string ( ) ] ) ;
9775
assert! ( app_dir.join ( " notes.txt" ) .exists ( ) ) ;
9776
}
9777
c8d9059e Andy McCall 10 days ago ↶
9778
9779
72484195 Andy McCall 1 month ago ↶
9780
# [ test ]
c8d9059e Andy McCall 10 days ago ↶
9781
fn a_new_bookmark_sends_only_what_the_remote_lacks ( ) {
9782
hermetic ( ) ;
9783
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9784
let root = dir.path ( ) ;
9785
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
9786
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
9787
let mut one = Repo:: clone( & url, & root.join ( " one" ) )
9788
.unwrap ( )
9789
.with_home ( root.join ( " home" ) ) ;
9790
for n in 0 .. 5 {
9791
write ( & root.join ( " one" ) , " a.txt" , format! ( " {n} \n " ) .as_bytes ( ) ) ;
9792
one.commit ( & format! ( " main {n} " ) , & [ ] ) .unwrap ( ) ;
9793
}
9794
assert! ( one.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9795
one.bookmark_create ( " feature" , None ) .unwrap ( ) ;
9796
one.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
9797
write ( & root.join ( " one" ) , " b.txt" , b " feature\n " ) ;
9798
let added = one.commit ( " feature" , & [ ] ) .unwrap ( ) ;
9799
let heads = one.bookmark_heads ( ) .unwrap ( ) ;
9800
let refs = one
9801
.open_named_remote ( " origin" )
9802
.unwrap ( )
9803
.refs ( )
9804
.unwrap ( )
9805
.refs;
9806
assert! (
9807
refs.iter ( ) .all ( | r | r.name ! = " feature" ) ,
9808
" new to the remote"
9809
) ;
9810
let sent = one
9811
.negotiate ( & one.remote_holds ( & refs, & [ ] ) , & heads)
9812
.unwrap ( ) ;
9813
assert_eq! ( sent, vec! [ added] , " only the change main lacks" ) ;
9814
assert! ( one.push_bookmark ( " origin" , " feature" , false ) .unwrap ( ) [ 0 ] .ok) ;
9815
let two = Repo:: clone( & url, & root.join ( " two" ) ) .unwrap ( ) ;
9816
assert! ( two.store ( ) .has ( & added) ) ;
9817
}
9818
9819
# [ test ]
72484195 Andy McCall 1 month ago ↶
9820
fn a_bookmark_only_behind_its_remote_catches_up ( ) {
9821
9822
hermetic ( ) ;
9823
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9824
let root = dir.path ( ) ;
9825
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
9826
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
9827
let one = Repo:: clone( & url, & root.join ( " one" ) )
9828
.unwrap ( )
9829
.with_home ( root.join ( " home" ) ) ;
9830
write ( & root.join ( " one" ) , " a.txt" , b " a\n " ) ;
9831
one.commit ( " A" , & [ ] ) .unwrap ( ) ;
9832
assert! ( one.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9833
let two = Repo:: clone( & url, & root.join ( " two" ) )
9834
.unwrap ( )
9835
.with_home ( root.join ( " home" ) ) ;
9836
write ( & root.join ( " two" ) , " b.txt" , b " b\n " ) ;
9837
two.commit ( " B" , & [ ] ) .unwrap ( ) ;
9838
assert! ( two.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9839
assert_eq! ( one.catch_up_bookmark ( " main" ) .unwrap ( ) , None , " not fetched" ) ;
9840
one.fetch ( " origin" ) .unwrap ( ) ;
9841
assert_eq! ( one.catch_up_bookmark ( " main" ) .unwrap ( ) , Some ( 1 ) ) ;
9842
assert_eq! ( one.bookmarks ( ) .unwrap ( ) [ 0 ] .behind, Some ( 0 ) ) ;
9843
9844
write ( & root.join ( " one" ) , " c.txt" , b " c\n " ) ;
9845
one.commit ( " C" , & [ ] ) .unwrap ( ) ;
9846
write ( & root.join ( " two" ) , " d.txt" , b " d\n " ) ;
9847
two.commit ( " D" , & [ ] ) .unwrap ( ) ;
9848
assert! ( two.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9849
one.fetch ( " origin" ) .unwrap ( ) ;
9850
assert_eq! ( one.catch_up_bookmark ( " main" ) .unwrap ( ) , None ) ;
9851
}
42ccf504 Andy McCall 27 days ago ↶
9852
9853
# [ test ]
9854
fn a_bookmark_is_compared_with_a_remote_that_has_it_when_there_is_no_origin ( ) {
9855
9856
hermetic ( ) ;
9857
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9858
let root = dir.path ( ) ;
9859
let _up = Repo:: init( & root.join ( " up" ) ) .unwrap ( ) ;
9860
let url = root.join ( " up" ) .to_string_lossy ( ) .to_string ( ) ;
9861
let other = Repo:: clone( & url, & root.join ( " other" ) )
9862
.unwrap ( )
9863
.with_home ( root.join ( " home" ) ) ;
9864
write ( & root.join ( " other" ) , " a.txt" , b " a\n " ) ;
9865
other.commit ( " a" , & [ ] ) .unwrap ( ) ;
9866
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9867
let mut one = Repo:: clone( & url, & root.join ( " one" ) )
9868
.unwrap ( )
9869
.with_home ( root.join ( " home" ) ) ;
9870
one.config.set ( " remote.earth.url" , & url) .unwrap ( ) ;
9871
one.config.set ( " remote.gcs.url" , & url) .unwrap ( ) ;
9872
one.config.set ( " remote.origin.url" , " " ) .unwrap ( ) ;
9873
one.save_config ( ) .unwrap ( ) ;
9874
write ( & root.join ( " other" ) , " b.txt" , b " b\n " ) ;
9875
other.commit ( " b" , & [ ] ) .unwrap ( ) ;
9876
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9877
one.fetch ( " earth" ) .unwrap ( ) ;
9878
let b = one
9879
.bookmarks ( )
9880
.unwrap ( )
9881
.into_iter ( )
9882
.find ( | b | b.name = = " main" )
9883
.unwrap ( ) ;
9884
assert_eq! (
9885
( b.remote.as_deref ( ) , b.behind) ,
9886
( Some ( " earth" ) , Some ( 1 ) ) ,
9887
" a stale origin copy is not used"
9888
) ;
9889
assert_eq! (
9890
one.default_remote ( ) ,
9891
" origin" ,
9892
" no default chosen among several"
9893
) ;
9894
let e = one.pull ( & one.default_remote ( ) ) .unwrap_err ( ) .to_string ( ) ;
9895
assert! ( e.contains ( " earth, gcs" ) & & e.contains ( " core.remote" ) , " {e}" ) ;
9896
one.config.set ( " core.remote" , " earth" ) .unwrap ( ) ;
9897
assert_eq! ( one.default_remote ( ) , " earth" ) ;
9898
}
cbe8499a Andy McCall 27 days ago ↶
9899
9900
# [ test ]
9901
fn a_missing_author_identity_says_how_to_fix_it ( ) {
9902
9903
9904
hermetic ( ) ;
9905
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9906
let mut repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9907
let missing = KeyId:: from_hex( & " ab" .repeat ( 32 ) ) .unwrap ( ) ;
9908
repo.config.set ( " author.key" , & missing.to_hex ( ) ) .unwrap ( ) ;
9909
let e = repo.identity ( & missing) .unwrap_err ( ) .to_string ( ) ;
9910
assert! (
9911
e.contains ( " this repository has no identity" ) & & e.contains ( " oxy config author.key" ) ,
9912
" {e}"
9913
) ;
9914
assert! ( e.contains ( " this repository's config" ) , " {e}" ) ;
9915
}
f6dccc45 Andy McCall 27 days ago ↶
9916
9917
# [ test ]
9918
fn a_working_copy_behind_its_bookmark_is_flagged_and_pull_brings_it_up ( ) {
9919
9920
9921
9922
hermetic ( ) ;
9923
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9924
let root = dir.path ( ) ;
9925
let _up = Repo:: init( & root.join ( " up" ) ) .unwrap ( ) ;
9926
let url = root.join ( " up" ) .to_string_lossy ( ) .to_string ( ) ;
9927
let other = Repo:: clone( & url, & root.join ( " other" ) )
9928
.unwrap ( )
9929
.with_home ( root.join ( " home" ) ) ;
9930
write ( & root.join ( " other" ) , " a.txt" , b " a\n " ) ;
9931
other.commit ( " a" , & [ ] ) .unwrap ( ) ;
9932
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9933
let one = Repo:: clone( & url, & root.join ( " one" ) )
9934
.unwrap ( )
9935
.with_home ( root.join ( " home" ) ) ;
9936
write ( & root.join ( " other" ) , " b.txt" , b " b\n " ) ;
9937
other.commit ( " b" , & [ ] ) .unwrap ( ) ;
9938
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9939
one.fetch ( " origin" ) .unwrap ( ) ;
9940
9941
let theirs = one.resolve_heads ( " origin/main" ) .unwrap ( ) ;
9942
one.bookmark_set ( " main" , & theirs) .unwrap ( ) ;
9943
assert! ( ! root.join ( " one/b.txt" ) .exists ( ) ) ;
9944
assert_eq! ( one.working_copy_behind ( ) .unwrap ( ) , Some ( 1 ) ) ;
9945
let r = one.pull ( " origin" ) .unwrap ( ) ;
9946
assert! (
9947
! r.changed & & r.checked_out,
9948
" nothing new, but the working copy caught up"
9949
) ;
9950
assert! ( root.join ( " one/b.txt" ) .exists ( ) ) ;
9951
assert_eq! ( one.working_copy_behind ( ) .unwrap ( ) , None ) ;
9952
}
170bb436 Andy McCall 26 days ago ↶
9953
9954
# [ test ]
9955
fn touched_nodes_cache_answers_as_the_changes_do ( ) {
9956
hermetic ( ) ;
9957
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9958
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9959
write ( dir.path ( ) , " a.txt" , b " one\n " ) ;
9960
write ( dir.path ( ) , " b.txt" , b " x\n " ) ;
9961
repo.snapshot ( " t" ) .unwrap ( ) ;
9962
let c1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
9963
write ( dir.path ( ) , " b.txt" , b " y\n " ) ;
9964
repo.snapshot ( " t" ) .unwrap ( ) ;
9965
repo.commit ( " b only" , & [ ] ) .unwrap ( ) ;
9966
fs:: rename( dir.path ( ) .join ( " a.txt" ) , dir.path ( ) .join ( " c.txt" ) ) .unwrap ( ) ;
9967
write ( dir.path ( ) , " c.txt" , b " one\n two\n " ) ;
9968
repo.snapshot ( " t" ) .unwrap ( ) ;
9969
let c3 = repo.commit ( " rename and edit" , & [ ] ) .unwrap ( ) ;
9970
9971
write ( dir.path ( ) , " c.txt" , b " one\n two\n three\n " ) ;
9972
repo.snapshot ( " t" ) .unwrap ( ) ;
9973
9974
let path = dir.path ( ) .join ( " .oxy/index/touched" ) ;
9975
let hashes = | es : Vec< LogEntry> | es.iter ( ) .map ( | e | e.hash ) .collect:: < Vec< _ > > ( ) ;
9976
let cold = hashes ( repo.log_for_path ( " c.txt" ) .unwrap ( ) ) ;
9977
assert! ( path.exists ( ) , " the first query writes the cache" ) ;
9978
let warm = hashes ( repo.log_for_path ( " c.txt" ) .unwrap ( ) ) ;
9979
assert_eq! ( cold, warm, " the cache answers as the changes do" ) ;
9980
let ( wc, _ ) = repo.working_copy ( ) .unwrap ( ) ;
9981
assert_eq! (
9982
cold,
9983
vec! [ wc, c3, c1] ,
9984
" renames followed, the working copy included"
9985
) ;
9986
9987
9988
let cached = super :: touched:: read_touched_for_test( & fs:: read( & path) .unwrap ( ) ) .unwrap ( ) ;
9989
assert! ( ! cached.contains ( & wc) , " a working-copy change is not cached" ) ;
9990
assert! ( cached.contains ( & c1) & & cached.contains ( & c3) ) ;
9991
9992
9993
let mut bytes = fs:: read( & path) .unwrap ( ) ;
9994
let last = bytes.len ( ) - 40 ;
9995
bytes[ last] ^ = 1 ;
9996
fs:: write( & path, & bytes) .unwrap ( ) ;
9997
assert_eq! ( hashes ( repo.log_for_path ( " c.txt" ) .unwrap ( ) ) , cold) ;
9998
let touches = repo.log_revset ( " touches(\" c.txt\" ) ~ @" ) .unwrap ( ) ;
9999
assert_eq! ( hashes ( touches) , vec! [ c3, c1] ) ;
10000
}
e8e6e0ca Andy McCall 26 days ago ↶
10001
10002
10003
10004
10005
10006
# [ test ]
10007
fn git_import_removes_every_copy_of_a_file_at_one_place ( ) {
10008
hermetic ( ) ;
10009
if std:: process:: Command:: new( " git" )
10010
.arg ( " --version" )
10011
.output ( )
10012
.is_err ( )
10013
{
10014
eprintln! ( " git not installed; skipping" ) ;
10015
return ;
10016
}
10017
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10018
let root = dir.path ( ) ;
10019
let g = root.join ( " src.git" ) ;
10020
fs:: create_dir_all( & g) .unwrap ( ) ;
10021
let git = | args : & [& str ]| {
10022
let out = std:: process:: Command:: new( " git" )
10023
.arg ( " -C" )
10024
.arg ( & g)
10025
.args ( [
10026
" -c" ,
10027
" user.name=Grace" ,
10028
" -c" ,
10029
" [email protected] " ,
10030
" -c" ,
10031
" commit.gpgsign=false" ,
10032
] )
10033
.args ( args)
10034
.output ( )
10035
.unwrap ( ) ;
10036
assert! (
10037
out.status.success ( ) ,
10038
" git {args:?}: {}" ,
10039
String :: from_utf8_lossy( & out.stderr)
10040
) ;
10041
} ;
10042
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
10043
write ( & g, " d/list.txt" , b " one\n two\n three\n four\n " ) ;
10044
write ( & g, " keep.txt" , b " k\n " ) ;
10045
git ( & [ " add" , " ." ] ) ;
10046
git ( & [ " commit" , " -qm" , " base" ] ) ;
10047
fs:: create_dir_all( g.join ( " d/sub" ) ) .unwrap ( ) ;
10048
git ( & [ " mv" , " d/list.txt" , " d/sub/list.txt" ] ) ;
10049
git ( & [ " commit" , " -qm" , " into sub" ] ) ;
10050
git ( & [ " checkout" , " -qb" , " side" ] ) ;
10051
git ( & [ " mv" , " d/sub/list.txt" , " d/list.txt" ] ) ;
10052
git ( & [ " commit" , " -qm" , " back, on side" ] ) ;
10053
git ( & [ " checkout" , " -q" , " main" ] ) ;
10054
git ( & [ " mv" , " d/sub/list.txt" , " d/list.txt" ] ) ;
10055
git ( & [ " commit" , " -qm" , " back, on main" ] ) ;
10056
git ( & [ " merge" , " -q" , " --no-edit" , " side" ] ) ;
10057
git ( & [ " rm" , " -rq" , " d" ] ) ;
10058
git ( & [ " commit" , " -qm" , " d goes" ] ) ;
10059
10060
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
10061
repo.import_git ( & g, " main" ) .unwrap ( ) ;
10062
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
10063
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
10064
let st = repo
10065
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
10066
.unwrap ( ) ;
10067
let view = st.tree ( ) .view ( ) ;
10068
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
10069
assert_eq! ( view.entries.keys ( ) .collect:: < Vec< _ > > ( ) , vec! [ " keep.txt" ] ) ;
10070
}
5552e69c Andy McCall 26 days ago ↶
10071
10072
10073
10074
10075
# [ test ]
10076
fn a_damaged_file_history_in_the_state_index_is_rebuilt ( ) {
10077
hermetic ( ) ;
10078
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10079
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
10080
for i in 0 .. 6 {
10081
write (
10082
dir.path ( ) ,
10083
& format! ( " f{i} .txt" ) ,
10084
format! ( " one {i} \n " ) .as_bytes ( ) ,
10085
) ;
10086
}
10087
repo.snapshot ( " t" ) .unwrap ( ) ;
10088
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
10089
for i in 0 .. 6 {
10090
write (
10091
dir.path ( ) ,
10092
& format! ( " f{i} .txt" ) ,
10093
format! ( " one {i} \n two\n " ) .as_bytes ( ) ,
10094
) ;
10095
}
10096
repo.snapshot ( " t" ) .unwrap ( ) ;
10097
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
10098
let want: Vec< Vec< u8 > > = ( 0 .. 6 )
10099
.map ( | i | format! ( " one {i} \n two\n " ) .into_bytes ( ) )
10100
.collect ( ) ;
10101
10102
10103
let ( _ , wc) = repo.working_copy ( ) .unwrap ( ) ;
10104
let base = repo.load_state_history ( & wc.deps) .unwrap ( ) ;
10105
repo.write_state_index ( & wc.deps, & base) ;
10106
for ( i, w) in want.iter ( ) .enumerate ( ) {
10107
assert_eq! ( & repo.file_at ( & format! ( " f{i} .txt" ) , None ) .unwrap ( ) , w) ;
10108
}
10109
let index = repo.state_indexes ( ) ;
10110
assert! ( ! index.is_empty ( ) , " a state index was written" ) ;
10111
10112
let mut bytes = fs:: read( & index[ 0 ] ) .unwrap ( ) ;
10113
let last = bytes.len ( ) - 1 ;
10114
bytes[ last] ^ = 0x40 ;
10115
fs:: write( & index[ 0 ] , & bytes) .unwrap ( ) ;
10116
10117
let reads: Vec< _ > = ( 0 .. 6 )
10118
.map ( | i | repo.file_at ( & format! ( " f{i} .txt" ) , None ) )
10119
.collect ( ) ;
10120
let damaged: Vec< String> = reads
10121
.iter ( )
10122
.filter_map ( | r | r.as_ref ( ) .err ( ) .map ( | e | e.to_string ( ) ) )
10123
.collect ( ) ;
10124
assert_eq! ( damaged.len ( ) , 1 , " {damaged:?}" ) ;
10125
assert! (
10126
damaged[ 0 ] .contains ( " state index was damaged" )
10127
& & damaged[ 0 ] .contains ( " run the command again" ) ,
10128
" {}" ,
10129
damaged[ 0 ]
10130
) ;
10131
10132
for ( i, w) in want.iter ( ) .enumerate ( ) {
10133
assert_eq! ( & repo.file_at ( & format! ( " f{i} .txt" ) , None ) .unwrap ( ) , w) ;
10134
}
10135
}
cb8b8082 Andy McCall 26 days ago ↶
10136
10137
10138
10139
10140
10141
# [ test ]
10142
fn a_long_push_goes_in_parts ( ) {
10143
hermetic ( ) ;
10144
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10145
let root = dir.path ( ) ;
10146
let origin = root.join ( " origin" ) ;
10147
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
10148
let main = root.join ( " main" ) ;
10149
let mut repo = Repo:: init( & main) .unwrap ( ) ;
10150
repo.config
10151
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
10152
.unwrap ( ) ;
10153
10154
repo.config.set ( " push.batch-bytes" , " 1" ) .unwrap ( ) ;
10155
repo.save_config ( ) .unwrap ( ) ;
10156
for i in 0 .. 6 {
10157
write (
10158
& main,
10159
& format! ( " f{i} .txt" ) ,
10160
format! ( " file {i} \n " ) .as_bytes ( ) ,
10161
) ;
10162
repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ;
10163
}
10164
let mut parts = Vec :: new( ) ;
10165
let results = repo
10166
.push_bookmark_with ( " origin" , " main" , false , & mut | m| parts.push ( m.to_string ( ) ) )
10167
.unwrap ( ) ;
10168
assert! (
10169
results.iter ( ) .any ( | r | r.ok & & r.name = = " main" ) ,
10170
" {results:?}"
10171
) ;
10172
assert_eq! ( parts.len ( ) , 6 , " {parts:?}" ) ;
10173
assert_eq! ( parts[ 0 ] , " part 1 of 6: 1 changes" ) ;
10174
10175
let remote = Repo:: open( & origin) .unwrap ( ) ;
10176
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
10177
assert_eq! ( remote.bookmark_heads ( ) .unwrap ( ) , heads) ;
10178
assert_eq! (
10179
remote.log_state ( & heads) .unwrap ( ) .len ( ) ,
10180
repo.log_state ( & heads) .unwrap ( ) .len ( )
10181
) ;
10182
10183
let got = remote
10184
.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ ] )
10185
.unwrap ( ) ;
10186
assert_eq! ( got.len ( ) , 1 ) ;
10187
assert! ( got[ 0 ] .ok & & got[ 0 ] .name = = OBJECTS_RESULT , " {got:?}" ) ;
10188
}
d6e0d5b8 Andy McCall 25 days ago ↶
10189
d3b27090 Andy McCall 11 days ago ↶
10190
10191
10192
10193
10194
# [ test ]
10195
fn a_heavy_change_sends_its_blobs_in_parts ( ) {
10196
hermetic ( ) ;
10197
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10198
let root = dir.path ( ) ;
10199
let origin = root.join ( " origin" ) ;
10200
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
10201
let main = root.join ( " main" ) ;
10202
let mut repo = Repo:: init( & main) .unwrap ( ) ;
10203
repo.config
10204
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
10205
.unwrap ( ) ;
10206
repo.config.set ( " push.batch-bytes" , " 50000" ) .unwrap ( ) ;
10207
repo.save_config ( ) .unwrap ( ) ;
10208
let mut x: u64 = 0x9e3779b97f4a7c15 ;
10209
let mut noise = | n : usize | -> Vec< u8 > {
10210
( 0 .. n)
10211
.map ( | _| {
10212
x ^ = x < < 13 ;
10213
x ^ = x > > 7 ;
10214
x ^ = x < < 17 ;
10215
( x > > 24 ) as u8
10216
} )
10217
.collect ( )
10218
} ;
10219
let files: Vec< Vec< u8 > > = ( 0 .. 4 ) .map ( | _| noise ( 40_000 ) ) .collect ( ) ;
10220
for ( i, f) in files.iter ( ) .enumerate ( ) {
10221
write ( & main, & format! ( " bin{i} .dat" ) , f) ;
10222
}
10223
repo.commit ( " four blobs" , & [ ] ) .unwrap ( ) ;
10224
let mut parts = Vec :: new( ) ;
10225
let results = repo
10226
.push_bookmark_with ( " origin" , " main" , false , & mut | m| parts.push ( m.to_string ( ) ) )
10227
.unwrap ( ) ;
10228
assert! (
10229
results.iter ( ) .any ( | r | r.ok & & r.name = = " main" ) ,
10230
" {results:?}"
10231
) ;
10232
assert! (
10233
parts.len ( ) > = 3 & & parts[ 0 ] .starts_with ( " objects 1 of" ) ,
10234
" {parts:?}"
10235
) ;
10236
let back = Repo:: clone( & origin.to_string_lossy ( ) , & root.join ( " back" ) ) .unwrap ( ) ;
10237
for ( i, f) in files.iter ( ) .enumerate ( ) {
10238
assert_eq! ( & fs:: read( root.join ( format! ( " back/bin{i} .dat" ) ) ) .unwrap ( ) , f) ;
10239
}
10240
drop ( back) ;
10241
}
10242
570e3531 Andy McCall 11 days ago ↶
10243
10244
10245
10246
10247
# [ test ]
10248
fn a_replacement_does_not_send_the_old_content_again ( ) {
10249
hermetic ( ) ;
10250
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10251
let root = dir.path ( ) ;
10252
let origin = root.join ( " origin" ) ;
10253
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
10254
let main = root.join ( " main" ) ;
10255
let mut repo = Repo:: init( & main) .unwrap ( ) ;
10256
repo.config
10257
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
10258
.unwrap ( ) ;
10259
repo.save_config ( ) .unwrap ( ) ;
10260
let blobs = | b : & Bundle| {
10261
b.objects
10262
.iter ( )
10263
.filter ( | ( ty , _) | * ty = = ObjectType:: Blob)
10264
.count ( )
10265
} ;
10266
write ( & main, " bin.dat" , b " \0 \x01 first binary" ) ;
10267
write ( & main, " gone.dat" , b " \0 \x02 to be removed" ) ;
10268
let added = repo.commit ( " add" , & [ ] ) .unwrap ( ) ;
10269
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10270
write ( & main, " bin.dat" , b " \0 \x01 second binary" ) ;
10271
fs:: remove_file( main.join ( " gone.dat" ) ) .unwrap ( ) ;
10272
let changed = repo.commit ( " replace and remove" , & [ ] ) .unwrap ( ) ;
10273
assert_eq! ( blobs ( & repo.bundle_for ( & [ changed] ) .unwrap ( ) ) , 1 ) ;
10274
assert_eq! ( blobs ( & repo.bundle_for ( & [ added, changed] ) .unwrap ( ) ) , 3 ) ;
10275
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10276
let origin = Repo:: open( & origin) .unwrap ( ) ;
10277
assert_eq! ( origin.fsck ( ) .unwrap ( ) .problems, vec! [ ] ) ;
10278
let back = Repo:: clone( & origin.root ( ) .to_string_lossy ( ) , & root.join ( " back" ) ) .unwrap ( ) ;
10279
assert_eq! ( back.fsck ( ) .unwrap ( ) .problems, vec! [ ] ) ;
10280
assert_eq! (
10281
fs:: read( root.join ( " back/bin.dat" ) ) .unwrap ( ) ,
10282
b " \0 \x01 second binary"
10283
) ;
10284
assert! ( ! root.join ( " back/gone.dat" ) .exists ( ) ) ;
10285
}
10286
d6e0d5b8 Andy McCall 25 days ago ↶
10287
10288
10289
10290
# [ test ]
10291
fn git_import_claims_your_own_commits ( ) {
10292
hermetic ( ) ;
10293
if std:: process:: Command:: new( " git" )
10294
.arg ( " --version" )
10295
.output ( )
10296
.is_err ( )
10297
{
10298
eprintln! ( " git not installed; skipping" ) ;
10299
return ;
10300
}
10301
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10302
let root = dir.path ( ) ;
10303
let g = root.join ( " src.git" ) ;
10304
fs:: create_dir_all( & g) .unwrap ( ) ;
10305
let git = | who : & str , args : & [& str ]| {
10306
let ( name, email) = who.split_once ( ' ' ) .unwrap ( ) ;
10307
let out = std:: process:: Command:: new( " git" )
10308
.arg ( " -C" )
10309
.arg ( & g)
10310
.args ( [
10311
" -c" ,
10312
& format! ( " user.name={name} " ) ,
10313
" -c" ,
10314
& format! ( " user.email={email} " ) ,
10315
" -c" ,
10316
" commit.gpgsign=false" ,
10317
] )
10318
.args ( args)
10319
.output ( )
10320
.unwrap ( ) ;
10321
assert! (
10322
out.status.success ( ) ,
10323
" git {args:?}: {}" ,
10324
String :: from_utf8_lossy( & out.stderr)
10325
) ;
10326
} ;
10327
git ( " Grace [email protected] " , & [ " init" , " -q" , " -b" , " main" ] ) ;
10328
write ( & g, " a.txt" , b " a\n " ) ;
10329
git ( " Grace [email protected] " , & [ " add" , " ." ] ) ;
10330
git ( " Grace [email protected] " , & [ " commit" , " -qm" , " mine" ] ) ;
10331
write ( & g, " b.txt" , b " b\n " ) ;
10332
git ( " Ada [email protected] " , & [ " add" , " ." ] ) ;
10333
git ( " Ada [email protected] " , & [ " commit" , " -qm" , " someone else's" ] ) ;
10334
10335
let mut repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
10336
let ( key, _ ) = repo
10337
.create_identity ( " Grace" , " [email protected] " , IdentityScope:: Repo)
10338
.unwrap ( ) ;
10339
repo.config.set ( " git.claim" , " true" ) .unwrap ( ) ;
10340
repo.save_config ( ) .unwrap ( ) ;
10341
let repo = Repo:: open( & root.join ( " o" ) ) .unwrap ( ) ;
10342
repo.import_git ( & g, " main" ) .unwrap ( ) ;
10343
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
10344
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
10345
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
10346
let by_message: std:: collections:: HashMap< String, KeyId> = repo
10347
.log_state ( & heads)
10348
.unwrap ( )
10349
.into_iter ( )
10350
.map ( | e | {
10351
(
10352
e.change.message.lines ( ) .next ( ) .unwrap_or ( " " ) .to_string ( ) ,
10353
e.change.author,
10354
)
10355
} )
10356
.collect ( ) ;
10357
assert_eq! ( by_message[ " mine" ] , key, " her own commit, any case of email" ) ;
10358
assert_eq! ( by_message[ " someone else's" ] , ObjectRef:: ZERO , " not hers" ) ;
10359
}
34fad4a9 Andy McCall 25 days ago ↶
10360
10361
10362
10363
# [ test ]
10364
fn removing_a_remote_leaves_nothing_of_it ( ) {
10365
hermetic ( ) ;
10366
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10367
let origin = dir.path ( ) .join ( " origin" ) ;
10368
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
10369
let main = dir.path ( ) .join ( " main" ) ;
10370
let mut repo = Repo:: init( & main) .unwrap ( ) ;
10371
repo.config
10372
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
10373
.unwrap ( ) ;
10374
repo.config
10375
.set ( " remote.origin.token" , " secret-ish" )
10376
.unwrap ( ) ;
10377
repo.config
10378
.set ( " remote.keep.url" , & origin.to_string_lossy ( ) )
10379
.unwrap ( ) ;
10380
repo.save_config ( ) .unwrap ( ) ;
10381
write ( & main, " a.txt" , b " a\n " ) ;
10382
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10383
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10384
assert! (
10385
repo.view ( ) .unwrap ( ) .remotes.contains_key ( " origin" ) ,
10386
" a tracking bookmark"
10387
) ;
10388
10389
repo.remove_remote ( " origin" ) .unwrap ( ) ;
10390
let again = Repo:: open( & main) .unwrap ( ) ;
10391
assert! (
10392
! again
10393
.config
10394
.repo_layer ( )
10395
.keys ( )
10396
.any ( | k | k.starts_with ( " remote.origin." ) ) ,
10397
" {:?}" ,
10398
again.config.repo_layer ( )
10399
) ;
10400
assert! ( ! again.config.remotes.contains_key ( " origin" ) ) ;
10401
assert! (
10402
! again.view ( ) .unwrap ( ) .remotes.contains_key ( " origin" ) ,
10403
" its tracking bookmarks go"
10404
) ;
10405
assert! (
10406
again.config.remotes.contains_key ( " keep" ) ,
10407
" another remote stays"
10408
) ;
10409
let mut again = again;
10410
assert! (
10411
again.remove_remote ( " origin" ) .is_err ( ) ,
10412
" a second removal is refused"
10413
) ;
10414
}
9d580f21 Andy McCall 25 days ago ↶
10415
10416
10417
10418
10419
# [ test ]
10420
fn gc_keeps_the_blobs_a_host_names ( ) {
10421
hermetic ( ) ;
10422
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10423
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
10424
write ( dir.path ( ) , " a.txt" , b " a\n " ) ;
10425
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10426
10427
let body: Vec< u8 > = ( 0 .. 400_000 u32 )
10428
.flat_map ( | i | ( i.wrapping_mul ( 2654435761 ) ) .to_le_bytes ( ) )
10429
.collect ( ) ;
10430
let kept = repo.store_blob ( & body) .unwrap ( ) ;
10431
let lost = repo.store_blob ( b " an artifact nobody names" ) .unwrap ( ) ;
10432
repo.gc_keeping ( true , & [ kept] , & mut | _ | { } ) .unwrap ( ) ;
10433
assert_eq! ( repo.read_blob ( & kept) .unwrap ( ) , body, " named: kept whole" ) ;
10434
assert! ( ! repo.store ( ) .has ( & lost) , " not named: pruned" ) ;
10435
repo.gc ( true ) .unwrap ( ) ;
10436
assert! ( ! repo.store ( ) .has ( & kept) , " a plain gc prunes it" ) ;
10437
}
530a0fc6 Andy McCall 25 days ago ↶
10438
10439
10440
10441
10442
# [ test ]
10443
fn undo_is_scoped_to_its_workspace ( ) {
10444
hermetic ( ) ;
10445
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10446
let root = dir.path ( ) .join ( " main" ) ;
10447
let a = Repo:: init( & root) .unwrap ( ) ;
10448
write ( & root, " base.txt" , b " base\n " ) ;
10449
let base = a.commit ( " base" , & [ ] ) .unwrap ( ) ;
10450
a.bookmark_create ( " feature" , None ) .unwrap ( ) ;
10451
let ws_dir = dir.path ( ) .join ( " ws" ) ;
10452
let heads = a.resolve_heads ( " feature" ) .unwrap ( ) ;
10453
let b = a
10454
.workspace_add_on ( & ws_dir, Some ( & heads) , Some ( " feature" ) )
10455
.unwrap ( ) ;
10456
10457
10458
write ( & root, " a.txt" , b " a\n " ) ;
10459
let ca = a.commit ( " from a" , & [ ] ) .unwrap ( ) ;
10460
write ( & ws_dir, " b.txt" , b " b\n " ) ;
10461
let cb = b.commit ( " from b" , & [ ] ) .unwrap ( ) ;
10462
10463
10464
let u = a.undo ( ) .unwrap ( ) ;
10465
assert_eq! ( u.command, " oxy commit -m \" from a\" " ) ;
10466
assert_eq! ( u.others_kept, 1 , " B's newer commit is left" ) ;
10467
assert! ( ! u.changes.is_empty ( ) ) ;
10468
let view = a.view ( ) .unwrap ( ) ;
10469
10470
assert_eq! ( a.heads_of ( & view, " main" ) .unwrap ( ) , vec! [ base] ) ;
10471
assert! (
10472
a.heads_of ( & view, " feature" ) .unwrap ( ) .contains ( & cb) ,
10473
" B's commit stands"
10474
) ;
10475
assert! (
10476
a.store ( ) .get_change ( & ca) .is_ok ( ) ,
10477
" the change itself is still in the store"
10478
) ;
10479
10480
assert! ( ! root.join ( " a.txt" ) .exists ( ) ) ;
10481
assert! ( a.snapshot ( " oxy status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
10482
assert! ( ws_dir.join ( " b.txt" ) .exists ( ) ) ;
10483
10484
10485
let u = b.undo ( ) .unwrap ( ) ;
10486
assert_eq! ( u.command, " oxy commit -m \" from b\" " ) ;
10487
assert! ( ! b
10488
.heads_of ( & b.view ( ) .unwrap ( ) , " feature" )
10489
.unwrap ( )
10490
.contains ( & cb) ) ;
10491
assert_eq! (
10492
a.heads_of ( & a.view ( ) .unwrap ( ) , " main" ) .unwrap ( ) ,
10493
vec! [ base] ,
10494
" A's undo stands"
10495
) ;
10496
10497
10498
10499
let e = a.undo ( ) .unwrap_err ( ) .to_string ( ) ;
10500
assert! ( e.contains ( " the workspace at" ) & & e.contains ( " ws" ) , " {e}" ) ;
10501
}
10502
10503
10504
10505
# [ test ]
10506
fn undo_on_a_shared_bookmark_keeps_the_other_workspaces_head ( ) {
10507
hermetic ( ) ;
10508
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10509
let root = dir.path ( ) .join ( " main" ) ;
10510
let a = Repo:: init( & root) .unwrap ( ) ;
10511
write ( & root, " base.txt" , b " base\n " ) ;
10512
a.commit ( " base" , & [ ] ) .unwrap ( ) ;
10513
let ws_dir = dir.path ( ) .join ( " ws" ) ;
10514
let b = a.workspace_add ( & ws_dir, None ) .unwrap ( ) ;
10515
write ( & root, " a.txt" , b " a\n " ) ;
10516
let ca = a.commit ( " from a" , & [ ] ) .unwrap ( ) ;
10517
write ( & ws_dir, " b.txt" , b " b\n " ) ;
10518
let cb = b.commit ( " from b" , & [ ] ) .unwrap ( ) ;
10519
let u = a.undo ( ) .unwrap ( ) ;
10520
assert_eq! ( u.command, " oxy commit -m \" from a\" " ) ;
10521
let heads = a.heads_of ( & a.view ( ) .unwrap ( ) , " main" ) .unwrap ( ) ;
10522
assert! ( heads.contains ( & cb) & & ! heads.contains ( & ca) , " {heads:?}" ) ;
10523
}
10524
10525
10526
10527
# [ test ]
10528
fn undo_refuses_to_take_back_another_workspaces_work ( ) {
10529
hermetic ( ) ;
10530
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10531
let root = dir.path ( ) .join ( " main" ) ;
10532
let a = Repo:: init( & root) .unwrap ( ) ;
10533
write ( & root, " base.txt" , b " base\n " ) ;
10534
a.commit ( " base" , & [ ] ) .unwrap ( ) ;
10535
let ws_dir = dir.path ( ) .join ( " ws" ) ;
10536
let b = a.workspace_add ( & ws_dir, None ) .unwrap ( ) ;
10537
10538
write ( & root, " a.txt" , b " a\n " ) ;
10539
a.commit ( " from a" , & [ ] ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
10540
b.refresh ( ) .unwrap ( ) ;
530a0fc6 Andy McCall 25 days ago ↶
10541
let heads = b.resolve_heads ( " main" ) .unwrap ( ) ;
10542
b.checkout ( & heads, false , " oxy checkout" ) .unwrap ( ) ;
10543
write ( & ws_dir, " a.txt" , b " a, then b\n " ) ;
10544
b.commit ( " from b" , & [ ] ) .unwrap ( ) ;
10545
let e = a.undo ( ) .unwrap_err ( ) .to_string ( ) ;
10546
assert! (
10547
e.contains ( " bookmark main" ) & & e.contains ( " builds on" ) ,
10548
" {e}"
10549
) ;
10550
assert! ( e.contains ( " oxy op restore" ) , " {e}" ) ;
10551
}
10552
10553
10554
10555
# [ test ]
10556
fn undo_says_when_the_commit_was_pushed ( ) {
10557
hermetic ( ) ;
10558
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10559
let root = dir.path ( ) ;
10560
Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
10561
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
10562
let repo = Repo:: clone( & url, & root.join ( " work" ) )
10563
.unwrap ( )
10564
.with_home ( root.join ( " home" ) ) ;
10565
write ( & root.join ( " work" ) , " a.txt" , b " a\n " ) ;
10566
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10567
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10568
10569
let u = repo.undo ( ) .unwrap ( ) ;
10570
assert_eq! ( u.command, " oxy commit -m \" a\" " ) ;
10571
assert_eq! ( u.pushed, vec! [ ( " main" .to_string ( ) , " origin" .to_string ( ) ) ] ) ;
10572
}
b689779e Andy McCall 25 days ago ↶
10573
10574
10575
10576
10577
# [ test ]
10578
fn large_new_files_are_left_out_of_snapshots ( ) {
10579
hermetic ( ) ;
10580
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10581
let root = dir.path ( ) ;
10582
let mut repo = Repo:: init( root) .unwrap ( ) ;
10583
repo.config.max_new_file = 1024 ;
10584
write ( root, " small.txt" , b " small\n " ) ;
10585
write ( root, " big.bin" , & vec! [ 0 u8 ; 4096 ] ) ;
10586
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10587
let paths: Vec< _ > = s.statuses.iter ( ) .map ( | p | p.path.as_str ( ) ) .collect ( ) ;
10588
assert_eq! ( paths, [ " small.txt" ] ) ;
10589
assert_eq! ( repo.skipped_large ( ) , vec! [ ( " big.bin" .to_string ( ) , 4096 ) ] ) ;
10590
repo.commit ( " small" , & [ ] ) .unwrap ( ) ;
10591
10592
10593
write ( root, " small.txt" , & vec! [ b ' x' ; 4096 ] ) ;
10594
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10595
assert! ( s.statuses.iter ( ) .any ( | p | p.path = = " small.txt" ) ) ;
10596
10597
repo.config.max_new_file = 0 ;
10598
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10599
assert! ( s.statuses.iter ( ) .any ( | p | p.path = = " big.bin" ) ) ;
10600
repo.config.max_new_file = 1024 ;
10601
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10602
assert! (
10603
s.statuses.iter ( ) .any ( | p | p.path = = " big.bin" ) ,
10604
" already in the working copy"
10605
) ;
10606
assert! ( repo.skipped_large ( ) .is_empty ( ) ) ;
10607
}
10608
10609
10610
10611
# [ test ]
10612
fn gc_prunes_what_only_expired_snapshots_held ( ) {
10613
hermetic ( ) ;
10614
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10615
let root = dir.path ( ) ;
10616
let mut repo = Repo:: init( root) .unwrap ( ) ;
10617
repo.config.gc_grace_days = 0 ;
10618
write ( root, " a.txt" , b " a\n " ) ;
10619
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10620
let image: Vec< u8 > = ( 0 .. 200_000 u32 ) .map ( | i | ( i % 7 ) as u8 ) .collect ( ) ;
10621
write ( root, " image.tar" , & image) ;
10622
repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10623
std:: fs:: remove_file( root.join ( " image.tar" ) ) .unwrap ( ) ;
10624
repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10625
let blob = Repo:: blob_ref( & image) ;
10626
assert! ( repo.store ( ) .has ( & blob) ) ;
10627
repo.gc ( true ) .unwrap ( ) ;
10628
assert! ( repo.store ( ) .has ( & blob) , " a recent snapshot keeps it" ) ;
10629
repo.config.snapshot_retention_days = 0 ;
10630
repo.gc ( true ) .unwrap ( ) ;
10631
assert! ( ! repo.store ( ) .has ( & blob) , " an expired snapshot does not" ) ;
10632
assert_eq! (
10633
repo.file_at ( " a.txt" , None ) .unwrap ( ) ,
10634
b " a\n " ,
10635
" committed files stay"
10636
) ;
fe9586d9 Andy McCall 24 days ago ↶
10637
}
10638
10639
10640
10641
10642
# [ test ]
10643
fn carrying_work_the_target_already_has_is_no_conflict ( ) {
10644
hermetic ( ) ;
10645
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10646
let root = dir.path ( ) ;
10647
let mut repo = Repo:: init( root) .unwrap ( ) ;
10648
write ( root, " a.txt" , b " one\n " ) ;
10649
write ( root, " b.txt" , b " b\n " ) ;
10650
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
10651
repo.bookmark_create ( " landed" , None ) .unwrap ( ) ;
10652
10653
repo.bookmark_switch ( " landed" , false ) .unwrap ( ) ;
10654
write ( root, " a.txt" , b " two\n " ) ;
10655
repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
10656
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
10657
assert_eq! ( std:: fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n " ) ;
10658
10659
write ( root, " a.txt" , b " two\n " ) ;
10660
write ( root, " b.txt" , b " b, edited\n " ) ;
10661
let ( _ , carried) = repo.bookmark_switch_carry ( " landed" ) .unwrap ( ) ;
10662
assert_eq! (
10663
carried,
10664
vec! [ " b.txt" .to_string ( ) ] ,
10665
" only the work landed lacks is carried"
10666
) ;
10667
assert_eq! ( std:: fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " two\n " ) ;
10668
assert_eq! ( std:: fs:: read( root.join ( " b.txt" ) ) .unwrap ( ) , b " b, edited\n " ) ;
10669
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10670
let paths: Vec< _ > = s.statuses.iter ( ) .map ( | p | p.path.as_str ( ) ) .collect ( ) ;
10671
assert_eq! ( paths, [ " b.txt" ] ) ;
10672
10673
10674
repo.bookmark_switch ( " main" , true ) .unwrap ( ) ;
10675
write ( root, " a.txt" , b " three\n " ) ;
10676
let e = repo
10677
.bookmark_switch_carry ( " landed" )
10678
.unwrap_err ( )
10679
.to_string ( ) ;
10680
assert! ( e.contains ( " a.txt" ) & & e.contains ( " --discard" ) , " {e}" ) ;
b689779e Andy McCall 25 days ago ↶
10681
}
aa4939e0 Andy McCall 24 days ago ↶
10682
10683
10684
10685
# [ test ]
10686
fn a_full_hash_resolves_without_its_hash_sign ( ) {
10687
hermetic ( ) ;
10688
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10689
let root = dir.path ( ) ;
10690
let repo = Repo:: init( root) .unwrap ( ) ;
10691
write ( root, " a.txt" , b " a\n " ) ;
10692
let h = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10693
assert_eq! ( repo.resolve_change ( & h.to_hex ( ) ) .unwrap ( ) , h) ;
10694
assert_eq! ( repo.resolve_change ( & format! ( " #{} " , h.to_hex ( ) ) ) .unwrap ( ) , h) ;
10695
assert! ( repo
10696
.resolve_change ( & " 0" .repeat ( 64 ) )
10697
.unwrap_err ( )
10698
.to_string ( )
10699
.contains ( " no change matches" ) ) ;
10700
}
f2f91ceb Andy McCall 23 days ago ↶
10701
10702
# [ test ]
10703
fn a_pin_change_moves_only_the_pin ( ) {
10704
hermetic ( ) ;
10705
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10706
let root = dir.path ( ) ;
10707
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
10708
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
10709
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
10710
.unwrap ( )
10711
.with_home ( root.join ( " home" ) ) ;
10712
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
10713
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
10714
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10715
let _app_origin = Repo:: init( & root.join ( " app-origin" ) ) .unwrap ( ) ;
10716
let app_dir = root.join ( " app" ) ;
10717
let app = Repo:: clone( & root.join ( " app-origin" ) .to_string_lossy ( ) , & app_dir)
10718
.unwrap ( )
10719
.with_home ( root.join ( " home" ) ) ;
10720
write ( & app_dir, " main.txt" , b " app\n " ) ;
10721
let pin1 = app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
10722
let a1 = app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
10723
10724
write ( & root.join ( " lib" ) , " more.txt" , b " v2\n " ) ;
10725
let l2 = lib.commit ( " lib v2" , & [ ] ) .unwrap ( ) ;
10726
let crate :: subrepo:: Pin:: Oxy { repo, bookmark, .. } = pin1 else {
10727
panic! ( " an Oxygen pin" )
10728
} ;
10729
let pin2 = crate :: subrepo:: Pin:: Oxy {
10730
repo,
10731
heads: vec! [ l2] ,
10732
bookmark,
10733
} ;
10734
let author = app.store ( ) .get_change ( & a1) .unwrap ( ) .author;
10735
let change = app
10736
.build_pin_change ( & [ a1] , " libs/lib" , & pin2, " Move lib to v2" , author)
10737
.unwrap ( ) ;
10738
let mut state = app.load_state ( & [ a1] ) .unwrap ( ) ;
10739
state.add ( change) .unwrap ( ) ;
10740
let files = state.materialize ( ) .files;
10741
assert_eq! ( files[ " libs/lib" ] .kind, NodeKind:: Subrepo) ;
10742
let FileContent:: Opaque { blobs } = & files[ " libs/lib" ] .content else {
10743
panic! ( " a pin is opaque" )
10744
} ;
10745
let read = crate :: subrepo:: Pin:: decode( & app.read_blob ( & blobs[ 0 ] ) .unwrap ( ) ) .unwrap ( ) ;
10746
assert_eq! ( read, pin2, " the new pin" ) ;
10747
assert! (
10748
matches! ( & files[ " main.txt" ] .content, FileContent:: Text { bytes, .. } if bytes.as_slice ( ) = = b " app\n " ) ,
10749
" nothing else changes"
10750
) ;
10751
10752
let e = app.build_pin_change ( & [ a1] , " main.txt" , & pin2, " x" , author) ;
10753
assert! ( format! ( " {:?} " , e.unwrap_err ( ) ) .contains ( " not a subrepo" ) ) ;
10754
let e = app.build_pin_change ( & [ a1] , " libs/none" , & pin2, " x" , author) ;
10755
assert! ( format! ( " {:?} " , e.unwrap_err ( ) ) .contains ( " does not exist" ) ) ;
10756
}
767ac6cd Andy McCall 18 days ago
10757
10758
10759
10760
10761
10762
10763
# [ test ]
10764
fn one_handle_keeps_one_head_until_it_refreshes ( ) {
10765
hermetic ( ) ;
10766
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10767
let root = dir.path ( ) ;
10768
let writer = Repo:: init( root) .unwrap ( ) ;
10769
write ( root, " a.txt" , b " one\n " ) ;
10770
let h1 = writer.commit ( " one" , & [ ] ) .unwrap ( ) ;
10771
let reader = Repo:: open( root) .unwrap ( ) ;
10772
let before = reader.view ( ) .unwrap ( ) ;
10773
assert_eq! ( reader.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
10774
write ( root, " a.txt" , b " two\n " ) ;
10775
let h2 = writer.commit ( " two" , & [ ] ) .unwrap ( ) ;
10776
assert_eq! (
10777
writer.bookmark_heads ( ) .unwrap ( ) ,
10778
vec! [ h2] ,
10779
" its own operation"
10780
) ;
10781
assert_eq! ( reader.view ( ) .unwrap ( ) , before, " the head it read first" ) ;
10782
assert_eq! ( reader.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
10783
reader.refresh ( ) .unwrap ( ) ;
10784
assert_eq! ( reader.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
10785
10786
10787
let stale = Repo:: open( root) .unwrap ( ) ;
10788
assert_eq! ( stale.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
10789
write ( root, " a.txt" , b " three\n " ) ;
10790
let h3 = writer.commit ( " three" , & [ ] ) .unwrap ( ) ;
10791
stale.bookmark_create ( " feature" , Some ( & [ h1] ) ) .unwrap ( ) ;
10792
let view = stale.view ( ) .unwrap ( ) ;
10793
assert! ( view.bookmarks.contains_key ( " feature" ) ) ;
10794
assert_eq! (
10795
stale.bookmark_heads ( ) .unwrap ( ) ,
10796
vec! [ h3] ,
10797
" merged, not lost"
10798
) ;
10799
let fresh = Repo:: open( root) .unwrap ( ) ;
10800
assert_eq! ( fresh.view ( ) .unwrap ( ) , view) ;
10801
}
10802
10803
10804
# [ test ]
10805
fn over_a_store_one_handle_keeps_one_head ( ) {
10806
hermetic ( ) ;
10807
let backend = std:: sync:: Arc:: new( crate :: strata:: MemoryStore:: new( ) ) ;
10808
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = Some ( backend.clone ( ) ) ) ;
10809
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10810
let a = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
10811
let ( h0, _ ) = a.head_op ( ) .unwrap ( ) ;
10812
let b = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
10813
assert_eq! ( b.head_op ( ) .unwrap ( ) .0 , h0) ;
10814
let state = a.store.put_state ( & StateObject:: new( vec! [ ] ) ) .unwrap ( ) ;
10815
let mut view = a.view ( ) .unwrap ( ) ;
10816
view.bookmarks.insert ( " x" .into ( ) , state) ;
10817
let r = a.record_op ( & [ h0] , " bookmark x" , view) .unwrap ( ) ;
10818
assert_eq! ( a.head_op ( ) .unwrap ( ) .0 , r) ;
10819
assert_eq! ( b.head_op ( ) .unwrap ( ) .0 , h0, " b keeps its head" ) ;
10820
b.refresh ( ) .unwrap ( ) ;
10821
assert_eq! ( b.head_op ( ) .unwrap ( ) .0 , r) ;
10822
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = None ) ;
10823
}
10824
10825
10826
10827
10828
# [ test ]
10829
fn gc_retires_and_a_later_gc_deletes_after_the_hour ( ) {
10830
hermetic ( ) ;
10831
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10832
let root = dir.path ( ) ;
10833
let repo = Repo:: init( root) .unwrap ( ) ;
10834
let mut heads = Vec :: new( ) ;
10835
for i in 0 .. 4 {
10836
write ( root, & format! ( " f{i} .txt" ) , format! ( " {i} \n " ) .as_bytes ( ) ) ;
10837
heads.push ( repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ) ;
10838
}
10839
let reader = Repo:: open( root) .unwrap ( ) ;
10840
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 5 ) ;
10841
let r = repo.gc ( true ) .unwrap ( ) ;
10842
assert_eq! ( r.retired, crate :: store:: RetiredReport:: default( ) ) ;
10843
assert_eq! ( repo.store ( ) .loose_count ( ) .unwrap ( ) , 0 ) ;
10844
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 5 , " read on from what it had" ) ;
10845
10846
let r = repo.gc ( true ) .unwrap ( ) ;
10847
assert_eq! ( ( r.retired.packs, r.retired.loose) , ( 0 , 0 ) ) ;
10848
assert! ( r.retired.kept > 0 , " {r:?}" ) ;
10849
write ( root, " f9.txt" , b " 9\n " ) ;
10850
heads.push ( repo.commit ( " c9" , & [ ] ) .unwrap ( ) ) ;
10851
crate :: store:: reader_tests:: age_retired(
10852
repo.store ( ) ,
10853
crate :: store:: RETIRED_FOR + std:: time:: Duration:: from_secs( 60 ) ,
10854
) ;
10855
let r = repo.gc ( true ) .unwrap ( ) ;
10856
assert! ( r.retired.packs > = 1 & & r.retired.loose > 0 , " {r:?}" ) ;
10857
assert_eq! ( r.retired.kept, 0 , " {r:?}" ) ;
10858
let r = repo.gc ( true ) .unwrap ( ) ;
10859
assert_eq! ( ( r.retired.packs, r.retired.loose) , ( 0 , 0 ) ) ;
10860
assert! ( r.retired.kept > 0 , " what the last gc retired is young" ) ;
10861
10862
10863
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 5 ) ;
10864
for h in & heads[ .. 4 ] {
10865
reader.store ( ) .get_change ( h) .unwrap ( ) ;
10866
}
10867
reader.refresh ( ) .unwrap ( ) ;
10868
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 6 ) ;
10869
let fresh = Repo:: open( root) .unwrap ( ) ;
10870
assert_eq! (
10871
fresh.store ( ) .pack_count ( ) ,
10872
1 ,
10873
" retired packs are not loaded"
10874
) ;
10875
}
10876
10877
10878
10879
# [ test ]
10880
fn a_repository_opens_when_a_listed_index_is_gone ( ) {
10881
hermetic ( ) ;
10882
let backend = crate :: store:: reader_tests:: racing( true ) ;
10883
let dyn_backend: std:: sync:: Arc< crate:: store:: Backend> = backend.clone ( ) ;
10884
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = Some ( dyn_backend) ) ;
10885
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10886
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
10887
write ( dir.path ( ) , " a.txt" , b " a\n " ) ;
10888
let h = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10889
repo.gc ( true ) .unwrap ( ) ;
10890
let again = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
10891
assert_eq! ( again.store ( ) .pack_count ( ) , 1 ) ;
10892
assert_eq! ( again.bookmark_heads ( ) .unwrap ( ) , vec! [ h] ) ;
10893
10894
10895
use crate :: strata:: Store as _ ;
10896
let idx = backend
10897
.list ( " pack-" )
10898
.unwrap ( )
10899
.into_iter ( )
10900
.map ( | e | e.key )
10901
.find ( | k | k.ends_with ( " .idx" ) & & k ! = " pack-0000.idx" )
10902
.unwrap ( ) ;
10903
let stem = idx.trim_end_matches ( " .idx" ) ;
10904
for ext in [ " pack" , " idx" ] {
10905
let bytes = backend.get ( & format! ( " {stem} .{ext} " ) ) .unwrap ( ) ;
10906
backend
10907
.put_new ( & format! ( " pack-copy.{ext} " ) , & bytes)
10908
.unwrap ( ) ;
10909
}
10910
* backend.doomed.lock ( ) .unwrap ( ) = Some ( " pack-copy.idx" .into ( ) ) ;
10911
let again = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
10912
assert_eq! ( again.store ( ) .pack_count ( ) , 1 ) ;
10913
assert_eq! ( again.bookmark_heads ( ) .unwrap ( ) , vec! [ h] ) ;
10914
assert! ( ! backend.has ( " pack-copy.idx" ) .unwrap ( ) ) ;
10915
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = None ) ;
10916
}