98286406 Andy McCall 1 month ago ↶
1
2
3
use super :: * ;
75167672 Andy McCall 1 month ago
4
use crate :: protocol:: SignedPolicy;
957fee30 Andy McCall 1 month ago ↶
5
use crate :: repo:: { BisectStep, Selection} ;
98286406 Andy McCall 1 month ago ↶
6
7
8
9
fn hermetic ( ) {
10
static ONCE : std:: sync:: Once = std:: sync:: Once:: new( ) ;
11
static DIR : std:: sync:: OnceLock< tempfile:: TempDir> = std:: sync:: OnceLock:: new( ) ;
12
ONCE .call_once ( | | {
13
let d = tempfile:: tempdir( ) .unwrap ( ) ;
14
std:: env:: set_var( " OXY_HOME" , d.path ( ) ) ;
15
DIR .set ( d) .ok ( ) ;
16
} ) ;
17
}
18
19
fn write ( root : & Path, rel : & str , content : & [u8 ]) {
20
let p = root.join ( rel) ;
21
fs:: create_dir_all( p.parent ( ) .unwrap ( ) ) .unwrap ( ) ;
22
fs:: write( p, content) .unwrap ( ) ;
23
}
24
25
fn statuses ( s : & SnapshotResult) -> Vec< ( String, ChangeStatus) > {
75167672 Andy McCall 1 month ago
26
s.statuses
27
.iter ( )
28
.map ( | p | ( p.path.clone ( ) , p.status) )
29
.collect ( )
98286406 Andy McCall 1 month ago ↶
30
}
31
32
# [ test ]
33
fn init_snapshot_commit_log_undo ( ) {
34
hermetic ( ) ;
35
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
36
let root = dir.path ( ) ;
37
let repo = Repo:: init( root) .unwrap ( ) ;
38
assert! ( Repo:: init( root) .is_err ( ) ) ;
39
let ops = repo.op_log ( ) .unwrap ( ) ;
40
assert_eq! ( ops.len ( ) , 1 ) ;
41
assert_eq! ( ops[ 0 ] .1. command, " oxy init" ) ;
42
43
44
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
45
assert! ( ! s.changed) ;
46
assert_eq! ( repo.op_log ( ) .unwrap ( ) .len ( ) , 1 ) ;
47
48
write ( root, " src/main.rs" , b " fn main() {}\n " ) ;
49
write ( root, " README.md" , b " # Hello\n " ) ;
50
write ( root, " .oxyignore" , b " /target\n " ) ;
51
write ( root, " target/junk.o" , b " \0 \0 \0 " ) ;
52
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
53
assert! ( s.changed) ;
54
assert_eq! (
55
statuses ( & s) ,
56
vec! [
57
( " .oxyignore" .into ( ) , ChangeStatus:: Added) ,
58
( " README.md" .into ( ) , ChangeStatus:: Added) ,
59
( " src/main.rs" .into ( ) , ChangeStatus:: Added) ,
60
]
61
) ;
62
assert_eq! ( repo.op_log ( ) .unwrap ( ) .len ( ) , 2 ) ;
63
64
let s2 = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
65
assert! ( ! s2.changed) ;
66
assert_eq! ( s2.wc_hash, s.wc_hash) ;
67
assert_eq! ( repo.op_log ( ) .unwrap ( ) .len ( ) , 2 ) ;
68
69
let h1 = repo.commit ( " Initial import" , & [ ] ) .unwrap ( ) ;
70
let c1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) ;
71
assert_eq! ( c1.kind, ChangeKind:: Normal) ;
72
assert! ( c1.deps.is_empty ( ) , " first change has nothing to depend on" ) ;
73
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
74
let ( wc_hash, wc) = repo.working_copy ( ) .unwrap ( ) ;
75
assert_eq! ( wc.deps, vec! [ h1] ) ;
76
assert! ( wc.edits.is_empty ( ) ) ;
77
assert! ( repo.commit ( " nothing" , & [ ] ) .is_err ( ) ) ;
78
79
75167672 Andy McCall 1 month ago
80
write (
81
root,
82
" src/main.rs" ,
83
b " fn main() {\n println!(\" hi\" );\n }\n " ,
84
) ;
98286406 Andy McCall 1 month ago ↶
85
fs:: remove_file( root.join ( " README.md" ) ) .unwrap ( ) ;
86
write ( root, " logo.png" , b " \x89 PNG\0 \0 \x01 " ) ;
87
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
88
assert_eq! (
89
statuses ( & s) ,
90
vec! [
91
( " README.md" .into ( ) , ChangeStatus:: Removed) ,
92
( " logo.png" .into ( ) , ChangeStatus:: Added) ,
93
( " src/main.rs" .into ( ) , ChangeStatus:: Modified) ,
94
]
95
) ;
96
assert_ne! ( s.wc_hash, wc_hash) ;
97
98
assert_eq! ( s.wc.change_id, wc.change_id) ;
99
assert! ( s.wc.deps.contains ( & h1) ) ;
100
101
let h2 = repo.commit ( " Second" , & [ ] ) .unwrap ( ) ;
102
let c2 = repo.store ( ) .get_change ( & h2) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
103
assert_eq! (
104
c2.deps,
105
vec! [ h1] ,
106
" everything it touches was introduced by h1"
107
) ;
98286406 Andy McCall 1 month ago ↶
108
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
109
110
111
let st = repo.load_state ( & [ h2] ) .unwrap ( ) ;
112
let snap = st.materialize ( ) ;
113
assert! ( ! snap.is_conflicted ( ) ) ;
75167672 Andy McCall 1 month ago
114
assert_eq! (
115
snap.files.keys ( ) .cloned ( ) .collect:: < Vec< _ > > ( ) ,
116
[ " .oxyignore" , " logo.png" , " src" , " src/main.rs" ]
117
) ;
118
assert! (
119
matches! ( & snap.files[ " src/main.rs" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " fn main() {\n println!(\" hi\" );\n }\n " )
120
) ;
98286406 Andy McCall 1 month ago ↶
121
assert! ( snap.files[ " logo.png" ] .mode.opaque) ;
122
if let FileContent:: Opaque { blobs } = & snap.files[ " logo.png" ] .content {
123
assert_eq! ( repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) , b " \x89 PNG\0 \0 \x01 " ) ;
124
} else {
125
panic! ( " logo should be opaque" ) ;
126
}
127
128
129
let log = repo.log ( ) .unwrap ( ) ;
130
assert_eq! ( log.len ( ) , 3 ) ;
131
assert! ( log[ 0 ] .is_working_copy) ;
132
assert_eq! ( log[ 1 ] .hash, h2) ;
133
assert! ( log[ 1 ] .is_head) ;
134
assert_eq! ( log[ 2 ] .hash, h1) ;
135
let d = repo.describe_change ( & h2) .unwrap ( ) ;
136
assert! ( d.contains ( " D README.md" ) , " {d}" ) ;
137
assert! ( d.contains ( " + println!(\" hi\" );" ) , " {d}" ) ;
138
assert! ( d.contains ( " -fn main() {}" ) , " {d}" ) ;
139
140
141
assert_eq! ( repo.resolve_change ( & c2.change_id.short ( 6 ) ) .unwrap ( ) , h2) ;
75167672 Andy McCall 1 month ago
142
assert_eq! (
143
repo.resolve_change ( & format! ( " #{} " , h1.short ( 8 ) ) ) .unwrap ( ) ,
144
h1
145
) ;
98286406 Andy McCall 1 month ago ↶
146
assert! ( repo.resolve_change ( " zzzz" ) .is_err ( ) ) ;
147
148
149
repo.undo ( ) .unwrap ( ) ;
150
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
151
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
152
assert! (
153
! s.changed,
154
" files on disk still match the pre-commit working copy"
155
) ;
98286406 Andy McCall 1 month ago ↶
156
assert_eq! ( s.statuses.len ( ) , 3 ) ;
157
}
158
159
160
fn conflicted_repo ( ) -> ( tempfile:: TempDir, Repo, ChangeHash, ChangeHash, ChangeHash) {
161
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
162
let root = dir.path ( ) ;
163
let repo = Repo:: init( root) .unwrap ( ) ;
164
write ( root, " f.txt" , b " a\n b\n c\n " ) ;
165
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
166
write ( root, " f.txt" , b " a\n B\n c\n " ) ;
167
let h2 = repo.commit ( " upper" , & [ ] ) .unwrap ( ) ;
168
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
169
write ( root, " f.txt" , b " a\n X\n c\n " ) ;
170
let h3 = repo.commit ( " ex" , & [ ] ) .unwrap ( ) ;
171
( dir, repo, h1, h2, h3)
172
}
173
3652104a Andy McCall 18 days ago ↶
174
175
176
98286406 Andy McCall 1 month ago ↶
177
# [ test ]
3652104a Andy McCall 18 days ago ↶
178
fn marker_like_text_does_not_block_a_resolution ( ) {
179
hermetic ( ) ;
180
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
181
let root = dir.path ( ) ;
182
let repo = Repo:: init( root) .unwrap ( ) ;
183
let example = " ```\n <<<<<<< oxy conflict 1 of 2, side 1: change k7m2p9 \" Add retry loop\" \n ... lines ...\n ======= oxy conflict 1 of 2, side 2: change q3x8w1 \" Refactor client\" \n >>>>>>> oxy conflict 1 of 2 end\n ```\n " ;
184
write (
185
root,
186
" doc.md" ,
187
format! ( " intro\n {example} b\n end\n " ) .as_bytes ( ) ,
188
) ;
189
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
190
write (
191
root,
192
" doc.md" ,
193
format! ( " intro\n {example} B\n end\n " ) .as_bytes ( ) ,
194
) ;
195
repo.commit ( " upper" , & [ ] ) .unwrap ( ) ;
196
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
197
write (
198
root,
199
" doc.md" ,
200
format! ( " intro\n {example} X\n end\n " ) .as_bytes ( ) ,
201
) ;
202
repo.commit ( " ex" , & [ ] ) .unwrap ( ) ;
203
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
204
.unwrap ( ) ;
205
let marked = fs:: read_to_string( root.join ( " doc.md" ) ) .unwrap ( ) ;
206
assert! ( marked.contains ( " oxy conflict 1 of 1" ) , " {marked}" ) ;
207
208
write ( root, " doc.md" , marked.replace ( " B\n " , " BB\n " ) .as_bytes ( ) ) ;
209
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
210
assert_eq! (
211
statuses ( & s) ,
212
vec! [ ( " doc.md" .into ( ) , ChangeStatus:: ConflictEdited) ]
213
) ;
214
215
write (
216
root,
217
" doc.md" ,
218
format! ( " intro\n {example} X\n end\n " ) .as_bytes ( ) ,
219
) ;
220
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
221
assert_eq! (
222
statuses ( & s) ,
223
vec! [ ( " doc.md" .into ( ) , ChangeStatus:: Resolved) ]
224
) ;
225
let h = repo.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
226
assert_eq! (
227
repo.store ( ) .get_change ( & h) .unwrap ( ) .kind,
228
ChangeKind:: Resolution
229
) ;
230
assert! ( ! repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) .is_conflicted ( ) ) ;
231
}
232
9bd98cfb Andy McCall 10 days ago ↶
233
234
235
3652104a Andy McCall 18 days ago ↶
236
# [ test ]
9bd98cfb Andy McCall 10 days ago ↶
237
fn adjacent_insertions_are_settled_by_keeping_both ( ) {
238
hermetic ( ) ;
239
for reverse in [ false , true ] {
240
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
241
let root = dir.path ( ) ;
242
let repo = Repo:: init( root) .unwrap ( ) ;
243
write ( root, " f.txt" , b " a\n b\n " ) ;
244
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
245
write ( root, " f.txt" , b " a\n first\n b\n " ) ;
246
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
247
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
248
write ( root, " f.txt" , b " a\n second\n b\n " ) ;
249
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
250
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
251
.unwrap ( ) ;
252
let marked = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
253
let ( f, s) = (
254
marked.find ( " first\n " ) .unwrap ( ) ,
255
marked.find ( " second\n " ) .unwrap ( ) ,
256
) ;
257
assert! ( f < s, " the earlier change's side comes first: {marked}" ) ;
258
259
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
260
let node = snap.base_snapshot.files[ " f.txt" ] .node;
261
assert_eq! ( snap.base.both_sides ( & node) .iter ( ) .flatten ( ) .count ( ) , 1 ) ;
262
let kept = repo.keep_both ( & snap, & [ ] , reverse) .unwrap ( ) ;
263
assert_eq! (
264
kept,
265
vec! [ KeptBoth {
266
path: " f.txt" .into ( ) ,
267
settled: vec! [ 1 ] ,
c0d4075c Andy McCall 10 days ago ↶
268
identical: vec! [ ] ,
9bd98cfb Andy McCall 10 days ago ↶
269
left: vec! [ ]
270
} ]
271
) ;
272
let want: & [ u8 ] = if reverse {
273
b " a\n second\n first\n b\n "
274
} else {
275
b " a\n first\n second\n b\n "
276
} ;
277
assert_eq! ( fs:: read( root.join ( " f.txt" ) ) .unwrap ( ) , want) ;
278
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
279
assert_eq! ( statuses ( & s) , vec! [ ( " f.txt" .into ( ) , ChangeStatus:: Resolved) ] ) ;
280
let h = repo.commit ( " both" , & [ ] ) .unwrap ( ) ;
281
assert_eq! (
282
repo.store ( ) .get_change ( & h) .unwrap ( ) .kind,
283
ChangeKind:: Resolution
284
) ;
285
assert! ( ! repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) .is_conflicted ( ) ) ;
286
}
287
}
288
4afe288b Andy McCall 10 days ago ↶
289
290
291
292
# [ test ]
293
fn union_files_keep_both_sides_on_checkout ( ) {
294
hermetic ( ) ;
295
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
296
let root = dir.path ( ) ;
297
let repo = Repo:: init( root) .unwrap ( ) ;
298
write ( root, " .oxyattributes" , b " CHANGELOG.md merge=union\n " ) ;
299
write (
300
root,
301
" CHANGELOG.md" ,
302
b " # Changelog\n ## Unreleased\n ## 1.0\n " ,
303
) ;
304
write ( root, " f.txt" , b " a\n b\n " ) ;
305
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
306
write (
307
root,
308
" CHANGELOG.md" ,
309
b " # Changelog\n ## Unreleased\n - one\n ## 1.0\n " ,
310
) ;
311
write ( root, " f.txt" , b " a\n one\n b\n " ) ;
312
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
313
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
314
write (
315
root,
316
" CHANGELOG.md" ,
317
b " # Changelog\n ## Unreleased\n - two\n ## 1.0\n " ,
318
) ;
319
write ( root, " f.txt" , b " a\n two\n b\n " ) ;
320
repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
321
let r = repo
322
.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
323
.unwrap ( ) ;
324
assert! ( r.conflicted) ;
325
assert_eq! ( r.kept_both.len ( ) , 1 , " {:?}" , r.kept_both) ;
326
assert_eq! ( r.kept_both[ 0 ] .path, " CHANGELOG.md" ) ;
327
assert_eq! (
328
fs:: read_to_string( root.join ( " CHANGELOG.md" ) ) .unwrap ( ) ,
329
" # Changelog\n ## Unreleased\n - one\n - two\n ## 1.0\n "
330
) ;
331
assert! ( has_conflict_markers ( & fs:: read( root.join ( " f.txt" ) ) .unwrap ( ) ) ) ;
332
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
333
assert_eq! (
334
statuses ( & s) ,
335
vec! [ ( " CHANGELOG.md" .into ( ) , ChangeStatus:: Resolved) ]
336
) ;
337
}
338
c0d4075c Andy McCall 10 days ago ↶
339
340
341
342
343
# [ test ]
344
fn identical_resolutions_on_two_bookmarks_settle_by_themselves ( ) {
345
hermetic ( ) ;
346
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
347
let root = dir.path ( ) ;
348
let repo = Repo:: init( root) .unwrap ( ) ;
349
write ( root, " f.txt" , b " a\n x\n b\n " ) ;
350
let base = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
351
write ( root, " f.txt" , b " a\n X1\n b\n " ) ;
352
let c1 = repo.commit ( " X1" , & [ ] ) .unwrap ( ) ;
353
repo.checkout ( & [ base] , false , " checkout" ) .unwrap ( ) ;
354
write ( root, " f.txt" , b " a\n X2\n b\n " ) ;
355
let c2 = repo.commit ( " X2" , & [ ] ) .unwrap ( ) ;
356
357
repo.checkout ( & [ c1, c2] , false , " checkout" ) .unwrap ( ) ;
358
assert! ( has_conflict_markers ( & fs:: read( root.join ( " f.txt" ) ) .unwrap ( ) ) ) ;
359
write ( root, " f.txt" , b " a\n X\n b\n " ) ;
360
let r1 = repo.commit ( " resolve on one" , & [ ] ) .unwrap ( ) ;
361
repo.checkout ( & [ c1, c2] , false , " checkout" ) .unwrap ( ) ;
362
write ( root, " f.txt" , b " a\n X\n b\n " ) ;
363
let r2 = repo.commit ( " resolve on two" , & [ ] ) .unwrap ( ) ;
364
assert_ne! ( r1, r2) ;
365
366
let st = repo.load_state ( & [ r1, r2] ) .unwrap ( ) ;
367
assert! (
368
st.materialize ( ) .is_conflicted ( ) ,
369
" two resolutions, one place"
370
) ;
371
assert_eq! (
372
Repo:: union_resolution( & st, & st.materialize ( ) ) ,
373
Some ( vec! [ ( " f.txt" .into ( ) , b " a\n X\n b\n " .to_vec ( ) ) ] ) ,
374
" a host settles it"
375
) ;
376
let r = repo.checkout ( & [ r1, r2] , false , " checkout" ) .unwrap ( ) ;
377
assert! ( r.conflicted) ;
378
assert_eq! ( r.kept_both.len ( ) , 1 , " {:?}" , r.kept_both) ;
379
assert_eq! (
380
( r.kept_both[ 0 ] .path.as_str ( ) , & r.kept_both[ 0 ] .identical) ,
381
( " f.txt" , & vec! [ 1 ] )
382
) ;
383
assert_eq! ( fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) , " a\n X\n b\n " ) ;
384
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
385
assert_eq! ( statuses ( & s) , vec! [ ( " f.txt" .into ( ) , ChangeStatus:: Resolved) ] ) ;
386
let merged = repo.commit ( " merge" , & [ ] ) .unwrap ( ) ;
387
let st = repo.load_state ( & [ merged] ) .unwrap ( ) ;
388
assert! ( ! st.materialize ( ) .is_conflicted ( ) ) ;
389
}
390
ac4ec124 Andy McCall 10 days ago ↶
391
392
393
394
# [ test ]
395
fn a_host_settles_a_state_whose_conflicts_are_all_union ( ) {
396
hermetic ( ) ;
397
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
398
let root = dir.path ( ) ;
399
let repo = Repo:: init( root) .unwrap ( ) ;
400
write ( root, " .oxyattributes" , b " *.md merge=union\n " ) ;
401
write ( root, " A.md" , b " a\n b\n " ) ;
402
write ( root, " B.md" , b " x\n y\n " ) ;
403
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
404
write ( root, " A.md" , b " a\n one\n b\n " ) ;
405
write ( root, " B.md" , b " x\n Y1\n " ) ;
406
let h2 = repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
407
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
408
write ( root, " A.md" , b " a\n two\n b\n " ) ;
409
let h3 = repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
410
let st = repo.load_state ( & [ h2, h3] ) .unwrap ( ) ;
411
assert_eq! (
412
Repo:: union_resolution( & st, & st.materialize ( ) ) ,
413
Some ( vec! [ ( " A.md" .into ( ) , b " a\n one\n two\n b\n " .to_vec ( ) ) ] )
414
) ;
415
416
repo.checkout ( & [ h3] , false , " checkout" ) .unwrap ( ) ;
417
write ( root, " B.md" , b " x\n Y2\n " ) ;
418
let h4 = repo.commit ( " Y2" , & [ ] ) .unwrap ( ) ;
419
let st = repo.load_state ( & [ h2, h4] ) .unwrap ( ) ;
420
assert! ( st.materialize ( ) .is_conflicted ( ) ) ;
421
assert_eq! ( Repo:: union_resolution( & st, & st.materialize ( ) ) , None ) ;
422
}
423
9bd98cfb Andy McCall 10 days ago ↶
424
425
426
427
# [ test ]
428
fn keeping_both_refuses_replaced_or_edited_lines ( ) {
429
hermetic ( ) ;
430
let ( dir, repo, _ , _ , _ ) = conflicted_repo ( ) ;
431
let root = dir.path ( ) ;
432
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
433
.unwrap ( ) ;
434
let marked = fs:: read( root.join ( " f.txt" ) ) .unwrap ( ) ;
435
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
436
let node = snap.base_snapshot.files[ " f.txt" ] .node;
437
assert_eq! ( snap.base.both_sides ( & node) , vec! [ None ] ) ;
438
let kept = repo.keep_both ( & snap, & [ " f.txt" .into ( ) ] , false ) .unwrap ( ) ;
439
assert! ( kept[ 0 ] .settled.is_empty ( ) ) ;
440
assert_eq! ( kept[ 0 ] .left[ 0 ] .0 , 1 ) ;
441
assert! ( kept[ 0 ] .left[ 0 ] .1. contains ( " both sides changed" ) , " {kept:?}" ) ;
442
assert_eq! ( fs:: read( root.join ( " f.txt" ) ) .unwrap ( ) , marked) ;
443
444
445
let sides = vec! [ Some ( vec! [ b " x\n " .to_vec ( ) , b " y\n " .to_vec ( ) ] ) ] ;
446
let text = b " a\n <<<<<<< oxy conflict 1 of 1, side 1 of 2: change k \" x\" \n x!\n ======= oxy conflict 1 of 1, side 2 of 2: change q \" y\" \n y\n >>>>>>> oxy conflict 1 of 1 end\n b\n " ;
447
let ( out, settled, left) = super :: snapshot:: keep_both_text( text, & sides, false ) ;
448
assert_eq! ( out, text.to_vec ( ) ) ;
449
assert! ( settled.is_empty ( ) ) ;
450
assert! ( left[ 0 ] .1. contains ( " edited" ) , " {left:?}" ) ;
451
452
let text = b " a\n <<<<<<< oxy conflict 1 of 1, side 1 of 2: change q \" y\" \n y\n ======= oxy conflict 1 of 1, side 2 of 2: change k \" x\" \n x\n >>>>>>> oxy conflict 1 of 1 end\n b\n " ;
453
let ( out, settled, _ ) = super :: snapshot:: keep_both_text( text, & sides, false ) ;
454
assert_eq! ( out, b " a\n x\n y\n b\n " .to_vec ( ) ) ;
455
assert_eq! ( settled, vec! [ 1 ] ) ;
456
}
457
458
# [ test ]
98286406 Andy McCall 1 month ago ↶
459
fn concurrent_edits_conflict_and_can_be_resolved ( ) {
460
hermetic ( ) ;
461
let ( dir, repo, h1, h2, h3) = conflicted_repo ( ) ;
462
let root = dir.path ( ) ;
463
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
464
assert_eq! ( heads, [ h2, h3] .into_iter ( ) .collect ( ) ) ;
465
let st = repo.load_state ( & [ h2, h3] ) .unwrap ( ) ;
466
assert! ( st.materialize ( ) .is_conflicted ( ) ) ;
467
468
75167672 Andy McCall 1 month ago
469
let r = repo
470
.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
471
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
472
assert! ( r.conflicted) ;
473
let marked = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
474
assert! (
475
marked.starts_with ( " a\n <<<<<<< oxy conflict 1 of 1" ) ,
476
" {marked}"
477
) ;
478
assert! (
479
marked.contains ( " B\n " ) & & marked.contains ( " X\n " ) & & marked.ends_with ( " end\n c\n " ) ,
480
" {marked}"
481
) ;
98286406 Andy McCall 1 month ago ↶
482
assert! ( has_conflict_markers ( marked.as_bytes ( ) ) ) ;
483
484
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
485
assert! ( s.statuses.is_empty ( ) ) ;
486
assert! ( ! s.resolves) ;
487
488
489
write ( root, " f.txt" , marked.replace ( " B\n " , " BB\n " ) .as_bytes ( ) ) ;
490
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
491
assert_eq! (
492
statuses ( & s) ,
493
vec! [ ( " f.txt" .into ( ) , ChangeStatus:: ConflictEdited) ]
494
) ;
98286406 Andy McCall 1 month ago ↶
495
assert! ( repo.commit ( " no" , & [ ] ) .is_err ( ) ) ;
496
497
498
write ( root, " f.txt" , b " a\n X\n c\n d\n " ) ;
499
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
500
assert_eq! ( statuses ( & s) , vec! [ ( " f.txt" .into ( ) , ChangeStatus:: Resolved) ] ) ;
501
assert! ( s.resolves) ;
502
let h4 = repo.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
503
let c4 = repo.store ( ) .get_change ( & h4) .unwrap ( ) ;
504
assert_eq! ( c4.kind, ChangeKind:: Resolution) ;
505
let mut want = vec! [ h1, h2, h3] ;
506
want.sort ( ) ;
75167672 Andy McCall 1 month ago
507
assert_eq! (
508
c4.deps, want,
509
" a resolution depends on exactly the contributors and what it references"
510
) ;
98286406 Andy McCall 1 month ago ↶
511
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h4] ) ;
512
let snap = repo.load_state ( & [ h4] ) .unwrap ( ) .materialize ( ) ;
513
assert! ( ! snap.is_conflicted ( ) ) ;
75167672 Andy McCall 1 month ago
514
assert! (
515
matches! ( & snap.files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n X\n c\n d\n " )
516
) ;
98286406 Andy McCall 1 month ago ↶
517
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
518
519
520
let mut st = State:: new( ) ;
521
for h in [ h4, h3, h1, h2] {
522
st.add_unchecked ( repo.store ( ) .get_change ( & h) .unwrap ( ) ) ;
523
}
524
st.validate ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
525
assert! (
526
matches! ( & st.materialize ( ) .files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n X\n c\n d\n " )
527
) ;
98286406 Andy McCall 1 month ago ↶
528
}
529
69953b50 Andy McCall 18 days ago ↶
530
531
532
98286406 Andy McCall 1 month ago ↶
533
# [ test ]
69953b50 Andy McCall 18 days ago ↶
534
fn resolutions_and_amends_take_paths ( ) {
535
hermetic ( ) ;
536
let ( dir, repo, _h1, _h2, _h3) = conflicted_repo ( ) ;
537
let root = dir.path ( ) ;
538
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
539
.unwrap ( ) ;
540
541
write ( root, " f.txt" , b " a\n X\n c\n " ) ;
542
write ( root, " notes.txt" , b " pending\n " ) ;
543
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
544
assert! ( s.resolves) ;
545
let err = repo
546
.commit_selection_partial ( " resolve" , & [ ] , & Selection:: Paths( vec! [ " notes.txt" .into ( ) ] ) )
547
.unwrap_err ( )
548
.to_string ( ) ;
549
assert! (
550
err.contains ( " takes every file it resolves" ) & & err.contains ( " f.txt" ) ,
551
" {err}"
552
) ;
553
let ( h4, partial) = repo
554
.commit_selection_partial ( " resolve" , & [ ] , & Selection:: Paths( vec! [ " f.txt" .into ( ) ] ) )
555
.unwrap ( ) ;
556
assert! ( partial) ;
557
assert_eq! (
558
repo.store ( ) .get_change ( & h4) .unwrap ( ) .kind,
559
ChangeKind:: Resolution
560
) ;
561
assert! ( ! repo
562
.load_state ( & [ h4] )
563
.unwrap ( )
564
.materialize ( )
565
.is_conflicted ( ) ) ;
566
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
567
assert_eq! (
568
statuses ( & s) ,
569
vec! [ ( " notes.txt" .into ( ) , ChangeStatus:: Added) ] ,
570
" the other work waits"
571
) ;
572
573
574
575
let h5 = repo.commit ( " notes" , & [ h4] ) .unwrap ( ) ;
576
let id5 = repo.store ( ) .get_change ( & h5) .unwrap ( ) .change_id;
577
write ( root, " notes.txt" , b " pending\n more\n " ) ;
578
write ( root, " other.txt" , b " later\n " ) ;
579
let h6 = repo
580
.amend_paths ( Some ( " notes, amended" ) , & [ " notes.txt" .into ( ) ] )
581
.unwrap ( ) ;
582
let c6 = repo.store ( ) .get_change ( & h6) .unwrap ( ) ;
583
assert_eq! ( ( c6.change_id, c6.message.as_str ( ) ) , ( id5, " notes, amended" ) ) ;
584
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h6] ) ;
585
let snap6 = repo.load_state ( & [ h6] ) .unwrap ( ) .materialize ( ) ;
586
assert! (
587
matches! ( & snap6.files[ " notes.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " pending\n more\n " )
588
) ;
589
assert! (
590
! snap6.files.contains_key ( " other.txt" ) ,
591
" other.txt stays out"
592
) ;
593
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
594
assert_eq! (
595
statuses ( & s) ,
596
vec! [ ( " other.txt" .into ( ) , ChangeStatus:: Added) ]
597
) ;
598
}
599
600
# [ test ]
98286406 Andy McCall 1 month ago ↶
601
fn resolution_may_keep_both_sides_or_neither ( ) {
602
hermetic ( ) ;
603
let ( dir, repo, _h1, _h2, _h3) = conflicted_repo ( ) ;
604
let root = dir.path ( ) ;
75167672 Andy McCall 1 month ago
605
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
606
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
607
608
write ( root, " f.txt" , b " a\n X\n B\n c\n " ) ;
609
let h = repo.commit ( " both" , & [ ] ) .unwrap ( ) ;
610
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
611
assert! ( ! snap.is_conflicted ( ) ) ;
75167672 Andy McCall 1 month ago
612
assert! (
613
matches! ( & snap.files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n X\n B\n c\n " )
614
) ;
98286406 Andy McCall 1 month ago ↶
615
616
repo.undo ( ) .unwrap ( ) ;
617
write ( root, " f.txt" , b " a\n c\n " ) ;
618
let h = repo.commit ( " neither" , & [ ] ) .unwrap ( ) ;
619
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
620
assert! ( ! snap.is_conflicted ( ) ) ;
75167672 Andy McCall 1 month ago
621
assert! (
622
matches! ( & snap.files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n c\n " )
623
) ;
98286406 Andy McCall 1 month ago ↶
624
}
625
626
# [ test ]
627
fn name_conflict_resolved_by_removing_a_candidate ( ) {
628
hermetic ( ) ;
629
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
630
let root = dir.path ( ) ;
631
let repo = Repo:: init( root) .unwrap ( ) ;
632
write ( root, " keep.txt" , b " k\n " ) ;
633
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
634
write ( root, " new.txt" , b " one\n " ) ;
635
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
636
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
637
write ( root, " new.txt" , b " two\n " ) ;
638
repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
639
let r = repo
640
.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
641
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
642
assert! ( r.conflicted) ;
75167672 Andy McCall 1 month ago
643
assert! (
644
root.join ( " new.txt.oxy-conflict-1" ) .exists ( )
645
& & root.join ( " new.txt.oxy-conflict-2" ) .exists ( )
646
) ;
98286406 Andy McCall 1 month ago ↶
647
assert! ( ! root.join ( " new.txt" ) .exists ( ) ) ;
648
649
75167672 Andy McCall 1 month ago
650
let ( keep, drop) = if fs:: read( root.join ( " new.txt.oxy-conflict-1" ) ) .unwrap ( ) = = b " two\n " {
651
( 1 , 2 )
652
} else {
653
( 2 , 1 )
654
} ;
98286406 Andy McCall 1 month ago ↶
655
fs:: remove_file( root.join ( format! ( " new.txt.oxy-conflict-{drop} " ) ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
656
fs:: rename(
657
root.join ( format! ( " new.txt.oxy-conflict-{keep} " ) ) ,
658
root.join ( " new.txt" ) ,
659
)
660
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
661
let h = repo.commit ( " pick two" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
662
assert_eq! (
663
repo.store ( ) .get_change ( & h) .unwrap ( ) .kind,
664
ChangeKind:: Resolution
665
) ;
98286406 Andy McCall 1 month ago ↶
666
667
668
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
669
assert_eq! ( heads, [ h1, h] .into_iter ( ) .collect ( ) ) ;
75167672 Andy McCall 1 month ago
670
let snap = repo
671
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
672
.unwrap ( )
673
.materialize ( ) ;
98286406 Andy McCall 1 month ago ↶
674
assert! ( ! snap.is_conflicted ( ) , " {:?}" , snap.tree.conflicts) ;
75167672 Andy McCall 1 month ago
675
assert_eq! (
676
snap.files.keys ( ) .cloned ( ) .collect:: < Vec< _ > > ( ) ,
677
[ " keep.txt" , " new.txt" ]
678
) ;
679
assert! (
680
matches! ( & snap.files[ " new.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " two\n " )
681
) ;
98286406 Andy McCall 1 month ago ↶
682
}
683
684
# [ test ]
685
fn identities_and_signed_commits ( ) {
686
hermetic ( ) ;
687
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
688
let root = dir.path ( ) ;
689
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
690
write ( root, " a.txt" , b " a\n " ) ;
691
let h0 = repo.commit ( " unsigned" , & [ ] ) .unwrap ( ) ;
692
assert_eq! ( repo.verify_change ( & h0) .unwrap ( ) , SignatureStatus:: Unsigned) ;
75167672 Andy McCall 1 month ago
693
assert_eq! (
694
repo.store ( ) .get_change ( & h0) .unwrap ( ) .author,
695
ObjectRef:: ZERO
696
) ;
98286406 Andy McCall 1 month ago ↶
697
75167672 Andy McCall 1 month ago
698
let ( key, id) = repo
699
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
700
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
701
assert_eq! ( id.hash ( ) , key) ;
702
assert_eq! ( repo.config.author_key, Some ( key) ) ;
703
704
let reopened = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
705
assert_eq! ( reopened.config.author_key, Some ( key) ) ;
706
assert_eq! ( reopened.secret_key ( ) .unwrap ( ) .unwrap ( ) .0 , key) ;
707
708
709
write ( root, " a.txt" , b " a\n b\n " ) ;
710
let h1 = repo.commit ( " authored" , & [ ] ) .unwrap ( ) ;
711
assert_eq! ( repo.store ( ) .get_change ( & h1) .unwrap ( ) .author, key) ;
712
assert_eq! ( repo.verify_change ( & h1) .unwrap ( ) , SignatureStatus:: Unsigned) ;
713
714
repo.sign_change ( & h1) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
715
assert! (
716
matches! ( repo.verify_change ( & h1) .unwrap ( ) , SignatureStatus:: Valid { key: k, .. } if k = = key)
717
) ;
98286406 Andy McCall 1 month ago ↶
718
719
repo.config.set ( " core.sign" , " change" ) .unwrap ( ) ;
720
repo.save_config ( ) .unwrap ( ) ;
721
write ( root, " a.txt" , b " a\n b\n c\n " ) ;
722
let h2 = repo.commit ( " signed" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
723
assert_eq! (
724
repo.verify_change ( & h2) .unwrap ( ) ,
725
SignatureStatus:: Valid {
726
key,
727
name: " Ada" .into ( ) ,
728
email: " [email protected] " .into ( ) ,
729
sealed: false ,
730
revoked: false
731
}
732
) ;
98286406 Andy McCall 1 month ago ↶
733
05e71ed6 Andy McCall 1 month ago ↶
734
735
736
98286406 Andy McCall 1 month ago ↶
737
let other = SecretKey:: generate( ) ;
738
let other_id = other.identity ( " Mallory" , " [email protected] " , 0 ) ;
739
let other_key = repo.store ( ) .put_identity ( & other_id) .unwrap ( ) ;
05e71ed6 Andy McCall 1 month ago ↶
740
write ( root, " a.txt" , b " a\n b\n c\n d\n " ) ;
741
repo.config.set ( " core.sign" , " seal" ) .unwrap ( ) ;
742
let h3 = repo.commit ( " ada's, unsigned" , & [ ] ) .unwrap ( ) ;
743
let forged = other.sign ( Domain:: Change, & other_key, & h3) ;
744
repo.store ( ) .put_signature ( & h3, & forged) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
745
assert! (
05e71ed6 Andy McCall 1 month ago ↶
746
matches! ( repo.verify_change ( & h3) .unwrap ( ) , SignatureStatus:: Invalid( m) if m.contains ( " neither the author" ) )
75167672 Andy McCall 1 month ago
747
) ;
05e71ed6 Andy McCall 1 month ago ↶
748
let adopted = other.sign ( Domain:: Change, & other_key, & h0) ;
749
repo.store ( ) .put_signature ( & h0, & adopted) .unwrap ( ) ;
750
assert! (
751
matches! ( repo.verify_change ( & h0) .unwrap ( ) , SignatureStatus:: Valid { name, .. } if name = = " Mallory" )
752
) ;
98286406 Andy McCall 1 month ago ↶
753
754
755
let mut sig = repo.store ( ) .signatures ( & h2) .unwrap ( ) .remove ( 0 ) ;
756
sig.ed[ 0 ] ^ = 1 ;
757
repo.store ( ) .put_signature ( & h2, & sig) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
758
assert! ( matches! (
759
repo.verify_change ( & h2) .unwrap ( ) ,
760
SignatureStatus:: Invalid( _ )
761
) ) ;
98286406 Andy McCall 1 month ago ↶
762
763
764
let no_keys = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " nowhere" ) ) ;
765
write ( root, " a.txt" , b " z\n " ) ;
766
assert! ( no_keys.commit ( " cannot sign" , & [ ] ) .is_err ( ) ) ;
767
}
768
769
# [ test ]
770
fn checkout_moves_the_working_copy ( ) {
771
hermetic ( ) ;
772
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
773
let root = dir.path ( ) ;
774
let repo = Repo:: init( root) .unwrap ( ) ;
775
write ( root, " a.txt" , b " one\n " ) ;
776
write ( root, " sub/deep/b.txt" , b " b\n " ) ;
777
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
778
write ( root, " a.txt" , b " two\n " ) ;
779
fs:: remove_file( root.join ( " sub/deep/b.txt" ) ) .unwrap ( ) ;
780
write ( root, " bin.dat" , & [ 0 , 1 , 2 , 3 ] ) ;
781
let h2 = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
782
783
let r = repo.checkout ( & [ h1] , false , " oxy checkout" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
784
assert_eq! (
785
r,
786
CheckoutReport {
787
written: 2 ,
788
removed: 1 ,
9b723f69 Andy McCall 25 days ago ↶
789
removed_paths: vec! [ " bin.dat" .into ( ) ] ,
75167672 Andy McCall 1 month ago
790
conflicted: false ,
d3cd7ef9 Andy McCall 1 month ago ↶
791
notes: vec! [ ] ,
792
lacks: 1 ,
4afe288b Andy McCall 10 days ago ↶
793
kept_both: vec! [ ] ,
75167672 Andy McCall 1 month ago
794
}
795
) ;
98286406 Andy McCall 1 month ago ↶
796
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n " ) ;
797
assert_eq! ( fs:: read( root.join ( " sub/deep/b.txt" ) ) .unwrap ( ) , b " b\n " ) ;
798
assert! ( ! root.join ( " bin.dat" ) .exists ( ) ) ;
799
let ( _ , wc) = repo.working_copy ( ) .unwrap ( ) ;
800
assert_eq! ( wc.deps, vec! [ h1] ) ;
75167672 Andy McCall 1 month ago
801
assert! (
802
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
803
" checkout leaves a clean working copy"
804
) ;
98286406 Andy McCall 1 month ago ↶
805
806
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
807
808
809
write ( root, " a.txt" , b " dirty\n " ) ;
810
assert! ( repo.checkout ( & [ h2] , false , " oxy checkout" ) .is_err ( ) ) ;
75167672 Andy McCall 1 month ago
811
let r = repo
812
.checkout ( & [ h2] , true , " oxy checkout --discard" )
813
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
814
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " two\n " ) ;
815
assert_eq! ( fs:: read( root.join ( " bin.dat" ) ) .unwrap ( ) , [ 0 , 1 , 2 , 3 ] ) ;
816
assert! ( ! root.join ( " sub" ) .exists ( ) , " empty directories are pruned" ) ;
817
assert_eq! ( r.removed, 1 ) ;
818
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
819
820
821
# [ cfg ( unix ) ]
822
{
823
fs:: remove_dir_all( root.join ( " sub" ) ) .ok ( ) ;
824
std:: os:: unix:: fs:: symlink( " /tmp" , root.join ( " sub" ) ) .unwrap ( ) ;
825
assert! ( repo.checkout ( & [ h1] , true , " oxy checkout" ) .is_err ( ) ) ;
826
}
827
}
828
829
# [ test ]
830
fn large_files_are_chunked_and_read_back ( ) {
831
hermetic ( ) ;
832
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
833
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
834
let mut big = vec! [ 0 u8 ; 700 * 1024 ] ;
835
for ( i, b) in big.iter_mut ( ) .enumerate ( ) {
836
* b = ( i as u32 ) .wrapping_mul ( 2654435761 ) .to_le_bytes ( ) [ 0 ] ;
837
}
838
let r = repo.store_blob ( & big) .unwrap ( ) ;
839
let blob = Blob:: decode( & repo.store ( ) .get_typed ( & r, ObjectType:: Blob) .unwrap ( ) ) .unwrap ( ) ;
840
assert! ( blob.chunks.len ( ) > = 3 , " {}" , blob.chunks.len ( ) ) ;
841
assert_eq! ( repo.read_blob ( & r) .unwrap ( ) , big) ;
842
assert_eq! ( Repo:: blob_ref( & big) , r) ;
843
}
844
845
# [ test ]
846
fn clone_push_pull_with_seals_and_attestations ( ) {
847
hermetic ( ) ;
848
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
849
let root = dir.path ( ) ;
850
let origin_path = root.join ( " origin" ) ;
851
let origin = Repo:: init( & origin_path) .unwrap ( ) ;
852
let origin_url = origin_path.to_string_lossy ( ) .to_string ( ) ;
853
854
75167672 Andy McCall 1 month ago
855
let mut a = Repo:: clone( & origin_url, & root.join ( " a" ) )
856
.unwrap ( )
857
.with_home ( root.join ( " home-a" ) ) ;
98286406 Andy McCall 1 month ago ↶
858
assert_eq! ( a.id ( ) .unwrap ( ) , origin.id ( ) .unwrap ( ) ) ;
75167672 Andy McCall 1 month ago
859
let ( key_a, _ ) = a
860
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
861
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
862
write ( & root.join ( " a" ) , " f.txt" , b " a\n " ) ;
863
let h1 = a.commit ( " first" , & [ ] ) .unwrap ( ) ;
864
let results = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
865
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
866
assert! ( results[ 0 ] .ok, " {}" , results[ 0 ] .reason) ;
867
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
868
75167672 Andy McCall 1 month ago
869
assert! (
870
matches! ( origin.authenticate_change ( & h1) .unwrap ( ) , SignatureStatus:: Valid { key, sealed: true , .. } if key = = key_a)
871
) ;
872
assert_eq! (
873
origin.verify_change ( & h1) .unwrap ( ) ,
874
SignatureStatus:: Unsigned
875
) ;
f8c69576 Andy McCall 1 month ago ↶
876
let serial_of = | att : & SignedAttest| match att.attest.body {
877
AttestBody:: Ref { serial, .. } => serial,
878
_ => unreachable! ( ) ,
879
} ;
98286406 Andy McCall 1 month ago ↶
880
let ( _ , att) = origin.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
881
882
883
let first = serial_of ( & att) ;
884
assert! ( first > 1_000_000_000_000 , " {first}" ) ;
98286406 Andy McCall 1 month ago ↶
885
let again = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
886
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
887
assert! ( again[ 0 ] .ok & & again[ 0 ] .reason.contains ( " up to date" ) ) ;
888
let ( _ , att) = origin.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
889
assert_eq! ( serial_of ( & att) , first, " a no-op push does not attest again" ) ;
98286406 Andy McCall 1 month ago ↶
890
891
892
let b = Repo:: clone( & origin_url, & root.join ( " b" ) ) .unwrap ( ) ;
893
assert_eq! ( fs:: read( root.join ( " b/f.txt" ) ) .unwrap ( ) , b " a\n " ) ;
894
assert_eq! ( b.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
75167672 Andy McCall 1 month ago
895
assert! ( matches! (
896
b.authenticate_change ( & h1) .unwrap ( ) ,
897
SignatureStatus:: Valid { sealed: true , .. }
898
) ) ;
98286406 Andy McCall 1 month ago ↶
899
900
901
write ( & root.join ( " b" ) , " f.txt" , b " a\n b\n " ) ;
902
let h2 = b.commit ( " second" , & [ ] ) .unwrap ( ) ;
903
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
904
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
905
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
906
907
908
write ( & root.join ( " a" ) , " g.txt" , b " g\n " ) ;
909
let h3 = a.commit ( " third" , & [ ] ) .unwrap ( ) ;
910
assert! ( a.push ( " origin" , false ) .is_err ( ) ) ;
911
let pr = a.pull ( " origin" ) .unwrap ( ) ;
912
assert! ( pr.changed & & pr.checked_out) ;
913
let mut want = vec! [ h2, h3] ;
914
want.sort ( ) ;
915
assert_eq! ( pr.heads, want) ;
916
assert_eq! ( fs:: read( root.join ( " a/f.txt" ) ) .unwrap ( ) , b " a\n b\n " ) ;
917
assert_eq! ( fs:: read( root.join ( " a/g.txt" ) ) .unwrap ( ) , b " g\n " ) ;
918
let results = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
919
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
920
assert! ( results[ 0 ] .ok, " {}" , results[ 0 ] .reason) ;
921
let mut origin_heads = origin.bookmark_heads ( ) .unwrap ( ) ;
922
origin_heads.sort ( ) ;
923
assert_eq! ( origin_heads, want) ;
924
let ( _ , att) = origin.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
925
assert_eq! ( serial_of ( & att) , first + 1 ) ;
98286406 Andy McCall 1 month ago ↶
926
927
928
let pr = b.pull ( " origin" ) .unwrap ( ) ;
929
assert_eq! ( pr.heads, want) ;
930
assert_eq! ( fs:: read( root.join ( " b/g.txt" ) ) .unwrap ( ) , b " g\n " ) ;
931
assert! ( ! b.snapshot ( " status" ) .unwrap ( ) .changed) ;
932
933
934
write ( & root.join ( " a" ) , " f.txt" , b " a\n A\n " ) ;
935
a.commit ( " a-edit" , & [ ] ) .unwrap ( ) ;
936
a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
937
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
938
write ( & root.join ( " b" ) , " f.txt" , b " a\n B\n " ) ;
939
b.commit ( " b-edit" , & [ ] ) .unwrap ( ) ;
940
let pr = b.pull ( " origin" ) .unwrap ( ) ;
941
assert! ( pr.changed & & pr.checked_out) ;
942
let text = fs:: read_to_string( root.join ( " b/f.txt" ) ) .unwrap ( ) ;
943
assert! ( text.contains ( " <<<<<<< oxy conflict" ) , " {text}" ) ;
944
write ( & root.join ( " b" ) , " f.txt" , b " a\n AB\n " ) ;
945
let hr = b.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
946
assert_eq! (
947
b.store ( ) .get_change ( & hr) .unwrap ( ) .kind,
948
ChangeKind:: Resolution
949
) ;
98286406 Andy McCall 1 month ago ↶
950
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
951
origin.refresh ( ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
952
a.pull ( " origin" ) .unwrap ( ) ;
953
assert_eq! ( fs:: read( root.join ( " a/f.txt" ) ) .unwrap ( ) , b " a\n AB\n " ) ;
954
955
956
let mut want = vec! [ hr, h3] ;
957
want.sort ( ) ;
958
assert_eq! ( a.bookmark_heads ( ) .unwrap ( ) , want) ;
959
assert! ( ! a.load_state ( & want) .unwrap ( ) .materialize ( ) .is_conflicted ( ) ) ;
960
}
961
962
# [ test ]
963
fn receive_push_rejects_bad_updates ( ) {
964
hermetic ( ) ;
965
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
966
let root = dir.path ( ) ;
967
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
968
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
969
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
970
.unwrap ( )
971
.with_home ( root.join ( " home" ) ) ;
972
let ( key, _ ) = a
973
.create_identity ( " Ada" , " a@b" , IdentityScope:: Repo)
974
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
975
write ( & root.join ( " a" ) , " f.txt" , b " x\n " ) ;
976
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
977
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
978
let empty = origin.store ( ) .put_state ( & StateObject:: new( vec! [ ] ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
979
let s1 = origin
980
.store ( )
981
.put_state ( & StateObject:: new( vec! [ h1] ) )
982
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
983
984
75167672 Andy McCall 1 month ago
985
let ghost = origin
986
.store ( )
987
.put_state ( & StateObject:: new( vec! [ ObjectRef( [ 9 ; 32 ] ) ] ) )
988
.unwrap ( ) ;
989
let r = origin
990
.receive_push (
991
Bundle:: default( ) ,
992
vec! [ RefUpdate {
993
name: " main" .into ( ) ,
994
old_state: s1,
995
new_state: ghost,
996
force: false ,
997
attestation: None ,
998
} ] ,
999
None ,
1000
vec! [ ] ,
1001
)
1002
.unwrap ( ) ;
1003
assert! (
1004
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " missing" ) ,
1005
" {}" ,
1006
r[ 0 ] .reason
1007
) ;
98286406 Andy McCall 1 month ago ↶
1008
1009
75167672 Andy McCall 1 month ago
1010
let r = origin
1011
.receive_push (
1012
Bundle:: default( ) ,
1013
vec! [ RefUpdate {
1014
name: " main" .into ( ) ,
1015
old_state: empty,
1016
new_state: s1,
1017
force: false ,
1018
attestation: None ,
1019
} ] ,
1020
None ,
1021
vec! [ ] ,
1022
)
1023
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1024
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " stale" ) , " {}" , r[ 0 ] .reason) ;
1025
1026
75167672 Andy McCall 1 month ago
1027
let r = origin
1028
.receive_push (
1029
Bundle:: default( ) ,
1030
vec! [ RefUpdate {
1031
name: " main" .into ( ) ,
1032
old_state: s1,
1033
new_state: empty,
1034
force: false ,
1035
attestation: None ,
1036
} ] ,
1037
None ,
1038
vec! [ ] ,
1039
)
1040
.unwrap ( ) ;
1041
assert! (
1042
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " fast-forward" ) ,
1043
" {}" ,
1044
r[ 0 ] .reason
1045
) ;
1046
let r = origin
1047
.receive_push (
1048
Bundle:: default( ) ,
1049
vec! [ RefUpdate {
1050
name: " main" .into ( ) ,
1051
old_state: s1,
1052
new_state: empty,
1053
force: true ,
1054
attestation: None ,
1055
} ] ,
1056
None ,
1057
vec! [ ] ,
1058
)
1059
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1060
assert! ( r[ 0 ] .ok) ;
1061
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ ] ) ;
1062
1063
75167672 Andy McCall 1 month ago
1064
let sa = a
1065
.sign_attest ( & Attest {
1066
repo: a.id ( ) .unwrap ( ) ,
1067
hlc: Hlc:: now( ) ,
1068
body: AttestBody:: Ref {
1069
name: " main" .into ( ) ,
1070
old_state: empty,
1071
new_state: s1,
1072
serial: 1 ,
1073
} ,
1074
} )
1075
.unwrap ( ) ;
1076
let r = origin
1077
.receive_push (
1078
Bundle:: default( ) ,
1079
vec! [ RefUpdate {
1080
name: " main" .into ( ) ,
1081
old_state: empty,
1082
new_state: s1,
1083
force: false ,
1084
attestation: Some ( sa.clone ( ) ) ,
1085
} ] ,
1086
None ,
1087
vec! [ ] ,
1088
)
1089
.unwrap ( ) ;
1090
assert! (
1091
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " serial" ) ,
1092
" {}" ,
1093
r[ 0 ] .reason
1094
) ;
98286406 Andy McCall 1 month ago ↶
1095
75167672 Andy McCall 1 month ago
1096
let sa2 = a
1097
.sign_attest ( & Attest {
1098
repo: a.id ( ) .unwrap ( ) ,
1099
hlc: Hlc:: now( ) ,
1100
body: AttestBody:: Ref {
1101
name: " main" .into ( ) ,
1102
old_state: s1,
1103
new_state: empty,
1104
serial: 5 ,
1105
} ,
1106
} )
1107
.unwrap ( ) ;
1108
let r = origin
1109
.receive_push (
1110
Bundle:: default( ) ,
1111
vec! [ RefUpdate {
1112
name: " main" .into ( ) ,
1113
old_state: empty,
1114
new_state: s1,
1115
force: false ,
1116
attestation: Some ( sa2) ,
1117
} ] ,
1118
None ,
1119
vec! [ ] ,
1120
)
1121
.unwrap ( ) ;
1122
assert! (
1123
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " describe" ) ,
1124
" {}" ,
1125
r[ 0 ] .reason
1126
) ;
98286406 Andy McCall 1 month ago ↶
1127
let _ = key;
1128
}
1129
1130
# [ test ]
1131
fn user_config_supplies_defaults_and_clones_sign ( ) {
1132
hermetic ( ) ;
1133
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1134
let root = dir.path ( ) ;
1135
let home = root.join ( " home" ) ;
1136
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1137
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1138
1139
1140
let mut a = Repo:: init( & root.join ( " a" ) ) .unwrap ( ) .with_home ( & home) ;
75167672 Andy McCall 1 month ago
1141
let ( key, _ ) = a
1142
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1143
.unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
1144
assert! ( ! a.config.repo_layer ( ) .contains_key ( " author.key" ) ) ;
1145
assert_eq! ( a.config.author_key, Some ( key) ) ;
1146
assert! ( home.join ( " config.toml" ) .exists ( ) ) ;
1147
a.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
1148
assert! ( a.config.sign_changes) ;
1149
1150
1151
a.config.set ( " core.sign" , " seal" ) .unwrap ( ) ;
1152
a.save_config ( ) .unwrap ( ) ;
1153
let again = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) .with_home ( & home) ;
1154
assert! ( ! again.config.sign_changes) ;
1155
assert_eq! ( again.config.author_key, Some ( key) ) ;
75167672 Andy McCall 1 month ago
1156
assert_eq! (
1157
read_config_file ( & root.join ( " a/.oxy/config" ) )
1158
.unwrap ( )
1159
.get ( " core.sign" )
1160
.map ( String :: as_str) ,
1161
Some ( " seal" )
1162
) ;
98286406 Andy McCall 1 month ago ↶
1163
1164
1165
1166
let b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) .with_home ( & home) ;
1167
assert! ( b.config.sign_changes) ;
1168
write ( & root.join ( " b" ) , " f.txt" , b " f\n " ) ;
1169
let h = b.commit ( " signed from a clone" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1170
assert! (
1171
matches! ( b.verify_change ( & h) .unwrap ( ) , SignatureStatus:: Valid { key: k, .. } if k = = key)
1172
) ;
98286406 Andy McCall 1 month ago ↶
1173
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
75167672 Andy McCall 1 month ago
1174
assert! ( matches! (
1175
origin.verify_change ( & h) .unwrap ( ) ,
1176
SignatureStatus:: Valid { .. }
1177
) ) ;
98286406 Andy McCall 1 month ago ↶
1178
}
1179
1180
# [ test ]
1181
fn format1_repositories_are_migrated_on_open ( ) {
1182
hermetic ( ) ;
1183
use crate :: oplog:: { Format1Op, Format1View} ;
1184
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1185
let root = dir.path ( ) ;
1186
let repo = Repo:: init( root) .unwrap ( ) ;
1187
write ( root, " a.txt" , b " a\n " ) ;
1188
let h1 = repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
1189
let ( wc_hash, _ ) = repo.working_copy ( ) .unwrap ( ) ;
1190
1191
let mut v1 = Format1View:: default( ) ;
1192
v1.bookmarks.insert ( " main" .into ( ) , vec! [ ] ) ;
1193
v1.workspaces.insert ( repo.workspace ( ) , wc_hash) ;
75167672 Andy McCall 1 month ago
1194
let init = Format1Op {
1195
parents: vec! [ ] ,
1196
hlc: Hlc:: default( ) ,
1197
command: " oxy init" .into ( ) ,
1198
view: v1.clone ( ) ,
1199
} ;
98286406 Andy McCall 1 month ago ↶
1200
let init_ref = repo.store ( ) .put ( ObjectType:: Op, & init.encode ( ) ) .unwrap ( ) ;
1201
v1.bookmarks.insert ( " main" .into ( ) , vec! [ h1] ) ;
75167672 Andy McCall 1 month ago
1202
let commit = Format1Op {
1203
parents: vec! [ init_ref] ,
1204
hlc: Hlc:: default( ) ,
1205
command: " oxy commit" .into ( ) ,
1206
view: v1,
1207
} ;
98286406 Andy McCall 1 month ago ↶
1208
let commit_ref = repo.store ( ) .put ( ObjectType:: Op, & commit.encode ( ) ) .unwrap ( ) ;
1209
write_atomic ( & root.join ( " .oxy/ops/head" ) , commit_ref.to_hex ( ) .as_bytes ( ) ) .unwrap ( ) ;
1210
write_atomic ( & root.join ( " .oxy/format" ) , FORMAT1_LINE .as_bytes ( ) ) .unwrap ( ) ;
1211
drop ( repo) ;
1212
1213
let repo = Repo:: open( root) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1214
assert_eq! (
1215
fs:: read_to_string( root.join ( " .oxy/format" ) ) .unwrap ( ) ,
1216
FORMAT_LINE
1217
) ;
98286406 Andy McCall 1 month ago ↶
1218
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
1219
let ops = repo.op_log ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1220
assert_eq! (
1221
ops.iter ( )
1222
.map ( | ( _, o ) | o.command.as_str ( ) )
1223
.collect:: < Vec< _ > > ( ) ,
1224
[ " oxy commit" , " oxy init" ]
1225
) ;
98286406 Andy McCall 1 month ago ↶
1226
assert_eq! ( repo.heads_of ( & ops[ 1 ] .1. view, " main" ) .unwrap ( ) , vec! [ ] ) ;
1227
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
1228
1229
assert_eq! ( Repo:: open( root) .unwrap ( ) .op_log ( ) .unwrap ( ) .len ( ) , 2 ) ;
1230
}
1231
1232
# [ test ]
1233
fn undo_skips_snapshot_operations ( ) {
1234
hermetic ( ) ;
1235
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1236
let root = dir.path ( ) ;
1237
let repo = Repo:: init( root) .unwrap ( ) ;
1238
write ( root, " a.txt" , b " a\n " ) ;
1239
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
1240
1241
write ( root, " b.txt" , b " b\n " ) ;
1242
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
1243
assert! ( s.changed) ;
1244
assert! ( repo.head_op ( ) .unwrap ( ) .1. is_snapshot ( ) ) ;
1245
assert_eq! ( repo.last_command ( ) .unwrap ( ) .command, " oxy commit -m \" a\" " ) ;
1246
1247
repo.undo ( ) .unwrap ( ) ;
1248
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ ] ) ;
1249
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
1250
let paths: Vec< _ > = s.statuses.iter ( ) .map ( | p | p.path.as_str ( ) ) .collect ( ) ;
75167672 Andy McCall 1 month ago
1251
assert_eq! (
1252
paths,
1253
[ " a.txt" , " b.txt" ] ,
1254
" both files are back in the working copy"
1255
) ;
98286406 Andy McCall 1 month ago ↶
1256
assert! ( repo.store ( ) .get_change ( & h1) .unwrap ( ) .deps.is_empty ( ) ) ;
1257
1258
assert_eq! ( repo.last_command ( ) .unwrap ( ) .command, " oxy init" ) ;
1259
assert! ( repo.undo ( ) .is_err ( ) ) ;
1260
}
1261
1262
# [ test ]
1263
fn independent_changes_commute_and_make_multiple_heads ( ) {
1264
hermetic ( ) ;
1265
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1266
let root = dir.path ( ) ;
1267
let repo = Repo:: init( root) .unwrap ( ) ;
1268
write ( root, " a.txt" , b " a\n " ) ;
1269
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
1270
write ( root, " b.txt" , b " b\n " ) ;
1271
let h2 = repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
1272
let c2 = repo.store ( ) .get_change ( & h2) .unwrap ( ) ;
1273
assert! ( c2.deps.is_empty ( ) , " b.txt touches nothing from h1" ) ;
1274
let mut heads = repo.bookmark_heads ( ) .unwrap ( ) ;
1275
heads.sort ( ) ;
1276
let mut want = vec! [ h1, h2] ;
1277
want.sort ( ) ;
1278
assert_eq! ( heads, want) ;
1279
1280
write ( root, " a.txt" , b " a\n a2\n " ) ;
1281
let h3 = repo.commit ( " a2" , & [ ] ) .unwrap ( ) ;
1282
assert_eq! ( repo.store ( ) .get_change ( & h3) .unwrap ( ) .deps, vec! [ h1] ) ;
1283
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
1284
assert_eq! ( heads, [ h2, h3] .into_iter ( ) .collect ( ) ) ;
1285
1286
write ( root, " c.txt" , b " c\n " ) ;
1287
let h4 = repo.commit ( " c" , & [ h2] ) .unwrap ( ) ;
1288
assert_eq! ( repo.store ( ) .get_change ( & h4) .unwrap ( ) .deps, vec! [ h2] ) ;
1289
}
1290
1291
# [ test ]
1292
fn mode_change_and_text_binary_switch ( ) {
1293
hermetic ( ) ;
1294
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1295
let root = dir.path ( ) ;
1296
let repo = Repo:: init( root) .unwrap ( ) ;
1297
write ( root, " run.sh" , b " #!/bin/sh\n " ) ;
1298
repo.commit ( " add script" , & [ ] ) .unwrap ( ) ;
1299
# [ cfg ( unix ) ]
1300
{
1301
use std:: os:: unix:: fs:: PermissionsExt;
1302
fs:: set_permissions( root.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
1303
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1304
assert_eq! (
1305
statuses ( & s) ,
1306
vec! [ ( " run.sh" .into ( ) , ChangeStatus:: ModeChanged) ]
1307
) ;
98286406 Andy McCall 1 month ago ↶
1308
let h = repo.commit ( " exec" , & [ ] ) .unwrap ( ) ;
1309
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1310
assert! ( snap.files[ " run.sh" ] .mode.exec) ;
1311
}
1312
1313
write ( root, " run.sh" , b " \0 binary" ) ;
1314
let h = repo.commit ( " binary" , & [ ] ) .unwrap ( ) ;
1315
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1316
assert! ( snap.files[ " run.sh" ] .mode.opaque) ;
1317
write ( root, " run.sh" , b " text again\n " ) ;
1318
let h = repo.commit ( " text" , & [ ] ) .unwrap ( ) ;
1319
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1320
assert! ( ! snap.files[ " run.sh" ] .mode.opaque) ;
75167672 Andy McCall 1 month ago
1321
assert! (
1322
matches! ( & snap.files[ " run.sh" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " text again\n " )
1323
) ;
98286406 Andy McCall 1 month ago ↶
1324
assert! ( ! snap.is_conflicted ( ) ) ;
1325
}
83e0677b Andy McCall 1 month ago ↶
1326
6eae06b6 Andy McCall 28 days ago ↶
1327
1328
1329
# [ cfg ( unix ) ]
1330
# [ test ]
1331
fn a_binary_file_changed_and_made_executable_at_once_keeps_both ( ) {
1332
use std:: os:: unix:: fs:: PermissionsExt;
1333
hermetic ( ) ;
1334
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1335
let root = dir.path ( ) ;
1336
let repo = Repo:: init( root) .unwrap ( ) ;
1337
write ( root, " tool.exe" , b " MZ\0 one" ) ;
1338
repo.commit ( " add" , & [ ] ) .unwrap ( ) ;
1339
write ( root, " tool.exe" , b " MZ\0 two" ) ;
1340
fs:: set_permissions( root.join ( " tool.exe" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
1341
let h = repo.commit ( " update and +x" , & [ ] ) .unwrap ( ) ;
1342
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1343
let f = & snap.files[ " tool.exe" ] ;
1344
assert! ( f.mode.opaque & & f.mode.exec, " {:?}" , f.mode) ;
1345
assert! (
1346
repo.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ,
1347
" nothing left to commit"
1348
) ;
1349
}
1350
f0a517d2 Andy McCall 19 days ago ↶
1351
1352
1353
1354
83e0677b Andy McCall 1 month ago ↶
1355
# [ test ]
f0a517d2 Andy McCall 19 days ago ↶
1356
fn a_certified_key_pushes_under_a_subject_grant ( ) {
1357
use crate :: cert:: { Certificate, SignedCertificate} ;
1358
use crate :: identity:: Scope;
1359
hermetic ( ) ;
1360
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1361
let root = dir.path ( ) ;
1362
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1363
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1364
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1365
.unwrap ( )
1366
.with_home ( root.join ( " home-a" ) ) ;
1367
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1368
.unwrap ( ) ;
1369
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1370
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1371
a.policy_init ( ) .unwrap ( ) ;
1372
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1373
let hub_sk = SecretKey:: generate( ) ;
1374
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1375
a.policy_grant_subject (
1376
" agent:claude@ada" ,
1377
hub.clone ( ) ,
1378
Scope:: CHANGES | Scope:: REFS ,
1379
None ,
1380
)
1381
.unwrap ( ) ;
1382
assert! ( a
1383
.push ( " origin" , false )
1384
.unwrap ( )
1385
.iter ( )
1386
.any ( | r | r.name = = " policy" & & r.ok ) ) ;
1387
1388
let now = Hlc:: now( ) .wall_ms;
1389
1390
1391
let agent = | name : & str , subject : & str , from : u64 , to : u64 | {
1392
let mut r = Repo:: clone( & url, & root.join ( name) )
1393
.unwrap ( )
1394
.with_home ( root.join ( format! ( " home-{name} " ) ) ) ;
1395
let ( _ , id) = r
1396
.create_identity ( name, " [email protected] " , IdentityScope:: User)
1397
.unwrap ( ) ;
1398
let cert = SignedCertificate:: sign(
1399
Certificate {
1400
key: id,
1401
issuer: hub.hash ( ) ,
1402
subject: subject.into ( ) ,
1403
claims: vec! [ ] ,
1404
scope: Scope:: CHANGES | Scope:: REFS ,
1405
not_before: from,
1406
expires: to,
a2387d54 Andy McCall 19 days ago ↶
1407
grant: None ,
f0a517d2 Andy McCall 19 days ago ↶
1408
} ,
1409
& hub,
1410
& hub_sk,
1411
) ;
1412
r.store ( ) .put_identity ( & hub) .unwrap ( ) ;
1413
r.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1414
write ( & root.join ( name) , & format! ( " {name} .txt" ) , b " agent work\n " ) ;
1415
r.commit ( " work" , & [ ] ) .unwrap ( ) ;
1416
r
1417
} ;
1418
1419
let claude = agent ( " claude" , " agent:claude@ada" , now - 60_000 , now + 600_000 ) ;
1420
let r = claude.push ( " origin" , false ) .unwrap ( ) ;
1421
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
1422
1423
a.pull ( " origin" ) .unwrap ( ) ;
1424
assert! ( a.bookmark_heads ( ) .unwrap ( ) .iter ( ) .any ( | h | a
1425
.store ( )
1426
.get_change ( h)
1427
.map ( | c | c.message = = " work" )
1428
.unwrap_or ( false ) ) ) ;
1429
f53cbe7a Andy McCall 19 days ago ↶
1430
1431
1432
1433
1434
let host = Repo:: open( & root.join ( " origin" ) )
1435
.unwrap ( )
1436
.with_acceptor ( hub.clone ( ) , hub_sk.clone ( ) ) ;
1437
let ( attest_ref, sa) = host.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
1438
host.record_acceptance ( & attest_ref) .unwrap ( ) ;
1439
let served = host.remote_refs ( ) .unwrap ( ) ;
1440
let acc = served
1441
.refs
1442
.iter ( )
1443
.find ( | r | r.name = = " main" )
1444
.unwrap ( )
1445
.accepted
1446
.clone ( )
1447
.expect ( " served with the refs" ) ;
1448
let signer = sa.sigs[ 0 ] .key;
1449
assert_eq! (
1450
a.certified_time ( & signer, & sa, Some ( & acc) ) ,
1451
acc.attest.hlc.wall_ms
1452
) ;
1453
assert_eq! ( a.certified_time ( & signer, & sa, None ) , sa.attest.hlc.wall_ms) ;
1454
let stranger_sk = SecretKey:: generate( ) ;
1455
let stranger = stranger_sk.identity ( " stranger" , " s@x" , 1 ) ;
1456
let mut forged = acc.clone ( ) ;
1457
forged.sigs = vec! [ stranger_sk.sign ( Domain:: Attest, & stranger.hash ( ) , & acc.attest.hash ( ) ) ] ;
1458
assert_eq! (
1459
a.certified_time ( & signer, & sa, Some ( & forged) ) ,
1460
sa.attest.hlc.wall_ms,
1461
" not the issuer's"
1462
) ;
1463
let mut elsewhere = acc.clone ( ) ;
1464
elsewhere.attest.body = AttestBody:: Accepted {
1465
attest: ObjectRef( [ 7 ; 32 ] ) ,
1466
} ;
1467
elsewhere.sigs = vec! [ hub_sk.sign ( Domain:: Attest, & hub.hash ( ) , & elsewhere.attest.hash ( ) ) ] ;
1468
assert_eq! (
1469
a.certified_time ( & signer, & sa, Some ( & elsewhere) ) ,
1470
sa.attest.hlc.wall_ms,
1471
" another attestation's"
1472
) ;
1473
a.pull ( " origin" ) .unwrap ( ) ;
1474
1475
host.gc ( true ) .unwrap ( ) ;
1476
assert! ( host.acceptance_of ( & attest_ref) .unwrap ( ) .is_some ( ) ) ;
1477
f0a517d2 Andy McCall 19 days ago ↶
1478
let late = agent ( " late" , " agent:claude@ada" , now - 1_200_000 , now - 600_000 ) ;
1479
let r = late.push ( " origin" , false ) .unwrap ( ) ;
1480
assert! (
1481
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " may not update" ) ,
1482
" an expired certificate: {r:?}"
1483
) ;
1484
let other = agent ( " other" , " agent:mallory@ada" , now - 60_000 , now + 600_000 ) ;
1485
let r = other.push ( " origin" , false ) .unwrap ( ) ;
1486
assert! (
1487
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " may not update" ) ,
1488
" a subject not granted: {r:?}"
1489
) ;
1490
}
1491
d8524a9f Andy McCall 17 days ago ↶
1492
1493
1494
1495
1496
# [ test ]
1497
fn a_tag_signed_by_an_earlier_short_lived_key_is_pushed ( ) {
1498
use crate :: cert:: { Certificate, SignedCertificate} ;
1499
use crate :: identity:: Scope;
1500
hermetic ( ) ;
1501
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1502
let root = dir.path ( ) ;
1503
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1504
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1505
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1506
.unwrap ( )
1507
.with_home ( root.join ( " home-a" ) ) ;
1508
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1509
.unwrap ( ) ;
1510
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1511
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1512
a.policy_init ( ) .unwrap ( ) ;
1513
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1514
let hub_sk = SecretKey:: generate( ) ;
1515
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1516
a.policy_grant_subject (
1517
" agent:claude@ada" ,
1518
hub.clone ( ) ,
1519
Scope:: CHANGES | Scope:: REFS ,
1520
Some ( " *" ) ,
1521
)
1522
.unwrap ( ) ;
1523
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1524
1525
let now = Hlc:: now( ) .wall_ms;
1526
let mut agent = Repo:: clone( & url, & root.join ( " agent" ) )
1527
.unwrap ( )
1528
.with_home ( root.join ( " home-agent" ) ) ;
1529
agent.store ( ) .put_identity ( & hub) .unwrap ( ) ;
1530
let certify = | agent : & Repo, id | {
1531
let cert = SignedCertificate:: sign(
1532
Certificate {
1533
key: id,
1534
issuer: hub.hash ( ) ,
1535
subject: " agent:claude@ada" .into ( ) ,
1536
claims: vec! [ ] ,
1537
scope: Scope:: CHANGES | Scope:: REFS ,
1538
not_before: now - 60_000 ,
1539
expires: now + 600_000 ,
1540
grant: None ,
1541
} ,
1542
& hub,
1543
& hub_sk,
1544
) ;
1545
agent.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1546
} ;
1547
1548
let ( first, first_id) = agent
1549
.create_identity ( " claude" , " [email protected] " , IdentityScope:: User)
1550
.unwrap ( ) ;
1551
certify ( & agent, first_id) ;
1552
let heads = agent.bookmark_heads ( ) .unwrap ( ) ;
1553
agent.tag_create ( " v1" , & heads, " first" ) .unwrap ( ) ;
1554
1555
let ( second, second_id) = agent
1556
.create_identity ( " claude" , " [email protected] " , IdentityScope:: User)
1557
.unwrap ( ) ;
1558
assert_ne! ( first, second) ;
1559
certify ( & agent, second_id) ;
1560
agent
1561
.set_user_config ( " author.signer" , & second.to_hex ( ) )
1562
.unwrap ( ) ;
1563
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
1ec6fdbc Andy McCall 17 days ago ↶
1564
assert! (
1565
host.store ( ) .get_identity ( & first) .is_err ( ) ,
1566
" the host has never seen the tag's key"
1567
) ;
d8524a9f Andy McCall 17 days ago ↶
1568
let r = agent.push ( " origin" , false ) .unwrap ( ) ;
1ec6fdbc Andy McCall 17 days ago ↶
1569
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
1570
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
1571
assert! (
1572
host.tag_refs ( ) .unwrap ( ) .iter ( ) .any ( | t | t.name = = " v1" ) ,
1573
" the tag arrived"
1574
) ;
1575
}
1576
1577
1578
1579
1580
1581
1582
1583
# [ test ]
1584
fn a_short_lived_key_s_tag_is_fetched_at_its_issuer_s_acceptance ( ) {
1585
use crate :: cert:: { Certificate, SignedCertificate} ;
1586
use crate :: identity:: Scope;
1587
hermetic ( ) ;
1588
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1589
let root = dir.path ( ) ;
1590
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1591
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1592
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1593
.unwrap ( )
1594
.with_home ( root.join ( " home-a" ) ) ;
1595
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1596
.unwrap ( ) ;
1597
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1598
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1599
a.policy_init ( ) .unwrap ( ) ;
1600
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1601
let hub_sk = SecretKey:: generate( ) ;
1602
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1603
a.policy_grant_subject (
1604
" agent:claude@ada" ,
1605
hub.clone ( ) ,
1606
Scope:: CHANGES | Scope:: REFS ,
1607
Some ( " *" ) ,
1608
)
1609
.unwrap ( ) ;
1610
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1611
1612
let now = Hlc:: now( ) .wall_ms;
1613
1614
let agent = | name : & str , from : u64 , to : u64 | {
1615
let mut r = Repo:: clone( & url, & root.join ( name) )
1616
.unwrap ( )
1617
.with_home ( root.join ( format! ( " home-{name} " ) ) ) ;
1618
r.store ( ) .put_identity ( & hub) .unwrap ( ) ;
1619
let ( _ , id) = r
1620
.create_identity ( name, " [email protected] " , IdentityScope:: User)
1621
.unwrap ( ) ;
1622
let cert = SignedCertificate:: sign(
1623
Certificate {
1624
key: id,
1625
issuer: hub.hash ( ) ,
1626
subject: " agent:claude@ada" .into ( ) ,
1627
claims: vec! [ ] ,
1628
scope: Scope:: CHANGES | Scope:: REFS ,
1629
not_before: from,
1630
expires: to,
1631
grant: None ,
1632
} ,
1633
& hub,
1634
& hub_sk,
1635
) ;
1636
r.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1637
r
1638
} ;
1639
1640
1641
1642
1643
let claude = agent ( " claude" , now - 60_000 , now + 600_000 ) ;
1644
let heads = claude.bookmark_heads ( ) .unwrap ( ) ;
1645
claude.tag_create ( " v1" , & heads, " one" ) .unwrap ( ) ;
1646
let r = claude.push ( " origin" , false ) .unwrap ( ) ;
d8524a9f Andy McCall 17 days ago ↶
1647
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
1ec6fdbc Andy McCall 17 days ago ↶
1648
let b = Repo:: clone( & url, & root.join ( " b" ) )
1649
.unwrap ( )
1650
.with_home ( root.join ( " home-b" ) ) ;
1651
let rep = b.fetch ( " origin" ) .unwrap ( ) ;
1652
assert! (
1653
rep.skipped
1654
.iter ( )
1655
.any ( | ( n , why ) | n = = " tags/v1" & & why.contains ( " no time to check its certificate at" ) ) ,
1656
" {rep:?}"
1657
) ;
1658
assert_eq! ( b.tag_heads ( " v1" ) .unwrap ( ) , None ) ;
1659
1660
1661
1662
let host = Repo:: open( & root.join ( " origin" ) )
1663
.unwrap ( )
1664
.with_acceptor ( hub.clone ( ) , hub_sk.clone ( ) ) ;
1665
let ( v1, _ ) = host.tag_attest ( " v1" ) .unwrap ( ) .unwrap ( ) ;
1666
host.record_acceptance ( & v1) .unwrap ( ) ;
1667
let served = host.tag_refs ( ) .unwrap ( ) ;
1668
assert! ( served
1669
.iter ( )
1670
.any ( | t | t.name = = " v1" & & t.accepted.is_some ( ) ) ) ;
1671
let rep = b.fetch ( " origin" ) .unwrap ( ) ;
1672
assert! ( rep.skipped.is_empty ( ) , " {rep:?}" ) ;
1673
assert_eq! ( b.tag_heads ( " v1" ) .unwrap ( ) , Some ( heads.clone ( ) ) ) ;
1674
1675
host.gc ( true ) .unwrap ( ) ;
1676
assert! ( host.acceptance_of ( & v1) .unwrap ( ) .is_some ( ) ) ;
1677
1678
1679
1680
let late = agent ( " late" , now - 7_200_000 , now - 3_600_000 ) ;
1681
late.tag_create ( " v2" , & heads, " two" ) .unwrap ( ) ;
1682
let r = late.push ( " origin" , false ) .unwrap ( ) ;
1683
assert! (
1684
r.iter ( )
1685
.any ( | x | x.name = = " tags/v2" & & ! x.ok & & x.reason.contains ( " may not create" ) ) ,
1686
" {r:?}"
1687
) ;
1688
1689
1690
1691
let ( v2, _ ) = late.tag_attest ( " v2" ) .unwrap ( ) .unwrap ( ) ;
1692
let acc = Attest {
1693
repo: late.id ( ) .unwrap ( ) ,
1694
hlc: Hlc {
1695
wall_ms: now - 5_400_000 ,
1696
logical: 0 ,
1697
} ,
1698
body: AttestBody:: Accepted { attest: v2 } ,
1699
} ;
1700
let sig = hub_sk.sign ( Domain:: Attest, & hub.hash ( ) , & acc.hash ( ) ) ;
1701
1702
let stranger_sk = SecretKey:: generate( ) ;
1703
let stranger = stranger_sk.identity ( " stranger" , " [email protected] " , 1 ) ;
1704
let forged = stranger_sk.sign ( Domain:: Attest, & stranger.hash ( ) , & acc.hash ( ) ) ;
1705
late.keep_acceptance (
1706
& v2,
1707
& SignedAttest {
1708
attest: acc.clone ( ) ,
1709
sigs: vec! [ forged] ,
1710
} ,
1711
)
1712
.unwrap ( ) ;
1713
let r = late.push ( " origin" , false ) .unwrap ( ) ;
1714
assert! (
1715
r.iter ( ) .any ( | x | x.name = = " tags/v2" & & ! x.ok ) ,
1716
" not the issuer's: {r:?}"
1717
) ;
1718
let theirs = SignedAttest {
1719
attest: acc,
1720
sigs: vec! [ sig] ,
1721
} ;
1722
1723
late.keep_acceptance ( & v2, & theirs) .unwrap ( ) ;
1724
let r = late.push ( " origin" , false ) .unwrap ( ) ;
1725
1726
1727
assert! ( r.iter ( ) .any ( | x | x.name = = " tags/v2" & & x.ok ) , " {r:?}" ) ;
d8524a9f Andy McCall 17 days ago ↶
1728
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
1ec6fdbc Andy McCall 17 days ago ↶
1729
let ( hv2, _ ) = host.tag_attest ( " v2" ) .unwrap ( ) .unwrap ( ) ;
1730
assert_eq! ( hv2, v2) ;
1731
assert! ( host
1732
.store ( )
1733
.index_get_pub ( " accepted" , & v2)
1734
.unwrap ( )
1735
.contains ( & theirs.attest.hash ( ) ) ) ;
1736
let c = Repo:: clone( & url, & root.join ( " c" ) )
1737
.unwrap ( )
1738
.with_home ( root.join ( " home-c" ) ) ;
1739
assert_eq! ( c.tag_heads ( " v2" ) .unwrap ( ) , Some ( heads.clone ( ) ) ) ;
1740
assert_eq! ( c.tag_heads ( " v1" ) .unwrap ( ) , Some ( heads) ) ;
d8524a9f Andy McCall 17 days ago ↶
1741
}
1742
d36ba2f7 Andy McCall 17 days ago ↶
1743
1744
1745
1746
1747
1748
# [ test ]
1749
fn a_bookmark_moved_by_a_certified_key_alone_is_fetched ( ) {
1750
use crate :: cert:: { Certificate, SignedCertificate} ;
1751
use crate :: identity:: Scope;
1752
hermetic ( ) ;
1753
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1754
let root = dir.path ( ) ;
1755
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1756
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1757
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
1758
.unwrap ( )
1759
.with_home ( root.join ( " home-a" ) ) ;
1760
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1761
.unwrap ( ) ;
1762
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1763
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1764
a.policy_init ( ) .unwrap ( ) ;
1765
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1766
let hub_sk = SecretKey:: generate( ) ;
1767
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1768
a.policy_grant_subject (
1769
" agent:claude@ada" ,
1770
hub.clone ( ) ,
1771
Scope:: CHANGES | Scope:: REFS ,
1772
None ,
1773
)
1774
.unwrap ( ) ;
1775
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1776
1777
let now = Hlc:: now( ) .wall_ms;
1778
let mut agent = Repo:: clone( & url, & root.join ( " agent" ) )
1779
.unwrap ( )
1780
.with_home ( root.join ( " home-agent" ) ) ;
1781
let ( key, id) = agent
1782
.create_identity ( " claude" , " [email protected] " , IdentityScope:: User)
1783
.unwrap ( ) ;
1784
let cert = SignedCertificate:: sign(
1785
Certificate {
1786
key: id,
1787
issuer: hub.hash ( ) ,
1788
subject: " agent:claude@ada" .into ( ) ,
1789
claims: vec! [ ] ,
1790
scope: Scope:: CHANGES | Scope:: REFS ,
1791
not_before: now - 60_000 ,
1792
expires: now + 600_000 ,
1793
grant: None ,
1794
} ,
1795
& hub,
1796
& hub_sk,
1797
) ;
1798
agent.store ( ) .put_identity ( & hub) .unwrap ( ) ;
1799
agent.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1800
let heads = agent.bookmark_heads ( ) .unwrap ( ) ;
1801
agent.bookmark_create ( " agent-work" , Some ( & heads) ) .unwrap ( ) ;
1802
let r = agent.push_bookmark ( " origin" , " agent-work" , false ) .unwrap ( ) ;
1803
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
1804
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
1805
let ( _ , sa) = host.latest_ref_attest ( " agent-work" ) .unwrap ( ) .unwrap ( ) ;
1806
assert_eq! ( sa.sigs[ 0 ] .key, key, " the certified key alone moved it" ) ;
1807
1808
1809
let bundle = host.bundle_raw_for ( & [ ] , None ) .unwrap ( ) ;
1810
assert! ( bundle.changes.is_empty ( ) & & bundle.raw_changes.is_empty ( ) ) ;
1811
assert_eq! ( bundle.certificates.len ( ) , 1 ) ;
1812
for k in [ key, hub.hash ( ) ] {
1813
assert! ( bundle
1814
.objects
1815
.iter ( )
1816
.any ( | ( t , b ) | * t = = ObjectType:: Identity & & hash_object ( * t, b) = = k) ) ;
1817
}
1818
1819
let c = Repo:: clone( & url, & root.join ( " c" ) ) .unwrap ( ) ;
1820
let tracked = c.view ( ) .unwrap ( ) .remotes[ " origin" ] .clone ( ) ;
1821
assert_eq! (
1822
tracked.get ( " agent-work" ) ,
1823
host.view ( ) .unwrap ( ) .bookmarks.get ( " agent-work" )
1824
) ;
1825
assert! ( c.store ( ) .certificate_of ( & key) .unwrap ( ) .is_some ( ) ) ;
1826
}
1827
028545df Andy McCall 18 days ago ↶
1828
1829
1830
1831
1832
1833
1834
# [ test ]
1835
fn a_fetched_attestation_is_judged_at_its_integrated_time ( ) {
1836
use super :: transparency:: LogCheck;
1837
use crate :: cert:: { Certificate, SignedCertificate} ;
1838
use crate :: identity:: Scope;
1839
use crate :: protocol:: { Integration, Logged, RefsResponse, RemoteRef} ;
1840
use crate :: tlog:: { self , Checkpoint, Entry, Hash, Integrated, Note} ;
1841
hermetic ( ) ;
1842
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1843
let root = dir.path ( ) ;
1844
let mut a = Repo:: init( & root.join ( " a" ) )
1845
.unwrap ( )
1846
.with_home ( root.join ( " home-a" ) ) ;
1847
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
1848
.unwrap ( ) ;
1849
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1850
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
1851
a.policy_init ( ) .unwrap ( ) ;
1852
let issuer_sk = SecretKey:: generate( ) ;
1853
let issuer = issuer_sk.identity ( " hub" , " [email protected] " , 1 ) ;
1854
a.policy_grant_subject (
1855
" agent:claude@ada" ,
1856
issuer.clone ( ) ,
1857
Scope:: CHANGES | Scope:: REFS ,
1858
None ,
1859
)
1860
.unwrap ( ) ;
1861
let p = a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy;
1862
1863
1864
let now = Hlc:: now( ) .wall_ms;
1865
let t0 = now - 2 * 3_600_000 ;
1866
let agent_sk = SecretKey:: generate( ) ;
1867
let agent = agent_sk.identity ( " claude" , " [email protected] " , t0) ;
1868
let cert = SignedCertificate:: sign(
1869
Certificate {
1870
key: agent.clone ( ) ,
1871
issuer: issuer.hash ( ) ,
1872
subject: " agent:claude@ada" .into ( ) ,
1873
claims: vec! [ ] ,
1874
scope: Scope:: CHANGES | Scope:: REFS ,
1875
not_before: t0,
1876
expires: t0 + 600_000 ,
1877
grant: None ,
1878
} ,
1879
& issuer,
1880
& issuer_sk,
1881
) ;
1882
a.store ( ) .put_identity ( & agent) .unwrap ( ) ;
1883
a.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
1884
let signer = agent.hash ( ) ;
1885
let attested = | claimed : u64 | {
1886
let attest = Attest {
1887
repo: a.id ( ) .unwrap ( ) ,
1888
hlc: Hlc {
1889
wall_ms: claimed,
1890
logical: 0 ,
1891
} ,
1892
body: AttestBody:: Ref {
1893
name: " main" .into ( ) ,
1894
old_state: ObjectRef( [ 1 ; 32 ] ) ,
1895
new_state: ObjectRef( [ 2 ; 32 ] ) ,
1896
serial: 1 ,
1897
} ,
1898
} ;
1899
let sig = agent_sk.sign ( Domain:: Attest, & signer, & attest.hash ( ) ) ;
1900
SignedAttest {
1901
attest,
1902
sigs: vec! [ sig] ,
1903
}
1904
} ;
1905
1906
1907
1908
let log_sk = SecretKey:: generate( ) ;
1909
let log_key = log_sk.identity ( " hub.example" , " [email protected] " , 1 ) ;
1910
let origin = " hub.example/log" ;
1911
let hub = " https://hub.example" ;
1912
let url = " https://hub.example/ada/a" ;
1913
let w_sk = SecretKey:: generate( ) ;
1914
let w = w_sk.identity ( " w" , " [email protected] " , 1 ) ;
1915
let stranger_sk = SecretKey:: generate( ) ;
1916
let stranger = stranger_sk.identity ( " s" , " [email protected] " , 1 ) ;
1917
let sa = attested ( t0 + 300_000 ) ;
1918
let entry_of = | i : u64 | {
1919
let ( kind, object) = if i = = 3 {
1920
( " ref" , sa.attest.hash ( ) )
1921
} else {
1922
( " seal" , ObjectRef( [ i as u8 ; 32 ] ) )
1923
} ;
1924
Entry {
1925
kind: kind.into ( ) ,
1926
repo: sa.attest.repo,
1927
object,
1928
signers: vec! [ signer] ,
1929
received: t0 + 300_000 ,
1930
}
1931
.encode ( )
1932
} ;
1933
let mut stored: Vec< Hash> = Vec :: new( ) ;
1934
for i in 0 .. 8 {
1935
let add = tlog:: stored_hashes_for_leaf( i, tlog:: leaf_hash( & entry_of ( i) ) , & stored) .unwrap ( ) ;
1936
stored.extend ( add) ;
1937
}
1938
let note_of = | n : u64 , cosigners : & [( & str , & Identity, & SecretKey, u64 ) ]| {
1939
let mut note = Checkpoint {
1940
origin: origin.into ( ) ,
1941
size: n,
1942
root: tlog:: tree_hash( n, & stored) .unwrap ( ) ,
1943
extensions: vec! [ ] ,
1944
}
1945
.sign ( & log_key, & log_sk)
1946
.unwrap ( ) ;
1947
for ( name, id, sk, t) in cosigners {
1948
note.sigs
1949
.extend ( tlog:: cosign( & note.text, name, id, sk, * t) .unwrap ( ) ) ;
1950
}
1951
note.encode ( )
1952
} ;
1953
let latest = note_of ( 8 , & [ ] ) ;
1954
let witnessed_at = ( t0 + 360_000 ) / 1000 ;
1955
let cosigned = note_of (
1956
4 ,
1957
& [
1958
( " w.example" , & w, & w_sk, witnessed_at) ,
1959
( " s.example" , & stranger, & stranger_sk, 1 ) ,
1960
] ,
1961
) ;
1962
let answer = | integration : Vec< Integration> , witnesses : Vec< ( String, Vec< u8 > ) > | RefsResponse {
1963
refs: vec! [ RemoteRef {
1964
name: " main" .into ( ) ,
1965
state: ObjectRef( [ 2 ; 32 ] ) ,
1966
heads: vec! [ ] ,
1967
attestation: Some ( sa.clone ( ) ) ,
1968
last_serial: None ,
1969
accepted: None ,
1970
logged: Some ( Logged {
1971
index: 3 ,
1972
entry: entry_of ( 3 ) ,
1973
proof: tlog:: prove_inclusion( 8 , 3 , & stored) .unwrap ( ) ,
1974
} ) ,
1975
integration,
1976
} ] ,
1977
log: Some ( latest.clone ( ) ) ,
1978
witnesses,
1979
.. RefsResponse:: default( )
1980
} ;
1981
let proof4 = tlog:: prove_inclusion( 4 , 3 , & stored) .unwrap ( ) ;
1982
let served = vec! [ Integration {
1983
note: cosigned.clone ( ) ,
1984
proof: proof4.clone ( ) ,
1985
} ] ;
1986
let listed = vec! [ ( " w.example" .to_string ( ) , w.encode ( ) ) ] ;
1987
1988
a.remember_log ( hub, & log_key, & Note:: parse( & latest) .unwrap ( ) )
1989
.unwrap ( ) ;
1990
1991
1992
1993
let resp = answer ( served.clone ( ) , listed.clone ( ) ) ;
1994
let lc = a.check_log ( url, None , & resp) .unwrap ( ) ;
1995
assert! ( lc.notes.is_empty ( ) , " {:?}" , lc.notes) ;
1996
let t = lc.time_of ( " main" ) .unwrap ( ) ;
1997
assert_eq! ( ( t.time, t.witness.as_str ( ) ) , ( witnessed_at, " w.example" ) ) ;
1998
1999
2000
assert! ( cert.cert.expires < now) ;
2001
let mut notes = Vec :: new( ) ;
2002
a.authorize_in_time ( & p, " origin" , & resp.refs[ 0 ] , & sa, & signer, & lc, & mut notes)
2003
.unwrap ( ) ;
2004
assert! ( notes.is_empty ( ) , " {notes:?}" ) ;
2005
2006
2007
2008
let lonely = vec! [ Integration {
2009
note: note_of ( 4 , & [ ( " s.example" , & stranger, & stranger_sk, 1 ) ] ) ,
2010
proof: proof4.clone ( ) ,
2011
} ] ;
2012
let resp = answer ( lonely, vec! [ ] ) ;
2013
let lc = a.check_log ( url, None , & resp) .unwrap ( ) ;
2014
assert! ( lc
2015
.time_of ( " main" )
2016
.unwrap_err ( )
2017
.contains ( " no witness this user trusts" ) ) ;
2018
let mut notes = Vec :: new( ) ;
2019
a.authorize_in_time ( & p, " origin" , & resp.refs[ 0 ] , & sa, & signer, & lc, & mut notes)
2020
.unwrap ( ) ;
2021
assert! (
2022
notes.len ( ) = = 1 & & notes[ 0 ] .contains ( " not at a witnessed time" ) ,
2023
" {notes:?}"
2024
) ;
2025
2026
2027
2028
let resp = answer (
2029
served.clone ( ) ,
2030
vec! [ ( " w.example" .into ( ) , stranger.encode ( ) ) ] ,
2031
) ;
2032
let lc = a.check_log ( url, None , & resp) .unwrap ( ) ;
2033
assert! (
2034
lc.notes.iter ( ) .any ( | n | n.contains ( " another key" ) ) ,
2035
" {:?}" ,
2036
lc.notes
2037
) ;
2038
assert! (
2039
lc.time_of ( " main" ) .is_err ( ) ,
2040
" w's cosignature is by its old key"
2041
) ;
2042
a.check_log ( url, None , & answer ( served.clone ( ) , listed.clone ( ) ) )
2043
.unwrap ( ) ;
2044
2045
2046
2047
let mut old = answer ( vec! [ ] , vec! [ ] ) ;
2048
old.log = None ;
2049
old.refs[ 0 ] .logged = None ;
2050
let lc = a.check_log ( url, None , & old) .unwrap ( ) ;
2051
assert! ( ! lc.kept & & lc.notes.is_empty ( ) ) ;
2052
let mut notes = Vec :: new( ) ;
2053
a.authorize_in_time ( & p, " origin" , & old.refs[ 0 ] , & sa, & signer, & lc, & mut notes)
2054
.unwrap ( ) ;
2055
assert! ( notes.is_empty ( ) , " {notes:?}" ) ;
2056
2057
2058
let at = | secs_after_t0 : u64 | {
2059
let mut times = std:: collections:: HashMap:: new( ) ;
2060
times.insert (
2061
" main" .to_string ( ) ,
2062
Ok ( Integrated {
2063
time: ( t0 + secs_after_t0 * 1000 ) / 1000 ,
2064
witness: " w.example" .into ( ) ,
2065
size: 4 ,
2066
} ) ,
2067
) ;
2068
LogCheck {
2069
kept: true ,
2070
times,
2071
.. LogCheck:: default( )
2072
}
2073
} ;
2074
let judged = | sa : & SignedAttest, lc : & LogCheck| {
2075
let mut notes = Vec :: new( ) ;
2076
a.authorize_in_time ( & p, " origin" , & old.refs[ 0 ] , sa, & signer, lc, & mut notes)
2077
.map ( | _| notes )
2078
} ;
2079
2080
2081
2082
let notes = judged ( & sa, & at ( 3600 ) ) .unwrap ( ) ;
2083
assert! (
2084
notes
2085
.iter ( )
2086
.any ( | n | n.contains ( " had lapsed when witness w.example" ) )
2087
& & notes
2088
.iter ( )
2089
.any ( | n | n.contains ( " 55 minutes before witness" ) ) ,
2090
" {notes:?}"
2091
) ;
2092
2093
2094
2095
let fast = attested ( now) ;
2096
let notes = judged ( & fast, & at ( 400 ) ) .unwrap ( ) ;
2097
assert! (
2098
notes.len ( ) = = 1 & & notes[ 0 ] .contains ( " after witness w.example saw it" ) ,
2099
" {notes:?}"
2100
) ;
2101
2102
assert! ( judged ( & fast, & at ( 3600 ) ) .is_err ( ) ) ;
2103
2104
assert! ( judged ( & sa, & at ( 500 ) ) .unwrap ( ) .is_empty ( ) ) ;
2105
}
2106
a2387d54 Andy McCall 19 days ago ↶
2107
2108
2109
2110
45a6f5ec Andy McCall 12 days ago ↶
2111
2112
2113
f0a517d2 Andy McCall 19 days ago ↶
2114
# [ test ]
45a6f5ec Andy McCall 12 days ago ↶
2115
fn a_grant_outlives_its_owner_s_rotation ( ) {
2116
use crate :: cert:: { Certificate, Grant, SignedCertificate, SignedGrant} ;
2117
use crate :: identity:: Scope;
2118
hermetic ( ) ;
2119
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2120
let root = dir.path ( ) ;
2121
let mut a = Repo:: init( & root.join ( " a" ) )
2122
.unwrap ( )
2123
.with_home ( root.join ( " home-a" ) ) ;
2124
let ( old, _ ) = a
2125
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
2126
.unwrap ( ) ;
2127
let ( _ , old_sk) = a.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
2128
let old_id = a.identity ( & old) .unwrap ( ) ;
2129
let hub_sk = SecretKey:: generate( ) ;
2130
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
2131
let now = Hlc:: now( ) .wall_ms;
2132
let grant = | from : u64 | {
2133
SignedGrant:: sign(
2134
Grant {
2135
grantor: old,
2136
subject: " agent:claude@ada" .into ( ) ,
2137
issuer: hub.hash ( ) ,
2138
scope: Scope:: CHANGES | Scope:: SEAL ,
2139
not_before: from,
2140
expires: now + 86_400_000 ,
2141
} ,
2142
& old_id,
2143
& old_sk,
2144
)
2145
} ;
2146
let before = grant ( now - 60_000 ) ;
2147
let after = grant ( now + 60_000 ) ;
2148
let rot = a.renew_identity ( None ) .unwrap ( ) ;
2149
assert_ne! ( rot.new, old) ;
2150
let agent = | name : & str , g : SignedGrant| {
2151
let mut r = Repo:: init( & root.join ( name) )
2152
.unwrap ( )
2153
.with_home ( root.join ( format! ( " home-{name} " ) ) ) ;
2154
let ( key, id) = r
2155
.create_identity ( name, " [email protected] " , IdentityScope:: User)
2156
.unwrap ( ) ;
2157
for i in [ & old_id, & rot.identity, & hub] {
2158
r.store ( ) .put_identity ( i) .unwrap ( ) ;
2159
}
2160
r.set_user_config ( " author.key" , & rot.new.to_hex ( ) ) .unwrap ( ) ;
2161
r.set_user_config ( " author.signer" , & key.to_hex ( ) ) .unwrap ( ) ;
2162
let cert = SignedCertificate:: sign(
2163
Certificate {
2164
key: id,
2165
issuer: hub.hash ( ) ,
2166
subject: " agent:claude@ada" .into ( ) ,
2167
claims: vec! [ ] ,
2168
scope: Scope:: CHANGES | Scope:: REFS | Scope:: SEAL ,
2169
not_before: now - 60_000 ,
2170
expires: now + 600_000 ,
2171
grant: Some ( g) ,
2172
} ,
2173
& hub,
2174
& hub_sk,
2175
) ;
2176
r.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
2177
write ( & root.join ( name) , " work.txt" , b " agent work\n " ) ;
2178
let h = r.commit ( " work" , & [ ] ) .unwrap ( ) ;
2179
assert_eq! ( r.store ( ) .get_change_meta ( & h) .unwrap ( ) .author, rot.new) ;
2180
r.seal ( & [ h] ) .unwrap ( ) ;
2181
( r, h)
2182
} ;
2183
let ( r, h) = agent ( " kept" , before) ;
2184
assert! (
2185
matches! (
2186
r.authenticate_change ( & h) .unwrap ( ) ,
2187
SignatureStatus:: Valid { .. }
2188
) ,
2189
" {:?}" ,
2190
r.authenticate_change ( & h) .unwrap ( )
2191
) ;
2192
let ( r, h) = agent ( " late" , after) ;
2193
assert! (
2194
matches! ( r.authenticate_change ( & h) .unwrap ( ) , SignatureStatus:: Invalid( e) if e.contains ( " neither" ) ) ,
2195
" a grant the old key made after the rotation: {:?}" ,
2196
r.authenticate_change ( & h) .unwrap ( )
2197
) ;
2198
}
2199
2200
# [ test ]
a2387d54 Andy McCall 19 days ago ↶
2201
fn a_certified_key_acts_for_its_author_under_a_grant ( ) {
2202
use crate :: cert:: { Certificate, Grant, SignedCertificate, SignedGrant} ;
2203
use crate :: identity:: { Identity, Scope} ;
2204
hermetic ( ) ;
2205
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2206
let root = dir.path ( ) ;
2207
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
2208
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
2209
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
2210
.unwrap ( )
2211
.with_home ( root.join ( " home-a" ) ) ;
2212
let ( ada, _ ) = a
2213
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
2214
.unwrap ( ) ;
2215
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
2216
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
2217
a.policy_init ( ) .unwrap ( ) ;
2218
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
2219
let hub_sk = SecretKey:: generate( ) ;
2220
let hub = hub_sk.identity ( " hub" , " [email protected] " , 1 ) ;
2221
a.policy_grant_subject (
2222
" agent:claude@ada" ,
2223
hub.clone ( ) ,
2224
Scope:: CHANGES | Scope:: REFS ,
2225
None ,
2226
)
2227
.unwrap ( ) ;
2228
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
2229
let ( _ , ada_sk) = a.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
2230
let ada_id = a.identity ( & ada) .unwrap ( ) ;
2231
2232
let now = Hlc:: now( ) .wall_ms;
2233
let grant = | by : & Identity, sk : & SecretKey, from : u64 , to : u64 | {
2234
SignedGrant:: sign(
2235
Grant {
2236
grantor: by.hash ( ) ,
2237
subject: " agent:claude@ada" .into ( ) ,
2238
issuer: hub.hash ( ) ,
2239
scope: Scope:: CHANGES | Scope:: SEAL ,
2240
not_before: from,
2241
expires: to,
2242
} ,
2243
by,
2244
sk,
2245
)
2246
} ;
2247
2248
2249
let agent = | name : & str , g : Option< SignedGrant> | {
2250
let mut r = Repo:: clone( & url, & root.join ( name) )
2251
.unwrap ( )
2252
.with_home ( root.join ( format! ( " home-{name} " ) ) ) ;
2253
let ( key, id) = r
2254
.create_identity ( name, " [email protected] " , IdentityScope:: User)
2255
.unwrap ( ) ;
2256
r.set_user_config ( " author.key" , & ada.to_hex ( ) ) .unwrap ( ) ;
2257
r.set_user_config ( " author.signer" , & key.to_hex ( ) ) .unwrap ( ) ;
2258
let cert = SignedCertificate:: sign(
2259
Certificate {
2260
key: id,
2261
issuer: hub.hash ( ) ,
2262
subject: " agent:claude@ada" .into ( ) ,
2263
claims: vec! [ ] ,
2264
scope: Scope:: CHANGES | Scope:: REFS | Scope:: SEAL ,
2265
not_before: now - 60_000 ,
2266
expires: now + 600_000 ,
2267
grant: g,
2268
} ,
2269
& hub,
2270
& hub_sk,
2271
) ;
2272
r.store ( ) .put_identity ( & hub) .unwrap ( ) ;
2273
r.store ( ) .put_certificate ( & cert) .unwrap ( ) ;
2274
write ( & root.join ( name) , & format! ( " {name} .txt" ) , b " agent work\n " ) ;
2275
let h = r.commit ( " work" , & [ ] ) .unwrap ( ) ;
2276
assert_eq! ( r.store ( ) .get_change_meta ( & h) .unwrap ( ) .author, ada) ;
2277
( r, key, h)
2278
} ;
2279
2280
let ( claude, key, h) = agent (
2281
" claude" ,
2282
Some ( grant ( & ada_id, & ada_sk, now - 60_000 , now + 86_400_000 ) ) ,
2283
) ;
2284
let r = claude.push ( " origin" , false ) .unwrap ( ) ;
2285
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
2286
let host = Repo:: open( & root.join ( " origin" ) ) .unwrap ( ) ;
68dcf21f Andy McCall 19 days ago ↶
2287
assert! (
2288
matches! (
2289
host.authenticate_change ( & h) .unwrap ( ) ,
2290
SignatureStatus:: Valid { key: k, sealed: true , name, .. } if k = = key & & name = = " agent:claude@ada"
2291
) ,
2292
" named by its certificate's subject"
2293
) ;
a2387d54 Andy McCall 19 days ago ↶
2294
a.pull ( " origin" ) .unwrap ( ) ;
2295
assert! ( matches! (
2296
a.authenticate_change ( & h) .unwrap ( ) ,
2297
SignatureStatus:: Valid { key: k, .. } if k = = key
2298
) ) ;
2299
2300
let ( none, _ , _ ) = agent ( " none" , None ) ;
2301
let r = none.push ( " origin" , false ) .unwrap ( ) ;
2302
assert! ( r.iter ( ) .all ( | x | ! x.ok) , " no grant: {r:?}" ) ;
2303
let mallory_sk = SecretKey:: generate( ) ;
2304
let mallory = mallory_sk.identity ( " Mallory" , " [email protected] " , 1 ) ;
2305
let ( stranger, _ , _ ) = agent (
2306
" stranger" ,
2307
Some ( grant ( & mallory, & mallory_sk, now - 60_000 , now + 600_000 ) ) ,
2308
) ;
2309
let r = stranger.push ( " origin" , false ) .unwrap ( ) ;
2310
assert! ( r.iter ( ) .all ( | x | ! x.ok) , " a stranger's grant: {r:?}" ) ;
2311
let ( ended, _ , eh) = agent (
2312
" ended" ,
2313
Some ( grant ( & ada_id, & ada_sk, now - 7_200_000 , now - 3_600_000 ) ) ,
2314
) ;
2315
let r = ended.push ( " origin" , false ) .unwrap ( ) ;
2316
assert! ( r.iter ( ) .all ( | x | ! x.ok) , " an ended grant: {r:?}" ) ;
2317
assert! ( matches! (
2318
ended.authenticate_change ( & eh) .unwrap ( ) ,
2319
SignatureStatus:: Invalid( e) if e.contains ( " grant did not cover" )
2320
) ) ;
2321
}
2322
2323
# [ test ]
83e0677b Andy McCall 1 month ago ↶
2324
fn trust_policy_governs_pushes_and_fetches ( ) {
2325
hermetic ( ) ;
2326
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2327
let root = dir.path ( ) ;
2328
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
2329
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
2330
2331
75167672 Andy McCall 1 month ago
2332
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
2333
.unwrap ( )
2334
.with_home ( root.join ( " home-a" ) ) ;
2335
let ( key_a, _ ) = a
2336
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
2337
.unwrap ( ) ;
2338
let mut b = Repo:: clone( & url, & root.join ( " b" ) )
2339
.unwrap ( )
2340
.with_home ( root.join ( " home-b" ) ) ;
83e0677b Andy McCall 1 month ago ↶
2341
write ( & root.join ( " b" ) , " b.txt" , b " b\n " ) ;
2342
b.commit ( " unsigned before policy" , & [ ] ) .unwrap ( ) ;
2343
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
2344
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2345
2346
2347
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
2348
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
2349
let sp = a.policy_init ( ) .unwrap ( ) ;
2350
assert_eq! ( sp.policy.serial, 1 ) ;
2351
a.pull ( " origin" ) .unwrap ( ) ;
2352
let results = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
2353
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2354
assert! ( results.iter ( ) .all ( | r | r.ok ) , " {results:?}" ) ;
2355
assert_eq! ( origin.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 1 ) ;
2356
2357
2358
b.pull ( " origin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2359
assert! (
2360
b.current_policy ( ) .unwrap ( ) .is_some ( ) ,
2361
" the policy arrived with the fetch"
2362
) ;
83e0677b Andy McCall 1 month ago ↶
2363
assert! ( b.push ( " origin" , false ) .is_err ( ) ) ;
2364
75167672 Andy McCall 1 month ago
2365
let ( key_b, id_b) = b
2366
.create_identity ( " Bob" , " [email protected] " , IdentityScope:: User)
2367
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2368
write ( & root.join ( " b" ) , " b.txt" , b " b2\n " ) ;
2369
let h_b2 = b.commit ( " b2 as Bob" , & [ ] ) .unwrap ( ) ;
2370
assert_eq! ( b.store ( ) .get_change ( & h_b2) .unwrap ( ) .author, key_b) ;
2371
let r = b.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
2372
origin.refresh ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2373
assert! (
2374
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " may not update" ) ,
2375
" {:?}" ,
2376
r
2377
) ;
83e0677b Andy McCall 1 month ago ↶
2378
2379
2380
let sp = a.policy_grant ( id_b.clone ( ) , None ) .unwrap ( ) ;
2381
assert_eq! ( sp.policy.serial, 2 ) ;
2382
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
2383
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2384
assert! ( r.iter ( ) .any ( | x | x.name = = " policy" & & x.ok ) , " {r:?}" ) ;
2385
75167672 Andy McCall 1 month ago
2386
assert! ( b
2387
.push ( " origin" , false )
2388
.unwrap_or_else ( | e | vec! [ RefResult {
2389
name: " main" .into ( ) ,
2390
ok: false ,
2391
reason: e.to_string ( )
2392
} ] )
2393
.iter ( )
2394
.all ( | x | ! x.ok) ) ;
83e0677b Andy McCall 1 month ago ↶
2395
b.pull ( " origin" ) .unwrap ( ) ;
2396
assert_eq! ( b.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 2 ) ;
2397
let r = b.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
2398
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2399
assert! ( r[ 0 ] .ok, " {:?}" , r) ;
2400
2401
let heads = origin.bookmark_heads ( ) .unwrap ( ) ;
2402
for h in & heads {
2403
let c = origin.store ( ) .get_change ( h) .unwrap ( ) ;
2404
if c.author = = key_b {
75167672 Andy McCall 1 month ago
2405
origin
2406
.change_allowed (
2407
h,
2408
& origin.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy,
2409
" main" ,
2410
)
2411
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2412
}
2413
}
2414
2415
2416
let mut forged = b.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.clone ( ) ;
2417
forged.serial = 3 ;
2418
forged.roots = vec! [ key_b] ;
2419
let fh = forged.hash ( ) ;
2420
let ( _ , sk_b) = b.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2421
let forged = SignedPolicy {
2422
policy: forged,
2423
sigs: vec! [ sk_b.sign ( Domain:: Policy, & key_b, & fh) ] ,
2424
} ;
2425
let r = origin
2426
.receive_push ( Bundle:: default( ) , vec! [ ] , Some ( forged) , vec! [ ] )
2427
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2428
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " root" ) , " {:?}" , r) ;
2429
let stale = a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1 ;
2430
let mut old = stale.clone ( ) ;
2431
old.policy.serial = 1 ;
75167672 Andy McCall 1 month ago
2432
let r = origin
2433
.receive_push ( Bundle:: default( ) , vec! [ ] , Some ( old) , vec! [ ] )
2434
.unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2435
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " serial" ) , " {:?}" , r) ;
2436
2437
2438
a.pull ( " origin" ) .unwrap ( ) ;
2439
a.policy_revoke ( & key_b) .unwrap ( ) ;
2440
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
2441
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2442
assert! ( r.iter ( ) .any ( | x | x.name = = " policy" & & x.ok ) , " {r:?}" ) ;
2443
b.pull ( " origin" ) .unwrap ( ) ;
2444
write ( & root.join ( " b" ) , " b.txt" , b " b3\n " ) ;
2445
b.commit ( " b3" , & [ ] ) .unwrap ( ) ;
2446
let r = b.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
2447
origin.refresh ( ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
2448
assert! ( ! r[ 0 ] .ok, " {:?}" , r) ;
2449
2450
75167672 Andy McCall 1 month ago
2451
let c = Repo:: clone( & url, & root.join ( " c" ) )
2452
.unwrap ( )
2453
.with_home ( root.join ( " home-c" ) ) ;
83e0677b Andy McCall 1 month ago ↶
2454
assert_eq! ( c.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 3 ) ;
75167672 Andy McCall 1 month ago
2455
assert! ( c
2456
.current_policy ( )
2457
.unwrap ( )
2458
.unwrap ( )
2459
.1
2460
.policy
2461
.roots
2462
.contains ( & key_a) ) ;
83e0677b Andy McCall 1 month ago ↶
2463
assert! ( ! c.snapshot ( " status" ) .unwrap ( ) .changed) ;
2464
}
d02e545d Andy McCall 1 month ago ↶
2465
9ae4d15a Andy McCall 1 month ago ↶
2466
d02e545d Andy McCall 1 month ago ↶
2467
# [ test ]
9ae4d15a Andy McCall 1 month ago ↶
2468
fn gc_reports_its_phases ( ) {
2469
hermetic ( ) ;
2470
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2471
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
2472
for i in 0 .. 3 {
2473
write (
2474
dir.path ( ) ,
2475
& format! ( " f{i} .txt" ) ,
2476
format! ( " {i} \n " ) .as_bytes ( ) ,
2477
) ;
2478
repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ;
2479
}
2480
let mut phases = Vec :: new( ) ;
2481
repo.gc_with ( true , & mut | p| phases.push ( p.to_string ( ) ) )
2482
.unwrap ( ) ;
2483
assert_eq! ( phases[ 0 ] , " finding what is reachable" , " {phases:?}" ) ;
2484
assert! (
2485
phases.iter ( ) .any ( | p | p.starts_with ( " pruning" ) ) ,
2486
" {phases:?}"
2487
) ;
2488
assert! (
2489
phases.iter ( ) .any ( | p | p.starts_with ( " packing 0 of " ) ) ,
2490
" {phases:?}"
2491
) ;
2492
assert! (
2493
phases
2494
.iter ( )
2495
.any ( | p | p.starts_with ( " writing the index of " ) ) ,
2496
" {phases:?}"
2497
) ;
2498
}
2499
2500
# [ test ]
d02e545d Andy McCall 1 month ago ↶
2501
fn gc_packs_objects_and_history_still_works ( ) {
2502
hermetic ( ) ;
2503
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2504
let root = dir.path ( ) ;
2505
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
2506
repo.create_identity ( " Ada" , " a@b" , IdentityScope:: User)
2507
.unwrap ( ) ;
d02e545d Andy McCall 1 month ago ↶
2508
repo.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
2509
write ( root, " a.txt" , b " one\n " ) ;
2510
let h1 = repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
2511
write ( root, " a.txt" , b " two\n " ) ;
2512
write ( root, " big.bin" , & vec! [ 0 u8 ; 100_000 ] ) ;
2513
let h2 = repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
ba160f60 Andy McCall 1 month ago ↶
2514
2515
for i in 0 .. 5 {
2516
write ( root, " a.txt" , format! ( " two\n {i} \n " ) .as_bytes ( ) ) ;
2517
repo.snapshot ( " status" ) .unwrap ( ) ;
2518
}
2519
write ( root, " a.txt" , b " two\n " ) ;
2520
repo.snapshot ( " status" ) .unwrap ( ) ;
d02e545d Andy McCall 1 month ago ↶
2521
let before = repo.store ( ) .list ( ) .unwrap ( ) ;
2522
assert! ( repo.store ( ) .loose_count ( ) .unwrap ( ) > 0 ) ;
ba160f60 Andy McCall 1 month ago ↶
2523
2524
2525
let r = repo.gc ( true ) .unwrap ( ) ;
2526
assert_eq! ( r.packed.objects, before.len ( ) ) ;
2527
assert_eq! ( r.pruned.objects, 0 ) ;
d02e545d Andy McCall 1 month ago ↶
2528
assert_eq! ( repo.store ( ) .loose_count ( ) .unwrap ( ) , 0 ) ;
2529
assert_eq! ( repo.store ( ) .list ( ) .unwrap ( ) , before) ;
ba160f60 Andy McCall 1 month ago ↶
2530
2531
2532
2533
repo.config.set ( " core.op-retention" , " 0" ) .unwrap ( ) ;
2534
repo.save_config ( ) .unwrap ( ) ;
21b2aec2 Andy McCall 1 month ago ↶
2535
let mut repo = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
ba160f60 Andy McCall 1 month ago ↶
2536
let r = repo.gc ( true ) .unwrap ( ) ;
2537
assert! ( r.pruned.objects > = 5 , " {r:?}" ) ;
2538
let after = repo.store ( ) .list ( ) .unwrap ( ) ;
2539
assert! ( after.len ( ) < before.len ( ) ) ;
2540
assert! ( after.contains ( & h1) & & after.contains ( & h2) ) ;
2541
for ( _ , op) in repo.op_log ( ) .unwrap ( ) {
2542
assert! ( ! op.command.is_empty ( ) , " operation objects are all kept" ) ;
2543
}
2544
d02e545d Andy McCall 1 month ago ↶
2545
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
75167672 Andy McCall 1 month ago
2546
assert! ( matches! (
2547
repo.verify_change ( & h2) .unwrap ( ) ,
2548
SignatureStatus:: Valid { .. }
2549
) ) ;
d02e545d Andy McCall 1 month ago ↶
2550
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 3 ) ;
2551
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
2552
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n " ) ;
2553
repo.checkout ( & [ h2] , false , " checkout" ) .unwrap ( ) ;
2554
assert_eq! ( fs:: read( root.join ( " big.bin" ) ) .unwrap ( ) .len ( ) , 100_000 ) ;
2555
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
ba160f60 Andy McCall 1 month ago ↶
2556
d02e545d Andy McCall 1 month ago ↶
2557
write ( root, " a.txt" , b " three\n " ) ;
2558
repo.commit ( " three" , & [ ] ) .unwrap ( ) ;
ba160f60 Andy McCall 1 month ago ↶
2559
let r = repo.gc ( false ) .unwrap ( ) ;
2560
assert_eq! ( r.pruned.objects, 0 ) ;
2561
assert! ( r.pruned.deferred > 0 ) ;
2562
let r = repo.gc ( true ) .unwrap ( ) ;
2563
assert! ( r.pruned.objects > 0 ) ;
2564
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 ) ;
21b2aec2 Andy McCall 1 month ago ↶
2565
2566
2567
repo.config.set ( " core.op-retention" , " 90" ) .unwrap ( ) ;
2568
repo.save_config ( ) .unwrap ( ) ;
2569
let repo = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2570
let r = repo.gc ( false ) .unwrap ( ) ;
2571
assert_eq! ( r.pruned.objects, 0 ) ;
2572
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 ) ;
d02e545d Andy McCall 1 month ago ↶
2573
}
614541c3 Andy McCall 1 month ago ↶
2574
2575
# [ test ]
2576
fn describe_amend_abandon_heads ( ) {
2577
hermetic ( ) ;
2578
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2579
let root = dir.path ( ) ;
2580
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
2581
repo.create_identity ( " Ada" , " a@b" , IdentityScope:: User)
2582
.unwrap ( ) ;
614541c3 Andy McCall 1 month ago ↶
2583
repo.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
2584
write ( root, " a.txt" , b " one\n " ) ;
2585
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
2586
write ( root, " a.txt" , b " one\n two\n " ) ;
2587
let h2 = repo.commit ( " secnod" , & [ ] ) .unwrap ( ) ;
2588
let id2 = repo.store ( ) .get_change ( & h2) .unwrap ( ) .change_id;
2589
2590
2591
let h2b = repo.describe ( & h2, " second" ) .unwrap ( ) ;
2592
assert_ne! ( h2b, h2) ;
2593
let c = repo.store ( ) .get_change ( & h2b) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2594
assert_eq! (
2595
( c.change_id, c.message.as_str ( ) , c.deps.clone ( ) ) ,
2596
( id2, " second" , vec! [ h1] )
2597
) ;
614541c3 Andy McCall 1 month ago ↶
2598
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2b] ) ;
75167672 Andy McCall 1 month ago
2599
assert! ( matches! (
2600
repo.verify_change ( & h2b) .unwrap ( ) ,
2601
SignatureStatus:: Valid { .. }
2602
) ) ;
614541c3 Andy McCall 1 month ago ↶
2603
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
2604
assert! ( s.statuses.is_empty ( ) , " {:?}" , s.statuses) ;
75167672 Andy McCall 1 month ago
2605
assert_eq! (
2606
repo.resolve_change ( & id2.short ( 8 ) ) .unwrap ( ) ,
2607
h2b,
2608
" the old object is no longer reachable from the log"
2609
) ;
c07e0e7d Andy McCall 1 month ago ↶
2610
2611
let h1b = repo.describe ( & h1, " first, described" ) .unwrap ( ) ;
2612
assert_ne! ( h1b, h1) ;
2613
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
2614
assert_eq! ( heads.len ( ) , 1 ) ;
2615
let top = repo.store ( ) .get_change ( & heads[ 0 ] ) .unwrap ( ) ;
2616
assert_eq! ( ( top.change_id, top.deps.clone ( ) ) , ( id2, vec! [ h1b] ) ) ;
75167672 Andy McCall 1 month ago
2617
assert! (
2618
matches! (
2619
repo.verify_change ( & heads[ 0 ] ) .unwrap ( ) ,
2620
SignatureStatus:: Valid { .. }
2621
) ,
2622
" rewritten own change is re-signed"
2623
) ;
c07e0e7d Andy McCall 1 month ago ↶
2624
let ( _ , wc) = repo.working_copy ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2625
assert_eq! (
2626
wc.deps,
2627
vec! [ heads[ 0 ] ] ,
2628
" the working copy follows the rewrite"
2629
) ;
c07e0e7d Andy McCall 1 month ago ↶
2630
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
2631
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
2632
assert! (
2633
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n " )
2634
) ;
614541c3 Andy McCall 1 month ago ↶
2635
2636
2637
write ( root, " a.txt" , b " one\n two\n three\n " ) ;
2638
write ( root, " b.txt" , b " b\n " ) ;
2639
let h2c = repo.amend ( None ) .unwrap ( ) ;
2640
let c = repo.store ( ) .get_change ( & h2c) .unwrap ( ) ;
2641
assert_eq! ( c.change_id, id2) ;
2642
assert_eq! ( c.message, " second" ) ;
2643
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2c] ) ;
75167672 Andy McCall 1 month ago
2644
assert_eq! (
2645
repo.log ( ) .unwrap ( ) .len ( ) ,
2646
3 ,
2647
" working copy, amended head, first"
2648
) ;
614541c3 Andy McCall 1 month ago ↶
2649
let snap = repo.load_state ( & [ h2c] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
2650
assert! (
2651
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n three\n " )
2652
) ;
614541c3 Andy McCall 1 month ago ↶
2653
assert! ( snap.files.contains_key ( " b.txt" ) ) ;
2654
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
75167672 Andy McCall 1 month ago
2655
assert! (
2656
repo.amend ( None ) .is_err ( ) ,
2657
" clean working copy and no message"
2658
) ;
614541c3 Andy McCall 1 month ago ↶
2659
let h2d = repo.amend ( Some ( " second, amended" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2660
assert_eq! (
2661
repo.store ( ) .get_change ( & h2d) .unwrap ( ) .message,
2662
" second, amended"
2663
) ;
614541c3 Andy McCall 1 month ago ↶
2664
2665
2666
repo.abandon ( & h2d) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2667
assert_eq! (
2668
repo.bookmark_heads ( ) .unwrap ( ) ,
2669
vec! [ h1b] ,
2670
" the described first change is the head again"
2671
) ;
614541c3 Andy McCall 1 month ago ↶
2672
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2673
let paths: Vec< _ > = s
2674
.statuses
2675
.iter ( )
2676
.map ( | p | ( p.path.as_str ( ) , p.status) )
2677
.collect ( ) ;
2678
assert_eq! (
2679
paths,
2680
vec! [
2681
( " a.txt" , ChangeStatus:: Modified) ,
2682
( " b.txt" , ChangeStatus:: Added)
2683
]
2684
) ;
614541c3 Andy McCall 1 month ago ↶
2685
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n two\n three\n " ) ;
2686
let h3 = repo.commit ( " recommitted" , & [ ] ) .unwrap ( ) ;
2687
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h3] ) ;
2688
2689
repo.undo ( ) .unwrap ( ) ;
2690
repo.undo ( ) .unwrap ( ) ;
2691
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2d] ) ;
c07e0e7d Andy McCall 1 month ago ↶
2692
6706bdcf Andy McCall 1 month ago ↶
2693
2694
2695
c07e0e7d Andy McCall 1 month ago ↶
2696
let base = repo.bookmark_heads ( ) .unwrap ( ) [ 0 ] ;
2697
let base_change = repo.store ( ) .get_change ( & base) .unwrap ( ) ;
2698
let h1_now = base_change.deps[ 0 ] ;
6706bdcf Andy McCall 1 month ago ↶
2699
let notes = repo.abandon ( & h1_now) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2700
assert! (
2701
! notes.is_empty ( ) ,
2702
" the dependent's edit of a.txt was reified"
2703
) ;
6706bdcf Andy McCall 1 month ago ↶
2704
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
2705
let after = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
2706
assert! (
2707
! after.files.contains_key ( " a.txt" ) & & after.files.contains_key ( " b.txt" ) ,
2708
" {:?}" ,
2709
after.files.keys ( )
2710
) ;
6706bdcf Andy McCall 1 month ago ↶
2711
repo.undo ( ) .unwrap ( ) ;
2712
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ base] ) ;
c07e0e7d Andy McCall 1 month ago ↶
2713
write ( root, " c.txt" , b " c\n " ) ;
2714
let hc = repo.commit ( " c" , & [ ] ) .unwrap ( ) ;
2715
write ( root, " d.txt" , b " d\n " ) ;
2716
let hd = repo.commit ( " d" , & [ hc] ) .unwrap ( ) ;
2717
assert_eq! ( repo.store ( ) .get_change ( & hd) .unwrap ( ) .deps, vec! [ hc] ) ;
2718
repo.abandon ( & hc) .unwrap ( ) ;
2719
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
2720
assert! ( ! heads.contains ( & hc) ) ;
75167672 Andy McCall 1 month ago
2721
let d_now = heads
2722
.iter ( )
2723
.find ( | h | repo.store ( ) .get_change ( h) .unwrap ( ) .message = = " d" )
2724
.copied ( )
2725
.unwrap ( ) ;
2726
assert! (
2727
repo.store ( ) .get_change ( & d_now) .unwrap ( ) .deps.is_empty ( ) ,
2728
" d inherited c's (empty) dependencies"
2729
) ;
c07e0e7d Andy McCall 1 month ago ↶
2730
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
2731
assert_eq! (
2732
s.statuses
2733
.iter ( )
2734
.map ( | p | p.path.as_str ( ) )
2735
.collect:: < Vec< _ > > ( ) ,
2736
vec! [ " c.txt" ] ,
2737
" c's content is now uncommitted work"
2738
) ;
614541c3 Andy McCall 1 month ago ↶
2739
}
79ab8528 Andy McCall 1 month ago ↶
2740
2741
# [ test ]
2742
fn git_export_mirrors_history ( ) {
2743
hermetic ( ) ;
75167672 Andy McCall 1 month ago
2744
if std:: process:: Command:: new( " git" )
2745
.arg ( " --version" )
2746
.output ( )
2747
.is_err ( )
2748
{
79ab8528 Andy McCall 1 month ago ↶
2749
eprintln! ( " git not installed; skipping" ) ;
2750
return ;
2751
}
2752
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2753
let root = dir.path ( ) ;
75167672 Andy McCall 1 month ago
2754
let mut repo = Repo:: init( & root.join ( " r" ) )
2755
.unwrap ( )
2756
.with_home ( root.join ( " home" ) ) ;
2757
repo.create_identity ( " Ada Lovelace" , " [email protected] " , IdentityScope:: User)
2758
.unwrap ( ) ;
79ab8528 Andy McCall 1 month ago ↶
2759
let r = root.join ( " r" ) ;
2760
write ( & r, " README.md" , b " hello\n " ) ;
2761
write ( & r, " src/main.rs" , b " fn main() {}\n " ) ;
2762
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
2763
write ( & r, " src/main.rs" , b " fn main() { run(); }\n " ) ;
2764
write ( & r, " logo.png" , b " \x89 PNG\0 \0 " ) ;
2765
let h2 = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
2766
2767
let git_dir = root.join ( " mirror.git" ) ;
2768
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
2769
assert_eq! ( ( rep.exported, rep.skipped, rep.heads) , ( 2 , 0 , 1 ) ) ;
2770
let git = | args : & [& str ]| -> String {
75167672 Andy McCall 1 month ago
2771
let out = std:: process:: Command:: new( " git" )
2772
.arg ( " -C" )
2773
.arg ( & git_dir)
2774
.args ( args)
2775
.output ( )
2776
.unwrap ( ) ;
2777
assert! (
2778
out.status.success ( ) ,
2779
" {}" ,
2780
String :: from_utf8_lossy( & out.stderr)
2781
) ;
79ab8528 Andy McCall 1 month ago ↶
2782
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
2783
} ;
2784
assert_eq! ( git ( & [ " log" , " --format=%s" , " main" ] ) , " second\n first" ) ;
2785
assert_eq! ( git ( & [ " show" , " main:src/main.rs" ] ) , " fn main() { run(); }" ) ;
2786
assert_eq! ( git ( & [ " show" , " main:README.md" ] ) , " hello" ) ;
2787
let body = git ( & [ " log" , " -1" , " --format=%B" , " main" ] ) ;
75167672 Andy McCall 1 month ago
2788
assert! (
2789
body.contains ( & format! ( " Oxy-Hash: {} " , h2.to_hex ( ) ) ) ,
2790
" {body}"
2791
) ;
79ab8528 Andy McCall 1 month ago ↶
2792
assert! ( body.contains ( " Change-Id: " ) , " {body}" ) ;
75167672 Andy McCall 1 month ago
2793
assert_eq! (
2794
git ( & [ " log" , " -1" , " --format=%an <%ae>" , " main" ] ) ,
2795
" Ada Lovelace <[email protected] >"
2796
) ;
2797
assert_eq! (
2798
git ( & [ " ls-tree" , " --name-only" , " main" ] ) ,
2799
" README.md\n logo.png\n src"
2800
) ;
79ab8528 Andy McCall 1 month ago ↶
2801
2802
2803
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
2804
assert_eq! ( ( rep.exported, rep.skipped) , ( 0 , 2 ) ) ;
2805
2806
2807
2808
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
2809
write ( & r, " other.txt" , b " o\n " ) ;
2810
let h3 = repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
2811
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
2812
assert_eq! ( ( rep.exported, rep.heads) , ( 1 , 2 ) ) ;
2813
assert_eq! ( git ( & [ " log" , " --format=%s" , " main" ] ) , " third\n second\n first" ) ;
2814
let parents = git ( & [ " rev-list" , " --parents" , " -1" , " main" ] ) ;
2815
assert_eq! ( parents.split ( ' ' ) .count ( ) , 2 , " one parent: {parents}" ) ;
2816
assert_eq! ( git ( & [ " show" , " main:other.txt" ] ) , " o" ) ;
2817
assert_eq! ( git ( & [ " show" , " main:src/main.rs" ] ) , " fn main() { run(); }" ) ;
75167672 Andy McCall 1 month ago
2818
assert_eq! (
2819
git ( & [ " ls-tree" , " --name-only" , " main" ] ) ,
2820
" README.md\n logo.png\n other.txt\n src"
2821
) ;
79ab8528 Andy McCall 1 month ago ↶
2822
let body = git ( & [ " log" , " -1" , " --format=%B" , " main" ] ) ;
75167672 Andy McCall 1 month ago
2823
assert! (
2824
body.contains ( & format! ( " Oxy-Hash: {} " , h3.to_hex ( ) ) ) & & body.contains ( " Oxy-State: " ) ,
2825
" {body}"
2826
) ;
79ab8528 Andy McCall 1 month ago ↶
2827
2828
repo.describe ( & h3, " third, described" ) .unwrap ( ) ;
2829
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
2830
assert_eq! ( rep.exported, 1 ) ;
75167672 Andy McCall 1 month ago
2831
assert_eq! (
2832
git ( & [ " log" , " -1" , " --format=%s" , " main" ] ) ,
2833
" third, described"
2834
) ;
79ab8528 Andy McCall 1 month ago ↶
2835
assert_eq! ( git ( & [ " log" , " --format=%s" , " main" ] ) .lines ( ) .count ( ) , 4 ) ;
2836
let _ = h2;
2837
}
c3069537 Andy McCall 1 month ago ↶
2838
2839
# [ test ]
2840
fn git_import_reproduces_trees ( ) {
c3d2ef61 Andy McCall 1 month ago ↶
2841
git_import_reproduces_trees_with ( None ) ;
2842
}
2843
2844
2845
2846
# [ test ]
2847
fn git_import_reproduces_trees_in_batches ( ) {
2848
git_import_reproduces_trees_with ( Some ( 1 ) ) ;
2849
}
2850
2851
fn git_import_reproduces_trees_with ( batch : Option< usize > ) {
2852
crate :: repo:: git:: set_merge_batch_for_tests( batch) ;
c3069537 Andy McCall 1 month ago ↶
2853
hermetic ( ) ;
75167672 Andy McCall 1 month ago
2854
if std:: process:: Command:: new( " git" )
2855
.arg ( " --version" )
2856
.output ( )
2857
.is_err ( )
2858
{
c3069537 Andy McCall 1 month ago ↶
2859
eprintln! ( " git not installed; skipping" ) ;
2860
return ;
2861
}
2862
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2863
let root = dir.path ( ) ;
2864
let g = root.join ( " src.git" ) ;
2865
fs:: create_dir_all( & g) .unwrap ( ) ;
2866
let git = | args : & [& str ]| -> String {
2867
let out = std:: process:: Command:: new( " git" )
2868
.arg ( " -C" )
2869
.arg ( & g)
75167672 Andy McCall 1 month ago
2870
.args ( [
2871
" -c" ,
2872
" user.name=Grace" ,
2873
" -c" ,
2874
" [email protected] " ,
2875
" -c" ,
2876
" commit.gpgsign=false" ,
2877
] )
c3069537 Andy McCall 1 month ago ↶
2878
.args ( args)
2879
.output ( )
2880
.unwrap ( ) ;
75167672 Andy McCall 1 month ago
2881
assert! (
2882
out.status.success ( ) ,
2883
" git {args:?}: {}" ,
2884
String :: from_utf8_lossy( & out.stderr)
2885
) ;
c3069537 Andy McCall 1 month ago ↶
2886
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
2887
} ;
2888
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
2889
write ( & g, " README.md" , b " # Hi\n " ) ;
2890
write ( & g, " src/lib.rs" , b " pub fn a() {}\n " ) ;
2891
git ( & [ " add" , " ." ] ) ;
2892
git ( & [ " commit" , " -q" , " -m" , " initial" ] ) ;
2893
write ( & g, " src/lib.rs" , b " pub fn a() {}\n pub fn b() {}\n " ) ;
2894
write ( & g, " run.sh" , b " #!/bin/sh\n " ) ;
2895
# [ cfg ( unix ) ]
2896
{
2897
use std:: os:: unix:: fs:: PermissionsExt;
2898
fs:: set_permissions( g.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
2899
}
2900
write ( & g, " logo.png" , b " \x89 PNG\0 \0 \x01 " ) ;
2901
git ( & [ " add" , " ." ] ) ;
75167672 Andy McCall 1 month ago
2902
git ( & [
2903
" commit" ,
2904
" -q" ,
2905
" -m" ,
2906
" second\n \n Change-Id: 0123456789abcdef0123456789abcdef" ,
2907
] ) ;
c3069537 Andy McCall 1 month ago ↶
2908
2909
git ( & [ " checkout" , " -q" , " -b" , " topic" , " HEAD~1" ] ) ;
2910
write ( & g, " README.md" , b " # Hi\n \n More.\n " ) ;
2911
git ( & [ " commit" , " -q" , " -am" , " readme" ] ) ;
2912
git ( & [ " checkout" , " -q" , " main" ] ) ;
2913
git ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge topic" , " topic" ] ) ;
2914
2915
2916
let initial = git ( & [ " rev-list" , " --max-parents=0" , " HEAD" ] ) ;
2917
git ( & [ " checkout" , " -q" , " -b" , " clash" , & initial] ) ;
2918
write ( & g, " src/lib.rs" , b " pub fn a() {}\n pub fn c() {}\n " ) ;
2919
git ( & [ " commit" , " -q" , " -am" , " clash" ] ) ;
2920
git ( & [ " checkout" , " -q" , " main" ] ) ;
75167672 Andy McCall 1 month ago
2921
let _ = std:: process:: Command:: new( " git" )
2922
.arg ( " -C" )
2923
.arg ( & g)
2924
.args ( [ " merge" , " -q" , " clash" ] )
2925
.output ( ) ;
2926
write (
2927
& g,
2928
" src/lib.rs" ,
2929
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n " ,
2930
) ;
c3069537 Andy McCall 1 month ago ↶
2931
git ( & [ " add" , " ." ] ) ;
2932
git ( & [ " commit" , " -q" , " -m" , " resolve clash" ] ) ;
2933
git ( & [ " rm" , " -q" , " logo.png" ] ) ;
2934
git ( & [ " commit" , " -q" , " -m" , " drop logo" ] ) ;
6e723b81 Andy McCall 1 month ago ↶
2935
2936
git ( & [ " mv" , " src/lib.rs" , " src/lib2.rs" ] ) ;
75167672 Andy McCall 1 month ago
2937
write (
2938
& g,
2939
" src/lib2.rs" ,
2940
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n pub fn d() {}\n " ,
2941
) ;
6e723b81 Andy McCall 1 month ago ↶
2942
git ( & [ " add" , " ." ] ) ;
2943
git ( & [ " commit" , " -q" , " -m" , " rename lib" ] ) ;
958fb27d Andy McCall 1 month ago ↶
2944
2945
2946
2947
git ( & [ " checkout" , " -q" , " -b" , " discarded" ] ) ;
2948
write ( & g, " run.sh" , b " #!/bin/sh\n echo discarded\n " ) ;
2949
git ( & [ " commit" , " -q" , " -am" , " discarded edit" ] ) ;
2950
git ( & [ " checkout" , " -q" , " main" ] ) ;
314db9b4 Andy McCall 1 month ago ↶
2951
git ( & [
2952
" merge" ,
2953
" -q" ,
2954
" -s" ,
2955
" ours" ,
2956
" -m" ,
2957
" merge discarded, keeping ours" ,
2958
" discarded" ,
2959
] ) ;
c3069537 Andy McCall 1 month ago ↶
2960
let expected_count: usize = git ( & [ " rev-list" , " --count" , " main" ] ) .parse ( ) .unwrap ( ) ;
2961
2962
let dest = root.join ( " oxy" ) ;
2963
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2964
let rep = repo.import_git ( & g, " main" ) .unwrap ( ) ;
2965
assert_eq! ( rep.commits, expected_count) ;
958fb27d Andy McCall 1 month ago ↶
2966
assert_eq! ( rep.merges, 3 ) ;
314db9b4 Andy McCall 1 month ago ↶
2967
assert_eq! (
2968
fs:: read( dest.join ( " run.sh" ) ) .unwrap ( ) ,
2969
b " #!/bin/sh\n " ,
2970
" the ours merge kept main's run.sh"
2971
) ;
958fb27d Andy McCall 1 month ago ↶
2972
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
2973
assert_eq! ( v.commits, expected_count, " every commit's tree reproduced" ) ;
2974
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
c3069537 Andy McCall 1 month ago ↶
2975
75167672 Andy McCall 1 month ago
2976
assert_eq! (
2977
fs:: read( dest.join ( " src/lib2.rs" ) ) .unwrap ( ) ,
2978
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n pub fn d() {}\n "
2979
) ;
6e723b81 Andy McCall 1 month ago ↶
2980
assert! ( ! dest.join ( " src/lib.rs" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
2981
assert_eq! (
2982
fs:: read( dest.join ( " README.md" ) ) .unwrap ( ) ,
2983
b " # Hi\n \n More.\n "
2984
) ;
c3069537 Andy McCall 1 month ago ↶
2985
assert! ( ! dest.join ( " logo.png" ) .exists ( ) ) ;
2986
# [ cfg ( unix ) ]
2987
{
2988
use std:: os:: unix:: fs:: PermissionsExt;
75167672 Andy McCall 1 month ago
2989
assert! (
2990
fs:: metadata( dest.join ( " run.sh" ) )
2991
.unwrap ( )
2992
.permissions ( )
2993
.mode ( )
2994
& 0o111
2995
! = 0
2996
) ;
c3069537 Andy McCall 1 month ago ↶
2997
}
75167672 Andy McCall 1 month ago
2998
assert! (
2999
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
3000
" clean after import"
3001
) ;
c3069537 Andy McCall 1 month ago ↶
3002
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
3003
let st = repo.load_state ( & heads) .unwrap ( ) ;
3004
assert! ( ! st.materialize ( ) .is_conflicted ( ) ) ;
3005
assert_eq! ( st.len ( ) , expected_count) ;
75167672 Andy McCall 1 month ago
3006
let tip = repo
3007
.log ( )
3008
.unwrap ( )
3009
.into_iter ( )
3010
.find ( | e | e.change.message.starts_with ( " rename lib" ) )
3011
.unwrap ( ) ;
6e723b81 Andy McCall 1 month ago ↶
3012
let c = repo.store ( ) .get_change ( & tip.hash) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3013
assert! (
3014
c.tree_ops
3015
.iter ( )
3016
.any ( | op | matches! ( op, TreeOp:: Move { name, .. } if name = = " lib2.rs" ) ) ,
3017
" {:?}" ,
3018
c.tree_ops
3019
) ;
3020
assert! ( ! c
3021
.tree_ops
3022
.iter ( )
3023
.any ( | op | matches! ( op, TreeOp:: AddNode { .. } ) ) ) ;
c3069537 Andy McCall 1 month ago ↶
3024
3025
let log = repo.log ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3026
let second = log
3027
.iter ( )
3028
.find ( | e | e.change.message.starts_with ( " second" ) )
3029
.unwrap ( ) ;
3030
assert_eq! (
3031
second.change.change_id,
3032
ChangeId:: from_hex( " 0123456789abcdef0123456789abcdef" ) .unwrap ( )
3033
) ;
3034
assert! ( second
3035
.change
3036
.message
3037
.contains ( " Git-Author: Grace <[email protected] >" ) ) ;
3038
let resolve = log
3039
.iter ( )
3040
.find ( | e | e.change.message.starts_with ( " resolve clash" ) )
3041
.unwrap ( ) ;
3042
assert_eq! (
3043
resolve.change.kind,
3044
ChangeKind:: Resolution,
3045
" a conflicting Git merge becomes a resolution"
3046
) ;
c3069537 Andy McCall 1 month ago ↶
3047
75167672 Andy McCall 1 month ago
3048
let repo2 = Repo:: init( & root.join ( " oxy2" ) )
3049
.unwrap ( )
3050
.with_home ( root.join ( " home" ) ) ;
c3069537 Andy McCall 1 month ago ↶
3051
repo2.import_git ( & g, " main" ) .unwrap ( ) ;
dfb23a3a Andy McCall 1 month ago ↶
3052
3053
3054
assert_eq! (
3055
repo2.bookmark_heads ( ) .unwrap ( ) ,
3056
repo.bookmark_heads ( ) .unwrap ( )
3057
) ;
75167672 Andy McCall 1 month ago
3058
let ids: BTreeSet< ChangeId> = repo
3059
.log ( )
3060
.unwrap ( )
3061
.iter ( )
3062
.filter ( | e | ! e.is_working_copy)
3063
.map ( | e | e.change.change_id )
3064
.collect ( ) ;
3065
let ids2: BTreeSet< ChangeId> = repo2
3066
.log ( )
3067
.unwrap ( )
3068
.iter ( )
3069
.filter ( | e | ! e.is_working_copy)
3070
.map ( | e | e.change.change_id )
3071
.collect ( ) ;
c3069537 Andy McCall 1 month ago ↶
3072
assert_eq! ( ids, ids2) ;
3073
3074
let mirror = root.join ( " mirror.git" ) ;
3075
repo.git_export ( & mirror) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3076
let out = std:: process:: Command:: new( " git" )
3077
.arg ( " -C" )
3078
.arg ( & mirror)
3079
.args ( [ " show" , " main:src/lib2.rs" ] )
3080
.output ( )
3081
.unwrap ( ) ;
3082
assert_eq! (
3083
String :: from_utf8_lossy( & out.stdout) ,
3084
" pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n pub fn d() {}\n "
3085
) ;
3086
let out = std:: process:: Command:: new( " git" )
3087
.arg ( " -C" )
3088
.arg ( & mirror)
3089
.args ( [ " show" , " main:src/lib.rs" ] )
3090
.output ( )
3091
.unwrap ( ) ;
3092
assert! (
3093
! out.status.success ( ) ,
3094
" the old path is gone from the export"
3095
) ;
c3069537 Andy McCall 1 month ago ↶
3096
}
252e8cb2 Andy McCall 1 month ago ↶
3097
3098
# [ test ]
3099
fn checkpoints_accelerate_loading_and_stay_correct ( ) {
3100
hermetic ( ) ;
3101
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3102
let root = dir.path ( ) ;
3103
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
3104
repo.config.set ( " core.checkpoint-interval" , " 5" ) .unwrap ( ) ;
3105
repo.save_config ( ) .unwrap ( ) ;
3106
let repo = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
3107
let mut hashes = Vec :: new( ) ;
3108
for i in 0 .. 12 {
75167672 Andy McCall 1 month ago
3109
write (
3110
root,
3111
" a.txt" ,
3112
format! (
3113
" {} \n " ,
3114
( 0 .. = i)
3115
.map ( | k | k.to_string ( ) )
3116
.collect:: < Vec< _ > > ( )
3117
.join ( " \n " )
3118
)
3119
.as_bytes ( ) ,
3120
) ;
252e8cb2 Andy McCall 1 month ago ↶
3121
write ( root, & format! ( " f{i} .txt" ) , b " x\n " ) ;
3122
hashes.push ( repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ) ;
3123
}
75167672 Andy McCall 1 month ago
3124
let ( _ , cp) = repo
3125
.latest_checkpoint ( " main" )
3126
.unwrap ( )
3127
.expect ( " automatic checkpoint" ) ;
252e8cb2 Andy McCall 1 month ago ↶
3128
assert! ( cp.count > = 5 & & cp.count < = 12 , " {}" , cp.count) ;
3129
3130
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
3131
let fast = repo.load_state ( & heads) .unwrap ( ) ;
3132
let full = repo.load_state_full ( & heads) .unwrap ( ) ;
3133
assert_eq! ( fast.len ( ) , full.len ( ) ) ;
3134
assert_eq! ( fast.heads ( ) , full.heads ( ) ) ;
3135
let ( a, b) = ( fast.materialize ( ) , full.materialize ( ) ) ;
75167672 Andy McCall 1 month ago
3136
assert_eq! (
3137
a.files.keys ( ) .collect:: < Vec< _ > > ( ) ,
3138
b.files.keys ( ) .collect:: < Vec< _ > > ( )
3139
) ;
252e8cb2 Andy McCall 1 month ago ↶
3140
for ( p, f) in & a.files {
3141
assert_eq! ( f.content, b.files[ p] .content, " {p}" ) ;
3142
}
3143
assert! ( repo.load_state_checkpointed ( & heads) .unwrap ( ) .is_some ( ) ) ;
3144
75167672 Andy McCall 1 month ago
3145
assert! ( repo
3146
.load_state_checkpointed ( & [ hashes[ 0 ] ] )
3147
.unwrap ( )
3148
.is_none ( ) ) ;
252e8cb2 Andy McCall 1 month ago ↶
3149
assert_eq! ( repo.load_state ( & [ hashes[ 0 ] ] ) .unwrap ( ) .len ( ) , 1 ) ;
3150
3151
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
3152
let h = repo.describe ( & hashes[ 3 ] , " c3 described" ) .unwrap ( ) ;
3153
assert_eq! ( repo.store ( ) .get_change ( & h) .unwrap ( ) .message, " c3 described" ) ;
3154
let ( r, n, bytes) = repo.checkpoint ( ) .unwrap ( ) ;
3155
assert! ( n > = 12 & & bytes > 0 ) ;
3156
assert_eq! ( repo.latest_checkpoint ( " main" ) .unwrap ( ) .unwrap ( ) .0 , r) ;
3157
write ( root, " a.txt" , b " after\n " ) ;
3158
let h_after = repo.commit ( " after" , & [ ] ) .unwrap ( ) ;
3159
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
3160
assert! ( st.contains ( & h_after) ) ;
75167672 Andy McCall 1 month ago
3161
assert! (
3162
matches! ( & st.materialize ( ) .files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " after\n " )
3163
) ;
252e8cb2 Andy McCall 1 month ago ↶
3164
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 14 ) ;
3165
3166
repo.gc ( true ) .unwrap ( ) ;
3167
assert! ( repo.latest_checkpoint ( " main" ) .unwrap ( ) .is_some ( ) ) ;
75167672 Andy McCall 1 month ago
3168
assert! ( repo
3169
.load_state_checkpointed ( & repo.bookmark_heads ( ) .unwrap ( ) )
3170
.unwrap ( )
3171
.is_some ( ) ) ;
252e8cb2 Andy McCall 1 month ago ↶
3172
}
135212de Andy McCall 1 month ago ↶
3173
3174
# [ test ]
3175
fn quick_status_for_prompts ( ) {
3176
hermetic ( ) ;
3177
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3178
let root = dir.path ( ) ;
3179
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
3180
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
3181
let a = Repo:: clone( & url, & root.join ( " a" ) )
3182
.unwrap ( )
3183
.with_home ( root.join ( " home" ) ) ;
135212de Andy McCall 1 month ago ↶
3184
let ops_before = a.op_log ( ) .unwrap ( ) .len ( ) ;
3185
let info = a.quick_status ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3186
assert_eq! (
3187
(
3188
info.bookmark.as_str ( ) ,
3189
info.heads,
3190
info.dirty,
3191
info.conflicted,
3192
info.ahead,
3193
info.behind
3194
) ,
3195
( " main" , 0 , false , false , Some ( 0 ) , Some ( 0 ) )
3196
) ;
135212de Andy McCall 1 month ago ↶
3197
write ( & root.join ( " a" ) , " f.txt" , b " f\n " ) ;
3198
assert! ( a.quick_status ( ) .unwrap ( ) .dirty, " a new file is dirty" ) ;
75167672 Andy McCall 1 month ago
3199
assert_eq! (
3200
a.op_log ( ) .unwrap ( ) .len ( ) ,
3201
ops_before,
3202
" quick_status records nothing"
3203
) ;
135212de Andy McCall 1 month ago ↶
3204
3205
a.snapshot ( " status" ) .unwrap ( ) ;
3206
assert! ( a.quick_status ( ) .unwrap ( ) .dirty) ;
3207
a.commit ( " f" , & [ ] ) .unwrap ( ) ;
3208
let info = a.quick_status ( ) .unwrap ( ) ;
3209
assert! ( ! info.dirty, " clean right after a commit" ) ;
3210
assert_eq! ( ( info.ahead, info.behind) , ( Some ( 1 ) , Some ( 0 ) ) ) ;
75167672 Andy McCall 1 month ago
3211
assert_eq! (
3212
info.format ( " %b %c%d%x%A%D" ) ,
3213
format! ( " main {} +1" , info.change_id)
3214
) ;
135212de Andy McCall 1 month ago ↶
3215
assert_eq! ( info.format ( " [%h heads] %a/%B %%" ) , " [1 heads] 1/0 %" ) ;
3216
3217
3218
let p = root.join ( " a/f.txt" ) ;
3219
let content = fs:: read( & p) .unwrap ( ) ;
3220
fs:: write( & p, & content) .unwrap ( ) ;
3221
assert! ( ! a.quick_status ( ) .unwrap ( ) .dirty) ;
3222
fs:: write( & p, b " g\n " ) .unwrap ( ) ;
3223
assert! ( a.quick_status ( ) .unwrap ( ) .dirty) ;
3224
fs:: write( & p, b " f\n " ) .unwrap ( ) ;
3225
3226
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
3227
assert_eq! ( a.quick_status ( ) .unwrap ( ) .ahead, Some ( 0 ) ) ;
75167672 Andy McCall 1 month ago
3228
let b = Repo:: clone( & url, & root.join ( " b" ) )
3229
.unwrap ( )
3230
.with_home ( root.join ( " home-b" ) ) ;
135212de Andy McCall 1 month ago ↶
3231
write ( & root.join ( " b" ) , " g.txt" , b " g\n " ) ;
3232
b.commit ( " g" , & [ ] ) .unwrap ( ) ;
3233
b.push ( " origin" , false ) .unwrap ( ) ;
3234
a.fetch ( " origin" ) .unwrap ( ) ;
3235
let info = a.quick_status ( ) .unwrap ( ) ;
3236
assert_eq! ( ( info.ahead, info.behind) , ( Some ( 0 ) , Some ( 1 ) ) ) ;
3237
assert_eq! ( info.format ( " %A%D" ) , " -1" ) ;
3238
let _ = origin;
3239
}
b3c5a59a Andy McCall 1 month ago ↶
3240
3241
# [ test ]
3242
fn lazy_blobs_are_fetched_on_first_read ( ) {
3243
hermetic ( ) ;
3244
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3245
let root = dir.path ( ) ;
3246
let origin_dir = root.join ( " origin" ) ;
75167672 Andy McCall 1 month ago
3247
let origin = Repo:: init( & origin_dir)
3248
.unwrap ( )
3249
.with_home ( root.join ( " home" ) ) ;
b3c5a59a Andy McCall 1 month ago ↶
3250
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
3251
let big: Vec< u8 > = ( 0 .. 200_000 u32 ) .map ( | i | ( i % 251 ) as u8 ) .collect ( ) ;
3252
write ( & origin_dir, " big.bin" , & big) ;
3253
write ( & origin_dir, " small.bin" , & [ 0 u8 , 1 , 2 ] ) ;
3254
write ( & origin_dir, " t.txt" , b " t\n " ) ;
3255
let h = origin.commit ( " blobs" , & [ ] ) .unwrap ( ) ;
3256
let big_ref = Repo:: blob_ref( & big) ;
3257
3258
75167672 Andy McCall 1 month ago
3259
let a = Repo:: init_with_id( & root.join ( " a" ) , origin.id ( ) .unwrap ( ) )
3260
.unwrap ( )
3261
.with_home ( root.join ( " home" ) ) ;
b3c5a59a Andy McCall 1 month ago ↶
3262
let a = {
3263
let mut a = a;
3264
a.config.set ( " remote.origin.url" , & url) .unwrap ( ) ;
3265
a.config.set ( " filter.max-blob" , " 100K" ) .unwrap ( ) ;
3266
a.save_config ( ) .unwrap ( ) ;
3267
a
3268
} ;
3269
assert_eq! ( a.config.max_blob, Some ( 100 * 1024 ) ) ;
3270
a.fetch ( " origin" ) .unwrap ( ) ;
3271
assert! ( a.store ( ) .has ( & h) ) ;
3272
assert! ( ! a.store ( ) .has ( & big_ref) , " withheld" ) ;
75167672 Andy McCall 1 month ago
3273
assert! (
3274
a.store ( ) .has ( & Repo:: blob_ref( & [ 0 u8 , 1 , 2 ] ) ) ,
3275
" small blob came along"
3276
) ;
b3c5a59a Andy McCall 1 month ago ↶
3277
assert_eq! ( a.lazy_remote ( & big_ref) .as_deref ( ) , Some ( " origin" ) ) ;
3278
3279
3280
let ( op_ref, op) = a.head_op ( ) .unwrap ( ) ;
3281
let mut view = op.view.clone ( ) ;
75167672 Andy McCall 1 month ago
3282
view.bookmarks.insert (
3283
" main" .into ( ) ,
3284
a.store ( ) .put_state ( & StateObject:: new( vec! [ h] ) ) .unwrap ( ) ,
3285
) ;
b3c5a59a Andy McCall 1 month ago ↶
3286
a.record_op ( & [ op_ref] , " adopt" , view) .unwrap ( ) ;
3287
3288
a.checkout ( & [ h] , true , " checkout" ) .unwrap ( ) ;
3289
assert! ( a.store ( ) .has ( & big_ref) , " fetched on first read" ) ;
3290
assert_eq! ( fs:: read( root.join ( " a/big.bin" ) ) .unwrap ( ) , big) ;
3291
assert! ( a.lazy_remote ( & big_ref) .is_none ( ) , " no longer registered" ) ;
3292
write ( & root.join ( " a" ) , " t.txt" , b " t2\n " ) ;
3293
a.commit ( " text edit" , & [ ] ) .unwrap ( ) ;
3294
3295
7421309f Andy McCall 1 month ago ↶
3296
let b = Repo:: clone_with( & url, & root.join ( " b" ) , Some ( 1024 ) , & [ ] ) .unwrap ( ) ;
b3c5a59a Andy McCall 1 month ago ↶
3297
assert_eq! ( b.config.max_blob, Some ( 1024 ) ) ;
3298
assert_eq! ( fs:: read( root.join ( " b/big.bin" ) ) .unwrap ( ) , big) ;
3299
assert_eq! ( parse_size ( " 2M" ) .unwrap ( ) , 2 < < 20 ) ;
3300
assert! ( parse_size ( " lots" ) .is_err ( ) ) ;
3301
}
7421309f Andy McCall 1 month ago ↶
3302
3303
# [ test ]
3304
fn sparse_checkout_materializes_only_chosen_paths ( ) {
3305
hermetic ( ) ;
3306
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3307
let root = dir.path ( ) ;
3308
let origin_dir = root.join ( " origin" ) ;
75167672 Andy McCall 1 month ago
3309
let origin = Repo:: init( & origin_dir)
3310
.unwrap ( )
3311
.with_home ( root.join ( " home" ) ) ;
7421309f Andy McCall 1 month ago ↶
3312
write ( & origin_dir, " src/main.rs" , b " fn main() {}\n " ) ;
3313
write ( & origin_dir, " src/lib.rs" , b " pub fn a() {}\n " ) ;
3314
write ( & origin_dir, " docs/guide.md" , b " # guide\n " ) ;
3315
let logo: Vec< u8 > = ( 0 .. 50_000 u32 ) .map ( | i | ( i % 7 ) as u8 ) .collect ( ) ;
3316
write ( & origin_dir, " docs/logo.png" , & logo) ;
3317
write ( & origin_dir, " README.md" , b " # top\n " ) ;
3318
let h1 = origin.commit ( " tree" , & [ ] ) .unwrap ( ) ;
3319
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
3320
75167672 Andy McCall 1 month ago
3321
let a = Repo:: clone_with( & url, & root.join ( " a" ) , None , & [ " src" .into ( ) ] )
3322
.unwrap ( )
3323
.with_home ( root.join ( " home" ) ) ;
7421309f Andy McCall 1 month ago ↶
3324
assert_eq! ( a.config.sparse_paths, vec! [ " src" ] ) ;
3325
assert_eq! ( a.config.max_blob, Some ( 0 ) , " sparse implies lazy blobs" ) ;
3326
assert! ( root.join ( " a/src/main.rs" ) .exists ( ) ) ;
3327
assert! ( ! root.join ( " a/docs" ) .exists ( ) & & ! root.join ( " a/README.md" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
3328
assert! (
3329
! a.store ( ) .has ( & Repo:: blob_ref( & logo) ) ,
3330
" out-of-scope blob never fetched"
3331
) ;
3332
assert! (
3333
! a.snapshot ( " status" ) .unwrap ( ) .changed,
3334
" out-of-scope files are not removals"
3335
) ;
05e71ed6 Andy McCall 1 month ago ↶
3336
assert! ( ! a.quick_status ( ) .unwrap ( ) .dirty) ;
7421309f Andy McCall 1 month ago ↶
3337
3338
75167672 Andy McCall 1 month ago
3339
write (
3340
& root.join ( " a" ) ,
3341
" src/lib.rs" ,
3342
b " pub fn a() {}\n pub fn b() {}\n " ,
3343
) ;
7421309f Andy McCall 1 month ago ↶
3344
let h2 = a.commit ( " edit lib" , & [ ] ) .unwrap ( ) ;
3345
let st = a.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
3346
assert! (
3347
st.files.contains_key ( " docs/guide.md" ) ,
3348
" the state still has everything"
3349
) ;
7421309f Andy McCall 1 month ago ↶
3350
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
3351
origin.refresh ( ) .unwrap ( ) ;
7421309f Andy McCall 1 month ago ↶
3352
3353
write ( & root.join ( " a" ) , " docs/new.md" , b " ignored\n " ) ;
3354
assert! ( a.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
3355
3356
3357
let mut a = a;
3358
a.config.set ( " filter.paths" , " src,docs" ) .unwrap ( ) ;
3359
a.save_config ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3360
let r = a
3361
.checkout ( & a.bookmark_heads ( ) .unwrap ( ) , true , " widen" )
3362
.unwrap ( ) ;
7421309f Andy McCall 1 month ago ↶
3363
assert! ( r.written > = 2 ) ;
75167672 Andy McCall 1 month ago
3364
assert_eq! (
3365
fs:: read( root.join ( " a/docs/logo.png" ) ) .unwrap ( ) ,
3366
logo,
3367
" now fetched lazily"
3368
) ;
7421309f Andy McCall 1 month ago ↶
3369
assert! ( ! root.join ( " a/README.md" ) .exists ( ) ) ;
3370
3371
origin.pull ( " origin" ) .err ( ) ;
75167672 Andy McCall 1 month ago
3372
let full = origin
3373
.load_state ( & origin.bookmark_heads ( ) .unwrap ( ) )
3374
.unwrap ( )
3375
.materialize ( ) ;
3376
assert! (
3377
matches! ( & full.files[ " src/lib.rs" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " pub fn a() {}\n pub fn b() {}\n " )
3378
) ;
7421309f Andy McCall 1 month ago ↶
3379
assert! ( full.files.contains_key ( " README.md" ) ) ;
3380
let _ = h1;
3381
}
2863456a Andy McCall 1 month ago ↶
3382
7bd805ff Andy McCall 12 days ago ↶
3383
3384
3385
3386
2863456a Andy McCall 1 month ago ↶
3387
# [ test ]
7bd805ff Andy McCall 12 days ago ↶
3388
fn a_depth_0_clone_brings_its_binary_files ( ) {
3389
hermetic ( ) ;
3390
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3391
let root = dir.path ( ) ;
3392
let origin_dir = root.join ( " origin" ) ;
3393
let origin = Repo:: init( & origin_dir)
3394
.unwrap ( )
3395
.with_home ( root.join ( " home" ) ) ;
3396
let png: Vec< u8 > = ( 0 .. 4000 u32 )
3397
.map ( | i | ( i * 7 % 251 ) as u8 )
3398
.chain ( [ 0 , 0xff ] )
3399
.collect ( ) ;
3400
3401
3402
write ( & origin_dir, " logo.png" , & png) ;
3403
write ( & origin_dir, " a.txt" , b " first\n " ) ;
3404
origin.commit ( " logo" , & [ ] ) .unwrap ( ) ;
3405
write ( & origin_dir, " a.txt" , b " later\n " ) ;
3406
origin.commit ( " later" , & [ ] ) .unwrap ( ) ;
3407
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) .len ( ) , 1 ) ;
3408
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
3409
Repo:: clone_shallow( & url, & root.join ( " a" ) , 0 , None , & [ ] ) .unwrap ( ) ;
3410
assert_eq! ( fs:: read( root.join ( " a/logo.png" ) ) .unwrap ( ) , png) ;
3411
let lazy = Repo:: clone_shallow( & url, & root.join ( " b" ) , 0 , Some ( 0 ) , & [ ] ) .unwrap ( ) ;
3412
assert_eq! ( fs:: read( root.join ( " b/a.txt" ) ) .unwrap ( ) , b " later\n " ) ;
3413
let blob = {
3414
let heads = lazy.bookmark_heads ( ) .unwrap ( ) ;
3415
let st = lazy.load_state_history ( & heads) .unwrap ( ) ;
3416
st.opaque_nodes ( )
3417
.iter ( )
3418
.flat_map ( | n | st.live_blobs ( n) )
3419
.next ( )
3420
.unwrap ( )
3421
} ;
3422
assert_eq! ( lazy.read_blob ( & blob) .unwrap ( ) , png, " fetched on read" ) ;
f3811eb2 Andy McCall 12 days ago ↶
3423
3424
3425
let mut a = Repo:: open( & root.join ( " a" ) )
3426
.unwrap ( )
3427
.with_home ( root.join ( " home" ) ) ;
3428
let head = origin.bookmark_heads ( ) .unwrap ( ) [ 0 ] ;
3429
assert! ( ! a.store ( ) .has ( & head) , " no change fetched" ) ;
3430
assert! ( a.boundary ( ) .contains ( & head) ) ;
3431
assert_eq! (
3432
a.log ( )
3433
.unwrap ( )
3434
.iter ( )
3435
.filter ( | e | ! e.is_working_copy)
3436
.count ( ) ,
3437
2 ,
3438
" the log from the checkpoint"
3439
) ;
3440
write ( & root.join ( " a" ) , " a.txt" , b " later\n mine\n " ) ;
3441
a.config.set ( " remote.origin.url" , & url) .unwrap ( ) ;
3442
a.save_config ( ) .unwrap ( ) ;
3443
let mine = a.commit ( " mine" , & [ ] ) .unwrap ( ) ;
3444
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
3445
origin.refresh ( ) .unwrap ( ) ;
3446
assert! ( origin.store ( ) .has ( & mine) ) ;
3447
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ mine] ) ;
3448
3449
origin
3450
.checkout ( & origin.bookmark_heads ( ) .unwrap ( ) , false , " co" )
3451
.unwrap ( ) ;
3452
write ( & origin_dir, " a.txt" , b " later\n mine\n theirs\n " ) ;
3453
origin.snapshot ( " t" ) .unwrap ( ) ;
3454
origin.commit ( " theirs" , & [ ] ) .unwrap ( ) ;
3455
a.pull ( " origin" ) .unwrap ( ) ;
3456
assert_eq! (
3457
fs:: read( root.join ( " a/a.txt" ) ) .unwrap ( ) ,
3458
b " later\n mine\n theirs\n "
3459
) ;
7bd805ff Andy McCall 12 days ago ↶
3460
}
3461
3462
# [ test ]
2863456a Andy McCall 1 month ago ↶
3463
fn shallow_clone_and_deepen ( ) {
3464
hermetic ( ) ;
3465
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3466
let root = dir.path ( ) ;
3467
let origin_dir = root.join ( " origin" ) ;
75167672 Andy McCall 1 month ago
3468
let origin = Repo:: init( & origin_dir)
3469
.unwrap ( )
3470
.with_home ( root.join ( " home" ) ) ;
2863456a Andy McCall 1 month ago ↶
3471
let mut hashes = Vec :: new( ) ;
3472
for i in 0 .. 8 {
75167672 Andy McCall 1 month ago
3473
write (
3474
& origin_dir,
3475
" a.txt" ,
3476
format! (
3477
" {} \n " ,
3478
( 0 .. = i)
3479
.map ( | k | k.to_string ( ) )
3480
.collect:: < Vec< _ > > ( )
3481
.join ( " \n " )
3482
)
3483
.as_bytes ( ) ,
3484
) ;
2863456a Andy McCall 1 month ago ↶
3485
hashes.push ( origin.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ) ;
3486
}
3487
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
3488
let a = Repo:: clone_shallow( & url, & root.join ( " a" ) , 3 , None , & [ ] )
3489
.unwrap ( )
3490
.with_home ( root.join ( " home" ) ) ;
2863456a Andy McCall 1 month ago ↶
3491
assert! ( a.is_shallow ( ) ) ;
75167672 Andy McCall 1 month ago
3492
assert_eq! (
3493
fs:: read_to_string( root.join ( " a/a.txt" ) ) .unwrap ( ) ,
3494
" 0\n 1\n 2\n 3\n 4\n 5\n 6\n 7\n "
3495
) ;
3496
assert! (
3497
a.store ( ) .has ( & hashes[ 7 ] ) & & a.store ( ) .has ( & hashes[ 5 ] ) ,
3498
" three levels present"
3499
) ;
2863456a Andy McCall 1 month ago ↶
3500
assert! ( ! a.store ( ) .has ( & hashes[ 4 ] ) , " beyond the depth is absent" ) ;
3501
assert_eq! ( a.boundary ( ) , [ hashes[ 4 ] ] .into_iter ( ) .collect ( ) ) ;
75167672 Andy McCall 1 month ago
3502
assert_eq! (
3503
a.log ( ) .unwrap ( ) .len ( ) ,
3504
9 ,
3505
" the log shows every change from the checkpoint's metadata"
3506
) ;
2863456a Andy McCall 1 month ago ↶
3507
assert! ( ! a.snapshot ( " status" ) .unwrap ( ) .changed) ;
3508
assert_eq! ( a.quick_status ( ) .unwrap ( ) .heads, 1 ) ;
3509
3510
3511
write ( & root.join ( " a" ) , " a.txt" , b " 0\n 1\n 2\n 3\n 4\n 5\n 6\n 7\n 8\n " ) ;
3512
let h_new = a.commit ( " c8" , & [ ] ) .unwrap ( ) ;
3513
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
3514
origin.refresh ( ) .unwrap ( ) ;
2863456a Andy McCall 1 month ago ↶
3515
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ h_new] ) ;
3516
3517
write ( & origin_dir, " b.txt" , b " b\n " ) ;
3518
origin.pull ( " origin" ) .err ( ) ;
3519
let ( _ , wc) = origin.working_copy ( ) .unwrap ( ) ;
3520
let _ = wc;
75167672 Andy McCall 1 month ago
3521
origin
3522
.checkout ( & origin.bookmark_heads ( ) .unwrap ( ) , true , " co" )
3523
.unwrap ( ) ;
2863456a Andy McCall 1 month ago ↶
3524
write ( & origin_dir, " b.txt" , b " b\n " ) ;
3525
let h_b = origin.commit ( " b" , & [ ] ) .unwrap ( ) ;
3526
let pr = a.pull ( " origin" ) .unwrap ( ) ;
3527
assert! ( pr.changed) ;
3528
assert! ( a.store ( ) .has ( & h_b) ) ;
3529
assert! ( root.join ( " a/b.txt" ) .exists ( ) ) ;
3530
3531
3532
3533
3534
assert! ( a.deepen ( " origin" , 2 ) .unwrap ( ) > = 2 ) ;
3535
assert! ( a.store ( ) .has ( & hashes[ 3 ] ) ) ;
3536
assert! ( a.is_shallow ( ) ) ;
3537
a.deepen ( " origin" , 100 ) .unwrap ( ) ;
3538
assert! ( ! a.is_shallow ( ) , " boundary reached the root" ) ;
3539
assert! ( a.store ( ) .has ( & hashes[ 0 ] ) ) ;
75167672 Andy McCall 1 month ago
3540
assert_eq! (
3541
a.load_state_full ( & a.bookmark_heads ( ) .unwrap ( ) )
3542
.unwrap ( )
3543
.len ( ) ,
3544
10
3545
) ;
2863456a Andy McCall 1 month ago ↶
3546
}
132b884b Andy McCall 1 month ago ↶
3547
3548
# [ test ]
b63fc7a7 Andy McCall 12 days ago ↶
3549
fn a_tag_is_withdrawn_by_a_signed_withdrawal ( ) {
3550
hermetic ( ) ;
3551
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3552
let root = dir.path ( ) ;
3553
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
3554
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
3555
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
3556
.unwrap ( )
3557
.with_home ( root.join ( " home-a" ) ) ;
3558
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
3559
.unwrap ( ) ;
3560
write ( & root.join ( " a" ) , " f.txt" , b " 1\n " ) ;
3561
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
3562
a.tag_create ( " v1" , & [ h1] , " first release" ) .unwrap ( ) ;
3563
a.tag_create ( " v2" , & [ h1] , " " ) .unwrap ( ) ;
3564
a.policy_init ( ) .unwrap ( ) ;
3565
a.push ( " origin" , false ) .unwrap ( ) ;
3566
origin.refresh ( ) .unwrap ( ) ;
3567
let original = a
3568
.tag_refs ( )
3569
.unwrap ( )
3570
.into_iter ( )
3571
.find ( | t | t.name = = " v1" )
3572
.unwrap ( ) ;
3573
let b = Repo:: clone( & url, & root.join ( " b" ) )
3574
.unwrap ( )
3575
.with_home ( root.join ( " home-b" ) ) ;
3576
assert_eq! ( b.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
3577
3578
3579
let mut c = Repo:: clone( & url, & root.join ( " c" ) )
3580
.unwrap ( )
3581
.with_home ( root.join ( " home-c" ) ) ;
3582
c.create_identity ( " Cy" , " [email protected] " , IdentityScope:: User)
3583
.unwrap ( ) ;
3584
let r = c.push_delete ( " origin" , " tags/v1" ) .unwrap ( ) ;
3585
assert! (
3586
! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " may not withdraw" ) ,
3587
" {r:?}"
3588
) ;
3589
assert_eq! ( c.tag_heads ( " v1" ) .unwrap ( ) , None , " withdrawn there first" ) ;
3590
origin.refresh ( ) .unwrap ( ) ;
3591
assert_eq! ( origin.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
3592
3593
3594
let r = a.push_delete ( " origin" , " tags/v1" ) .unwrap ( ) ;
3595
assert! ( r[ 0 ] .ok & & r[ 0 ] .reason = = " withdrawn" , " {r:?}" ) ;
3596
origin.refresh ( ) .unwrap ( ) ;
3597
assert_eq! ( origin.tag_heads ( " v1" ) .unwrap ( ) , None ) ;
3598
assert_eq! ( a.tag_heads ( " v1" ) .unwrap ( ) , None ) ;
3599
assert! ( origin.tag_withdrawal ( " v1" ) .unwrap ( ) .is_some ( ) ) ;
3600
assert! ( origin.tag_attest ( " v1" ) .unwrap ( ) .is_some ( ) , " kept beside it" ) ;
3601
let refs = origin.remote_refs ( ) .unwrap ( ) ;
3602
assert! ( refs.tags.iter ( ) .all ( | t | t.name ! = " v1" ) ) ;
3603
assert_eq! ( refs.withdrawn.len ( ) , 1 ) ;
3604
let r = a.push_delete ( " origin" , " tags/v1" ) .unwrap ( ) ;
3605
assert! ( r[ 0 ] .ok & & r[ 0 ] .reason = = " already withdrawn" , " {r:?}" ) ;
3606
3607
3608
let rep = b.fetch ( " origin" ) .unwrap ( ) ;
3609
assert_eq! ( rep.withdrawn_tags, vec! [ " v1" .to_string ( ) ] ) ;
3610
assert_eq! ( b.tag_heads ( " v1" ) .unwrap ( ) , None ) ;
3611
assert! ( b.tag_create ( " v1" , & [ h1] , " " ) .is_err ( ) ) ;
3612
let r = origin
3613
.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ original] )
3614
.unwrap ( ) ;
3615
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " withdrawn" ) , " {r:?}" ) ;
3616
3617
3618
a.tag_delete ( " v2" , " tagged too early" ) .unwrap ( ) ;
3619
let r = a.push ( " origin" , false ) .unwrap ( ) ;
3620
assert! (
3621
r.iter ( )
3622
.any ( | x | x.name = = " tags/v2" & & x.ok & & x.reason = = " withdrawn" ) ,
3623
" {r:?}"
3624
) ;
3625
origin.refresh ( ) .unwrap ( ) ;
3626
assert_eq! ( origin.tag_heads ( " v2" ) .unwrap ( ) , None ) ;
3627
let ( _ , w) = origin.tag_withdrawal ( " v2" ) .unwrap ( ) .unwrap ( ) ;
3628
assert! (
3629
matches! ( & w.attest.body, AttestBody:: Untag { reason, .. } if reason = = " tagged too early" )
3630
) ;
3631
}
3632
3633
# [ test ]
132b884b Andy McCall 1 month ago ↶
3634
fn tags_are_immutable_named_states_and_travel ( ) {
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 ( ) ;
75167672 Andy McCall 1 month ago
3640
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
3641
.unwrap ( )
3642
.with_home ( root.join ( " home-a" ) ) ;
3643
let ( key_a, _ ) = a
3644
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
3645
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3646
write ( & root.join ( " a" ) , " f.txt" , b " 1\n " ) ;
3647
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
3648
write ( & root.join ( " a" ) , " f.txt" , b " 1\n 2\n " ) ;
3649
let h2 = a.commit ( " two" , & [ ] ) .unwrap ( ) ;
3650
3651
let t = a.tag_create ( " v1" , & [ h1] , " first release" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3652
assert_eq! (
3653
( t.heads.clone ( ) , t.signer.as_deref ( ) ) ,
3654
( vec! [ h1] , Some ( " Ada" ) )
3655
) ;
132b884b Andy McCall 1 month ago ↶
3656
assert! ( a.tag_create ( " v1" , & [ h2] , " " ) .is_err ( ) , " immutable" ) ;
3657
assert! ( a.tag_create ( " bad/name" , & [ h2] , " " ) .is_err ( ) ) ;
3658
assert_eq! ( a.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
3659
assert_eq! ( a.resolve_heads ( " v1" ) .unwrap ( ) , vec! [ h1] ) ;
3660
assert_eq! ( a.resolve_heads ( " main" ) .unwrap ( ) , vec! [ h2] ) ;
3661
let ( _ , sa) = a.tag_attest ( " v1" ) .unwrap ( ) .unwrap ( ) ;
3662
assert_eq! ( sa.sigs[ 0 ] .key, key_a) ;
ee130dd9 Andy McCall 16 days ago ↶
3663
3664
3665
let mut refs = a.remote_refs ( ) .unwrap ( ) ;
3666
a.refs_for_client ( & mut refs, None ) ;
3667
assert_eq! ( refs.tags.len ( ) , 1 ) ;
132b884b Andy McCall 1 month ago ↶
3668
75167672 Andy McCall 1 month ago
3669
a.checkout ( & a.resolve_heads ( " v1" ) .unwrap ( ) , false , " co" )
3670
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3671
assert_eq! ( fs:: read( root.join ( " a/f.txt" ) ) .unwrap ( ) , b " 1\n " ) ;
75167672 Andy McCall 1 month ago
3672
a.checkout ( & a.bookmark_heads ( ) .unwrap ( ) , false , " co" )
3673
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3674
3675
3676
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
3677
origin.refresh ( ) .unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3678
assert! ( r.iter ( ) .any ( | x | x.name = = " tags/v1" & & x.ok ) , " {r:?}" ) ;
3679
assert_eq! ( origin.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
75167672 Andy McCall 1 month ago
3680
let mut forged = a
3681
.tag_refs ( )
3682
.unwrap ( )
3683
.into_iter ( )
3684
.find ( | t | t.name = = " v1" )
3685
.unwrap ( ) ;
3686
forged.state = origin
3687
.store ( )
3688
.put_state ( & StateObject:: new( vec! [ h2] ) )
3689
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3690
forged.heads = vec! [ h2] ;
3691
forged.attestation = None ;
75167672 Andy McCall 1 month ago
3692
let r = origin
3693
.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ forged] )
3694
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3695
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " immutable" ) , " {r:?}" ) ;
3696
3697
75167672 Andy McCall 1 month ago
3698
let b = Repo:: clone( & url, & root.join ( " b" ) )
3699
.unwrap ( )
3700
.with_home ( root.join ( " home-b" ) ) ;
132b884b Andy McCall 1 month ago ↶
3701
assert_eq! ( b.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
3702
assert! ( b.tags ( ) .unwrap ( ) [ 0 ] .signer.as_deref ( ) = = Some ( " Ada" ) ) ;
3703
a.tag_create ( " v2" , & [ h2] , " second" ) .unwrap ( ) ;
3704
a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
3705
origin.refresh ( ) .unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3706
let rep = b.fetch ( " origin" ) .unwrap ( ) ;
3707
assert_eq! ( rep.new_tags, 1 ) ;
3708
assert_eq! ( b.tag_heads ( " v2" ) .unwrap ( ) , Some ( vec! [ h2] ) ) ;
3709
3710
3711
a.policy_init ( ) .unwrap ( ) ;
3712
a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
3713
origin.refresh ( ) .unwrap ( ) ;
1ec6fdbc Andy McCall 17 days ago ↶
3714
let unattested = crate :: protocol:: TagRef:: new(
3715
" v3" .into ( ) ,
3716
origin
75167672 Andy McCall 1 month ago
3717
.store ( )
3718
.put_state ( & StateObject:: new( vec! [ h2] ) )
3719
.unwrap ( ) ,
1ec6fdbc Andy McCall 17 days ago ↶
3720
vec! [ h2] ,
3721
None ,
3722
) ;
75167672 Andy McCall 1 month ago
3723
let r = origin
3724
.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ unattested] )
3725
.unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3726
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " attestation" ) , " {r:?}" ) ;
3727
a.tag_create ( " v3" , & [ h2] , " " ) .unwrap ( ) ;
3728
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
3729
origin.refresh ( ) .unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
3730
assert! ( r.iter ( ) .any ( | x | x.name = = " tags/v3" & & x.ok ) , " {r:?}" ) ;
3731
assert! ( a.gc ( true ) .is_ok ( ) ) ;
3732
assert_eq! ( a.tags ( ) .unwrap ( ) .len ( ) , 3 , " tags survive gc" ) ;
e5d9b051 Andy McCall 20 days ago ↶
3733
3734
3735
3736
3737
let rot = a.rotate_identity ( ) .unwrap ( ) ;
3738
let r = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
3739
origin.refresh ( ) .unwrap ( ) ;
e5d9b051 Andy McCall 20 days ago ↶
3740
assert! ( r.iter ( ) .all ( | x | x.ok ) , " {r:?}" ) ;
3741
let c = Repo:: clone( & url, & root.join ( " c" ) )
3742
.unwrap ( )
3743
.with_home ( root.join ( " home-c" ) ) ;
3744
assert_eq! ( c.tag_heads ( " v3" ) .unwrap ( ) , Some ( vec! [ h2] ) ) ;
3745
let ( _ , sp) = c.current_policy ( ) .unwrap ( ) .unwrap ( ) ;
3746
assert! ( ! sp.policy.authorizes ( & key_a, " tags/v9" ) ) ;
3747
let made = c.identity ( & rot.new) .unwrap ( ) .created;
3748
assert! ( c.predecessor_authorized ( & sp.policy, & key_a, " tags/v9" , made - 1 ) ) ;
3749
assert! ( ! c.predecessor_authorized ( & sp.policy, & key_a, " tags/v9" , made) ) ;
3750
assert! ( ! c.predecessor_authorized ( & sp.policy, & rot.new, " tags/v9" , 0 ) ) ;
132b884b Andy McCall 1 month ago ↶
3751
}
4e88e774 Andy McCall 1 month ago ↶
3752
3753
# [ test ]
3754
fn multiple_workspaces_share_a_repository ( ) {
3755
hermetic ( ) ;
3756
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3757
let root = dir.path ( ) ;
3758
let main_dir = root.join ( " main" ) ;
3759
let repo = Repo:: init( & main_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
3760
write ( & main_dir, " a.txt" , b " a\n " ) ;
3761
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
3762
assert_eq! ( repo.workspaces ( ) .unwrap ( ) .len ( ) , 1 ) ;
3763
3764
3765
let ws_dir = root.join ( " ws2" ) ;
4476cdbb Andy McCall 11 days ago ↶
3766
let ops = repo.op_log ( ) .unwrap ( ) .len ( ) ;
4e88e774 Andy McCall 1 month ago ↶
3767
let ws = repo.workspace_add ( & ws_dir, None ) .unwrap ( ) ;
4476cdbb Andy McCall 11 days ago ↶
3768
assert_eq! (
3769
ws.op_log ( ) .unwrap ( ) .len ( ) ,
3770
ops + 2 ,
3771
" the add and its checkout, and no snapshot finding the tree deleted (oxygen#137)"
3772
) ;
4e88e774 Andy McCall 1 month ago ↶
3773
assert! ( ws_dir.join ( " .oxy" ) .is_file ( ) ) ;
3774
assert_eq! ( fs:: read( ws_dir.join ( " a.txt" ) ) .unwrap ( ) , b " a\n " ) ;
3775
assert_ne! ( ws.workspace ( ) , repo.workspace ( ) ) ;
3776
let list = repo.workspaces ( ) .unwrap ( ) ;
3777
assert_eq! ( list.len ( ) , 2 ) ;
75167672 Andy McCall 1 month ago
3778
assert! ( list
3779
.iter ( )
3780
.any ( | w | w.id = = ws.workspace ( ) & & w.path = = ws_dir.canonicalize ( ) .unwrap ( ) & & ! w.current ) ) ;
4e88e774 Andy McCall 1 month ago ↶
3781
assert! ( ! ws.snapshot ( " status" ) .unwrap ( ) .changed) ;
3782
3783
3784
write ( & ws_dir, " b.txt" , b " b\n " ) ;
3785
let s = ws.snapshot ( " status" ) .unwrap ( ) ;
3786
assert_eq! ( s.statuses.len ( ) , 1 ) ;
75167672 Andy McCall 1 month ago
3787
assert! (
3788
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
3789
" the first workspace is untouched"
3790
) ;
4e88e774 Andy McCall 1 month ago ↶
3791
let h2 = ws.commit ( " b from ws2" , & [ ] ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
3792
repo.refresh ( ) .unwrap ( ) ;
4e88e774 Andy McCall 1 month ago ↶
3793
let mut heads = repo.bookmark_heads ( ) .unwrap ( ) ;
3794
heads.sort ( ) ;
3795
let mut want = vec! [ h1, h2] ;
3796
want.sort ( ) ;
3797
assert_eq! ( heads, want, " both workspaces see the bookmark move" ) ;
3798
assert! ( ! main_dir.join ( " b.txt" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
3799
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " co" )
3800
.unwrap ( ) ;
4e88e774 Andy McCall 1 month ago ↶
3801
assert! ( main_dir.join ( " b.txt" ) .exists ( ) ) ;
3802
3803
3804
let reopened = Repo:: open( & ws_dir) .unwrap ( ) ;
3805
assert_eq! ( reopened.workspace ( ) , ws.workspace ( ) ) ;
3806
assert_eq! ( reopened.id ( ) .unwrap ( ) , repo.id ( ) .unwrap ( ) ) ;
3807
assert_eq! ( reopened.quick_status ( ) .unwrap ( ) .heads, 2 ) ;
3808
3809
3810
assert! ( repo.workspace_add ( & main_dir.join ( " inner" ) , None ) .is_err ( ) ) ;
3811
assert! ( repo.workspace_forget ( & repo.workspace ( ) ) .is_err ( ) ) ;
3812
repo.workspace_forget ( & ws.workspace ( ) ) .unwrap ( ) ;
3813
assert_eq! ( repo.workspaces ( ) .unwrap ( ) .len ( ) , 1 ) ;
3814
assert! ( ws_dir.join ( " b.txt" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
3815
assert! (
3816
ws.snapshot ( " status" ) .is_err ( ) ,
3817
" a forgotten workspace is no longer in the view"
3818
) ;
4e88e774 Andy McCall 1 month ago ↶
3819
6caacfff Andy McCall 1 month ago ↶
3820
3821
4e88e774 Andy McCall 1 month ago ↶
3822
let lock = repo.lock_snapshot ( ) .unwrap ( ) ;
6caacfff Andy McCall 1 month ago ↶
3823
let again = repo.lock_working_copy ( ) .unwrap ( ) ;
3824
drop ( again) ;
4e88e774 Andy McCall 1 month ago ↶
3825
drop ( lock) ;
75167672 Andy McCall 1 month ago
3826
let lock_path = main_dir
3827
.join ( " .oxy/workspaces" )
3828
.join ( repo.workspace ( ) .to_hex ( ) )
3829
.join ( " lock" ) ;
4e88e774 Andy McCall 1 month ago ↶
3830
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 120 ) ;
6caacfff Andy McCall 1 month ago ↶
3831
let age = | p : & Path| fs:: File:: open( p) .unwrap ( ) .set_modified ( old) .unwrap ( ) ;
3832
fs:: write( & lock_path, std:: process:: id( ) .to_string ( ) ) .unwrap ( ) ;
3833
age ( & lock_path) ;
3834
assert! (
3835
! super :: workspace:: lock_is_stale( & lock_path) ,
3836
" a live process keeps its lock"
3837
) ;
3838
fs:: write( & lock_path, " 4294967295" ) .unwrap ( ) ;
3839
age ( & lock_path) ;
4e88e774 Andy McCall 1 month ago ↶
3840
assert! ( repo.lock_snapshot ( ) .is_ok ( ) , " stale lock taken over" ) ;
3841
}
cdb5beba Andy McCall 1 month ago ↶
3842
3843
# [ test ]
3844
fn attributes_binary_eol_nocompress_lockable ( ) {
3845
hermetic ( ) ;
3846
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3847
let root = dir.path ( ) ;
3848
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
3849
write (
3850
root,
3851
" .oxyattributes" ,
3852
b " *.dat binary nocompress\n *.txt eol=crlf\n assets/** lockable\n " ,
3853
) ;
cdb5beba Andy McCall 1 month ago ↶
3854
write ( root, " plain.dat" , b " no nul bytes here\n " ) ;
3855
write ( root, " notes.txt" , b " one\r \n two\r \n " ) ;
3856
write ( root, " assets/model.bin" , b " \0 model" ) ;
3857
let h = repo.commit ( " attrs" , & [ ] ) .unwrap ( ) ;
3858
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
3859
assert! (
3860
snap.files[ " plain.dat" ] .mode.opaque,
3861
" binary attribute forces opaque without NUL bytes"
3862
) ;
3863
assert! (
3864
matches! ( & snap.files[ " notes.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n " ) ,
3865
" stored with LF"
3866
) ;
3867
assert! (
3868
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
3869
" CRLF on disk equals LF in the state"
3870
) ;
cdb5beba Andy McCall 1 month ago ↶
3871
3872
let r = repo.gc ( true ) .unwrap ( ) ;
3873
assert! ( r.packed.objects > 0 ) ;
3874
3875
fs:: remove_file( root.join ( " notes.txt" ) ) .unwrap ( ) ;
3876
repo.checkout ( & [ h] , true , " co" ) .unwrap ( ) ;
3877
assert_eq! ( fs:: read( root.join ( " notes.txt" ) ) .unwrap ( ) , b " one\r \n two\r \n " ) ;
75167672 Andy McCall 1 month ago
3878
assert! ( fs:: metadata( root.join ( " assets/model.bin" ) )
3879
.unwrap ( )
3880
.permissions ( )
3881
.readonly ( ) ) ;
cdb5beba Andy McCall 1 month ago ↶
3882
assert! ( repo.lock_path ( " plain.dat" ) .is_err ( ) , " not lockable" ) ;
3883
repo.lock_path ( " assets/model.bin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3884
assert! ( ! fs:: metadata( root.join ( " assets/model.bin" ) )
3885
.unwrap ( )
3886
.permissions ( )
3887
.readonly ( ) ) ;
cdb5beba Andy McCall 1 month ago ↶
3888
assert! ( repo.is_locked ( " assets/model.bin" ) ) ;
3889
write ( root, " assets/model.bin" , b " \0 model2" ) ;
3890
repo.commit ( " edit model" , & [ ] ) .unwrap ( ) ;
3891
repo.unlock_path ( " assets/model.bin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3892
assert! ( fs:: metadata( root.join ( " assets/model.bin" ) )
3893
.unwrap ( )
3894
.permissions ( )
3895
.readonly ( ) ) ;
cdb5beba Andy McCall 1 month ago ↶
3896
3897
repo.checkout ( & [ h] , true , " co" ) .unwrap ( ) ;
3898
assert_eq! ( fs:: read( root.join ( " assets/model.bin" ) ) .unwrap ( ) , b " \0 model" ) ;
3899
}
6d3eefbc Andy McCall 1 month ago ↶
3900
3901
# [ cfg ( unix ) ]
3902
# [ test ]
3903
fn hooks_can_veto_observe_and_add_dependencies ( ) {
3904
use std:: os:: unix:: fs:: PermissionsExt;
3905
hermetic ( ) ;
3906
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3907
let root = dir.path ( ) ;
3908
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
3909
let hooks = root.join ( " .oxy/hooks" ) ;
3910
fs:: create_dir_all( & hooks) .unwrap ( ) ;
3911
let hook = | name : & str , body : & str | {
3912
let p = hooks.join ( name) ;
3913
fs:: write( & p, format! ( " #!/bin/sh\n {body} \n " ) ) .unwrap ( ) ;
3914
fs:: set_permissions( & p, fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
3915
} ;
3916
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" ) ;
3917
hook ( " post-commit" , " cat > \" $OXY_ROOT/.last-post-commit\" " ) ;
3918
hook ( " post-checkout" , " cat > \" $OXY_ROOT/.last-post-checkout\" " ) ;
3919
hook ( " pre-push" , " echo pushing blocked >&2; exit 3" ) ;
3920
write ( root, " .oxyignore" , b " .last-*\n " ) ;
3921
write ( root, " a.txt" , b " a\n " ) ;
3922
let err = repo.commit ( " WIP: nope" , & [ ] ) .unwrap_err ( ) .to_string ( ) ;
3923
assert! ( err.contains ( " pre-commit refused: no WIP commits" ) , " {err}" ) ;
3924
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
3925
let pre = fs:: read_to_string( root.join ( " .last-pre-commit" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3926
assert! (
3927
pre.contains ( " \" event\" :\" pre-commit\" " )
3928
& & pre.contains ( " \" path\" :\" a.txt\" " )
3929
& & pre.contains ( " \" message\" :\" first\" " ) ,
3930
" {pre}"
3931
) ;
6d3eefbc Andy McCall 1 month ago ↶
3932
let post = fs:: read_to_string( root.join ( " .last-post-commit" ) ) .unwrap ( ) ;
3933
assert! ( post.contains ( & h1.to_hex ( ) ) , " {post}" ) ;
3934
repo.checkout ( & [ h1] , false , " co" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3935
assert! ( fs:: read_to_string( root.join ( " .last-post-checkout" ) )
3936
.unwrap ( )
3937
.contains ( " post-checkout" ) ) ;
6d3eefbc Andy McCall 1 month ago ↶
3938
repo.config.set ( " remote.origin.url" , " /nowhere" ) .unwrap ( ) ;
3939
repo.save_config ( ) .unwrap ( ) ;
3940
let err = repo.push ( " origin" , false ) .unwrap_err ( ) .to_string ( ) ;
3941
assert! ( err.contains ( " pre-push refused: pushing blocked" ) , " {err}" ) ;
3942
3943
3944
write ( root, " b.txt" , b " b\n " ) ;
75167672 Andy McCall 1 month ago
3945
hook (
3946
" semantic-deps" ,
3947
& format! (
3948
" cat > \" $OXY_ROOT/.last-semantic\" ; echo '{{ \" deps\" : [\" {} \" ]}} '" ,
3949
h1.to_hex ( )
3950
) ,
3951
) ;
6d3eefbc Andy McCall 1 month ago ↶
3952
let h2 = repo.commit ( " b without hook" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3953
assert! (
3954
repo.store ( ) .get_change ( & h2) .unwrap ( ) .deps.is_empty ( ) ,
3955
" hook disabled by default"
3956
) ;
6d3eefbc Andy McCall 1 month ago ↶
3957
repo.config.set ( " hooks.semantic-deps" , " true" ) .unwrap ( ) ;
3958
repo.save_config ( ) .unwrap ( ) ;
3959
write ( root, " c.txt" , b " c\n " ) ;
3960
let h3 = repo.commit ( " c with hook" , & [ ] ) .unwrap ( ) ;
3961
assert_eq! ( repo.store ( ) .get_change ( & h3) .unwrap ( ) .deps, vec! [ h1] ) ;
3962
let sem = fs:: read_to_string( root.join ( " .last-semantic" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
3963
assert! (
3964
sem.contains ( " \" touched_paths\" :[\" c.txt\" ]" ) & & sem.contains ( " candidate_deps" ) ,
3965
" {sem}"
3966
) ;
6d3eefbc Andy McCall 1 month ago ↶
3967
assert_eq! ( json_str ( " a\" b\\ c\n " ) , " \" a\\ \" b\\ \\ c\\ n\" " ) ;
3968
}
23b217d7 Andy McCall 1 month ago ↶
3969
3970
# [ test ]
3971
fn amend_into_older_change_and_split ( ) {
3972
hermetic ( ) ;
3973
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
3974
let root = dir.path ( ) ;
3975
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
3976
repo.create_identity ( " Ada" , " a@b" , IdentityScope:: User)
3977
.unwrap ( ) ;
23b217d7 Andy McCall 1 month ago ↶
3978
repo.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
3979
write ( root, " a.txt" , b " one\n two\n " ) ;
3980
let h0 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
3981
write ( root, " a.txt" , b " one\n TWO\n " ) ;
3982
let h1 = repo.commit ( " edit two" , & [ ] ) .unwrap ( ) ;
3983
write ( root, " a.txt" , b " one\n TWO\n three\n " ) ;
3984
let h2 = repo.commit ( " add three" , & [ ] ) .unwrap ( ) ;
3985
let id1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) .change_id;
3986
3987
3988
write ( root, " a.txt" , b " one\n TWO!\n three\n " ) ;
3989
let h1b = repo.amend_into ( & h1, None ) .unwrap ( ) ;
3990
assert_ne! ( h1b, h1) ;
3991
let c1b = repo.store ( ) .get_change ( & h1b) .unwrap ( ) ;
3992
assert_eq! ( c1b.change_id, id1) ;
3993
assert_eq! ( c1b.deps, vec! [ h0] ) ;
75167672 Andy McCall 1 month ago
3994
assert! ( matches! (
3995
repo.verify_change ( & h1b) .unwrap ( ) ,
3996
SignatureStatus:: Valid { .. }
3997
) ) ;
23b217d7 Andy McCall 1 month ago ↶
3998
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
3999
assert_eq! ( heads.len ( ) , 1 ) ;
4000
let top = repo.store ( ) .get_change ( & heads[ 0 ] ) .unwrap ( ) ;
4001
assert_eq! ( top.message, " add three" ) ;
75167672 Andy McCall 1 month ago
4002
assert_eq! (
4003
top.deps.iter ( ) .copied ( ) .collect:: < BTreeSet< _ > > ( ) ,
4004
[ h0, h1b] .into_iter ( ) .collect ( ) ,
4005
" depends on the file's creator and the rewritten line"
4006
) ;
23b217d7 Andy McCall 1 month ago ↶
4007
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
4008
assert! (
4009
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n TWO!\n three\n " )
4010
) ;
4011
assert! (
4012
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
4013
" the working copy is clean after folding"
4014
) ;
23b217d7 Andy McCall 1 month ago ↶
4015
4016
let only = repo.load_state ( & [ h1b] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
4017
assert! (
4018
matches! ( & only.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n TWO!\n " )
4019
) ;
23b217d7 Andy McCall 1 month ago ↶
4020
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 ) ;
4021
4022
4023
write ( root, " a.txt" , b " one\n TWO!\n three!\n " ) ;
4024
let err = repo.amend_into ( & h1b, None ) .unwrap_err ( ) .to_string ( ) ;
4025
assert! ( err.contains ( " introduced after" ) , " {err}" ) ;
75167672 Andy McCall 1 month ago
4026
assert! (
4027
repo.snapshot ( " status" ) .unwrap ( ) .statuses.len ( ) = = 1 ,
4028
" nothing was changed by the refusal"
4029
) ;
23b217d7 Andy McCall 1 month ago ↶
4030
write ( root, " a.txt" , b " one\n TWO!\n three\n " ) ;
4031
4032
4033
write ( root, " b.txt" , b " b\n " ) ;
4034
write ( root, " c.txt" , b " c\n " ) ;
4035
let h3 = repo.commit ( " b and c" , & [ ] ) .unwrap ( ) ;
4036
let id3 = repo.store ( ) .get_change ( & h3) .unwrap ( ) .change_id;
4037
let ( p1, p2) = repo.split ( & h3, & [ " b.txt" .into ( ) ] ) .unwrap ( ) ;
4038
let c_1 = repo.store ( ) .get_change ( & p1) .unwrap ( ) ;
4039
let c_2 = repo.store ( ) .get_change ( & p2) .unwrap ( ) ;
4040
assert! ( c_1 .message.contains ( " part 1: b.txt" ) ) ;
4041
assert_eq! ( c_2 .change_id, id3) ;
4042
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
75167672 Andy McCall 1 month ago
4043
assert! (
4044
heads.contains ( & p1) & & heads.contains ( & p2) ,
4045
" independent parts are both heads"
4046
) ;
4047
let snap = repo
4048
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
4049
.unwrap ( )
4050
.materialize ( ) ;
23b217d7 Andy McCall 1 month ago ↶
4051
assert! ( snap.files.contains_key ( " b.txt" ) & & snap.files.contains_key ( " c.txt" ) ) ;
4052
let only1 = repo.load_state ( & [ p1] ) .unwrap ( ) .materialize ( ) ;
4053
assert! ( only1.files.contains_key ( " b.txt" ) & & ! only1.files.contains_key ( " c.txt" ) ) ;
4054
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4055
assert! ( repo.split ( & p2, & [ " nope.txt" .into ( ) ] ) .is_err ( ) ) ;
4056
let _ = h2;
4057
}
28fc23d7 Andy McCall 1 month ago ↶
4058
4059
# [ test ]
4060
fn identity_lifecycle ( ) {
4061
hermetic ( ) ;
4062
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4063
let root = dir.path ( ) ;
4064
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
4065
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
4066
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
4067
.unwrap ( )
4068
.with_home ( root.join ( " home-a" ) ) ;
28fc23d7 Andy McCall 1 month ago ↶
4069
a.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4070
let ( key_a, _ ) = a
4071
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
4072
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4073
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
4074
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
4075
assert! ( a.policy_init ( ) .unwrap ( ) .applied) ;
4076
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
767ac6cd Andy McCall 18 days ago
4077
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4078
4079
75167672 Andy McCall 1 month ago
4080
let mut e = Repo:: init( & root.join ( " e" ) )
4081
.unwrap ( )
4082
.with_home ( root.join ( " home-e" ) ) ;
28fc23d7 Andy McCall 1 month ago ↶
4083
e.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4084
e.create_identity_with (
4085
" Eve" ,
4086
" [email protected] " ,
4087
IdentityScope:: User,
4088
Some ( Hlc:: now( ) .wall_ms - 1 ) ,
4089
)
4090
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4091
write ( & root.join ( " e" ) , " e.txt" , b " e\n " ) ;
4092
let he = e.commit ( " late" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4093
assert! (
4094
matches! ( e.verify_change ( & he) .unwrap ( ) , SignatureStatus:: Invalid( m) if m.contains ( " expired" ) )
4095
) ;
28fc23d7 Andy McCall 1 month ago ↶
4096
75167672 Andy McCall 1 month ago
4097
let r = e
4098
.renew_identity ( Some ( Hlc:: now( ) .wall_ms + 86_400_000 ) )
4099
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4100
assert_eq! ( e.identity ( & r.new) .unwrap ( ) .previous, Some ( r.old) ) ;
4101
write ( & root.join ( " e" ) , " e.txt" , b " e2\n " ) ;
4102
let he2 = e.commit ( " in time" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4103
assert! (
4104
matches! ( e.verify_change ( & he2) .unwrap ( ) , SignatureStatus:: Valid { key, .. } if key = = r.new)
4105
) ;
28fc23d7 Andy McCall 1 month ago ↶
4106
4107
4108
let r = a.rotate_identity ( ) .unwrap ( ) ;
4109
assert_eq! ( r.old, key_a) ;
4110
assert_eq! ( a.config.author_key, Some ( r.new) ) ;
4111
assert_eq! ( a.identity ( & r.new) .unwrap ( ) .previous, Some ( key_a) ) ;
4112
assert! ( r.policy.as_ref ( ) .unwrap ( ) .applied) ;
4113
let ( _ , sp) = a.current_policy ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4114
assert_eq! (
4115
( sp.policy.serial, sp.policy.roots.clone ( ) ) ,
4116
( 2 , vec! [ r.new] )
4117
) ;
4118
assert! (
4119
! sp.policy.revoked.contains ( & key_a) & & sp.policy.authorizes ( & r.new, " main" ) ,
4120
" superseded, not revoked"
4121
) ;
28fc23d7 Andy McCall 1 month ago ↶
4122
assert! ( sp.policy.succeeds ( & r.new, & key_a) & & ! sp.policy.succeeds ( & key_a, & r.new) ) ;
75167672 Andy McCall 1 month ago
4123
assert! (
4124
matches! ( a.verify_change ( & h1) .unwrap ( ) , SignatureStatus:: Valid { key, revoked: false , .. } if key = = key_a)
4125
) ;
28fc23d7 Andy McCall 1 month ago ↶
4126
write ( & root.join ( " a" ) , " a.txt" , b " a2\n " ) ;
4127
let h2 = a.commit ( " two" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4128
assert! (
4129
matches! ( a.verify_change ( & h2) .unwrap ( ) , SignatureStatus:: Valid { key, revoked: false , .. } if key = = r.new)
4130
) ;
28fc23d7 Andy McCall 1 month ago ↶
4131
4132
let res = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
4133
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4134
assert! ( res.iter ( ) .all ( | x | x.ok ) , " {res:?}" ) ;
4135
assert_eq! ( origin.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 2 ) ;
4136
let key_a = r.new;
4137
4138
4139
75167672 Andy McCall 1 month ago
4140
let mut b = Repo:: clone( & url, & root.join ( " b" ) )
4141
.unwrap ( )
4142
.with_home ( root.join ( " home-b" ) ) ;
4143
let ( key_b, id_b) = b
4144
.create_identity ( " Bob" , " [email protected] " , IdentityScope:: User)
4145
.unwrap ( ) ;
7ba155f2 Andy McCall 1 month ago ↶
4146
let r = a.delegate_add ( id_b.clone ( ) , Scope:: ALL , None ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4147
assert_eq! ( r.old, key_a) ;
4148
let key_a2 = r.new;
75167672 Andy McCall 1 month ago
4149
assert_eq! (
7ba155f2 Andy McCall 1 month ago ↶
4150
a.identity ( & key_a2) .unwrap ( ) .delegates,
4151
vec! [ Delegate:: full( key_b) ]
4152
) ;
4153
assert_eq! (
75167672 Andy McCall 1 month ago
4154
a.identity ( & key_a2) .unwrap ( ) .ed25519_pk,
4155
a.identity ( & key_a) .unwrap ( ) .ed25519_pk
7ba155f2 Andy McCall 1 month ago ↶
4156
) ;
4157
assert! (
4158
a.delegate_add ( id_b.clone ( ) , Scope:: ALL , None ) .is_err ( ) ,
4159
" already a delegate"
75167672 Andy McCall 1 month ago
4160
) ;
28fc23d7 Andy McCall 1 month ago ↶
4161
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | x | x.ok ) ) ;
767ac6cd Andy McCall 18 days ago
4162
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4163
write ( & root.join ( " a" ) , " a.txt" , b " a3\n " ) ;
4164
let h3 = a.commit ( " three" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4165
assert! (
4166
matches! ( a.verify_change ( & h3) .unwrap ( ) , SignatureStatus:: Valid { key, .. } if key = = key_a2) ,
4167
" the same secret signs under the new KeyId"
4168
) ;
28fc23d7 Andy McCall 1 month ago ↶
4169
4170
a.set_user_config ( " core.sign" , " seal" ) .unwrap ( ) ;
4171
write ( & root.join ( " a" ) , " a.txt" , b " a4\n " ) ;
4172
let h4 = a.commit ( " four" , & [ ] ) .unwrap ( ) ;
4173
assert_eq! ( a.verify_change ( & h4) .unwrap ( ) , SignatureStatus:: Unsigned) ;
4174
let ( _ , sk_b) = b.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4175
a.store ( )
4176
.put_signature ( & h4, & sk_b.sign ( Domain:: Change, & key_b, & h4) )
4177
.unwrap ( ) ;
4178
assert! (
4179
matches! ( a.verify_change ( & h4) .unwrap ( ) , SignatureStatus:: Valid { key, .. } if key = = key_b)
4180
) ;
28fc23d7 Andy McCall 1 month ago ↶
4181
a.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
4182
4183
let r = a.delegate_remove ( & key_b) .unwrap ( ) ;
4184
assert! ( a.identity ( & r.new) .unwrap ( ) .delegates.is_empty ( ) ) ;
4185
assert! ( a.delegate_remove ( & key_b) .is_err ( ) ) ;
4186
let key_a3 = r.new;
4187
let res = a.push ( " origin" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
4188
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4189
assert! ( res.iter ( ) .all ( | x | x.ok ) , " {res:?}" ) ;
4190
4191
4192
4193
assert! ( a.policy_root_add ( id_b.clone ( ) ) .unwrap ( ) .applied) ;
4194
assert! ( a.policy_root_add ( id_b.clone ( ) ) .is_err ( ) ) ;
4195
assert! ( a.policy_set_threshold ( 3 ) .is_err ( ) ) ;
4196
assert! ( a.policy_set_threshold ( 2 ) .unwrap ( ) .applied) ;
4197
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | x | x.ok ) ) ;
767ac6cd Andy McCall 18 days ago
4198
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4199
b.pull ( " origin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4200
assert_eq! (
4201
b.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.root_threshold,
4202
2
4203
) ;
4204
let mut c = Repo:: init( & root.join ( " c" ) )
4205
.unwrap ( )
4206
.with_home ( root.join ( " home-c" ) ) ;
4207
let ( key_c, id_c) = c
4208
.create_identity ( " Cy" , " [email protected] " , IdentityScope:: User)
4209
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4210
let serial_before = a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial;
4211
let proposal = a.policy_grant ( id_c, None ) .unwrap ( ) ;
4212
assert! ( ! proposal.applied & & proposal.sigs.len ( ) = = 1 ) ;
4213
assert_eq! ( proposal.policy.serial, serial_before + 1 ) ;
75167672 Andy McCall 1 month ago
4214
assert_eq! (
4215
a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial,
4216
serial_before,
4217
" a proposal changes nothing yet"
4218
) ;
28fc23d7 Andy McCall 1 month ago ↶
4219
4220
let bytes = proposal.signed ( ) .encode ( ) ;
75167672 Andy McCall 1 month ago
4221
let signed = b
4222
.policy_sign ( SignedPolicy:: decode( & bytes) .unwrap ( ) )
4223
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4224
assert! ( signed.applied & & signed.sigs.len ( ) = = 2 ) ;
75167672 Andy McCall 1 month ago
4225
assert! ( b
4226
.current_policy ( )
4227
.unwrap ( )
4228
.unwrap ( )
4229
.1
4230
.policy
4231
.authorizes ( & key_c, " main" ) ) ;
4232
assert! (
4233
b.policy_sign ( signed.signed ( ) ) .is_err ( ) ,
4234
" a stale proposal is refused"
4235
) ;
28fc23d7 Andy McCall 1 month ago ↶
4236
75167672 Andy McCall 1 month ago
4237
assert! ( b
4238
.push ( " origin" , false )
4239
.unwrap ( )
4240
.iter ( )
4241
.any ( | x | x.name = = " policy" & & x.ok ) ) ;
767ac6cd Andy McCall 18 days ago
4242
origin.refresh ( ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4243
a.pull ( " origin" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4244
assert_eq! (
4245
a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial,
4246
serial_before + 1
4247
) ;
28fc23d7 Andy McCall 1 month ago ↶
4248
4249
assert! ( a.policy_revoke ( & key_a3) .is_err ( ) ) ;
75167672 Andy McCall 1 month ago
4250
let u = a
4251
.revoke_identity ( & key_c, " left the project" )
4252
.unwrap ( )
4253
.unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
4254
assert! ( ! u.applied) ;
4255
assert! ( a.is_revoked ( & key_c) .unwrap ( ) ) ;
4256
assert! ( ! b.is_revoked ( & key_c) .unwrap ( ) ) ;
4257
let u = b.policy_sign ( u.signed ( ) ) .unwrap ( ) ;
4258
assert! ( u.applied & & u.policy.is_revoked ( & key_c) ) ;
4259
assert! ( b.is_revoked ( & key_c) .unwrap ( ) ) ;
4260
75167672 Andy McCall 1 month ago
4261
assert! (
4262
b.policy_revoke ( & key_a3) .is_err ( ) ,
4263
" would leave one root for a threshold of 2"
4264
) ;
28fc23d7 Andy McCall 1 month ago ↶
4265
4266
let key_c_own = c.config.author_key.unwrap ( ) ;
75167672 Andy McCall 1 month ago
4267
assert! ( c
4268
.revoke_identity ( & key_c_own, " lost laptop" )
4269
.unwrap ( )
4270
.is_none ( ) ) ;
28fc23d7 Andy McCall 1 month ago ↶
4271
assert! ( c.is_revoked ( & key_c_own) .unwrap ( ) ) ;
4272
assert_eq! ( key_c_own, key_c) ;
4273
}
4274
6e723b81 Andy McCall 1 month ago ↶
4275
# [ test ]
4276
fn renames_are_detected_and_tracked_as_moves ( ) {
4277
hermetic ( ) ;
4278
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4279
let root = dir.path ( ) ;
4280
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
4281
write (
4282
root,
4283
" src/old.rs" ,
4284
b " fn a() {}\n fn b() {}\n fn c() {}\n fn d() {}\n " ,
4285
) ;
6e723b81 Andy McCall 1 month ago ↶
4286
write ( root, " img.bin" , b " \x89 PNG\0 \0 \x01 \x02 " ) ;
4287
write ( root, " tiny.txt" , b " x\n " ) ;
4288
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4289
let node_old = repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) .files[ " src/old.rs" ] .node;
4290
let node_img = repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) .files[ " img.bin" ] .node;
4291
4292
4293
4294
fs:: remove_file( root.join ( " src/old.rs" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4295
write (
4296
root,
4297
" lib/new.rs" ,
4298
b " fn a() {}\n fn b() {}\n fn c() {}\n fn e() {}\n " ,
4299
) ;
6e723b81 Andy McCall 1 month ago ↶
4300
fs:: remove_file( root.join ( " img.bin" ) ) .unwrap ( ) ;
4301
write ( root, " assets/img.bin" , b " \x89 PNG\0 \0 \x01 \x02 " ) ;
4302
fs:: remove_file( root.join ( " tiny.txt" ) ) .unwrap ( ) ;
4303
write ( root, " other.txt" , b " completely different\n " ) ;
4304
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4305
let statuses: Vec< ( & str , ChangeStatus) > = snap
4306
.statuses
4307
.iter ( )
4308
.map ( | s | ( s.path.as_str ( ) , s.status) )
4309
.collect ( ) ;
6e723b81 Andy McCall 1 month ago ↶
4310
assert_eq! (
4311
statuses,
75167672 Andy McCall 1 month ago
4312
vec! [
4313
( " assets/img.bin" , ChangeStatus:: Renamed) ,
4314
( " lib/new.rs" , ChangeStatus:: Renamed) ,
4315
( " other.txt" , ChangeStatus:: Added) ,
4316
( " src" , ChangeStatus:: Removed) ,
4317
( " tiny.txt" , ChangeStatus:: Removed)
4318
]
6e723b81 Andy McCall 1 month ago ↶
4319
) ;
4320
let h2 = repo.commit ( " move things" , & [ ] ) .unwrap ( ) ;
4321
let c = repo.store ( ) .get_change ( & h2) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4322
let moves: Vec< _ > = c
4323
.tree_ops
4324
.iter ( )
4325
.filter ( | op | matches! ( op, TreeOp:: Move { .. } ) )
4326
.collect ( ) ;
6e723b81 Andy McCall 1 month ago ↶
4327
assert_eq! ( moves.len ( ) , 2 , " {:?}" , c.tree_ops) ;
75167672 Andy McCall 1 month ago
4328
assert! ( c.tree_ops.iter ( ) .any (
4329
| op | matches! ( op, TreeOp:: Move { node, name, .. } if * node = = node_old & & name = = " new.rs" )
4330
) ) ;
4331
assert! ( c.tree_ops.iter ( ) .any (
4332
| op | matches! ( op, TreeOp:: Move { node, name, .. } if * node = = node_img & & name = = " img.bin" )
4333
) ) ;
6e723b81 Andy McCall 1 month ago ↶
4334
let snap = repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
4335
assert_eq! (
4336
snap.files[ " lib/new.rs" ] .node, node_old,
4337
" the node survives the rename"
4338
) ;
4339
assert! (
4340
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 " )
4341
) ;
6e723b81 Andy McCall 1 month ago ↶
4342
assert! ( ! snap.files.contains_key ( " src/old.rs" ) & & ! snap.files.contains_key ( " src" ) ) ;
75167672 Andy McCall 1 month ago
4343
assert! ( render_change (
4344
& c,
4345
& repo.load_state ( & [ h1] ) .unwrap ( ) ,
4346
& repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( )
4347
)
4348
.contains ( " R " ) ) ;
6e723b81 Andy McCall 1 month ago ↶
4349
4350
let other = tempfile:: tempdir( ) .unwrap ( ) ;
4351
let clone = Repo:: clone( & root.to_string_lossy ( ) , other.path ( ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4352
assert_eq! (
4353
fs:: read( other.path ( ) .join ( " lib/new.rs" ) ) .unwrap ( ) ,
4354
b " fn a() {}\n fn b() {}\n fn c() {}\n fn e() {}\n "
4355
) ;
6e723b81 Andy McCall 1 month ago ↶
4356
assert! ( ! other.path ( ) .join ( " src" ) .exists ( ) ) ;
4357
assert! ( ! clone.snapshot ( " status" ) .unwrap ( ) .changed) ;
4358
4359
assert_eq! ( similarity ( b " a\n b\n " , b " a\n b\n " ) , 100 ) ;
4360
assert_eq! ( similarity ( b " a\n b\n c\n d\n " , b " a\n b\n x\n y\n " ) , 50 ) ;
4361
assert_eq! ( similarity ( b " hello\n " , b " world\n " ) , 0 ) ;
4362
assert_eq! ( similarity ( b " " , b " x\n " ) , 0 ) ;
4363
}
4364
30ea9c1e Andy McCall 1 month ago ↶
4365
# [ test ]
4366
fn git_fetch_and_push_round_trip ( ) {
4367
hermetic ( ) ;
75167672 Andy McCall 1 month ago
4368
if std:: process:: Command:: new( " git" )
4369
.arg ( " --version" )
4370
.output ( )
4371
.is_err ( )
4372
{
30ea9c1e Andy McCall 1 month ago ↶
4373
eprintln! ( " git not installed; skipping" ) ;
4374
return ;
4375
}
4376
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4377
let root = dir.path ( ) ;
4378
let git_in = | d : & Path, args : & [& str ]| -> String {
4379
let out = std:: process:: Command:: new( " git" )
4380
.arg ( " -C" )
4381
.arg ( d)
75167672 Andy McCall 1 month ago
4382
.args ( [
4383
" -c" ,
4384
" user.name=Grace" ,
4385
" -c" ,
4386
" [email protected] " ,
4387
" -c" ,
4388
" commit.gpgsign=false" ,
4389
] )
30ea9c1e Andy McCall 1 month ago ↶
4390
.args ( args)
4391
.output ( )
4392
.unwrap ( ) ;
75167672 Andy McCall 1 month ago
4393
assert! (
4394
out.status.success ( ) ,
4395
" git {args:?}: {}" ,
4396
String :: from_utf8_lossy( & out.stderr)
4397
) ;
30ea9c1e Andy McCall 1 month ago ↶
4398
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
4399
} ;
4400
4401
let remote = root.join ( " remote.git" ) ;
4402
fs:: create_dir_all( & remote) .unwrap ( ) ;
4403
git_in ( & remote, & [ " init" , " -q" , " --bare" , " -b" , " main" ] ) ;
4404
let work = root.join ( " work" ) ;
75167672 Andy McCall 1 month ago
4405
git_in (
4406
root,
4407
& [
4408
" clone" ,
4409
" -q" ,
4410
remote.to_str ( ) .unwrap ( ) ,
4411
work.to_str ( ) .unwrap ( ) ,
4412
] ,
4413
) ;
30ea9c1e Andy McCall 1 month ago ↶
4414
git_in ( & work, & [ " checkout" , " -q" , " -b" , " main" ] ) ;
4415
write ( & work, " README.md" , b " # Project\n " ) ;
4416
write ( & work, " src/lib.rs" , b " pub fn a() {}\n " ) ;
4417
git_in ( & work, & [ " add" , " ." ] ) ;
4418
git_in ( & work, & [ " commit" , " -q" , " -m" , " initial" ] ) ;
4419
write ( & work, " src/lib.rs" , b " pub fn a() {}\n pub fn b() {}\n " ) ;
4420
git_in ( & work, & [ " commit" , " -q" , " -am" , " second" ] ) ;
4421
git_in ( & work, & [ " push" , " -q" , " origin" , " main" ] ) ;
4422
let tip1 = git_in ( & work, & [ " rev-parse" , " HEAD" ] ) ;
4423
4424
4425
let o = root.join ( " oxy" ) ;
4426
let mut repo = Repo:: init( & o) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
4427
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
4428
.unwrap ( ) ;
30ea9c1e Andy McCall 1 month ago ↶
4429
let url = remote.to_string_lossy ( ) .to_string ( ) ;
4430
let r = repo.git_fetch ( & url, " main" ) .unwrap ( ) ;
4431
assert_eq! ( ( r.commits, r.heads) , ( 2 , 1 ) ) ;
75167672 Andy McCall 1 month ago
4432
assert_eq! (
4433
fs:: read( o.join ( " src/lib.rs" ) ) .unwrap ( ) ,
4434
b " pub fn a() {}\n pub fn b() {}\n "
4435
) ;
30ea9c1e Andy McCall 1 month ago ↶
4436
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
75167672 Andy McCall 1 month ago
4437
assert_eq! (
4438
repo.git_fetch ( & url, " main" ) .unwrap ( ) .commits,
4439
0 ,
4440
" nothing new"
4441
) ;
30ea9c1e Andy McCall 1 month ago ↶
4442
4443
75167672 Andy McCall 1 month ago
4444
write (
4445
& o,
4446
" src/lib.rs" ,
4447
b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n " ,
4448
) ;
30ea9c1e Andy McCall 1 month ago ↶
4449
write ( & o, " NOTES.md" , b " from oxy\n " ) ;
4450
let h_oxy = repo.commit ( " add c and notes" , & [ ] ) .unwrap ( ) ;
4451
let r = repo.git_push ( & url, " main" ) .unwrap ( ) ;
4452
assert_eq! ( r.exported, 1 , " {r:?}" ) ;
4453
git_in ( & work, & [ " pull" , " -q" , " --ff-only" , " origin" , " main" ] ) ;
75167672 Andy McCall 1 month ago
4454
assert_eq! (
4455
git_in ( & work, & [ " log" , " --format=%s" , " main" ] ) ,
4456
" add c and notes\n second\n initial"
4457
) ;
30ea9c1e Andy McCall 1 month ago ↶
4458
assert_eq! ( git_in ( & work, & [ " rev-parse" , " HEAD~1" ] ) , tip1) ;
4459
assert_eq! ( fs:: read( work.join ( " NOTES.md" ) ) .unwrap ( ) , b " from oxy\n " ) ;
4460
assert_eq! ( git_in ( & work, & [ " log" , " -1" , " --format=%an" , " main" ] ) , " Ada" ) ;
75167672 Andy McCall 1 month ago
4461
assert! ( git_in ( & work, & [ " log" , " -1" , " --format=%B" , " main" ] )
4462
.contains ( & format! ( " Oxy-Hash: {} " , h_oxy.to_hex ( ) ) ) ) ;
4463
assert_eq! (
4464
repo.git_push ( & url, " main" ) .unwrap ( ) .exported,
4465
0 ,
4466
" already pushed"
4467
) ;
30ea9c1e Andy McCall 1 month ago ↶
4468
4469
4470
4471
write ( & work, " README.md" , b " # Project\n \n More.\n " ) ;
4472
git_in ( & work, & [ " commit" , " -q" , " -am" , " readme from git" ] ) ;
4473
git_in ( & work, & [ " push" , " -q" , " origin" , " main" ] ) ;
4474
let tip2 = git_in ( & work, & [ " rev-parse" , " HEAD" ] ) ;
75167672 Andy McCall 1 month ago
4475
assert! ( repo
4476
.git_push ( & url, " main" )
4477
.unwrap_err ( )
4478
.to_string ( )
4479
.contains ( " fetch" ) ) ;
30ea9c1e Andy McCall 1 month ago ↶
4480
let r = repo.git_fetch ( & url, " main" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4481
assert_eq! (
4482
( r.commits, r.heads) ,
4483
( 1 , 2 ) ,
4484
" the Git change and the oxy change commute"
4485
) ;
4486
assert_eq! (
4487
fs:: read( o.join ( " README.md" ) ) .unwrap ( ) ,
4488
b " # Project\n \n More.\n "
4489
) ;
30ea9c1e Andy McCall 1 month ago ↶
4490
assert_eq! ( fs:: read( o.join ( " NOTES.md" ) ) .unwrap ( ) , b " from oxy\n " ) ;
4491
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4492
4493
write ( & o, " NOTES.md" , b " from oxy\n again\n " ) ;
4494
repo.commit ( " notes again" , & [ ] ) .unwrap ( ) ;
4495
assert_eq! ( repo.git_push ( & url, " main" ) .unwrap ( ) .exported, 1 ) ;
4496
git_in ( & work, & [ " pull" , " -q" , " --ff-only" , " origin" , " main" ] ) ;
4497
assert_eq! ( git_in ( & work, & [ " rev-parse" , " HEAD~1" ] ) , tip2) ;
75167672 Andy McCall 1 month ago
4498
assert_eq! (
4499
fs:: read( work.join ( " NOTES.md" ) ) .unwrap ( ) ,
4500
b " from oxy\n again\n "
4501
) ;
4502
assert_eq! (
4503
git_in ( & work, & [ " log" , " --format=%s" , " main" ] )
4504
.lines ( )
4505
.count ( ) ,
4506
5
4507
) ;
30ea9c1e Andy McCall 1 month ago ↶
4508
}
4509
2b872d07 Andy McCall 1 month ago ↶
4510
4511
4512
# [ test ]
4513
# [ ignore ]
4514
fn bench_one_gibibyte_file ( ) {
4515
hermetic ( ) ;
4516
4517
let dir = match std:: env:: var_os( " OXY_BENCH_DIR" ) {
4518
Some ( d) => tempfile:: tempdir_in( d) .unwrap ( ) ,
4519
None => tempfile:: tempdir( ) .unwrap ( ) ,
4520
} ;
4521
let root = dir.path ( ) ;
75167672 Andy McCall 1 month ago
4522
let repo = Repo:: init( & root.join ( " r" ) )
4523
.unwrap ( )
4524
.with_home ( root.join ( " home" ) ) ;
2b872d07 Andy McCall 1 month ago ↶
4525
let size = 1 usize < < 30 ;
4526
let mut data = vec! [ 0 u8 ; size] ;
4527
let mut x = 0x9e3779b97f4a7c15 u64 ;
4528
for chunk in data.chunks_mut ( 8 ) {
4529
x ^ = x < < 13 ;
4530
x ^ = x > > 7 ;
4531
x ^ = x < < 17 ;
4532
let b = x.to_le_bytes ( ) ;
4533
chunk.copy_from_slice ( & b[ .. chunk.len ( ) ] ) ;
4534
}
4535
let t = std:: time:: Instant:: now( ) ;
4536
write ( & root.join ( " r" ) , " big.bin" , & data) ;
4537
eprintln! ( " write 1 GiB to disk: {:.2?}" , t.elapsed ( ) ) ;
4538
let t = std:: time:: Instant:: now( ) ;
4539
let h = repo.commit ( " big" , & [ ] ) .unwrap ( ) ;
4540
eprintln! ( " commit (chunk + hash + store): {:.2?}" , t.elapsed ( ) ) ;
4541
let t = std:: time:: Instant:: now( ) ;
4542
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4543
eprintln! ( " status on a clean 1 GiB working copy: {:.2?}" , t.elapsed ( ) ) ;
4544
let t = std:: time:: Instant:: now( ) ;
4545
let objects = repo.store ( ) .list ( ) .unwrap ( ) .len ( ) ;
4546
eprintln! ( " objects stored: {objects} ({:.2?} to list)" , t.elapsed ( ) ) ;
4547
let t = std:: time:: Instant:: now( ) ;
4548
let g = repo.gc ( true ) .unwrap ( ) ;
4549
eprintln! ( " gc (pack): {:.2?} {g:?}" , t.elapsed ( ) ) ;
4550
let t = std:: time:: Instant:: now( ) ;
4551
let clone = Repo:: clone( & root.join ( " r" ) .to_string_lossy ( ) , & root.join ( " c" ) ) .unwrap ( ) ;
4552
eprintln! ( " clone (fetch + checkout): {:.2?}" , t.elapsed ( ) ) ;
4553
let t = std:: time:: Instant:: now( ) ;
4554
let back = fs:: read( root.join ( " c/big.bin" ) ) .unwrap ( ) ;
4555
assert_eq! ( blake3:: hash( & back) , blake3:: hash( & data) ) ;
4556
eprintln! ( " read back and verify: {:.2?}" , t.elapsed ( ) ) ;
4557
assert_eq! ( clone.bookmark_heads ( ) .unwrap ( ) , vec! [ h] ) ;
4558
let store_bytes: u64 = walkdir_size ( & root.join ( " r/.oxy/objects" ) ) ;
75167672 Andy McCall 1 month ago
4559
eprintln! (
4560
" store size: {:.1} MiB for 1 GiB of data" ,
4561
store_bytes as f64 / ( 1024. 0 * 1024. 0 )
4562
) ;
2b872d07 Andy McCall 1 month ago ↶
4563
}
4564
4565
fn walkdir_size ( p : & Path) -> u64 {
4566
let mut total = 0 ;
4567
if let Ok ( rd) = fs:: read_dir( p) {
4568
for e in rd.flatten ( ) {
4569
let path = e.path ( ) ;
4570
if path.is_dir ( ) {
4571
total + = walkdir_size ( & path) ;
4572
} else if let Ok ( m) = fs:: metadata( & path) {
4573
total + = m.len ( ) ;
4574
}
4575
}
4576
}
4577
total
4578
}
4579
b7f102fc Andy McCall 1 month ago ↶
4580
4581
4582
# [ cfg ( unix ) ]
4583
# [ test ]
4584
fn stat_cache_skips_reading_unchanged_opaque_files ( ) {
4585
use std:: os:: unix:: fs:: PermissionsExt;
4586
hermetic ( ) ;
4587
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4588
let root = dir.path ( ) ;
4589
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4590
let mut big = vec! [ 0 u8 ; 300 * 1024 ] ;
4591
for ( i, b) in big.iter_mut ( ) .enumerate ( ) {
4592
* b = ( i as u32 ) .wrapping_mul ( 2654435761 ) .to_le_bytes ( ) [ 1 ] ;
4593
}
4594
big[ 0 ] = 0 ;
4595
write ( root, " big.bin" , & big) ;
4596
write ( root, " a.txt" , b " text\n " ) ;
4597
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4598
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4599
assert! (
4600
m.entries[ " big.bin" ] .blob.is_some ( ) ,
4601
" opaque files record their blob"
4602
) ;
b7f102fc Andy McCall 1 month ago ↶
4603
assert! ( m.entries[ " a.txt" ] .blob.is_none ( ) ) ;
4604
let blob = m.entries[ " big.bin" ] .blob.unwrap ( ) ;
4605
assert! ( repo.store ( ) .has ( & blob) ) ;
4606
4607
4608
4609
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
75167672 Andy McCall 1 month ago
4610
fs:: File:: options( )
4611
.write ( true )
4612
.open ( root.join ( " big.bin" ) )
4613
.unwrap ( )
4614
.set_modified ( old)
4615
.unwrap ( ) ;
b7f102fc Andy McCall 1 month ago ↶
4616
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4617
4618
fs:: set_permissions( root.join ( " big.bin" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
4619
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4620
assert! (
4621
! snap.changed & & snap.statuses.is_empty ( ) ,
4622
" {:?}" ,
4623
snap.statuses
4624
) ;
b7f102fc Andy McCall 1 month ago ↶
4625
fs:: set_permissions( root.join ( " big.bin" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
4626
4627
let mut big2 = big.clone ( ) ;
4628
big2[ 10 ] ^ = 0xff ;
4629
write ( root, " big.bin" , & big2) ;
4630
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4631
assert_eq! (
4632
snap.statuses
4633
.iter ( )
4634
.map ( | s | ( s.path.as_str ( ) , s.status) )
4635
.collect:: < Vec< _ > > ( ) ,
4636
vec! [ ( " big.bin" , ChangeStatus:: Modified) ]
4637
) ;
b7f102fc Andy McCall 1 month ago ↶
4638
let h2 = repo.commit ( " edit" , & [ ] ) .unwrap ( ) ;
4639
let files = repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
4640
assert_eq! (
4641
files[ " big.bin" ] .content,
4642
FileContent:: Opaque {
4643
blobs: vec! [ Repo:: blob_ref( & big2) ]
4644
}
4645
) ;
b7f102fc Andy McCall 1 month ago ↶
4646
4647
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
4648
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
4649
assert_eq! ( m.entries[ " big.bin" ] .blob, Some ( Repo:: blob_ref( & big) ) ) ;
4650
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4651
}
4652
50a046e4 Andy McCall 1 month ago ↶
4653
# [ test ]
4654
fn status_json_lists_conflicts_with_contributors ( ) {
4655
hermetic ( ) ;
4656
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4657
let root = dir.path ( ) ;
4658
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4659
write ( root, " a.txt" , b " one\n " ) ;
4660
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4661
write ( root, " a.txt" , b " one\n left\n " ) ;
4662
let ha = repo.commit ( " left" , & [ ] ) .unwrap ( ) ;
4663
repo.checkout ( & [ h1] , true , " checkout" ) .unwrap ( ) ;
4664
write ( root, " a.txt" , b " one\n right\n " ) ;
4665
let hb = repo.commit ( " right" , & [ ] ) .unwrap ( ) ;
4666
repo.checkout ( & [ ha, hb] , true , " checkout" ) .unwrap ( ) ;
4667
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4668
assert! ( snap.base_snapshot.is_conflicted ( ) ) ;
4669
let json = repo.status_json ( & snap) .unwrap ( ) ;
4670
let ida = repo.store ( ) .get_change ( & ha) .unwrap ( ) .change_id.to_hex ( ) ;
4671
let idb = repo.store ( ) .get_change ( & hb) .unwrap ( ) .change_id.to_hex ( ) ;
4672
assert! ( json.contains ( " \" conflicted\" :true" ) , " {json}" ) ;
75167672 Andy McCall 1 month ago
4673
assert! (
4674
json.contains ( " \" kind\" :\" text\" ,\" path\" :\" a.txt\" " ) ,
4675
" {json}"
4676
) ;
50a046e4 Andy McCall 1 month ago ↶
4677
assert! ( json.contains ( & ida) & & json.contains ( & idb) , " {json}" ) ;
4678
assert! ( json.contains ( " \" resolved\" :false" ) , " {json}" ) ;
4679
assert! ( json.starts_with ( ' {' ) & & json.ends_with ( ' }' ) ) ;
4680
4681
write ( root, " a.txt" , b " one\n left\n right\n " ) ;
4682
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4683
let json = repo.status_json ( & snap) .unwrap ( ) ;
4684
assert! ( json.contains ( " \" resolved\" :true" ) , " {json}" ) ;
4685
repo.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
4686
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4687
let json = repo.status_json ( & snap) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4688
assert! (
4689
json.contains ( " \" clean\" :true,\" conflicted\" :false,\" paths\" :[],\" conflicts\" :[]" ) ,
4690
" {json}"
4691
) ;
50a046e4 Andy McCall 1 month ago ↶
4692
}
4693
9ac1fbe2 Andy McCall 1 month ago ↶
4694
# [ test ]
4695
fn ignore_rules_do_not_untrack_files ( ) {
4696
hermetic ( ) ;
4697
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4698
let root = dir.path ( ) ;
4699
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4700
write ( root, " src.c" , b " int x;\n " ) ;
4701
write ( root, " build/out.o" , b " \0 obj" ) ;
4702
write ( root, " sub/keep.log" , b " keep\n " ) ;
4703
write ( root, " sub/junk.log" , b " junk\n " ) ;
4704
write ( root, " .oxyignore" , b " /build\n *.log\n !sub/keep.log\n " ) ;
4705
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
4706
let added: Vec< & str > = snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect ( ) ;
75167672 Andy McCall 1 month ago
4707
assert_eq! (
4708
added,
4709
vec! [ " .oxyignore" , " src.c" , " sub/keep.log" ] ,
4710
" anchored, glob, and negated rules"
4711
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4712
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
4713
4714
write ( root, " .oxyignore" , b " /build\n *.log\n !sub/keep.log\n src.c\n " ) ;
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 ( ) )
4720
.collect:: < Vec< _ > > ( ) ,
4721
vec! [ " .oxyignore" ]
4722
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4723
let h2 = repo.commit ( " ignore src.c" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4724
assert! ( repo
4725
.load_state ( & [ h2] )
4726
.unwrap ( )
4727
.materialize ( )
4728
.files
4729
.contains_key ( " src.c" ) ) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4730
4731
assert_eq! ( repo.quick_dirty ( ) .unwrap ( ) , Some ( false ) ) ;
4732
write ( root, " src.c" , b " int x, y;\n " ) ;
4733
assert_eq! ( repo.quick_dirty ( ) .unwrap ( ) , Some ( true ) ) ;
4734
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4735
assert_eq! (
4736
snap.statuses
4737
.iter ( )
4738
.map ( | s | ( s.path.as_str ( ) , s.status) )
4739
.collect:: < Vec< _ > > ( ) ,
4740
vec! [ ( " src.c" , ChangeStatus:: Modified) ]
4741
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4742
let h3 = repo.commit ( " edit src.c" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4743
assert! (
4744
matches! ( & repo.load_state ( & [ h3] ) .unwrap ( ) .materialize ( ) .files[ " src.c" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " int x, y;\n " )
4745
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4746
4747
fs:: remove_file( root.join ( " src.c" ) ) .unwrap ( ) ;
4748
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4749
assert_eq! (
4750
snap.statuses
4751
.iter ( )
4752
.map ( | s | ( s.path.as_str ( ) , s.status) )
4753
.collect:: < Vec< _ > > ( ) ,
4754
vec! [ ( " src.c" , ChangeStatus:: Removed) ]
4755
) ;
9ac1fbe2 Andy McCall 1 month ago ↶
4756
repo.checkout ( & [ h3] , true , " checkout" ) .unwrap ( ) ;
4757
assert_eq! ( fs:: read( root.join ( " src.c" ) ) .unwrap ( ) , b " int x, y;\n " ) ;
4758
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4759
let _ = h1;
4760
}
4761
b837d2b5 Andy McCall 1 month ago ↶
4762
# [ test ]
4763
fn split_works_on_non_heads_by_node ( ) {
4764
hermetic ( ) ;
4765
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4766
let root = dir.path ( ) ;
4767
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4768
write ( root, " a.txt" , b " one\n " ) ;
4769
write ( root, " junk/x.lock" , b " lock\n " ) ;
4770
let h1 = repo.commit ( " work plus junk" , & [ ] ) .unwrap ( ) ;
4771
write ( root, " a.txt" , b " one\n two\n " ) ;
4772
let h2 = repo.commit ( " more work" , & [ ] ) .unwrap ( ) ;
4773
assert_eq! ( repo.store ( ) .get_change ( & h2) .unwrap ( ) .deps, vec! [ h1] ) ;
4774
let id1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) .change_id;
4775
4776
let ( hj, h1b) = repo.split ( & h1, & [ " junk" .into ( ) ] ) .unwrap ( ) ;
4777
assert_ne! ( h1b, h1) ;
75167672 Andy McCall 1 month ago
4778
assert_eq! (
4779
repo.store ( ) .get_change ( & h1b) .unwrap ( ) .change_id,
4780
id1,
4781
" the rest keeps the ChangeId"
4782
) ;
b837d2b5 Andy McCall 1 month ago ↶
4783
let junk = repo.store ( ) .get_change ( & hj) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4784
assert! ( junk.tree_ops.iter ( ) .all (
4785
| op | matches! ( op, TreeOp:: AddNode { name, .. } if name = = " junk" | | name = = " x.lock" )
4786
) ) ;
b837d2b5 Andy McCall 1 month ago ↶
4787
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4788
assert! (
4789
heads.contains ( & hj) ,
4790
" the split-off part is a head of its own"
4791
) ;
b837d2b5 Andy McCall 1 month ago ↶
4792
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
75167672 Andy McCall 1 month ago
4793
assert! (
4794
matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n " ) ,
4795
" the dependent was rewritten onto the new hash"
4796
) ;
b837d2b5 Andy McCall 1 month ago ↶
4797
assert! ( snap.files.contains_key ( " junk/x.lock" ) ) ;
75167672 Andy McCall 1 month ago
4798
let h2b = heads
4799
.iter ( )
4800
.find ( | h | repo.store ( ) .get_change ( h) .unwrap ( ) .message = = " more work" )
4801
.copied ( )
4802
.unwrap ( ) ;
b837d2b5 Andy McCall 1 month ago ↶
4803
assert_eq! ( repo.store ( ) .get_change ( & h2b) .unwrap ( ) .deps, vec! [ h1b] ) ;
4804
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
4805
4806
repo.abandon ( & hj) .unwrap ( ) ;
4807
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
4808
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
4809
assert! ( ! snap.files.contains_key ( " junk/x.lock" ) & & ! snap.files.contains_key ( " junk" ) ) ;
4810
assert! ( snap.files.contains_key ( " a.txt" ) ) ;
4811
4812
write ( root, " junk/y.lock" , b " y\n " ) ;
4813
let hb = repo.commit ( " junk base" , & [ ] ) .unwrap ( ) ;
4814
write ( root, " junk/y.lock" , b " yy\n " ) ;
4815
write ( root, " b.txt" , b " b\n " ) ;
4816
repo.commit ( " touches junk" , & [ ] ) .unwrap ( ) ;
4817
let err = repo.split ( & hb, & [ " junk" .into ( ) ] ) .unwrap_err ( ) .to_string ( ) ;
4818
assert! ( err.contains ( " depends on the split-off paths" ) , " {err}" ) ;
4819
}
4820
7b9809e6 Andy McCall 1 month ago ↶
4821
# [ test ]
4822
fn bookmarks_switch_merge_and_push ( ) {
4823
hermetic ( ) ;
4824
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4825
let root = dir.path ( ) ;
4826
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
4827
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
4828
let mut repo = Repo:: clone( & url, & root.join ( " r" ) )
4829
.unwrap ( )
4830
.with_home ( root.join ( " home" ) ) ;
7b9809e6 Andy McCall 1 month ago ↶
4831
let r = root.join ( " r" ) ;
4832
write ( & r, " a.txt" , b " a\n " ) ;
4833
let h_a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
4834
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
4835
origin.refresh ( ) .unwrap ( ) ;
7b9809e6 Andy McCall 1 month ago ↶
4836
4837
4838
assert! ( repo.bookmark_create ( " bad name.." , None ) .is_err ( ) ) ;
4839
assert! ( repo.bookmark_create ( " -x" , None ) .is_err ( ) ) ;
4840
assert_eq! ( repo.bookmark_create ( " feature" , None ) .unwrap ( ) , vec! [ h_a] ) ;
4841
assert! ( repo.bookmark_create ( " feature" , None ) .is_err ( ) , " exists" ) ;
4842
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
4843
assert_eq! ( repo.config.bookmark, " feature" ) ;
4844
write ( & r, " a.txt" , b " a\n b\n " ) ;
4845
write ( & r, " b.txt" , b " b\n " ) ;
4846
let h_b = repo.commit ( " b on feature" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4847
assert_eq! (
4848
repo.bookmark_heads ( ) .unwrap ( ) ,
4849
vec! [ h_b] ,
4850
" b edits a.txt, so it depends on a"
4851
) ;
4852
assert_eq! (
4853
repo.resolve_heads ( " main" ) .unwrap ( ) ,
4854
vec! [ h_a] ,
4855
" main is untouched"
4856
) ;
4857
assert_eq! (
4858
repo.resolve_change ( " feature" ) .unwrap ( ) ,
4859
h_b,
4860
" a one-head bookmark names its change"
4861
) ;
7b9809e6 Andy McCall 1 month ago ↶
4862
4863
let reopened = Repo:: open( & r) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
4864
assert_eq! ( reopened.config.bookmark, " feature" ) ;
4865
4866
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
4867
assert! ( ! r.join ( " b.txt" ) .exists ( ) ) ;
4868
assert_eq! ( fs:: read( r.join ( " a.txt" ) ) .unwrap ( ) , b " a\n " ) ;
4869
write ( & r, " c.txt" , b " c\n " ) ;
4870
let h_c = repo.commit ( " c on main" , & [ ] ) .unwrap ( ) ;
4871
let list = repo.bookmarks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4872
assert_eq! (
4873
list.iter ( )
4874
.map ( | b | ( b.name.as_str ( ) , b.current) )
4875
.collect:: < Vec< _ > > ( ) ,
4876
vec! [ ( " feature" , false ) , ( " main" , true ) ]
4877
) ;
4878
assert_eq! (
4879
list.iter ( ) .find ( | b | b.name = = " main" ) .unwrap ( ) .ahead,
4880
Some ( 1 ) ,
4881
" c is not on origin yet"
4882
) ;
7b9809e6 Andy McCall 1 month ago ↶
4883
4884
let m = repo.merge ( " feature" ) .unwrap ( ) ;
4885
assert! ( m.changed & & m.checked_out) ;
4886
let mut want = vec! [ h_b, h_c] ;
4887
want.sort ( ) ;
4888
assert_eq! ( m.heads, want) ;
4889
assert_eq! ( fs:: read( r.join ( " b.txt" ) ) .unwrap ( ) , b " b\n " ) ;
4890
assert! ( ! repo.merge ( " feature" ) .unwrap ( ) .changed, " already merged" ) ;
4891
assert! ( repo.merge ( " main" ) .is_err ( ) ) ;
4892
4893
assert! ( repo.push_bookmark ( " origin" , " feature" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
4894
origin.refresh ( ) .unwrap ( ) ;
7b9809e6 Andy McCall 1 month ago ↶
4895
assert_eq! ( origin.resolve_heads ( " feature" ) .unwrap ( ) , vec! [ h_b] ) ;
4896
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
4897
origin.refresh ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4898
assert_eq! (
4899
repo.bookmarks ( )
4900
.unwrap ( )
4901
.iter ( )
4902
.find ( | b | b.name = = " main" )
4903
.unwrap ( )
4904
.ahead,
4905
Some ( 0 )
4906
) ;
7b9809e6 Andy McCall 1 month ago ↶
4907
4908
let ws = repo.workspace_add ( & root.join ( " ws" ) , None ) .unwrap ( ) ;
4909
assert_eq! ( ws.config.bookmark, " main" ) ;
4910
let mut ws = ws;
4911
ws.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4912
assert_eq! (
4913
Repo:: open( & r) .unwrap ( ) .config.bookmark,
4914
" main" ,
4915
" the first workspace is unaffected"
4916
) ;
4917
assert! (
4918
repo.bookmark_delete ( " feature" ) .is_err ( ) ,
4919
" current bookmark of another workspace"
4920
) ;
7b9809e6 Andy McCall 1 month ago ↶
4921
ws.bookmark_switch ( " main" , false ) .unwrap ( ) ;
4922
4923
assert_eq! ( repo.bookmark_set ( " feature" , & [ h_a] ) .unwrap ( ) , vec! [ h_a] ) ;
4924
assert! ( repo.bookmark_delete ( " main" ) .is_err ( ) , " current here" ) ;
4925
repo.bookmark_delete ( " feature" ) .unwrap ( ) ;
4926
assert! ( repo.resolve_heads ( " feature" ) .is_err ( ) ) ;
4927
assert! ( repo.bookmark_set ( " feature" , & [ h_a] ) .is_err ( ) ) ;
75167672 Andy McCall 1 month ago
4928
assert! (
4929
repo.checkout ( & [ h_a] , false , " checkout" ) .is_ok ( ) ,
4930
" changes of a deleted bookmark remain"
4931
) ;
7b9809e6 Andy McCall 1 month ago ↶
4932
}
4933
4dee277c Andy McCall 1 month ago ↶
4934
# [ test ]
4935
fn oxygen_subrepos_pin_follow_and_update ( ) {
4936
hermetic ( ) ;
4937
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
4938
let root = dir.path ( ) ;
4939
4940
let lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
4941
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
4942
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
4943
.unwrap ( )
4944
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4945
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
4946
let l1 = lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
4947
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
4948
let _ = lib_origin;
4949
4950
4951
let app_origin = Repo:: init( & root.join ( " app-origin" ) ) .unwrap ( ) ;
4952
let app_url = root.join ( " app-origin" ) .to_string_lossy ( ) .to_string ( ) ;
4953
let _ = app_origin;
4954
let app_dir = root.join ( " app" ) ;
75167672 Andy McCall 1 month ago
4955
let app = Repo:: clone( & app_url, & app_dir)
4956
.unwrap ( )
4957
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4958
write ( & app_dir, " main.txt" , b " app\n " ) ;
4959
let pin = app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
4960
assert! ( matches! ( & pin, crate :: subrepo:: Pin:: Oxy { heads, .. } if heads = = & vec! [ l1] ) ) ;
4961
assert! ( app_dir.join ( " .oxymodules" ) .exists ( ) & & app_dir.join ( " libs/lib/.oxy" ) .is_dir ( ) ) ;
4962
let snap = app.snapshot ( " status" ) .unwrap ( ) ;
4963
let paths: Vec< & str > = snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect ( ) ;
75167672 Andy McCall 1 month ago
4964
assert_eq! (
4965
paths,
4966
vec! [ " .oxymodules" , " libs/lib" , " main.txt" ] ,
4967
" the nested files are not walked"
4968
) ;
4dee277c Andy McCall 1 month ago ↶
4969
let a1 = app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
4970
let files = app.load_state ( & [ a1] ) .unwrap ( ) .materialize ( ) .files;
4971
assert_eq! ( files[ " libs/lib" ] .kind, NodeKind:: Subrepo) ;
4972
assert! ( ! files.contains_key ( " libs/lib/lib.txt" ) ) ;
4973
assert! ( ! app.snapshot ( " status" ) .unwrap ( ) .changed) ;
4974
let subs = app.subrepos ( ) .unwrap ( ) ;
4975
assert_eq! ( subs.len ( ) , 1 ) ;
4976
assert_eq! ( subs[ 0 ] .actual.as_ref ( ) , Some ( & subs[ 0 ] .pin) ) ;
4977
4978
75167672 Andy McCall 1 month ago
4979
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) )
4980
.unwrap ( )
4981
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
4982
write ( & app_dir.join ( " libs/lib" ) , " lib.txt" , b " v2\n " ) ;
4983
let l2 = nested.commit ( " lib v2" , & [ ] ) .unwrap ( ) ;
4984
assert! ( nested.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
4985
let snap = app.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
4986
assert_eq! (
4987
snap.statuses
4988
.iter ( )
4989
.map ( | s | ( s.path.as_str ( ) , s.status) )
4990
.collect:: < Vec< _ > > ( ) ,
4991
vec! [ ( " libs/lib" , ChangeStatus:: Modified) ]
4992
) ;
4dee277c Andy McCall 1 month ago ↶
4993
let a2 = app.commit ( " bump lib" , & [ ] ) .unwrap ( ) ;
1a3b6203 Andy McCall 1 month ago ↶
4994
4995
4dee277c Andy McCall 1 month ago ↶
4996
let r = app.checkout ( & [ a1] , false , " checkout" ) .unwrap ( ) ;
1a3b6203 Andy McCall 1 month ago ↶
4997
assert! (
4998
r.notes.len ( ) = = 1 & & r.notes[ 0 ] .contains ( " keeps 1 newer change" ) ,
4999
" {:?}" ,
5000
r.notes
5001
) ;
4dee277c Andy McCall 1 month ago ↶
5002
assert_eq! ( fs:: read( app_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v1\n " ) ;
1a3b6203 Andy McCall 1 month ago ↶
5003
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) ) .unwrap ( ) ;
5004
assert_eq! ( nested.bookmark_heads ( ) .unwrap ( ) , vec! [ l2] ) ;
5005
assert_eq! ( nested.working_copy ( ) .unwrap ( ) .1. deps, vec! [ l1] ) ;
4dee277c Andy McCall 1 month ago ↶
5006
assert! ( ! app.snapshot ( " status" ) .unwrap ( ) .changed) ;
5007
5008
write ( & app_dir.join ( " libs/lib" ) , " scratch.txt" , b " wip\n " ) ;
5009
let r = app.checkout ( & [ a2] , false , " checkout" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5010
assert! (
5011
r.notes.iter ( ) .any ( | n | n.contains ( " uncommitted" ) ) ,
5012
" {:?}" ,
5013
r.notes
5014
) ;
4dee277c Andy McCall 1 month ago ↶
5015
assert_eq! ( fs:: read( app_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v1\n " ) ;
5016
fs:: remove_file( app_dir.join ( " libs/lib/scratch.txt" ) ) .unwrap ( ) ;
5017
let r = app.submodule_update ( false , & [ ] ) .unwrap ( ) ;
5018
assert_eq! ( r, vec! [ ( " libs/lib" .to_string ( ) , SubrepoSync:: Updated) ] ) ;
5019
assert_eq! ( fs:: read( app_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v2\n " ) ;
5020
let _ = l2;
5021
5022
5023
assert! ( app.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
5024
let clone_dir = root.join ( " app-clone" ) ;
75167672 Andy McCall 1 month ago
5025
let clone = Repo:: clone( & app_url, & clone_dir)
5026
.unwrap ( )
5027
.with_home ( root.join ( " home" ) ) ;
4dee277c Andy McCall 1 month ago ↶
5028
assert! ( ! clone_dir.join ( " libs/lib/.oxy" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
5029
assert_eq! (
5030
clone.submodule_update ( false , & [ ] ) .unwrap ( ) ,
5031
vec! [ ( " libs/lib" .to_string ( ) , SubrepoSync:: Missing) ]
5032
) ;
5033
assert_eq! (
5034
clone.submodule_update ( true , & [ ] ) .unwrap ( ) ,
5035
vec! [ ( " libs/lib" .to_string ( ) , SubrepoSync:: Updated) ]
5036
) ;
5037
assert_eq! (
5038
fs:: read( clone_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) ,
5039
b " v2\n "
5040
) ;
4dee277c Andy McCall 1 month ago ↶
5041
assert! ( ! clone.snapshot ( " status" ) .unwrap ( ) .changed) ;
5042
5043
clone.submodule_remove ( " libs/lib" ) .unwrap ( ) ;
5044
let snap = clone.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5045
assert! (
5046
snap.statuses
5047
.iter ( )
5048
.any ( | s | s.path = = " libs/lib" & & s.status = = ChangeStatus:: Removed ) ,
5049
" {:?}" ,
5050
snap.statuses
5051
) ;
4dee277c Andy McCall 1 month ago ↶
5052
let c = clone.commit ( " drop lib" , & [ ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5053
assert! ( ! clone
5054
.load_state ( & [ c] )
5055
.unwrap ( )
5056
.materialize ( )
5057
.files
5058
.contains_key ( " libs/lib" ) ) ;
4dee277c Andy McCall 1 month ago ↶
5059
assert! ( clone_dir.join ( " libs/lib/lib.txt" ) .exists ( ) ) ;
75167672 Andy McCall 1 month ago
5060
assert! (
5061
! clone.snapshot ( " status" ) .unwrap ( ) .changed,
5062
" an unregistered nested repository is ignored"
5063
) ;
64dc5197 Andy McCall 8 days ago ↶
5064
5065
5066
5067
5068
let modules = format! ( " [libs/lib]\n url = {lib_url} \n " ) ;
5069
let pinned = | c : ChangeHash| {
5070
clone
5071
.load_state ( & [ c] )
5072
.unwrap ( )
5073
.materialize ( )
5074
.files
5075
.get ( " libs/lib" )
5076
.map ( | n | n.kind )
5077
} ;
5078
write ( & clone_dir, " .oxymodules" , modules.as_bytes ( ) ) ;
5079
let c = clone.commit ( " lib again" , & [ ] ) .unwrap ( ) ;
5080
assert_eq! ( pinned ( c) , Some ( NodeKind:: Subrepo) ) ;
5081
clone.submodule_remove ( " libs/lib" ) .unwrap ( ) ;
5082
let c = clone.commit ( " drop lib again" , & [ ] ) .unwrap ( ) ;
5083
assert_eq! ( pinned ( c) , None ) ;
5084
write ( & clone_dir, " .oxymodules" , modules.as_bytes ( ) ) ;
5085
let c = clone.commit ( " lib back" , & [ ] ) .unwrap ( ) ;
5086
assert_eq! (
5087
pinned ( c) ,
5088
Some ( NodeKind:: Subrepo) ,
5089
" pinned in the change that registers it"
5090
) ;
5091
assert! ( ! clone.snapshot ( " status" ) .unwrap ( ) .changed) ;
4dee277c Andy McCall 1 month ago ↶
5092
}
5093
5094
# [ test ]
5095
fn git_submodules_import_and_export_as_gitlinks ( ) {
5096
hermetic ( ) ;
75167672 Andy McCall 1 month ago
5097
if std:: process:: Command:: new( " git" )
5098
.arg ( " --version" )
5099
.output ( )
5100
.is_err ( )
5101
{
4dee277c Andy McCall 1 month ago ↶
5102
eprintln! ( " git not installed; skipping" ) ;
5103
return ;
5104
}
5105
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5106
let root = dir.path ( ) ;
5107
let git_in = | d : & Path, args : & [& str ]| -> String {
5108
let out = std:: process:: Command:: new( " git" )
5109
.arg ( " -C" )
5110
.arg ( d)
75167672 Andy McCall 1 month ago
5111
.args ( [
5112
" -c" ,
5113
" user.name=Grace" ,
5114
" -c" ,
5115
" [email protected] " ,
5116
" -c" ,
5117
" commit.gpgsign=false" ,
5118
" -c" ,
5119
" protocol.file.allow=always" ,
5120
] )
4dee277c Andy McCall 1 month ago ↶
5121
.args ( args)
5122
.output ( )
5123
.unwrap ( ) ;
75167672 Andy McCall 1 month ago
5124
assert! (
5125
out.status.success ( ) ,
5126
" git {args:?}: {}" ,
5127
String :: from_utf8_lossy( & out.stderr)
5128
) ;
4dee277c Andy McCall 1 month ago ↶
5129
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
5130
} ;
5131
let sub = root.join ( " sub" ) ;
5132
fs:: create_dir_all( & sub) .unwrap ( ) ;
5133
git_in ( & sub, & [ " init" , " -q" , " -b" , " main" ] ) ;
5134
write ( & sub, " s.txt" , b " sub\n " ) ;
5135
git_in ( & sub, & [ " add" , " ." ] ) ;
5136
git_in ( & sub, & [ " commit" , " -q" , " -m" , " sub initial" ] ) ;
5137
let sub_sha = git_in ( & sub, & [ " rev-parse" , " HEAD" ] ) ;
5138
let g = root.join ( " main.git" ) ;
5139
fs:: create_dir_all( & g) .unwrap ( ) ;
5140
git_in ( & g, & [ " init" , " -q" , " -b" , " main" ] ) ;
5141
write ( & g, " README.md" , b " # app\n " ) ;
5142
git_in ( & g, & [ " add" , " ." ] ) ;
5143
git_in ( & g, & [ " commit" , " -q" , " -m" , " initial" ] ) ;
75167672 Andy McCall 1 month ago
5144
git_in (
5145
& g,
5146
& [
5147
" submodule" ,
5148
" add" ,
5149
" -q" ,
5150
sub.to_str ( ) .unwrap ( ) ,
5151
" vendor/sub" ,
5152
] ,
5153
) ;
4dee277c Andy McCall 1 month ago ↶
5154
git_in ( & g, & [ " commit" , " -q" , " -m" , " add submodule" ] ) ;
5155
5156
let dest = root.join ( " oxy" ) ;
5157
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5158
let rep = repo.import_git ( & g, " main" ) .unwrap ( ) ;
5159
assert_eq! ( ( rep.commits, rep.submodules) , ( 2 , 1 ) ) ;
75167672 Andy McCall 1 month ago
5160
let files = repo
5161
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
5162
.unwrap ( )
5163
.materialize ( )
5164
.files;
4dee277c Andy McCall 1 month ago ↶
5165
assert_eq! ( files[ " vendor/sub" ] .kind, NodeKind:: Subrepo) ;
75167672 Andy McCall 1 month ago
5166
let FileContent:: Opaque { blobs } = & files[ " vendor/sub" ] .content else {
5167
panic! ( )
5168
} ;
5169
assert_eq! (
5170
crate :: subrepo:: Pin:: decode( & repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) ) .unwrap ( ) ,
5171
crate :: subrepo:: Pin:: Git {
5172
sha: sub_sha.clone ( ) ,
5173
branch: " HEAD" .into ( )
5174
}
5175
) ;
5176
let modules =
5177
crate :: subrepo:: Modules:: parse( & fs:: read_to_string( dest.join ( " .oxymodules" ) ) .unwrap ( ) ) ;
4dee277c Andy McCall 1 month ago ↶
5178
assert_eq! ( modules.entries[ " vendor/sub" ] .url, sub.to_string_lossy ( ) ) ;
5179
assert! ( modules.entries[ " vendor/sub" ] .git) ;
75167672 Andy McCall 1 month ago
5180
assert! (
5181
files.contains_key ( " .gitmodules" ) ,
5182
" the Git file is kept for round trips"
5183
) ;
5184
assert! (
5185
! dest.join ( " vendor/sub/.git" ) .exists ( ) ,
5186
" not cloned until update --init"
5187
) ;
4dee277c Andy McCall 1 month ago ↶
5188
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
5189
75167672 Andy McCall 1 month ago
5190
assert_eq! (
5191
repo.submodule_update ( true , & [ ] ) .unwrap ( ) ,
5192
vec! [ ( " vendor/sub" .to_string ( ) , SubrepoSync:: Updated) ]
5193
) ;
5194
assert_eq! (
5195
git_in ( & dest.join ( " vendor/sub" ) , & [ " rev-parse" , " HEAD" ] ) ,
5196
sub_sha
5197
) ;
4dee277c Andy McCall 1 month ago ↶
5198
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
5199
5200
let mirror = root.join ( " mirror.git" ) ;
5201
let rep = repo.git_export ( & mirror) .unwrap ( ) ;
5202
assert_eq! ( rep.skipped_subrepos, 0 ) ;
5203
let ls = git_in ( & mirror, & [ " ls-tree" , " main" , " vendor/sub" ] ) ;
75167672 Andy McCall 1 month ago
5204
assert! (
5205
ls.starts_with ( " 160000 commit " ) & & ls.contains ( & sub_sha) ,
5206
" {ls}"
5207
) ;
5208
assert_eq! (
5209
git_in ( & mirror, & [ " ls-tree" , " --name-only" , " main" ] ) ,
5210
" .gitmodules\n README.md\n vendor"
5211
) ;
4dee277c Andy McCall 1 month ago ↶
5212
let orig_tree = git_in ( & g, & [ " rev-parse" , " main^{tree}" ] ) ;
75167672 Andy McCall 1 month ago
5213
assert_eq! (
5214
git_in ( & mirror, & [ " rev-parse" , " main^{tree}" ] ) ,
5215
orig_tree,
5216
" tree round trip"
5217
) ;
4dee277c Andy McCall 1 month ago ↶
5218
}
5219
957fee30 Andy McCall 1 month ago ↶
5220
# [ test ]
5221
fn partial_commits_by_path_and_by_hunk ( ) {
5222
hermetic ( ) ;
5223
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5224
let root = dir.path ( ) ;
5225
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5226
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
5227
write ( root, " b.txt" , b " b\n " ) ;
5228
let h0 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
5229
write ( root, " a.txt" , b " 0\n 1\n 2\n 3\n 4\n " ) ;
5230
write ( root, " b.txt" , b " b\n bb\n " ) ;
5231
write ( root, " c.txt" , b " c\n " ) ;
5232
75167672 Andy McCall 1 month ago
5233
let h1 = repo
5234
.commit_selection ( " a only" , & [ ] , & Selection:: Paths( vec! [ " a.txt" .into ( ) ] ) )
5235
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5236
let c1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5237
assert! (
5238
c1.tree_ops.is_empty ( ) & & c1.edits.iter ( ) .all ( | e | e.node ( ) = = c1.edits[ 0 ] .node ( ) ) ,
5239
" {:?}" ,
5240
c1.tree_ops
5241
) ;
957fee30 Andy McCall 1 month ago ↶
5242
assert_eq! ( c1.deps, vec! [ h0] ) ;
5243
let files = repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
5244
assert! (
5245
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 0\n 1\n 2\n 3\n 4\n " )
5246
) ;
957fee30 Andy McCall 1 month ago ↶
5247
assert! ( matches! ( & files[ " b.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " b\n " ) ) ;
5248
assert! ( ! files.contains_key ( " c.txt" ) ) ;
5249
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5250
assert_eq! (
5251
snap.statuses
5252
.iter ( )
5253
.map ( | s | ( s.path.as_str ( ) , s.status) )
5254
.collect:: < Vec< _ > > ( ) ,
5255
vec! [
5256
( " b.txt" , ChangeStatus:: Modified) ,
5257
( " c.txt" , ChangeStatus:: Added)
5258
]
5259
) ;
5260
assert_eq! (
5261
fs:: read( root.join ( " b.txt" ) ) .unwrap ( ) ,
5262
b " b\n bb\n " ,
5263
" disk untouched"
5264
) ;
957fee30 Andy McCall 1 month ago ↶
5265
5266
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5267
let b_hunk = hunks
5268
.iter ( )
5269
.find ( | h | h.path = = " b.txt" & & h.text.contains ( " +bb" ) )
5270
.unwrap ( )
5271
.index;
5272
let h2 = repo
5273
.commit_selection ( " b only" , & [ ] , & Selection:: Hunks( vec! [ b_hunk] ) )
5274
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5275
let files = repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
5276
assert! (
5277
matches! ( & files[ " b.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " b\n bb\n " )
5278
) ;
957fee30 Andy McCall 1 month ago ↶
5279
assert! ( ! files.contains_key ( " c.txt" ) ) ;
5280
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5281
assert_eq! (
5282
snap.statuses
5283
.iter ( )
5284
.map ( | s | s.path.as_str ( ) )
5285
.collect:: < Vec< _ > > ( ) ,
5286
vec! [ " c.txt" ]
5287
) ;
957fee30 Andy McCall 1 month ago ↶
5288
5289
5290
write ( root, " d/e.txt" , b " e\n " ) ;
5291
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5292
let e_insert = hunks
5293
.iter ( )
5294
.find ( | h | h.path = = " d/e.txt" & & h.text.contains ( " +e" ) )
5295
.unwrap ( )
5296
.index;
5297
let h3 = repo
5298
.commit_selection ( " e only" , & [ ] , & Selection:: Hunks( vec! [ e_insert] ) )
5299
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5300
let c3 = repo.store ( ) .get_change ( & h3) .unwrap ( ) ;
5301
assert_eq! ( c3.tree_ops.len ( ) , 2 , " {:?}" , c3.tree_ops) ;
5302
let files = repo.load_state ( & [ h3] ) .unwrap ( ) .materialize ( ) .files;
5303
assert! ( files.contains_key ( " d/e.txt" ) & & ! files.contains_key ( " c.txt" ) ) ;
d8c086ca Andy McCall 1 month ago ↶
5304
5305
write ( root, " f/g.txt" , b " g\n " ) ;
5306
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5307
let g_add = hunks
5308
.iter ( )
5309
.find ( | h | h.path = = " f/g.txt" & & h.text.starts_with ( " A " ) )
5310
.unwrap ( )
5311
.index;
5312
let h4 = repo
5313
.commit_selection ( " g by addition" , & [ ] , & Selection:: Hunks( vec! [ g_add] ) )
5314
.unwrap ( ) ;
d8c086ca Andy McCall 1 month ago ↶
5315
let files = repo.load_state ( & [ h4] ) .unwrap ( ) .materialize ( ) .files;
75167672 Andy McCall 1 month ago
5316
assert! (
5317
matches! ( & files[ " f/g.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " g\n " )
5318
) ;
5319
assert! ( ! repo
5320
.snapshot ( " status" )
5321
.unwrap ( )
5322
.statuses
5323
.iter ( )
5324
.any ( | s | s.path = = " f/g.txt" ) ) ;
957fee30 Andy McCall 1 month ago ↶
5325
5326
repo.commit ( " the rest" , & [ ] ) .unwrap ( ) ;
5327
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
75167672 Andy McCall 1 month ago
5328
let files = repo
5329
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
5330
.unwrap ( )
5331
.materialize ( )
5332
.files;
957fee30 Andy McCall 1 month ago ↶
5333
assert! ( files.contains_key ( " c.txt" ) ) ;
5334
}
5335
5336
# [ test ]
5337
fn blame_attributes_lines_to_introducing_changes_across_renames ( ) {
5338
hermetic ( ) ;
5339
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5340
let root = dir.path ( ) ;
5341
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
75167672 Andy McCall 1 month ago
5342
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
5343
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5344
write ( root, " a.txt" , b " one\n " ) ;
5345
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
5346
write ( root, " a.txt" , b " one\n two\n " ) ;
5347
let h2 = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
5348
let b = repo.blame ( " a.txt" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5349
assert_eq! (
5350
b.iter ( )
5351
.map ( | l | ( l.line, l.hash, l.text.as_slice ( ) ) )
5352
.collect:: < Vec< _ > > ( ) ,
5353
vec! [ ( 1 , h1, b " one\n " .as_slice ( ) ) , ( 2 , h2, b " two\n " .as_slice ( ) ) ]
5354
) ;
957fee30 Andy McCall 1 month ago ↶
5355
assert! ( b.iter ( ) .all ( | l | l.author = = " Ada" ) ) ;
75167672 Andy McCall 1 month ago
5356
assert_eq! (
5357
b[ 0 ] .change_id,
5358
repo.store ( ) .get_change ( & h1) .unwrap ( ) .change_id
5359
) ;
957fee30 Andy McCall 1 month ago ↶
5360
fs:: rename( root.join ( " a.txt" ) , root.join ( " b.txt" ) ) .unwrap ( ) ;
5361
repo.commit ( " rename" , & [ ] ) .unwrap ( ) ;
5362
let b = repo.blame ( " b.txt" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5363
assert_eq! (
5364
b.iter ( ) .map ( | l | l.hash ) .collect:: < Vec< _ > > ( ) ,
5365
vec! [ h1, h2] ,
5366
" the node survives the rename"
5367
) ;
957fee30 Andy McCall 1 month ago ↶
5368
assert! ( repo.blame ( " a.txt" ) .is_err ( ) ) ;
5369
}
5370
5371
# [ test ]
5372
fn bisect_finds_the_first_bad_change ( ) {
5373
hermetic ( ) ;
5374
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5375
let root = dir.path ( ) ;
5376
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5377
let mut content = String :: new( ) ;
5378
let mut hashes = Vec :: new( ) ;
5379
for i in 1 .. = 8 {
5380
content.push_str ( & format! ( " line {i} {} \n " , if i = = 5 { " bug" } else { " " } ) ) ;
5381
write ( root, " f.txt" , content.as_bytes ( ) ) ;
5382
hashes.push ( repo.commit ( & format! ( " step {i} " ) , & [ ] ) .unwrap ( ) ) ;
5383
}
5384
5385
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ hashes[ 7 ] ] ) ;
5386
repo.bisect_start ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5387
assert! ( matches! (
5388
repo.bisect_mark ( Some ( true ) , & [ hashes[ 0 ] ] ) .unwrap ( ) ,
5389
BisectStep:: NeedMarks { good: 1 , bad: 0 }
5390
) ) ;
957fee30 Andy McCall 1 month ago ↶
5391
let step = repo.bisect_mark ( Some ( false ) , & [ hashes[ 7 ] ] ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5392
assert! (
5393
matches! ( step, BisectStep:: Testing { remaining: 7 , .. } ) ,
5394
" {step:?}"
5395
) ;
957fee30 Andy McCall 1 month ago ↶
5396
75167672 Andy McCall 1 month ago
5397
let step = repo
5398
.bisect_run ( & [ " sh" .into ( ) , " -c" .into ( ) , " ! grep -q bug f.txt" .into ( ) ] )
5399
.unwrap ( ) ;
957fee30 Andy McCall 1 month ago ↶
5400
assert_eq! ( step, BisectStep:: Found { culprit: hashes[ 4 ] } ) ;
75167672 Andy McCall 1 month ago
5401
assert_eq! (
5402
repo.bisect_status ( ) .unwrap ( ) ,
5403
Some ( BisectStep:: Found { culprit: hashes[ 4 ] } )
5404
) ;
957fee30 Andy McCall 1 month ago ↶
5405
repo.bisect_reset ( ) .unwrap ( ) ;
5406
assert_eq! ( fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) , content) ;
5407
assert! ( repo.bisect_status ( ) .unwrap ( ) .is_none ( ) ) ;
5408
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
5409
5410
repo.bisect_start ( ) .unwrap ( ) ;
5411
repo.bisect_mark ( Some ( true ) , & [ hashes[ 0 ] ] ) .unwrap ( ) ;
5412
let mut step = repo.bisect_mark ( Some ( false ) , & [ hashes[ 7 ] ] ) .unwrap ( ) ;
5413
let mut skipped_once = false ;
5414
let mut guard = 0 ;
5415
while let BisectStep:: Testing { .. } = step {
5416
guard + = 1 ;
5417
assert! ( guard < 20 ) ;
5418
let text = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
5419
if ! skipped_once & & text.contains ( " line 6" ) {
5420
5421
skipped_once = true ;
5422
step = repo.bisect_mark ( None , & [ ] ) .unwrap ( ) ;
5423
continue ;
5424
}
5425
step = repo.bisect_mark ( Some ( ! text.contains ( " bug" ) ) , & [ ] ) .unwrap ( ) ;
5426
}
5427
assert! ( skipped_once) ;
5428
assert_eq! ( step, BisectStep:: Found { culprit: hashes[ 4 ] } ) ;
5429
repo.bisect_reset ( ) .unwrap ( ) ;
5430
5431
repo.bisect_start ( ) .unwrap ( ) ;
5432
repo.bisect_mark ( Some ( true ) , & [ hashes[ 0 ] ] ) .unwrap ( ) ;
5433
let mut step = repo.bisect_mark ( Some ( false ) , & [ hashes[ 7 ] ] ) .unwrap ( ) ;
5434
let mut guard = 0 ;
5435
while let BisectStep:: Testing { heads, .. } = & step {
5436
guard + = 1 ;
5437
assert! ( guard < 20 ) ;
5438
let text = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5439
let is_culprit_state =
5440
heads.contains ( & hashes[ 4 ] ) & & ! heads.iter ( ) .any ( | h | hashes[ 5 .. ] .contains ( h) ) ;
5441
step = if is_culprit_state {
5442
repo.bisect_mark ( None , & [ ] ) .unwrap ( )
5443
} else {
5444
repo.bisect_mark ( Some ( ! text.contains ( " bug" ) ) , & [ ] ) .unwrap ( )
5445
} ;
957fee30 Andy McCall 1 month ago ↶
5446
}
5447
match step {
75167672 Andy McCall 1 month ago
5448
BisectStep:: Inconclusive { candidates } => assert! (
5449
candidates.contains ( & hashes[ 4 ] ) & & candidates.len ( ) < = 2 ,
5450
" {candidates:?}"
5451
) ,
957fee30 Andy McCall 1 month ago ↶
5452
other => panic! ( " {other:?}" ) ,
5453
}
5454
repo.bisect_reset ( ) .unwrap ( ) ;
5455
}
5456
728da0f9 Andy McCall 1 month ago ↶
5457
# [ test ]
5458
fn squash_folds_a_change_into_its_dependency ( ) {
5459
hermetic ( ) ;
5460
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5461
let root = dir.path ( ) ;
5462
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5463
write ( root, " a.txt" , b " 1\n " ) ;
5464
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
5465
write ( root, " a.txt" , b " 1\n 2\n " ) ;
5466
let c = repo.commit ( " C" , & [ ] ) .unwrap ( ) ;
5467
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
5468
let e = repo.commit ( " E" , & [ ] ) .unwrap ( ) ;
5469
write ( root, " b.txt" , b " b\n " ) ;
5470
let b = repo.commit ( " B" , & [ ] ) .unwrap ( ) ;
5471
let id_a = repo.store ( ) .get_change ( & a) .unwrap ( ) .change_id;
5472
let id_e = repo.store ( ) .get_change ( & e) .unwrap ( ) .change_id;
5473
5474
let err = repo.squash ( & e, & a) .unwrap_err ( ) .to_string ( ) ;
5475
assert! ( err.contains ( & c.short ( 12 ) ) , " {err}" ) ;
5476
5477
let a2 = repo.squash ( & c, & a) .unwrap ( ) ;
5478
let a2c = repo.store ( ) .get_change ( & a2) .unwrap ( ) ;
5479
assert_eq! ( a2c.change_id, id_a) ;
5480
assert! ( a2c.deps.is_empty ( ) ) ;
5481
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5482
let st = repo.load_state ( & heads) .unwrap ( ) ;
5483
assert! ( ! st.contains ( & c) & & ! st.contains ( & a) & & ! st.contains ( & e) ) ;
75167672 Andy McCall 1 month ago
5484
let e2 = st
5485
.changes ( )
5486
.find ( | ( _, m ) | m.change_id = = id_e )
5487
.map ( | ( h , _) | * h)
5488
.unwrap ( ) ;
5489
assert_eq! (
5490
repo.store ( ) .get_change ( & e2) .unwrap ( ) .deps,
5491
vec! [ a2] ,
5492
" E now depends on A' alone"
5493
) ;
728da0f9 Andy McCall 1 month ago ↶
5494
let files = st.materialize ( ) .files;
75167672 Andy McCall 1 month ago
5495
assert! (
5496
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 1\n 2\n 3\n " )
5497
) ;
728da0f9 Andy McCall 1 month ago ↶
5498
assert! ( files.contains_key ( " b.txt" ) ) ;
5499
assert! ( st.contains ( & b) ) ;
5500
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
5501
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 , " working copy, B, E', A'" ) ;
5502
5503
repo.undo ( ) .unwrap ( ) ;
5504
repo.undo ( ) .unwrap ( ) ;
5505
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
5506
assert! ( st.contains ( & c) & & st.contains ( & a) & & st.contains ( & e) ) ;
5507
}
5508
5509
# [ test ]
5510
fn rebase_re_expresses_a_change_against_another_base ( ) {
5511
hermetic ( ) ;
5512
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5513
let root = dir.path ( ) ;
5514
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5515
write ( root, " a.txt" , b " 1\n 2\n " ) ;
5516
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
5517
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
5518
let c = repo.commit ( " C: append 3" , & [ ] ) .unwrap ( ) ;
5519
write ( root, " a.txt" , b " 1\n 2\n 3\n 4\n " ) ;
5520
let e = repo.commit ( " E: append 4" , & [ ] ) .unwrap ( ) ;
5521
5522
repo.checkout ( & [ a] , true , " checkout" ) .unwrap ( ) ;
5523
write ( root, " a.txt" , b " 0\n 1\n 2\n " ) ;
5524
let d = repo.commit ( " D: prepend 0" , & [ ] ) .unwrap ( ) ;
5525
let id_c = repo.store ( ) .get_change ( & c) .unwrap ( ) .change_id;
5526
let id_e = repo.store ( ) .get_change ( & e) .unwrap ( ) .change_id;
5527
5528
let c2 = repo.rebase ( & c, & [ d] ) .unwrap ( ) ;
5529
let c2c = repo.store ( ) .get_change ( & c2) .unwrap ( ) ;
5530
assert_eq! ( c2c.change_id, id_c) ;
5531
assert! ( c2c.deps.contains ( & d) , " {:?}" , c2c.deps) ;
5532
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5533
let st = repo.load_state ( & heads) .unwrap ( ) ;
5534
assert! ( ! st.contains ( & c) & & st.contains ( & d) ) ;
75167672 Andy McCall 1 month ago
5535
let e2 = st
5536
.changes ( )
5537
.find ( | ( _, m ) | m.change_id = = id_e )
5538
.map ( | ( h , _) | * h)
5539
.unwrap ( ) ;
728da0f9 Andy McCall 1 month ago ↶
5540
let e2deps = repo.store ( ) .get_change ( & e2) .unwrap ( ) .deps;
75167672 Andy McCall 1 month ago
5541
assert! (
5542
e2deps.contains ( & c2) & & ! e2deps.contains ( & c) & & e2deps.contains ( & a) ,
5543
" E follows C by content and still depends on A, which introduced the file: {e2deps:?}"
5544
) ;
728da0f9 Andy McCall 1 month ago ↶
5545
let files = st.materialize ( ) .files;
75167672 Andy McCall 1 month ago
5546
assert! (
5547
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 0\n 1\n 2\n 3\n 4\n " ) ,
5548
" {:?}" ,
5549
files[ " a.txt" ] .content
5550
) ;
728da0f9 Andy McCall 1 month ago ↶
5551
assert! ( ! files[ " a.txt" ] .is_conflicted ( ) ) ;
5552
5553
assert! ( repo.rebase ( & c2, & [ c2] ) .is_err ( ) ) ;
5554
let err = repo.rebase ( & e2, & [ d] ) .unwrap_err ( ) .to_string ( ) ;
75167672 Andy McCall 1 month ago
5555
assert! (
5556
err.contains ( " lacks dependencies" ) ,
5557
" E needs C, which D's state does not have: {err}"
5558
) ;
728da0f9 Andy McCall 1 month ago ↶
5559
}
5560
c792033a Andy McCall 1 month ago ↶
5561
# [ test ]
5562
fn unsigned_tags_keep_their_message ( ) {
5563
hermetic ( ) ;
5564
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5565
let root = dir.path ( ) ;
5566
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
5567
write ( root, " a.txt" , b " a\n " ) ;
5568
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
5569
assert! ( repo.secret_key ( ) .unwrap ( ) .is_none ( ) ) ;
75167672 Andy McCall 1 month ago
5570
repo.tag_create ( " v1" , & repo.bookmark_heads ( ) .unwrap ( ) , " first release" )
5571
.unwrap ( ) ;
c792033a Andy McCall 1 month ago ↶
5572
let tags = repo.tags ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
5573
assert_eq! (
5574
(
5575
tags[ 0 ] .name.as_str ( ) ,
5576
tags[ 0 ] .message.as_str ( ) ,
5577
tags[ 0 ] .signer.as_deref ( )
5578
) ,
5579
( " v1" , " first release" , None )
5580
) ;
5581
assert! (
5582
repo.tag_refs ( ) .unwrap ( ) [ 0 ] .attestation.is_none ( ) ,
5583
" an unsigned attestation is not advertised"
5584
) ;
c792033a Andy McCall 1 month ago ↶
5585
}
387335ff Andy McCall 1 month ago ↶
5586
5587
# [ test ]
5588
fn fsck_passes_a_sound_repository_and_names_each_fault ( ) {
64818938 Andy McCall 1 month ago ↶
5589
5590
5591
if std:: env:: var_os( " OXY_STORE_BACKEND" ) .is_some ( ) {
5592
return ;
5593
}
387335ff Andy McCall 1 month ago ↶
5594
hermetic ( ) ;
5595
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5596
let root = dir.path ( ) ;
5597
let origin_path = root.join ( " origin" ) ;
5598
let origin = Repo:: init( & origin_path) .unwrap ( ) ;
5599
let mut a = Repo:: clone( & origin_path.to_string_lossy ( ) , & root.join ( " a" ) )
5600
.unwrap ( )
5601
.with_home ( root.join ( " home-a" ) ) ;
5602
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
5603
.unwrap ( ) ;
5604
write ( & root.join ( " a" ) , " f.txt" , b " one\n " ) ;
5605
write ( & root.join ( " a" ) , " bin.dat" , b " \0 \x01 \x02 binary" ) ;
5606
let h1 = a.commit ( " first" , & [ ] ) .unwrap ( ) ;
5607
write ( & root.join ( " a" ) , " f.txt" , b " one\n two\n " ) ;
5608
let h2 = a.commit ( " second" , & [ ] ) .unwrap ( ) ;
5609
a.tag_create ( " v1" , & [ h2] , " first release" ) .unwrap ( ) ;
5610
a.checkpoint ( ) .unwrap ( ) ;
5611
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
5612
origin.refresh ( ) .unwrap ( ) ;
387335ff Andy McCall 1 month ago ↶
5613
5614
5615
for repo in [ & a, & origin] {
5616
let rep = repo.fsck ( ) .unwrap ( ) ;
5617
assert_eq! ( rep.problems, vec! [ ] , " {rep:?}" ) ;
5618
5619
assert! ( rep.changes > = 2 & & rep.signed > = 2 , " {rep:?}" ) ;
5620
assert! ( rep.ops > 1 & & rep.objects > 5 ) ;
5621
}
5622
assert! ( origin.fsck ( ) .unwrap ( ) .attestations > = 1 ) ;
5623
5624
5625
let c1 = a.store ( ) .path_for ( & h1) ;
5626
let pristine = fs:: read( & c1) .unwrap ( ) ;
5627
let mut bytes = pristine.clone ( ) ;
5628
* bytes.last_mut ( ) .unwrap ( ) ^ = 1 ;
5629
fs:: write( & c1, & bytes) .unwrap ( ) ;
5630
let rep = a.fsck ( ) .unwrap ( ) ;
5631
assert! ( rep.errors ( ) > = 1 ) ;
5632
assert! (
5633
rep.problems
5634
.iter ( )
5635
.any ( | p | p.object = = Some ( h1) & & p.what.contains ( " unreadable" ) ) ,
5636
" {rep:?}"
5637
) ;
5638
fs:: write( & c1, & pristine) .unwrap ( ) ;
5639
assert_eq! ( a.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
5640
5641
5642
fs:: remove_file( & c1) .unwrap ( ) ;
5643
let rep = a.fsck ( ) .unwrap ( ) ;
5644
assert! (
5645
rep.problems.iter ( ) .any ( | p | p.severity = = Severity:: Error
5646
& & p.object = = Some ( h1)
5647
& & p.what.contains ( " change missing, needed by change" ) ) ,
5648
" {rep:?}"
5649
) ;
5650
fs:: write( & c1, & pristine) .unwrap ( ) ;
5651
5652
5653
let c2 = a.store ( ) .get_change ( & h1) .unwrap ( ) ;
5654
let blob = c2
5655
.edits
5656
.iter ( )
5657
.find_map ( | e | match e {
5658
Edit:: Replace { new: Some ( b) , .. } => Some ( * b) ,
5659
_ => None ,
5660
} )
5661
.expect ( " the binary file is a blob" ) ;
5662
let bp = a.store ( ) .path_for ( & blob) ;
5663
let blob_bytes = fs:: read( & bp) .unwrap ( ) ;
5664
fs:: remove_file( & bp) .unwrap ( ) ;
5665
let rep = a.fsck ( ) .unwrap ( ) ;
5666
assert! (
5667
rep.problems
5668
.iter ( )
5669
.any ( | p | p.object = = Some ( blob) & & p.what.contains ( " blob missing" ) ) ,
5670
" {rep:?}"
5671
) ;
5672
fs:: write( & bp, & blob_bytes) .unwrap ( ) ;
5673
5674
5675
let head = root.join ( " a/.oxy/ops/head" ) ;
5676
let saved = fs:: read( & head) .unwrap ( ) ;
5677
fs:: write( & head, b " not a hash" ) .unwrap ( ) ;
5678
let rep = a.fsck ( ) .unwrap ( ) ;
5679
assert! (
5680
rep.problems.iter ( ) .any ( | p | p.what.contains ( " ops/head" ) ) ,
5681
" {rep:?}"
5682
) ;
5683
fs:: write( & head, saved) .unwrap ( ) ;
5684
assert_eq! ( a.fsck ( ) .unwrap ( ) .problems, vec! [ ] ) ;
5685
}
4dcb8c09 Andy McCall 1 month ago ↶
5686
5687
5688
fn copy_tree ( from : & Path, to : & Path) {
5689
fs:: create_dir_all( to) .unwrap ( ) ;
5690
for e in fs:: read_dir( from) .unwrap ( ) {
5691
let e = e.unwrap ( ) ;
5692
let target = to.join ( e.file_name ( ) ) ;
5693
if e.file_type ( ) .unwrap ( ) .is_dir ( ) {
5694
copy_tree ( & e.path ( ) , & target) ;
5695
} else {
5696
fs:: copy( e.path ( ) , & target) .unwrap ( ) ;
5697
}
5698
}
5699
}
5700
5701
5702
5703
5704
# [ test ]
5705
fn crash_at_every_step_of_commit_and_push_leaves_sound_repositories ( ) {
64818938 Andy McCall 1 month ago ↶
5706
5707
5708
if std:: env:: var_os( " OXY_STORE_BACKEND" ) .is_some ( ) {
5709
return ;
5710
}
4dcb8c09 Andy McCall 1 month ago ↶
5711
use crate :: durable:: failpoint;
5712
hermetic ( ) ;
5713
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5714
let pristine = dir.path ( ) .join ( " pristine" ) ;
5715
let origin_path = pristine.join ( " origin" ) ;
5716
Repo:: init( & origin_path) .unwrap ( ) ;
5717
let mut a = Repo:: clone( & origin_path.to_string_lossy ( ) , & pristine.join ( " a" ) )
5718
.unwrap ( )
5719
.with_home ( dir.path ( ) .join ( " home" ) ) ;
5720
a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
5721
.unwrap ( ) ;
5722
write ( & pristine.join ( " a" ) , " f.txt" , b " one\n " ) ;
5723
a.commit ( " first" , & [ ] ) .unwrap ( ) ;
5724
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
5725
drop ( a) ;
5726
5727
type Op = fn ( & Repo) -> Result< ( ) > ;
5728
let edit_and_commit: Op = | r | {
5729
write ( r.root ( ) , " f.txt" , b " one\n two\n " ) ;
5730
write ( r.root ( ) , " big.bin" , & vec! [ 7 u8 ; 300_000 ] ) ;
5731
r.commit ( " second" , & [ ] ) .map ( | _| ( ) )
5732
} ;
5733
let push: Op = | r | {
5734
let res = r.push ( " origin" , false ) ? ;
5735
if res.iter ( ) .all ( | x | x.ok ) {
5736
Ok ( ( ) )
5737
} else {
5738
Err ( RepoError:: Msg( format! ( " {res:?} " ) ) )
5739
}
5740
} ;
5741
5742
for ( name, prepare, op) in [
5743
( " commit" , None , edit_and_commit) ,
5744
( " push" , Some ( edit_and_commit) , push) ,
5745
] {
5746
let mut steps = 0 ;
5747
for n in 1 .. {
5748
let run = dir.path ( ) .join ( format! ( " {name} -{n} " ) ) ;
5749
copy_tree ( & pristine, & run) ;
5750
5751
let open = | p : & Path| {
5752
let mut r = Repo:: open( p) .unwrap ( ) .with_home ( dir.path ( ) .join ( " home" ) ) ;
5753
r.config
5754
.set ( " remote.origin.url" , & run.join ( " origin" ) .to_string_lossy ( ) )
5755
.unwrap ( ) ;
5756
r.save_config ( ) .unwrap ( ) ;
5757
r
5758
} ;
5759
let a = open ( & run.join ( " a" ) ) ;
5760
if let Some ( prep) = prepare {
5761
prep ( & a) .unwrap ( ) ;
5762
}
5763
failpoint:: crash_after( n) ;
5764
let outcome = op ( & a) ;
5765
let crashed = failpoint:: disarm( ) ;
5766
drop ( a) ;
5767
if ! crashed {
5768
outcome.unwrap_or_else ( | e | panic! ( " {name} failed without a crash: {e}" ) ) ;
5769
steps = n - 1 ;
5770
break ;
5771
}
5772
assert! (
5773
outcome.is_err ( ) ,
5774
" {name}: a crash at step {n} was swallowed"
5775
) ;
5776
for repo in [ " a" , " origin" ] {
5777
let r = Repo:: open( & run.join ( repo) ) .unwrap_or_else ( | e | {
5778
panic! ( " {name} crash at step {n}: {repo} no longer opens: {e}" )
5779
} ) ;
5780
let rep = r.fsck ( ) .unwrap ( ) ;
5781
assert_eq! (
5782
rep.errors ( ) ,
5783
0 ,
5784
" {name} crash at step {n}: {repo} fails fsck: {:?}" ,
5785
rep.problems
5786
) ;
5787
}
5788
5789
let a = open ( & run.join ( " a" ) ) ;
5790
5791
let landed = a
5792
.bookmark_heads ( )
5793
.unwrap ( )
5794
.iter ( )
5795
.any ( | h | a.store ( ) .get_change ( h) .unwrap ( ) .message = = " second" ) ;
5796
let again = match ( name, landed) {
5797
( " commit" , true ) => Ok ( ( ) ) ,
5798
( " commit" , false ) => a.commit ( " second" , & [ ] ) .map ( | _| ( ) ) ,
5799
_ => op ( & a) ,
5800
} ;
5801
again.unwrap_or_else ( | e | panic! ( " {name} crash at step {n}: retry failed: {e}" ) ) ;
5802
assert_eq! ( a.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
5803
fs:: remove_dir_all( & run) .unwrap ( ) ;
5804
}
5805
assert! ( steps > = 3 , " {name} has only {steps} durable steps?" ) ;
5806
}
5807
}
19555025 Andy McCall 1 month ago ↶
5808
5809
5810
5811
5812
# [ test ]
5813
fn concurrent_commits_in_two_workspaces_lose_nothing ( ) {
5814
hermetic ( ) ;
5815
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5816
let root = dir.path ( ) ;
5817
let origin = root.join ( " origin" ) ;
5818
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
5819
let main = root.join ( " main" ) ;
5820
let mut repo = Repo:: init( & main) .unwrap ( ) ;
5821
repo.config
5822
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
5823
.unwrap ( ) ;
5824
repo.save_config ( ) .unwrap ( ) ;
5825
write ( & main, " seed.txt" , b " seed\n " ) ;
5826
repo.commit ( " seed" , & [ ] ) .unwrap ( ) ;
5827
let second = root.join ( " second" ) ;
5828
repo.workspace_add ( & second, None ) .unwrap ( ) ;
5829
drop ( repo) ;
5830
5831
const EACH : usize = 15 ;
5832
std:: thread:: scope( | s | {
5833
for ( tag, dir, push) in [ ( " a" , main.clone ( ) , true ) , ( " b" , second.clone ( ) , false ) ] {
5834
s.spawn ( move | | {
5835
let r = Repo:: open( & dir) .unwrap ( ) ;
5836
for i in 0 .. EACH {
5837
write (
5838
& dir,
5839
& format! ( " {tag} -{i} .txt" ) ,
5840
format! ( " {tag} {i} \n " ) .as_bytes ( ) ,
5841
) ;
5842
r.commit ( & format! ( " {tag} {i} " ) , & [ ] ) .unwrap ( ) ;
5843
if push & & i % 5 = = 4 {
5844
5845
let _ = r.push ( " origin" , false ) ;
5846
}
5847
}
5848
} ) ;
5849
}
5850
} ) ;
5851
5852
let repo = Repo:: open( & main) .unwrap ( ) ;
5853
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
5854
let state = repo.load_state ( & heads) .unwrap ( ) ;
5855
let messages: BTreeSet< String> = state.changes ( ) .map ( | ( _, m ) | m.message.clone ( ) ) .collect ( ) ;
5856
for tag in [ " a" , " b" ] {
5857
for i in 0 .. EACH {
5858
assert! (
5859
messages.contains ( & format! ( " {tag} {i} " ) ) ,
5860
" lost commit {tag} {i}; have {messages:?}"
5861
) ;
5862
}
5863
}
5864
let rep = repo.fsck ( ) .unwrap ( ) ;
5865
assert_eq! ( rep.errors ( ) , 0 , " {:?}" , rep.problems) ;
5866
assert! ( repo.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
5867
let hosted = Repo:: open( & origin) .unwrap ( ) ;
5868
let hosted_heads = hosted.bookmark_heads ( ) .unwrap ( ) ;
5869
assert_eq! (
5870
hosted.load_state ( & hosted_heads) .unwrap ( ) .len ( ) ,
5871
state.len ( ) ,
5872
" the remote has every commit"
5873
) ;
5874
}
5875
5876
5877
5878
5879
# [ test ]
5880
fn operations_from_the_same_parent_are_merged ( ) {
5881
hermetic ( ) ;
5882
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5883
let root = dir.path ( ) ;
5884
let repo = Repo:: init( root) .unwrap ( ) ;
5885
write ( root, " a.txt" , b " a\n " ) ;
5886
let a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
5887
write ( root, " b.txt" , b " b\n " ) ;
5888
let b = repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
5889
let ( x, op) = repo.head_op ( ) .unwrap ( ) ;
5890
let state = | heads : Vec< ChangeHash> | repo.store ( ) .put_state ( & StateObject:: new( heads) ) .unwrap ( ) ;
5891
5892
5893
let mut one = op.view.clone ( ) ;
5894
one.bookmarks.insert ( " main" .into ( ) , state ( vec! [ a] ) ) ;
5895
one.bookmarks.insert ( " topic" .into ( ) , state ( vec! [ a] ) ) ;
5896
let mut two = op.view.clone ( ) ;
5897
two.bookmarks.insert ( " main" .into ( ) , state ( vec! [ b] ) ) ;
5898
repo.record_op ( & [ x] , " one" , one) .unwrap ( ) ;
5899
let merged = repo.record_op ( & [ x] , " two" , two) .unwrap ( ) ;
5900
5901
let ( head, op) = repo.head_op ( ) .unwrap ( ) ;
5902
assert_eq! ( head, merged) ;
5903
assert_eq! ( op.parents.len ( ) , 2 , " a merge of the two operations" ) ;
5904
assert! ( op.command.contains ( " merged with a concurrent operation" ) ) ;
5905
let mut main = repo
5906
.store ( )
5907
.get_state ( & op.view.bookmarks[ " main" ] )
5908
.unwrap ( )
5909
.heads;
5910
main.sort ( ) ;
5911
let mut want = vec! [ a, b] ;
5912
want.sort ( ) ;
5913
assert_eq! ( main, want, " both sides' heads are kept" ) ;
5914
assert! (
5915
op.view.bookmarks.contains_key ( " topic" ) ,
5916
" a one-sided change survives"
5917
) ;
5918
assert_eq! ( repo.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
5919
}
3e5772cc Andy McCall 1 month ago ↶
5920
5921
# [ test ]
5922
fn failed_requests_come_with_a_next_step ( ) {
5923
use crate :: protocol:: hint_for;
5924
assert! ( hint_for ( " https://h/x/oxy/v1/push: HTTP 401: bad token" )
5925
.unwrap ( )
5926
.contains ( " token" ) ) ;
5927
assert! ( hint_for ( " …: HTTP 403: forbidden" )
5928
.unwrap ( )
5929
.contains ( " repo:write" ) ) ;
5930
assert! ( hint_for ( " …: HTTP 404: no such repository" )
5931
.unwrap ( )
5932
.contains ( " owner and repository name" ) ) ;
5933
assert! ( hint_for ( " invalid peer certificate: UnknownIssuer" )
5934
.unwrap ( )
5935
.contains ( " oxyhub-ca.pem" ) ) ;
5936
assert! ( hint_for ( " Connect error: Connection refused (os error 111)" )
5937
.unwrap ( )
5938
.contains ( " running" ) ) ;
5939
assert! ( hint_for ( " Dns Failed: failed to lookup address information" )
5940
.unwrap ( )
5941
.contains ( " resolve" ) ) ;
5942
assert_eq! ( hint_for ( " something else entirely" ) , None ) ;
5943
}
5944
5945
# [ test ]
5946
fn doctor_names_problems_and_what_to_do ( ) {
5947
hermetic ( ) ;
5948
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
5949
let root = dir.path ( ) ;
5950
let origin = root.join ( " origin" ) ;
5951
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
5952
let mut repo = Repo:: init( & root.join ( " r" ) )
5953
.unwrap ( )
5954
.with_home ( root.join ( " home" ) ) ;
5955
let find = | r : & DoctorReport, area : & str , h : Health| {
5956
r.checks
5957
.iter ( )
5958
.find ( | c | c.area = = area & & c.health = = h )
5959
.cloned ( )
5960
} ;
5961
5962
let r = repo.doctor ( true ) ;
5963
assert! ( find ( & r, " identity" , Health:: Warn)
5964
.unwrap ( )
5965
.hint
5966
.unwrap ( )
5967
.contains ( " oxy identity create" ) ) ;
5968
assert! ( find ( & r, " repository" , Health:: Ok) .is_some ( ) ) ;
5969
5970
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
5971
.unwrap ( ) ;
5972
repo.config.set ( " author.name" , " Ada" ) .unwrap ( ) ;
5973
repo.config.set ( " author.email" , " [email protected] " ) .unwrap ( ) ;
5974
repo.config
5975
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
5976
.unwrap ( ) ;
5977
repo.config
5978
.set ( " remote.down.url" , " http://127.0.0.1:9/a/b" )
5979
.unwrap ( ) ;
5980
repo.config.set ( " remote.down.token" , " t" ) .unwrap ( ) ;
5981
repo.config
5982
.set ( " remote.badca.url" , " https://example.invalid/a/b" )
5983
.unwrap ( ) ;
5984
repo.config
5985
.set (
5986
" remote.badca.ca" ,
5987
& root.join ( " missing.pem" ) .to_string_lossy ( ) ,
5988
)
5989
.unwrap ( ) ;
5990
repo.save_config ( ) .unwrap ( ) ;
5991
5992
let r = repo.doctor ( true ) ;
5993
assert! ( find ( & r, " identity" , Health:: Ok) .is_some ( ) , " {r:?}" ) ;
5994
assert! ( find ( & r, " author" , Health:: Ok) .is_some ( ) , " {r:?}" ) ;
5995
assert! ( find ( & r, " remote origin" , Health:: Ok)
5996
.unwrap ( )
5997
.message
5998
.contains ( " first push binds it" ) ) ;
5999
let down = find ( & r, " remote down" , Health:: Fail) .unwrap ( ) ;
6000
assert! (
6001
down.hint.as_deref ( ) .unwrap_or ( " " ) .contains ( " running" ) ,
6002
" {down:?}"
6003
) ;
6004
let badca = find ( & r, " remote badca" , Health:: Fail) .unwrap ( ) ;
6005
assert! (
6006
badca.hint.unwrap ( ) .contains ( " remote.badca.ca" ) ,
6007
" the remote's own name is in the hint"
6008
) ;
6009
assert_eq! ( r.worst ( ) , Health:: Fail) ;
6010
6011
6012
let r = repo.doctor ( false ) ;
6013
assert! ( find ( & r, " remote down" , Health:: Fail) .is_none ( ) ) ;
6014
assert! ( find ( & r, " remote down" , Health:: Ok)
6015
.unwrap ( )
6016
.message
6017
.contains ( " not contacted" ) ) ;
6018
}
62794ecc Andy McCall 1 month ago ↶
6019
6020
6021
6022
6023
# [ test ]
6024
fn deleting_a_line_after_an_insertion_below_it_is_not_a_conflict ( ) {
6025
hermetic ( ) ;
6026
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6027
let root = dir.path ( ) ;
6028
let repo = Repo:: init( root) .unwrap ( ) ;
6029
write ( root, " f" , b " a\n b\n c\n " ) ;
6030
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6031
write ( root, " f" , b " a\n b\n x\n c\n " ) ;
6032
let insert = repo.commit ( " insert x after b" , & [ ] ) .unwrap ( ) ;
6033
write ( root, " f" , b " a\n x\n c\n " ) ;
6034
let delete = repo.commit ( " delete b" , & [ ] ) .unwrap ( ) ;
6035
assert! (
6036
repo.store ( )
6037
.get_change ( & delete)
6038
.unwrap ( )
6039
.deps
6040
.contains ( & insert) ,
6041
" the deletion depends on the insertion below the deleted line"
6042
) ;
6043
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6044
assert_eq! ( heads, vec! [ delete] ) ;
6045
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
6046
assert! ( ! snap.is_conflicted ( ) ) ;
6047
assert! (
6048
matches! ( & snap.files[ " f" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n x\n c\n " )
6049
) ;
6050
}
6051
6052
6053
6054
# [ test ]
6055
fn a_binary_file_reverted_to_an_earlier_blob_keeps_it ( ) {
6056
hermetic ( ) ;
6057
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6058
let root = dir.path ( ) ;
6059
let repo = Repo:: init( root) .unwrap ( ) ;
6060
write ( root, " bin" , b " \0 A" ) ;
6061
let v1 = repo.commit ( " v1" , & [ ] ) .unwrap ( ) ;
6062
write ( root, " bin" , b " \0 B" ) ;
6063
let v2 = repo.commit ( " v2" , & [ ] ) .unwrap ( ) ;
6064
assert! ( repo.store ( ) .get_change ( & v2) .unwrap ( ) .deps.contains ( & v1) ) ;
6065
write ( root, " bin" , b " \0 A" ) ;
6066
let v3 = repo.commit ( " back to A" , & [ ] ) .unwrap ( ) ;
6067
assert! ( repo.store ( ) .get_change ( & v3) .unwrap ( ) .deps.contains ( & v2) ) ;
6068
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ v3] ) ;
6069
let state = repo.load_state ( & [ v3] ) .unwrap ( ) ;
6070
let snap = state.materialize ( ) ;
6071
let FileContent:: Opaque { blobs } = & snap.files[ " bin" ] .content else {
6072
panic! ( " opaque" )
6073
} ;
6074
assert_eq! ( blobs.len ( ) , 1 , " one live blob, not none and not two" ) ;
6075
assert_eq! ( repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) , b " \0 A" ) ;
6076
}
6077
6078
6079
6080
6081
# [ test ]
6082
fn git_import_resolves_a_binary_merged_in_git_and_verifies ( ) {
6083
hermetic ( ) ;
6084
if std:: process:: Command:: new( " git" )
6085
.arg ( " --version" )
6086
.output ( )
6087
.is_err ( )
6088
{
6089
eprintln! ( " git not installed; skipping" ) ;
6090
return ;
6091
}
6092
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6093
let root = dir.path ( ) ;
6094
let g = root.join ( " src.git" ) ;
6095
fs:: create_dir_all( & g) .unwrap ( ) ;
6096
let git = | args : & [& str ]| -> String {
6097
let out = std:: process:: Command:: new( " git" )
6098
.arg ( " -C" )
6099
.arg ( & g)
6100
.args ( [
6101
" -c" ,
6102
" user.name=Grace" ,
6103
" -c" ,
6104
" [email protected] " ,
6105
" -c" ,
6106
" commit.gpgsign=false" ,
6107
] )
6108
.args ( args)
6109
.output ( )
6110
.unwrap ( ) ;
6111
assert! (
6112
out.status.success ( ) ,
6113
" git {args:?}: {}" ,
6114
String :: from_utf8_lossy( & out.stderr)
6115
) ;
6116
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
6117
} ;
6118
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
6119
write ( & g, " logo.bin" , b " \0 base" ) ;
6120
write ( & g, " notes.txt" , b " a\n b\n c\n " ) ;
6121
git ( & [ " add" , " ." ] ) ;
6122
git ( & [ " commit" , " -qm" , " base" ] ) ;
6123
git ( & [ " checkout" , " -qb" , " side" ] ) ;
6124
write ( & g, " logo.bin" , b " \0 side" ) ;
6125
write ( & g, " notes.txt" , b " a\n b\n x\n c\n " ) ;
6126
git ( & [ " commit" , " -qam" , " side" ] ) ;
6127
git ( & [ " checkout" , " -q" , " main" ] ) ;
6128
write ( & g, " logo.bin" , b " \0 main" ) ;
6129
git ( & [ " commit" , " -qam" , " main" ] ) ;
6130
6131
let _ = std:: process:: Command:: new( " git" )
6132
.arg ( " -C" )
6133
.arg ( & g)
6134
.args ( [
6135
" -c" ,
6136
" user.name=G" ,
6137
" -c" ,
6138
" user.email=g@e" ,
6139
" merge" ,
6140
" -q" ,
6141
" side" ,
6142
] )
6143
.output ( ) ;
6144
write ( & g, " logo.bin" , b " \0 main" ) ;
6145
git ( & [ " add" , " ." ] ) ;
6146
git ( & [ " commit" , " -qm" , " merge side" ] ) ;
6147
6148
write ( & g, " logo.bin" , b " \0 base" ) ;
6149
git ( & [ " commit" , " -qam" , " back to base" ] ) ;
6150
6151
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
6152
let rep = repo.import_git ( & g, " main" ) .unwrap ( ) ;
6153
assert_eq! ( rep.merges, 1 ) ;
6154
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
6155
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
6156
assert_eq! ( v.commits, 5 ) ;
6157
let snap = repo
6158
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
6159
.unwrap ( )
6160
.materialize ( ) ;
6161
assert! ( ! snap.is_conflicted ( ) ) ;
6162
let FileContent:: Opaque { blobs } = & snap.files[ " logo.bin" ] .content else {
6163
panic! ( " opaque" )
6164
} ;
6165
assert_eq! ( repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) , b " \0 base" ) ;
6166
}
c863c7a7 Andy McCall 1 month ago ↶
6167
6168
6169
6170
6171
# [ cfg ( unix ) ]
6172
# [ test ]
6173
fn windows_file_systems_keep_exec_bits_and_symlinks ( ) {
6174
use crate :: portable:: Platform;
6175
use std:: os:: unix:: fs:: PermissionsExt;
6176
hermetic ( ) ;
6177
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6178
let root = dir.path ( ) ;
6179
let repo = Repo:: init( root) .unwrap ( ) ;
6180
write ( root, " run.sh" , b " #!/bin/sh\n " ) ;
6181
fs:: set_permissions( root.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
6182
std:: os:: unix:: fs:: symlink( " run.sh" , root.join ( " link" ) ) .unwrap ( ) ;
6183
repo.commit ( " script and link" , & [ ] ) .unwrap ( ) ;
6184
drop ( repo) ;
6185
6186
6187
fs:: set_permissions( root.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6188
fs:: remove_file( root.join ( " link" ) ) .unwrap ( ) ;
6189
write ( root, " link" , b " run.sh" ) ;
6190
let win = Repo:: open( root) .unwrap ( ) .with_platform ( Platform:: windows( ) ) ;
6191
let s = win.snapshot ( " oxy status" ) .unwrap ( ) ;
6192
assert! (
6193
s.statuses.is_empty ( ) ,
6194
" nothing changed on Windows: {:?}" ,
6195
statuses ( & s)
6196
) ;
6197
6198
write ( root, " run.sh" , b " #!/bin/sh\n echo hi\n " ) ;
6199
write ( root, " link" , b " other" ) ;
6200
let h = win.commit ( " edit on Windows" , & [ ] ) .unwrap ( ) ;
6201
let snap = win.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
6202
assert! ( snap.files[ " run.sh" ] .mode.exec, " the exec bit survives" ) ;
6203
assert_eq! ( snap.files[ " link" ] .kind, NodeKind:: Symlink) ;
6204
6205
let linux = Repo:: open( root) .unwrap ( ) ;
6206
assert! ( ! linux.snapshot ( " oxy status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6207
}
6208
6209
6210
6211
# [ test ]
6212
fn paths_other_platforms_cannot_hold_are_reported ( ) {
6213
use crate :: portable:: Platform;
6214
hermetic ( ) ;
6215
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6216
let root = dir.path ( ) ;
6217
let repo = Repo:: init( root) .unwrap ( ) ;
6218
write ( root, " README.md" , b " a\n " ) ;
6219
write ( root, " readme.md" , b " b\n " ) ;
6220
write ( root, " src/aux.c" , b " int x;\n " ) ;
6221
let h = repo.commit ( " unportable" , & [ ] ) .unwrap ( ) ;
6222
let r = repo.doctor ( false ) ;
6223
let port = r.checks.iter ( ) .find ( | c | c.area = = " portability" ) .unwrap ( ) ;
6224
assert_eq! ( port.health, Health:: Warn) ;
6225
assert! (
6226
port.message.contains ( " differ only in case" ) & & port.message.contains ( " aux.c" ) ,
6227
" {}" ,
6228
port.message
6229
) ;
6230
let mac = Repo:: open( root) .unwrap ( ) .with_platform ( Platform:: macos( ) ) ;
6231
let rep = mac.checkout ( & [ h] , true , " oxy checkout" ) .unwrap ( ) ;
6232
assert! (
6233
rep.notes
6234
.iter ( )
6235
.any ( | n | n.contains ( " README.md and readme.md" ) ) ,
6236
" {:?}" ,
6237
rep.notes
6238
) ;
6239
assert! (
6240
! rep.notes.iter ( ) .any ( | n | n.contains ( " aux.c" ) ) ,
6241
" macOS allows aux.c"
6242
) ;
6243
}
bc489b6c Andy McCall 1 month ago ↶
6244
6245
# [ test ]
6246
fn history_and_blame_of_a_loaded_state_match_the_full_walk ( ) {
6247
hermetic ( ) ;
6248
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6249
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
6250
write ( dir.path ( ) , " a.txt" , b " one\n " ) ;
6251
write ( dir.path ( ) , " other.txt" , b " x\n " ) ;
6252
write ( dir.path ( ) , " bin.dat" , & [ 0 u8 , 1 , 2 , 0 , 255 ] ) ;
6253
repo.snapshot ( " t" ) .unwrap ( ) ;
6254
let c1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
6255
write ( dir.path ( ) , " other.txt" , b " y\n " ) ;
6256
repo.snapshot ( " t" ) .unwrap ( ) ;
6257
repo.commit ( " other only" , & [ ] ) .unwrap ( ) ;
6258
write ( dir.path ( ) , " a.txt" , b " one\n two\n " ) ;
6259
repo.snapshot ( " t" ) .unwrap ( ) ;
6260
let c3 = repo.commit ( " second line" , & [ ] ) .unwrap ( ) ;
6261
write ( dir.path ( ) , " other.txt" , b " z\n " ) ;
6262
repo.snapshot ( " t" ) .unwrap ( ) ;
6263
repo.commit ( " other again" , & [ ] ) .unwrap ( ) ;
6264
6265
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6266
let state = repo.load_state ( & heads) .unwrap ( ) ;
6267
let full: Vec< ChangeHash> = repo
6268
.log_for_path_in ( " a.txt" , & heads)
6269
.unwrap ( )
6270
.iter ( )
6271
.map ( | e | e.hash )
6272
.collect ( ) ;
6273
assert_eq! (
6274
full,
6275
vec! [ c3, c1] ,
6276
" newest first, only changes touching the path"
6277
) ;
6278
let newest: Vec< ChangeHash> = repo
6279
.log_for_path_of ( & state, " a.txt" , Some ( 1 ) )
6280
.unwrap ( )
6281
.iter ( )
6282
.map ( | e | e.hash )
6283
.collect ( ) ;
6284
assert_eq! ( newest, vec! [ c3] , " stops at the first" ) ;
6285
assert_eq! (
6286
repo.log_for_path_of ( & state, " a.txt" , None ) .unwrap ( ) .len ( ) ,
6287
2
6288
) ;
6289
assert! ( repo
6290
.log_for_path_of ( & state, " missing.txt" , Some ( 1 ) )
6291
.is_err ( ) ) ;
6292
assert_eq! (
6293
repo.log_state ( & heads) .unwrap ( ) .len ( ) ,
6294
repo.log_of ( & state) .len ( )
6295
) ;
6296
6297
let tree = state.tree ( ) .view ( ) ;
6298
let via_state = repo.blame_of ( & state, & tree, " a.txt" ) .unwrap ( ) ;
6299
let via_heads = repo.blame_in ( " a.txt" , & heads) .unwrap ( ) ;
6300
assert_eq! (
6301
via_state
6302
.iter ( )
6303
.map ( | l | ( l.hash, l.text.clone ( ) ) )
6304
.collect:: < Vec< _ > > ( ) ,
6305
via_heads
6306
.iter ( )
6307
.map ( | l | ( l.hash, l.text.clone ( ) ) )
6308
.collect:: < Vec< _ > > ( )
6309
) ;
6310
assert_eq! (
6311
via_state.iter ( ) .map ( | l | l.hash ) .collect:: < Vec< _ > > ( ) ,
6312
vec! [ c1, c3]
6313
) ;
6314
assert! ( repo.blame_of ( & state, & tree, " missing.txt" ) .is_err ( ) ) ;
6315
}
696bbf29 Andy McCall 1 month ago ↶
6316
6317
6318
6319
6320
# [ cfg ( unix ) ]
6321
# [ test ]
6322
fn stat_cache_takes_unchanged_text_from_the_base_and_misses_no_edit ( ) {
6323
use std:: os:: unix:: fs:: PermissionsExt;
6324
hermetic ( ) ;
6325
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6326
let root = dir.path ( ) ;
6327
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6328
write ( root, " a.txt" , b " one\n two\n " ) ;
6329
write ( root, " b.txt" , b " bee\n " ) ;
6330
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6331
let age = | p : & str | {
6332
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6333
fs:: File:: options( )
6334
.write ( true )
6335
.open ( root.join ( p) )
6336
.unwrap ( )
6337
.set_modified ( old)
6338
.unwrap ( ) ;
6339
} ;
6340
age ( " a.txt" ) ;
6341
age ( " b.txt" ) ;
6342
assert! (
6343
! repo.snapshot ( " status" ) .unwrap ( ) .changed,
6344
" records the aged stat"
6345
) ;
6346
6347
6348
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6349
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
6350
assert! (
6351
! snap.changed & & snap.statuses.is_empty ( ) ,
6352
" {:?}" ,
6353
snap.statuses
6354
) ;
6355
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6356
6357
6358
6359
write ( root, " b.txt" , b " bee edited\n " ) ;
6360
age ( " b.txt" ) ;
6361
let first = repo.snapshot ( " status" ) .unwrap ( ) ;
6362
assert_eq! (
6363
statuses ( & first) ,
6364
vec! [ ( " b.txt" .to_string ( ) , ChangeStatus:: Modified) ]
6365
) ;
6366
let again = repo.snapshot ( " status" ) .unwrap ( ) ;
6367
assert_eq! (
6368
statuses ( & again) ,
6369
vec! [ ( " b.txt" .to_string ( ) , ChangeStatus:: Modified) ] ,
6370
" still seen once stat-clean"
6371
) ;
6372
assert! ( ! again.changed, " and the working copy hashes the same" ) ;
6373
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6374
assert! (
6375
repo.snapshot ( " status" ) .is_err ( ) ,
6376
" an edited file must be read, never taken from the base"
6377
) ;
6378
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6379
6380
6381
write ( root, " a.txt" , b " one\n TWO\n " ) ;
6382
assert_eq! (
6383
statuses ( & repo.snapshot ( " status" ) .unwrap ( ) ) ,
6384
vec! [
6385
( " a.txt" .to_string ( ) , ChangeStatus:: Modified) ,
6386
( " b.txt" .to_string ( ) , ChangeStatus:: Modified)
6387
]
6388
) ;
6389
6390
repo.commit ( " edits" , & [ ] ) .unwrap ( ) ;
6391
age ( " a.txt" ) ;
6392
age ( " b.txt" ) ;
6393
assert! ( repo.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6394
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6395
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6396
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
6397
assert! ( ! snap.changed & & snap.statuses.is_empty ( ) ) ;
6398
fs:: set_permissions( root.join ( " a.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6399
fs:: set_permissions( root.join ( " b.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6400
}
afaba5f1 Andy McCall 1 month ago ↶
6401
6402
6403
6404
6405
# [ test ]
6406
fn checkpoints_keep_large_graphs_as_blobs_of_their_own ( ) {
6407
hermetic ( ) ;
6408
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6409
let root = dir.path ( ) ;
6410
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6411
let long: String = ( 0 .. 300 )
6412
.map ( | i | format! ( " line {i} of a long file\n " ) )
6413
.collect ( ) ;
6414
write ( root, " long.txt" , long.as_bytes ( ) ) ;
6415
write ( root, " short.txt" , b " short\n " ) ;
6416
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6417
let ( r1, _ , _ ) = repo.checkpoint ( ) .unwrap ( ) ;
6418
let cp1 = crate :: objects:: Checkpoint:: decode(
6419
& repo.store ( ) .get_typed ( & r1, ObjectType:: Checkpoint) .unwrap ( ) ,
6420
)
6421
.unwrap ( ) ;
6422
let graphs1 = repo.checkpoint_graph_blobs ( & cp1) .unwrap ( ) ;
6423
assert_eq! ( graphs1.len ( ) , 1 , " only the long file's graph is a blob" ) ;
6424
6425
6426
repo.gc ( true ) .unwrap ( ) ;
6427
assert! ( repo.store ( ) .has ( & graphs1[ 0 ] ) ) ;
6428
let report = repo.fsck ( ) .unwrap ( ) ;
6429
assert_eq! ( report.errors ( ) , 0 , " {:?}" , report.problems) ;
6430
6431
6432
write ( root, " short.txt" , b " short, edited\n " ) ;
6433
repo.commit ( " edit the short file" , & [ ] ) .unwrap ( ) ;
6434
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6435
let fast = repo.load_state ( & heads) .unwrap ( ) ;
6436
let full = repo.load_state_full ( & heads) .unwrap ( ) ;
6437
assert_eq! ( fast.checkpoint_payload ( ) , full.checkpoint_payload ( ) ) ;
6438
6439
6440
let ( r2, _ , _ ) = repo.checkpoint ( ) .unwrap ( ) ;
6441
let cp2 = crate :: objects:: Checkpoint:: decode(
6442
& repo.store ( ) .get_typed ( & r2, ObjectType:: Checkpoint) .unwrap ( ) ,
6443
)
6444
.unwrap ( ) ;
6445
assert_eq! ( repo.checkpoint_graph_blobs ( & cp2) .unwrap ( ) , graphs1) ;
6446
6447
6448
let bundle = repo.checkpoint_bundle_for ( & heads) .unwrap ( ) ;
6449
assert! ( bundle.objects.iter ( ) .any ( | ( t , body ) | * t = = ObjectType:: Blob
6450
& & crate :: hash:: hash_object( ObjectType:: Blob, body) = = graphs1[ 0 ] ) ) ;
6451
6452
6453
let state = repo.load_state_full ( & heads) .unwrap ( ) ;
6454
let old = repo.store_blob ( & state.checkpoint_payload ( ) ) .unwrap ( ) ;
6455
let cp_old = crate :: objects:: Checkpoint {
6456
heads: cp2.heads.clone ( ) ,
6457
count: cp2.count,
6458
payload: old,
6459
} ;
6460
let r_old = repo
6461
.store ( )
6462
.put ( ObjectType:: Checkpoint, & cp_old.encode ( ) )
6463
.unwrap ( ) ;
6464
repo.install_checkpoint ( " main" , & r_old) .unwrap ( ) ;
6465
assert_eq! (
6466
repo.load_state ( & heads) .unwrap ( ) .checkpoint_payload ( ) ,
6467
full.checkpoint_payload ( )
6468
) ;
6469
assert_eq! ( repo.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
6470
6471
6472
6473
let dir2 = tempfile:: tempdir( ) .unwrap ( ) ;
6474
let root2 = dir2.path ( ) ;
6475
let repo2 = Repo:: init( root2) .unwrap ( ) .with_home ( root2.join ( " home" ) ) ;
6476
write ( root2, " long.txt" , long.as_bytes ( ) ) ;
6477
repo2.commit ( " base" , & [ ] ) .unwrap ( ) ;
6478
let ( r, _ , _ ) = repo2.checkpoint ( ) .unwrap ( ) ;
6479
let cp = crate :: objects:: Checkpoint:: decode(
6480
& repo2.store ( ) .get_typed ( & r, ObjectType:: Checkpoint) .unwrap ( ) ,
6481
)
6482
.unwrap ( ) ;
6483
let graph = repo2.checkpoint_graph_blobs ( & cp) .unwrap ( ) [ 0 ] ;
64818938 Andy McCall 1 month ago ↶
6484
repo2.store ( ) .remove_loose_for_test ( & graph) .unwrap ( ) ;
afaba5f1 Andy McCall 1 month ago ↶
6485
assert! ( repo2.load_state ( & repo2.bookmark_heads ( ) .unwrap ( ) ) .is_err ( ) ) ;
6486
assert! ( repo2.fsck ( ) .unwrap ( ) .errors ( ) > 0 , " fsck reports it" ) ;
6487
}
e3d071ce Andy McCall 1 month ago ↶
6488
6489
6490
6491
6492
# [ test ]
6493
fn identity_is_found_in_the_key_directory_for_a_new_repository ( ) {
6494
hermetic ( ) ;
6495
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6496
let home = dir.path ( ) .join ( " home" ) ;
6497
let mut first = Repo:: init( & dir.path ( ) .join ( " a" ) )
6498
.unwrap ( )
6499
.with_home ( home.clone ( ) ) ;
6500
let ( key, _ ) = first
6501
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
6502
.unwrap ( ) ;
6503
let fresh = Repo:: init( & dir.path ( ) .join ( " b" ) )
6504
.unwrap ( )
6505
.with_home ( home.clone ( ) ) ;
6506
assert! ( ! fresh.store ( ) .has ( & key) , " nothing stored yet" ) ;
6507
let id = fresh.identity ( & key) .unwrap ( ) ;
6508
assert_eq! ( ( id.name.as_str ( ) , id.hash ( ) ) , ( " Ada" , key) ) ;
6509
6510
let keys = fresh.keys_dir.clone ( ) ;
6511
let ( other, _ ) = first
6512
.create_identity ( " Bob" , " [email protected] " , IdentityScope:: User)
6513
.unwrap ( ) ;
6514
fs:: copy(
6515
keys.join ( format! ( " {} .identity" , other.to_hex ( ) ) ) ,
6516
keys.join ( format! ( " {} .identity" , key.to_hex ( ) ) ) ,
6517
)
6518
.unwrap ( ) ;
6519
assert! ( fresh
6520
.identity ( & key)
6521
.unwrap_err ( )
6522
.to_string ( )
6523
.contains ( " does not hold identity" ) ) ;
6524
}
cc7a0056 Andy McCall 1 month ago ↶
6525
6526
6527
6528
6529
6530
# [ cfg ( unix ) ]
6531
# [ test ]
6532
fn status_snapshots_skip_files_unchanged_since_the_manifest ( ) {
6533
use std:: os:: unix:: fs:: PermissionsExt;
6534
hermetic ( ) ;
6535
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6536
let root = dir.path ( ) ;
6537
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6538
for ( p, c) in [ ( " a.txt" , " one\n " ) , ( " b.txt" , " bee\n " ) , ( " d/c.txt" , " sea\n " ) ] {
6539
write ( root, p, c.as_bytes ( ) ) ;
6540
}
6541
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6542
let age = | p : & str | {
6543
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6544
fs:: File:: options( )
6545
.write ( true )
6546
.open ( root.join ( p) )
6547
.unwrap ( )
6548
.set_modified ( old)
6549
.unwrap ( ) ;
6550
} ;
6551
let lock = | p : & str , mode : u32 | {
6552
fs:: set_permissions( root.join ( p) , fs:: Permissions:: from_mode( mode) ) .unwrap ( )
6553
} ;
6554
for p in [ " a.txt" , " b.txt" , " d/c.txt" ] {
6555
age ( p) ;
6556
}
6557
assert! ( repo.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6558
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
6559
assert! (
6560
m.entries.values ( ) .all ( | e | e.graph.is_some ( ) ) ,
6561
" keys recorded"
6562
) ;
6563
6564
6565
6566
lock ( " a.txt" , 0o000 ) ;
6567
lock ( " b.txt" , 0o000 ) ;
6568
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6569
assert! ( ! snap.changed & & snap.statuses.is_empty ( ) ) ;
6570
assert_eq! (
6571
snap.base_snapshot
6572
.unrendered
6573
.iter ( )
6574
.cloned ( )
6575
.collect:: < Vec< _ > > ( ) ,
6576
vec! [ " a.txt" , " b.txt" , " d/c.txt" ]
6577
) ;
6578
assert! ( ! snap.base.is_full ( ) ) ;
6579
assert! ( ! snap.base_snapshot.is_conflicted ( ) ) ;
6580
6581
let full = repo.snapshot ( " status" ) .unwrap ( ) ;
6582
assert! ( full.statuses.is_empty ( ) & & full.base.is_full ( ) ) ;
6583
assert! ( full.base_snapshot.unrendered.is_empty ( ) ) ;
6584
assert! ( matches! ( & full.base_snapshot.files[ " a.txt" ] .content,
6585
FileContent:: Text { bytes, .. } if bytes = = b " one\n " ) ) ;
6586
6587
6588
write ( root, " d/c.txt" , b " sea edited\n " ) ;
6589
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6590
assert_eq! (
6591
statuses ( & snap) ,
6592
vec! [ ( " d/c.txt" .to_string ( ) , ChangeStatus:: Modified) ]
6593
) ;
6594
assert! ( ! snap.base_snapshot.unrendered.contains ( " d/c.txt" ) ) ;
6595
6596
lock ( " b.txt" , 0o100 ) ;
6597
assert_eq! (
6598
statuses ( & repo.snapshot_status ( " status" ) .unwrap ( ) ) ,
6599
vec! [
6600
( " b.txt" .to_string ( ) , ChangeStatus:: ModeChanged) ,
6601
( " d/c.txt" .to_string ( ) , ChangeStatus:: Modified)
6602
]
6603
) ;
6604
lock ( " b.txt" , 0o000 ) ;
6605
6606
6607
6608
repo.commit_selection ( " edit c" , & [ ] , & Selection:: Paths( vec! [ " d/c.txt" .into ( ) ] ) )
6609
.unwrap ( ) ;
6610
age ( " d/c.txt" ) ;
6611
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6612
assert! ( snap.statuses.is_empty ( ) , " {:?}" , snap.statuses) ;
6613
assert! ( snap.base_snapshot.unrendered.contains ( " a.txt" ) ) ;
6614
lock ( " d/c.txt" , 0o000 ) ;
6615
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6616
assert! ( snap.statuses.is_empty ( ) ) ;
6617
assert! ( snap.base_snapshot.unrendered.contains ( " d/c.txt" ) ) ;
6618
for p in [ " a.txt" , " b.txt" , " d/c.txt" ] {
6619
lock ( p, 0o644 ) ;
6620
}
6621
}
6622
6623
6624
6625
# [ cfg ( unix ) ]
6626
# [ test ]
6627
fn a_changed_attributes_file_disables_skipping_unchanged_files ( ) {
6628
hermetic ( ) ;
6629
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6630
let root = dir.path ( ) ;
6631
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6632
write ( root, " a.txt" , b " crlf\r \n " ) ;
6633
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6634
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6635
fs:: File:: options( )
6636
.write ( true )
6637
.open ( root.join ( " a.txt" ) )
6638
.unwrap ( )
6639
.set_modified ( old)
6640
.unwrap ( ) ;
6641
assert! ( repo.snapshot_status ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6642
assert! ( repo
6643
.snapshot_status ( " status" )
6644
.unwrap ( )
6645
.base_snapshot
6646
.unrendered
6647
.contains ( " a.txt" ) ) ;
6648
6649
write ( root, " .oxyattributes" , b " *.txt eol=lf\n " ) ;
6650
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
6651
assert_eq! (
6652
statuses ( & snap) ,
6653
vec! [
6654
( " .oxyattributes" .to_string ( ) , ChangeStatus:: Added) ,
6655
( " a.txt" .to_string ( ) , ChangeStatus:: Modified)
6656
]
6657
) ;
6658
}
54cd08a1 Andy McCall 1 month ago ↶
6659
6660
6661
6662
6663
# [ test ]
6664
fn bookmarks_with_slashes_push_and_checkpoint ( ) {
6665
hermetic ( ) ;
6666
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6667
let root = dir.path ( ) ;
6668
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
6669
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
6670
let mut repo = Repo:: clone( & url, & root.join ( " r" ) )
6671
.unwrap ( )
6672
.with_home ( root.join ( " home" ) ) ;
6673
6674
6675
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
6676
.unwrap ( ) ;
6677
let r = root.join ( " r" ) ;
6678
write ( & r, " a.txt" , b " a\n " ) ;
6679
let h_a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
6680
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
6681
origin.refresh ( ) .unwrap ( ) ;
54cd08a1 Andy McCall 1 month ago ↶
6682
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
6683
repo.bookmark_create ( " feature/x" , None ) .unwrap ( ) ;
6684
repo.bookmark_switch ( " feature/x" , false ) .unwrap ( ) ;
6685
write ( & r, " b.txt" , b " b\n " ) ;
6686
let h_b = repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
6687
for name in [ " feature" , " feature/x" ] {
6688
let res = repo.push_bookmark ( " origin" , name, false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
6689
origin.refresh ( ) .unwrap ( ) ;
54cd08a1 Andy McCall 1 month ago ↶
6690
assert! ( res[ 0 ] .ok, " {name}: {res:?}" ) ;
6691
}
6692
assert_eq! ( origin.resolve_heads ( " feature" ) .unwrap ( ) , vec! [ h_a] ) ;
6693
assert_eq! (
6694
origin.resolve_heads ( " feature/x" ) .unwrap ( ) ,
6695
repo.resolve_heads ( " feature/x" ) .unwrap ( )
6696
) ;
6697
assert! ( origin.resolve_heads ( " feature/x" ) .unwrap ( ) .contains ( & h_b) ) ;
6698
assert! ( origin.latest_ref_attest ( " feature/x" ) .unwrap ( ) .is_some ( ) ) ;
6699
assert! ( origin.latest_ref_attest ( " feature" ) .unwrap ( ) .is_some ( ) ) ;
6700
6701
6702
let ( cp, _ , _ ) = repo.checkpoint ( ) .unwrap ( ) ;
6703
assert_eq! ( repo.latest_checkpoint ( " feature/x" ) .unwrap ( ) .unwrap ( ) .0 , cp) ;
6704
assert! ( repo.latest_checkpoint ( " feature" ) .unwrap ( ) .is_none ( ) ) ;
6705
let heads = repo.resolve_heads ( " feature/x" ) .unwrap ( ) ;
6706
assert! ( repo.load_state_checkpointed ( & heads) .unwrap ( ) .is_some ( ) ) ;
f8c69576 Andy McCall 1 month ago ↶
6707
}
6708
6709
6710
6711
6712
# [ test ]
6713
fn bookmarks_are_deleted_on_a_remote ( ) {
6714
hermetic ( ) ;
6715
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6716
let root = dir.path ( ) ;
6717
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
6718
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
6719
let mut repo = Repo:: clone( & url, & root.join ( " r" ) )
6720
.unwrap ( )
6721
.with_home ( root.join ( " home" ) ) ;
6722
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo)
6723
.unwrap ( ) ;
6724
let r = root.join ( " r" ) ;
6725
write ( & r, " a.txt" , b " a\n " ) ;
6726
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
6727
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
6728
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6729
repo.bookmark_create ( " feature/x" , None ) .unwrap ( ) ;
6730
assert! ( repo.push_bookmark ( " origin" , " feature/x" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
6731
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6732
assert! ( origin.resolve_heads ( " feature/x" ) .is_ok ( ) ) ;
6733
6734
let res = repo.push_delete ( " origin" , " feature/x" ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
6735
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6736
assert! ( res[ 0 ] .ok, " {res:?}" ) ;
6737
assert! ( ! origin.view ( ) .unwrap ( ) .bookmarks.contains_key ( " feature/x" ) ) ;
6738
let ( _ , att) = origin.latest_ref_attest ( " feature/x" ) .unwrap ( ) .unwrap ( ) ;
6739
assert! (
6740
matches! ( att.attest.body, AttestBody:: Ref { new_state, .. } if new_state.is_zero ( ) ) ,
6741
" the deletion is attested"
6742
) ;
6743
assert! (
6744
repo.view ( ) .unwrap ( ) .bookmarks.contains_key ( " feature/x" ) ,
6745
" the local bookmark stays"
6746
) ;
6747
assert! ( ! repo
6748
.view ( )
6749
.unwrap ( )
6750
.remotes
6751
.get ( " origin" )
6752
.is_some_and ( | m | m.contains_key ( " feature/x" ) ) ) ;
6753
6754
6755
let res = repo.push_bookmark ( " origin" , " feature/x" , false ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
6756
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6757
assert! ( res[ 0 ] .ok, " {res:?}" ) ;
6758
assert! ( origin.view ( ) .unwrap ( ) .bookmarks.contains_key ( " feature/x" ) ) ;
6759
6760
6761
assert! ( repo.push_delete ( " origin" , " nope" ) .is_err ( ) ) ;
6762
let res = repo.push_delete ( " origin" , " main" ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
6763
origin.refresh ( ) .unwrap ( ) ;
f8c69576 Andy McCall 1 month ago ↶
6764
assert! (
6765
! res[ 0 ] .ok & & res[ 0 ] .reason.contains ( " default bookmark" ) ,
6766
" {res:?}"
6767
) ;
6768
assert! ( origin.view ( ) .unwrap ( ) .bookmarks.contains_key ( " main" ) ) ;
54cd08a1 Andy McCall 1 month ago ↶
6769
}
61e3bce7 Andy McCall 1 month ago ↶
6770
6771
6772
6773
6774
# [ test ]
6775
fn switching_bookmarks_carries_uncommitted_work ( ) {
6776
hermetic ( ) ;
6777
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6778
let root = dir.path ( ) ;
6779
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6780
write ( root, " a.txt" , b " a\n " ) ;
6781
write ( root, " b.txt" , b " b\n " ) ;
6782
write ( root, " gone.txt" , b " gone\n " ) ;
6783
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6784
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
6785
6786
write ( root, " a.txt" , b " a\n more\n " ) ;
6787
write ( root, " new.txt" , b " new\n " ) ;
6788
fs:: remove_file( root.join ( " gone.txt" ) ) .unwrap ( ) ;
44f1ebe7 Andy McCall 18 days ago ↶
6789
let ( report, carried) = repo.bookmark_switch_carry ( " feature" ) .unwrap ( ) ;
61e3bce7 Andy McCall 1 month ago ↶
6790
assert_eq! ( carried, vec! [ " a.txt" , " gone.txt" , " new.txt" ] ) ;
44f1ebe7 Andy McCall 18 days ago ↶
6791
6792
assert_eq! (
6793
( report.removed, report.removed_paths.len ( ) ) ,
6794
( 0 , 0 ) ,
6795
" {report:?}"
6796
) ;
61e3bce7 Andy McCall 1 month ago ↶
6797
assert_eq! ( repo.config.bookmark, " feature" ) ;
6798
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " a\n more\n " ) ;
6799
assert_eq! ( fs:: read( root.join ( " new.txt" ) ) .unwrap ( ) , b " new\n " ) ;
6800
assert! ( ! root.join ( " gone.txt" ) .exists ( ) ) ;
6801
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
6802
assert_eq! (
6803
statuses ( & snap) ,
6804
vec! [
6805
( " a.txt" .to_string ( ) , ChangeStatus:: Modified) ,
6806
( " gone.txt" .to_string ( ) , ChangeStatus:: Removed) ,
6807
( " new.txt" .to_string ( ) , ChangeStatus:: Added)
6808
]
6809
) ;
6810
repo.commit ( " the work" , & [ ] ) .unwrap ( ) ;
6811
assert! ( repo.resolve_heads ( " main" ) .unwrap ( ) .len ( ) = = 1 ) ;
6812
6813
6814
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
6815
write ( root, " b.txt" , b " b on main\n " ) ;
6816
repo.commit ( " b on main" , & [ ] ) .unwrap ( ) ;
6817
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
6818
write ( root, " b.txt" , b " b edited\n " ) ;
6819
let err = repo.bookmark_switch_carry ( " main" ) .unwrap_err ( ) .to_string ( ) ;
6820
assert! ( err.contains ( " b.txt" ) & & err.contains ( " --discard" ) , " {err}" ) ;
6821
assert_eq! ( repo.config.bookmark, " feature" , " nothing happened" ) ;
6822
assert_eq! ( fs:: read( root.join ( " b.txt" ) ) .unwrap ( ) , b " b edited\n " ) ;
6823
}
1c2bd9ab Andy McCall 1 month ago ↶
6824
6825
6826
6827
6828
# [ test ]
6829
fn merges_move_clean_working_copies_and_checkouts_say_what_blocks_them ( ) {
6830
hermetic ( ) ;
6831
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6832
let root = dir.path ( ) ;
6833
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6834
write ( root, " a.txt" , b " a\n " ) ;
6835
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
6836
write ( root, " b.txt" , b " b\n " ) ;
6837
repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
6838
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
6839
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
6840
write ( root, " c.txt" , b " c\n " ) ;
6841
repo.commit ( " c on feature" , & [ ] ) .unwrap ( ) ;
6842
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
6843
6844
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
6845
assert! ( ! root.join ( " b.txt" ) .exists ( ) ) ;
6846
let m = repo.merge ( " feature" ) .unwrap ( ) ;
6847
assert! ( m.checked_out, " a clean working copy follows the merge" ) ;
6848
for f in [ " a.txt" , " b.txt" , " c.txt" ] {
6849
assert! ( root.join ( f) .exists ( ) , " {f}" ) ;
6850
}
6851
6852
6853
fs:: remove_file( root.join ( " b.txt" ) ) .unwrap ( ) ;
6854
write ( root, " a.txt" , b " a edited\n " ) ;
6855
let err = repo
6856
.checkout ( & [ h1] , false , " checkout" )
6857
.unwrap_err ( )
6858
.to_string ( ) ;
6859
assert! (
6860
err.contains ( " modified a.txt" ) & & err.contains ( " removed b.txt" ) & & err.contains ( " --carry" ) ,
6861
" {err}"
6862
) ;
fe9586d9 Andy McCall 25 days ago ↶
6863
6864
6865
1c2bd9ab Andy McCall 1 month ago ↶
6866
let ( _ , carried) = repo
6867
.checkout_carry ( & [ h1] , " the first change" , " checkout" )
6868
.unwrap ( ) ;
6869
assert_eq! ( carried, vec! [ " a.txt" ] ) ;
6870
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " a edited\n " ) ;
6871
assert! ( ! root.join ( " b.txt" ) .exists ( ) , " h1 has no b.txt" ) ;
6872
}
e7ac6c2e Andy McCall 1 month ago ↶
6873
6874
6875
6876
6877
# [ test ]
6878
fn a_new_repository_is_healthy_before_its_first_commit ( ) {
6879
hermetic ( ) ;
6880
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6881
let root = dir.path ( ) ;
6882
let home = root.join ( " home" ) ;
6883
let mut first = Repo:: init( & root.join ( " first" ) )
6884
.unwrap ( )
6885
.with_home ( home.clone ( ) ) ;
6886
let ( key, _ ) = first
6887
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
6888
.unwrap ( ) ;
6889
let repo = Repo:: init( & root.join ( " second" ) )
6890
.unwrap ( )
6891
.with_home ( home.clone ( ) ) ;
6892
6893
let keys = repo.keys_dir.clone ( ) ;
6894
let _ = fs:: remove_file( keys.join ( format! ( " {} .identity" , key.to_hex ( ) ) ) ) ;
6895
assert! ( keys.join ( format! ( " {} .secret" , key.to_hex ( ) ) ) .is_file ( ) ) ;
6896
let r = repo.doctor ( true ) ;
6897
let id = r.checks.iter ( ) .find ( | c | c.area = = " identity" ) .unwrap ( ) ;
6898
assert_eq! ( id.health, Health:: Ok, " {}" , id.message) ;
6899
assert! ( id.message.contains ( " first signed change" ) , " {}" , id.message) ;
6900
6901
6902
let lib = root.join ( " lib" ) ;
6903
let l = Repo:: init( & lib) .unwrap ( ) .with_home ( home.clone ( ) ) ;
6904
write ( & lib, " x.txt" , b " x\n " ) ;
6905
l.commit ( " x" , & [ ] ) .unwrap ( ) ;
6906
let second = root.join ( " second" ) ;
6907
write ( & second, " a.txt" , b " a\n " ) ;
6908
repo.submodule_add ( & lib.to_string_lossy ( ) , " deps/lib" , None )
6909
.unwrap ( ) ;
6910
let subs = repo.subrepos ( ) .unwrap ( ) ;
6911
assert_eq! ( subs.len ( ) , 1 ) ;
6912
assert_eq! (
6913
( subs[ 0 ] .path.as_str ( ) , subs[ 0 ] .committed) ,
6914
( " deps/lib" , false )
6915
) ;
6916
assert_eq! ( subs[ 0 ] .actual.as_ref ( ) , Some ( & subs[ 0 ] .pin) ) ;
6917
repo.commit ( " add lib" , & [ ] ) .unwrap ( ) ;
6918
let subs = repo.subrepos ( ) .unwrap ( ) ;
6919
assert! ( subs[ 0 ] .committed, " committed now" ) ;
6920
}
8ab265dc Andy McCall 1 month ago ↶
6921
6922
6923
6924
6925
# [ cfg ( unix ) ]
6926
# [ test ]
6927
fn commits_and_diffs_leave_unchanged_files_alone ( ) {
6928
use std:: os:: unix:: fs:: PermissionsExt;
6929
hermetic ( ) ;
6930
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6931
let root = dir.path ( ) ;
6932
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6933
write ( root, " big.txt" , b " lots\n of\n lines\n " ) ;
6934
write ( root, " a.txt" , b " one\n two\n " ) ;
6935
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
6936
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
6937
for f in [ " big.txt" , " a.txt" ] {
6938
fs:: File:: options( )
6939
.write ( true )
6940
.open ( root.join ( f) )
6941
.unwrap ( )
6942
.set_modified ( old)
6943
.unwrap ( ) ;
6944
}
6945
assert! ( repo.snapshot_status ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
6946
6947
fs:: set_permissions( root.join ( " big.txt" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
6948
6949
write ( root, " a.txt" , b " one\n TWO\n three\n " ) ;
6950
let diff = repo.diff_files ( None ) .unwrap ( ) ;
6951
assert_eq! (
6952
diff.iter ( ) .map ( | f | f.path.as_str ( ) ) .collect:: < Vec< _ > > ( ) ,
6953
[ " a.txt" ]
6954
) ;
6955
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
6956
assert! ( ! hunks.is_empty ( ) & & hunks.iter ( ) .all ( | h | h.path = = " a.txt" ) ) ;
6957
let h = repo.commit ( " edit a" , & [ ] ) .unwrap ( ) ;
6958
6959
6960
let c = repo.store ( ) .get_change ( & h) .unwrap ( ) ;
6961
let full = repo.load_state ( & c.deps) .unwrap ( ) ;
6962
let want = render_change ( & c, & full, & full.materialize ( ) ) ;
6963
assert_eq! ( repo.describe_change ( & h) .unwrap ( ) , want) ;
6964
let ( base, snap) = repo.change_base ( & c) .unwrap ( ) ;
6965
assert! ( ! base.is_full ( ) & & snap.unrendered.contains ( " big.txt" ) ) ;
6966
6967
6968
write ( root, " a.txt" , b " scratch\n " ) ;
6969
assert_eq! ( repo.discard ( & [ " a.txt" .into ( ) ] ) .unwrap ( ) , 1 ) ;
6970
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n TWO\n three\n " ) ;
6971
fs:: set_permissions( root.join ( " big.txt" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
6972
}
f7510b63 Andy McCall 1 month ago ↶
6973
6974
6975
6976
6977
# [ test ]
6978
fn replace_conflicts_are_settled_when_asked ( ) {
6979
hermetic ( ) ;
6980
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
6981
let root = dir.path ( ) ;
6982
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
6983
let blob = | n : u8 | {
6984
let mut v = vec! [ 0 u8 ; 64 ] ;
6985
v[ 1 ] = n;
6986
v
6987
} ;
6988
write ( root, " img.bin" , & blob ( 1 ) ) ;
6989
repo.commit ( " add img" , & [ ] ) .unwrap ( ) ;
6990
repo.bookmark_create ( " other" , None ) .unwrap ( ) ;
6991
write ( root, " img.bin" , & blob ( 2 ) ) ;
6992
repo.commit ( " img two" , & [ ] ) .unwrap ( ) ;
6993
repo.bookmark_switch ( " other" , false ) .unwrap ( ) ;
6994
write ( root, " img.bin" , & blob ( 3 ) ) ;
6995
repo.commit ( " img three" , & [ ] ) .unwrap ( ) ;
6996
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
6997
repo.merge ( " other" ) .unwrap ( ) ;
6998
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
6999
let merged = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
7000
assert! (
7001
matches! ( & merged.files[ " img.bin" ] .content, FileContent:: Opaque { blobs } if blobs.len ( ) = = 2 ) ,
7002
" a replace conflict"
7003
) ;
7004
7005
assert! ( repo
7006
.snapshot ( " status" )
7007
.unwrap ( )
7008
.statuses
7009
.iter ( )
7010
.all ( | s | s.path ! = " img.bin" ) ) ;
7011
7012
7013
let author = repo.author ( ) ;
7014
let edits = [ FileEdit {
7015
path: " img.bin" .into ( ) ,
7016
content: Some ( blob ( 3 ) ) ,
7017
} ] ;
7018
let c = repo
7019
.build_change ( & heads, & edits, " keep three" , author)
7020
.unwrap ( ) ;
7021
assert_eq! ( c.kind, ChangeKind:: Resolution) ;
7022
let mut st = repo.load_state ( & heads) .unwrap ( ) ;
7023
for d in & c.deps {
7024
assert! ( st.contains ( d) ) ;
7025
}
7026
assert! (
7027
heads.iter ( ) .all ( | h | c.deps.contains ( h) ) ,
7028
" depends on both contributors"
7029
) ;
7030
st.add ( c) .unwrap ( ) ;
7031
let after = st.materialize ( ) ;
7032
assert! ( ! after.is_conflicted ( ) ) ;
7033
assert_eq! (
7034
after.files[ " img.bin" ] .content,
7035
FileContent:: Opaque {
7036
blobs: vec! [ Repo:: blob_ref( & blob ( 3 ) ) ]
7037
}
7038
) ;
7039
7040
7041
let edits = [ FileEdit {
7042
path: " img.bin" .into ( ) ,
7043
content: Some ( blob ( 9 ) ) ,
7044
} ] ;
7045
let c = repo.build_change ( & heads, & edits, " new" , author) .unwrap ( ) ;
7046
let mut st = repo.load_state ( & heads) .unwrap ( ) ;
7047
st.add ( c) .unwrap ( ) ;
7048
assert_eq! (
7049
st.materialize ( ) .files[ " img.bin" ] .content,
7050
FileContent:: Opaque {
7051
blobs: vec! [ Repo:: blob_ref( & blob ( 9 ) ) ]
7052
}
7053
) ;
7054
}
83d61e80 Andy McCall 1 month ago ↶
7055
7056
7057
7058
7059
# [ test ]
7060
fn line_ranges_have_exact_histories ( ) {
7061
hermetic ( ) ;
7062
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7063
let root = dir.path ( ) ;
7064
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
7065
write ( root, " f.rs" , b " use x;\n fn a() {\n one();\n two();\n }\n " ) ;
7066
let base = repo.commit ( " add f" , & [ ] ) .unwrap ( ) ;
7067
write ( root, " f.rs" , b " use y;\n fn a() {\n one();\n two();\n }\n " ) ;
7068
let outside = repo.commit ( " change the import" , & [ ] ) .unwrap ( ) ;
7069
write ( root, " f.rs" , b " use y;\n fn a() {\n ONE();\n two();\n }\n " ) ;
7070
let inside = repo.commit ( " edit a line of a" , & [ ] ) .unwrap ( ) ;
7071
write ( root, " f.rs" , b " use y;\n fn a() {\n ONE();\n }\n " ) ;
7072
let deleted = repo.commit ( " delete a line of a" , & [ ] ) .unwrap ( ) ;
7073
write ( root, " f.rs" , b " use y;\n fn a() {\n ONE();\n }\n fn b() {}\n " ) ;
7074
let after = repo.commit ( " add b after a" , & [ ] ) .unwrap ( ) ;
7075
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
7076
7077
7078
let hist: Vec< ChangeHash> = repo
7079
.line_history ( & heads, " f.rs" , 2 , 4 )
7080
.unwrap ( )
7081
.iter ( )
7082
.map ( | e | e.hash )
7083
.collect ( ) ;
7084
for ( h, what) in [
7085
( base, " wrote it" ) ,
7086
( inside, " edited a line" ) ,
7087
( deleted, " deleted a line from inside" ) ,
7088
] {
7089
assert! ( hist.contains ( & h) , " {what}" ) ;
7090
}
7091
for ( h, what) in [ ( outside, " an edit above" ) , ( after, " an addition below" ) ] {
7092
assert! ( ! hist.contains ( & h) , " {what}" ) ;
7093
}
7094
assert_eq! ( hist[ 0 ] , deleted, " newest first" ) ;
7095
7096
let one: Vec< ChangeHash> = repo
7097
.line_history ( & heads, " f.rs" , 1 , 1 )
7098
.unwrap ( )
7099
.iter ( )
7100
.map ( | e | e.hash )
7101
.collect ( ) ;
7102
assert! ( one.contains ( & outside) & & ! one.contains ( & inside) ) ;
7103
assert! ( repo.line_history ( & heads, " f.rs" , 3 , 9 ) .is_err ( ) ) ;
7104
assert! ( repo.line_history ( & heads, " f.rs" , 0 , 1 ) .is_err ( ) ) ;
7105
assert! ( repo.line_history ( & heads, " nope.rs" , 1 , 1 ) .is_err ( ) ) ;
7106
}
fc1a466a Andy McCall 1 month ago ↶
7107
7108
# [ test ]
7109
fn receipts_are_kept_with_their_state_and_verified ( ) {
7110
use crate :: receipt:: { Receipt, SignedReceipt} ;
7111
hermetic ( ) ;
7112
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7113
let root = dir.path ( ) ;
7114
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
7115
write ( root, " a.txt" , b " 1\n " ) ;
7116
repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
7117
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
7118
let state = repo
7119
.store ( )
7120
.put_state ( & StateObject:: new( heads.clone ( ) ) )
7121
.unwrap ( ) ;
7122
let out = repo.store_blob ( b " built\n " ) .unwrap ( ) ;
7123
let receipt = Receipt {
7124
job_name: " build" .into ( ) ,
7125
pipeline_hash: ObjectRef( [ 1 ; 32 ] ) ,
7126
scope_tree_hash: ObjectRef( [ 2 ; 32 ] ) ,
7127
inputs: vec! [ ] ,
7128
environment: ObjectRef( [ 3 ; 32 ] ) ,
7129
outputs: vec! [ out] ,
7130
exit_code: 0 ,
7131
duration_ms: 42 ,
7132
} ;
7133
let sk = SecretKey:: generate( ) ;
7134
let runner = sk.identity ( " earth-1" , " runner@earth" , 1 ) ;
7135
let sr = SignedReceipt:: sign(
7136
receipt.clone ( ) ,
7137
repo.id ( ) .unwrap ( ) ,
7138
state,
7139
" demo#1" ,
7140
& runner,
7141
& sk,
7142
) ;
7143
7144
let r = repo.add_receipt ( & sr) .unwrap ( ) ;
7145
let found = repo.receipts ( & heads) .unwrap ( ) ;
7146
assert_eq! ( found.len ( ) , 1 ) ;
7147
assert_eq! ( found[ 0 ] .attest, r) ;
7148
assert_eq! ( found[ 0 ] .signed, sr) ;
7149
assert! ( found[ 0 ] .problem.is_none ( ) & & ! found[ 0 ] .revoked) ;
7150
assert! ( repo.receipts ( & [ ObjectRef( [ 5 ; 32 ] ) ] ) .unwrap ( ) .is_empty ( ) ) ;
7151
7152
7153
let elsewhere = SignedReceipt:: sign( receipt.clone ( ) , [ 0 ; 16 ] , state, " x" , & runner, & sk) ;
7154
assert! ( repo
7155
.add_receipt ( & elsewhere)
7156
.unwrap_err ( )
7157
.to_string ( )
7158
.contains ( " another repository" ) ) ;
7159
let mut forged = sr.clone ( ) ;
7160
forged.receipt.exit_code = 1 ;
7161
assert! ( repo
7162
.add_receipt ( & forged)
7163
.unwrap_err ( )
7164
.to_string ( )
7165
.contains ( " does not verify" ) ) ;
7166
c63fed2e Andy McCall 19 days ago ↶
7167
7168
7169
let hub_sk = SecretKey:: generate( ) ;
7170
let hub = hub_sk.identity ( " oxygenhub.io" , " [email protected] " , 1 ) ;
7171
let job_sk = SecretKey:: generate( ) ;
7172
let job = job_sk.identity ( " job 2 test" , " [email protected] " , 1 ) ;
7173
let mut test_receipt = receipt.clone ( ) ;
7174
test_receipt.job_name = " test" .into ( ) ;
7175
let plain = SignedReceipt:: sign(
7176
test_receipt,
7177
repo.id ( ) .unwrap ( ) ,
7178
state,
7179
" demo#2" ,
7180
& job,
7181
& job_sk,
7182
) ;
7183
let now = plain.attest.hlc.wall_ms;
7184
let cert = crate :: cert:: SignedCertificate:: sign(
7185
crate :: cert:: Certificate {
7186
key: job.clone ( ) ,
7187
issuer: hub.hash ( ) ,
7188
subject: " repo:andy/demo:bookmark:main" .into ( ) ,
7189
claims: vec! [ ] ,
7190
scope: crate :: identity:: Scope:: SEAL ,
7191
not_before: now - 1_000 ,
7192
expires: now + 600_000 ,
a2387d54 Andy McCall 19 days ago ↶
7193
grant: None ,
c63fed2e Andy McCall 19 days ago ↶
7194
} ,
7195
& hub,
7196
& hub_sk,
7197
) ;
7198
let certified = plain.with_certificate ( cert, hub.clone ( ) ) ;
7199
repo.add_receipt ( & certified) .unwrap ( ) ;
7200
let found = repo.receipts ( & heads) .unwrap ( ) ;
7201
let got = found
7202
.iter ( )
7203
.find ( | i | i.signed.receipt.job_name = = " test" )
7204
.unwrap ( ) ;
7205
assert_eq! ( got.signed, certified) ;
7206
assert! ( got.problem.is_none ( ) , " {:?}" , got.problem) ;
7207
7208
7209
fc1a466a Andy McCall 1 month ago ↶
7210
repo.gc ( true ) .unwrap ( ) ;
c63fed2e Andy McCall 19 days ago ↶
7211
let found = repo.receipts ( & heads) .unwrap ( ) ;
7212
assert_eq! ( found.len ( ) , 2 ) ;
7213
let got = found
7214
.iter ( )
7215
.find ( | i | i.signed.receipt.job_name = = " test" )
7216
.unwrap ( ) ;
7217
assert_eq! ( got.signed, certified, " the certificate survives gc" ) ;
7218
assert! ( got.problem.is_none ( ) ) ;
fc1a466a Andy McCall 1 month ago ↶
7219
assert_eq! ( repo.read_blob ( & out) .unwrap ( ) , b " built\n " ) ;
7220
assert! ( repo.fsck ( ) .unwrap ( ) .problems.is_empty ( ) ) ;
dfb23a3a Andy McCall 1 month ago ↶
7221
}
7222
7223
# [ test ]
3a4b3e01 Andy McCall 20 days ago ↶
7224
fn approvals_are_kept_with_their_state_and_survive_gc ( ) {
7225
hermetic ( ) ;
7226
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7227
let root = dir.path ( ) ;
7228
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
7229
write ( root, " a.txt" , b " 1\n " ) ;
7230
repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
7231
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
7232
let state = repo.store ( ) .put_state ( & StateObject:: new( heads) ) .unwrap ( ) ;
7233
let plan = repo.store_blob ( b " the reviewed plan\n " ) .unwrap ( ) ;
7234
let sk = SecretKey:: generate( ) ;
7235
let andy = sk.identity ( " Andy" , " [email protected] " , 1 ) ;
7236
let statement = crate :: approval:: Statement {
7237
repo: repo.id ( ) .unwrap ( ) ,
7238
run: " https://hub.example/andy/infra/flow/8" .into ( ) ,
7239
job: " apply" .into ( ) ,
7240
job_hash: ObjectRef( [ 1 ; 32 ] ) ,
7241
state,
7242
inputs: vec! [ plan] ,
7243
environment: " prod" .into ( ) ,
7244
approve: true ,
7245
} ;
7246
let sa = crate :: approval:: SignedApproval:: sign( & statement, & andy, & sk) ;
7247
let r = repo.add_approval ( & sa) .unwrap ( ) ;
7248
assert_eq! ( repo.store ( ) .approvals_for ( & state) .unwrap ( ) , vec! [ r] ) ;
7249
7250
let elsewhere = crate :: approval:: SignedApproval:: sign(
7251
& crate :: approval:: Statement {
7252
repo: [ 0 ; 16 ] ,
7253
.. statement.clone ( )
7254
} ,
7255
& andy,
7256
& sk,
7257
) ;
7258
assert! ( repo
7259
.add_approval ( & elsewhere)
7260
.unwrap_err ( )
7261
.to_string ( )
7262
.contains ( " another repository" ) ) ;
7263
let mut forged = sa.clone ( ) ;
7264
if let AttestBody:: Approval { environment, .. } = & mut forged.attest.body {
7265
* environment = " dev" .into ( ) ;
7266
}
7267
assert! ( repo
7268
.add_approval ( & forged)
7269
.unwrap_err ( )
7270
.to_string ( )
7271
.contains ( " does not verify" ) ) ;
7272
7273
repo.gc ( true ) .unwrap ( ) ;
7274
assert_eq! ( repo.store ( ) .approvals_for ( & state) .unwrap ( ) , vec! [ r] ) ;
7275
assert_eq! ( repo.store ( ) .get_attest ( & r) .unwrap ( ) , sa.attest) ;
7276
assert_eq! ( repo.store ( ) .signatures ( & r) .unwrap ( ) , sa.sigs) ;
7277
assert! ( repo.fsck ( ) .unwrap ( ) .problems.is_empty ( ) ) ;
7278
}
7279
7280
# [ test ]
dfb23a3a Andy McCall 1 month ago ↶
7281
fn status_leaves_an_unchanged_manifest_alone ( ) {
7282
hermetic ( ) ;
7283
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7284
let root = dir.path ( ) ;
7285
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
7286
write ( root, " a.txt" , b " 1\n " ) ;
7287
write ( root, " b.txt" , b " 2\n " ) ;
7288
repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
7289
7290
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 60 ) ;
7291
for f in [ " a.txt" , " b.txt" ] {
7292
fs:: File:: options( )
7293
.write ( true )
7294
.open ( root.join ( f) )
7295
.unwrap ( )
7296
.set_modified ( old)
7297
.unwrap ( ) ;
7298
}
7299
repo.snapshot_status ( " status" ) .unwrap ( ) ;
7300
let path = repo.manifest_path ( ) ;
7301
let first = fs:: read( & path) .unwrap ( ) ;
7302
repo.snapshot_status ( " status" ) .unwrap ( ) ;
7303
assert_eq! (
7304
fs:: read( & path) .unwrap ( ) ,
7305
first,
7306
" an unchanged tree does not rewrite the manifest"
7307
) ;
7308
7309
write ( root, " a.txt" , b " 1\n 2\n " ) ;
7310
repo.snapshot_status ( " status" ) .unwrap ( ) ;
7311
assert_ne! ( fs:: read( & path) .unwrap ( ) , first) ;
fc1a466a Andy McCall 1 month ago ↶
7312
}
6f8aeb99 Andy McCall 1 month ago ↶
7313
7314
# [ test ]
7315
fn large_text_files_travel_and_survive_gc_as_text_blobs ( ) {
7316
hermetic ( ) ;
7317
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7318
let root = dir.path ( ) ;
7319
let origin_path = root.join ( " origin" ) ;
7320
Repo:: init( & origin_path) .unwrap ( ) ;
7321
let origin_url = origin_path.to_string_lossy ( ) .to_string ( ) ;
7322
let a = Repo:: clone( & origin_url, & root.join ( " a" ) ) .unwrap ( ) ;
7323
let big: String = ( 0 .. 5000 ) .map ( | i | format! ( " line {i} \n " ) ) .collect ( ) ;
7324
write ( & root.join ( " a" ) , " big.txt" , big.as_bytes ( ) ) ;
7325
write ( & root.join ( " a" ) , " small.txt" , b " small\n " ) ;
7326
let h1 = a.commit ( " large" , & [ ] ) .unwrap ( ) ;
7327
let ( _ , body) = a.store ( ) .get ( & h1) .unwrap ( ) ;
7328
assert! ( body.len ( ) < 2048 , " the change is {} bytes" , body.len ( ) ) ;
7329
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7330
7331
7332
let b = Repo:: clone( & origin_url, & root.join ( " b" ) ) .unwrap ( ) ;
7333
assert_eq! ( fs:: read( root.join ( " b/big.txt" ) ) .unwrap ( ) , big.as_bytes ( ) ) ;
7334
7335
let edited = big.replace ( " line 2500\n " , " line 2500, edited\n " ) ;
7336
write ( & root.join ( " b" ) , " big.txt" , edited.as_bytes ( ) ) ;
7337
let h2 = b.commit ( " edit" , & [ ] ) .unwrap ( ) ;
7338
assert! ( b.store ( ) .get_change ( & h2) .unwrap ( ) .edits.len ( ) < = 2 ) ;
7339
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7340
assert! ( a.pull ( " origin" ) .unwrap ( ) .checked_out) ;
7341
assert_eq! ( fs:: read( root.join ( " a/big.txt" ) ) .unwrap ( ) , edited.as_bytes ( ) ) ;
7342
assert_eq! (
7343
a.fsck ( ) .unwrap ( ) .errors ( ) ,
7344
0 ,
7345
" {:?}" ,
7346
a.fsck ( ) .unwrap ( ) .problems
7347
) ;
7348
7349
7350
let mut a = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) ;
7351
a.config.set ( " core.op-retention" , " 0" ) .unwrap ( ) ;
7352
a.save_config ( ) .unwrap ( ) ;
7353
let a = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) ;
7354
a.gc ( true ) .unwrap ( ) ;
7355
let a = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) ;
7356
assert_eq! ( a.store ( ) .get_change ( & h1) .unwrap ( ) .message, " large" ) ;
7357
assert_eq! (
7358
a.fsck ( ) .unwrap ( ) .errors ( ) ,
7359
0 ,
7360
" {:?}" ,
7361
a.fsck ( ) .unwrap ( ) .problems
7362
) ;
7363
7364
let c = Repo:: clone( & root.join ( " a" ) .to_string_lossy ( ) , & root.join ( " c" ) ) .unwrap ( ) ;
7365
assert_eq! ( fs:: read( root.join ( " c/big.txt" ) ) .unwrap ( ) , edited.as_bytes ( ) ) ;
7366
drop ( c) ;
6caacfff Andy McCall 1 month ago ↶
7367
}
7368
7369
# [ test ]
7370
fn operations_that_both_replace_a_working_copy_are_not_merged ( ) {
7371
hermetic ( ) ;
7372
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7373
let root = dir.path ( ) ;
7374
let repo = Repo:: init( root) .unwrap ( ) ;
7375
write ( root, " a.txt" , b " a\n " ) ;
7376
let a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
7377
write ( root, " b.txt" , b " b\n " ) ;
7378
repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
7379
let ( x, op) = repo.head_op ( ) .unwrap ( ) ;
7380
let ws = * op.view.workspaces.keys ( ) .next ( ) .unwrap ( ) ;
7381
let mut one = op.view.clone ( ) ;
7382
one.workspaces.insert ( ws, a) ;
7383
let mut two = op.view.clone ( ) ;
7384
two.workspaces.insert ( ws, ObjectRef( [ 7 ; 32 ] ) ) ;
7385
let first = repo.record_op ( & [ x] , " one" , one) .unwrap ( ) ;
7386
let e = repo.record_op ( & [ x] , " two" , two) .unwrap_err ( ) ;
7387
assert! (
7388
e.to_string ( )
7389
.contains ( " changed this working copy at the same time" ) ,
7390
" {e}"
7391
) ;
7392
assert_eq! (
7393
repo.head_op ( ) .unwrap ( ) .0 ,
7394
first,
7395
" the second is not recorded"
7396
) ;
7397
}
7398
7399
7400
7401
7402
# [ test ]
7403
fn a_snapshot_during_a_switch_never_records_the_switch_as_edits ( ) {
7404
hermetic ( ) ;
7405
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7406
let root = dir.path ( ) .to_path_buf ( ) ;
7407
let mut repo = Repo:: init( & root) .unwrap ( ) ;
7408
write ( & root, " keep.txt" , b " keep\n " ) ;
7409
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7410
repo.bookmark_create ( " small" , None ) .unwrap ( ) ;
7411
for i in 0 .. 300 {
7412
write (
7413
& root,
7414
& format! ( " many/f{i} .txt" ) ,
7415
format! ( " file {i} \n " ) .as_bytes ( ) ,
7416
) ;
7417
}
7418
repo.commit ( " many" , & [ ] ) .unwrap ( ) ;
7419
let stop = std:: sync:: Arc:: new( std:: sync:: atomic:: AtomicBool:: new( false ) ) ;
7420
let watcher = {
7421
let ( root, stop) = ( root.clone ( ) , stop.clone ( ) ) ;
7422
std:: thread:: spawn( move | | {
7423
let r = Repo:: open( & root) .unwrap ( ) ;
7424
let mut ran = 0 ;
7425
while ! stop.load ( std:: sync:: atomic:: Ordering:: Relaxed) {
7426
if r.snapshot_status ( " oxy status" ) .is_ok ( ) {
7427
ran + = 1 ;
7428
}
7429
}
7430
ran
7431
} )
7432
} ;
7433
for i in 0 .. 20 {
7434
let to = if i % 2 = = 0 { " small" } else { " main" } ;
7435
repo.bookmark_switch ( to, false ) .unwrap ( ) ;
7436
}
7437
stop.store ( true , std:: sync:: atomic:: Ordering:: Relaxed) ;
7438
assert! ( watcher.join ( ) .unwrap ( ) > 0 , " the watcher snapshotted" ) ;
7439
7440
let repo = Repo:: open( & root) .unwrap ( ) ;
7441
let s = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7442
assert! ( s.statuses.is_empty ( ) , " {:?}" , statuses ( & s) ) ;
7443
assert! ( s.wc.edits.is_empty ( ) & & s.wc.tree_ops.is_empty ( ) ) ;
7444
assert_eq! ( s.wc.deps, repo.bookmark_heads ( ) .unwrap ( ) ) ;
7445
assert! ( root.join ( " many/f299.txt" ) .exists ( ) ) ;
1a3b6203 Andy McCall 1 month ago ↶
7446
}
7447
7448
7449
7450
7451
# [ test ]
7452
fn a_parent_switch_with_carry_leaves_a_subrepo_ahead_of_its_pin_alone ( ) {
7453
hermetic ( ) ;
7454
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7455
let root = dir.path ( ) ;
7456
Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
7457
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
7458
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) ) .unwrap ( ) ;
7459
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
7460
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
7461
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7462
7463
let app_dir = root.join ( " app" ) ;
7464
let mut app = Repo:: init( & app_dir) .unwrap ( ) ;
7465
write ( & app_dir, " main.txt" , b " app\n " ) ;
7466
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
7467
app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
7468
app.bookmark_create ( " feature" , None ) .unwrap ( ) ;
7469
7470
7471
let nested_dir = app_dir.join ( " libs/lib" ) ;
7472
let nested = Repo:: open( & nested_dir) .unwrap ( ) ;
7473
write ( & nested_dir, " new.txt" , b " new\n " ) ;
7474
let l2 = nested.commit ( " lib: new file" , & [ ] ) .unwrap ( ) ;
7475
drop ( nested) ;
7476
7477
let ( _ , carried) = app.bookmark_switch_carry ( " feature" ) .unwrap ( ) ;
7478
assert! ( carried.contains ( & " libs/lib" .to_string ( ) ) , " {carried:?}" ) ;
7479
let nested = Repo:: open( & nested_dir) .unwrap ( ) ;
7480
assert! (
7481
nested.bookmark_heads ( ) .unwrap ( ) .contains ( & l2) ,
7482
" its bookmark kept its work"
7483
) ;
7484
assert! ( nested_dir.join ( " new.txt" ) .exists ( ) , " and its files" ) ;
7485
let s = app.snapshot ( " status" ) .unwrap ( ) ;
7486
assert_eq! (
7487
statuses ( & s) ,
7488
vec! [ ( " libs/lib" .to_string ( ) , ChangeStatus:: Modified) ]
7489
) ;
6f8aeb99 Andy McCall 1 month ago ↶
7490
}
7d9dab13 Andy McCall 1 month ago ↶
7491
7492
7493
7494
7495
# [ test ]
7496
fn pull_takes_a_remote_move_to_changes_already_here ( ) {
7497
hermetic ( ) ;
7498
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7499
let root = dir.path ( ) ;
7500
let origin_path = root.join ( " origin" ) ;
7501
let origin = Repo:: init( & origin_path) .unwrap ( ) ;
7502
let url = origin_path.to_string_lossy ( ) .to_string ( ) ;
7503
let mut b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) ;
7504
write ( & root.join ( " b" ) , " base.txt" , b " base\n " ) ;
7505
b.commit ( " base" , & [ ] ) .unwrap ( ) ;
7506
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
7507
origin.refresh ( ) .unwrap ( ) ;
7d9dab13 Andy McCall 1 month ago ↶
7508
7509
7510
b.bookmark_create ( " feat" , None ) .unwrap ( ) ;
7511
b.bookmark_switch ( " feat" , false ) .unwrap ( ) ;
7512
write ( & root.join ( " b" ) , " x.txt" , b " x\n " ) ;
7513
let x = b.commit ( " x" , & [ ] ) .unwrap ( ) ;
7514
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
767ac6cd Andy McCall 18 days ago
7515
origin.refresh ( ) .unwrap ( ) ;
7d9dab13 Andy McCall 1 month ago ↶
7516
b.bookmark_switch ( " main" , false ) .unwrap ( ) ;
7517
7518
7519
let feat = origin
7520
.store ( )
7521
.get_state ( & origin.view ( ) .unwrap ( ) .bookmarks[ " feat" ] )
7522
.unwrap ( )
7523
.heads;
7524
origin.bookmark_set ( " main" , & feat) .unwrap ( ) ;
7525
7526
let r = b.pull ( " origin" ) .unwrap ( ) ;
7527
assert! ( r.changed, " {r:?}" ) ;
7528
assert! ( r.heads.contains ( & x) | | b.load_state ( & r.heads) .unwrap ( ) .contains ( & x) ) ;
7529
let main = b
7530
.bookmarks ( )
7531
.unwrap ( )
7532
.into_iter ( )
7533
.find ( | m | m.name = = " main" )
7534
.unwrap ( ) ;
7535
assert_eq! ( main.behind, Some ( 0 ) , " {main:?}" ) ;
7536
}
86e1824f Andy McCall 1 month ago ↶
7537
14edbaaf Andy McCall 1 month ago ↶
7538
7539
7540
7541
7542
# [ test ]
7543
fn remote_bookmarks_are_named_switched_to_pulled_and_cloned ( ) {
7544
hermetic ( ) ;
7545
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7546
let root = dir.path ( ) ;
7547
let origin_path = root.join ( " origin" ) ;
7548
let mut origin = Repo:: init( & origin_path) .unwrap ( ) ;
7549
let url = origin_path.to_string_lossy ( ) .to_string ( ) ;
7550
let mut a = Repo:: clone( & url, & root.join ( " a" ) ) .unwrap ( ) ;
7551
write ( & root.join ( " a" ) , " base.txt" , b " base\n " ) ;
7552
a.commit ( " base" , & [ ] ) .unwrap ( ) ;
7553
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7554
a.bookmark_create ( " develop" , None ) .unwrap ( ) ;
7555
a.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
7556
write ( & root.join ( " a" ) , " base.txt" , b " base\n d1\n " ) ;
7557
let d1 = a.commit ( " d1" , & [ ] ) .unwrap ( ) ;
7558
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7559
7560
7561
let c_root = root.join ( " c" ) ;
7562
let mut c = Repo:: clone( & url, & c_root ) .unwrap ( ) ;
7563
assert_eq! ( c.config.bookmark, " main" ) ;
7564
assert! ( ! c.view ( ) .unwrap ( ) .bookmarks.contains_key ( " develop" ) ) ;
7565
assert_eq! ( c.resolve_heads ( " origin/develop" ) .unwrap ( ) , vec! [ d1] ) ;
7566
assert_eq! ( c.resolve_change ( " origin/develop" ) .unwrap ( ) , d1) ;
7567
assert_eq! ( c.remote_only_bookmarks ( ) .unwrap ( ) .len ( ) , 1 ) ;
7568
7569
7570
c.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
7571
assert_eq! ( c.config.bookmark, " develop" ) ;
7572
assert_eq! ( c.bookmark_heads ( ) .unwrap ( ) , vec! [ d1] ) ;
7573
assert_eq! ( fs:: read( c_root .join ( " base.txt" ) ) .unwrap ( ) , b " base\n d1\n " ) ;
7574
assert! ( c.remote_only_bookmarks ( ) .unwrap ( ) .is_empty ( ) ) ;
7575
assert! ( c.bookmark_switch ( " nowhere" , false ) .is_err ( ) ) ;
7576
7577
7578
write ( & root.join ( " a" ) , " base.txt" , b " base\n d1\n d2\n " ) ;
7579
let d2 = a.commit ( " d2" , & [ ] ) .unwrap ( ) ;
7580
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7581
a.bookmark_switch ( " main" , false ) .unwrap ( ) ;
7582
write ( & root.join ( " a" ) , " m.txt" , b " m\n " ) ;
7583
let m = a.commit ( " m" , & [ ] ) .unwrap ( ) ;
7584
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
7585
let r = c.pull ( " origin" ) .unwrap ( ) ;
7586
assert! ( r.changed & & r.checked_out, " {r:?}" ) ;
7587
assert_eq! ( r.heads, vec! [ d2] ) ;
7588
assert_eq! ( r.others, vec! [ " main" .to_string ( ) ] ) ;
7589
assert! ( c.resolve_heads ( " main" ) .unwrap ( ) .contains ( & m) ) ;
7590
assert_eq! (
7591
fs:: read( c_root .join ( " base.txt" ) ) .unwrap ( ) ,
7592
b " base\n d1\n d2\n "
7593
) ;
7594
let again = c.pull ( " origin" ) .unwrap ( ) ;
7595
assert! ( ! again.changed & & again.others.is_empty ( ) , " {again:?}" ) ;
7596
7597
7598
origin.config.set ( " workspace.bookmark" , " develop" ) .unwrap ( ) ;
7599
origin.save_config ( ) .unwrap ( ) ;
7600
let e = Repo:: clone( & url, & root.join ( " e" ) ) .unwrap ( ) ;
7601
assert_eq! ( e.config.bookmark, " develop" ) ;
7602
assert_eq! ( e.bookmark_heads ( ) .unwrap ( ) , vec! [ d2] ) ;
7603
let reopened = Repo:: open( & root.join ( " e" ) ) .unwrap ( ) ;
7604
assert_eq! ( reopened.config.bookmark, " develop" ) ;
7605
7606
let mut e = reopened;
7607
e.bookmark_switch ( " origin/main" , false ) .unwrap ( ) ;
7608
assert_eq! ( e.config.bookmark, " main" ) ;
7609
assert! ( e.bookmark_heads ( ) .unwrap ( ) .contains ( & m) ) ;
7610
}
7611
98a1620a Andy McCall 1 month ago ↶
7612
7613
7614
7615
7616
# [ test ]
7617
fn operations_can_be_read_restored_and_reverted ( ) {
7618
hermetic ( ) ;
7619
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7620
let root = dir.path ( ) ;
7621
let repo = Repo:: init( root) .unwrap ( ) ;
7622
write ( root, " a.txt" , b " a\n " ) ;
7623
let first = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
7624
write ( root, " a.txt" , b " a\n b\n " ) ;
7625
let second = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
7626
let at_second = repo.head_op ( ) .unwrap ( ) .0. to_hex ( ) ;
7627
repo.bookmark_create ( " keep" , None ) .unwrap ( ) ;
7628
let created = repo.head_op ( ) .unwrap ( ) .0. to_hex ( ) ;
7629
write ( root, " n.txt" , b " n\n " ) ;
7630
let third = repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
7631
7632
7633
let ops = repo.op_log ( ) .unwrap ( ) ;
7634
let changes = repo.view_changes ( & ops[ 1 ] .1. view, & ops[ 0 ] .1. view) .unwrap ( ) ;
7635
assert! (
7636
changes
7637
.iter ( )
7638
.any ( | c | matches! ( c, ViewChange:: BookmarkMoved { name, .. } if name = = " main" ) ) ,
7639
" {changes:?}"
7640
) ;
7641
assert! ( changes
7642
.iter ( )
7643
.any ( | c | matches! ( c, ViewChange:: WorkingCopy { .. } ) ) ) ;
7644
7645
7646
let old = Repo:: open( root) .unwrap ( ) .at_op ( & at_second[ .. 10 ] ) .unwrap ( ) ;
7647
assert_eq! ( old.bookmark_heads ( ) .unwrap ( ) , vec! [ second] ) ;
7648
assert! ( ! old.view ( ) .unwrap ( ) .bookmarks.contains_key ( " keep" ) ) ;
7649
write ( root, " z.txt" , b " z\n " ) ;
7650
assert! ( old.commit ( " refused" , & [ ] ) .is_err ( ) , " --at-op is read-only" ) ;
7651
fs:: remove_file( root.join ( " z.txt" ) ) .unwrap ( ) ;
7652
assert! ( repo.resolve_op ( " zz" ) .is_err ( ) ) ;
7653
assert_eq! ( repo.resolve_op ( " @" ) .unwrap ( ) .0 , repo.head_op ( ) .unwrap ( ) .0 ) ;
7654
7655
7656
repo.op_restore ( & at_second, false ) .unwrap ( ) ;
7657
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ second] ) ;
7658
assert! ( ! root.join ( " n.txt" ) .exists ( ) ) ;
7659
repo.undo ( ) .unwrap ( ) ;
7660
let mut heads = repo.bookmark_heads ( ) .unwrap ( ) ;
7661
heads.sort ( ) ;
7662
let mut want = vec! [ second, third] ;
7663
want.sort ( ) ;
7664
assert_eq! ( heads, want) ;
7665
assert! (
7666
root.join ( " n.txt" ) .exists ( ) ,
7667
" the files came back with the undo"
7668
) ;
7669
7670
7671
let r = repo.op_revert ( & created) .unwrap ( ) ;
7672
assert_eq! ( r.reverted, vec! [ " keep" .to_string ( ) ] ) ;
7673
assert! ( ! repo.view ( ) .unwrap ( ) .bookmarks.contains_key ( " keep" ) ) ;
7674
assert! ( repo
7675
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
7676
.unwrap ( )
7677
.contains ( & third) ) ;
7678
assert! ( repo.op_revert ( & created) .is_err ( ) , " nothing left to revert" ) ;
7679
let _ = first;
7680
}
7681
36041796 Andy McCall 1 month ago ↶
7682
7683
7684
7685
# [ test ]
7686
fn status_with_uncommitted_work_comes_from_the_cache_until_a_file_changes ( ) {
7687
hermetic ( ) ;
7688
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7689
let root = dir.path ( ) ;
7690
let repo = Repo:: init( root) .unwrap ( ) ;
7691
write ( root, " a.txt" , b " a\n " ) ;
7692
write ( root, " b.txt" , b " b\n " ) ;
7693
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7694
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , Some ( vec! [ ] ) , " clean" ) ;
7695
7696
write ( root, " a.txt" , b " a\n more\n " ) ;
7697
assert_eq! (
7698
repo.status_by_manifest ( ) .unwrap ( ) ,
7699
None ,
7700
" no snapshot of the edit yet"
7701
) ;
7702
let snap = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7703
assert_eq! ( snap.statuses.len ( ) , 1 ) ;
7704
let cached = repo
7705
.status_by_manifest ( )
7706
.unwrap ( )
7707
.expect ( " answered from the cache" ) ;
7708
assert_eq! ( cached.len ( ) , 1 ) ;
7709
assert_eq! (
7710
( cached[ 0 ] .path.as_str ( ) , cached[ 0 ] .status) ,
7711
( " a.txt" , ChangeStatus:: Modified)
7712
) ;
7713
7714
7715
write ( root, " a.txt" , b " a\n more\n and more\n " ) ;
7716
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None , " edited again" ) ;
7717
repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7718
assert! ( repo.status_by_manifest ( ) .unwrap ( ) .is_some ( ) ) ;
7719
write ( root, " new.txt" , b " n\n " ) ;
7720
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None , " a file added" ) ;
7721
fs:: remove_file( root.join ( " new.txt" ) ) .unwrap ( ) ;
7722
assert! (
7723
repo.status_by_manifest ( ) .unwrap ( ) .is_some ( ) ,
7724
" back as it was"
7725
) ;
7726
fs:: remove_file( root.join ( " b.txt" ) ) .unwrap ( ) ;
7727
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None , " a file removed" ) ;
7728
write ( root, " b.txt" , b " b\n " ) ;
7729
7730
7731
repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7732
repo.commit ( " more" , & [ ] ) .unwrap ( ) ;
7733
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , Some ( vec! [ ] ) ) ;
7734
}
7735
77445673 Andy McCall 1 month ago ↶
7736
7737
7738
7739
# [ test ]
7740
fn the_working_copy_diff_from_the_cache_matches_a_snapshot ( ) {
7741
hermetic ( ) ;
7742
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7743
let root = dir.path ( ) ;
7744
let repo = Repo:: init( root) .unwrap ( ) ;
7745
write ( root, " a.txt" , b " one\n two\n three\n " ) ;
7746
write ( root, " gone.txt" , b " bye\n " ) ;
7747
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7748
write ( root, " a.txt" , b " one\n 2\n three\n four\n " ) ;
7749
write ( root, " new.txt" , b " hello\n " ) ;
7750
fs:: remove_file( root.join ( " gone.txt" ) ) .unwrap ( ) ;
7751
let snap = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7752
let slow = render_change ( & snap.wc, & snap.base, & snap.base_snapshot) ;
7753
assert! (
7754
repo.status_by_manifest ( ) .unwrap ( ) .is_some ( ) ,
7755
" the cache holds"
7756
) ;
7757
let ( wc, base, snapshot) = repo.working_copy_with_base ( ) .unwrap ( ) ;
7758
assert_eq! ( render_change ( & wc, & base, & snapshot) , slow) ;
7759
assert_eq! ( repo.diff_files ( None ) .unwrap ( ) .len ( ) , 3 ) ;
7760
}
7761
1ec3f008 Andy McCall 1 month ago ↶
7762
7763
7764
7765
7766
# [ test ]
7767
fn the_manifest_check_skips_listing_while_no_directory_changed ( ) {
7768
hermetic ( ) ;
7769
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7770
let root = dir.path ( ) ;
7771
let repo = Repo:: init( root) .unwrap ( ) ;
7772
write ( root, " a.txt" , b " a\n " ) ;
7773
write ( root, " sub/deep/b.txt" , b " b\n " ) ;
7774
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7775
7776
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 60 ) ;
7777
for p in [ " a.txt" , " sub/deep/b.txt" , " sub/deep" , " sub" , " " ] {
7778
fs:: File:: open( root.join ( p) )
7779
.unwrap ( )
7780
.set_modified ( old)
7781
.unwrap ( ) ;
7782
}
7783
repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7784
repo.write_manifest ( false , false ) .unwrap ( ) ;
7785
let m = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) .2 ;
7786
assert! ( m.dirs.iter ( ) .any ( | ( d , _) | d = = " sub/deep" ) , " {:?}" , m.dirs) ;
7787
assert_eq! (
7788
repo.disk_differs_by_dirs ( & m) ,
7789
Some ( false ) ,
7790
" no listing needed"
7791
) ;
7792
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , Some ( vec! [ ] ) ) ;
7793
7794
7795
write ( root, " sub/deep/b.txt" , b " c\n " ) ;
7796
assert_eq! ( repo.disk_differs_by_dirs ( & m) , Some ( true ) ) ;
7797
write ( root, " sub/deep/b.txt" , b " b\n " ) ;
7798
fs:: File:: open( root.join ( " sub/deep/b.txt" ) )
7799
.unwrap ( )
7800
.set_modified ( old)
7801
.unwrap ( ) ;
7802
assert_eq! ( repo.disk_differs_by_dirs ( & m) , Some ( false ) ) ;
7803
7804
7805
write ( root, " sub/deep/new.txt" , b " n\n " ) ;
7806
assert_eq! ( repo.disk_differs_by_dirs ( & m) , None ) ;
7807
assert_eq! ( repo.status_by_manifest ( ) .unwrap ( ) , None ) ;
7808
fs:: remove_file( root.join ( " sub/deep/new.txt" ) ) .unwrap ( ) ;
7809
7810
fs:: remove_file( root.join ( " a.txt" ) ) .unwrap ( ) ;
7811
assert_eq! ( repo.disk_differs_by_dirs ( & m) , None ) ;
7812
}
7813
e226a6cc Andy McCall 1 month ago ↶
7814
7815
7816
7817
# [ test ]
7818
fn a_commit_after_a_status_uses_the_cached_snapshot ( ) {
7819
hermetic ( ) ;
7820
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7821
let root = dir.path ( ) ;
7822
let repo = Repo:: init( root) .unwrap ( ) ;
7823
write ( root, " a.txt" , b " a\n b\n " ) ;
7824
write ( root, " .oxyattributes" , b " *.bin binary\n " ) ;
7825
repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
7826
write ( root, " a.txt" , b " a\n B\n " ) ;
7827
write ( root, " new.txt" , b " new\n " ) ;
7828
let snap = repo.snapshot_status ( " oxy status" ) .unwrap ( ) ;
7829
let cached = repo
7830
.snapshot_from_cache ( )
7831
.unwrap ( )
7832
.expect ( " the cache holds" ) ;
7833
assert_eq! ( cached.wc_hash, snap.wc_hash) ;
7834
assert_eq! ( cached.statuses, snap.statuses) ;
7835
assert! (
7836
! cached.attributes.is_empty ( ) ,
7837
" the .oxyattributes file was read"
7838
) ;
7839
let h = repo.commit ( " edit" , & [ ] ) .unwrap ( ) ;
7840
let c = repo.store ( ) .get_change ( & h) .unwrap ( ) ;
7841
assert_eq! (
7842
c.tree_ops.len ( ) + c.edits.len ( ) ,
7843
snap.wc.tree_ops.len ( ) + snap.wc.edits.len ( )
7844
) ;
7845
assert_eq! (
7846
repo.status_by_manifest ( ) .unwrap ( ) ,
7847
Some ( vec! [ ] ) ,
7848
" clean after the commit"
7849
) ;
7850
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " a\n B\n " ) ;
7851
}
7852
0389c3e8 Andy McCall 1 month ago ↶
7853
7854
7855
7856
7857
# [ test ]
7858
fn a_verified_clean_check_restamps_the_manifest_safely ( ) {
7859
hermetic ( ) ;
7860
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7861
let root = dir.path ( ) ;
7862
let repo = Repo:: init( root) .unwrap ( ) ;
7863
write ( root, " a.txt" , b " one\n " ) ;
7864
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
7865
let before = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) .2. written_ns;
7866
assert! ( repo.clean_by_manifest ( ) .unwrap ( ) ) ;
7867
let after = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) .2. written_ns;
7868
assert! ( after > before, " restamped" ) ;
7869
write ( root, " a.txt" , b " two\n " ) ;
7870
assert! (
7871
! repo.clean_by_manifest ( ) .unwrap ( ) ,
7872
" a same-size rewrite is still seen"
7873
) ;
7874
}
7875
86e1824f Andy McCall 1 month ago ↶
7876
7877
7878
7879
# [ test ]
7880
fn history_only_states_read_text_only_for_the_graphs_they_build ( ) {
7881
hermetic ( ) ;
7882
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7883
let root = dir.path ( ) ;
7884
let repo = Repo:: init( root) .unwrap ( ) ;
7885
let big: String = ( 0 .. 3000 ) .map ( | i | format! ( " big line {i} \n " ) ) .collect ( ) ;
7886
let other: String = ( 0 .. 3000 ) .map ( | i | format! ( " other line {i} \n " ) ) .collect ( ) ;
7887
write ( root, " big.txt" , big.as_bytes ( ) ) ;
7888
write ( root, " other.txt" , other.as_bytes ( ) ) ;
7889
let h = repo.commit ( " two large files" , & [ ] ) .unwrap ( ) ;
7890
write ( root, " big.txt" , format! ( " {big} one more\n " ) .as_bytes ( ) ) ;
7891
let h2 = repo.commit ( " append" , & [ ] ) .unwrap ( ) ;
7892
7893
let full = repo.load_state_full ( & [ h2] ) .unwrap ( ) .materialize ( ) ;
7894
let text = | snap : & crate:: state:: Snapshot, p : & str | match & snap.files[ p] .content {
7895
FileContent:: Text { bytes, .. } => bytes.clone ( ) ,
7896
other => panic! ( " {other:?}" ) ,
7897
} ;
7898
7899
let mut hist = repo.load_state_history ( & [ h2] ) .unwrap ( ) ;
7900
let big_node = full.files[ " big.txt" ] .node;
7901
assert! ( hist.graph_key ( & big_node) .is_some ( ) , " not built yet" ) ;
7902
repo.load_all_graphs ( & mut hist) .unwrap ( ) ;
7903
let rendered = hist.materialize ( ) ;
7904
for p in [ " big.txt" , " other.txt" ] {
7905
assert_eq! ( text ( & rendered, p) , text ( & full, p) , " {p}" ) ;
7906
}
7907
7908
7909
7910
let ( _ , texts) = repo.store ( ) .get_change_lazy ( & h) .unwrap ( ) ;
7911
assert_eq! ( texts.len ( ) , 2 ) ;
7912
let content = | r : & ObjectRef| -> Vec< u8 > {
7913
let blob = crate :: objects:: Blob:: decode( & repo.store ( ) .get ( r) .unwrap ( ) .1 ) .unwrap ( ) ;
7914
blob.chunks
7915
.iter ( )
7916
.flat_map ( | c | repo.store ( ) .get ( c) .unwrap ( ) .1 )
7917
.collect ( )
7918
} ;
7919
let other_text = texts
7920
.iter ( )
a4fad5b0 Andy McCall 1 month ago ↶
7921
.filter_map ( | t | match t {
0f93f49c Andy McCall 1 month ago ↶
7922
crate :: objects:: TextRef:: Blob( r, _ , _ ) => Some ( * r) ,
a4fad5b0 Andy McCall 1 month ago ↶
7923
_ => None ,
7924
} )
86e1824f Andy McCall 1 month ago ↶
7925
.find ( | r | content ( r) .windows ( 12 ) .any ( | w | w = = b " other line 1" ) )
7926
.unwrap ( ) ;
64818938 Andy McCall 1 month ago ↶
7927
repo.store ( ) .remove_loose_for_test ( & other_text) .unwrap ( ) ;
86e1824f Andy McCall 1 month ago ↶
7928
let mut hist = repo.load_state_history ( & [ h2] ) .unwrap ( ) ;
7929
repo.load_graphs_of ( & mut hist, & [ big_node] ) .unwrap ( ) ;
7930
assert! ( repo
7931
.load_graphs_of ( & mut hist, & [ full.files[ " other.txt" ] .node] )
7932
.is_err ( ) ) ;
7933
}
7934
7935
7936
7937
7938
# [ test ]
7939
fn a_commit_leaves_a_manifest_the_next_status_trusts ( ) {
7940
use std:: os:: unix:: fs:: PermissionsExt;
7941
hermetic ( ) ;
7942
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7943
let root = dir.path ( ) ;
7944
let repo = Repo:: init( root) .unwrap ( ) ;
7945
let files = [ " a.txt" , " b.txt" , " d/c.txt" ] ;
7946
for p in files {
7947
write ( root, p, format! ( " {p} \n " ) .as_bytes ( ) ) ;
7948
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
7949
fs:: File:: options( )
7950
.write ( true )
7951
.open ( root.join ( p) )
7952
.unwrap ( )
7953
.set_modified ( old)
7954
.unwrap ( ) ;
7955
}
7956
repo.commit ( " added" , & [ ] ) .unwrap ( ) ;
7957
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
7958
assert! (
7959
m.entries.values ( ) .all ( | e | e.graph.is_some ( ) ) ,
7960
" keys recorded at commit"
7961
) ;
7962
7963
for p in files {
7964
fs:: set_permissions( root.join ( p) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
7965
}
7966
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
7967
for p in files {
7968
fs:: set_permissions( root.join ( p) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
7969
}
7970
assert! ( ! snap.changed & & snap.statuses.is_empty ( ) ) ;
7971
assert_eq! ( snap.base_snapshot.unrendered.len ( ) , files.len ( ) ) ;
7972
}
c5ce0367 Andy McCall 1 month ago ↶
7973
7974
7975
7976
# [ test ]
7977
fn checkout_touches_only_what_differs_and_renders_in_batches ( ) {
7978
use std:: os:: unix:: fs:: PermissionsExt;
7979
hermetic ( ) ;
7980
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
7981
let root = dir.path ( ) ;
7982
let origin = root.join ( " origin" ) ;
7983
let mut repo = Repo:: init( & origin) .unwrap ( ) ;
7984
7985
let n = 2100 ;
7986
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
7987
for i in 0 .. n {
7988
let p = format! ( " f/{i:04} .txt" ) ;
7989
write ( & origin, & p, format! ( " file {i} \n " ) .as_bytes ( ) ) ;
7990
fs:: File:: options( )
7991
.write ( true )
7992
.open ( origin.join ( & p) )
7993
.unwrap ( )
7994
.set_modified ( old)
7995
.unwrap ( ) ;
7996
}
7997
repo.commit ( " many" , & [ ] ) .unwrap ( ) ;
7998
repo.bookmark_create ( " feature" , None ) .unwrap ( ) ;
7999
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
8000
write ( & origin, " f/0007.txt" , b " changed\n " ) ;
8001
repo.commit ( " one change" , & [ ] ) .unwrap ( ) ;
8002
8003
8004
for i in 0 .. n {
8005
if i ! = 7 {
8006
let p = origin.join ( format! ( " f/{i:04} .txt" ) ) ;
8007
fs:: set_permissions( & p, fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
8008
}
8009
}
8010
let r = repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
8011
for i in 0 .. n {
8012
let p = origin.join ( format! ( " f/{i:04} .txt" ) ) ;
8013
fs:: set_permissions( & p, fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
8014
}
8015
assert_eq! ( ( r.written, r.removed) , ( 1 , 0 ) , " {r:?}" ) ;
8016
assert_eq! ( fs:: read( origin.join ( " f/0007.txt" ) ) .unwrap ( ) , b " file 7\n " ) ;
8017
assert! ( repo.snapshot_status ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
8018
8019
8020
let c = root.join ( " clone" ) ;
8021
Repo:: clone( & origin.to_string_lossy ( ) , & c) .unwrap ( ) ;
8022
for i in [ 0 , 2047 , 2048 , n - 1 ] {
8023
assert_eq! (
8024
fs:: read( c.join ( format! ( " f/{i:04} .txt" ) ) ) .unwrap ( ) ,
8025
format! ( " file {i} \n " ) .as_bytes ( )
8026
) ;
8027
}
8028
let cloned = Repo:: open( & c) .unwrap ( ) ;
8029
assert! ( cloned
8030
.snapshot_status ( " status" )
8031
.unwrap ( )
8032
.statuses
8033
.is_empty ( ) ) ;
d3cd7ef9 Andy McCall 1 month ago ↶
8034
}
8035
8036
8037
8038
8039
# [ test ]
8040
fn a_racy_manifest_entry_is_checked_by_hash ( ) {
8041
hermetic ( ) ;
8042
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8043
let root = dir.path ( ) ;
8044
let repo = Repo:: init( root) .unwrap ( ) ;
8045
write ( root, " a.txt" , b " one\n " ) ;
8046
repo.commit ( " fresh" , & [ ] ) .unwrap ( ) ;
8047
assert! ( repo.clean_by_manifest ( ) .unwrap ( ) , " fresh but equal" ) ;
8048
let t = fs:: metadata( root.join ( " a.txt" ) )
8049
.unwrap ( )
8050
.modified ( )
8051
.unwrap ( ) ;
8052
write ( root, " a.txt" , b " two\n " ) ;
8053
fs:: File:: options( )
8054
.write ( true )
8055
.open ( root.join ( " a.txt" ) )
8056
.unwrap ( )
8057
.set_modified ( t)
8058
.unwrap ( ) ;
8059
assert! (
8060
! repo.clean_by_manifest ( ) .unwrap ( ) ,
8061
" same size and time, other bytes"
8062
) ;
8063
assert_eq! (
8064
statuses ( & repo.snapshot_status ( " status" ) .unwrap ( ) ) ,
8065
vec! [ ( " a.txt" .to_string ( ) , ChangeStatus:: Modified) ]
8066
) ;
c5ce0367 Andy McCall 1 month ago ↶
8067
}
2c75672f Andy McCall 1 month ago ↶
8068
8069
8070
8071
# [ test ]
8072
fn a_raw_bundle_without_a_changes_text_is_refused ( ) {
8073
hermetic ( ) ;
8074
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8075
let root = dir.path ( ) ;
8076
let a = Repo:: init( & root.join ( " a" ) ) .unwrap ( ) ;
8077
let big: String = ( 0 .. 3000 ) .map ( | i | format! ( " line {i} \n " ) ) .collect ( ) ;
8078
write ( & root.join ( " a" ) , " big.txt" , big.as_bytes ( ) ) ;
8079
let h = a.commit ( " big" , & [ ] ) .unwrap ( ) ;
8080
let bundle = a.bundle_raw_for ( & [ h] , None ) .unwrap ( ) ;
8081
assert_eq! ( bundle.raw_changes.len ( ) , 1 ) ;
8082
assert! ( bundle.changes.is_empty ( ) ) ;
8083
let b = Repo:: init( & root.join ( " b" ) ) .unwrap ( ) ;
8084
let mut without = bundle.clone ( ) ;
8085
without.objects.retain ( | ( ty , _) | * ty ! = ObjectType:: Blob) ;
8086
let e = b.import_bundle ( & without, false ) .unwrap_err ( ) ;
8087
assert! ( e.to_string ( ) .contains ( " came without its text blob" ) , " {e}" ) ;
8088
assert! ( ! b.store ( ) .has ( & h) ) ;
8089
assert_eq! ( b.import_bundle ( & bundle, false ) .unwrap ( ) , vec! [ h] ) ;
8090
assert_eq! ( b.store ( ) .get_change ( & h) .unwrap ( ) .message, " big" ) ;
8091
}
a87c7b9d Andy McCall 1 month ago ↶
8092
8093
8094
8095
8096
8097
# [ test ]
8098
fn git_import_brings_a_directory_back_rather_than_making_a_second ( ) {
8099
hermetic ( ) ;
8100
if std:: process:: Command:: new( " git" )
8101
.arg ( " --version" )
8102
.output ( )
8103
.is_err ( )
8104
{
8105
eprintln! ( " git not installed; skipping" ) ;
8106
return ;
8107
}
8108
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8109
let root = dir.path ( ) ;
8110
let g = root.join ( " src.git" ) ;
8111
fs:: create_dir_all( & g) .unwrap ( ) ;
8112
let git = | args : & [& str ]| -> String {
8113
let out = std:: process:: Command:: new( " git" )
8114
.arg ( " -C" )
8115
.arg ( & g)
8116
.args ( [
8117
" -c" ,
8118
" user.name=Grace" ,
8119
" -c" ,
8120
" [email protected] " ,
8121
" -c" ,
8122
" commit.gpgsign=false" ,
8123
] )
8124
.args ( args)
8125
.output ( )
8126
.unwrap ( ) ;
8127
assert! (
8128
out.status.success ( ) ,
8129
" git {args:?}: {}" ,
8130
String :: from_utf8_lossy( & out.stderr)
8131
) ;
8132
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
8133
} ;
8134
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
8135
write ( & g, " d/a.txt" , b " a\n " ) ;
8136
write ( & g, " c/x.txt" , b " x\n " ) ;
8137
git ( & [ " add" , " ." ] ) ;
8138
git ( & [ " commit" , " -qm" , " base" ] ) ;
8139
8140
git ( & [ " checkout" , " -qb" , " side" ] ) ;
8141
git ( & [ " mv" , " c/x.txt" , " d/x.txt" ] ) ;
8142
git ( & [ " commit" , " -qm" , " x into d" ] ) ;
8143
8144
git ( & [ " checkout" , " -q" , " main" ] ) ;
8145
git ( & [ " rm" , " -q" , " d/a.txt" ] ) ;
8146
git ( & [ " commit" , " -qm" , " d goes" ] ) ;
8147
write ( & g, " d/b.txt" , b " b\n " ) ;
8148
git ( & [ " add" , " ." ] ) ;
8149
git ( & [ " commit" , " -qm" , " d comes back" ] ) ;
8150
git ( & [ " mv" , " c/x.txt" , " d/x.txt" ] ) ;
8151
git ( & [ " commit" , " -qm" , " x into d again" ] ) ;
8152
git ( & [ " merge" , " -q" , " --no-edit" , " side" ] ) ;
8153
8154
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
8155
repo.import_git ( & g, " main" ) .unwrap ( ) ;
8156
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
8157
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
8158
let st = repo
8159
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
8160
.unwrap ( ) ;
8161
let view = st.tree ( ) .view ( ) ;
8162
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
8163
8164
8165
let first = repo
8166
.log ( )
8167
.unwrap ( )
8168
.into_iter ( )
8169
.find ( | e | e.change.message.starts_with ( " base" ) )
8170
.unwrap ( ) ;
8171
let base = repo
8172
.load_state_history ( & [ first.hash] )
8173
.unwrap ( )
8174
.tree ( )
8175
.view ( ) ;
8176
assert_eq! ( view.entries[ " d" ] .node, base.entries[ " d" ] .node) ;
8177
}
8178
8179
8180
8181
8182
# [ test ]
8183
fn git_import_merge_retires_a_placement_under_a_removed_directory ( ) {
8184
hermetic ( ) ;
8185
if std:: process:: Command:: new( " git" )
8186
.arg ( " --version" )
8187
.output ( )
8188
.is_err ( )
8189
{
8190
eprintln! ( " git not installed; skipping" ) ;
8191
return ;
8192
}
8193
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8194
let root = dir.path ( ) ;
8195
let g = root.join ( " src.git" ) ;
8196
fs:: create_dir_all( & g) .unwrap ( ) ;
8197
let run = | args : & [& str ]| {
8198
std:: process:: Command:: new( " git" )
8199
.arg ( " -C" )
8200
.arg ( & g)
8201
.args ( [
8202
" -c" ,
8203
" user.name=Grace" ,
8204
" -c" ,
8205
" [email protected] " ,
8206
" -c" ,
8207
" commit.gpgsign=false" ,
8208
] )
8209
.args ( args)
8210
.output ( )
8211
.unwrap ( )
8212
} ;
8213
let git = | args : & [& str ]| {
8214
let out = run ( args) ;
8215
assert! (
8216
out.status.success ( ) ,
8217
" git {args:?}: {}" ,
8218
String :: from_utf8_lossy( & out.stderr)
8219
) ;
8220
} ;
8221
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
8222
write ( & g, " c/x.txt" , b " x\n " ) ;
8223
write ( & g, " keep.txt" , b " k\n " ) ;
8224
git ( & [ " add" , " ." ] ) ;
8225
git ( & [ " commit" , " -qm" , " base" ] ) ;
8226
8227
8228
git ( & [ " checkout" , " -qb" , " side" ] ) ;
8229
git ( & [ " mv" , " c/x.txt" , " c/y.txt" ] ) ;
8230
git ( & [ " commit" , " -qm" , " rename within c" ] ) ;
8231
git ( & [ " checkout" , " -q" , " main" ] ) ;
8232
fs:: create_dir_all( g.join ( " n" ) ) .unwrap ( ) ;
8233
git ( & [ " mv" , " c/x.txt" , " n/x.txt" ] ) ;
8234
git ( & [ " commit" , " -qm" , " move out of c" ] ) ;
8235
8236
let _ = run ( & [ " merge" , " -q" , " side" ] ) ;
8237
let _ = run ( & [
8238
" rm" ,
8239
" -q" ,
8240
" --cached" ,
8241
" --ignore-unmatch" ,
8242
" c/y.txt" ,
8243
" c/x.txt" ,
8244
] ) ;
8245
let _ = std:: fs:: remove_dir_all( g.join ( " c" ) ) ;
8246
write ( & g, " n/x.txt" , b " x\n " ) ;
8247
git ( & [ " add" , " -A" ] ) ;
8248
git ( & [ " commit" , " -qm" , " merge: keep n/x.txt" ] ) ;
8249
8250
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
8251
repo.import_git ( & g, " main" ) .unwrap ( ) ;
8252
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
8253
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
8254
let st = repo
8255
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
8256
.unwrap ( ) ;
8257
let view = st.tree ( ) .view ( ) ;
8258
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
8259
assert! ( view.entries.contains_key ( " n/x.txt" ) ) ;
8260
}
8261
8262
8263
8264
8265
# [ test ]
8266
fn git_import_merge_leaves_no_orphan_behind ( ) {
8267
hermetic ( ) ;
8268
if std:: process:: Command:: new( " git" )
8269
.arg ( " --version" )
8270
.output ( )
8271
.is_err ( )
8272
{
8273
eprintln! ( " git not installed; skipping" ) ;
8274
return ;
8275
}
8276
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8277
let root = dir.path ( ) ;
8278
let g = root.join ( " src.git" ) ;
8279
fs:: create_dir_all( & g) .unwrap ( ) ;
8280
let git = | args : & [& str ]| {
8281
let out = std:: process:: Command:: new( " git" )
8282
.arg ( " -C" )
8283
.arg ( & g)
8284
.args ( [
8285
" -c" ,
8286
" user.name=Grace" ,
8287
" -c" ,
8288
" [email protected] " ,
8289
" -c" ,
8290
" commit.gpgsign=false" ,
8291
] )
8292
.args ( args)
8293
.output ( )
8294
.unwrap ( ) ;
8295
assert! (
8296
out.status.success ( ) ,
8297
" git {args:?}: {}" ,
8298
String :: from_utf8_lossy( & out.stderr)
8299
) ;
8300
} ;
8301
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
8302
write ( & g, " d/f.txt" , b " f\n " ) ;
8303
write ( & g, " keep.txt" , b " k\n " ) ;
8304
git ( & [ " add" , " ." ] ) ;
8305
git ( & [ " commit" , " -qm" , " base" ] ) ;
8306
git ( & [ " checkout" , " -qb" , " side" ] ) ;
8307
write ( & g, " d/new.txt" , b " new\n " ) ;
8308
git ( & [ " add" , " ." ] ) ;
8309
git ( & [ " commit" , " -qm" , " add into d" ] ) ;
8310
git ( & [ " checkout" , " -q" , " main" ] ) ;
8311
git ( & [ " rm" , " -q" , " d/f.txt" ] ) ;
8312
git ( & [ " commit" , " -qm" , " d goes" ] ) ;
8313
git ( & [ " merge" , " -q" , " --no-edit" , " side" ] ) ;
8314
8315
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
8316
repo.import_git ( & g, " main" ) .unwrap ( ) ;
8317
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
8318
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
8319
let st = repo
8320
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
8321
.unwrap ( ) ;
8322
let view = st.tree ( ) .view ( ) ;
8323
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
8324
assert! ( view.entries.contains_key ( " d/new.txt" ) ) ;
8325
}
d77e1702 Andy McCall 1 month ago ↶
8326
8327
8328
8329
# [ test ]
8330
fn fsck_names_a_missing_text_blob ( ) {
8331
hermetic ( ) ;
8332
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8333
let root = dir.path ( ) ;
8334
let repo = Repo:: init( root) .unwrap ( ) ;
8335
let big: String = ( 0 .. 3000 ) .map ( | i | format! ( " line {i} \n " ) ) .collect ( ) ;
8336
write ( root, " big.txt" , big.as_bytes ( ) ) ;
8337
let h = repo.commit ( " big" , & [ ] ) .unwrap ( ) ;
8338
assert_eq! ( repo.fsck ( ) .unwrap ( ) .errors ( ) , 0 ) ;
8339
let ( _ , texts) = repo.store ( ) .get_change_lazy ( & h) .unwrap ( ) ;
8340
let Some ( crate :: objects:: TextRef:: Blob( b, _ , _ ) ) = texts.first ( ) .copied ( ) else {
8341
panic! ( " a stored text" )
8342
} ;
64818938 Andy McCall 1 month ago ↶
8343
repo.store ( ) .remove_loose_for_test ( & b) .unwrap ( ) ;
d77e1702 Andy McCall 1 month ago ↶
8344
let rep = repo.fsck ( ) .unwrap ( ) ;
8345
assert! ( rep.errors ( ) > 0 ) ;
8346
assert! (
8347
rep.problems.iter ( ) .any ( | p | p.what.contains ( & h.short ( 12 ) ) ) ,
8348
" {:?}" ,
8349
rep.problems
8350
) ;
8351
}
a07f60a3 Andy McCall 1 month ago ↶
8352
8353
8354
8355
8356
# [ test ]
8357
fn a_subrepo_on_the_parents_hub_borrows_its_credentials ( ) {
8358
hermetic ( ) ;
8359
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8360
let mut repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
8361
repo.config
8362
.set (
8363
" remote.origin.url" ,
8364
" https://Hub.example:7343/doomsdayone/metalgear" ,
8365
)
8366
.unwrap ( ) ;
8367
repo.config.set ( " remote.origin.token" , " secret" ) .unwrap ( ) ;
8368
repo.config
8369
.set ( " remote.origin.ca" , " /tmp/hub-ca.pem" )
8370
.unwrap ( ) ;
8371
let same = repo.credentials_for ( " https://hub.example:7343/doomsdayone/rstar-api" ) ;
8372
assert_eq! ( same.token.as_deref ( ) , Some ( " secret" ) ) ;
8373
assert_eq! ( same.ca.as_deref ( ) , Some ( Path:: new( " /tmp/hub-ca.pem" ) ) ) ;
8374
for other in [
8375
" https://elsewhere.example:7343/doomsdayone/rstar-api" ,
8376
" https://hub.example/doomsdayone/rstar-api" ,
8377
" http://hub.example:7343/doomsdayone/rstar-api" ,
8378
] {
8379
let o = repo.credentials_for ( other) ;
8380
assert_ne! ( o.token.as_deref ( ) , Some ( " secret" ) , " {other}" ) ;
8381
}
8382
}
ce14f0d6 Andy McCall 1 month ago ↶
8383
ff1f62fe Andy McCall 1 month ago ↶
8384
8385
8386
ce14f0d6 Andy McCall 1 month ago ↶
8387
# [ test ]
ff1f62fe Andy McCall 1 month ago ↶
8388
fn remotes_use_the_credential_store_and_the_enclosing_checkout ( ) {
8389
hermetic ( ) ;
8390
let home = tempfile:: tempdir( ) .unwrap ( ) ;
8391
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8392
let mut parent = Repo:: init( dir.path ( ) ) .unwrap ( ) .with_home ( home.path ( ) ) ;
8393
parent
8394
.config
8395
.set ( " remote.origin.url" , " https://hub.example/o/parent" )
8396
.unwrap ( ) ;
8397
parent.save_config ( ) .unwrap ( ) ;
8398
assert_eq! ( parent.remote_options ( " origin" ) .token, None ) ;
8399
crate :: credentials:: Credentials:: at( home.path ( ) )
8400
.save ( " https://hub.example" , " stored" , Some ( " andy" ) , None )
8401
.unwrap ( ) ;
8402
assert_eq! (
8403
parent.remote_options ( " origin" ) .token.as_deref ( ) ,
8404
Some ( " stored" )
8405
) ;
8406
8407
let empty_home = tempfile:: tempdir( ) .unwrap ( ) ;
8408
let nested = dir.path ( ) .join ( " deps" ) .join ( " child" ) ;
8409
let mut child = Repo:: init( & nested) .unwrap ( ) .with_home ( empty_home.path ( ) ) ;
8410
child
8411
.config
8412
.set ( " remote.origin.url" , " https://hub.example/o/child" )
8413
.unwrap ( ) ;
8414
child.save_config ( ) .unwrap ( ) ;
8415
assert_eq! ( child.remote_options ( " origin" ) .token, None ) ;
8416
parent
8417
.config
8418
.set ( " remote.origin.token" , " checkout" )
8419
.unwrap ( ) ;
8420
parent.save_config ( ) .unwrap ( ) ;
8421
assert_eq! (
8422
child.remote_options ( " origin" ) .token.as_deref ( ) ,
8423
Some ( " checkout" )
8424
) ;
8425
8426
child
8427
.config
8428
.set ( " remote.origin.url" , " https://other.example/o/child" )
8429
.unwrap ( ) ;
8430
assert_eq! ( child.remote_options ( " origin" ) .token, None ) ;
8431
}
8432
8433
# [ test ]
ce14f0d6 Andy McCall 1 month ago ↶
8434
fn the_metadata_cache_survives_a_torn_tail ( ) {
8435
hermetic ( ) ;
8436
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8437
let root = dir.path ( ) ;
8438
let repo = Repo:: init( root) .unwrap ( ) ;
8439
write ( root, " a.txt" , b " 1\n " ) ;
8440
let h = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8441
let meta = repo.store ( ) .get_change_meta ( & h) .unwrap ( ) ;
8442
let cache = repo.store ( ) .dir ( ) .join ( " metas" ) ;
8443
let len = fs:: metadata( & cache) .unwrap ( ) .len ( ) ;
8444
assert! ( len > 0 , " a change read is cached" ) ;
8445
8446
fs:: OpenOptions:: new( )
8447
.append ( true )
8448
.open ( & cache)
8449
.unwrap ( )
8450
.write_all ( & [ 7 ; 20 ] )
8451
.unwrap ( ) ;
8452
let again = Repo:: open( root) .unwrap ( ) ;
8453
assert_eq! ( again.store ( ) .get_change_meta ( & h) .unwrap ( ) , meta) ;
8454
assert_eq! ( fs:: metadata( & cache) .unwrap ( ) .len ( ) , len) ;
8455
}
f9466021 Andy McCall 1 month ago ↶
8456
8457
# [ test ]
8458
fn a_manifest_that_does_not_check_reads_as_none ( ) {
8459
hermetic ( ) ;
8460
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8461
let root = dir.path ( ) ;
8462
let repo = Repo:: init( root) .unwrap ( ) ;
8463
write ( root, " a.txt" , b " 1\n " ) ;
8464
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8465
let path = repo.manifest_path ( ) ;
8466
let mut bytes = fs:: read( & path) .unwrap ( ) ;
8467
assert! ( Repo:: open( root) .unwrap ( ) .read_manifest ( ) .unwrap ( ) .is_some ( ) ) ;
8468
let last = bytes.len ( ) - 1 ;
8469
bytes[ last] ^ = 1 ;
8470
fs:: write( & path, & bytes) .unwrap ( ) ;
8471
assert! ( Repo:: open( root) .unwrap ( ) .read_manifest ( ) .unwrap ( ) .is_none ( ) ) ;
8472
8473
let again = Repo:: open( root) .unwrap ( ) ;
8474
assert! ( ! again.snapshot ( " status" ) .unwrap ( ) .changed) ;
8475
assert! ( again.read_manifest ( ) .unwrap ( ) .is_some ( ) ) ;
8476
}
8477
8478
# [ test ]
8479
fn a_commit_leaves_a_manifest_that_proves_the_working_copy_clean ( ) {
8480
hermetic ( ) ;
8481
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8482
let root = dir.path ( ) ;
8483
let repo = Repo:: init( root) .unwrap ( ) ;
8484
write ( root, " a.txt" , b " 1\n " ) ;
8485
write ( root, " b.txt" , b " 2\n " ) ;
8486
8487
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 60 ) ;
8488
fs:: File:: options( )
8489
.write ( true )
8490
.open ( root.join ( " b.txt" ) )
8491
.unwrap ( )
8492
.set_modified ( old)
8493
.unwrap ( ) ;
8494
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8495
assert! ( repo.clean_by_manifest ( ) .unwrap ( ) , " after the first commit" ) ;
8496
repo.snapshot_status ( " status" ) .unwrap ( ) ;
8497
write ( root, " a.txt" , b " 1\n 2\n " ) ;
8498
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
8499
assert! (
8500
repo.clean_by_manifest ( ) .unwrap ( ) ,
8501
" after a commit of one file"
8502
) ;
8503
8504
8505
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
8506
repo.snapshot_status ( " status" ) .unwrap ( ) ;
8507
write ( root, " a.txt" , b " 1\n 2\n 3\n 4\n 5\n " ) ;
8508
repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
8509
assert! (
8510
repo.clean_by_manifest ( ) .unwrap ( ) ,
8511
" after an edit a status saw"
8512
) ;
8513
}
e266b7d4 Andy McCall 1 month ago ↶
8514
8515
# [ test ]
8516
fn a_conflicted_rebase_can_be_resolved_in_the_working_copy ( ) {
8517
hermetic ( ) ;
8518
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8519
let root = dir.path ( ) ;
8520
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8521
write ( root, " a.txt" , b " 1\n 2\n " ) ;
8522
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
8523
repo.bookmark_create ( " develop" , Some ( & [ a] ) ) .unwrap ( ) ;
8524
write ( root, " a.txt" , b " 1\n X\n " ) ;
8525
let c = repo.commit ( " C: 2 to X" , & [ ] ) .unwrap ( ) ;
8526
repo.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
8527
write ( root, " a.txt" , b " 1\n Y\n " ) ;
8528
let d = repo.commit ( " D: 2 to Y" , & [ ] ) .unwrap ( ) ;
8529
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
8530
8531
let err = repo.rebase ( & c, & [ d] ) .unwrap_err ( ) ;
8532
assert! ( matches! ( err, RepoError:: Conflicted( _ ) ) , " {err}" ) ;
8533
let r = repo.rebase_into_working_copy ( & c, & [ d] ) .unwrap ( ) ;
8534
assert! ( r.changed & & r.checked_out) ;
8535
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8536
assert! (
8537
snap.base_snapshot.is_conflicted ( ) ,
8538
" the conflict is in the working copy"
8539
) ;
8540
let text = fs:: read_to_string( root.join ( " a.txt" ) ) .unwrap ( ) ;
8541
assert! ( text.contains ( ' X' ) & & text.contains ( ' Y' ) , " {text}" ) ;
8542
8543
write ( root, " a.txt" , b " 1\n XY\n " ) ;
8544
repo.commit ( " Resolve" , & [ ] ) .unwrap ( ) ;
8545
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
8546
assert! ( st.contains ( & c) & & st.contains ( & d) ) ;
8547
let files = st.materialize ( ) .files;
8548
assert! ( ! files[ " a.txt" ] .is_conflicted ( ) ) ;
8549
assert! (
8550
matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 1\n XY\n " )
8551
) ;
8552
8553
8554
repo.bookmark_switch ( " develop" , false ) .unwrap ( ) ;
8555
write ( root, " b.txt" , b " b\n " ) ;
8556
let e = repo.commit ( " E" , & [ ] ) .unwrap ( ) ;
8557
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
8558
let err = repo.rebase_into_working_copy ( & e, & [ a] ) .unwrap_err ( ) ;
8559
assert! (
8560
err.to_string ( ) .contains ( " not on the current bookmark" ) ,
8561
" {err}"
8562
) ;
8563
}
d4dbab97 Andy McCall 1 month ago ↶
8564
8565
# [ test ]
8566
fn an_ignore_rule_drops_an_added_file_but_not_a_tracked_one ( ) {
8567
hermetic ( ) ;
8568
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8569
let root = dir.path ( ) ;
8570
let repo = Repo:: init( root) .unwrap ( ) ;
8571
write ( root, " a.txt" , b " a\n " ) ;
8572
write ( root, " kept.log" , b " tracked\n " ) ;
8573
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8574
write ( root, " build.log" , b " made\n " ) ;
8575
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8576
assert! ( snap.statuses.iter ( ) .any ( | s | s.path = = " build.log" ) ) ;
8577
8578
8579
write ( root, " .gitignore" , b " *.log\n " ) ;
8580
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8581
let paths: Vec< & str > = snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect ( ) ;
8582
assert_eq! ( paths, vec! [ " .gitignore" ] , " {paths:?}" ) ;
8583
write ( root, " kept.log" , b " tracked, edited\n " ) ;
8584
let snap = repo.snapshot_status ( " status" ) .unwrap ( ) ;
8585
assert! ( snap.statuses.iter ( ) .any ( | s | s.path = = " kept.log" ) ) ;
8586
}
fd280228 Andy McCall 1 month ago ↶
8587
8588
# [ test ]
8589
fn shed_graphs_come_back_the_same ( ) {
8590
hermetic ( ) ;
8591
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8592
let root = dir.path ( ) ;
8593
let repo = Repo:: init( root) .unwrap ( ) ;
8594
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
8595
write ( root, " b.txt" , b " x\n " ) ;
8596
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8597
write ( root, " a.txt" , b " 1\n two\n 3\n 4\n " ) ;
8598
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
8599
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
8600
let mut st = repo.load_state_history ( & heads) .unwrap ( ) ;
8601
let view = st.tree ( ) .view ( ) ;
8602
let nodes: Vec< NodeId> = [ " a.txt" , " b.txt" ]
8603
.iter ( )
8604
.map ( | p | view.entries[ * p] .node )
8605
.collect ( ) ;
8606
repo.load_graphs_of ( & mut st, & nodes) .unwrap ( ) ;
8607
let before: Vec< Vec< u8 > > = nodes.iter ( ) .map ( | n | st.render_file ( n) .0 ) .collect ( ) ;
8608
assert_eq! ( st.built_count ( ) , 2 ) ;
8609
let shed = st.shed_graphs ( | b | repo.store_blob ( b) ) .unwrap ( ) ;
8610
assert_eq! ( ( shed, st.built_count ( ) ) , ( 2 , 0 ) ) ;
8611
8612
write ( root, " a.txt" , b " 1\n two\n 3\n 4\n 5\n " ) ;
8613
let h3 = repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
8614
let ( skeleton, texts) = repo.store ( ) .get_change_lazy ( & h3) .unwrap ( ) ;
8615
st.add_known ( h3, skeleton, texts) ;
8616
repo.load_graphs_of ( & mut st, & nodes) .unwrap ( ) ;
8617
let after: Vec< Vec< u8 > > = nodes.iter ( ) .map ( | n | st.render_file ( n) .0 ) .collect ( ) ;
8618
assert_eq! ( after[ 1 ] , before[ 1 ] ) ;
8619
assert_eq! ( after[ 0 ] , b " 1\n two\n 3\n 4\n 5\n " ) ;
8620
}
360aa74a Andy McCall 1 month ago ↶
8621
8622
# [ test ]
8623
fn many_edited_files_diff_in_parallel ( ) {
8624
8625
8626
8627
hermetic ( ) ;
8628
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8629
let root = dir.path ( ) ;
8630
let repo = Repo:: init( root) .unwrap ( ) ;
8631
let text = | i : usize , round : usize | {
8632
let mut s = String :: new( ) ;
8633
for line in 0 .. 50 {
8634
if round > 0 & & line % 7 = = i % 7 {
8635
s.push_str ( & format! ( " edited {round} {i} {line} \n " ) ) ;
8636
} else {
8637
s.push_str ( & format! ( " file {i} line {line} \n " ) ) ;
8638
}
8639
}
8640
if round > 0 & & i.is_multiple_of ( 3 ) {
8641
s.push_str ( " appended\n " ) ;
8642
}
8643
s
8644
} ;
8645
for i in 0 .. 40 {
8646
write ( root, & format! ( " d/f{i} .txt" ) , text ( i, 0 ) .as_bytes ( ) ) ;
8647
}
8648
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
8649
for i in 0 .. 40 {
8650
write ( root, & format! ( " d/f{i} .txt" ) , text ( i, 1 ) .as_bytes ( ) ) ;
8651
}
8652
let h = repo.commit ( " edit every file" , & [ ] ) .unwrap ( ) ;
8653
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
8654
for i in 0 .. 40 {
8655
let want = text ( i, 1 ) ;
8656
assert! (
8657
matches! ( & snap.files[ & format! ( " d/f{i} .txt" ) ] .content, FileContent:: Text { bytes, .. } if bytes = = want.as_bytes ( ) ) ,
8658
" d/f{i}.txt as committed"
8659
) ;
8660
}
8661
assert! (
8662
repo.commit ( " nothing" , & [ ] ) .is_err ( ) ,
8663
" nothing left to commit"
8664
) ;
8665
}
b35df36b Andy McCall 1 month ago ↶
8666
8667
8668
8669
8670
8671
# [ test ]
8672
fn a_fetched_change_shows_as_behind_and_resolves ( ) {
8673
hermetic ( ) ;
8674
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8675
let root = dir.path ( ) ;
8676
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
8677
drop ( origin) ;
8678
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
8679
let a = Repo:: clone( & url, & root.join ( " a" ) )
8680
.unwrap ( )
8681
.with_home ( root.join ( " home-a" ) ) ;
8682
write ( & root.join ( " a" ) , " f.txt" , b " one\n " ) ;
8683
a.commit ( " one" , & [ ] ) .unwrap ( ) ;
8684
a.push ( " origin" , false ) .unwrap ( ) ;
8685
let b = Repo:: clone( & url, & root.join ( " b" ) )
8686
.unwrap ( )
8687
.with_home ( root.join ( " home-b" ) ) ;
8688
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 0 ) ) ) ;
8689
8690
write ( & root.join ( " a" ) , " f.txt" , b " one\n two\n " ) ;
8691
let h2 = a.commit ( " two" , & [ ] ) .unwrap ( ) ;
8692
a.push ( " origin" , false ) .unwrap ( ) ;
8693
b.fetch ( " origin" ) .unwrap ( ) ;
8694
8695
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 1 ) ) ) ;
8696
assert_eq! ( b.tracking ( " origin" , " nope" ) .unwrap ( ) , None ) ;
8697
assert_eq! (
8698
b.resolve_change ( & format! ( " #{} " , & h2.to_hex ( ) [ .. 12 ] ) )
8699
.unwrap ( ) ,
8700
h2
8701
) ;
8702
assert_eq! (
8703
b.remote_only_location ( & h2) .unwrap ( ) .as_deref ( ) ,
8704
Some ( " origin/main" )
8705
) ;
8706
8707
8708
b.fetch ( " origin" ) .unwrap ( ) ;
8709
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 1 ) ) ) ;
8710
b.pull ( " origin" ) .unwrap ( ) ;
8711
assert_eq! ( b.tracking ( " origin" , " main" ) .unwrap ( ) , Some ( ( 0 , 0 ) ) ) ;
8712
assert_eq! ( b.remote_only_location ( & h2) .unwrap ( ) , None ) ;
8713
8714
8715
let mut c = Repo:: init( & root.join ( " c" ) ) .unwrap ( ) ;
8716
assert_eq! ( c.default_remote ( ) , " origin" ) ;
8717
c.config.set ( " remote.earth.url" , & url) .unwrap ( ) ;
8718
assert_eq! ( c.default_remote ( ) , " earth" ) ;
8719
c.config.set ( " remote.origin.url" , & url) .unwrap ( ) ;
8720
assert_eq! ( c.default_remote ( ) , " origin" ) ;
8721
}
827281ee Andy McCall 1 month ago ↶
8722
8723
8724
8725
# [ test ]
8726
fn a_clone_from_a_relative_path_records_it_absolute ( ) {
8727
hermetic ( ) ;
8728
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8729
let root = dir.path ( ) ;
8730
Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
8731
let rel = crate :: protocol:: stored_url( " relative/repo" ) ;
8732
assert! ( std:: path:: Path:: new( & rel) .is_absolute ( ) , " {rel}" ) ;
8733
assert_eq! (
8734
crate :: protocol:: stored_url( " https://hub.example/o/n" ) ,
8735
" https://hub.example/o/n"
8736
) ;
8737
assert! ( crate :: protocol:: stored_url( " file://x" ) .starts_with ( " file:///" ) ) ;
8d803563 Andy McCall 1 month ago ↶
8738
assert! ( crate :: protocol:: stored_url( " dir://x" ) .starts_with ( " dir:///" ) ) ;
8739
8740
assert_eq! (
8741
crate :: protocol:: stored_url( " s3://bucket/prefix?profile=p" ) ,
8742
" s3://bucket/prefix?profile=p"
8743
) ;
827281ee Andy McCall 1 month ago ↶
8744
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
8745
let b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) ;
8746
assert_eq! ( b.config.remotes[ " origin" ] , url) ;
8747
b.fetch ( " origin" ) .unwrap ( ) ;
8748
}
a46f3e4f Andy McCall 1 month ago ↶
8749
8750
8751
8752
8753
# [ test ]
8754
fn a_workspace_added_on_a_bookmark_is_on_it_with_its_subrepos ( ) {
8755
hermetic ( ) ;
8756
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8757
let root = dir.path ( ) ;
8758
Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
8759
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
8760
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
8761
.unwrap ( )
8762
.with_home ( root.join ( " home" ) ) ;
8763
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
8764
let l1 = lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
8765
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
8766
8767
let app_dir = root.join ( " app" ) ;
8768
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8769
write ( & app_dir, " main.txt" , b " app\n " ) ;
8770
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
8771
app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
8772
8773
app.bookmark_create ( " develop" , None ) .unwrap ( ) ;
8774
app.bookmark_create ( " feature" , None ) .unwrap ( ) ;
8775
let mut app = app;
8776
app.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
8777
assert_eq! ( app.config.bookmark, " feature" ) ;
8778
8779
8780
std:: fs:: rename( root.join ( " lib-origin" ) , root.join ( " lib-origin-gone" ) ) .unwrap ( ) ;
8781
let heads = app.resolve_heads ( " develop" ) .unwrap ( ) ;
8782
let ws = app
8783
.workspace_add_on ( & root.join ( " ws" ) , Some ( & heads) , Some ( " develop" ) )
8784
.unwrap ( ) ;
8785
assert_eq! (
8786
ws.config.bookmark, " develop" ,
8787
" the new workspace is on develop"
8788
) ;
8789
assert_eq! (
8790
app.config.bookmark, " feature" ,
8791
" and this one stays on its own"
8792
) ;
8793
let sub = Repo:: open( & root.join ( " ws/libs/lib" ) ) .unwrap ( ) ;
8794
assert_eq! (
8795
sub.config.remotes[ " origin" ] , lib_url,
8796
" its origin is the subrepo's URL"
8797
) ;
8798
assert_eq! ( sub.bookmark_heads ( ) .unwrap ( ) , vec! [ l1] , " at its pin" ) ;
8799
assert_eq! (
8800
std:: fs:: read( root.join ( " ws/libs/lib/lib.txt" ) ) .unwrap ( ) ,
8801
b " v1\n "
8802
) ;
8803
}
47ce8b8e Andy McCall 1 month ago ↶
8804
8805
8806
8807
8808
# [ test ]
8809
fn revsets_select_changes_by_relation_author_text_date_and_path ( ) {
8810
hermetic ( ) ;
8811
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8812
let root = dir.path ( ) ;
8813
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
8814
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
8815
.unwrap ( ) ;
8816
write ( root, " src/a.rs" , b " one\n " ) ;
8817
let a = repo.commit ( " first: add a" , & [ ] ) .unwrap ( ) ;
8818
write ( root, " src/a.rs" , b " one\n two\n " ) ;
8819
let b = repo.commit ( " second: grow a" , & [ ] ) .unwrap ( ) ;
8820
8821
repo.checkout ( & [ a] , false , " checkout a" ) .unwrap ( ) ;
8822
write ( root, " docs/notes.md" , b " notes\n " ) ;
8823
let c = repo.commit ( " third: notes" , & [ ] ) .unwrap ( ) ;
8824
8825
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" )
8826
.unwrap ( ) ;
8827
fs:: remove_file( root.join ( " src/a.rs" ) ) .unwrap ( ) ;
8828
write ( root, " src/z.rs" , b " one\n two\n " ) ;
8829
let d = repo.commit ( " fourth: rename a to z" , & [ ] ) .unwrap ( ) ;
8830
let wc = repo.working_copy ( ) .unwrap ( ) .0 ;
8831
8832
repo.set_user_config ( " revset.mine" , " author(me) & ::main" )
8833
.unwrap ( ) ;
8834
assert! ( repo.set_user_config ( " revset.bad" , " a &" ) .is_err ( ) ) ;
8835
let set = | src : & str | -> Vec< ChangeHash> {
8836
let mut v = repo.revset ( src) .unwrap ( ) .hashes;
8837
v.sort ( ) ;
8838
v
8839
} ;
8840
let sorted = | hs : & [ChangeHash]| -> Vec< ChangeHash> {
8841
let mut v = hs.to_vec ( ) ;
8842
v.sort ( ) ;
8843
v
8844
} ;
8845
8846
8847
8848
8849
let heads = sorted ( & repo.bookmark_heads ( ) .unwrap ( ) ) ;
8850
let meta = | h : & ChangeHash| repo.store ( ) .get_change_meta ( h) .unwrap ( ) ;
8851
assert! ( heads.contains ( & d) & & heads.contains ( & c) ) ;
8852
8853
assert_eq! ( set ( " main" ) , heads) ;
8854
assert_eq! ( set ( " ::main" ) , sorted ( & [ a, b, c, d] ) ) ;
8855
assert_eq! ( set ( " @" ) , vec! [ wc] ) ;
8856
assert_eq! ( set ( " ::@" ) , sorted ( & [ a, b, c, d, wc] ) ) ;
8857
assert_eq! ( set ( & format! ( " ::#{} " , b.short ( 12 ) ) ) , sorted ( & [ a, b] ) ) ;
8858
assert_eq! ( set ( & format! ( " #{} ::" , a.short ( 12 ) ) ) , sorted ( & [ a, b, d, wc] ) ) ;
8859
assert_eq! (
8860
set ( & format! ( " #{} ::#{} " , a.short ( 12 ) , b.short ( 12 ) ) ) ,
8861
sorted ( & [ a, b] )
8862
) ;
8863
8864
assert_eq! ( set ( & format! ( " #{} ..main" , b.short ( 12 ) ) ) , sorted ( & [ c, d] ) ) ;
8865
assert_eq! ( set ( & format! ( " #{} .." , c.short ( 12 ) ) ) , sorted ( & [ a, b, d, wc] ) ) ;
8866
assert_eq! ( set ( " ..main" ) , sorted ( & [ a, b, c, d] ) ) ;
8867
8868
assert_eq! (
8869
set ( & format! ( " ::main ~ #{} " , d.short ( 12 ) ) ) ,
8870
sorted ( & [ a, b, c] )
8871
) ;
8872
assert_eq! (
8873
set ( & format! ( " ::main & #{} | #{} " , b.short ( 12 ) , c.short ( 12 ) ) ) ,
8874
sorted ( & [ b, c] )
8875
) ;
8876
assert_eq! ( set ( " all() ~ ::main" ) , vec! [ wc] ) ;
8877
8878
let mut head_deps: Vec< ChangeHash> = heads.iter ( ) .flat_map ( | h | meta ( h) .deps) .collect ( ) ;
8879
head_deps.sort ( ) ;
8880
head_deps.dedup ( ) ;
8881
assert_eq! ( set ( " deps(main)" ) , head_deps) ;
8882
let on_a: Vec< ChangeHash> = [ b, c, d]
8883
.into_iter ( )
8884
.filter ( | h | meta ( h) .deps.contains ( & a) )
8885
.collect ( ) ;
8886
assert_eq! ( set ( & format! ( " dependents(#{} )" , a.short ( 12 ) ) ) , sorted ( & on_a) ) ;
8887
assert_eq! ( set ( " heads(::main)" ) , heads) ;
8888
assert_eq! ( set ( " roots(::main)" ) , sorted ( & [ a, c] ) ) ;
8889
assert_eq! (
8890
set ( & format! (
8891
" heads(#{} | #{} | #{} )" ,
8892
a.short ( 12 ) ,
8893
b.short ( 12 ) ,
8894
c.short ( 12 )
8895
) ) ,
8896
sorted ( & [ b, c] )
8897
) ;
8898
assert_eq! ( set ( " latest(::main, 1)" ) , vec! [ d] ) ;
8899
assert_eq! ( set ( " kind(working-copy)" ) , vec! [ wc] ) ;
8900
assert_eq! ( set ( " none()" ) , vec! [ ] ) ;
8901
8902
let key = repo.config.author_key.unwrap ( ) ;
8903
assert_eq! ( set ( " author(ada) & ::main" ) , sorted ( & [ a, b, c, d] ) ) ;
8904
assert_eq! ( set ( " author(\" [email protected] \" ) & ::main" ) .len ( ) , 4 ) ;
8905
assert_eq! ( set ( & format! ( " author({} ) & ::main" , key.short ( 12 ) ) ) .len ( ) , 4 ) ;
8906
assert_eq! ( set ( " author(me) & ::main" ) .len ( ) , 4 ) ;
8907
assert_eq! ( set ( " author(nobody)" ) , vec! [ ] ) ;
8908
assert_eq! ( set ( " description(notes)" ) , vec! [ c] ) ;
8909
assert_eq! ( set ( " description(\" rename a\" )" ) , vec! [ d] ) ;
8910
assert_eq! ( set ( " date(-1h..) & ::main" ) .len ( ) , 4 ) ;
8911
assert_eq! ( set ( " date(..2000-01-01)" ) , vec! [ ] ) ;
8912
8913
assert_eq! ( set ( " touches(\" src/z.rs\" )" ) , sorted ( & [ a, b, d] ) ) ;
8914
assert_eq! ( set ( " touches(\" src/**\" ) & ::main" ) , sorted ( & [ a, b, d] ) ) ;
8915
assert_eq! ( set ( " touches(\" docs/*\" )" ) , vec! [ c] ) ;
8916
assert_eq! ( set ( " touches(\" nothing/*\" )" ) , vec! [ ] ) ;
8917
8918
assert_eq! ( repo.revset ( " ::main" ) .unwrap ( ) .hashes[ 0 ] , d) ;
8919
8920
assert_eq! ( set ( " mine()" ) .len ( ) , 4 ) ;
8921
let e = repo.revset ( " nosuch()" ) .unwrap_err ( ) .to_string ( ) ;
8922
assert! ( e.contains ( " unknown function nosuch()" ) , " {e}" ) ;
8923
let e = repo.revset ( " main &" ) .unwrap_err ( ) .to_string ( ) ;
8924
assert! ( e.contains ( " at character" ) , " {e}" ) ;
8925
let e = repo.revset ( " zzzz" ) .unwrap_err ( ) .to_string ( ) ;
8926
assert! ( e.contains ( " no change matches" ) , " {e}" ) ;
8927
let e = repo
8928
.revset_with_budget ( " ::main" , std:: time:: Duration:: ZERO )
8929
.unwrap_err ( )
8930
.to_string ( ) ;
8931
assert! ( e.contains ( " budget" ) , " {e}" ) ;
8932
8933
let entries = repo.log_revset ( " ::@" ) .unwrap ( ) ;
8934
assert! ( entries[ 0 ] .is_working_copy & & entries.iter ( ) .any ( | e | e.is_head & & e.hash = = d ) ) ;
2c9ddac3 Andy McCall 1 month ago ↶
8935
8936
8937
assert_eq! ( set ( " unsigned() & ::main" ) , sorted ( & [ a, b, c, d] ) ) ;
8938
assert_eq! ( set ( " signed_by(me)" ) , vec! [ ] ) ;
8939
repo.sign_change ( & d) .unwrap ( ) ;
8940
assert_eq! ( set ( " signed_by(me)" ) , vec! [ d] ) ;
8941
assert_eq! ( set ( " signed_by([email protected] )" ) , vec! [ d] ) ;
8942
assert_eq! ( set ( & format! ( " signed_by({} )" , key.short ( 12 ) ) ) , vec! [ d] ) ;
8943
assert_eq! ( set ( " unsigned() & ::main" ) , sorted ( & [ a, b, c] ) ) ;
8944
assert_eq! ( set ( " delegated()" ) , vec! [ ] ) ;
8945
assert_eq! ( set ( " revoked()" ) , vec! [ ] ) ;
8946
assert_eq! ( set ( " attested(tag)" ) , vec! [ ] ) ;
8947
repo.tag_create ( " v1" , & [ b] , " first" ) .unwrap ( ) ;
8948
assert_eq! ( set ( " attested(tag)" ) , sorted ( & [ a, b] ) ) ;
8949
assert_eq! ( set ( " attested(tag) & unsigned()" ) , sorted ( & [ a, b] ) ) ;
8950
assert_eq! ( set ( " attested(receipt)" ) , vec! [ ] ) ;
8951
assert_eq! ( set ( " built(test)" ) , vec! [ ] ) ;
8952
let e = repo.revset ( " attested(seal)" ) .unwrap_err ( ) .to_string ( ) ;
8953
assert! ( e.contains ( " tag, receipt or ref" ) , " {e}" ) ;
8954
repo.revoke_identity ( & key, " test" ) .unwrap ( ) ;
8955
assert_eq! ( set ( " revoked()" ) , vec! [ d] ) ;
8956
assert_eq! ( set ( " signed_by(me) ~ revoked()" ) , vec! [ ] ) ;
47ce8b8e Andy McCall 1 month ago ↶
8957
}
614f55e1 Andy McCall 1 month ago ↶
8958
8959
# [ test ]
8960
fn a_rotation_is_recorded_wherever_the_old_key_was_named ( ) {
8961
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
8962
let root = dir.path ( ) ;
8963
let home = root.join ( " home" ) ;
8964
8965
8966
let mut a = Repo:: init( & root.join ( " a" ) ) .unwrap ( ) .with_home ( & home) ;
8967
let ( key, _ ) = a
8968
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
8969
.unwrap ( ) ;
8970
a.config.set ( " author.key" , & key.to_hex ( ) ) .unwrap ( ) ;
8971
a.save_config ( ) .unwrap ( ) ;
8972
let agent = SecretKey:: generate( ) .identity ( " Agent" , " [email protected] " , 1 ) ;
8973
let r = a
8974
.delegate_add ( agent.clone ( ) , crate :: identity:: Scope:: CHANGES , None )
8975
.unwrap ( ) ;
8976
assert_eq! (
8977
read_config_file ( & home.join ( " config.toml" ) ) .unwrap ( ) [ " author.key" ] ,
8978
r.new.to_hex ( ) ,
8979
" the user file follows"
8980
) ;
8981
assert_eq! (
8982
read_config_file ( & root.join ( " a/.oxy/config" ) ) .unwrap ( ) [ " author.key" ] ,
8983
r.new.to_hex ( ) ,
8984
" the pinning repository follows"
8985
) ;
8986
let b = Repo:: init( & root.join ( " b" ) ) .unwrap ( ) .with_home ( & home) ;
8987
assert_eq! (
8988
b.config.author_key,
8989
Some ( r.new) ,
8990
" a checkout without a pin follows the user file"
8991
) ;
8992
8993
let other_home = root.join ( " home2" ) ;
8994
let mut c = Repo:: init( & root.join ( " c" ) ) .unwrap ( ) .with_home ( & other_home) ;
8995
let ( ck, _ ) = c
8996
.create_identity ( " Cy" , " [email protected] " , IdentityScope:: Repo)
8997
.unwrap ( ) ;
8998
assert! ( ! other_home.join ( " config.toml" ) .exists ( ) ) ;
8999
let r2 = c.rotate_identity ( ) .unwrap ( ) ;
9000
assert_ne! ( r2.new, ck) ;
9001
assert_eq! (
9002
read_config_file ( & root.join ( " c/.oxy/config" ) ) .unwrap ( ) [ " author.key" ] ,
9003
r2.new.to_hex ( )
9004
) ;
9005
assert! (
9006
! other_home.join ( " config.toml" ) .exists ( ) ,
9007
" a repository-scoped identity stays repository-scoped"
9008
) ;
9009
9010
let ( d1, d2) = (
9011
SecretKey:: generate( ) .identity ( " D1" , " d1@x" , 1 ) ,
9012
SecretKey:: generate( ) .identity ( " D2" , " d2@x" , 1 ) ,
9013
) ;
9014
let r3 = c
9015
.delegate_add_many ( vec! [ d1.clone ( ) , d2] , crate :: identity:: Scope:: CHANGES , None )
9016
.unwrap ( ) ;
9017
assert_eq! ( r3.identity.delegates.len ( ) , 2 ) ;
9018
assert! ( c
9019
.delegate_add ( d1, crate :: identity:: Scope:: CHANGES , None )
9020
.is_err ( ) ) ;
9021
}
33be2409 Andy McCall 1 month ago ↶
9022
9023
9024
9025
9026
9027
# [ test ]
9028
fn git_import_joins_unrelated_histories_that_add_the_same_file ( ) {
9029
9030
9031
crate :: repo:: git:: set_merge_batch_for_tests( Some ( 1 ) ) ;
9032
hermetic ( ) ;
9033
if std:: process:: Command:: new( " git" )
9034
.arg ( " --version" )
9035
.output ( )
9036
.is_err ( )
9037
{
9038
eprintln! ( " git not installed; skipping" ) ;
9039
return ;
9040
}
9041
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9042
let root = dir.path ( ) ;
9043
let g = root.join ( " src.git" ) ;
9044
fs:: create_dir_all( & g) .unwrap ( ) ;
9045
let git = | args : & [& str ]| -> String {
9046
let out = std:: process:: Command:: new( " git" )
9047
.arg ( " -C" )
9048
.arg ( & g)
9049
.args ( [
9050
" -c" ,
9051
" user.name=Grace" ,
9052
" -c" ,
9053
" [email protected] " ,
9054
" -c" ,
9055
" commit.gpgsign=false" ,
9056
] )
9057
.args ( args)
9058
.output ( )
9059
.unwrap ( ) ;
9060
assert! (
9061
out.status.success ( ) ,
9062
" git {args:?}: {}" ,
9063
String :: from_utf8_lossy( & out.stderr)
9064
) ;
9065
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
9066
} ;
9067
let text = b " /* header */\n const a = 1;\n const b = 2;\n " ;
9068
9069
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
9070
write ( & g, " gulpfile.js" , text) ;
9071
write ( & g, " a.txt" , b " a\n " ) ;
9072
git ( & [ " add" , " ." ] ) ;
9073
git ( & [ " commit" , " -q" , " -m" , " A root" ] ) ;
9074
9075
git ( & [ " checkout" , " -q" , " --orphan" , " fork" ] ) ;
9076
git ( & [ " rm" , " -q" , " -r" , " --cached" , " ." ] ) ;
9077
for f in [ " gulpfile.js" , " a.txt" ] {
9078
let _ = fs:: remove_file( g.join ( f) ) ;
9079
}
9080
write ( & g, " gulpfile.js" , text) ;
9081
write ( & g, " b.txt" , b " b\n " ) ;
9082
git ( & [ " add" , " ." ] ) ;
9083
git ( & [ " commit" , " -q" , " -m" , " B root" ] ) ;
9084
9085
git ( & [ " checkout" , " -q" , " -b" , " one" , " main" ] ) ;
9086
git ( & [
9087
" merge" ,
9088
" -q" ,
9089
" --no-ff" ,
9090
" --allow-unrelated-histories" ,
9091
" -m" ,
9092
" join on one" ,
9093
" fork" ,
9094
] ) ;
9095
git ( & [ " checkout" , " -q" , " -b" , " two" , " main" ] ) ;
9096
write ( & g, " a.txt" , b " a\n two\n " ) ;
9097
git ( & [ " commit" , " -q" , " -am" , " work on two" ] ) ;
9098
git ( & [
9099
" merge" ,
9100
" -q" ,
9101
" --no-ff" ,
9102
" --allow-unrelated-histories" ,
9103
" -m" ,
9104
" join on two" ,
9105
" fork" ,
9106
] ) ;
9107
9108
git ( & [ " checkout" , " -q" , " main" ] ) ;
9109
git ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge one" , " one" ] ) ;
9110
git ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge two" , " two" ] ) ;
9111
write (
9112
& g,
9113
" gulpfile.js" ,
9114
b " /* header */\n const a = 1;\n const b = 3;\n " ,
9115
) ;
9116
git ( & [ " commit" , " -q" , " -am" , " after" ] ) ;
9117
9118
let dest = root.join ( " dest" ) ;
9119
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9120
repo.import_git ( & g, " main" ) .unwrap ( ) ;
9121
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
9122
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
9123
assert_eq! (
9124
fs:: read( dest.join ( " gulpfile.js" ) ) .unwrap ( ) ,
9125
b " /* header */\n const a = 1;\n const b = 3;\n "
9126
) ;
9127
}
9128
9129
9130
9131
9132
# [ test ]
9133
fn git_import_joins_where_each_join_kept_a_different_copy ( ) {
9134
9135
9136
crate :: repo:: git:: set_merge_batch_for_tests( Some ( 1 ) ) ;
9137
hermetic ( ) ;
9138
if std:: process:: Command:: new( " git" )
9139
.arg ( " --version" )
9140
.output ( )
9141
.is_err ( )
9142
{
9143
eprintln! ( " git not installed; skipping" ) ;
9144
return ;
9145
}
9146
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9147
let root = dir.path ( ) ;
9148
let g = root.join ( " src.git" ) ;
9149
fs:: create_dir_all( & g) .unwrap ( ) ;
9150
let run = | args : & [& str ]| {
9151
std:: process:: Command:: new( " git" )
9152
.arg ( " -C" )
9153
.arg ( & g)
9154
.args ( [
9155
" -c" ,
9156
" user.name=Grace" ,
9157
" -c" ,
9158
" [email protected] " ,
9159
" -c" ,
9160
" commit.gpgsign=false" ,
9161
] )
9162
.args ( args)
9163
.output ( )
9164
.unwrap ( )
9165
} ;
9166
let git = | args : & [& str ]| -> String {
9167
let out = run ( args) ;
9168
assert! (
9169
out.status.success ( ) ,
9170
" git {args:?}: {}" ,
9171
String :: from_utf8_lossy( & out.stderr)
9172
) ;
9173
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
9174
} ;
9175
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
9176
write (
9177
& g,
9178
" gulpfile.js" ,
9179
b " /* header */\n const a = 1;\n const b = 2;\n " ,
9180
) ;
9181
git ( & [ " add" , " ." ] ) ;
9182
git ( & [ " commit" , " -q" , " -m" , " A root" ] ) ;
9183
write (
9184
& g,
9185
" gulpfile.js" ,
9186
b " /* header */\n const a = 1;\n \n const b = 2;\n " ,
9187
) ;
9188
git ( & [ " commit" , " -q" , " -am" , " whitespace" ] ) ;
9189
git ( & [ " checkout" , " -q" , " --orphan" , " fork" ] ) ;
9190
git ( & [ " rm" , " -q" , " -r" , " --cached" , " ." ] ) ;
9191
let _ = fs:: remove_file( g.join ( " gulpfile.js" ) ) ;
9192
write (
9193
& g,
9194
" gulpfile.js" ,
9195
b " /* header */\n const a = 1;\n const b = 2;\n const c = 3;\n " ,
9196
) ;
9197
git ( & [ " add" , " ." ] ) ;
9198
git ( & [ " commit" , " -q" , " -m" , " B root" ] ) ;
9199
9200
for ( branch, keep) in [ ( " one" , " ours" ) , ( " two" , " theirs" ) ] {
9201
git ( & [ " checkout" , " -q" , " -b" , branch, " main" ] ) ;
9202
let _ = run ( & [
9203
" merge" ,
9204
" -q" ,
9205
" --no-ff" ,
9206
" --allow-unrelated-histories" ,
9207
" -m" ,
9208
" join" ,
9209
" fork" ,
9210
] ) ;
9211
git ( & [ " checkout" , & format! ( " --{keep} " ) , " gulpfile.js" ] ) ;
9212
git ( & [ " add" , " gulpfile.js" ] ) ;
9213
git ( & [ " commit" , " -q" , " -m" , & format! ( " join on {branch} " ) ] ) ;
9214
}
9215
9216
git ( & [ " checkout" , " -q" , " one" ] ) ;
9217
write (
9218
& g,
9219
" gulpfile.js" ,
9220
b " /* header */\n const a = 1;\n \n const b = 2;\n // tslint\n " ,
9221
) ;
9222
git ( & [ " commit" , " -q" , " -am" , " tslint on one" ] ) ;
9223
git ( & [ " checkout" , " -q" , " two" ] ) ;
9224
write (
9225
& g,
9226
" gulpfile.js" ,
9227
b " /* header */\n const a = 1;\n const b = 2;\n const c = 3;\n // fmt\n " ,
9228
) ;
9229
write ( & g, " extra.txt" , b " x\n " ) ;
9230
write ( & g, " more.txt" , b " m\n " ) ;
9231
git ( & [ " add" , " ." ] ) ;
9232
git ( & [ " commit" , " -q" , " -am" , " fmt on two" ] ) ;
9233
9234
git ( & [ " checkout" , " -q" , " one" ] ) ;
9235
let _ = run ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge two" , " two" ] ) ;
9236
write (
9237
& g,
9238
" gulpfile.js" ,
9239
b " /* header */\n const a = 1;\n const b = 2;\n const c = 3;\n // tslint\n // fmt\n " ,
9240
) ;
9241
git ( & [ " add" , " gulpfile.js" ] ) ;
9242
git ( & [ " commit" , " -q" , " -m" , " merge two into one" ] ) ;
9243
9244
let dest = root.join ( " dest" ) ;
9245
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9246
repo.import_git ( & g, " one" ) .unwrap ( ) ;
9247
let v = repo.verify_git_import ( & g, " one" ) .unwrap ( ) ;
9248
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
9249
}
dd21fd82 Andy McCall 1 month ago ↶
9250
9251
9252
9253
9254
9255
9256
# [ test ]
9257
fn over_a_store_the_operation_head_moves_by_compare_and_swap ( ) {
9258
use crate :: strata:: { MemoryStore, Store as _ } ;
9259
let backend = std:: sync:: Arc:: new( MemoryStore:: new( ) ) ;
9260
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = Some ( backend.clone ( ) ) ) ;
9261
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9262
let a = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9263
assert! (
9264
! dir.path ( ) .join ( " .oxy/ops/head" ) .exists ( ) ,
9265
" the head is not a file"
9266
) ;
9267
let ( h0, op0) = a.head_op ( ) .unwrap ( ) ;
9268
let record = backend
9269
.read_ref ( HEAD_RECORD )
9270
.unwrap ( )
9271
.expect ( " the head is the store's record" ) ;
9272
assert_eq! ( String :: from_utf8( record.0 ) .unwrap ( ) , h0.to_hex ( ) ) ;
9273
9274
let b = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
9275
let state = a.store.put_state ( & StateObject:: new( vec! [ ] ) ) .unwrap ( ) ;
9276
let mut va = op0.view.clone ( ) ;
9277
va.bookmarks.insert ( " from-a" .into ( ) , state) ;
9278
let mut vb = op0.view.clone ( ) ;
9279
vb.bookmarks.insert ( " from-b" .into ( ) , state) ;
9280
let rb = b.record_op ( & [ h0] , " bookmark from-b" , vb) .unwrap ( ) ;
9281
assert_eq! ( b.head_ref ( ) .unwrap ( ) , rb) ;
9282
9283
let ra = a.record_op ( & [ h0] , " bookmark from-a" , va) .unwrap ( ) ;
9284
let ( head, op) = Repo:: open( dir.path ( ) ) .unwrap ( ) .head_op ( ) .unwrap ( ) ;
9285
assert_eq! ( head, ra) ;
9286
assert! (
9287
op.command.ends_with ( " (merged with a concurrent operation)" ) ,
9288
" {}" ,
9289
op.command
9290
) ;
9291
assert_eq! ( op.parents, vec! [ rb, op.parents[ 1 ] ] ) ;
9292
assert! ( op.view.bookmarks.contains_key ( " from-a" ) & & op.view.bookmarks.contains_key ( " from-b" ) ) ;
9293
9294
9295
let ( h, v) = a.read_head ( ) .unwrap ( ) ;
9296
assert! ( b.swap_head ( & rb, v.as_ref ( ) ) .unwrap ( ) ) ;
9297
assert! ( ! a.swap_head ( & h, v.as_ref ( ) ) .unwrap ( ) , " stale" ) ;
767ac6cd Andy McCall 18 days ago
9298
a.refresh ( ) .unwrap ( ) ;
dd21fd82 Andy McCall 1 month ago ↶
9299
assert_eq! ( a.head_ref ( ) .unwrap ( ) , rb) ;
9300
9301
let other = tempfile:: tempdir( ) .unwrap ( ) ;
9302
let err = Repo:: init( other.path ( ) )
9303
.err ( )
9304
.expect ( " the store already holds a log" )
9305
.to_string ( ) ;
9306
assert! (
9307
err.contains ( " already holds a repository's operation log" ) ,
9308
" {err}"
9309
) ;
9310
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = None ) ;
9311
}
d069701a Andy McCall 1 month ago ↶
9312
9313
9314
9315
9316
# [ test ]
9317
fn a_repository_runs_on_a_store_with_a_cache_in_front ( ) {
9318
hermetic ( ) ;
9319
if std:: env:: var_os( " OXY_STORE_BACKEND" ) .is_some ( ) {
9320
return ;
9321
}
9322
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9323
let ( root, bucket) = ( dir.path ( ) .join ( " work" ) , dir.path ( ) .join ( " bucket" ) ) ;
9324
let url = format! ( " dir://{} " , bucket.display ( ) ) ;
9325
let repo = Repo:: init_on( & root, [ 7 ; 16 ] , Some ( & url) ) .unwrap ( ) ;
9326
assert_eq! ( repo.config.storage_url.as_deref ( ) , Some ( url.as_str ( ) ) ) ;
9327
write ( & root, " a.txt" , b " one\n " ) ;
9328
let h1 = repo.commit ( " First" , & [ ] ) .unwrap ( ) ;
9329
write ( & root, " a.txt" , b " one\n two\n " ) ;
9330
let h2 = repo.commit ( " Second" , & [ ] ) .unwrap ( ) ;
9331
assert! (
9332
! root.join ( " .oxy/ops/head" ) .exists ( ) ,
9333
" the head is in the store"
9334
) ;
9335
assert! ( bucket.join ( " ops/head" ) .is_file ( ) ) ;
9336
assert! ( root.join ( " .oxy/cache" ) .is_dir ( ) ) ;
9337
drop ( repo) ;
9338
9339
9340
for drop_cache in [ false , true ] {
9341
if drop_cache {
9342
std:: fs:: remove_dir_all( root.join ( " .oxy/cache" ) ) .unwrap ( ) ;
9343
}
9344
let repo = Repo:: open( & root) .unwrap ( ) ;
9345
let log: Vec< String> = repo
9346
.op_log ( )
9347
.unwrap ( )
9348
.into_iter ( )
9349
.map ( | ( _, op ) | op.command )
9350
.collect ( ) ;
9351
assert! ( log.len ( ) > = 3 , " {log:?}" ) ;
9352
assert_eq! ( repo.store ( ) .get_change ( & h1) .unwrap ( ) .message, " First" ) ;
9353
let heads = repo
9354
.store ( )
9355
.get_state ( & repo.view ( ) .unwrap ( ) .bookmarks[ " main" ] )
9356
.unwrap ( )
9357
.heads;
9358
assert_eq! ( heads, vec! [ h2] ) ;
9359
assert_eq! (
9360
repo.load_state ( & heads) .unwrap ( ) .materialize ( ) .files.len ( ) ,
9361
1
9362
) ;
9363
}
9364
9365
9366
let err = Repo:: init_on( & dir.path ( ) .join ( " other" ) , [ 8 ; 16 ] , Some ( & url) )
9367
.err ( )
9368
.unwrap ( )
9369
.to_string ( ) ;
9370
assert! (
9371
err.contains ( " already holds a repository's operation log" ) ,
9372
" {err}"
9373
) ;
9374
let err = Repo:: init_on( & dir.path ( ) .join ( " third" ) , [ 9 ; 16 ] , Some ( " nope://x" ) )
9375
.err ( )
9376
.unwrap ( )
9377
.to_string ( ) ;
9378
assert! ( err.contains ( " cannot open that kind of store" ) , " {err}" ) ;
9379
}
80677b7a Andy McCall 1 month ago ↶
9380
9381
9382
9383
9384
9385
# [ test ]
9386
fn the_quick_dirty_check_compares_a_subrepo_by_its_pin ( ) {
9387
hermetic ( ) ;
9388
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9389
let root = dir.path ( ) ;
9390
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9391
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9392
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9393
.unwrap ( )
9394
.with_home ( root.join ( " home" ) ) ;
9395
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9396
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9397
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9398
let app_dir = root.join ( " app" ) ;
9399
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9400
write ( & app_dir, " main.txt" , b " app\n " ) ;
9401
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9402
app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
9403
9404
std:: thread:: sleep( std:: time:: Duration:: from_millis( 2100 ) ) ;
9405
let reopen = | | Repo:: open( & app_dir) .unwrap ( ) ;
9406
assert_eq! (
9407
reopen ( ) .quick_dirty ( ) .unwrap ( ) ,
9408
Some ( false ) ,
9409
" clean, as status says"
9410
) ;
9411
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) ) .unwrap ( ) ;
9412
write ( & app_dir.join ( " libs/lib" ) , " lib.txt" , b " v2\n " ) ;
9413
nested.commit ( " lib v2" , & [ ] ) .unwrap ( ) ;
9414
assert_eq! (
9415
reopen ( ) .quick_dirty ( ) .unwrap ( ) ,
9416
Some ( true ) ,
9417
" the subrepo moved"
9418
) ;
9419
reopen ( ) .commit ( " pin lib v2" , & [ ] ) .unwrap ( ) ;
9420
std:: thread:: sleep( std:: time:: Duration:: from_millis( 2100 ) ) ;
9421
assert_eq! (
9422
reopen ( ) .quick_dirty ( ) .unwrap ( ) ,
9423
Some ( false ) ,
9424
" the new pin is committed"
9425
) ;
9426
}
7993d465 Andy McCall 1 month ago ↶
9427
9428
# [ test ]
9429
fn a_module_entry_that_was_never_added_is_reported_as_unpinned ( ) {
9430
9431
hermetic ( ) ;
9432
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9433
let root = dir.path ( ) ;
9434
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9435
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9436
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9437
.unwrap ( )
9438
.with_home ( root.join ( " home" ) ) ;
9439
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9440
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9441
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9442
let app_dir = root.join ( " app" ) ;
9443
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9444
write (
9445
& app_dir,
9446
" .oxymodules" ,
9447
format! ( " [libs/lib]\n url = {lib_url} \n " ) .as_bytes ( ) ,
9448
) ;
9449
let unpinned = app.unpinned_modules ( ) .unwrap ( ) ;
9450
assert_eq! ( unpinned.len ( ) , 1 ) ;
9451
assert_eq! ( unpinned[ 0 ] .0 , " libs/lib" ) ;
9452
assert_eq! ( unpinned[ 0 ] .1. url, lib_url) ;
9453
assert! ( app.submodule_update ( true , & [ ] ) .unwrap ( ) .is_empty ( ) ) ;
9454
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9455
assert! ( app.unpinned_modules ( ) .unwrap ( ) .is_empty ( ) , " added now" ) ;
9456
}
bf3a6781 Andy McCall 1 month ago ↶
9457
9458
# [ test ]
9459
fn the_bookmark_revset_function_names_only_bookmarks ( ) {
9460
9461
hermetic ( ) ;
9462
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9463
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9464
write ( dir.path ( ) , " a.txt" , b " a\n " ) ;
9465
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
9466
write ( dir.path ( ) , " b.txt" , b " b\n " ) ;
9467
let b = repo.commit ( " B" , & [ ] ) .unwrap ( ) ;
9468
let mut heads = repo.revset ( " bookmark(main)" ) .unwrap ( ) .hashes;
9469
heads.sort ( ) ;
9470
let mut want = repo.bookmark_heads ( ) .unwrap ( ) ;
9471
want.sort ( ) ;
9472
assert_eq! ( heads, want) ;
9473
assert! ( heads.contains ( & b) ) ;
9474
assert_eq! ( repo.revset ( " main" ) .unwrap ( ) .hashes.len ( ) , heads.len ( ) ) ;
9475
9476
let prefix = repo.store ( ) .get_change_meta ( & a) .unwrap ( ) .change_id.short ( 8 ) ;
9477
assert! ( repo.revset ( & prefix) .is_ok ( ) ) ;
9478
let e = repo
9479
.revset ( & format! ( " bookmark({prefix} )" ) )
9480
.unwrap_err ( )
9481
.to_string ( ) ;
9482
assert! ( e.contains ( " no such bookmark" ) , " {e}" ) ;
9483
}
72484195 Andy McCall 1 month ago ↶
9484
157d02e9 Andy McCall 11 days ago ↶
9485
9486
9487
72484195 Andy McCall 1 month ago ↶
9488
# [ test ]
157d02e9 Andy McCall 11 days ago ↶
9489
fn a_replace_conflict_is_settled_from_the_working_copy ( ) {
9490
hermetic ( ) ;
9491
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9492
let root = dir.path ( ) ;
9493
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9494
let blob = | n : u8 | {
9495
let mut v = vec! [ 0 u8 ; 64 ] ;
9496
v[ 1 ] = n;
9497
v
9498
} ;
9499
write ( root, " img.bin" , & blob ( 1 ) ) ;
9500
write ( root, " a.txt" , b " a\n " ) ;
9501
repo.commit ( " add img" , & [ ] ) .unwrap ( ) ;
9502
repo.bookmark_create ( " other" , None ) .unwrap ( ) ;
9503
write ( root, " img.bin" , & blob ( 2 ) ) ;
9504
repo.commit ( " img two" , & [ ] ) .unwrap ( ) ;
9505
repo.bookmark_switch ( " other" , false ) .unwrap ( ) ;
9506
write ( root, " img.bin" , & blob ( 3 ) ) ;
9507
repo.commit ( " img three" , & [ ] ) .unwrap ( ) ;
9508
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
9509
repo.merge ( " other" ) .unwrap ( ) ;
9510
9511
9512
write ( root, " img.bin" , & blob ( 9 ) ) ;
9513
write ( root, " a.txt" , b " a\n more\n " ) ;
9514
assert! (
9515
repo.commit ( " edit a" , & [ ] ) .is_err ( ) ,
9516
" refused while conflicted"
9517
) ;
9518
let snap = repo.snapshot ( " oxy resolve" ) .unwrap ( ) ;
9519
assert! (
9520
repo.choose_resolutions ( & snap.base_snapshot, & [ " a.txt" .to_string ( ) ] )
9521
.is_err ( ) ,
9522
" a.txt has no replace conflict"
9523
) ;
9524
repo.choose_resolutions ( & snap.base_snapshot, & [ " img.bin" .to_string ( ) ] )
9525
.unwrap ( ) ;
9526
let snap = repo.snapshot ( " oxy resolve" ) .unwrap ( ) ;
9527
assert! ( snap
9528
.statuses
9529
.iter ( )
9530
.any ( | s | s.path = = " img.bin" & & s.status = = ChangeStatus:: Resolved ) ) ;
9531
let c = repo.commit ( " settle img" , & [ ] ) .unwrap ( ) ;
9532
assert_eq! (
9533
repo.store ( ) .get_change ( & c) .unwrap ( ) .kind,
9534
ChangeKind:: Resolution
9535
) ;
9536
let after = repo
9537
.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) )
9538
.unwrap ( )
9539
.materialize ( ) ;
9540
assert! ( ! after.is_conflicted ( ) ) ;
9541
assert_eq! (
9542
after.files[ " img.bin" ] .content,
9543
FileContent:: Opaque {
9544
blobs: vec! [ Repo:: blob_ref( & blob ( 9 ) ) ]
9545
}
9546
) ;
9547
assert! (
9548
matches! ( & after.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n more\n " )
9549
) ;
9550
9551
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
9552
assert! ( snap.statuses.is_empty ( ) , " {:?}" , snap.statuses) ;
9553
assert! ( repo.resolutions ( ) .unwrap ( ) .is_empty ( ) ) ;
9554
}
9555
9556
9557
# [ test ]
9558
fn a_pin_conflict_is_settled_from_the_working_copy ( ) {
9559
hermetic ( ) ;
9560
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9561
let root = dir.path ( ) ;
9562
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9563
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9564
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9565
.unwrap ( )
9566
.with_home ( root.join ( " home" ) ) ;
9567
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9568
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9569
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9570
let app_dir = root.join ( " app" ) ;
9571
let mut app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9572
write ( & app_dir, " main.txt" , b " app\n " ) ;
9573
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9574
app.commit ( " pin v1" , & [ ] ) .unwrap ( ) ;
9575
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) )
9576
.unwrap ( )
9577
.with_home ( root.join ( " home" ) ) ;
9578
fn pin ( app : & Repo, nested : & Repo, text : & [u8 ], message : & str ) {
9579
write ( nested.root ( ) , " lib.txt" , text) ;
9580
nested.commit ( message, & [ ] ) .unwrap ( ) ;
9581
app.commit ( & format! ( " pin {message} " ) , & [ ] ) .unwrap ( ) ;
9582
}
9583
app.bookmark_create ( " other" , None ) .unwrap ( ) ;
9584
pin ( & app, & nested, b " v2\n " , " v2" ) ;
9585
app.bookmark_switch ( " other" , false ) .unwrap ( ) ;
9586
pin ( & app, & nested, b " v3\n " , " v3" ) ;
9587
app.bookmark_switch ( " main" , false ) .unwrap ( ) ;
9588
app.merge ( " other" ) .unwrap ( ) ;
9589
let merged = app
9590
.load_state ( & app.bookmark_heads ( ) .unwrap ( ) )
9591
.unwrap ( )
9592
.materialize ( ) ;
9593
assert! (
9594
matches! ( & merged.files[ " libs/lib" ] .content, FileContent:: Opaque { blobs } if blobs.len ( ) = = 2 ) ,
9595
" a pin conflict"
9596
) ;
9597
9598
write ( & app_dir.join ( " libs/lib" ) , " lib.txt" , b " v4\n " ) ;
9599
nested.commit ( " v4" , & [ ] ) .unwrap ( ) ;
9600
let snap = app.snapshot ( " oxy resolve" ) .unwrap ( ) ;
9601
app.choose_resolutions ( & snap.base_snapshot, & [ " libs/lib" .to_string ( ) ] )
9602
.unwrap ( ) ;
9603
let c = app.commit ( " settle the pin" , & [ ] ) .unwrap ( ) ;
9604
assert_eq! (
9605
app.store ( ) .get_change ( & c) .unwrap ( ) .kind,
9606
ChangeKind:: Resolution
9607
) ;
9608
let after = app
9609
.load_state ( & app.bookmark_heads ( ) .unwrap ( ) )
9610
.unwrap ( )
9611
.materialize ( ) ;
9612
assert! ( ! after.is_conflicted ( ) ) ;
9613
assert! ( app.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
9614
}
9615
9616
# [ test ]
72484195 Andy McCall 1 month ago ↶
9617
fn carrying_work_over_a_subrepo_already_at_the_target_pin_is_no_conflict ( ) {
9618
9619
9620
hermetic ( ) ;
9621
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9622
let root = dir.path ( ) ;
9623
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
9624
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
9625
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
9626
.unwrap ( )
9627
.with_home ( root.join ( " home" ) ) ;
9628
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
9629
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
9630
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9631
let app_dir = root.join ( " app" ) ;
9632
let app = Repo:: init( & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
9633
write ( & app_dir, " main.txt" , b " app\n " ) ;
9634
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
9635
9636
fs:: rename( app_dir.join ( " libs/lib" ) , root.join ( " aside" ) ) .unwrap ( ) ;
9637
app.commit ( " initial" , & [ ] ) .unwrap ( ) ;
9638
let stale = app.bookmark_heads ( ) .unwrap ( ) ;
9639
fs:: rename( root.join ( " aside" ) , app_dir.join ( " libs/lib" ) ) .unwrap ( ) ;
9640
app.commit ( " pin lib" , & [ ] ) .unwrap ( ) ;
9641
let newer = app.bookmark_heads ( ) .unwrap ( ) ;
9642
app.checkout ( & stale, false , " test" ) .unwrap ( ) ;
9643
write ( & app_dir, " notes.txt" , b " mine\n " ) ;
9644
let ( _ , carried) = app.checkout_carry ( & newer, " main" , " test" ) .unwrap ( ) ;
9645
assert_eq! ( carried, vec! [ " notes.txt" .to_string ( ) ] ) ;
9646
assert! ( app_dir.join ( " notes.txt" ) .exists ( ) ) ;
9647
}
9648
c8d9059e Andy McCall 11 days ago ↶
9649
9650
72484195 Andy McCall 1 month ago ↶
9651
# [ test ]
c8d9059e Andy McCall 11 days ago ↶
9652
fn a_new_bookmark_sends_only_what_the_remote_lacks ( ) {
9653
hermetic ( ) ;
9654
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9655
let root = dir.path ( ) ;
9656
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
9657
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
9658
let mut one = Repo:: clone( & url, & root.join ( " one" ) )
9659
.unwrap ( )
9660
.with_home ( root.join ( " home" ) ) ;
9661
for n in 0 .. 5 {
9662
write ( & root.join ( " one" ) , " a.txt" , format! ( " {n} \n " ) .as_bytes ( ) ) ;
9663
one.commit ( & format! ( " main {n} " ) , & [ ] ) .unwrap ( ) ;
9664
}
9665
assert! ( one.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9666
one.bookmark_create ( " feature" , None ) .unwrap ( ) ;
9667
one.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
9668
write ( & root.join ( " one" ) , " b.txt" , b " feature\n " ) ;
9669
let added = one.commit ( " feature" , & [ ] ) .unwrap ( ) ;
9670
let heads = one.bookmark_heads ( ) .unwrap ( ) ;
9671
let refs = one
9672
.open_named_remote ( " origin" )
9673
.unwrap ( )
9674
.refs ( )
9675
.unwrap ( )
9676
.refs;
9677
assert! (
9678
refs.iter ( ) .all ( | r | r.name ! = " feature" ) ,
9679
" new to the remote"
9680
) ;
9681
let sent = one
9682
.negotiate ( & one.remote_holds ( & refs, & [ ] ) , & heads)
9683
.unwrap ( ) ;
9684
assert_eq! ( sent, vec! [ added] , " only the change main lacks" ) ;
9685
assert! ( one.push_bookmark ( " origin" , " feature" , false ) .unwrap ( ) [ 0 ] .ok) ;
9686
let two = Repo:: clone( & url, & root.join ( " two" ) ) .unwrap ( ) ;
9687
assert! ( two.store ( ) .has ( & added) ) ;
9688
}
9689
9690
# [ test ]
72484195 Andy McCall 1 month ago ↶
9691
fn a_bookmark_only_behind_its_remote_catches_up ( ) {
9692
9693
hermetic ( ) ;
9694
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9695
let root = dir.path ( ) ;
9696
let _origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
9697
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
9698
let one = Repo:: clone( & url, & root.join ( " one" ) )
9699
.unwrap ( )
9700
.with_home ( root.join ( " home" ) ) ;
9701
write ( & root.join ( " one" ) , " a.txt" , b " a\n " ) ;
9702
one.commit ( " A" , & [ ] ) .unwrap ( ) ;
9703
assert! ( one.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9704
let two = Repo:: clone( & url, & root.join ( " two" ) )
9705
.unwrap ( )
9706
.with_home ( root.join ( " home" ) ) ;
9707
write ( & root.join ( " two" ) , " b.txt" , b " b\n " ) ;
9708
two.commit ( " B" , & [ ] ) .unwrap ( ) ;
9709
assert! ( two.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9710
assert_eq! ( one.catch_up_bookmark ( " main" ) .unwrap ( ) , None , " not fetched" ) ;
9711
one.fetch ( " origin" ) .unwrap ( ) ;
9712
assert_eq! ( one.catch_up_bookmark ( " main" ) .unwrap ( ) , Some ( 1 ) ) ;
9713
assert_eq! ( one.bookmarks ( ) .unwrap ( ) [ 0 ] .behind, Some ( 0 ) ) ;
9714
9715
write ( & root.join ( " one" ) , " c.txt" , b " c\n " ) ;
9716
one.commit ( " C" , & [ ] ) .unwrap ( ) ;
9717
write ( & root.join ( " two" ) , " d.txt" , b " d\n " ) ;
9718
two.commit ( " D" , & [ ] ) .unwrap ( ) ;
9719
assert! ( two.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9720
one.fetch ( " origin" ) .unwrap ( ) ;
9721
assert_eq! ( one.catch_up_bookmark ( " main" ) .unwrap ( ) , None ) ;
9722
}
42ccf504 Andy McCall 28 days ago ↶
9723
9724
# [ test ]
9725
fn a_bookmark_is_compared_with_a_remote_that_has_it_when_there_is_no_origin ( ) {
9726
9727
hermetic ( ) ;
9728
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9729
let root = dir.path ( ) ;
9730
let _up = Repo:: init( & root.join ( " up" ) ) .unwrap ( ) ;
9731
let url = root.join ( " up" ) .to_string_lossy ( ) .to_string ( ) ;
9732
let other = Repo:: clone( & url, & root.join ( " other" ) )
9733
.unwrap ( )
9734
.with_home ( root.join ( " home" ) ) ;
9735
write ( & root.join ( " other" ) , " a.txt" , b " a\n " ) ;
9736
other.commit ( " a" , & [ ] ) .unwrap ( ) ;
9737
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9738
let mut one = Repo:: clone( & url, & root.join ( " one" ) )
9739
.unwrap ( )
9740
.with_home ( root.join ( " home" ) ) ;
9741
one.config.set ( " remote.earth.url" , & url) .unwrap ( ) ;
9742
one.config.set ( " remote.gcs.url" , & url) .unwrap ( ) ;
9743
one.config.set ( " remote.origin.url" , " " ) .unwrap ( ) ;
9744
one.save_config ( ) .unwrap ( ) ;
9745
write ( & root.join ( " other" ) , " b.txt" , b " b\n " ) ;
9746
other.commit ( " b" , & [ ] ) .unwrap ( ) ;
9747
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9748
one.fetch ( " earth" ) .unwrap ( ) ;
9749
let b = one
9750
.bookmarks ( )
9751
.unwrap ( )
9752
.into_iter ( )
9753
.find ( | b | b.name = = " main" )
9754
.unwrap ( ) ;
9755
assert_eq! (
9756
( b.remote.as_deref ( ) , b.behind) ,
9757
( Some ( " earth" ) , Some ( 1 ) ) ,
9758
" a stale origin copy is not used"
9759
) ;
9760
assert_eq! (
9761
one.default_remote ( ) ,
9762
" origin" ,
9763
" no default chosen among several"
9764
) ;
9765
let e = one.pull ( & one.default_remote ( ) ) .unwrap_err ( ) .to_string ( ) ;
9766
assert! ( e.contains ( " earth, gcs" ) & & e.contains ( " core.remote" ) , " {e}" ) ;
9767
one.config.set ( " core.remote" , " earth" ) .unwrap ( ) ;
9768
assert_eq! ( one.default_remote ( ) , " earth" ) ;
9769
}
cbe8499a Andy McCall 28 days ago ↶
9770
9771
# [ test ]
9772
fn a_missing_author_identity_says_how_to_fix_it ( ) {
9773
9774
9775
hermetic ( ) ;
9776
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9777
let mut repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9778
let missing = KeyId:: from_hex( & " ab" .repeat ( 32 ) ) .unwrap ( ) ;
9779
repo.config.set ( " author.key" , & missing.to_hex ( ) ) .unwrap ( ) ;
9780
let e = repo.identity ( & missing) .unwrap_err ( ) .to_string ( ) ;
9781
assert! (
9782
e.contains ( " this repository has no identity" ) & & e.contains ( " oxy config author.key" ) ,
9783
" {e}"
9784
) ;
9785
assert! ( e.contains ( " this repository's config" ) , " {e}" ) ;
9786
}
f6dccc45 Andy McCall 27 days ago ↶
9787
9788
# [ test ]
9789
fn a_working_copy_behind_its_bookmark_is_flagged_and_pull_brings_it_up ( ) {
9790
9791
9792
9793
hermetic ( ) ;
9794
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9795
let root = dir.path ( ) ;
9796
let _up = Repo:: init( & root.join ( " up" ) ) .unwrap ( ) ;
9797
let url = root.join ( " up" ) .to_string_lossy ( ) .to_string ( ) ;
9798
let other = Repo:: clone( & url, & root.join ( " other" ) )
9799
.unwrap ( )
9800
.with_home ( root.join ( " home" ) ) ;
9801
write ( & root.join ( " other" ) , " a.txt" , b " a\n " ) ;
9802
other.commit ( " a" , & [ ] ) .unwrap ( ) ;
9803
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9804
let one = Repo:: clone( & url, & root.join ( " one" ) )
9805
.unwrap ( )
9806
.with_home ( root.join ( " home" ) ) ;
9807
write ( & root.join ( " other" ) , " b.txt" , b " b\n " ) ;
9808
other.commit ( " b" , & [ ] ) .unwrap ( ) ;
9809
assert! ( other.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
9810
one.fetch ( " origin" ) .unwrap ( ) ;
9811
9812
let theirs = one.resolve_heads ( " origin/main" ) .unwrap ( ) ;
9813
one.bookmark_set ( " main" , & theirs) .unwrap ( ) ;
9814
assert! ( ! root.join ( " one/b.txt" ) .exists ( ) ) ;
9815
assert_eq! ( one.working_copy_behind ( ) .unwrap ( ) , Some ( 1 ) ) ;
9816
let r = one.pull ( " origin" ) .unwrap ( ) ;
9817
assert! (
9818
! r.changed & & r.checked_out,
9819
" nothing new, but the working copy caught up"
9820
) ;
9821
assert! ( root.join ( " one/b.txt" ) .exists ( ) ) ;
9822
assert_eq! ( one.working_copy_behind ( ) .unwrap ( ) , None ) ;
9823
}
170bb436 Andy McCall 27 days ago ↶
9824
9825
# [ test ]
9826
fn touched_nodes_cache_answers_as_the_changes_do ( ) {
9827
hermetic ( ) ;
9828
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9829
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9830
write ( dir.path ( ) , " a.txt" , b " one\n " ) ;
9831
write ( dir.path ( ) , " b.txt" , b " x\n " ) ;
9832
repo.snapshot ( " t" ) .unwrap ( ) ;
9833
let c1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
9834
write ( dir.path ( ) , " b.txt" , b " y\n " ) ;
9835
repo.snapshot ( " t" ) .unwrap ( ) ;
9836
repo.commit ( " b only" , & [ ] ) .unwrap ( ) ;
9837
fs:: rename( dir.path ( ) .join ( " a.txt" ) , dir.path ( ) .join ( " c.txt" ) ) .unwrap ( ) ;
9838
write ( dir.path ( ) , " c.txt" , b " one\n two\n " ) ;
9839
repo.snapshot ( " t" ) .unwrap ( ) ;
9840
let c3 = repo.commit ( " rename and edit" , & [ ] ) .unwrap ( ) ;
9841
9842
write ( dir.path ( ) , " c.txt" , b " one\n two\n three\n " ) ;
9843
repo.snapshot ( " t" ) .unwrap ( ) ;
9844
9845
let path = dir.path ( ) .join ( " .oxy/index/touched" ) ;
9846
let hashes = | es : Vec< LogEntry> | es.iter ( ) .map ( | e | e.hash ) .collect:: < Vec< _ > > ( ) ;
9847
let cold = hashes ( repo.log_for_path ( " c.txt" ) .unwrap ( ) ) ;
9848
assert! ( path.exists ( ) , " the first query writes the cache" ) ;
9849
let warm = hashes ( repo.log_for_path ( " c.txt" ) .unwrap ( ) ) ;
9850
assert_eq! ( cold, warm, " the cache answers as the changes do" ) ;
9851
let ( wc, _ ) = repo.working_copy ( ) .unwrap ( ) ;
9852
assert_eq! (
9853
cold,
9854
vec! [ wc, c3, c1] ,
9855
" renames followed, the working copy included"
9856
) ;
9857
9858
9859
let cached = super :: touched:: read_touched_for_test( & fs:: read( & path) .unwrap ( ) ) .unwrap ( ) ;
9860
assert! ( ! cached.contains ( & wc) , " a working-copy change is not cached" ) ;
9861
assert! ( cached.contains ( & c1) & & cached.contains ( & c3) ) ;
9862
9863
9864
let mut bytes = fs:: read( & path) .unwrap ( ) ;
9865
let last = bytes.len ( ) - 40 ;
9866
bytes[ last] ^ = 1 ;
9867
fs:: write( & path, & bytes) .unwrap ( ) ;
9868
assert_eq! ( hashes ( repo.log_for_path ( " c.txt" ) .unwrap ( ) ) , cold) ;
9869
let touches = repo.log_revset ( " touches(\" c.txt\" ) ~ @" ) .unwrap ( ) ;
9870
assert_eq! ( hashes ( touches) , vec! [ c3, c1] ) ;
9871
}
e8e6e0ca Andy McCall 27 days ago ↶
9872
9873
9874
9875
9876
9877
# [ test ]
9878
fn git_import_removes_every_copy_of_a_file_at_one_place ( ) {
9879
hermetic ( ) ;
9880
if std:: process:: Command:: new( " git" )
9881
.arg ( " --version" )
9882
.output ( )
9883
.is_err ( )
9884
{
9885
eprintln! ( " git not installed; skipping" ) ;
9886
return ;
9887
}
9888
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9889
let root = dir.path ( ) ;
9890
let g = root.join ( " src.git" ) ;
9891
fs:: create_dir_all( & g) .unwrap ( ) ;
9892
let git = | args : & [& str ]| {
9893
let out = std:: process:: Command:: new( " git" )
9894
.arg ( " -C" )
9895
.arg ( & g)
9896
.args ( [
9897
" -c" ,
9898
" user.name=Grace" ,
9899
" -c" ,
9900
" [email protected] " ,
9901
" -c" ,
9902
" commit.gpgsign=false" ,
9903
] )
9904
.args ( args)
9905
.output ( )
9906
.unwrap ( ) ;
9907
assert! (
9908
out.status.success ( ) ,
9909
" git {args:?}: {}" ,
9910
String :: from_utf8_lossy( & out.stderr)
9911
) ;
9912
} ;
9913
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
9914
write ( & g, " d/list.txt" , b " one\n two\n three\n four\n " ) ;
9915
write ( & g, " keep.txt" , b " k\n " ) ;
9916
git ( & [ " add" , " ." ] ) ;
9917
git ( & [ " commit" , " -qm" , " base" ] ) ;
9918
fs:: create_dir_all( g.join ( " d/sub" ) ) .unwrap ( ) ;
9919
git ( & [ " mv" , " d/list.txt" , " d/sub/list.txt" ] ) ;
9920
git ( & [ " commit" , " -qm" , " into sub" ] ) ;
9921
git ( & [ " checkout" , " -qb" , " side" ] ) ;
9922
git ( & [ " mv" , " d/sub/list.txt" , " d/list.txt" ] ) ;
9923
git ( & [ " commit" , " -qm" , " back, on side" ] ) ;
9924
git ( & [ " checkout" , " -q" , " main" ] ) ;
9925
git ( & [ " mv" , " d/sub/list.txt" , " d/list.txt" ] ) ;
9926
git ( & [ " commit" , " -qm" , " back, on main" ] ) ;
9927
git ( & [ " merge" , " -q" , " --no-edit" , " side" ] ) ;
9928
git ( & [ " rm" , " -rq" , " d" ] ) ;
9929
git ( & [ " commit" , " -qm" , " d goes" ] ) ;
9930
9931
let repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
9932
repo.import_git ( & g, " main" ) .unwrap ( ) ;
9933
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
9934
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
9935
let st = repo
9936
.load_state_history ( & repo.bookmark_heads ( ) .unwrap ( ) )
9937
.unwrap ( ) ;
9938
let view = st.tree ( ) .view ( ) ;
9939
assert! ( view.conflicts.is_empty ( ) , " {:?}" , view.conflicts) ;
9940
assert_eq! ( view.entries.keys ( ) .collect:: < Vec< _ > > ( ) , vec! [ " keep.txt" ] ) ;
9941
}
5552e69c Andy McCall 27 days ago ↶
9942
9943
9944
9945
9946
# [ test ]
9947
fn a_damaged_file_history_in_the_state_index_is_rebuilt ( ) {
9948
hermetic ( ) ;
9949
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
9950
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
9951
for i in 0 .. 6 {
9952
write (
9953
dir.path ( ) ,
9954
& format! ( " f{i} .txt" ) ,
9955
format! ( " one {i} \n " ) .as_bytes ( ) ,
9956
) ;
9957
}
9958
repo.snapshot ( " t" ) .unwrap ( ) ;
9959
repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
9960
for i in 0 .. 6 {
9961
write (
9962
dir.path ( ) ,
9963
& format! ( " f{i} .txt" ) ,
9964
format! ( " one {i} \n two\n " ) .as_bytes ( ) ,
9965
) ;
9966
}
9967
repo.snapshot ( " t" ) .unwrap ( ) ;
9968
repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
9969
let want: Vec< Vec< u8 > > = ( 0 .. 6 )
9970
.map ( | i | format! ( " one {i} \n two\n " ) .into_bytes ( ) )
9971
.collect ( ) ;
9972
9973
9974
let ( _ , wc) = repo.working_copy ( ) .unwrap ( ) ;
9975
let base = repo.load_state_history ( & wc.deps) .unwrap ( ) ;
9976
repo.write_state_index ( & wc.deps, & base) ;
9977
for ( i, w) in want.iter ( ) .enumerate ( ) {
9978
assert_eq! ( & repo.file_at ( & format! ( " f{i} .txt" ) , None ) .unwrap ( ) , w) ;
9979
}
9980
let index = repo.state_indexes ( ) ;
9981
assert! ( ! index.is_empty ( ) , " a state index was written" ) ;
9982
9983
let mut bytes = fs:: read( & index[ 0 ] ) .unwrap ( ) ;
9984
let last = bytes.len ( ) - 1 ;
9985
bytes[ last] ^ = 0x40 ;
9986
fs:: write( & index[ 0 ] , & bytes) .unwrap ( ) ;
9987
9988
let reads: Vec< _ > = ( 0 .. 6 )
9989
.map ( | i | repo.file_at ( & format! ( " f{i} .txt" ) , None ) )
9990
.collect ( ) ;
9991
let damaged: Vec< String> = reads
9992
.iter ( )
9993
.filter_map ( | r | r.as_ref ( ) .err ( ) .map ( | e | e.to_string ( ) ) )
9994
.collect ( ) ;
9995
assert_eq! ( damaged.len ( ) , 1 , " {damaged:?}" ) ;
9996
assert! (
9997
damaged[ 0 ] .contains ( " state index was damaged" )
9998
& & damaged[ 0 ] .contains ( " run the command again" ) ,
9999
" {}" ,
10000
damaged[ 0 ]
10001
) ;
10002
10003
for ( i, w) in want.iter ( ) .enumerate ( ) {
10004
assert_eq! ( & repo.file_at ( & format! ( " f{i} .txt" ) , None ) .unwrap ( ) , w) ;
10005
}
10006
}
cb8b8082 Andy McCall 27 days ago ↶
10007
10008
10009
10010
10011
10012
# [ test ]
10013
fn a_long_push_goes_in_parts ( ) {
10014
hermetic ( ) ;
10015
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10016
let root = dir.path ( ) ;
10017
let origin = root.join ( " origin" ) ;
10018
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
10019
let main = root.join ( " main" ) ;
10020
let mut repo = Repo:: init( & main) .unwrap ( ) ;
10021
repo.config
10022
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
10023
.unwrap ( ) ;
10024
10025
repo.config.set ( " push.batch-bytes" , " 1" ) .unwrap ( ) ;
10026
repo.save_config ( ) .unwrap ( ) ;
10027
for i in 0 .. 6 {
10028
write (
10029
& main,
10030
& format! ( " f{i} .txt" ) ,
10031
format! ( " file {i} \n " ) .as_bytes ( ) ,
10032
) ;
10033
repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ;
10034
}
10035
let mut parts = Vec :: new( ) ;
10036
let results = repo
10037
.push_bookmark_with ( " origin" , " main" , false , & mut | m| parts.push ( m.to_string ( ) ) )
10038
.unwrap ( ) ;
10039
assert! (
10040
results.iter ( ) .any ( | r | r.ok & & r.name = = " main" ) ,
10041
" {results:?}"
10042
) ;
10043
assert_eq! ( parts.len ( ) , 6 , " {parts:?}" ) ;
10044
assert_eq! ( parts[ 0 ] , " part 1 of 6: 1 changes" ) ;
10045
10046
let remote = Repo:: open( & origin) .unwrap ( ) ;
10047
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
10048
assert_eq! ( remote.bookmark_heads ( ) .unwrap ( ) , heads) ;
10049
assert_eq! (
10050
remote.log_state ( & heads) .unwrap ( ) .len ( ) ,
10051
repo.log_state ( & heads) .unwrap ( ) .len ( )
10052
) ;
10053
10054
let got = remote
10055
.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ ] )
10056
.unwrap ( ) ;
10057
assert_eq! ( got.len ( ) , 1 ) ;
10058
assert! ( got[ 0 ] .ok & & got[ 0 ] .name = = OBJECTS_RESULT , " {got:?}" ) ;
10059
}
d6e0d5b8 Andy McCall 26 days ago ↶
10060
d3b27090 Andy McCall 12 days ago ↶
10061
10062
10063
10064
10065
# [ test ]
10066
fn a_heavy_change_sends_its_blobs_in_parts ( ) {
10067
hermetic ( ) ;
10068
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10069
let root = dir.path ( ) ;
10070
let origin = root.join ( " origin" ) ;
10071
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
10072
let main = root.join ( " main" ) ;
10073
let mut repo = Repo:: init( & main) .unwrap ( ) ;
10074
repo.config
10075
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
10076
.unwrap ( ) ;
10077
repo.config.set ( " push.batch-bytes" , " 50000" ) .unwrap ( ) ;
10078
repo.save_config ( ) .unwrap ( ) ;
10079
let mut x: u64 = 0x9e3779b97f4a7c15 ;
10080
let mut noise = | n : usize | -> Vec< u8 > {
10081
( 0 .. n)
10082
.map ( | _| {
10083
x ^ = x < < 13 ;
10084
x ^ = x > > 7 ;
10085
x ^ = x < < 17 ;
10086
( x > > 24 ) as u8
10087
} )
10088
.collect ( )
10089
} ;
10090
let files: Vec< Vec< u8 > > = ( 0 .. 4 ) .map ( | _| noise ( 40_000 ) ) .collect ( ) ;
10091
for ( i, f) in files.iter ( ) .enumerate ( ) {
10092
write ( & main, & format! ( " bin{i} .dat" ) , f) ;
10093
}
10094
repo.commit ( " four blobs" , & [ ] ) .unwrap ( ) ;
10095
let mut parts = Vec :: new( ) ;
10096
let results = repo
10097
.push_bookmark_with ( " origin" , " main" , false , & mut | m| parts.push ( m.to_string ( ) ) )
10098
.unwrap ( ) ;
10099
assert! (
10100
results.iter ( ) .any ( | r | r.ok & & r.name = = " main" ) ,
10101
" {results:?}"
10102
) ;
10103
assert! (
10104
parts.len ( ) > = 3 & & parts[ 0 ] .starts_with ( " objects 1 of" ) ,
10105
" {parts:?}"
10106
) ;
10107
let back = Repo:: clone( & origin.to_string_lossy ( ) , & root.join ( " back" ) ) .unwrap ( ) ;
10108
for ( i, f) in files.iter ( ) .enumerate ( ) {
10109
assert_eq! ( & fs:: read( root.join ( format! ( " back/bin{i} .dat" ) ) ) .unwrap ( ) , f) ;
10110
}
10111
drop ( back) ;
10112
}
10113
570e3531 Andy McCall 11 days ago ↶
10114
10115
10116
10117
10118
# [ test ]
10119
fn a_replacement_does_not_send_the_old_content_again ( ) {
10120
hermetic ( ) ;
10121
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10122
let root = dir.path ( ) ;
10123
let origin = root.join ( " origin" ) ;
10124
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
10125
let main = root.join ( " main" ) ;
10126
let mut repo = Repo:: init( & main) .unwrap ( ) ;
10127
repo.config
10128
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
10129
.unwrap ( ) ;
10130
repo.save_config ( ) .unwrap ( ) ;
10131
let blobs = | b : & Bundle| {
10132
b.objects
10133
.iter ( )
10134
.filter ( | ( ty , _) | * ty = = ObjectType:: Blob)
10135
.count ( )
10136
} ;
10137
write ( & main, " bin.dat" , b " \0 \x01 first binary" ) ;
10138
write ( & main, " gone.dat" , b " \0 \x02 to be removed" ) ;
10139
let added = repo.commit ( " add" , & [ ] ) .unwrap ( ) ;
10140
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10141
write ( & main, " bin.dat" , b " \0 \x01 second binary" ) ;
10142
fs:: remove_file( main.join ( " gone.dat" ) ) .unwrap ( ) ;
10143
let changed = repo.commit ( " replace and remove" , & [ ] ) .unwrap ( ) ;
10144
assert_eq! ( blobs ( & repo.bundle_for ( & [ changed] ) .unwrap ( ) ) , 1 ) ;
10145
assert_eq! ( blobs ( & repo.bundle_for ( & [ added, changed] ) .unwrap ( ) ) , 3 ) ;
10146
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10147
let origin = Repo:: open( & origin) .unwrap ( ) ;
10148
assert_eq! ( origin.fsck ( ) .unwrap ( ) .problems, vec! [ ] ) ;
10149
let back = Repo:: clone( & origin.root ( ) .to_string_lossy ( ) , & root.join ( " back" ) ) .unwrap ( ) ;
10150
assert_eq! ( back.fsck ( ) .unwrap ( ) .problems, vec! [ ] ) ;
10151
assert_eq! (
10152
fs:: read( root.join ( " back/bin.dat" ) ) .unwrap ( ) ,
10153
b " \0 \x01 second binary"
10154
) ;
10155
assert! ( ! root.join ( " back/gone.dat" ) .exists ( ) ) ;
10156
}
10157
d6e0d5b8 Andy McCall 26 days ago ↶
10158
10159
10160
10161
# [ test ]
10162
fn git_import_claims_your_own_commits ( ) {
10163
hermetic ( ) ;
10164
if std:: process:: Command:: new( " git" )
10165
.arg ( " --version" )
10166
.output ( )
10167
.is_err ( )
10168
{
10169
eprintln! ( " git not installed; skipping" ) ;
10170
return ;
10171
}
10172
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10173
let root = dir.path ( ) ;
10174
let g = root.join ( " src.git" ) ;
10175
fs:: create_dir_all( & g) .unwrap ( ) ;
10176
let git = | who : & str , args : & [& str ]| {
10177
let ( name, email) = who.split_once ( ' ' ) .unwrap ( ) ;
10178
let out = std:: process:: Command:: new( " git" )
10179
.arg ( " -C" )
10180
.arg ( & g)
10181
.args ( [
10182
" -c" ,
10183
& format! ( " user.name={name} " ) ,
10184
" -c" ,
10185
& format! ( " user.email={email} " ) ,
10186
" -c" ,
10187
" commit.gpgsign=false" ,
10188
] )
10189
.args ( args)
10190
.output ( )
10191
.unwrap ( ) ;
10192
assert! (
10193
out.status.success ( ) ,
10194
" git {args:?}: {}" ,
10195
String :: from_utf8_lossy( & out.stderr)
10196
) ;
10197
} ;
10198
git ( " Grace [email protected] " , & [ " init" , " -q" , " -b" , " main" ] ) ;
10199
write ( & g, " a.txt" , b " a\n " ) ;
10200
git ( " Grace [email protected] " , & [ " add" , " ." ] ) ;
10201
git ( " Grace [email protected] " , & [ " commit" , " -qm" , " mine" ] ) ;
10202
write ( & g, " b.txt" , b " b\n " ) ;
10203
git ( " Ada [email protected] " , & [ " add" , " ." ] ) ;
10204
git ( " Ada [email protected] " , & [ " commit" , " -qm" , " someone else's" ] ) ;
10205
10206
let mut repo = Repo:: init( & root.join ( " o" ) ) .unwrap ( ) ;
10207
let ( key, _ ) = repo
10208
.create_identity ( " Grace" , " [email protected] " , IdentityScope:: Repo)
10209
.unwrap ( ) ;
10210
repo.config.set ( " git.claim" , " true" ) .unwrap ( ) ;
10211
repo.save_config ( ) .unwrap ( ) ;
10212
let repo = Repo:: open( & root.join ( " o" ) ) .unwrap ( ) ;
10213
repo.import_git ( & g, " main" ) .unwrap ( ) ;
10214
let v = repo.verify_git_import ( & g, " main" ) .unwrap ( ) ;
10215
assert! ( v.mismatches.is_empty ( ) , " {:?}" , v.mismatches) ;
10216
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
10217
let by_message: std:: collections:: HashMap< String, KeyId> = repo
10218
.log_state ( & heads)
10219
.unwrap ( )
10220
.into_iter ( )
10221
.map ( | e | {
10222
(
10223
e.change.message.lines ( ) .next ( ) .unwrap_or ( " " ) .to_string ( ) ,
10224
e.change.author,
10225
)
10226
} )
10227
.collect ( ) ;
10228
assert_eq! ( by_message[ " mine" ] , key, " her own commit, any case of email" ) ;
10229
assert_eq! ( by_message[ " someone else's" ] , ObjectRef:: ZERO , " not hers" ) ;
10230
}
34fad4a9 Andy McCall 26 days ago ↶
10231
10232
10233
10234
# [ test ]
10235
fn removing_a_remote_leaves_nothing_of_it ( ) {
10236
hermetic ( ) ;
10237
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10238
let origin = dir.path ( ) .join ( " origin" ) ;
10239
Repo:: init_bare_unbound( & origin) .unwrap ( ) ;
10240
let main = dir.path ( ) .join ( " main" ) ;
10241
let mut repo = Repo:: init( & main) .unwrap ( ) ;
10242
repo.config
10243
.set ( " remote.origin.url" , & origin.to_string_lossy ( ) )
10244
.unwrap ( ) ;
10245
repo.config
10246
.set ( " remote.origin.token" , " secret-ish" )
10247
.unwrap ( ) ;
10248
repo.config
10249
.set ( " remote.keep.url" , & origin.to_string_lossy ( ) )
10250
.unwrap ( ) ;
10251
repo.save_config ( ) .unwrap ( ) ;
10252
write ( & main, " a.txt" , b " a\n " ) ;
10253
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10254
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10255
assert! (
10256
repo.view ( ) .unwrap ( ) .remotes.contains_key ( " origin" ) ,
10257
" a tracking bookmark"
10258
) ;
10259
10260
repo.remove_remote ( " origin" ) .unwrap ( ) ;
10261
let again = Repo:: open( & main) .unwrap ( ) ;
10262
assert! (
10263
! again
10264
.config
10265
.repo_layer ( )
10266
.keys ( )
10267
.any ( | k | k.starts_with ( " remote.origin." ) ) ,
10268
" {:?}" ,
10269
again.config.repo_layer ( )
10270
) ;
10271
assert! ( ! again.config.remotes.contains_key ( " origin" ) ) ;
10272
assert! (
10273
! again.view ( ) .unwrap ( ) .remotes.contains_key ( " origin" ) ,
10274
" its tracking bookmarks go"
10275
) ;
10276
assert! (
10277
again.config.remotes.contains_key ( " keep" ) ,
10278
" another remote stays"
10279
) ;
10280
let mut again = again;
10281
assert! (
10282
again.remove_remote ( " origin" ) .is_err ( ) ,
10283
" a second removal is refused"
10284
) ;
10285
}
9d580f21 Andy McCall 26 days ago ↶
10286
10287
10288
10289
10290
# [ test ]
10291
fn gc_keeps_the_blobs_a_host_names ( ) {
10292
hermetic ( ) ;
10293
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10294
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
10295
write ( dir.path ( ) , " a.txt" , b " a\n " ) ;
10296
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10297
10298
let body: Vec< u8 > = ( 0 .. 400_000 u32 )
10299
.flat_map ( | i | ( i.wrapping_mul ( 2654435761 ) ) .to_le_bytes ( ) )
10300
.collect ( ) ;
10301
let kept = repo.store_blob ( & body) .unwrap ( ) ;
10302
let lost = repo.store_blob ( b " an artifact nobody names" ) .unwrap ( ) ;
10303
repo.gc_keeping ( true , & [ kept] , & mut | _ | { } ) .unwrap ( ) ;
10304
assert_eq! ( repo.read_blob ( & kept) .unwrap ( ) , body, " named: kept whole" ) ;
10305
assert! ( ! repo.store ( ) .has ( & lost) , " not named: pruned" ) ;
10306
repo.gc ( true ) .unwrap ( ) ;
10307
assert! ( ! repo.store ( ) .has ( & kept) , " a plain gc prunes it" ) ;
10308
}
530a0fc6 Andy McCall 25 days ago ↶
10309
10310
10311
10312
10313
# [ test ]
10314
fn undo_is_scoped_to_its_workspace ( ) {
10315
hermetic ( ) ;
10316
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10317
let root = dir.path ( ) .join ( " main" ) ;
10318
let a = Repo:: init( & root) .unwrap ( ) ;
10319
write ( & root, " base.txt" , b " base\n " ) ;
10320
let base = a.commit ( " base" , & [ ] ) .unwrap ( ) ;
10321
a.bookmark_create ( " feature" , None ) .unwrap ( ) ;
10322
let ws_dir = dir.path ( ) .join ( " ws" ) ;
10323
let heads = a.resolve_heads ( " feature" ) .unwrap ( ) ;
10324
let b = a
10325
.workspace_add_on ( & ws_dir, Some ( & heads) , Some ( " feature" ) )
10326
.unwrap ( ) ;
10327
10328
10329
write ( & root, " a.txt" , b " a\n " ) ;
10330
let ca = a.commit ( " from a" , & [ ] ) .unwrap ( ) ;
10331
write ( & ws_dir, " b.txt" , b " b\n " ) ;
10332
let cb = b.commit ( " from b" , & [ ] ) .unwrap ( ) ;
10333
10334
10335
let u = a.undo ( ) .unwrap ( ) ;
10336
assert_eq! ( u.command, " oxy commit -m \" from a\" " ) ;
10337
assert_eq! ( u.others_kept, 1 , " B's newer commit is left" ) ;
10338
assert! ( ! u.changes.is_empty ( ) ) ;
10339
let view = a.view ( ) .unwrap ( ) ;
10340
10341
assert_eq! ( a.heads_of ( & view, " main" ) .unwrap ( ) , vec! [ base] ) ;
10342
assert! (
10343
a.heads_of ( & view, " feature" ) .unwrap ( ) .contains ( & cb) ,
10344
" B's commit stands"
10345
) ;
10346
assert! (
10347
a.store ( ) .get_change ( & ca) .is_ok ( ) ,
10348
" the change itself is still in the store"
10349
) ;
10350
10351
assert! ( ! root.join ( " a.txt" ) .exists ( ) ) ;
10352
assert! ( a.snapshot ( " oxy status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
10353
assert! ( ws_dir.join ( " b.txt" ) .exists ( ) ) ;
10354
10355
10356
let u = b.undo ( ) .unwrap ( ) ;
10357
assert_eq! ( u.command, " oxy commit -m \" from b\" " ) ;
10358
assert! ( ! b
10359
.heads_of ( & b.view ( ) .unwrap ( ) , " feature" )
10360
.unwrap ( )
10361
.contains ( & cb) ) ;
10362
assert_eq! (
10363
a.heads_of ( & a.view ( ) .unwrap ( ) , " main" ) .unwrap ( ) ,
10364
vec! [ base] ,
10365
" A's undo stands"
10366
) ;
10367
10368
10369
10370
let e = a.undo ( ) .unwrap_err ( ) .to_string ( ) ;
10371
assert! ( e.contains ( " the workspace at" ) & & e.contains ( " ws" ) , " {e}" ) ;
10372
}
10373
10374
10375
10376
# [ test ]
10377
fn undo_on_a_shared_bookmark_keeps_the_other_workspaces_head ( ) {
10378
hermetic ( ) ;
10379
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10380
let root = dir.path ( ) .join ( " main" ) ;
10381
let a = Repo:: init( & root) .unwrap ( ) ;
10382
write ( & root, " base.txt" , b " base\n " ) ;
10383
a.commit ( " base" , & [ ] ) .unwrap ( ) ;
10384
let ws_dir = dir.path ( ) .join ( " ws" ) ;
10385
let b = a.workspace_add ( & ws_dir, None ) .unwrap ( ) ;
10386
write ( & root, " a.txt" , b " a\n " ) ;
10387
let ca = a.commit ( " from a" , & [ ] ) .unwrap ( ) ;
10388
write ( & ws_dir, " b.txt" , b " b\n " ) ;
10389
let cb = b.commit ( " from b" , & [ ] ) .unwrap ( ) ;
10390
let u = a.undo ( ) .unwrap ( ) ;
10391
assert_eq! ( u.command, " oxy commit -m \" from a\" " ) ;
10392
let heads = a.heads_of ( & a.view ( ) .unwrap ( ) , " main" ) .unwrap ( ) ;
10393
assert! ( heads.contains ( & cb) & & ! heads.contains ( & ca) , " {heads:?}" ) ;
10394
}
10395
10396
10397
10398
# [ test ]
10399
fn undo_refuses_to_take_back_another_workspaces_work ( ) {
10400
hermetic ( ) ;
10401
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10402
let root = dir.path ( ) .join ( " main" ) ;
10403
let a = Repo:: init( & root) .unwrap ( ) ;
10404
write ( & root, " base.txt" , b " base\n " ) ;
10405
a.commit ( " base" , & [ ] ) .unwrap ( ) ;
10406
let ws_dir = dir.path ( ) .join ( " ws" ) ;
10407
let b = a.workspace_add ( & ws_dir, None ) .unwrap ( ) ;
10408
10409
write ( & root, " a.txt" , b " a\n " ) ;
10410
a.commit ( " from a" , & [ ] ) .unwrap ( ) ;
767ac6cd Andy McCall 18 days ago
10411
b.refresh ( ) .unwrap ( ) ;
530a0fc6 Andy McCall 25 days ago ↶
10412
let heads = b.resolve_heads ( " main" ) .unwrap ( ) ;
10413
b.checkout ( & heads, false , " oxy checkout" ) .unwrap ( ) ;
10414
write ( & ws_dir, " a.txt" , b " a, then b\n " ) ;
10415
b.commit ( " from b" , & [ ] ) .unwrap ( ) ;
10416
let e = a.undo ( ) .unwrap_err ( ) .to_string ( ) ;
10417
assert! (
10418
e.contains ( " bookmark main" ) & & e.contains ( " builds on" ) ,
10419
" {e}"
10420
) ;
10421
assert! ( e.contains ( " oxy op restore" ) , " {e}" ) ;
10422
}
10423
10424
10425
10426
# [ test ]
10427
fn undo_says_when_the_commit_was_pushed ( ) {
10428
hermetic ( ) ;
10429
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10430
let root = dir.path ( ) ;
10431
Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
10432
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
10433
let repo = Repo:: clone( & url, & root.join ( " work" ) )
10434
.unwrap ( )
10435
.with_home ( root.join ( " home" ) ) ;
10436
write ( & root.join ( " work" ) , " a.txt" , b " a\n " ) ;
10437
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10438
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10439
10440
let u = repo.undo ( ) .unwrap ( ) ;
10441
assert_eq! ( u.command, " oxy commit -m \" a\" " ) ;
10442
assert_eq! ( u.pushed, vec! [ ( " main" .to_string ( ) , " origin" .to_string ( ) ) ] ) ;
10443
}
b689779e Andy McCall 25 days ago ↶
10444
10445
10446
10447
10448
# [ test ]
10449
fn large_new_files_are_left_out_of_snapshots ( ) {
10450
hermetic ( ) ;
10451
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10452
let root = dir.path ( ) ;
10453
let mut repo = Repo:: init( root) .unwrap ( ) ;
10454
repo.config.max_new_file = 1024 ;
10455
write ( root, " small.txt" , b " small\n " ) ;
10456
write ( root, " big.bin" , & vec! [ 0 u8 ; 4096 ] ) ;
10457
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10458
let paths: Vec< _ > = s.statuses.iter ( ) .map ( | p | p.path.as_str ( ) ) .collect ( ) ;
10459
assert_eq! ( paths, [ " small.txt" ] ) ;
10460
assert_eq! ( repo.skipped_large ( ) , vec! [ ( " big.bin" .to_string ( ) , 4096 ) ] ) ;
10461
repo.commit ( " small" , & [ ] ) .unwrap ( ) ;
10462
10463
10464
write ( root, " small.txt" , & vec! [ b ' x' ; 4096 ] ) ;
10465
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10466
assert! ( s.statuses.iter ( ) .any ( | p | p.path = = " small.txt" ) ) ;
10467
10468
repo.config.max_new_file = 0 ;
10469
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10470
assert! ( s.statuses.iter ( ) .any ( | p | p.path = = " big.bin" ) ) ;
10471
repo.config.max_new_file = 1024 ;
10472
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10473
assert! (
10474
s.statuses.iter ( ) .any ( | p | p.path = = " big.bin" ) ,
10475
" already in the working copy"
10476
) ;
10477
assert! ( repo.skipped_large ( ) .is_empty ( ) ) ;
10478
}
10479
10480
10481
10482
# [ test ]
10483
fn gc_prunes_what_only_expired_snapshots_held ( ) {
10484
hermetic ( ) ;
10485
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10486
let root = dir.path ( ) ;
10487
let mut repo = Repo:: init( root) .unwrap ( ) ;
10488
repo.config.gc_grace_days = 0 ;
10489
write ( root, " a.txt" , b " a\n " ) ;
10490
repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10491
let image: Vec< u8 > = ( 0 .. 200_000 u32 ) .map ( | i | ( i % 7 ) as u8 ) .collect ( ) ;
10492
write ( root, " image.tar" , & image) ;
10493
repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10494
std:: fs:: remove_file( root.join ( " image.tar" ) ) .unwrap ( ) ;
10495
repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10496
let blob = Repo:: blob_ref( & image) ;
10497
assert! ( repo.store ( ) .has ( & blob) ) ;
10498
repo.gc ( true ) .unwrap ( ) ;
10499
assert! ( repo.store ( ) .has ( & blob) , " a recent snapshot keeps it" ) ;
10500
repo.config.snapshot_retention_days = 0 ;
10501
repo.gc ( true ) .unwrap ( ) ;
10502
assert! ( ! repo.store ( ) .has ( & blob) , " an expired snapshot does not" ) ;
10503
assert_eq! (
10504
repo.file_at ( " a.txt" , None ) .unwrap ( ) ,
10505
b " a\n " ,
10506
" committed files stay"
10507
) ;
fe9586d9 Andy McCall 25 days ago ↶
10508
}
10509
10510
10511
10512
10513
# [ test ]
10514
fn carrying_work_the_target_already_has_is_no_conflict ( ) {
10515
hermetic ( ) ;
10516
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10517
let root = dir.path ( ) ;
10518
let mut repo = Repo:: init( root) .unwrap ( ) ;
10519
write ( root, " a.txt" , b " one\n " ) ;
10520
write ( root, " b.txt" , b " b\n " ) ;
10521
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
10522
repo.bookmark_create ( " landed" , None ) .unwrap ( ) ;
10523
10524
repo.bookmark_switch ( " landed" , false ) .unwrap ( ) ;
10525
write ( root, " a.txt" , b " two\n " ) ;
10526
repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
10527
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
10528
assert_eq! ( std:: fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n " ) ;
10529
10530
write ( root, " a.txt" , b " two\n " ) ;
10531
write ( root, " b.txt" , b " b, edited\n " ) ;
10532
let ( _ , carried) = repo.bookmark_switch_carry ( " landed" ) .unwrap ( ) ;
10533
assert_eq! (
10534
carried,
10535
vec! [ " b.txt" .to_string ( ) ] ,
10536
" only the work landed lacks is carried"
10537
) ;
10538
assert_eq! ( std:: fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " two\n " ) ;
10539
assert_eq! ( std:: fs:: read( root.join ( " b.txt" ) ) .unwrap ( ) , b " b, edited\n " ) ;
10540
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
10541
let paths: Vec< _ > = s.statuses.iter ( ) .map ( | p | p.path.as_str ( ) ) .collect ( ) ;
10542
assert_eq! ( paths, [ " b.txt" ] ) ;
10543
10544
10545
repo.bookmark_switch ( " main" , true ) .unwrap ( ) ;
10546
write ( root, " a.txt" , b " three\n " ) ;
10547
let e = repo
10548
.bookmark_switch_carry ( " landed" )
10549
.unwrap_err ( )
10550
.to_string ( ) ;
10551
assert! ( e.contains ( " a.txt" ) & & e.contains ( " --discard" ) , " {e}" ) ;
b689779e Andy McCall 25 days ago ↶
10552
}
aa4939e0 Andy McCall 25 days ago ↶
10553
10554
10555
10556
# [ test ]
10557
fn a_full_hash_resolves_without_its_hash_sign ( ) {
10558
hermetic ( ) ;
10559
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10560
let root = dir.path ( ) ;
10561
let repo = Repo:: init( root) .unwrap ( ) ;
10562
write ( root, " a.txt" , b " a\n " ) ;
10563
let h = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10564
assert_eq! ( repo.resolve_change ( & h.to_hex ( ) ) .unwrap ( ) , h) ;
10565
assert_eq! ( repo.resolve_change ( & format! ( " #{} " , h.to_hex ( ) ) ) .unwrap ( ) , h) ;
10566
assert! ( repo
10567
.resolve_change ( & " 0" .repeat ( 64 ) )
10568
.unwrap_err ( )
10569
.to_string ( )
10570
.contains ( " no change matches" ) ) ;
10571
}
f2f91ceb Andy McCall 24 days ago ↶
10572
10573
# [ test ]
10574
fn a_pin_change_moves_only_the_pin ( ) {
10575
hermetic ( ) ;
10576
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10577
let root = dir.path ( ) ;
10578
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
10579
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
10580
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
10581
.unwrap ( )
10582
.with_home ( root.join ( " home" ) ) ;
10583
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
10584
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
10585
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10586
let _app_origin = Repo:: init( & root.join ( " app-origin" ) ) .unwrap ( ) ;
10587
let app_dir = root.join ( " app" ) ;
10588
let app = Repo:: clone( & root.join ( " app-origin" ) .to_string_lossy ( ) , & app_dir)
10589
.unwrap ( )
10590
.with_home ( root.join ( " home" ) ) ;
10591
write ( & app_dir, " main.txt" , b " app\n " ) ;
10592
let pin1 = app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
10593
let a1 = app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
10594
10595
write ( & root.join ( " lib" ) , " more.txt" , b " v2\n " ) ;
10596
let l2 = lib.commit ( " lib v2" , & [ ] ) .unwrap ( ) ;
10597
let crate :: subrepo:: Pin:: Oxy { repo, bookmark, .. } = pin1 else {
10598
panic! ( " an Oxygen pin" )
10599
} ;
10600
let pin2 = crate :: subrepo:: Pin:: Oxy {
10601
repo,
10602
heads: vec! [ l2] ,
10603
bookmark,
10604
} ;
10605
let author = app.store ( ) .get_change ( & a1) .unwrap ( ) .author;
10606
let change = app
10607
.build_pin_change ( & [ a1] , " libs/lib" , & pin2, " Move lib to v2" , author)
10608
.unwrap ( ) ;
10609
let mut state = app.load_state ( & [ a1] ) .unwrap ( ) ;
10610
state.add ( change) .unwrap ( ) ;
10611
let files = state.materialize ( ) .files;
10612
assert_eq! ( files[ " libs/lib" ] .kind, NodeKind:: Subrepo) ;
10613
let FileContent:: Opaque { blobs } = & files[ " libs/lib" ] .content else {
10614
panic! ( " a pin is opaque" )
10615
} ;
10616
let read = crate :: subrepo:: Pin:: decode( & app.read_blob ( & blobs[ 0 ] ) .unwrap ( ) ) .unwrap ( ) ;
10617
assert_eq! ( read, pin2, " the new pin" ) ;
10618
assert! (
10619
matches! ( & files[ " main.txt" ] .content, FileContent:: Text { bytes, .. } if bytes.as_slice ( ) = = b " app\n " ) ,
10620
" nothing else changes"
10621
) ;
10622
10623
let e = app.build_pin_change ( & [ a1] , " main.txt" , & pin2, " x" , author) ;
10624
assert! ( format! ( " {:?} " , e.unwrap_err ( ) ) .contains ( " not a subrepo" ) ) ;
10625
let e = app.build_pin_change ( & [ a1] , " libs/none" , & pin2, " x" , author) ;
10626
assert! ( format! ( " {:?} " , e.unwrap_err ( ) ) .contains ( " does not exist" ) ) ;
10627
}
767ac6cd Andy McCall 18 days ago
10628
b5e98094 Andy McCall 10 days ago ↶
10629
10630
10631
10632
# [ test ]
10633
fn a_subrepo_at_its_pinned_state_on_another_bookmark_is_unmodified ( ) {
10634
hermetic ( ) ;
10635
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10636
let root = dir.path ( ) ;
10637
let _lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
10638
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
10639
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) )
10640
.unwrap ( )
10641
.with_home ( root.join ( " home" ) ) ;
10642
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
10643
lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
10644
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
10645
let _app_origin = Repo:: init( & root.join ( " app-origin" ) ) .unwrap ( ) ;
10646
let app_dir = root.join ( " app" ) ;
10647
let mut app = Repo:: clone( & root.join ( " app-origin" ) .to_string_lossy ( ) , & app_dir)
10648
.unwrap ( )
10649
.with_home ( root.join ( " home" ) ) ;
10650
write ( & app_dir, " main.txt" , b " app\n " ) ;
10651
app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
10652
let a1 = app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
10653
10654
10655
let mut nested = Repo:: open( & app_dir.join ( " libs/lib" ) )
10656
.unwrap ( )
10657
.with_home ( root.join ( " home" ) ) ;
10658
let heads = nested.bookmark_heads ( ) .unwrap ( ) ;
10659
nested.bookmark_create ( " feature" , Some ( & heads) ) .unwrap ( ) ;
10660
nested.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
10661
let snap = app.snapshot ( " status" ) .unwrap ( ) ;
10662
assert! (
10663
! snap.changed,
10664
" only the bookmark differs: {:?}" ,
10665
snap.statuses
10666
) ;
10667
app.bookmark_create ( " other" , Some ( & [ a1] ) ) .unwrap ( ) ;
10668
app.bookmark_switch ( " other" , false ) .unwrap ( ) ;
10669
10670
10671
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) )
10672
.unwrap ( )
10673
.with_home ( root.join ( " home" ) ) ;
10674
write ( & app_dir.join ( " libs/lib" ) , " lib.txt" , b " v2\n " ) ;
10675
nested.commit ( " lib v2" , & [ ] ) .unwrap ( ) ;
10676
let snap = app.snapshot ( " status" ) .unwrap ( ) ;
10677
assert_eq! (
10678
snap.statuses
10679
.iter ( )
10680
.map ( | s | ( s.path.as_str ( ) , s.status) )
10681
.collect:: < Vec< _ > > ( ) ,
10682
vec! [ ( " libs/lib" , ChangeStatus:: Modified) ]
10683
) ;
10684
}
10685
767ac6cd Andy McCall 18 days ago
10686
10687
10688
10689
10690
10691
# [ test ]
10692
fn one_handle_keeps_one_head_until_it_refreshes ( ) {
10693
hermetic ( ) ;
10694
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10695
let root = dir.path ( ) ;
10696
let writer = Repo:: init( root) .unwrap ( ) ;
10697
write ( root, " a.txt" , b " one\n " ) ;
10698
let h1 = writer.commit ( " one" , & [ ] ) .unwrap ( ) ;
10699
let reader = Repo:: open( root) .unwrap ( ) ;
10700
let before = reader.view ( ) .unwrap ( ) ;
10701
assert_eq! ( reader.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
10702
write ( root, " a.txt" , b " two\n " ) ;
10703
let h2 = writer.commit ( " two" , & [ ] ) .unwrap ( ) ;
10704
assert_eq! (
10705
writer.bookmark_heads ( ) .unwrap ( ) ,
10706
vec! [ h2] ,
10707
" its own operation"
10708
) ;
10709
assert_eq! ( reader.view ( ) .unwrap ( ) , before, " the head it read first" ) ;
10710
assert_eq! ( reader.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
10711
reader.refresh ( ) .unwrap ( ) ;
10712
assert_eq! ( reader.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
10713
10714
10715
let stale = Repo:: open( root) .unwrap ( ) ;
10716
assert_eq! ( stale.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
10717
write ( root, " a.txt" , b " three\n " ) ;
10718
let h3 = writer.commit ( " three" , & [ ] ) .unwrap ( ) ;
10719
stale.bookmark_create ( " feature" , Some ( & [ h1] ) ) .unwrap ( ) ;
10720
let view = stale.view ( ) .unwrap ( ) ;
10721
assert! ( view.bookmarks.contains_key ( " feature" ) ) ;
10722
assert_eq! (
10723
stale.bookmark_heads ( ) .unwrap ( ) ,
10724
vec! [ h3] ,
10725
" merged, not lost"
10726
) ;
10727
let fresh = Repo:: open( root) .unwrap ( ) ;
10728
assert_eq! ( fresh.view ( ) .unwrap ( ) , view) ;
10729
}
10730
10731
10732
# [ test ]
10733
fn over_a_store_one_handle_keeps_one_head ( ) {
10734
hermetic ( ) ;
10735
let backend = std:: sync:: Arc:: new( crate :: strata:: MemoryStore:: new( ) ) ;
10736
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = Some ( backend.clone ( ) ) ) ;
10737
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10738
let a = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
10739
let ( h0, _ ) = a.head_op ( ) .unwrap ( ) ;
10740
let b = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
10741
assert_eq! ( b.head_op ( ) .unwrap ( ) .0 , h0) ;
10742
let state = a.store.put_state ( & StateObject:: new( vec! [ ] ) ) .unwrap ( ) ;
10743
let mut view = a.view ( ) .unwrap ( ) ;
10744
view.bookmarks.insert ( " x" .into ( ) , state) ;
10745
let r = a.record_op ( & [ h0] , " bookmark x" , view) .unwrap ( ) ;
10746
assert_eq! ( a.head_op ( ) .unwrap ( ) .0 , r) ;
10747
assert_eq! ( b.head_op ( ) .unwrap ( ) .0 , h0, " b keeps its head" ) ;
10748
b.refresh ( ) .unwrap ( ) ;
10749
assert_eq! ( b.head_op ( ) .unwrap ( ) .0 , r) ;
10750
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = None ) ;
10751
}
10752
10753
10754
10755
10756
# [ test ]
10757
fn gc_retires_and_a_later_gc_deletes_after_the_hour ( ) {
10758
hermetic ( ) ;
10759
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10760
let root = dir.path ( ) ;
10761
let repo = Repo:: init( root) .unwrap ( ) ;
10762
let mut heads = Vec :: new( ) ;
10763
for i in 0 .. 4 {
10764
write ( root, & format! ( " f{i} .txt" ) , format! ( " {i} \n " ) .as_bytes ( ) ) ;
10765
heads.push ( repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ) ;
10766
}
10767
let reader = Repo:: open( root) .unwrap ( ) ;
10768
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 5 ) ;
10769
let r = repo.gc ( true ) .unwrap ( ) ;
10770
assert_eq! ( r.retired, crate :: store:: RetiredReport:: default( ) ) ;
10771
assert_eq! ( repo.store ( ) .loose_count ( ) .unwrap ( ) , 0 ) ;
10772
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 5 , " read on from what it had" ) ;
10773
10774
let r = repo.gc ( true ) .unwrap ( ) ;
10775
assert_eq! ( ( r.retired.packs, r.retired.loose) , ( 0 , 0 ) ) ;
10776
assert! ( r.retired.kept > 0 , " {r:?}" ) ;
10777
write ( root, " f9.txt" , b " 9\n " ) ;
10778
heads.push ( repo.commit ( " c9" , & [ ] ) .unwrap ( ) ) ;
10779
crate :: store:: reader_tests:: age_retired(
10780
repo.store ( ) ,
10781
crate :: store:: RETIRED_FOR + std:: time:: Duration:: from_secs( 60 ) ,
10782
) ;
10783
let r = repo.gc ( true ) .unwrap ( ) ;
10784
assert! ( r.retired.packs > = 1 & & r.retired.loose > 0 , " {r:?}" ) ;
10785
assert_eq! ( r.retired.kept, 0 , " {r:?}" ) ;
10786
let r = repo.gc ( true ) .unwrap ( ) ;
10787
assert_eq! ( ( r.retired.packs, r.retired.loose) , ( 0 , 0 ) ) ;
10788
assert! ( r.retired.kept > 0 , " what the last gc retired is young" ) ;
10789
10790
10791
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 5 ) ;
10792
for h in & heads[ .. 4 ] {
10793
reader.store ( ) .get_change ( h) .unwrap ( ) ;
10794
}
10795
reader.refresh ( ) .unwrap ( ) ;
10796
assert_eq! ( reader.log ( ) .unwrap ( ) .len ( ) , 6 ) ;
10797
let fresh = Repo:: open( root) .unwrap ( ) ;
10798
assert_eq! (
10799
fresh.store ( ) .pack_count ( ) ,
10800
1 ,
10801
" retired packs are not loaded"
10802
) ;
10803
}
10804
10805
10806
10807
# [ test ]
10808
fn a_repository_opens_when_a_listed_index_is_gone ( ) {
10809
hermetic ( ) ;
10810
let backend = crate :: store:: reader_tests:: racing( true ) ;
10811
let dyn_backend: std:: sync:: Arc< crate:: store:: Backend> = backend.clone ( ) ;
10812
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = Some ( dyn_backend) ) ;
10813
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10814
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
10815
write ( dir.path ( ) , " a.txt" , b " a\n " ) ;
10816
let h = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
10817
repo.gc ( true ) .unwrap ( ) ;
10818
let again = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
10819
assert_eq! ( again.store ( ) .pack_count ( ) , 1 ) ;
10820
assert_eq! ( again.bookmark_heads ( ) .unwrap ( ) , vec! [ h] ) ;
10821
10822
10823
use crate :: strata:: Store as _ ;
10824
let idx = backend
10825
.list ( " pack-" )
10826
.unwrap ( )
10827
.into_iter ( )
10828
.map ( | e | e.key )
10829
.find ( | k | k.ends_with ( " .idx" ) & & k ! = " pack-0000.idx" )
10830
.unwrap ( ) ;
10831
let stem = idx.trim_end_matches ( " .idx" ) ;
10832
for ext in [ " pack" , " idx" ] {
10833
let bytes = backend.get ( & format! ( " {stem} .{ext} " ) ) .unwrap ( ) ;
10834
backend
10835
.put_new ( & format! ( " pack-copy.{ext} " ) , & bytes)
10836
.unwrap ( ) ;
10837
}
10838
* backend.doomed.lock ( ) .unwrap ( ) = Some ( " pack-copy.idx" .into ( ) ) ;
10839
let again = Repo:: open( dir.path ( ) ) .unwrap ( ) ;
10840
assert_eq! ( again.store ( ) .pack_count ( ) , 1 ) ;
10841
assert_eq! ( again.bookmark_heads ( ) .unwrap ( ) , vec! [ h] ) ;
10842
assert! ( ! backend.has ( " pack-copy.idx" ) .unwrap ( ) ) ;
10843
crate :: store:: TEST_BACKEND .with ( | t | * t.borrow_mut ( ) = None ) ;
10844
}
2452b440 Andy McCall 10 days ago ↶
10845
10846
10847
10848
10849
10850
# [ test ]
10851
fn author_for_follows_the_key_the_hub_named ( ) {
10852
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10853
let home = dir.path ( ) .join ( " home" ) ;
10854
fs:: create_dir_all( home.join ( " keys" ) ) .unwrap ( ) ;
10855
let user = home.join ( " config.toml" ) ;
10856
let repo_cfg = dir.path ( ) .join ( " config" ) ;
10857
let ( old, new, pin) = (
10858
KeyId:: from_hex( & " a" .repeat ( 64 ) ) .unwrap ( ) ,
10859
KeyId:: from_hex( & " b" .repeat ( 64 ) ) .unwrap ( ) ,
10860
KeyId:: from_hex( & " c" .repeat ( 64 ) ) .unwrap ( ) ,
10861
) ;
10862
fs:: write( & user, format! ( " author.key = \" {} \" \n " , old.to_hex ( ) ) ) .unwrap ( ) ;
10863
fs:: write(
10864
home.join ( " keys" ) .join ( super :: config:: AUTHOR_FOR_FILE ) ,
10865
format! ( " {} \n " , new.to_hex ( ) ) ,
10866
)
10867
.unwrap ( ) ;
10868
fs:: write( & repo_cfg, " " ) .unwrap ( ) ;
10869
assert_eq! (
10870
Config:: load( Some ( & user) , & repo_cfg) .unwrap ( ) .author_key,
10871
Some ( old) ,
10872
" no author.for: the note is ignored"
10873
) ;
10874
fs:: write(
10875
& user,
10876
format! (
10877
" author.key = \" {} \" \n author.for = \" andymccall\" \n " ,
10878
old.to_hex ( )
10879
) ,
10880
)
10881
.unwrap ( ) ;
10882
assert_eq! (
10883
Config:: load( Some ( & user) , & repo_cfg) .unwrap ( ) .author_key,
10884
Some ( new)
10885
) ;
10886
fs:: write( & repo_cfg, format! ( " author.key = \" {} \" \n " , pin.to_hex ( ) ) ) .unwrap ( ) ;
10887
assert_eq! (
10888
Config:: load( Some ( & user) , & repo_cfg) .unwrap ( ) .author_key,
10889
Some ( pin) ,
10890
" a repository's pin wins"
10891
) ;
10892
}
6f4226bf Andy McCall 5 days ago ↶
10893
10894
# [ test ]
10895
fn git_export_graph_is_deterministic_and_merges_heads ( ) {
10896
hermetic ( ) ;
10897
if std:: process:: Command:: new( " git" )
10898
.arg ( " --version" )
10899
.output ( )
10900
.is_err ( )
10901
{
10902
eprintln! ( " git not installed; skipping" ) ;
10903
return ;
10904
}
10905
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
10906
let root = dir.path ( ) ;
10907
let mut repo = Repo:: init( & root.join ( " r" ) )
10908
.unwrap ( )
10909
.with_home ( root.join ( " home" ) ) ;
10910
repo.create_identity ( " Ada Lovelace" , " [email protected] " , IdentityScope:: User)
10911
.unwrap ( ) ;
10912
let r = root.join ( " r" ) ;
10913
write ( & r, " README.md" , b " hello\n " ) ;
10914
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
10915
write ( & r, " README.md" , b " hello\n world\n " ) ;
10916
write ( & r, " src/main.rs" , b " fn main() {}\n " ) ;
10917
let h2 = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
10918
repo.tag_create ( " v1" , & [ h2] , " the first release" ) .unwrap ( ) ;
10919
10920
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
10921
write ( & r, " other.txt" , b " o\n " ) ;
10922
let h3 = repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
10923
repo.bookmark_create ( " gone" , Some ( & [ h1] ) ) .unwrap ( ) ;
10924
10925
let git = | git_dir : & Path, args : & [& str ]| -> String {
10926
let out = std:: process:: Command:: new( " git" )
10927
.arg ( " -C" )
10928
.arg ( git_dir)
10929
.args ( args)
10930
.output ( )
10931
.unwrap ( ) ;
10932
assert! (
10933
out.status.success ( ) ,
10934
" git {args:?}: {}" ,
10935
String :: from_utf8_lossy( & out.stderr)
10936
) ;
10937
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
10938
} ;
10939
let a = root.join ( " a.git" ) ;
10940
let rep = repo.git_export_graph ( & a) .unwrap ( ) ;
10941
assert_eq! ( ( rep.commits, rep.tags) , ( 3 , 1 ) , " {rep:?}" ) ;
10942
assert_eq! ( rep.branches, 2 , " main and gone: {rep:?}" ) ;
10943
10944
10945
10946
let by_hash = | hash : ChangeHash| {
10947
git (
10948
& a,
10949
& [
10950
" log" ,
10951
" --all" ,
10952
" --format=%H" ,
10953
& format! ( " --grep=Oxy-Hash: {} " , hash.to_hex ( ) ) ,
10954
] ,
10955
)
10956
} ;
10957
let ( c1, c2, c3) = ( by_hash ( h1) , by_hash ( h2) , by_hash ( h3) ) ;
10958
for ( h, c) in [ ( h1, & c1) , ( h2, & c2) , ( h3, & c3) ] {
10959
let mut want: Vec< String> = repo
10960
.store ( )
10961
.get_change_meta ( & h)
10962
.unwrap ( )
10963
.deps
10964
.iter ( )
10965
.map ( | d | by_hash ( * d) )
10966
.collect ( ) ;
10967
want.sort ( ) ;
10968
let mut have: Vec< String> = git ( & a, & [ " rev-list" , " --parents" , " -1" , c] )
10969
.split ( ' ' )
10970
.skip ( 1 )
10971
.map ( String :: from)
10972
.collect ( ) ;
10973
have.sort ( ) ;
10974
assert_eq! ( have, want, " {h:?}'s commit's parents" ) ;
10975
}
10976
assert! (
10977
! repo.store ( ) .get_change_meta ( & h2) .unwrap ( ) .deps.is_empty ( ) ,
10978
" second edits first's README"
10979
) ;
10980
assert_eq! (
10981
git ( & a, & [ " show" , & format! ( " {c2} :src/main.rs" ) ] ) ,
10982
" fn main() {}"
10983
) ;
10984
assert! (
10985
git ( & a, & [ " ls-tree" , " --name-only" , & c3] )
10986
.lines ( )
10987
.all ( | l | l ! = " src" ) ,
10988
" a change's tree is its own closure, not the bookmark's"
10989
) ;
10990
assert_eq! (
10991
git ( & a, & [ " log" , " -1" , " --format=%an <%ae>" , & c1] ) ,
10992
" Ada Lovelace <[email protected] >"
10993
) ;
10994
10995
10996
let main = git ( & a, & [ " rev-parse" , " main" ] ) ;
10997
let mut parents: Vec< String> = git ( & a, & [ " rev-list" , " --parents" , " -1" , " main" ] )
10998
.split ( ' ' )
10999
.skip ( 1 )
11000
.map ( String :: from)
11001
.collect ( ) ;
11002
parents.sort ( ) ;
11003
let mut want = vec! [ c2.clone ( ) , c3.clone ( ) ] ;
11004
want.sort ( ) ;
11005
assert_eq! ( parents, want, " the merge's parents are the heads" ) ;
11006
assert_eq! (
11007
git ( & a, & [ " ls-tree" , " --name-only" , " main" ] ) ,
11008
" README.md\n other.txt\n src"
11009
) ;
11010
assert! ( git ( & a, & [ " log" , " -1" , " --format=%B" , " main" ] ) .contains ( " Merge of 2 heads" ) ) ;
11011
assert_eq! (
11012
git ( & a, & [ " rev-parse" , " v1^{commit}" ] ) ,
11013
c2,
11014
" the tag is its head's commit"
11015
) ;
11016
assert_eq! ( git ( & a, & [ " symbolic-ref" , " HEAD" ] ) , " refs/heads/main" ) ;
11017
11018
11019
repo.bookmark_delete ( " gone" ) .unwrap ( ) ;
11020
let rep = repo.git_export_graph ( & a) .unwrap ( ) ;
11021
assert_eq! ( rep.commits, 0 , " {rep:?}" ) ;
11022
assert! (
11023
git ( & a, & [ " for-each-ref" , " --format=%(refname)" , " refs/heads" ] )
11024
.lines ( )
11025
.all ( | r | r ! = " refs/heads/gone" )
11026
) ;
11027
assert_eq! (
11028
git ( & a, & [ " rev-parse" , " main" ] ) ,
11029
main,
11030
" the merge is made once"
11031
) ;
11032
11033
11034
let other = Repo:: clone( r.to_str ( ) .unwrap ( ) , & root.join ( " clone" ) )
11035
.unwrap ( )
11036
.with_home ( root.join ( " home" ) ) ;
11037
let b = root.join ( " b.git" ) ;
11038
other.git_export_graph ( & b) .unwrap ( ) ;
11039
assert_eq! (
11040
git ( & b, & [ " rev-parse" , " main" ] ) ,
11041
main,
11042
" deterministic across repositories"
11043
) ;
11044
assert_eq! ( git ( & b, & [ " rev-parse" , " v1^{commit}" ] ) , c2) ;
11045
}
2f0b4b06 Andy McCall 5 days ago ↶
11046
11047
# [ test ]
11048
fn a_fetch_of_many_bookmarks_takes_shared_changes_once ( ) {
11049
11050
11051
11052
11053
hermetic ( ) ;
11054
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
11055
let root = dir.path ( ) ;
11056
drop ( Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ) ;
11057
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
11058
let mut a = Repo:: clone( & url, & root.join ( " a" ) )
11059
.unwrap ( )
11060
.with_home ( root.join ( " home-a" ) ) ;
11061
let w = root.join ( " a" ) ;
11062
write ( & w, " f.txt" , b " one\n " ) ;
11063
a.commit ( " one" , & [ ] ) .unwrap ( ) ;
11064
write ( & w, " f.txt" , b " one\n two\n " ) ;
11065
a.commit ( " two" , & [ ] ) .unwrap ( ) ;
11066
a.push ( " origin" , false ) .unwrap ( ) ;
11067
11068
for i in 0 .. 5 {
11069
a.bookmark_create ( & format! ( " feat{i} " ) , None ) .unwrap ( ) ;
11070
a.bookmark_switch ( & format! ( " feat{i} " ) , false ) .unwrap ( ) ;
11071
write ( & w, & format! ( " feat{i} .txt" ) , format! ( " {i} \n " ) .as_bytes ( ) ) ;
11072
a.commit ( & format! ( " feat {i} " ) , & [ ] ) .unwrap ( ) ;
11073
a.push ( " origin" , false ) .unwrap ( ) ;
11074
a.bookmark_switch ( " main" , false ) .unwrap ( ) ;
11075
}
11076
11077
let b = Repo:: clone( & url, & root.join ( " b" ) )
11078
.unwrap ( )
11079
.with_home ( root.join ( " home-b" ) ) ;
11080
let report = b.fetch ( " origin" ) .unwrap ( ) ;
11081
assert_eq! ( report.refs.len ( ) , 6 , " every bookmark: {:?}" , report.refs) ;
11082
assert! (
11083
report.refs.iter ( ) .all ( | ( _, _, n ) | * n = = 0 ) ,
11084
" the clone brought them all: {:?}" ,
11085
report.refs
11086
) ;
11087
11088
11089
a.bookmark_switch ( " feat3" , false ) .unwrap ( ) ;
11090
write ( & w, " feat3.txt" , b " 3\n more\n " ) ;
11091
a.commit ( " feat 3 again" , & [ ] ) .unwrap ( ) ;
11092
a.push ( " origin" , false ) .unwrap ( ) ;
11093
let fetches = | | crate :: protocol:: LOCAL_FETCHES .with ( | n | n.get ( ) ) ;
11094
let at = fetches ( ) ;
11095
let report = b.fetch ( " origin" ) .unwrap ( ) ;
11096
assert_eq! ( fetches ( ) - at, 1 , " one bundle for all six bookmarks" ) ;
11097
let counts: Vec< ( String, usize ) > = report
11098
.refs
11099
.iter ( )
11100
.map ( | ( n , _, c ) | ( n.clone ( ) , * c) )
11101
.collect ( ) ;
11102
assert! ( counts.contains ( & ( " feat3" .to_string ( ) , 1 ) ) , " {counts:?}" ) ;
11103
assert_eq! (
11104
counts.iter ( ) .filter ( | ( _, c ) | * c > 0 ) .count ( ) ,
11105
1 ,
11106
" only feat3 had anything new: {counts:?}"
11107
) ;
11108
11109
11110
let at = fetches ( ) ;
11111
let report = b.fetch ( " origin" ) .unwrap ( ) ;
11112
assert_eq! ( fetches ( ) - at, 0 , " nothing to fetch, no round trip" ) ;
11113
assert_eq! ( report.refs.len ( ) , 6 ) ;
11114
assert! (
11115
report.refs.iter ( ) .all ( | ( _, _, n ) | * n = = 0 ) ,
11116
" {:?}" ,
11117
report.refs
11118
) ;
11119
}
aded4110 Andy McCall 2 days ago ↶
11120
11121
# [ test ]
11122
fn a_stale_pinned_key_cannot_fork_the_identity ( ) {
11123
11124
11125
11126
hermetic ( ) ;
11127
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
11128
let root = dir.path ( ) ;
11129
let home = root.join ( " home" ) ;
11130
let mut a = Repo:: init( & root.join ( " a" ) ) .unwrap ( ) .with_home ( & home) ;
11131
let ( first, _ ) = a
11132
.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User)
11133
.unwrap ( ) ;
11134
let r = a.rotate_identity ( ) .unwrap ( ) ;
11135
assert_eq! ( ( r.old, a.config.author_key) , ( first, Some ( r.new) ) ) ;
11136
assert_eq! ( a.successors ( & first) .unwrap ( ) , vec! [ r.new] ) ;
11137
assert_eq! ( a.newest ( & first) .unwrap ( ) , r.new) ;
11138
11139
11140
let mut b = Repo:: init( & root.join ( " b" ) ) .unwrap ( ) .with_home ( & home) ;
11141
b.config.set ( " author.key" , & first.to_hex ( ) ) .unwrap ( ) ;
11142
b.save_config ( ) .unwrap ( ) ;
11143
let mut b = Repo:: open( & root.join ( " b" ) ) .unwrap ( ) .with_home ( & home) ;
11144
assert_eq! ( b.config.author_key, Some ( first) ) ;
11145
for e in [
11146
b.rotate_identity ( ) .unwrap_err ( ) .to_string ( ) ,
11147
b.renew_identity ( None ) .unwrap_err ( ) .to_string ( ) ,
11148
] {
11149
assert! ( e.contains ( " fork" ) & & e.contains ( & r.new.short ( 12 ) ) , " {e}" ) ;
11150
assert! (
11151
e.contains ( & format! ( " oxy config author.key {} " , r.new.to_hex ( ) ) ) ,
11152
" {e}"
11153
) ;
11154
}
11155
assert_eq! (
11156
b.successors ( & first) .unwrap ( ) ,
11157
vec! [ r.new] ,
11158
" nothing was made"
11159
) ;
11160
11161
11162
b.config.set ( " author.key" , & r.new.to_hex ( ) ) .unwrap ( ) ;
11163
b.save_config ( ) .unwrap ( ) ;
11164
let mut b = Repo:: open( & root.join ( " b" ) ) .unwrap ( ) .with_home ( & home) ;
11165
let r2 = b.rotate_identity ( ) .unwrap ( ) ;
11166
assert_eq! ( r2.old, r.new) ;
11167
assert_eq! ( b.newest ( & first) .unwrap ( ) , r2.new) ;
11168
}