98286406 Andy McCall 1 month ago ↶
1
2
3
use super :: * ;
957fee30 Andy McCall 1 month ago ↶
4
use crate :: repo:: { BisectStep, Selection} ;
83e0677b Andy McCall 1 month ago ↶
5
use crate :: protocol:: SignedPolicy;
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) > {
26
s.statuses.iter ( ) .map ( | p | ( p.path.clone ( ) , p.status) ) .collect ( )
27
}
28
29
# [ test ]
30
fn init_snapshot_commit_log_undo ( ) {
31
hermetic ( ) ;
32
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
33
let root = dir.path ( ) ;
34
let repo = Repo:: init( root) .unwrap ( ) ;
35
assert! ( Repo:: init( root) .is_err ( ) ) ;
36
let ops = repo.op_log ( ) .unwrap ( ) ;
37
assert_eq! ( ops.len ( ) , 1 ) ;
38
assert_eq! ( ops[ 0 ] .1. command, " oxy init" ) ;
39
40
41
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
42
assert! ( ! s.changed) ;
43
assert_eq! ( repo.op_log ( ) .unwrap ( ) .len ( ) , 1 ) ;
44
45
write ( root, " src/main.rs" , b " fn main() {}\n " ) ;
46
write ( root, " README.md" , b " # Hello\n " ) ;
47
write ( root, " .oxyignore" , b " /target\n " ) ;
48
write ( root, " target/junk.o" , b " \0 \0 \0 " ) ;
49
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
50
assert! ( s.changed) ;
51
assert_eq! (
52
statuses ( & s) ,
53
vec! [
54
( " .oxyignore" .into ( ) , ChangeStatus:: Added) ,
55
( " README.md" .into ( ) , ChangeStatus:: Added) ,
56
( " src/main.rs" .into ( ) , ChangeStatus:: Added) ,
57
]
58
) ;
59
assert_eq! ( repo.op_log ( ) .unwrap ( ) .len ( ) , 2 ) ;
60
61
let s2 = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
62
assert! ( ! s2.changed) ;
63
assert_eq! ( s2.wc_hash, s.wc_hash) ;
64
assert_eq! ( repo.op_log ( ) .unwrap ( ) .len ( ) , 2 ) ;
65
66
let h1 = repo.commit ( " Initial import" , & [ ] ) .unwrap ( ) ;
67
let c1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) ;
68
assert_eq! ( c1.kind, ChangeKind:: Normal) ;
69
assert! ( c1.deps.is_empty ( ) , " first change has nothing to depend on" ) ;
70
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
71
let ( wc_hash, wc) = repo.working_copy ( ) .unwrap ( ) ;
72
assert_eq! ( wc.deps, vec! [ h1] ) ;
73
assert! ( wc.edits.is_empty ( ) ) ;
74
assert! ( repo.commit ( " nothing" , & [ ] ) .is_err ( ) ) ;
75
76
77
write ( root, " src/main.rs" , b " fn main() {\n println!(\" hi\" );\n }\n " ) ;
78
fs:: remove_file( root.join ( " README.md" ) ) .unwrap ( ) ;
79
write ( root, " logo.png" , b " \x89 PNG\0 \0 \x01 " ) ;
80
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
81
assert_eq! (
82
statuses ( & s) ,
83
vec! [
84
( " README.md" .into ( ) , ChangeStatus:: Removed) ,
85
( " logo.png" .into ( ) , ChangeStatus:: Added) ,
86
( " src/main.rs" .into ( ) , ChangeStatus:: Modified) ,
87
]
88
) ;
89
assert_ne! ( s.wc_hash, wc_hash) ;
90
91
assert_eq! ( s.wc.change_id, wc.change_id) ;
92
assert! ( s.wc.deps.contains ( & h1) ) ;
93
94
let h2 = repo.commit ( " Second" , & [ ] ) .unwrap ( ) ;
95
let c2 = repo.store ( ) .get_change ( & h2) .unwrap ( ) ;
96
assert_eq! ( c2.deps, vec! [ h1] , " everything it touches was introduced by h1" ) ;
97
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
98
99
100
let st = repo.load_state ( & [ h2] ) .unwrap ( ) ;
101
let snap = st.materialize ( ) ;
102
assert! ( ! snap.is_conflicted ( ) ) ;
103
assert_eq! ( snap.files.keys ( ) .cloned ( ) .collect:: < Vec< _ > > ( ) , [ " .oxyignore" , " logo.png" , " src" , " src/main.rs" ] ) ;
104
assert! ( matches! ( & snap.files[ " src/main.rs" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " fn main() {\n println!(\" hi\" );\n }\n " ) ) ;
105
assert! ( snap.files[ " logo.png" ] .mode.opaque) ;
106
if let FileContent:: Opaque { blobs } = & snap.files[ " logo.png" ] .content {
107
assert_eq! ( repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) , b " \x89 PNG\0 \0 \x01 " ) ;
108
} else {
109
panic! ( " logo should be opaque" ) ;
110
}
111
112
113
let log = repo.log ( ) .unwrap ( ) ;
114
assert_eq! ( log.len ( ) , 3 ) ;
115
assert! ( log[ 0 ] .is_working_copy) ;
116
assert_eq! ( log[ 1 ] .hash, h2) ;
117
assert! ( log[ 1 ] .is_head) ;
118
assert_eq! ( log[ 2 ] .hash, h1) ;
119
let d = repo.describe_change ( & h2) .unwrap ( ) ;
120
assert! ( d.contains ( " D README.md" ) , " {d}" ) ;
121
assert! ( d.contains ( " + println!(\" hi\" );" ) , " {d}" ) ;
122
assert! ( d.contains ( " -fn main() {}" ) , " {d}" ) ;
123
124
125
assert_eq! ( repo.resolve_change ( & c2.change_id.short ( 6 ) ) .unwrap ( ) , h2) ;
126
assert_eq! ( repo.resolve_change ( & format! ( " #{} " , h1.short ( 8 ) ) ) .unwrap ( ) , h1) ;
127
assert! ( repo.resolve_change ( " zzzz" ) .is_err ( ) ) ;
128
129
130
repo.undo ( ) .unwrap ( ) ;
131
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
132
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
133
assert! ( ! s.changed, " files on disk still match the pre-commit working copy" ) ;
134
assert_eq! ( s.statuses.len ( ) , 3 ) ;
135
}
136
137
138
fn conflicted_repo ( ) -> ( tempfile:: TempDir, Repo, ChangeHash, ChangeHash, ChangeHash) {
139
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
140
let root = dir.path ( ) ;
141
let repo = Repo:: init( root) .unwrap ( ) ;
142
write ( root, " f.txt" , b " a\n b\n c\n " ) ;
143
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
144
write ( root, " f.txt" , b " a\n B\n c\n " ) ;
145
let h2 = repo.commit ( " upper" , & [ ] ) .unwrap ( ) ;
146
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
147
write ( root, " f.txt" , b " a\n X\n c\n " ) ;
148
let h3 = repo.commit ( " ex" , & [ ] ) .unwrap ( ) ;
149
( dir, repo, h1, h2, h3)
150
}
151
152
# [ test ]
153
fn concurrent_edits_conflict_and_can_be_resolved ( ) {
154
hermetic ( ) ;
155
let ( dir, repo, h1, h2, h3) = conflicted_repo ( ) ;
156
let root = dir.path ( ) ;
157
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
158
assert_eq! ( heads, [ h2, h3] .into_iter ( ) .collect ( ) ) ;
159
let st = repo.load_state ( & [ h2, h3] ) .unwrap ( ) ;
160
assert! ( st.materialize ( ) .is_conflicted ( ) ) ;
161
162
163
let r = repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" ) .unwrap ( ) ;
164
assert! ( r.conflicted) ;
165
let marked = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
166
assert! ( marked.starts_with ( " a\n <<<<<<< oxy conflict 1 of 1" ) , " {marked}" ) ;
167
assert! ( marked.contains ( " B\n " ) & & marked.contains ( " X\n " ) & & marked.ends_with ( " end\n c\n " ) , " {marked}" ) ;
168
assert! ( has_conflict_markers ( marked.as_bytes ( ) ) ) ;
169
170
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
171
assert! ( s.statuses.is_empty ( ) ) ;
172
assert! ( ! s.resolves) ;
173
174
175
write ( root, " f.txt" , marked.replace ( " B\n " , " BB\n " ) .as_bytes ( ) ) ;
176
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
177
assert_eq! ( statuses ( & s) , vec! [ ( " f.txt" .into ( ) , ChangeStatus:: ConflictEdited) ] ) ;
178
assert! ( repo.commit ( " no" , & [ ] ) .is_err ( ) ) ;
179
180
181
write ( root, " f.txt" , b " a\n X\n c\n d\n " ) ;
182
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
183
assert_eq! ( statuses ( & s) , vec! [ ( " f.txt" .into ( ) , ChangeStatus:: Resolved) ] ) ;
184
assert! ( s.resolves) ;
185
let h4 = repo.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
186
let c4 = repo.store ( ) .get_change ( & h4) .unwrap ( ) ;
187
assert_eq! ( c4.kind, ChangeKind:: Resolution) ;
188
let mut want = vec! [ h1, h2, h3] ;
189
want.sort ( ) ;
190
assert_eq! ( c4.deps, want, " a resolution depends on exactly the contributors and what it references" ) ;
191
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h4] ) ;
192
let snap = repo.load_state ( & [ h4] ) .unwrap ( ) .materialize ( ) ;
193
assert! ( ! snap.is_conflicted ( ) ) ;
194
assert! ( matches! ( & snap.files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n X\n c\n d\n " ) ) ;
195
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
196
197
198
let mut st = State:: new( ) ;
199
for h in [ h4, h3, h1, h2] {
200
st.add_unchecked ( repo.store ( ) .get_change ( & h) .unwrap ( ) ) ;
201
}
202
st.validate ( ) .unwrap ( ) ;
203
assert! ( matches! ( & st.materialize ( ) .files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n X\n c\n d\n " ) ) ;
204
}
205
206
# [ test ]
207
fn resolution_may_keep_both_sides_or_neither ( ) {
208
hermetic ( ) ;
209
let ( dir, repo, _h1, _h2, _h3) = conflicted_repo ( ) ;
210
let root = dir.path ( ) ;
211
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" ) .unwrap ( ) ;
212
213
write ( root, " f.txt" , b " a\n X\n B\n c\n " ) ;
214
let h = repo.commit ( " both" , & [ ] ) .unwrap ( ) ;
215
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
216
assert! ( ! snap.is_conflicted ( ) ) ;
217
assert! ( matches! ( & snap.files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n X\n B\n c\n " ) ) ;
218
219
repo.undo ( ) .unwrap ( ) ;
220
write ( root, " f.txt" , b " a\n c\n " ) ;
221
let h = repo.commit ( " neither" , & [ ] ) .unwrap ( ) ;
222
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
223
assert! ( ! snap.is_conflicted ( ) ) ;
224
assert! ( matches! ( & snap.files[ " f.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " a\n c\n " ) ) ;
225
}
226
227
# [ test ]
228
fn name_conflict_resolved_by_removing_a_candidate ( ) {
229
hermetic ( ) ;
230
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
231
let root = dir.path ( ) ;
232
let repo = Repo:: init( root) .unwrap ( ) ;
233
write ( root, " keep.txt" , b " k\n " ) ;
234
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
235
write ( root, " new.txt" , b " one\n " ) ;
236
repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
237
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
238
write ( root, " new.txt" , b " two\n " ) ;
239
repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
240
let r = repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " checkout main" ) .unwrap ( ) ;
241
assert! ( r.conflicted) ;
242
assert! ( root.join ( " new.txt.oxy-conflict-1" ) .exists ( ) & & root.join ( " new.txt.oxy-conflict-2" ) .exists ( ) ) ;
243
assert! ( ! root.join ( " new.txt" ) .exists ( ) ) ;
244
245
246
let ( keep, drop) = if fs:: read( root.join ( " new.txt.oxy-conflict-1" ) ) .unwrap ( ) = = b " two\n " { ( 1 , 2 ) } else { ( 2 , 1 ) } ;
247
fs:: remove_file( root.join ( format! ( " new.txt.oxy-conflict-{drop} " ) ) ) .unwrap ( ) ;
248
fs:: rename( root.join ( format! ( " new.txt.oxy-conflict-{keep} " ) ) , root.join ( " new.txt" ) ) .unwrap ( ) ;
249
let h = repo.commit ( " pick two" , & [ ] ) .unwrap ( ) ;
250
assert_eq! ( repo.store ( ) .get_change ( & h) .unwrap ( ) .kind, ChangeKind:: Resolution) ;
251
252
253
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
254
assert_eq! ( heads, [ h1, h] .into_iter ( ) .collect ( ) ) ;
255
let snap = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) .materialize ( ) ;
256
assert! ( ! snap.is_conflicted ( ) , " {:?}" , snap.tree.conflicts) ;
257
assert_eq! ( snap.files.keys ( ) .cloned ( ) .collect:: < Vec< _ > > ( ) , [ " keep.txt" , " new.txt" ] ) ;
258
assert! ( matches! ( & snap.files[ " new.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " two\n " ) ) ;
259
}
260
261
# [ test ]
262
fn identities_and_signed_commits ( ) {
263
hermetic ( ) ;
264
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
265
let root = dir.path ( ) ;
266
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
267
write ( root, " a.txt" , b " a\n " ) ;
268
let h0 = repo.commit ( " unsigned" , & [ ] ) .unwrap ( ) ;
269
assert_eq! ( repo.verify_change ( & h0) .unwrap ( ) , SignatureStatus:: Unsigned) ;
270
assert_eq! ( repo.store ( ) .get_change ( & h0) .unwrap ( ) .author, ObjectRef:: ZERO ) ;
271
272
let ( key, id) = repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo) .unwrap ( ) ;
273
assert_eq! ( id.hash ( ) , key) ;
274
assert_eq! ( repo.config.author_key, Some ( key) ) ;
275
276
let reopened = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
277
assert_eq! ( reopened.config.author_key, Some ( key) ) ;
278
assert_eq! ( reopened.secret_key ( ) .unwrap ( ) .unwrap ( ) .0 , key) ;
279
280
281
write ( root, " a.txt" , b " a\n b\n " ) ;
282
let h1 = repo.commit ( " authored" , & [ ] ) .unwrap ( ) ;
283
assert_eq! ( repo.store ( ) .get_change ( & h1) .unwrap ( ) .author, key) ;
284
assert_eq! ( repo.verify_change ( & h1) .unwrap ( ) , SignatureStatus:: Unsigned) ;
285
286
repo.sign_change ( & h1) .unwrap ( ) ;
287
assert! ( matches! ( repo.verify_change ( & h1) .unwrap ( ) , SignatureStatus:: Valid { key: k, .. } if k = = key) ) ;
288
289
repo.config.set ( " core.sign" , " change" ) .unwrap ( ) ;
290
repo.save_config ( ) .unwrap ( ) ;
291
write ( root, " a.txt" , b " a\n b\n c\n " ) ;
292
let h2 = repo.commit ( " signed" , & [ ] ) .unwrap ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
293
assert_eq! ( repo.verify_change ( & h2) .unwrap ( ) , SignatureStatus:: Valid { key, name: " Ada" .into ( ) , email: " [email protected] " .into ( ) , sealed: false , revoked: false } ) ;
98286406 Andy McCall 1 month ago ↶
294
295
296
let other = SecretKey:: generate( ) ;
297
let other_id = other.identity ( " Mallory" , " [email protected] " , 0 ) ;
298
let other_key = repo.store ( ) .put_identity ( & other_id) .unwrap ( ) ;
299
let forged = other.sign ( Domain:: Change, & other_key, & h0) ;
300
repo.store ( ) .put_signature ( & h0, & forged) .unwrap ( ) ;
301
assert! ( matches! ( repo.verify_change ( & h0) .unwrap ( ) , SignatureStatus:: Invalid( m) if m.contains ( " neither the author" ) ) ) ;
302
303
304
let mut sig = repo.store ( ) .signatures ( & h2) .unwrap ( ) .remove ( 0 ) ;
305
sig.ed[ 0 ] ^ = 1 ;
306
repo.store ( ) .put_signature ( & h2, & sig) .unwrap ( ) ;
307
assert! ( matches! ( repo.verify_change ( & h2) .unwrap ( ) , SignatureStatus:: Invalid( _ ) ) ) ;
308
309
310
let no_keys = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " nowhere" ) ) ;
311
write ( root, " a.txt" , b " z\n " ) ;
312
assert! ( no_keys.commit ( " cannot sign" , & [ ] ) .is_err ( ) ) ;
313
}
314
315
# [ test ]
316
fn checkout_moves_the_working_copy ( ) {
317
hermetic ( ) ;
318
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
319
let root = dir.path ( ) ;
320
let repo = Repo:: init( root) .unwrap ( ) ;
321
write ( root, " a.txt" , b " one\n " ) ;
322
write ( root, " sub/deep/b.txt" , b " b\n " ) ;
323
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
324
write ( root, " a.txt" , b " two\n " ) ;
325
fs:: remove_file( root.join ( " sub/deep/b.txt" ) ) .unwrap ( ) ;
326
write ( root, " bin.dat" , & [ 0 , 1 , 2 , 3 ] ) ;
327
let h2 = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
328
329
let r = repo.checkout ( & [ h1] , false , " oxy checkout" ) .unwrap ( ) ;
4dee277c Andy McCall 1 month ago ↶
330
assert_eq! ( r, CheckoutReport { written: 2 , removed: 1 , conflicted: false , notes: vec! [ ] } ) ;
98286406 Andy McCall 1 month ago ↶
331
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n " ) ;
332
assert_eq! ( fs:: read( root.join ( " sub/deep/b.txt" ) ) .unwrap ( ) , b " b\n " ) ;
333
assert! ( ! root.join ( " bin.dat" ) .exists ( ) ) ;
334
let ( _ , wc) = repo.working_copy ( ) .unwrap ( ) ;
335
assert_eq! ( wc.deps, vec! [ h1] ) ;
336
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed, " checkout leaves a clean working copy" ) ;
337
338
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
339
340
341
write ( root, " a.txt" , b " dirty\n " ) ;
342
assert! ( repo.checkout ( & [ h2] , false , " oxy checkout" ) .is_err ( ) ) ;
343
let r = repo.checkout ( & [ h2] , true , " oxy checkout --discard" ) .unwrap ( ) ;
344
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " two\n " ) ;
345
assert_eq! ( fs:: read( root.join ( " bin.dat" ) ) .unwrap ( ) , [ 0 , 1 , 2 , 3 ] ) ;
346
assert! ( ! root.join ( " sub" ) .exists ( ) , " empty directories are pruned" ) ;
347
assert_eq! ( r.removed, 1 ) ;
348
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
349
350
351
# [ cfg ( unix ) ]
352
{
353
fs:: remove_dir_all( root.join ( " sub" ) ) .ok ( ) ;
354
std:: os:: unix:: fs:: symlink( " /tmp" , root.join ( " sub" ) ) .unwrap ( ) ;
355
assert! ( repo.checkout ( & [ h1] , true , " oxy checkout" ) .is_err ( ) ) ;
356
}
357
}
358
359
# [ test ]
360
fn large_files_are_chunked_and_read_back ( ) {
361
hermetic ( ) ;
362
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
363
let repo = Repo:: init( dir.path ( ) ) .unwrap ( ) ;
364
let mut big = vec! [ 0 u8 ; 700 * 1024 ] ;
365
for ( i, b) in big.iter_mut ( ) .enumerate ( ) {
366
* b = ( i as u32 ) .wrapping_mul ( 2654435761 ) .to_le_bytes ( ) [ 0 ] ;
367
}
368
let r = repo.store_blob ( & big) .unwrap ( ) ;
369
let blob = Blob:: decode( & repo.store ( ) .get_typed ( & r, ObjectType:: Blob) .unwrap ( ) ) .unwrap ( ) ;
370
assert! ( blob.chunks.len ( ) > = 3 , " {}" , blob.chunks.len ( ) ) ;
371
assert_eq! ( repo.read_blob ( & r) .unwrap ( ) , big) ;
372
assert_eq! ( Repo:: blob_ref( & big) , r) ;
373
}
374
375
# [ test ]
376
fn clone_push_pull_with_seals_and_attestations ( ) {
377
hermetic ( ) ;
378
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
379
let root = dir.path ( ) ;
380
let origin_path = root.join ( " origin" ) ;
381
let origin = Repo:: init( & origin_path) .unwrap ( ) ;
382
let origin_url = origin_path.to_string_lossy ( ) .to_string ( ) ;
383
384
385
let mut a = Repo:: clone( & origin_url, & root.join ( " a" ) ) .unwrap ( ) .with_home ( root.join ( " home-a" ) ) ;
386
assert_eq! ( a.id ( ) .unwrap ( ) , origin.id ( ) .unwrap ( ) ) ;
387
let ( key_a, _ ) = a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: Repo) .unwrap ( ) ;
388
write ( & root.join ( " a" ) , " f.txt" , b " a\n " ) ;
389
let h1 = a.commit ( " first" , & [ ] ) .unwrap ( ) ;
390
let results = a.push ( " origin" , false ) .unwrap ( ) ;
391
assert! ( results[ 0 ] .ok, " {}" , results[ 0 ] .reason) ;
392
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
393
394
assert! ( matches! ( origin.authenticate_change ( & h1) .unwrap ( ) , SignatureStatus:: Valid { key, sealed: true , .. } if key = = key_a) ) ;
395
assert_eq! ( origin.verify_change ( & h1) .unwrap ( ) , SignatureStatus:: Unsigned) ;
396
let ( _ , att) = origin.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
397
assert! ( matches! ( att.attest.body, AttestBody:: Ref { serial: 1 , .. } ) ) ;
398
let again = a.push ( " origin" , false ) .unwrap ( ) ;
399
assert! ( again[ 0 ] .ok & & again[ 0 ] .reason.contains ( " up to date" ) ) ;
400
let ( _ , att) = origin.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
401
assert! ( matches! ( att.attest.body, AttestBody:: Ref { serial: 1 , .. } ) , " a no-op push does not attest again" ) ;
402
403
404
let b = Repo:: clone( & origin_url, & root.join ( " b" ) ) .unwrap ( ) ;
405
assert_eq! ( fs:: read( root.join ( " b/f.txt" ) ) .unwrap ( ) , b " a\n " ) ;
406
assert_eq! ( b.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
407
assert! ( matches! ( b.authenticate_change ( & h1) .unwrap ( ) , SignatureStatus:: Valid { sealed: true , .. } ) ) ;
408
409
410
write ( & root.join ( " b" ) , " f.txt" , b " a\n b\n " ) ;
411
let h2 = b.commit ( " second" , & [ ] ) .unwrap ( ) ;
412
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
413
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
414
415
416
write ( & root.join ( " a" ) , " g.txt" , b " g\n " ) ;
417
let h3 = a.commit ( " third" , & [ ] ) .unwrap ( ) ;
418
assert! ( a.push ( " origin" , false ) .is_err ( ) ) ;
419
let pr = a.pull ( " origin" ) .unwrap ( ) ;
420
assert! ( pr.changed & & pr.checked_out) ;
421
let mut want = vec! [ h2, h3] ;
422
want.sort ( ) ;
423
assert_eq! ( pr.heads, want) ;
424
assert_eq! ( fs:: read( root.join ( " a/f.txt" ) ) .unwrap ( ) , b " a\n b\n " ) ;
425
assert_eq! ( fs:: read( root.join ( " a/g.txt" ) ) .unwrap ( ) , b " g\n " ) ;
426
let results = a.push ( " origin" , false ) .unwrap ( ) ;
427
assert! ( results[ 0 ] .ok, " {}" , results[ 0 ] .reason) ;
428
let mut origin_heads = origin.bookmark_heads ( ) .unwrap ( ) ;
429
origin_heads.sort ( ) ;
430
assert_eq! ( origin_heads, want) ;
431
let ( _ , att) = origin.latest_ref_attest ( " main" ) .unwrap ( ) .unwrap ( ) ;
432
assert! ( matches! ( att.attest.body, AttestBody:: Ref { serial: 2 , .. } ) ) ;
433
434
435
let pr = b.pull ( " origin" ) .unwrap ( ) ;
436
assert_eq! ( pr.heads, want) ;
437
assert_eq! ( fs:: read( root.join ( " b/g.txt" ) ) .unwrap ( ) , b " g\n " ) ;
438
assert! ( ! b.snapshot ( " status" ) .unwrap ( ) .changed) ;
439
440
441
write ( & root.join ( " a" ) , " f.txt" , b " a\n A\n " ) ;
442
a.commit ( " a-edit" , & [ ] ) .unwrap ( ) ;
443
a.push ( " origin" , false ) .unwrap ( ) ;
444
write ( & root.join ( " b" ) , " f.txt" , b " a\n B\n " ) ;
445
b.commit ( " b-edit" , & [ ] ) .unwrap ( ) ;
446
let pr = b.pull ( " origin" ) .unwrap ( ) ;
447
assert! ( pr.changed & & pr.checked_out) ;
448
let text = fs:: read_to_string( root.join ( " b/f.txt" ) ) .unwrap ( ) ;
449
assert! ( text.contains ( " <<<<<<< oxy conflict" ) , " {text}" ) ;
450
write ( & root.join ( " b" ) , " f.txt" , b " a\n AB\n " ) ;
451
let hr = b.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
452
assert_eq! ( b.store ( ) .get_change ( & hr) .unwrap ( ) .kind, ChangeKind:: Resolution) ;
453
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
454
a.pull ( " origin" ) .unwrap ( ) ;
455
assert_eq! ( fs:: read( root.join ( " a/f.txt" ) ) .unwrap ( ) , b " a\n AB\n " ) ;
456
457
458
let mut want = vec! [ hr, h3] ;
459
want.sort ( ) ;
460
assert_eq! ( a.bookmark_heads ( ) .unwrap ( ) , want) ;
461
assert! ( ! a.load_state ( & want) .unwrap ( ) .materialize ( ) .is_conflicted ( ) ) ;
462
}
463
464
# [ test ]
465
fn receive_push_rejects_bad_updates ( ) {
466
hermetic ( ) ;
467
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
468
let root = dir.path ( ) ;
469
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
470
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
471
let mut a = Repo:: clone( & url, & root.join ( " a" ) ) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
472
let ( key, _ ) = a.create_identity ( " Ada" , " a@b" , IdentityScope:: Repo) .unwrap ( ) ;
473
write ( & root.join ( " a" ) , " f.txt" , b " x\n " ) ;
474
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
475
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
476
let empty = origin.store ( ) .put_state ( & StateObject:: new( vec! [ ] ) ) .unwrap ( ) ;
477
let s1 = origin.store ( ) .put_state ( & StateObject:: new( vec! [ h1] ) ) .unwrap ( ) ;
478
479
480
let ghost = origin.store ( ) .put_state ( & StateObject:: new( vec! [ ObjectRef( [ 9 ; 32 ] ) ] ) ) .unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
481
let r = origin.receive_push ( Bundle:: default( ) , vec! [ RefUpdate { name: " main" .into ( ) , old_state: s1, new_state: ghost, force: false , attestation: None } ] , None , vec! [ ] ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
482
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " missing" ) , " {}" , r[ 0 ] .reason) ;
483
484
132b884b Andy McCall 1 month ago ↶
485
let r = origin.receive_push ( Bundle:: default( ) , vec! [ RefUpdate { name: " main" .into ( ) , old_state: empty, new_state: s1, force: false , attestation: None } ] , None , vec! [ ] ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
486
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " stale" ) , " {}" , r[ 0 ] .reason) ;
487
488
132b884b Andy McCall 1 month ago ↶
489
let r = origin.receive_push ( Bundle:: default( ) , vec! [ RefUpdate { name: " main" .into ( ) , old_state: s1, new_state: empty, force: false , attestation: None } ] , None , vec! [ ] ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
490
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " fast-forward" ) , " {}" , r[ 0 ] .reason) ;
132b884b Andy McCall 1 month ago ↶
491
let r = origin.receive_push ( Bundle:: default( ) , vec! [ RefUpdate { name: " main" .into ( ) , old_state: s1, new_state: empty, force: true , attestation: None } ] , None , vec! [ ] ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
492
assert! ( r[ 0 ] .ok) ;
493
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ ] ) ;
494
495
496
let sa = a.sign_attest ( & Attest { repo: a.id ( ) .unwrap ( ) , hlc: Hlc:: now( ) , body: AttestBody:: Ref { name: " main" .into ( ) , old_state: empty, new_state: s1, serial: 1 } } ) .unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
497
let r = origin.receive_push ( Bundle:: default( ) , vec! [ RefUpdate { name: " main" .into ( ) , old_state: empty, new_state: s1, force: false , attestation: Some ( sa.clone ( ) ) } ] , None , vec! [ ] ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
498
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " serial" ) , " {}" , r[ 0 ] .reason) ;
499
500
let sa2 = a.sign_attest ( & Attest { repo: a.id ( ) .unwrap ( ) , hlc: Hlc:: now( ) , body: AttestBody:: Ref { name: " main" .into ( ) , old_state: s1, new_state: empty, serial: 5 } } ) .unwrap ( ) ;
132b884b Andy McCall 1 month ago ↶
501
let r = origin.receive_push ( Bundle:: default( ) , vec! [ RefUpdate { name: " main" .into ( ) , old_state: empty, new_state: s1, force: false , attestation: Some ( sa2) } ] , None , vec! [ ] ) .unwrap ( ) ;
98286406 Andy McCall 1 month ago ↶
502
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " describe" ) , " {}" , r[ 0 ] .reason) ;
503
let _ = key;
504
}
505
506
# [ test ]
507
fn user_config_supplies_defaults_and_clones_sign ( ) {
508
hermetic ( ) ;
509
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
510
let root = dir.path ( ) ;
511
let home = root.join ( " home" ) ;
512
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
513
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
514
515
516
let mut a = Repo:: init( & root.join ( " a" ) ) .unwrap ( ) .with_home ( & home) ;
517
let ( key, _ ) = a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User) .unwrap ( ) ;
518
assert! ( ! a.config.repo_layer ( ) .contains_key ( " author.key" ) ) ;
519
assert_eq! ( a.config.author_key, Some ( key) ) ;
520
assert! ( home.join ( " config.toml" ) .exists ( ) ) ;
521
a.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
522
assert! ( a.config.sign_changes) ;
523
524
525
a.config.set ( " core.sign" , " seal" ) .unwrap ( ) ;
526
a.save_config ( ) .unwrap ( ) ;
527
let again = Repo:: open( & root.join ( " a" ) ) .unwrap ( ) .with_home ( & home) ;
528
assert! ( ! again.config.sign_changes) ;
529
assert_eq! ( again.config.author_key, Some ( key) ) ;
530
assert_eq! ( read_config_file ( & root.join ( " a/.oxy/config" ) ) .unwrap ( ) .get ( " core.sign" ) .map ( String :: as_str) , Some ( " seal" ) ) ;
531
532
533
534
let b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) .with_home ( & home) ;
535
assert! ( b.config.sign_changes) ;
536
write ( & root.join ( " b" ) , " f.txt" , b " f\n " ) ;
537
let h = b.commit ( " signed from a clone" , & [ ] ) .unwrap ( ) ;
538
assert! ( matches! ( b.verify_change ( & h) .unwrap ( ) , SignatureStatus:: Valid { key: k, .. } if k = = key) ) ;
539
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
540
assert! ( matches! ( origin.verify_change ( & h) .unwrap ( ) , SignatureStatus:: Valid { .. } ) ) ;
541
}
542
543
# [ test ]
544
fn format1_repositories_are_migrated_on_open ( ) {
545
hermetic ( ) ;
546
use crate :: oplog:: { Format1Op, Format1View} ;
547
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
548
let root = dir.path ( ) ;
549
let repo = Repo:: init( root) .unwrap ( ) ;
550
write ( root, " a.txt" , b " a\n " ) ;
551
let h1 = repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
552
let ( wc_hash, _ ) = repo.working_copy ( ) .unwrap ( ) ;
553
554
let mut v1 = Format1View:: default( ) ;
555
v1.bookmarks.insert ( " main" .into ( ) , vec! [ ] ) ;
556
v1.workspaces.insert ( repo.workspace ( ) , wc_hash) ;
557
let init = Format1Op { parents: vec! [ ] , hlc: Hlc:: default( ) , command: " oxy init" .into ( ) , view: v1.clone ( ) } ;
558
let init_ref = repo.store ( ) .put ( ObjectType:: Op, & init.encode ( ) ) .unwrap ( ) ;
559
v1.bookmarks.insert ( " main" .into ( ) , vec! [ h1] ) ;
560
let commit = Format1Op { parents: vec! [ init_ref] , hlc: Hlc:: default( ) , command: " oxy commit" .into ( ) , view: v1 } ;
561
let commit_ref = repo.store ( ) .put ( ObjectType:: Op, & commit.encode ( ) ) .unwrap ( ) ;
562
write_atomic ( & root.join ( " .oxy/ops/head" ) , commit_ref.to_hex ( ) .as_bytes ( ) ) .unwrap ( ) ;
563
write_atomic ( & root.join ( " .oxy/format" ) , FORMAT1_LINE .as_bytes ( ) ) .unwrap ( ) ;
564
drop ( repo) ;
565
566
let repo = Repo:: open( root) .unwrap ( ) ;
567
assert_eq! ( fs:: read_to_string( root.join ( " .oxy/format" ) ) .unwrap ( ) , FORMAT_LINE ) ;
568
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h1] ) ;
569
let ops = repo.op_log ( ) .unwrap ( ) ;
570
assert_eq! ( ops.iter ( ) .map ( | ( _, o ) | o.command.as_str ( ) ) .collect:: < Vec< _ > > ( ) , [ " oxy commit" , " oxy init" ] ) ;
571
assert_eq! ( repo.heads_of ( & ops[ 1 ] .1. view, " main" ) .unwrap ( ) , vec! [ ] ) ;
572
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
573
574
assert_eq! ( Repo:: open( root) .unwrap ( ) .op_log ( ) .unwrap ( ) .len ( ) , 2 ) ;
575
}
576
577
# [ test ]
578
fn undo_skips_snapshot_operations ( ) {
579
hermetic ( ) ;
580
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
581
let root = dir.path ( ) ;
582
let repo = Repo:: init( root) .unwrap ( ) ;
583
write ( root, " a.txt" , b " a\n " ) ;
584
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
585
586
write ( root, " b.txt" , b " b\n " ) ;
587
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
588
assert! ( s.changed) ;
589
assert! ( repo.head_op ( ) .unwrap ( ) .1. is_snapshot ( ) ) ;
590
assert_eq! ( repo.last_command ( ) .unwrap ( ) .command, " oxy commit -m \" a\" " ) ;
591
592
repo.undo ( ) .unwrap ( ) ;
593
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ ] ) ;
594
let s = repo.snapshot ( " oxy status" ) .unwrap ( ) ;
595
let paths: Vec< _ > = s.statuses.iter ( ) .map ( | p | p.path.as_str ( ) ) .collect ( ) ;
596
assert_eq! ( paths, [ " a.txt" , " b.txt" ] , " both files are back in the working copy" ) ;
597
assert! ( repo.store ( ) .get_change ( & h1) .unwrap ( ) .deps.is_empty ( ) ) ;
598
599
assert_eq! ( repo.last_command ( ) .unwrap ( ) .command, " oxy init" ) ;
600
assert! ( repo.undo ( ) .is_err ( ) ) ;
601
}
602
603
# [ test ]
604
fn independent_changes_commute_and_make_multiple_heads ( ) {
605
hermetic ( ) ;
606
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
607
let root = dir.path ( ) ;
608
let repo = Repo:: init( root) .unwrap ( ) ;
609
write ( root, " a.txt" , b " a\n " ) ;
610
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
611
write ( root, " b.txt" , b " b\n " ) ;
612
let h2 = repo.commit ( " b" , & [ ] ) .unwrap ( ) ;
613
let c2 = repo.store ( ) .get_change ( & h2) .unwrap ( ) ;
614
assert! ( c2.deps.is_empty ( ) , " b.txt touches nothing from h1" ) ;
615
let mut heads = repo.bookmark_heads ( ) .unwrap ( ) ;
616
heads.sort ( ) ;
617
let mut want = vec! [ h1, h2] ;
618
want.sort ( ) ;
619
assert_eq! ( heads, want) ;
620
621
write ( root, " a.txt" , b " a\n a2\n " ) ;
622
let h3 = repo.commit ( " a2" , & [ ] ) .unwrap ( ) ;
623
assert_eq! ( repo.store ( ) .get_change ( & h3) .unwrap ( ) .deps, vec! [ h1] ) ;
624
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
625
assert_eq! ( heads, [ h2, h3] .into_iter ( ) .collect ( ) ) ;
626
627
write ( root, " c.txt" , b " c\n " ) ;
628
let h4 = repo.commit ( " c" , & [ h2] ) .unwrap ( ) ;
629
assert_eq! ( repo.store ( ) .get_change ( & h4) .unwrap ( ) .deps, vec! [ h2] ) ;
630
}
631
632
# [ test ]
633
fn mode_change_and_text_binary_switch ( ) {
634
hermetic ( ) ;
635
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
636
let root = dir.path ( ) ;
637
let repo = Repo:: init( root) .unwrap ( ) ;
638
write ( root, " run.sh" , b " #!/bin/sh\n " ) ;
639
repo.commit ( " add script" , & [ ] ) .unwrap ( ) ;
640
# [ cfg ( unix ) ]
641
{
642
use std:: os:: unix:: fs:: PermissionsExt;
643
fs:: set_permissions( root.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
644
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
645
assert_eq! ( statuses ( & s) , vec! [ ( " run.sh" .into ( ) , ChangeStatus:: ModeChanged) ] ) ;
646
let h = repo.commit ( " exec" , & [ ] ) .unwrap ( ) ;
647
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
648
assert! ( snap.files[ " run.sh" ] .mode.exec) ;
649
}
650
651
write ( root, " run.sh" , b " \0 binary" ) ;
652
let h = repo.commit ( " binary" , & [ ] ) .unwrap ( ) ;
653
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
654
assert! ( snap.files[ " run.sh" ] .mode.opaque) ;
655
write ( root, " run.sh" , b " text again\n " ) ;
656
let h = repo.commit ( " text" , & [ ] ) .unwrap ( ) ;
657
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
658
assert! ( ! snap.files[ " run.sh" ] .mode.opaque) ;
659
assert! ( matches! ( & snap.files[ " run.sh" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " text again\n " ) ) ;
660
assert! ( ! snap.is_conflicted ( ) ) ;
661
}
83e0677b Andy McCall 1 month ago ↶
662
663
# [ test ]
664
fn trust_policy_governs_pushes_and_fetches ( ) {
665
hermetic ( ) ;
666
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
667
let root = dir.path ( ) ;
668
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
669
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
670
671
672
let mut a = Repo:: clone( & url, & root.join ( " a" ) ) .unwrap ( ) .with_home ( root.join ( " home-a" ) ) ;
673
let ( key_a, _ ) = a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User) .unwrap ( ) ;
674
let mut b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) .with_home ( root.join ( " home-b" ) ) ;
675
write ( & root.join ( " b" ) , " b.txt" , b " b\n " ) ;
676
b.commit ( " unsigned before policy" , & [ ] ) .unwrap ( ) ;
677
assert! ( b.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
678
679
680
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
681
a.commit ( " a" , & [ ] ) .unwrap ( ) ;
682
let sp = a.policy_init ( ) .unwrap ( ) ;
683
assert_eq! ( sp.policy.serial, 1 ) ;
684
a.pull ( " origin" ) .unwrap ( ) ;
685
let results = a.push ( " origin" , false ) .unwrap ( ) ;
686
assert! ( results.iter ( ) .all ( | r | r.ok ) , " {results:?}" ) ;
687
assert_eq! ( origin.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 1 ) ;
688
689
690
b.pull ( " origin" ) .unwrap ( ) ;
691
assert! ( b.current_policy ( ) .unwrap ( ) .is_some ( ) , " the policy arrived with the fetch" ) ;
692
assert! ( b.push ( " origin" , false ) .is_err ( ) ) ;
693
694
let ( key_b, id_b) = b.create_identity ( " Bob" , " [email protected] " , IdentityScope:: User) .unwrap ( ) ;
695
write ( & root.join ( " b" ) , " b.txt" , b " b2\n " ) ;
696
let h_b2 = b.commit ( " b2 as Bob" , & [ ] ) .unwrap ( ) ;
697
assert_eq! ( b.store ( ) .get_change ( & h_b2) .unwrap ( ) .author, key_b) ;
698
let r = b.push ( " origin" , false ) .unwrap ( ) ;
699
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " may not update" ) , " {:?}" , r) ;
700
701
702
let sp = a.policy_grant ( id_b.clone ( ) , None ) .unwrap ( ) ;
703
assert_eq! ( sp.policy.serial, 2 ) ;
704
let r = a.push ( " origin" , false ) .unwrap ( ) ;
705
assert! ( r.iter ( ) .any ( | x | x.name = = " policy" & & x.ok ) , " {r:?}" ) ;
706
707
assert! ( b.push ( " origin" , false ) .unwrap_or_else ( | e | vec! [ RefResult { name: " main" .into ( ) , ok: false , reason: e.to_string ( ) } ] ) .iter ( ) .all ( | x | ! x.ok) ) ;
708
b.pull ( " origin" ) .unwrap ( ) ;
709
assert_eq! ( b.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 2 ) ;
710
let r = b.push ( " origin" , false ) .unwrap ( ) ;
711
assert! ( r[ 0 ] .ok, " {:?}" , r) ;
712
713
let heads = origin.bookmark_heads ( ) .unwrap ( ) ;
714
for h in & heads {
715
let c = origin.store ( ) .get_change ( h) .unwrap ( ) ;
716
if c.author = = key_b {
717
origin.change_allowed ( h, & origin.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy, " main" ) .unwrap ( ) ;
718
}
719
}
720
721
722
let mut forged = b.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.clone ( ) ;
723
forged.serial = 3 ;
724
forged.roots = vec! [ key_b] ;
725
let fh = forged.hash ( ) ;
726
let ( _ , sk_b) = b.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
727
let forged = SignedPolicy { policy: forged, sigs: vec! [ sk_b.sign ( Domain:: Policy, & key_b, & fh) ] } ;
132b884b Andy McCall 1 month ago ↶
728
let r = origin.receive_push ( Bundle:: default( ) , vec! [ ] , Some ( forged) , vec! [ ] ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
729
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " root" ) , " {:?}" , r) ;
730
let stale = a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1 ;
731
let mut old = stale.clone ( ) ;
732
old.policy.serial = 1 ;
132b884b Andy McCall 1 month ago ↶
733
let r = origin.receive_push ( Bundle:: default( ) , vec! [ ] , Some ( old) , vec! [ ] ) .unwrap ( ) ;
83e0677b Andy McCall 1 month ago ↶
734
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " serial" ) , " {:?}" , r) ;
735
736
737
a.pull ( " origin" ) .unwrap ( ) ;
738
a.policy_revoke ( & key_b) .unwrap ( ) ;
739
let r = a.push ( " origin" , false ) .unwrap ( ) ;
740
assert! ( r.iter ( ) .any ( | x | x.name = = " policy" & & x.ok ) , " {r:?}" ) ;
741
b.pull ( " origin" ) .unwrap ( ) ;
742
write ( & root.join ( " b" ) , " b.txt" , b " b3\n " ) ;
743
b.commit ( " b3" , & [ ] ) .unwrap ( ) ;
744
let r = b.push ( " origin" , false ) .unwrap ( ) ;
745
assert! ( ! r[ 0 ] .ok, " {:?}" , r) ;
746
747
748
let c = Repo:: clone( & url, & root.join ( " c" ) ) .unwrap ( ) .with_home ( root.join ( " home-c" ) ) ;
749
assert_eq! ( c.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 3 ) ;
750
assert! ( c.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.roots.contains ( & key_a) ) ;
751
assert! ( ! c.snapshot ( " status" ) .unwrap ( ) .changed) ;
752
}
d02e545d Andy McCall 1 month ago ↶
753
754
# [ test ]
755
fn gc_packs_objects_and_history_still_works ( ) {
756
hermetic ( ) ;
757
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
758
let root = dir.path ( ) ;
759
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
760
repo.create_identity ( " Ada" , " a@b" , IdentityScope:: User) .unwrap ( ) ;
761
repo.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
762
write ( root, " a.txt" , b " one\n " ) ;
763
let h1 = repo.commit ( " one" , & [ ] ) .unwrap ( ) ;
764
write ( root, " a.txt" , b " two\n " ) ;
765
write ( root, " big.bin" , & vec! [ 0 u8 ; 100_000 ] ) ;
766
let h2 = repo.commit ( " two" , & [ ] ) .unwrap ( ) ;
ba160f60 Andy McCall 1 month ago ↶
767
768
for i in 0 .. 5 {
769
write ( root, " a.txt" , format! ( " two\n {i} \n " ) .as_bytes ( ) ) ;
770
repo.snapshot ( " status" ) .unwrap ( ) ;
771
}
772
write ( root, " a.txt" , b " two\n " ) ;
773
repo.snapshot ( " status" ) .unwrap ( ) ;
d02e545d Andy McCall 1 month ago ↶
774
let before = repo.store ( ) .list ( ) .unwrap ( ) ;
775
assert! ( repo.store ( ) .loose_count ( ) .unwrap ( ) > 0 ) ;
ba160f60 Andy McCall 1 month ago ↶
776
777
778
let r = repo.gc ( true ) .unwrap ( ) ;
779
assert_eq! ( r.packed.objects, before.len ( ) ) ;
780
assert_eq! ( r.pruned.objects, 0 ) ;
d02e545d Andy McCall 1 month ago ↶
781
assert_eq! ( repo.store ( ) .loose_count ( ) .unwrap ( ) , 0 ) ;
782
assert_eq! ( repo.store ( ) .list ( ) .unwrap ( ) , before) ;
ba160f60 Andy McCall 1 month ago ↶
783
784
785
786
repo.config.set ( " core.op-retention" , " 0" ) .unwrap ( ) ;
787
repo.save_config ( ) .unwrap ( ) ;
d02e545d Andy McCall 1 month ago ↶
788
let repo = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
ba160f60 Andy McCall 1 month ago ↶
789
let r = repo.gc ( true ) .unwrap ( ) ;
790
assert! ( r.pruned.objects > = 5 , " {r:?}" ) ;
791
let after = repo.store ( ) .list ( ) .unwrap ( ) ;
792
assert! ( after.len ( ) < before.len ( ) ) ;
793
assert! ( after.contains ( & h1) & & after.contains ( & h2) ) ;
794
for ( _ , op) in repo.op_log ( ) .unwrap ( ) {
795
assert! ( ! op.command.is_empty ( ) , " operation objects are all kept" ) ;
796
}
797
d02e545d Andy McCall 1 month ago ↶
798
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2] ) ;
799
assert! ( matches! ( repo.verify_change ( & h2) .unwrap ( ) , SignatureStatus:: Valid { .. } ) ) ;
800
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 3 ) ;
801
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
802
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n " ) ;
803
repo.checkout ( & [ h2] , false , " checkout" ) .unwrap ( ) ;
804
assert_eq! ( fs:: read( root.join ( " big.bin" ) ) .unwrap ( ) .len ( ) , 100_000 ) ;
805
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
ba160f60 Andy McCall 1 month ago ↶
806
d02e545d Andy McCall 1 month ago ↶
807
write ( root, " a.txt" , b " three\n " ) ;
808
repo.commit ( " three" , & [ ] ) .unwrap ( ) ;
ba160f60 Andy McCall 1 month ago ↶
809
let r = repo.gc ( false ) .unwrap ( ) ;
810
assert_eq! ( r.pruned.objects, 0 ) ;
811
assert! ( r.pruned.deferred > 0 ) ;
812
let r = repo.gc ( true ) .unwrap ( ) ;
813
assert! ( r.pruned.objects > 0 ) ;
814
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 ) ;
d02e545d Andy McCall 1 month ago ↶
815
}
614541c3 Andy McCall 1 month ago ↶
816
817
# [ test ]
818
fn describe_amend_abandon_heads ( ) {
819
hermetic ( ) ;
820
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
821
let root = dir.path ( ) ;
822
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
823
repo.create_identity ( " Ada" , " a@b" , IdentityScope:: User) .unwrap ( ) ;
824
repo.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
825
write ( root, " a.txt" , b " one\n " ) ;
826
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
827
write ( root, " a.txt" , b " one\n two\n " ) ;
828
let h2 = repo.commit ( " secnod" , & [ ] ) .unwrap ( ) ;
829
let id2 = repo.store ( ) .get_change ( & h2) .unwrap ( ) .change_id;
830
831
832
let h2b = repo.describe ( & h2, " second" ) .unwrap ( ) ;
833
assert_ne! ( h2b, h2) ;
834
let c = repo.store ( ) .get_change ( & h2b) .unwrap ( ) ;
835
assert_eq! ( ( c.change_id, c.message.as_str ( ) , c.deps.clone ( ) ) , ( id2, " second" , vec! [ h1] ) ) ;
836
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2b] ) ;
837
assert! ( matches! ( repo.verify_change ( & h2b) .unwrap ( ) , SignatureStatus:: Valid { .. } ) ) ;
838
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
839
assert! ( s.statuses.is_empty ( ) , " {:?}" , s.statuses) ;
840
assert_eq! ( repo.resolve_change ( & id2.short ( 8 ) ) .unwrap ( ) , h2b, " the old object is no longer reachable from the log" ) ;
c07e0e7d Andy McCall 1 month ago ↶
841
842
let h1b = repo.describe ( & h1, " first, described" ) .unwrap ( ) ;
843
assert_ne! ( h1b, h1) ;
844
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
845
assert_eq! ( heads.len ( ) , 1 ) ;
846
let top = repo.store ( ) .get_change ( & heads[ 0 ] ) .unwrap ( ) ;
847
assert_eq! ( ( top.change_id, top.deps.clone ( ) ) , ( id2, vec! [ h1b] ) ) ;
848
assert! ( matches! ( repo.verify_change ( & heads[ 0 ] ) .unwrap ( ) , SignatureStatus:: Valid { .. } ) , " rewritten own change is re-signed" ) ;
849
let ( _ , wc) = repo.working_copy ( ) .unwrap ( ) ;
850
assert_eq! ( wc.deps, vec! [ heads[ 0 ] ] , " the working copy follows the rewrite" ) ;
851
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
852
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
853
assert! ( matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n " ) ) ;
614541c3 Andy McCall 1 month ago ↶
854
855
856
write ( root, " a.txt" , b " one\n two\n three\n " ) ;
857
write ( root, " b.txt" , b " b\n " ) ;
858
let h2c = repo.amend ( None ) .unwrap ( ) ;
859
let c = repo.store ( ) .get_change ( & h2c) .unwrap ( ) ;
860
assert_eq! ( c.change_id, id2) ;
861
assert_eq! ( c.message, " second" ) ;
862
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2c] ) ;
863
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 3 , " working copy, amended head, first" ) ;
864
let snap = repo.load_state ( & [ h2c] ) .unwrap ( ) .materialize ( ) ;
865
assert! ( matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n three\n " ) ) ;
866
assert! ( snap.files.contains_key ( " b.txt" ) ) ;
867
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
868
assert! ( repo.amend ( None ) .is_err ( ) , " clean working copy and no message" ) ;
869
let h2d = repo.amend ( Some ( " second, amended" ) ) .unwrap ( ) ;
870
assert_eq! ( repo.store ( ) .get_change ( & h2d) .unwrap ( ) .message, " second, amended" ) ;
871
872
873
repo.abandon ( & h2d) .unwrap ( ) ;
c07e0e7d Andy McCall 1 month ago ↶
874
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h1b] , " the described first change is the head again" ) ;
614541c3 Andy McCall 1 month ago ↶
875
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
876
let paths: Vec< _ > = s.statuses.iter ( ) .map ( | p | ( p.path.as_str ( ) , p.status) ) .collect ( ) ;
877
assert_eq! ( paths, vec! [ ( " a.txt" , ChangeStatus:: Modified) , ( " b.txt" , ChangeStatus:: Added) ] ) ;
878
assert_eq! ( fs:: read( root.join ( " a.txt" ) ) .unwrap ( ) , b " one\n two\n three\n " ) ;
879
let h3 = repo.commit ( " recommitted" , & [ ] ) .unwrap ( ) ;
880
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h3] ) ;
881
882
repo.undo ( ) .unwrap ( ) ;
883
repo.undo ( ) .unwrap ( ) ;
884
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h2d] ) ;
c07e0e7d Andy McCall 1 month ago ↶
885
6706bdcf Andy McCall 1 month ago ↶
886
887
888
c07e0e7d Andy McCall 1 month ago ↶
889
let base = repo.bookmark_heads ( ) .unwrap ( ) [ 0 ] ;
890
let base_change = repo.store ( ) .get_change ( & base) .unwrap ( ) ;
891
let h1_now = base_change.deps[ 0 ] ;
6706bdcf Andy McCall 1 month ago ↶
892
let notes = repo.abandon ( & h1_now) .unwrap ( ) ;
893
assert! ( ! notes.is_empty ( ) , " the dependent's edit of a.txt was reified" ) ;
894
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
895
let after = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
896
assert! ( ! after.files.contains_key ( " a.txt" ) & & after.files.contains_key ( " b.txt" ) , " {:?}" , after.files.keys ( ) ) ;
897
repo.undo ( ) .unwrap ( ) ;
898
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ base] ) ;
c07e0e7d Andy McCall 1 month ago ↶
899
write ( root, " c.txt" , b " c\n " ) ;
900
let hc = repo.commit ( " c" , & [ ] ) .unwrap ( ) ;
901
write ( root, " d.txt" , b " d\n " ) ;
902
let hd = repo.commit ( " d" , & [ hc] ) .unwrap ( ) ;
903
assert_eq! ( repo.store ( ) .get_change ( & hd) .unwrap ( ) .deps, vec! [ hc] ) ;
904
repo.abandon ( & hc) .unwrap ( ) ;
905
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
906
assert! ( ! heads.contains ( & hc) ) ;
907
let d_now = heads.iter ( ) .find ( | h | repo.store ( ) .get_change ( h) .unwrap ( ) .message = = " d" ) .copied ( ) .unwrap ( ) ;
908
assert! ( repo.store ( ) .get_change ( & d_now) .unwrap ( ) .deps.is_empty ( ) , " d inherited c's (empty) dependencies" ) ;
909
let s = repo.snapshot ( " status" ) .unwrap ( ) ;
910
assert_eq! ( s.statuses.iter ( ) .map ( | p | p.path.as_str ( ) ) .collect:: < Vec< _ > > ( ) , vec! [ " c.txt" ] , " c's content is now uncommitted work" ) ;
614541c3 Andy McCall 1 month ago ↶
911
}
79ab8528 Andy McCall 1 month ago ↶
912
913
# [ test ]
914
fn git_export_mirrors_history ( ) {
915
hermetic ( ) ;
916
if std:: process:: Command:: new( " git" ) .arg ( " --version" ) .output ( ) .is_err ( ) {
917
eprintln! ( " git not installed; skipping" ) ;
918
return ;
919
}
920
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
921
let root = dir.path ( ) ;
922
let mut repo = Repo:: init( & root.join ( " r" ) ) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
923
repo.create_identity ( " Ada Lovelace" , " [email protected] " , IdentityScope:: User) .unwrap ( ) ;
924
let r = root.join ( " r" ) ;
925
write ( & r, " README.md" , b " hello\n " ) ;
926
write ( & r, " src/main.rs" , b " fn main() {}\n " ) ;
927
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
928
write ( & r, " src/main.rs" , b " fn main() { run(); }\n " ) ;
929
write ( & r, " logo.png" , b " \x89 PNG\0 \0 " ) ;
930
let h2 = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
931
932
let git_dir = root.join ( " mirror.git" ) ;
933
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
934
assert_eq! ( ( rep.exported, rep.skipped, rep.heads) , ( 2 , 0 , 1 ) ) ;
935
let git = | args : & [& str ]| -> String {
936
let out = std:: process:: Command:: new( " git" ) .arg ( " -C" ) .arg ( & git_dir) .args ( args) .output ( ) .unwrap ( ) ;
937
assert! ( out.status.success ( ) , " {}" , String :: from_utf8_lossy( & out.stderr) ) ;
938
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
939
} ;
940
assert_eq! ( git ( & [ " log" , " --format=%s" , " main" ] ) , " second\n first" ) ;
941
assert_eq! ( git ( & [ " show" , " main:src/main.rs" ] ) , " fn main() { run(); }" ) ;
942
assert_eq! ( git ( & [ " show" , " main:README.md" ] ) , " hello" ) ;
943
let body = git ( & [ " log" , " -1" , " --format=%B" , " main" ] ) ;
944
assert! ( body.contains ( & format! ( " Oxy-Hash: {} " , h2.to_hex ( ) ) ) , " {body}" ) ;
945
assert! ( body.contains ( " Change-Id: " ) , " {body}" ) ;
946
assert_eq! ( git ( & [ " log" , " -1" , " --format=%an <%ae>" , " main" ] ) , " Ada Lovelace <[email protected] >" ) ;
947
assert_eq! ( git ( & [ " ls-tree" , " --name-only" , " main" ] ) , " README.md\n logo.png\n src" ) ;
948
949
950
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
951
assert_eq! ( ( rep.exported, rep.skipped) , ( 0 , 2 ) ) ;
952
953
954
955
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
956
write ( & r, " other.txt" , b " o\n " ) ;
957
let h3 = repo.commit ( " third" , & [ ] ) .unwrap ( ) ;
958
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
959
assert_eq! ( ( rep.exported, rep.heads) , ( 1 , 2 ) ) ;
960
assert_eq! ( git ( & [ " log" , " --format=%s" , " main" ] ) , " third\n second\n first" ) ;
961
let parents = git ( & [ " rev-list" , " --parents" , " -1" , " main" ] ) ;
962
assert_eq! ( parents.split ( ' ' ) .count ( ) , 2 , " one parent: {parents}" ) ;
963
assert_eq! ( git ( & [ " show" , " main:other.txt" ] ) , " o" ) ;
964
assert_eq! ( git ( & [ " show" , " main:src/main.rs" ] ) , " fn main() { run(); }" ) ;
965
assert_eq! ( git ( & [ " ls-tree" , " --name-only" , " main" ] ) , " README.md\n logo.png\n other.txt\n src" ) ;
966
let body = git ( & [ " log" , " -1" , " --format=%B" , " main" ] ) ;
967
assert! ( body.contains ( & format! ( " Oxy-Hash: {} " , h3.to_hex ( ) ) ) & & body.contains ( " Oxy-State: " ) , " {body}" ) ;
968
969
repo.describe ( & h3, " third, described" ) .unwrap ( ) ;
970
let rep = repo.git_export ( & git_dir) .unwrap ( ) ;
971
assert_eq! ( rep.exported, 1 ) ;
972
assert_eq! ( git ( & [ " log" , " -1" , " --format=%s" , " main" ] ) , " third, described" ) ;
973
assert_eq! ( git ( & [ " log" , " --format=%s" , " main" ] ) .lines ( ) .count ( ) , 4 ) ;
974
let _ = h2;
975
}
c3069537 Andy McCall 1 month ago ↶
976
977
# [ test ]
978
fn git_import_reproduces_trees ( ) {
979
hermetic ( ) ;
980
if std:: process:: Command:: new( " git" ) .arg ( " --version" ) .output ( ) .is_err ( ) {
981
eprintln! ( " git not installed; skipping" ) ;
982
return ;
983
}
984
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
985
let root = dir.path ( ) ;
986
let g = root.join ( " src.git" ) ;
987
fs:: create_dir_all( & g) .unwrap ( ) ;
988
let git = | args : & [& str ]| -> String {
989
let out = std:: process:: Command:: new( " git" )
990
.arg ( " -C" )
991
.arg ( & g)
992
.args ( [ " -c" , " user.name=Grace" , " -c" , " [email protected] " , " -c" , " commit.gpgsign=false" ] )
993
.args ( args)
994
.output ( )
995
.unwrap ( ) ;
996
assert! ( out.status.success ( ) , " git {args:?}: {}" , String :: from_utf8_lossy( & out.stderr) ) ;
997
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
998
} ;
999
git ( & [ " init" , " -q" , " -b" , " main" ] ) ;
1000
write ( & g, " README.md" , b " # Hi\n " ) ;
1001
write ( & g, " src/lib.rs" , b " pub fn a() {}\n " ) ;
1002
git ( & [ " add" , " ." ] ) ;
1003
git ( & [ " commit" , " -q" , " -m" , " initial" ] ) ;
1004
write ( & g, " src/lib.rs" , b " pub fn a() {}\n pub fn b() {}\n " ) ;
1005
write ( & g, " run.sh" , b " #!/bin/sh\n " ) ;
1006
# [ cfg ( unix ) ]
1007
{
1008
use std:: os:: unix:: fs:: PermissionsExt;
1009
fs:: set_permissions( g.join ( " run.sh" ) , fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
1010
}
1011
write ( & g, " logo.png" , b " \x89 PNG\0 \0 \x01 " ) ;
1012
git ( & [ " add" , " ." ] ) ;
1013
git ( & [ " commit" , " -q" , " -m" , " second\n \n Change-Id: 0123456789abcdef0123456789abcdef" ] ) ;
1014
1015
git ( & [ " checkout" , " -q" , " -b" , " topic" , " HEAD~1" ] ) ;
1016
write ( & g, " README.md" , b " # Hi\n \n More.\n " ) ;
1017
git ( & [ " commit" , " -q" , " -am" , " readme" ] ) ;
1018
git ( & [ " checkout" , " -q" , " main" ] ) ;
1019
git ( & [ " merge" , " -q" , " --no-ff" , " -m" , " merge topic" , " topic" ] ) ;
1020
1021
1022
let initial = git ( & [ " rev-list" , " --max-parents=0" , " HEAD" ] ) ;
1023
git ( & [ " checkout" , " -q" , " -b" , " clash" , & initial] ) ;
1024
write ( & g, " src/lib.rs" , b " pub fn a() {}\n pub fn c() {}\n " ) ;
1025
git ( & [ " commit" , " -q" , " -am" , " clash" ] ) ;
1026
git ( & [ " checkout" , " -q" , " main" ] ) ;
1027
let _ = std:: process:: Command:: new( " git" ) .arg ( " -C" ) .arg ( & g) .args ( [ " merge" , " -q" , " clash" ] ) .output ( ) ;
1028
write ( & g, " src/lib.rs" , b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n " ) ;
1029
git ( & [ " add" , " ." ] ) ;
1030
git ( & [ " commit" , " -q" , " -m" , " resolve clash" ] ) ;
1031
git ( & [ " rm" , " -q" , " logo.png" ] ) ;
1032
git ( & [ " commit" , " -q" , " -m" , " drop logo" ] ) ;
6e723b81 Andy McCall 1 month ago ↶
1033
1034
git ( & [ " mv" , " src/lib.rs" , " src/lib2.rs" ] ) ;
1035
write ( & g, " src/lib2.rs" , b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n pub fn d() {}\n " ) ;
1036
git ( & [ " add" , " ." ] ) ;
1037
git ( & [ " commit" , " -q" , " -m" , " rename lib" ] ) ;
c3069537 Andy McCall 1 month ago ↶
1038
let expected_count: usize = git ( & [ " rev-list" , " --count" , " main" ] ) .parse ( ) .unwrap ( ) ;
1039
1040
let dest = root.join ( " oxy" ) ;
1041
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
1042
let rep = repo.import_git ( & g, " main" ) .unwrap ( ) ;
1043
assert_eq! ( rep.commits, expected_count) ;
1044
assert_eq! ( rep.merges, 2 ) ;
1045
6e723b81 Andy McCall 1 month ago ↶
1046
assert_eq! ( fs:: read( dest.join ( " src/lib2.rs" ) ) .unwrap ( ) , b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n pub fn d() {}\n " ) ;
1047
assert! ( ! dest.join ( " src/lib.rs" ) .exists ( ) ) ;
c3069537 Andy McCall 1 month ago ↶
1048
assert_eq! ( fs:: read( dest.join ( " README.md" ) ) .unwrap ( ) , b " # Hi\n \n More.\n " ) ;
1049
assert! ( ! dest.join ( " logo.png" ) .exists ( ) ) ;
1050
# [ cfg ( unix ) ]
1051
{
1052
use std:: os:: unix:: fs:: PermissionsExt;
1053
assert! ( fs:: metadata( dest.join ( " run.sh" ) ) .unwrap ( ) .permissions ( ) .mode ( ) & 0o111 ! = 0 ) ;
1054
}
1055
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed, " clean after import" ) ;
1056
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
1057
let st = repo.load_state ( & heads) .unwrap ( ) ;
1058
assert! ( ! st.materialize ( ) .is_conflicted ( ) ) ;
1059
assert_eq! ( st.len ( ) , expected_count) ;
6e723b81 Andy McCall 1 month ago ↶
1060
let tip = repo.log ( ) .unwrap ( ) .into_iter ( ) .find ( | e | e.change.message.starts_with ( " rename lib" ) ) .unwrap ( ) ;
1061
let c = repo.store ( ) .get_change ( & tip.hash) .unwrap ( ) ;
1062
assert! ( c.tree_ops.iter ( ) .any ( | op | matches! ( op, TreeOp:: Move { name, .. } if name = = " lib2.rs" ) ) , " {:?}" , c.tree_ops) ;
1063
assert! ( ! c.tree_ops.iter ( ) .any ( | op | matches! ( op, TreeOp:: AddNode { .. } ) ) ) ;
c3069537 Andy McCall 1 month ago ↶
1064
1065
let log = repo.log ( ) .unwrap ( ) ;
1066
let second = log.iter ( ) .find ( | e | e.change.message.starts_with ( " second" ) ) .unwrap ( ) ;
1067
assert_eq! ( second.change.change_id, ChangeId:: from_hex( " 0123456789abcdef0123456789abcdef" ) .unwrap ( ) ) ;
1068
assert! ( second.change.message.contains ( " Git-Author: Grace <[email protected] >" ) ) ;
1069
let resolve = log.iter ( ) .find ( | e | e.change.message.starts_with ( " resolve clash" ) ) .unwrap ( ) ;
1070
assert_eq! ( resolve.change.kind, ChangeKind:: Resolution, " a conflicting Git merge becomes a resolution" ) ;
1071
1072
let repo2 = Repo:: init( & root.join ( " oxy2" ) ) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
1073
repo2.import_git ( & g, " main" ) .unwrap ( ) ;
1074
let ids: BTreeSet< ChangeId> = repo.log ( ) .unwrap ( ) .iter ( ) .filter ( | e | ! e.is_working_copy) .map ( | e | e.change.change_id ) .collect ( ) ;
1075
let ids2: BTreeSet< ChangeId> = repo2.log ( ) .unwrap ( ) .iter ( ) .filter ( | e | ! e.is_working_copy) .map ( | e | e.change.change_id ) .collect ( ) ;
1076
assert_eq! ( ids, ids2) ;
1077
1078
let mirror = root.join ( " mirror.git" ) ;
1079
repo.git_export ( & mirror) .unwrap ( ) ;
6e723b81 Andy McCall 1 month ago ↶
1080
let out = std:: process:: Command:: new( " git" ) .arg ( " -C" ) .arg ( & mirror) .args ( [ " show" , " main:src/lib2.rs" ] ) .output ( ) .unwrap ( ) ;
1081
assert_eq! ( String :: from_utf8_lossy( & out.stdout) , " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n pub fn d() {}\n " ) ;
c3069537 Andy McCall 1 month ago ↶
1082
let out = std:: process:: Command:: new( " git" ) .arg ( " -C" ) .arg ( & mirror) .args ( [ " show" , " main:src/lib.rs" ] ) .output ( ) .unwrap ( ) ;
6e723b81 Andy McCall 1 month ago ↶
1083
assert! ( ! out.status.success ( ) , " the old path is gone from the export" ) ;
c3069537 Andy McCall 1 month ago ↶
1084
}
252e8cb2 Andy McCall 1 month ago ↶
1085
1086
# [ test ]
1087
fn checkpoints_accelerate_loading_and_stay_correct ( ) {
1088
hermetic ( ) ;
1089
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1090
let root = dir.path ( ) ;
1091
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
1092
repo.config.set ( " core.checkpoint-interval" , " 5" ) .unwrap ( ) ;
1093
repo.save_config ( ) .unwrap ( ) ;
1094
let repo = Repo:: open( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
1095
let mut hashes = Vec :: new( ) ;
1096
for i in 0 .. 12 {
1097
write ( root, " a.txt" , format! ( " {} \n " , ( 0 .. = i) .map ( | k | k.to_string ( ) ) .collect:: < Vec< _ > > ( ) .join ( " \n " ) ) .as_bytes ( ) ) ;
1098
write ( root, & format! ( " f{i} .txt" ) , b " x\n " ) ;
1099
hashes.push ( repo.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ) ;
1100
}
1101
let ( _ , cp) = repo.latest_checkpoint ( " main" ) .unwrap ( ) .expect ( " automatic checkpoint" ) ;
1102
assert! ( cp.count > = 5 & & cp.count < = 12 , " {}" , cp.count) ;
1103
1104
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
1105
let fast = repo.load_state ( & heads) .unwrap ( ) ;
1106
let full = repo.load_state_full ( & heads) .unwrap ( ) ;
1107
assert_eq! ( fast.len ( ) , full.len ( ) ) ;
1108
assert_eq! ( fast.heads ( ) , full.heads ( ) ) ;
1109
let ( a, b) = ( fast.materialize ( ) , full.materialize ( ) ) ;
1110
assert_eq! ( a.files.keys ( ) .collect:: < Vec< _ > > ( ) , b.files.keys ( ) .collect:: < Vec< _ > > ( ) ) ;
1111
for ( p, f) in & a.files {
1112
assert_eq! ( f.content, b.files[ p] .content, " {p}" ) ;
1113
}
1114
assert! ( repo.load_state_checkpointed ( & heads) .unwrap ( ) .is_some ( ) ) ;
1115
1116
assert! ( repo.load_state_checkpointed ( & [ hashes[ 0 ] ] ) .unwrap ( ) .is_none ( ) ) ;
1117
assert_eq! ( repo.load_state ( & [ hashes[ 0 ] ] ) .unwrap ( ) .len ( ) , 1 ) ;
1118
1119
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
1120
let h = repo.describe ( & hashes[ 3 ] , " c3 described" ) .unwrap ( ) ;
1121
assert_eq! ( repo.store ( ) .get_change ( & h) .unwrap ( ) .message, " c3 described" ) ;
1122
let ( r, n, bytes) = repo.checkpoint ( ) .unwrap ( ) ;
1123
assert! ( n > = 12 & & bytes > 0 ) ;
1124
assert_eq! ( repo.latest_checkpoint ( " main" ) .unwrap ( ) .unwrap ( ) .0 , r) ;
1125
write ( root, " a.txt" , b " after\n " ) ;
1126
let h_after = repo.commit ( " after" , & [ ] ) .unwrap ( ) ;
1127
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
1128
assert! ( st.contains ( & h_after) ) ;
1129
assert! ( matches! ( & st.materialize ( ) .files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " after\n " ) ) ;
1130
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 14 ) ;
1131
1132
repo.gc ( true ) .unwrap ( ) ;
1133
assert! ( repo.latest_checkpoint ( " main" ) .unwrap ( ) .is_some ( ) ) ;
1134
assert! ( repo.load_state_checkpointed ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) .is_some ( ) ) ;
1135
}
135212de Andy McCall 1 month ago ↶
1136
1137
# [ test ]
1138
fn quick_status_for_prompts ( ) {
1139
hermetic ( ) ;
1140
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1141
let root = dir.path ( ) ;
1142
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1143
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1144
let a = Repo:: clone( & url, & root.join ( " a" ) ) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
1145
let ops_before = a.op_log ( ) .unwrap ( ) .len ( ) ;
1146
let info = a.quick_status ( ) .unwrap ( ) ;
1147
assert_eq! ( ( info.bookmark.as_str ( ) , info.heads, info.dirty, info.conflicted, info.ahead, info.behind) , ( " main" , 0 , false , false , Some ( 0 ) , Some ( 0 ) ) ) ;
1148
write ( & root.join ( " a" ) , " f.txt" , b " f\n " ) ;
1149
assert! ( a.quick_status ( ) .unwrap ( ) .dirty, " a new file is dirty" ) ;
1150
assert_eq! ( a.op_log ( ) .unwrap ( ) .len ( ) , ops_before, " quick_status records nothing" ) ;
1151
1152
a.snapshot ( " status" ) .unwrap ( ) ;
1153
assert! ( a.quick_status ( ) .unwrap ( ) .dirty) ;
1154
a.commit ( " f" , & [ ] ) .unwrap ( ) ;
1155
let info = a.quick_status ( ) .unwrap ( ) ;
1156
assert! ( ! info.dirty, " clean right after a commit" ) ;
1157
assert_eq! ( ( info.ahead, info.behind) , ( Some ( 1 ) , Some ( 0 ) ) ) ;
1158
assert_eq! ( info.format ( " %b %c%d%x%A%D" ) , format! ( " main {} +1" , info.change_id) ) ;
1159
assert_eq! ( info.format ( " [%h heads] %a/%B %%" ) , " [1 heads] 1/0 %" ) ;
1160
1161
1162
let p = root.join ( " a/f.txt" ) ;
1163
let content = fs:: read( & p) .unwrap ( ) ;
1164
fs:: write( & p, & content) .unwrap ( ) ;
1165
assert! ( ! a.quick_status ( ) .unwrap ( ) .dirty) ;
1166
fs:: write( & p, b " g\n " ) .unwrap ( ) ;
1167
assert! ( a.quick_status ( ) .unwrap ( ) .dirty) ;
1168
fs:: write( & p, b " f\n " ) .unwrap ( ) ;
1169
1170
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
1171
assert_eq! ( a.quick_status ( ) .unwrap ( ) .ahead, Some ( 0 ) ) ;
1172
let b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) .with_home ( root.join ( " home-b" ) ) ;
1173
write ( & root.join ( " b" ) , " g.txt" , b " g\n " ) ;
1174
b.commit ( " g" , & [ ] ) .unwrap ( ) ;
1175
b.push ( " origin" , false ) .unwrap ( ) ;
1176
a.fetch ( " origin" ) .unwrap ( ) ;
1177
let info = a.quick_status ( ) .unwrap ( ) ;
1178
assert_eq! ( ( info.ahead, info.behind) , ( Some ( 0 ) , Some ( 1 ) ) ) ;
1179
assert_eq! ( info.format ( " %A%D" ) , " -1" ) ;
1180
let _ = origin;
1181
}
b3c5a59a Andy McCall 1 month ago ↶
1182
1183
# [ test ]
1184
fn lazy_blobs_are_fetched_on_first_read ( ) {
1185
hermetic ( ) ;
1186
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1187
let root = dir.path ( ) ;
1188
let origin_dir = root.join ( " origin" ) ;
1189
let origin = Repo:: init( & origin_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
1190
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
1191
let big: Vec< u8 > = ( 0 .. 200_000 u32 ) .map ( | i | ( i % 251 ) as u8 ) .collect ( ) ;
1192
write ( & origin_dir, " big.bin" , & big) ;
1193
write ( & origin_dir, " small.bin" , & [ 0 u8 , 1 , 2 ] ) ;
1194
write ( & origin_dir, " t.txt" , b " t\n " ) ;
1195
let h = origin.commit ( " blobs" , & [ ] ) .unwrap ( ) ;
1196
let big_ref = Repo:: blob_ref( & big) ;
1197
1198
1199
let a = Repo:: init_with_id( & root.join ( " a" ) , origin.id ( ) .unwrap ( ) ) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
1200
let a = {
1201
let mut a = a;
1202
a.config.set ( " remote.origin.url" , & url) .unwrap ( ) ;
1203
a.config.set ( " filter.max-blob" , " 100K" ) .unwrap ( ) ;
1204
a.save_config ( ) .unwrap ( ) ;
1205
a
1206
} ;
1207
assert_eq! ( a.config.max_blob, Some ( 100 * 1024 ) ) ;
1208
a.fetch ( " origin" ) .unwrap ( ) ;
1209
assert! ( a.store ( ) .has ( & h) ) ;
1210
assert! ( ! a.store ( ) .has ( & big_ref) , " withheld" ) ;
1211
assert! ( a.store ( ) .has ( & Repo:: blob_ref( & [ 0 u8 , 1 , 2 ] ) ) , " small blob came along" ) ;
1212
assert_eq! ( a.lazy_remote ( & big_ref) .as_deref ( ) , Some ( " origin" ) ) ;
1213
1214
1215
let ( op_ref, op) = a.head_op ( ) .unwrap ( ) ;
1216
let mut view = op.view.clone ( ) ;
1217
view.bookmarks.insert ( " main" .into ( ) , a.store ( ) .put_state ( & StateObject:: new( vec! [ h] ) ) .unwrap ( ) ) ;
1218
a.record_op ( & [ op_ref] , " adopt" , view) .unwrap ( ) ;
1219
1220
a.checkout ( & [ h] , true , " checkout" ) .unwrap ( ) ;
1221
assert! ( a.store ( ) .has ( & big_ref) , " fetched on first read" ) ;
1222
assert_eq! ( fs:: read( root.join ( " a/big.bin" ) ) .unwrap ( ) , big) ;
1223
assert! ( a.lazy_remote ( & big_ref) .is_none ( ) , " no longer registered" ) ;
1224
write ( & root.join ( " a" ) , " t.txt" , b " t2\n " ) ;
1225
a.commit ( " text edit" , & [ ] ) .unwrap ( ) ;
1226
1227
7421309f Andy McCall 1 month ago ↶
1228
let b = Repo:: clone_with( & url, & root.join ( " b" ) , Some ( 1024 ) , & [ ] ) .unwrap ( ) ;
b3c5a59a Andy McCall 1 month ago ↶
1229
assert_eq! ( b.config.max_blob, Some ( 1024 ) ) ;
1230
assert_eq! ( fs:: read( root.join ( " b/big.bin" ) ) .unwrap ( ) , big) ;
1231
assert_eq! ( parse_size ( " 2M" ) .unwrap ( ) , 2 < < 20 ) ;
1232
assert! ( parse_size ( " lots" ) .is_err ( ) ) ;
1233
}
7421309f Andy McCall 1 month ago ↶
1234
1235
# [ test ]
1236
fn sparse_checkout_materializes_only_chosen_paths ( ) {
1237
hermetic ( ) ;
1238
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1239
let root = dir.path ( ) ;
1240
let origin_dir = root.join ( " origin" ) ;
1241
let origin = Repo:: init( & origin_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
1242
write ( & origin_dir, " src/main.rs" , b " fn main() {}\n " ) ;
1243
write ( & origin_dir, " src/lib.rs" , b " pub fn a() {}\n " ) ;
1244
write ( & origin_dir, " docs/guide.md" , b " # guide\n " ) ;
1245
let logo: Vec< u8 > = ( 0 .. 50_000 u32 ) .map ( | i | ( i % 7 ) as u8 ) .collect ( ) ;
1246
write ( & origin_dir, " docs/logo.png" , & logo) ;
1247
write ( & origin_dir, " README.md" , b " # top\n " ) ;
1248
let h1 = origin.commit ( " tree" , & [ ] ) .unwrap ( ) ;
1249
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
1250
1251
let a = Repo:: clone_with( & url, & root.join ( " a" ) , None , & [ " src" .into ( ) ] ) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
1252
assert_eq! ( a.config.sparse_paths, vec! [ " src" ] ) ;
1253
assert_eq! ( a.config.max_blob, Some ( 0 ) , " sparse implies lazy blobs" ) ;
1254
assert! ( root.join ( " a/src/main.rs" ) .exists ( ) ) ;
1255
assert! ( ! root.join ( " a/docs" ) .exists ( ) & & ! root.join ( " a/README.md" ) .exists ( ) ) ;
1256
assert! ( ! a.store ( ) .has ( & Repo:: blob_ref( & logo) ) , " out-of-scope blob never fetched" ) ;
1257
assert! ( ! a.snapshot ( " status" ) .unwrap ( ) .changed, " out-of-scope files are not removals" ) ;
1258
assert_eq! ( a.quick_status ( ) .unwrap ( ) .dirty, false ) ;
1259
1260
1261
write ( & root.join ( " a" ) , " src/lib.rs" , b " pub fn a() {}\n pub fn b() {}\n " ) ;
1262
let h2 = a.commit ( " edit lib" , & [ ] ) .unwrap ( ) ;
1263
let st = a.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) ;
1264
assert! ( st.files.contains_key ( " docs/guide.md" ) , " the state still has everything" ) ;
1265
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
1266
1267
write ( & root.join ( " a" ) , " docs/new.md" , b " ignored\n " ) ;
1268
assert! ( a.snapshot ( " status" ) .unwrap ( ) .statuses.is_empty ( ) ) ;
1269
1270
1271
let mut a = a;
1272
a.config.set ( " filter.paths" , " src,docs" ) .unwrap ( ) ;
1273
a.save_config ( ) .unwrap ( ) ;
1274
let r = a.checkout ( & a.bookmark_heads ( ) .unwrap ( ) , true , " widen" ) .unwrap ( ) ;
1275
assert! ( r.written > = 2 ) ;
1276
assert_eq! ( fs:: read( root.join ( " a/docs/logo.png" ) ) .unwrap ( ) , logo, " now fetched lazily" ) ;
1277
assert! ( ! root.join ( " a/README.md" ) .exists ( ) ) ;
1278
1279
origin.pull ( " origin" ) .err ( ) ;
1280
let full = origin.load_state ( & origin.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) .materialize ( ) ;
1281
assert! ( matches! ( & full.files[ " src/lib.rs" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " pub fn a() {}\n pub fn b() {}\n " ) ) ;
1282
assert! ( full.files.contains_key ( " README.md" ) ) ;
1283
let _ = h1;
1284
}
2863456a Andy McCall 1 month ago ↶
1285
1286
# [ test ]
1287
fn shallow_clone_and_deepen ( ) {
1288
hermetic ( ) ;
1289
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1290
let root = dir.path ( ) ;
1291
let origin_dir = root.join ( " origin" ) ;
1292
let origin = Repo:: init( & origin_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
1293
let mut hashes = Vec :: new( ) ;
1294
for i in 0 .. 8 {
1295
write ( & origin_dir, " a.txt" , format! ( " {} \n " , ( 0 .. = i) .map ( | k | k.to_string ( ) ) .collect:: < Vec< _ > > ( ) .join ( " \n " ) ) .as_bytes ( ) ) ;
1296
hashes.push ( origin.commit ( & format! ( " c{i} " ) , & [ ] ) .unwrap ( ) ) ;
1297
}
1298
let url = origin_dir.to_string_lossy ( ) .to_string ( ) ;
1299
let a = Repo:: clone_shallow( & url, & root.join ( " a" ) , 3 , None , & [ ] ) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
1300
assert! ( a.is_shallow ( ) ) ;
1301
assert_eq! ( fs:: read_to_string( root.join ( " a/a.txt" ) ) .unwrap ( ) , " 0\n 1\n 2\n 3\n 4\n 5\n 6\n 7\n " ) ;
1302
assert! ( a.store ( ) .has ( & hashes[ 7 ] ) & & a.store ( ) .has ( & hashes[ 5 ] ) , " three levels present" ) ;
1303
assert! ( ! a.store ( ) .has ( & hashes[ 4 ] ) , " beyond the depth is absent" ) ;
1304
assert_eq! ( a.boundary ( ) , [ hashes[ 4 ] ] .into_iter ( ) .collect ( ) ) ;
1305
assert_eq! ( a.log ( ) .unwrap ( ) .len ( ) , 9 , " the log shows every change from the checkpoint's metadata" ) ;
1306
assert! ( ! a.snapshot ( " status" ) .unwrap ( ) .changed) ;
1307
assert_eq! ( a.quick_status ( ) .unwrap ( ) .heads, 1 ) ;
1308
1309
1310
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 " ) ;
1311
let h_new = a.commit ( " c8" , & [ ] ) .unwrap ( ) ;
1312
assert! ( a.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
1313
assert_eq! ( origin.bookmark_heads ( ) .unwrap ( ) , vec! [ h_new] ) ;
1314
1315
write ( & origin_dir, " b.txt" , b " b\n " ) ;
1316
origin.pull ( " origin" ) .err ( ) ;
1317
let ( _ , wc) = origin.working_copy ( ) .unwrap ( ) ;
1318
let _ = wc;
1319
origin.checkout ( & origin.bookmark_heads ( ) .unwrap ( ) , true , " co" ) .unwrap ( ) ;
1320
write ( & origin_dir, " b.txt" , b " b\n " ) ;
1321
let h_b = origin.commit ( " b" , & [ ] ) .unwrap ( ) ;
1322
let pr = a.pull ( " origin" ) .unwrap ( ) ;
1323
assert! ( pr.changed) ;
1324
assert! ( a.store ( ) .has ( & h_b) ) ;
1325
assert! ( root.join ( " a/b.txt" ) .exists ( ) ) ;
1326
1327
1328
1329
1330
assert! ( a.deepen ( " origin" , 2 ) .unwrap ( ) > = 2 ) ;
1331
assert! ( a.store ( ) .has ( & hashes[ 3 ] ) ) ;
1332
assert! ( a.is_shallow ( ) ) ;
1333
a.deepen ( " origin" , 100 ) .unwrap ( ) ;
1334
assert! ( ! a.is_shallow ( ) , " boundary reached the root" ) ;
1335
assert! ( a.store ( ) .has ( & hashes[ 0 ] ) ) ;
1336
assert_eq! ( a.load_state_full ( & a.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) .len ( ) , 10 ) ;
1337
}
132b884b Andy McCall 1 month ago ↶
1338
1339
# [ test ]
1340
fn tags_are_immutable_named_states_and_travel ( ) {
1341
hermetic ( ) ;
1342
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1343
let root = dir.path ( ) ;
1344
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1345
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1346
let mut a = Repo:: clone( & url, & root.join ( " a" ) ) .unwrap ( ) .with_home ( root.join ( " home-a" ) ) ;
1347
let ( key_a, _ ) = a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User) .unwrap ( ) ;
1348
write ( & root.join ( " a" ) , " f.txt" , b " 1\n " ) ;
1349
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
1350
write ( & root.join ( " a" ) , " f.txt" , b " 1\n 2\n " ) ;
1351
let h2 = a.commit ( " two" , & [ ] ) .unwrap ( ) ;
1352
1353
let t = a.tag_create ( " v1" , & [ h1] , " first release" ) .unwrap ( ) ;
1354
assert_eq! ( ( t.heads.clone ( ) , t.signer.as_deref ( ) ) , ( vec! [ h1] , Some ( " Ada" ) ) ) ;
1355
assert! ( a.tag_create ( " v1" , & [ h2] , " " ) .is_err ( ) , " immutable" ) ;
1356
assert! ( a.tag_create ( " bad/name" , & [ h2] , " " ) .is_err ( ) ) ;
1357
assert_eq! ( a.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
1358
assert_eq! ( a.resolve_heads ( " v1" ) .unwrap ( ) , vec! [ h1] ) ;
1359
assert_eq! ( a.resolve_heads ( " main" ) .unwrap ( ) , vec! [ h2] ) ;
1360
let ( _ , sa) = a.tag_attest ( " v1" ) .unwrap ( ) .unwrap ( ) ;
1361
assert_eq! ( sa.sigs[ 0 ] .key, key_a) ;
1362
1363
a.checkout ( & a.resolve_heads ( " v1" ) .unwrap ( ) , false , " co" ) .unwrap ( ) ;
1364
assert_eq! ( fs:: read( root.join ( " a/f.txt" ) ) .unwrap ( ) , b " 1\n " ) ;
1365
a.checkout ( & a.bookmark_heads ( ) .unwrap ( ) , false , " co" ) .unwrap ( ) ;
1366
1367
1368
let r = a.push ( " origin" , false ) .unwrap ( ) ;
1369
assert! ( r.iter ( ) .any ( | x | x.name = = " tags/v1" & & x.ok ) , " {r:?}" ) ;
1370
assert_eq! ( origin.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
1371
let mut forged = a.tag_refs ( ) .unwrap ( ) .into_iter ( ) .find ( | t | t.name = = " v1" ) .unwrap ( ) ;
1372
forged.state = origin.store ( ) .put_state ( & StateObject:: new( vec! [ h2] ) ) .unwrap ( ) ;
1373
forged.heads = vec! [ h2] ;
1374
forged.attestation = None ;
1375
let r = origin.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ forged] ) .unwrap ( ) ;
1376
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " immutable" ) , " {r:?}" ) ;
1377
1378
1379
let b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) .with_home ( root.join ( " home-b" ) ) ;
1380
assert_eq! ( b.tag_heads ( " v1" ) .unwrap ( ) , Some ( vec! [ h1] ) ) ;
1381
assert! ( b.tags ( ) .unwrap ( ) [ 0 ] .signer.as_deref ( ) = = Some ( " Ada" ) ) ;
1382
a.tag_create ( " v2" , & [ h2] , " second" ) .unwrap ( ) ;
1383
a.push ( " origin" , false ) .unwrap ( ) ;
1384
let rep = b.fetch ( " origin" ) .unwrap ( ) ;
1385
assert_eq! ( rep.new_tags, 1 ) ;
1386
assert_eq! ( b.tag_heads ( " v2" ) .unwrap ( ) , Some ( vec! [ h2] ) ) ;
1387
1388
1389
a.policy_init ( ) .unwrap ( ) ;
1390
a.push ( " origin" , false ) .unwrap ( ) ;
1391
let unattested = crate :: protocol:: TagRef { name: " v3" .into ( ) , state: origin.store ( ) .put_state ( & StateObject:: new( vec! [ h2] ) ) .unwrap ( ) , heads: vec! [ h2] , attestation: None } ;
1392
let r = origin.receive_push ( Bundle:: default( ) , vec! [ ] , None , vec! [ unattested] ) .unwrap ( ) ;
1393
assert! ( ! r[ 0 ] .ok & & r[ 0 ] .reason.contains ( " attestation" ) , " {r:?}" ) ;
1394
a.tag_create ( " v3" , & [ h2] , " " ) .unwrap ( ) ;
1395
let r = a.push ( " origin" , false ) .unwrap ( ) ;
1396
assert! ( r.iter ( ) .any ( | x | x.name = = " tags/v3" & & x.ok ) , " {r:?}" ) ;
1397
assert! ( a.gc ( true ) .is_ok ( ) ) ;
1398
assert_eq! ( a.tags ( ) .unwrap ( ) .len ( ) , 3 , " tags survive gc" ) ;
1399
}
4e88e774 Andy McCall 1 month ago ↶
1400
1401
# [ test ]
1402
fn multiple_workspaces_share_a_repository ( ) {
1403
hermetic ( ) ;
1404
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1405
let root = dir.path ( ) ;
1406
let main_dir = root.join ( " main" ) ;
1407
let repo = Repo:: init( & main_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
1408
write ( & main_dir, " a.txt" , b " a\n " ) ;
1409
let h1 = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
1410
assert_eq! ( repo.workspaces ( ) .unwrap ( ) .len ( ) , 1 ) ;
1411
1412
1413
let ws_dir = root.join ( " ws2" ) ;
1414
let ws = repo.workspace_add ( & ws_dir, None ) .unwrap ( ) ;
1415
assert! ( ws_dir.join ( " .oxy" ) .is_file ( ) ) ;
1416
assert_eq! ( fs:: read( ws_dir.join ( " a.txt" ) ) .unwrap ( ) , b " a\n " ) ;
1417
assert_ne! ( ws.workspace ( ) , repo.workspace ( ) ) ;
1418
let list = repo.workspaces ( ) .unwrap ( ) ;
1419
assert_eq! ( list.len ( ) , 2 ) ;
1420
assert! ( list.iter ( ) .any ( | w | w.id = = ws.workspace ( ) & & w.path = = ws_dir.canonicalize ( ) .unwrap ( ) & & ! w.current ) ) ;
1421
assert! ( ! ws.snapshot ( " status" ) .unwrap ( ) .changed) ;
1422
1423
1424
write ( & ws_dir, " b.txt" , b " b\n " ) ;
1425
let s = ws.snapshot ( " status" ) .unwrap ( ) ;
1426
assert_eq! ( s.statuses.len ( ) , 1 ) ;
1427
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed, " the first workspace is untouched" ) ;
1428
let h2 = ws.commit ( " b from ws2" , & [ ] ) .unwrap ( ) ;
1429
let mut heads = repo.bookmark_heads ( ) .unwrap ( ) ;
1430
heads.sort ( ) ;
1431
let mut want = vec! [ h1, h2] ;
1432
want.sort ( ) ;
1433
assert_eq! ( heads, want, " both workspaces see the bookmark move" ) ;
1434
assert! ( ! main_dir.join ( " b.txt" ) .exists ( ) ) ;
1435
repo.checkout ( & repo.bookmark_heads ( ) .unwrap ( ) , false , " co" ) .unwrap ( ) ;
1436
assert! ( main_dir.join ( " b.txt" ) .exists ( ) ) ;
1437
1438
1439
let reopened = Repo:: open( & ws_dir) .unwrap ( ) ;
1440
assert_eq! ( reopened.workspace ( ) , ws.workspace ( ) ) ;
1441
assert_eq! ( reopened.id ( ) .unwrap ( ) , repo.id ( ) .unwrap ( ) ) ;
1442
assert_eq! ( reopened.quick_status ( ) .unwrap ( ) .heads, 2 ) ;
1443
1444
1445
assert! ( repo.workspace_add ( & main_dir.join ( " inner" ) , None ) .is_err ( ) ) ;
1446
assert! ( repo.workspace_forget ( & repo.workspace ( ) ) .is_err ( ) ) ;
1447
repo.workspace_forget ( & ws.workspace ( ) ) .unwrap ( ) ;
1448
assert_eq! ( repo.workspaces ( ) .unwrap ( ) .len ( ) , 1 ) ;
1449
assert! ( ws_dir.join ( " b.txt" ) .exists ( ) ) ;
1450
assert! ( ws.snapshot ( " status" ) .is_err ( ) , " a forgotten workspace is no longer in the view" ) ;
1451
1452
1453
let lock = repo.lock_snapshot ( ) .unwrap ( ) ;
1454
drop ( lock) ;
1455
let lock_path = main_dir.join ( " .oxy/workspaces" ) .join ( repo.workspace ( ) .to_hex ( ) ) .join ( " lock" ) ;
1456
fs:: write( & lock_path, " 1" ) .unwrap ( ) ;
1457
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 120 ) ;
1458
fs:: File:: open( & lock_path) .unwrap ( ) .set_modified ( old) .unwrap ( ) ;
1459
assert! ( repo.lock_snapshot ( ) .is_ok ( ) , " stale lock taken over" ) ;
1460
}
cdb5beba Andy McCall 1 month ago ↶
1461
1462
# [ test ]
1463
fn attributes_binary_eol_nocompress_lockable ( ) {
1464
hermetic ( ) ;
1465
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1466
let root = dir.path ( ) ;
1467
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
1468
write ( root, " .oxyattributes" , b " *.dat binary nocompress\n *.txt eol=crlf\n assets/** lockable\n " ) ;
1469
write ( root, " plain.dat" , b " no nul bytes here\n " ) ;
1470
write ( root, " notes.txt" , b " one\r \n two\r \n " ) ;
1471
write ( root, " assets/model.bin" , b " \0 model" ) ;
1472
let h = repo.commit ( " attrs" , & [ ] ) .unwrap ( ) ;
1473
let snap = repo.load_state ( & [ h] ) .unwrap ( ) .materialize ( ) ;
1474
assert! ( snap.files[ " plain.dat" ] .mode.opaque, " binary attribute forces opaque without NUL bytes" ) ;
1475
assert! ( matches! ( & snap.files[ " notes.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n " ) , " stored with LF" ) ;
1476
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed, " CRLF on disk equals LF in the state" ) ;
1477
1478
let r = repo.gc ( true ) .unwrap ( ) ;
1479
assert! ( r.packed.objects > 0 ) ;
1480
1481
fs:: remove_file( root.join ( " notes.txt" ) ) .unwrap ( ) ;
1482
repo.checkout ( & [ h] , true , " co" ) .unwrap ( ) ;
1483
assert_eq! ( fs:: read( root.join ( " notes.txt" ) ) .unwrap ( ) , b " one\r \n two\r \n " ) ;
1484
assert! ( fs:: metadata( root.join ( " assets/model.bin" ) ) .unwrap ( ) .permissions ( ) .readonly ( ) ) ;
1485
assert! ( repo.lock_path ( " plain.dat" ) .is_err ( ) , " not lockable" ) ;
1486
repo.lock_path ( " assets/model.bin" ) .unwrap ( ) ;
1487
assert! ( ! fs:: metadata( root.join ( " assets/model.bin" ) ) .unwrap ( ) .permissions ( ) .readonly ( ) ) ;
1488
assert! ( repo.is_locked ( " assets/model.bin" ) ) ;
1489
write ( root, " assets/model.bin" , b " \0 model2" ) ;
1490
repo.commit ( " edit model" , & [ ] ) .unwrap ( ) ;
1491
repo.unlock_path ( " assets/model.bin" ) .unwrap ( ) ;
1492
assert! ( fs:: metadata( root.join ( " assets/model.bin" ) ) .unwrap ( ) .permissions ( ) .readonly ( ) ) ;
1493
1494
repo.checkout ( & [ h] , true , " co" ) .unwrap ( ) ;
1495
assert_eq! ( fs:: read( root.join ( " assets/model.bin" ) ) .unwrap ( ) , b " \0 model" ) ;
1496
}
6d3eefbc Andy McCall 1 month ago ↶
1497
1498
# [ cfg ( unix ) ]
1499
# [ test ]
1500
fn hooks_can_veto_observe_and_add_dependencies ( ) {
1501
use std:: os:: unix:: fs:: PermissionsExt;
1502
hermetic ( ) ;
1503
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1504
let root = dir.path ( ) ;
1505
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
1506
let hooks = root.join ( " .oxy/hooks" ) ;
1507
fs:: create_dir_all( & hooks) .unwrap ( ) ;
1508
let hook = | name : & str , body : & str | {
1509
let p = hooks.join ( name) ;
1510
fs:: write( & p, format! ( " #!/bin/sh\n {body} \n " ) ) .unwrap ( ) ;
1511
fs:: set_permissions( & p, fs:: Permissions:: from_mode( 0o755 ) ) .unwrap ( ) ;
1512
} ;
1513
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" ) ;
1514
hook ( " post-commit" , " cat > \" $OXY_ROOT/.last-post-commit\" " ) ;
1515
hook ( " post-checkout" , " cat > \" $OXY_ROOT/.last-post-checkout\" " ) ;
1516
hook ( " pre-push" , " echo pushing blocked >&2; exit 3" ) ;
1517
write ( root, " .oxyignore" , b " .last-*\n " ) ;
1518
write ( root, " a.txt" , b " a\n " ) ;
1519
let err = repo.commit ( " WIP: nope" , & [ ] ) .unwrap_err ( ) .to_string ( ) ;
1520
assert! ( err.contains ( " pre-commit refused: no WIP commits" ) , " {err}" ) ;
1521
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
1522
let pre = fs:: read_to_string( root.join ( " .last-pre-commit" ) ) .unwrap ( ) ;
1523
assert! ( pre.contains ( " \" event\" :\" pre-commit\" " ) & & pre.contains ( " \" path\" :\" a.txt\" " ) & & pre.contains ( " \" message\" :\" first\" " ) , " {pre}" ) ;
1524
let post = fs:: read_to_string( root.join ( " .last-post-commit" ) ) .unwrap ( ) ;
1525
assert! ( post.contains ( & h1.to_hex ( ) ) , " {post}" ) ;
1526
repo.checkout ( & [ h1] , false , " co" ) .unwrap ( ) ;
1527
assert! ( fs:: read_to_string( root.join ( " .last-post-checkout" ) ) .unwrap ( ) .contains ( " post-checkout" ) ) ;
1528
repo.config.set ( " remote.origin.url" , " /nowhere" ) .unwrap ( ) ;
1529
repo.save_config ( ) .unwrap ( ) ;
1530
let err = repo.push ( " origin" , false ) .unwrap_err ( ) .to_string ( ) ;
1531
assert! ( err.contains ( " pre-push refused: pushing blocked" ) , " {err}" ) ;
1532
1533
1534
write ( root, " b.txt" , b " b\n " ) ;
1535
hook ( " semantic-deps" , & format! ( " cat > \" $OXY_ROOT/.last-semantic\" ; echo '{{ \" deps\" : [\" {} \" ]}} '" , h1.to_hex ( ) ) ) ;
1536
let h2 = repo.commit ( " b without hook" , & [ ] ) .unwrap ( ) ;
1537
assert! ( repo.store ( ) .get_change ( & h2) .unwrap ( ) .deps.is_empty ( ) , " hook disabled by default" ) ;
1538
repo.config.set ( " hooks.semantic-deps" , " true" ) .unwrap ( ) ;
1539
repo.save_config ( ) .unwrap ( ) ;
1540
write ( root, " c.txt" , b " c\n " ) ;
1541
let h3 = repo.commit ( " c with hook" , & [ ] ) .unwrap ( ) ;
1542
assert_eq! ( repo.store ( ) .get_change ( & h3) .unwrap ( ) .deps, vec! [ h1] ) ;
1543
let sem = fs:: read_to_string( root.join ( " .last-semantic" ) ) .unwrap ( ) ;
1544
assert! ( sem.contains ( " \" touched_paths\" :[\" c.txt\" ]" ) & & sem.contains ( " candidate_deps" ) , " {sem}" ) ;
1545
assert_eq! ( json_str ( " a\" b\\ c\n " ) , " \" a\\ \" b\\ \\ c\\ n\" " ) ;
1546
}
23b217d7 Andy McCall 1 month ago ↶
1547
1548
# [ test ]
1549
fn amend_into_older_change_and_split ( ) {
1550
hermetic ( ) ;
1551
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1552
let root = dir.path ( ) ;
1553
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
1554
repo.create_identity ( " Ada" , " a@b" , IdentityScope:: User) .unwrap ( ) ;
1555
repo.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
1556
write ( root, " a.txt" , b " one\n two\n " ) ;
1557
let h0 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
1558
write ( root, " a.txt" , b " one\n TWO\n " ) ;
1559
let h1 = repo.commit ( " edit two" , & [ ] ) .unwrap ( ) ;
1560
write ( root, " a.txt" , b " one\n TWO\n three\n " ) ;
1561
let h2 = repo.commit ( " add three" , & [ ] ) .unwrap ( ) ;
1562
let id1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) .change_id;
1563
1564
1565
write ( root, " a.txt" , b " one\n TWO!\n three\n " ) ;
1566
let h1b = repo.amend_into ( & h1, None ) .unwrap ( ) ;
1567
assert_ne! ( h1b, h1) ;
1568
let c1b = repo.store ( ) .get_change ( & h1b) .unwrap ( ) ;
1569
assert_eq! ( c1b.change_id, id1) ;
1570
assert_eq! ( c1b.deps, vec! [ h0] ) ;
1571
assert! ( matches! ( repo.verify_change ( & h1b) .unwrap ( ) , SignatureStatus:: Valid { .. } ) ) ;
1572
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
1573
assert_eq! ( heads.len ( ) , 1 ) ;
1574
let top = repo.store ( ) .get_change ( & heads[ 0 ] ) .unwrap ( ) ;
1575
assert_eq! ( top.message, " add three" ) ;
1576
assert_eq! ( top.deps.iter ( ) .copied ( ) .collect:: < BTreeSet< _ > > ( ) , [ h0, h1b] .into_iter ( ) .collect ( ) , " depends on the file's creator and the rewritten line" ) ;
1577
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
1578
assert! ( matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n TWO!\n three\n " ) ) ;
1579
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed, " the working copy is clean after folding" ) ;
1580
1581
let only = repo.load_state ( & [ h1b] ) .unwrap ( ) .materialize ( ) ;
1582
assert! ( matches! ( & only.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n TWO!\n " ) ) ;
1583
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 ) ;
1584
1585
1586
write ( root, " a.txt" , b " one\n TWO!\n three!\n " ) ;
1587
let err = repo.amend_into ( & h1b, None ) .unwrap_err ( ) .to_string ( ) ;
1588
assert! ( err.contains ( " introduced after" ) , " {err}" ) ;
1589
assert! ( repo.snapshot ( " status" ) .unwrap ( ) .statuses.len ( ) = = 1 , " nothing was changed by the refusal" ) ;
1590
write ( root, " a.txt" , b " one\n TWO!\n three\n " ) ;
1591
1592
1593
write ( root, " b.txt" , b " b\n " ) ;
1594
write ( root, " c.txt" , b " c\n " ) ;
1595
let h3 = repo.commit ( " b and c" , & [ ] ) .unwrap ( ) ;
1596
let id3 = repo.store ( ) .get_change ( & h3) .unwrap ( ) .change_id;
1597
let ( p1, p2) = repo.split ( & h3, & [ " b.txt" .into ( ) ] ) .unwrap ( ) ;
1598
let c_1 = repo.store ( ) .get_change ( & p1) .unwrap ( ) ;
1599
let c_2 = repo.store ( ) .get_change ( & p2) .unwrap ( ) ;
1600
assert! ( c_1 .message.contains ( " part 1: b.txt" ) ) ;
1601
assert_eq! ( c_2 .change_id, id3) ;
1602
let heads: BTreeSet< _ > = repo.bookmark_heads ( ) .unwrap ( ) .into_iter ( ) .collect ( ) ;
1603
assert! ( heads.contains ( & p1) & & heads.contains ( & p2) , " independent parts are both heads" ) ;
1604
let snap = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) .materialize ( ) ;
1605
assert! ( snap.files.contains_key ( " b.txt" ) & & snap.files.contains_key ( " c.txt" ) ) ;
1606
let only1 = repo.load_state ( & [ p1] ) .unwrap ( ) .materialize ( ) ;
1607
assert! ( only1.files.contains_key ( " b.txt" ) & & ! only1.files.contains_key ( " c.txt" ) ) ;
1608
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
1609
assert! ( repo.split ( & p2, & [ " nope.txt" .into ( ) ] ) .is_err ( ) ) ;
1610
let _ = h2;
1611
}
28fc23d7 Andy McCall 1 month ago ↶
1612
1613
# [ test ]
1614
fn identity_lifecycle ( ) {
1615
hermetic ( ) ;
1616
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1617
let root = dir.path ( ) ;
1618
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
1619
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
1620
let mut a = Repo:: clone( & url, & root.join ( " a" ) ) .unwrap ( ) .with_home ( root.join ( " home-a" ) ) ;
1621
a.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
1622
let ( key_a, _ ) = a.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User) .unwrap ( ) ;
1623
write ( & root.join ( " a" ) , " a.txt" , b " a\n " ) ;
1624
let h1 = a.commit ( " one" , & [ ] ) .unwrap ( ) ;
1625
assert! ( a.policy_init ( ) .unwrap ( ) .applied) ;
1626
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | r | r.ok ) ) ;
1627
1628
1629
let mut e = Repo:: init( & root.join ( " e" ) ) .unwrap ( ) .with_home ( root.join ( " home-e" ) ) ;
1630
e.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
1631
e.create_identity_with ( " Eve" , " [email protected] " , IdentityScope:: User, Some ( Hlc:: now( ) .wall_ms - 1 ) ) .unwrap ( ) ;
1632
write ( & root.join ( " e" ) , " e.txt" , b " e\n " ) ;
1633
let he = e.commit ( " late" , & [ ] ) .unwrap ( ) ;
1634
assert! ( matches! ( e.verify_change ( & he) .unwrap ( ) , SignatureStatus:: Invalid( m) if m.contains ( " expired" ) ) ) ;
1635
1636
let r = e.renew_identity ( Some ( Hlc:: now( ) .wall_ms + 86_400_000 ) ) .unwrap ( ) ;
1637
assert_eq! ( e.identity ( & r.new) .unwrap ( ) .previous, Some ( r.old) ) ;
1638
write ( & root.join ( " e" ) , " e.txt" , b " e2\n " ) ;
1639
let he2 = e.commit ( " in time" , & [ ] ) .unwrap ( ) ;
1640
assert! ( matches! ( e.verify_change ( & he2) .unwrap ( ) , SignatureStatus:: Valid { key, .. } if key = = r.new) ) ;
1641
1642
1643
let r = a.rotate_identity ( ) .unwrap ( ) ;
1644
assert_eq! ( r.old, key_a) ;
1645
assert_eq! ( a.config.author_key, Some ( r.new) ) ;
1646
assert_eq! ( a.identity ( & r.new) .unwrap ( ) .previous, Some ( key_a) ) ;
1647
assert! ( r.policy.as_ref ( ) .unwrap ( ) .applied) ;
1648
let ( _ , sp) = a.current_policy ( ) .unwrap ( ) .unwrap ( ) ;
1649
assert_eq! ( ( sp.policy.serial, sp.policy.roots.clone ( ) ) , ( 2 , vec! [ r.new] ) ) ;
1650
assert! ( ! sp.policy.revoked.contains ( & key_a) & & sp.policy.authorizes ( & r.new, " main" ) , " superseded, not revoked" ) ;
1651
assert! ( sp.policy.succeeds ( & r.new, & key_a) & & ! sp.policy.succeeds ( & key_a, & r.new) ) ;
1652
assert! ( matches! ( a.verify_change ( & h1) .unwrap ( ) , SignatureStatus:: Valid { key, revoked: false , .. } if key = = key_a) ) ;
1653
write ( & root.join ( " a" ) , " a.txt" , b " a2\n " ) ;
1654
let h2 = a.commit ( " two" , & [ ] ) .unwrap ( ) ;
1655
assert! ( matches! ( a.verify_change ( & h2) .unwrap ( ) , SignatureStatus:: Valid { key, revoked: false , .. } if key = = r.new) ) ;
1656
1657
let res = a.push ( " origin" , false ) .unwrap ( ) ;
1658
assert! ( res.iter ( ) .all ( | x | x.ok ) , " {res:?}" ) ;
1659
assert_eq! ( origin.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, 2 ) ;
1660
let key_a = r.new;
1661
1662
1663
1664
let mut b = Repo:: clone( & url, & root.join ( " b" ) ) .unwrap ( ) .with_home ( root.join ( " home-b" ) ) ;
1665
let ( key_b, id_b) = b.create_identity ( " Bob" , " [email protected] " , IdentityScope:: User) .unwrap ( ) ;
1666
let r = a.delegate_add ( id_b.clone ( ) ) .unwrap ( ) ;
1667
assert_eq! ( r.old, key_a) ;
1668
let key_a2 = r.new;
1669
assert_eq! ( a.identity ( & key_a2) .unwrap ( ) .delegates, vec! [ key_b] ) ;
1670
assert_eq! ( a.identity ( & key_a2) .unwrap ( ) .ed25519_pk, a.identity ( & key_a) .unwrap ( ) .ed25519_pk) ;
1671
assert! ( a.delegate_add ( id_b.clone ( ) ) .is_err ( ) , " already a delegate" ) ;
1672
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | x | x.ok ) ) ;
1673
write ( & root.join ( " a" ) , " a.txt" , b " a3\n " ) ;
1674
let h3 = a.commit ( " three" , & [ ] ) .unwrap ( ) ;
1675
assert! ( matches! ( a.verify_change ( & h3) .unwrap ( ) , SignatureStatus:: Valid { key, .. } if key = = key_a2) , " the same secret signs under the new KeyId" ) ;
1676
1677
a.set_user_config ( " core.sign" , " seal" ) .unwrap ( ) ;
1678
write ( & root.join ( " a" ) , " a.txt" , b " a4\n " ) ;
1679
let h4 = a.commit ( " four" , & [ ] ) .unwrap ( ) ;
1680
assert_eq! ( a.verify_change ( & h4) .unwrap ( ) , SignatureStatus:: Unsigned) ;
1681
let ( _ , sk_b) = b.secret_key ( ) .unwrap ( ) .unwrap ( ) ;
1682
a.store ( ) .put_signature ( & h4, & sk_b.sign ( Domain:: Change, & key_b, & h4) ) .unwrap ( ) ;
1683
assert! ( matches! ( a.verify_change ( & h4) .unwrap ( ) , SignatureStatus:: Valid { key, .. } if key = = key_b) ) ;
1684
a.set_user_config ( " core.sign" , " change" ) .unwrap ( ) ;
1685
1686
let r = a.delegate_remove ( & key_b) .unwrap ( ) ;
1687
assert! ( a.identity ( & r.new) .unwrap ( ) .delegates.is_empty ( ) ) ;
1688
assert! ( a.delegate_remove ( & key_b) .is_err ( ) ) ;
1689
let key_a3 = r.new;
1690
let res = a.push ( " origin" , false ) .unwrap ( ) ;
1691
assert! ( res.iter ( ) .all ( | x | x.ok ) , " {res:?}" ) ;
1692
1693
1694
1695
assert! ( a.policy_root_add ( id_b.clone ( ) ) .unwrap ( ) .applied) ;
1696
assert! ( a.policy_root_add ( id_b.clone ( ) ) .is_err ( ) ) ;
1697
assert! ( a.policy_set_threshold ( 3 ) .is_err ( ) ) ;
1698
assert! ( a.policy_set_threshold ( 2 ) .unwrap ( ) .applied) ;
1699
assert! ( a.push ( " origin" , false ) .unwrap ( ) .iter ( ) .all ( | x | x.ok ) ) ;
1700
b.pull ( " origin" ) .unwrap ( ) ;
1701
assert_eq! ( b.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.root_threshold, 2 ) ;
1702
let mut c = Repo:: init( & root.join ( " c" ) ) .unwrap ( ) .with_home ( root.join ( " home-c" ) ) ;
1703
let ( key_c, id_c) = c.create_identity ( " Cy" , " [email protected] " , IdentityScope:: User) .unwrap ( ) ;
1704
let serial_before = a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial;
1705
let proposal = a.policy_grant ( id_c, None ) .unwrap ( ) ;
1706
assert! ( ! proposal.applied & & proposal.sigs.len ( ) = = 1 ) ;
1707
assert_eq! ( proposal.policy.serial, serial_before + 1 ) ;
1708
assert_eq! ( a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, serial_before, " a proposal changes nothing yet" ) ;
1709
1710
let bytes = proposal.signed ( ) .encode ( ) ;
1711
let signed = b.policy_sign ( SignedPolicy:: decode( & bytes) .unwrap ( ) ) .unwrap ( ) ;
1712
assert! ( signed.applied & & signed.sigs.len ( ) = = 2 ) ;
1713
assert! ( b.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.authorizes ( & key_c, " main" ) ) ;
1714
assert! ( b.policy_sign ( signed.signed ( ) ) .is_err ( ) , " a stale proposal is refused" ) ;
1715
1716
assert! ( b.push ( " origin" , false ) .unwrap ( ) .iter ( ) .any ( | x | x.name = = " policy" & & x.ok ) ) ;
1717
a.pull ( " origin" ) .unwrap ( ) ;
1718
assert_eq! ( a.current_policy ( ) .unwrap ( ) .unwrap ( ) .1. policy.serial, serial_before + 1 ) ;
1719
1720
assert! ( a.policy_revoke ( & key_a3) .is_err ( ) ) ;
1721
let u = a.revoke_identity ( & key_c, " left the project" ) .unwrap ( ) .unwrap ( ) ;
1722
assert! ( ! u.applied) ;
1723
assert! ( a.is_revoked ( & key_c) .unwrap ( ) ) ;
1724
assert! ( ! b.is_revoked ( & key_c) .unwrap ( ) ) ;
1725
let u = b.policy_sign ( u.signed ( ) ) .unwrap ( ) ;
1726
assert! ( u.applied & & u.policy.is_revoked ( & key_c) ) ;
1727
assert! ( b.is_revoked ( & key_c) .unwrap ( ) ) ;
1728
1729
assert! ( b.policy_revoke ( & key_a3) .is_err ( ) , " would leave one root for a threshold of 2" ) ;
1730
1731
let key_c_own = c.config.author_key.unwrap ( ) ;
1732
assert! ( c.revoke_identity ( & key_c_own, " lost laptop" ) .unwrap ( ) .is_none ( ) ) ;
1733
assert! ( c.is_revoked ( & key_c_own) .unwrap ( ) ) ;
1734
assert_eq! ( key_c_own, key_c) ;
1735
}
1736
6e723b81 Andy McCall 1 month ago ↶
1737
# [ test ]
1738
fn renames_are_detected_and_tracked_as_moves ( ) {
1739
hermetic ( ) ;
1740
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1741
let root = dir.path ( ) ;
1742
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
1743
write ( root, " src/old.rs" , b " fn a() {}\n fn b() {}\n fn c() {}\n fn d() {}\n " ) ;
1744
write ( root, " img.bin" , b " \x89 PNG\0 \0 \x01 \x02 " ) ;
1745
write ( root, " tiny.txt" , b " x\n " ) ;
1746
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
1747
let node_old = repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) .files[ " src/old.rs" ] .node;
1748
let node_img = repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) .files[ " img.bin" ] .node;
1749
1750
1751
1752
fs:: remove_file( root.join ( " src/old.rs" ) ) .unwrap ( ) ;
1753
write ( root, " lib/new.rs" , b " fn a() {}\n fn b() {}\n fn c() {}\n fn e() {}\n " ) ;
1754
fs:: remove_file( root.join ( " img.bin" ) ) .unwrap ( ) ;
1755
write ( root, " assets/img.bin" , b " \x89 PNG\0 \0 \x01 \x02 " ) ;
1756
fs:: remove_file( root.join ( " tiny.txt" ) ) .unwrap ( ) ;
1757
write ( root, " other.txt" , b " completely different\n " ) ;
1758
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
1759
let statuses: Vec< ( & str , ChangeStatus) > = snap.statuses.iter ( ) .map ( | s | ( s.path.as_str ( ) , s.status) ) .collect ( ) ;
1760
assert_eq! (
1761
statuses,
1762
vec! [ ( " assets/img.bin" , ChangeStatus:: Renamed) , ( " lib/new.rs" , ChangeStatus:: Renamed) , ( " other.txt" , ChangeStatus:: Added) , ( " src" , ChangeStatus:: Removed) , ( " tiny.txt" , ChangeStatus:: Removed) ]
1763
) ;
1764
let h2 = repo.commit ( " move things" , & [ ] ) .unwrap ( ) ;
1765
let c = repo.store ( ) .get_change ( & h2) .unwrap ( ) ;
1766
let moves: Vec< _ > = c.tree_ops.iter ( ) .filter ( | op | matches! ( op, TreeOp:: Move { .. } ) ) .collect ( ) ;
1767
assert_eq! ( moves.len ( ) , 2 , " {:?}" , c.tree_ops) ;
1768
assert! ( c.tree_ops.iter ( ) .any ( | op | matches! ( op, TreeOp:: Move { node, name, .. } if * node = = node_old & & name = = " new.rs" ) ) ) ;
1769
assert! ( c.tree_ops.iter ( ) .any ( | op | matches! ( op, TreeOp:: Move { node, name, .. } if * node = = node_img & & name = = " img.bin" ) ) ) ;
1770
let snap = repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) ;
1771
assert_eq! ( snap.files[ " lib/new.rs" ] .node, node_old, " the node survives the rename" ) ;
1772
assert! ( 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 " ) ) ;
1773
assert! ( ! snap.files.contains_key ( " src/old.rs" ) & & ! snap.files.contains_key ( " src" ) ) ;
1774
assert! ( render_change ( & c, & repo.load_state ( & [ h1] ) .unwrap ( ) , & repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) ) .contains ( " R " ) ) ;
1775
1776
let other = tempfile:: tempdir( ) .unwrap ( ) ;
1777
let clone = Repo:: clone( & root.to_string_lossy ( ) , other.path ( ) ) .unwrap ( ) ;
1778
assert_eq! ( fs:: read( other.path ( ) .join ( " lib/new.rs" ) ) .unwrap ( ) , b " fn a() {}\n fn b() {}\n fn c() {}\n fn e() {}\n " ) ;
1779
assert! ( ! other.path ( ) .join ( " src" ) .exists ( ) ) ;
1780
assert! ( ! clone.snapshot ( " status" ) .unwrap ( ) .changed) ;
1781
1782
assert_eq! ( similarity ( b " a\n b\n " , b " a\n b\n " ) , 100 ) ;
1783
assert_eq! ( similarity ( b " a\n b\n c\n d\n " , b " a\n b\n x\n y\n " ) , 50 ) ;
1784
assert_eq! ( similarity ( b " hello\n " , b " world\n " ) , 0 ) ;
1785
assert_eq! ( similarity ( b " " , b " x\n " ) , 0 ) ;
1786
}
1787
30ea9c1e Andy McCall 1 month ago ↶
1788
# [ test ]
1789
fn git_fetch_and_push_round_trip ( ) {
1790
hermetic ( ) ;
1791
if std:: process:: Command:: new( " git" ) .arg ( " --version" ) .output ( ) .is_err ( ) {
1792
eprintln! ( " git not installed; skipping" ) ;
1793
return ;
1794
}
1795
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1796
let root = dir.path ( ) ;
1797
let git_in = | d : & Path, args : & [& str ]| -> String {
1798
let out = std:: process:: Command:: new( " git" )
1799
.arg ( " -C" )
1800
.arg ( d)
1801
.args ( [ " -c" , " user.name=Grace" , " -c" , " [email protected] " , " -c" , " commit.gpgsign=false" ] )
1802
.args ( args)
1803
.output ( )
1804
.unwrap ( ) ;
1805
assert! ( out.status.success ( ) , " git {args:?}: {}" , String :: from_utf8_lossy( & out.stderr) ) ;
1806
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
1807
} ;
1808
1809
let remote = root.join ( " remote.git" ) ;
1810
fs:: create_dir_all( & remote) .unwrap ( ) ;
1811
git_in ( & remote, & [ " init" , " -q" , " --bare" , " -b" , " main" ] ) ;
1812
let work = root.join ( " work" ) ;
1813
git_in ( root, & [ " clone" , " -q" , remote.to_str ( ) .unwrap ( ) , work.to_str ( ) .unwrap ( ) ] ) ;
1814
git_in ( & work, & [ " checkout" , " -q" , " -b" , " main" ] ) ;
1815
write ( & work, " README.md" , b " # Project\n " ) ;
1816
write ( & work, " src/lib.rs" , b " pub fn a() {}\n " ) ;
1817
git_in ( & work, & [ " add" , " ." ] ) ;
1818
git_in ( & work, & [ " commit" , " -q" , " -m" , " initial" ] ) ;
1819
write ( & work, " src/lib.rs" , b " pub fn a() {}\n pub fn b() {}\n " ) ;
1820
git_in ( & work, & [ " commit" , " -q" , " -am" , " second" ] ) ;
1821
git_in ( & work, & [ " push" , " -q" , " origin" , " main" ] ) ;
1822
let tip1 = git_in ( & work, & [ " rev-parse" , " HEAD" ] ) ;
1823
1824
1825
let o = root.join ( " oxy" ) ;
1826
let mut repo = Repo:: init( & o) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
1827
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User) .unwrap ( ) ;
1828
let url = remote.to_string_lossy ( ) .to_string ( ) ;
1829
let r = repo.git_fetch ( & url, " main" ) .unwrap ( ) ;
1830
assert_eq! ( ( r.commits, r.heads) , ( 2 , 1 ) ) ;
1831
assert_eq! ( fs:: read( o.join ( " src/lib.rs" ) ) .unwrap ( ) , b " pub fn a() {}\n pub fn b() {}\n " ) ;
1832
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
1833
assert_eq! ( repo.git_fetch ( & url, " main" ) .unwrap ( ) .commits, 0 , " nothing new" ) ;
1834
1835
1836
write ( & o, " src/lib.rs" , b " pub fn a() {}\n pub fn b() {}\n pub fn c() {}\n " ) ;
1837
write ( & o, " NOTES.md" , b " from oxy\n " ) ;
1838
let h_oxy = repo.commit ( " add c and notes" , & [ ] ) .unwrap ( ) ;
1839
let r = repo.git_push ( & url, " main" ) .unwrap ( ) ;
1840
assert_eq! ( r.exported, 1 , " {r:?}" ) ;
1841
git_in ( & work, & [ " pull" , " -q" , " --ff-only" , " origin" , " main" ] ) ;
1842
assert_eq! ( git_in ( & work, & [ " log" , " --format=%s" , " main" ] ) , " add c and notes\n second\n initial" ) ;
1843
assert_eq! ( git_in ( & work, & [ " rev-parse" , " HEAD~1" ] ) , tip1) ;
1844
assert_eq! ( fs:: read( work.join ( " NOTES.md" ) ) .unwrap ( ) , b " from oxy\n " ) ;
1845
assert_eq! ( git_in ( & work, & [ " log" , " -1" , " --format=%an" , " main" ] ) , " Ada" ) ;
1846
assert! ( git_in ( & work, & [ " log" , " -1" , " --format=%B" , " main" ] ) .contains ( & format! ( " Oxy-Hash: {} " , h_oxy.to_hex ( ) ) ) ) ;
1847
assert_eq! ( repo.git_push ( & url, " main" ) .unwrap ( ) .exported, 0 , " already pushed" ) ;
1848
1849
1850
1851
write ( & work, " README.md" , b " # Project\n \n More.\n " ) ;
1852
git_in ( & work, & [ " commit" , " -q" , " -am" , " readme from git" ] ) ;
1853
git_in ( & work, & [ " push" , " -q" , " origin" , " main" ] ) ;
1854
let tip2 = git_in ( & work, & [ " rev-parse" , " HEAD" ] ) ;
1855
assert! ( repo.git_push ( & url, " main" ) .unwrap_err ( ) .to_string ( ) .contains ( " fetch" ) ) ;
1856
let r = repo.git_fetch ( & url, " main" ) .unwrap ( ) ;
1857
assert_eq! ( ( r.commits, r.heads) , ( 1 , 2 ) , " the Git change and the oxy change commute" ) ;
1858
assert_eq! ( fs:: read( o.join ( " README.md" ) ) .unwrap ( ) , b " # Project\n \n More.\n " ) ;
1859
assert_eq! ( fs:: read( o.join ( " NOTES.md" ) ) .unwrap ( ) , b " from oxy\n " ) ;
1860
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
1861
1862
write ( & o, " NOTES.md" , b " from oxy\n again\n " ) ;
1863
repo.commit ( " notes again" , & [ ] ) .unwrap ( ) ;
1864
assert_eq! ( repo.git_push ( & url, " main" ) .unwrap ( ) .exported, 1 ) ;
1865
git_in ( & work, & [ " pull" , " -q" , " --ff-only" , " origin" , " main" ] ) ;
1866
assert_eq! ( git_in ( & work, & [ " rev-parse" , " HEAD~1" ] ) , tip2) ;
1867
assert_eq! ( fs:: read( work.join ( " NOTES.md" ) ) .unwrap ( ) , b " from oxy\n again\n " ) ;
1868
assert_eq! ( git_in ( & work, & [ " log" , " --format=%s" , " main" ] ) .lines ( ) .count ( ) , 5 ) ;
1869
}
1870
2b872d07 Andy McCall 1 month ago ↶
1871
1872
1873
# [ test ]
1874
# [ ignore ]
1875
fn bench_one_gibibyte_file ( ) {
1876
hermetic ( ) ;
1877
1878
let dir = match std:: env:: var_os( " OXY_BENCH_DIR" ) {
1879
Some ( d) => tempfile:: tempdir_in( d) .unwrap ( ) ,
1880
None => tempfile:: tempdir( ) .unwrap ( ) ,
1881
} ;
1882
let root = dir.path ( ) ;
1883
let repo = Repo:: init( & root.join ( " r" ) ) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
1884
let size = 1 usize < < 30 ;
1885
let mut data = vec! [ 0 u8 ; size] ;
1886
let mut x = 0x9e3779b97f4a7c15 u64 ;
1887
for chunk in data.chunks_mut ( 8 ) {
1888
x ^ = x < < 13 ;
1889
x ^ = x > > 7 ;
1890
x ^ = x < < 17 ;
1891
let b = x.to_le_bytes ( ) ;
1892
chunk.copy_from_slice ( & b[ .. chunk.len ( ) ] ) ;
1893
}
1894
let t = std:: time:: Instant:: now( ) ;
1895
write ( & root.join ( " r" ) , " big.bin" , & data) ;
1896
eprintln! ( " write 1 GiB to disk: {:.2?}" , t.elapsed ( ) ) ;
1897
let t = std:: time:: Instant:: now( ) ;
1898
let h = repo.commit ( " big" , & [ ] ) .unwrap ( ) ;
1899
eprintln! ( " commit (chunk + hash + store): {:.2?}" , t.elapsed ( ) ) ;
1900
let t = std:: time:: Instant:: now( ) ;
1901
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
1902
eprintln! ( " status on a clean 1 GiB working copy: {:.2?}" , t.elapsed ( ) ) ;
1903
let t = std:: time:: Instant:: now( ) ;
1904
let objects = repo.store ( ) .list ( ) .unwrap ( ) .len ( ) ;
1905
eprintln! ( " objects stored: {objects} ({:.2?} to list)" , t.elapsed ( ) ) ;
1906
let t = std:: time:: Instant:: now( ) ;
1907
let g = repo.gc ( true ) .unwrap ( ) ;
1908
eprintln! ( " gc (pack): {:.2?} {g:?}" , t.elapsed ( ) ) ;
1909
let t = std:: time:: Instant:: now( ) ;
1910
let clone = Repo:: clone( & root.join ( " r" ) .to_string_lossy ( ) , & root.join ( " c" ) ) .unwrap ( ) ;
1911
eprintln! ( " clone (fetch + checkout): {:.2?}" , t.elapsed ( ) ) ;
1912
let t = std:: time:: Instant:: now( ) ;
1913
let back = fs:: read( root.join ( " c/big.bin" ) ) .unwrap ( ) ;
1914
assert_eq! ( blake3:: hash( & back) , blake3:: hash( & data) ) ;
1915
eprintln! ( " read back and verify: {:.2?}" , t.elapsed ( ) ) ;
1916
assert_eq! ( clone.bookmark_heads ( ) .unwrap ( ) , vec! [ h] ) ;
1917
let store_bytes: u64 = walkdir_size ( & root.join ( " r/.oxy/objects" ) ) ;
1918
eprintln! ( " store size: {:.1} MiB for 1 GiB of data" , store_bytes as f64 / ( 1024. 0 * 1024. 0 ) ) ;
1919
}
1920
1921
fn walkdir_size ( p : & Path) -> u64 {
1922
let mut total = 0 ;
1923
if let Ok ( rd) = fs:: read_dir( p) {
1924
for e in rd.flatten ( ) {
1925
let path = e.path ( ) ;
1926
if path.is_dir ( ) {
1927
total + = walkdir_size ( & path) ;
1928
} else if let Ok ( m) = fs:: metadata( & path) {
1929
total + = m.len ( ) ;
1930
}
1931
}
1932
}
1933
total
1934
}
1935
b7f102fc Andy McCall 1 month ago ↶
1936
1937
1938
# [ cfg ( unix ) ]
1939
# [ test ]
1940
fn stat_cache_skips_reading_unchanged_opaque_files ( ) {
1941
use std:: os:: unix:: fs:: PermissionsExt;
1942
hermetic ( ) ;
1943
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1944
let root = dir.path ( ) ;
1945
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
1946
let mut big = vec! [ 0 u8 ; 300 * 1024 ] ;
1947
for ( i, b) in big.iter_mut ( ) .enumerate ( ) {
1948
* b = ( i as u32 ) .wrapping_mul ( 2654435761 ) .to_le_bytes ( ) [ 1 ] ;
1949
}
1950
big[ 0 ] = 0 ;
1951
write ( root, " big.bin" , & big) ;
1952
write ( root, " a.txt" , b " text\n " ) ;
1953
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
1954
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
1955
assert! ( m.entries[ " big.bin" ] .blob.is_some ( ) , " opaque files record their blob" ) ;
1956
assert! ( m.entries[ " a.txt" ] .blob.is_none ( ) ) ;
1957
let blob = m.entries[ " big.bin" ] .blob.unwrap ( ) ;
1958
assert! ( repo.store ( ) .has ( & blob) ) ;
1959
1960
1961
1962
let old = std:: time:: SystemTime:: now( ) - std:: time:: Duration:: from_secs( 30 ) ;
1963
fs:: File:: options( ) .write ( true ) .open ( root.join ( " big.bin" ) ) .unwrap ( ) .set_modified ( old) .unwrap ( ) ;
1964
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
1965
1966
fs:: set_permissions( root.join ( " big.bin" ) , fs:: Permissions:: from_mode( 0o000 ) ) .unwrap ( ) ;
1967
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
1968
assert! ( ! snap.changed & & snap.statuses.is_empty ( ) , " {:?}" , snap.statuses) ;
1969
fs:: set_permissions( root.join ( " big.bin" ) , fs:: Permissions:: from_mode( 0o644 ) ) .unwrap ( ) ;
1970
1971
let mut big2 = big.clone ( ) ;
1972
big2[ 10 ] ^ = 0xff ;
1973
write ( root, " big.bin" , & big2) ;
1974
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
1975
assert_eq! ( snap.statuses.iter ( ) .map ( | s | ( s.path.as_str ( ) , s.status) ) .collect:: < Vec< _ > > ( ) , vec! [ ( " big.bin" , ChangeStatus:: Modified) ] ) ;
1976
let h2 = repo.commit ( " edit" , & [ ] ) .unwrap ( ) ;
1977
let files = repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) .files;
1978
assert_eq! ( files[ " big.bin" ] .content, FileContent:: Opaque { blobs: vec! [ Repo:: blob_ref( & big2) ] } ) ;
1979
1980
repo.checkout ( & [ h1] , false , " checkout" ) .unwrap ( ) ;
1981
let ( _ , _ , m) = repo.read_manifest ( ) .unwrap ( ) .unwrap ( ) ;
1982
assert_eq! ( m.entries[ " big.bin" ] .blob, Some ( Repo:: blob_ref( & big) ) ) ;
1983
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
1984
}
1985
50a046e4 Andy McCall 1 month ago ↶
1986
# [ test ]
1987
fn status_json_lists_conflicts_with_contributors ( ) {
1988
hermetic ( ) ;
1989
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1990
let root = dir.path ( ) ;
1991
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
1992
write ( root, " a.txt" , b " one\n " ) ;
1993
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
1994
write ( root, " a.txt" , b " one\n left\n " ) ;
1995
let ha = repo.commit ( " left" , & [ ] ) .unwrap ( ) ;
1996
repo.checkout ( & [ h1] , true , " checkout" ) .unwrap ( ) ;
1997
write ( root, " a.txt" , b " one\n right\n " ) ;
1998
let hb = repo.commit ( " right" , & [ ] ) .unwrap ( ) ;
1999
repo.checkout ( & [ ha, hb] , true , " checkout" ) .unwrap ( ) ;
2000
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
2001
assert! ( snap.base_snapshot.is_conflicted ( ) ) ;
2002
let json = repo.status_json ( & snap) .unwrap ( ) ;
2003
let ida = repo.store ( ) .get_change ( & ha) .unwrap ( ) .change_id.to_hex ( ) ;
2004
let idb = repo.store ( ) .get_change ( & hb) .unwrap ( ) .change_id.to_hex ( ) ;
2005
assert! ( json.contains ( " \" conflicted\" :true" ) , " {json}" ) ;
2006
assert! ( json.contains ( " \" kind\" :\" text\" ,\" path\" :\" a.txt\" " ) , " {json}" ) ;
2007
assert! ( json.contains ( & ida) & & json.contains ( & idb) , " {json}" ) ;
2008
assert! ( json.contains ( " \" resolved\" :false" ) , " {json}" ) ;
2009
assert! ( json.starts_with ( ' {' ) & & json.ends_with ( ' }' ) ) ;
2010
2011
write ( root, " a.txt" , b " one\n left\n right\n " ) ;
2012
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
2013
let json = repo.status_json ( & snap) .unwrap ( ) ;
2014
assert! ( json.contains ( " \" resolved\" :true" ) , " {json}" ) ;
2015
repo.commit ( " resolve" , & [ ] ) .unwrap ( ) ;
2016
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
2017
let json = repo.status_json ( & snap) .unwrap ( ) ;
2018
assert! ( json.contains ( " \" clean\" :true,\" conflicted\" :false,\" paths\" :[],\" conflicts\" :[]" ) , " {json}" ) ;
2019
}
2020
9ac1fbe2 Andy McCall 1 month ago ↶
2021
# [ test ]
2022
fn ignore_rules_do_not_untrack_files ( ) {
2023
hermetic ( ) ;
2024
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2025
let root = dir.path ( ) ;
2026
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2027
write ( root, " src.c" , b " int x;\n " ) ;
2028
write ( root, " build/out.o" , b " \0 obj" ) ;
2029
write ( root, " sub/keep.log" , b " keep\n " ) ;
2030
write ( root, " sub/junk.log" , b " junk\n " ) ;
2031
write ( root, " .oxyignore" , b " /build\n *.log\n !sub/keep.log\n " ) ;
2032
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
2033
let added: Vec< & str > = snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect ( ) ;
2034
assert_eq! ( added, vec! [ " .oxyignore" , " src.c" , " sub/keep.log" ] , " anchored, glob, and negated rules" ) ;
2035
let h1 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
2036
2037
write ( root, " .oxyignore" , b " /build\n *.log\n !sub/keep.log\n src.c\n " ) ;
2038
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
2039
assert_eq! ( snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect:: < Vec< _ > > ( ) , vec! [ " .oxyignore" ] ) ;
2040
let h2 = repo.commit ( " ignore src.c" , & [ ] ) .unwrap ( ) ;
2041
assert! ( repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) .files.contains_key ( " src.c" ) ) ;
2042
2043
assert_eq! ( repo.quick_dirty ( ) .unwrap ( ) , Some ( false ) ) ;
2044
write ( root, " src.c" , b " int x, y;\n " ) ;
2045
assert_eq! ( repo.quick_dirty ( ) .unwrap ( ) , Some ( true ) ) ;
2046
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
2047
assert_eq! ( snap.statuses.iter ( ) .map ( | s | ( s.path.as_str ( ) , s.status) ) .collect:: < Vec< _ > > ( ) , vec! [ ( " src.c" , ChangeStatus:: Modified) ] ) ;
2048
let h3 = repo.commit ( " edit src.c" , & [ ] ) .unwrap ( ) ;
2049
assert! ( matches! ( & repo.load_state ( & [ h3] ) .unwrap ( ) .materialize ( ) .files[ " src.c" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " int x, y;\n " ) ) ;
2050
2051
fs:: remove_file( root.join ( " src.c" ) ) .unwrap ( ) ;
2052
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
2053
assert_eq! ( snap.statuses.iter ( ) .map ( | s | ( s.path.as_str ( ) , s.status) ) .collect:: < Vec< _ > > ( ) , vec! [ ( " src.c" , ChangeStatus:: Removed) ] ) ;
2054
repo.checkout ( & [ h3] , true , " checkout" ) .unwrap ( ) ;
2055
assert_eq! ( fs:: read( root.join ( " src.c" ) ) .unwrap ( ) , b " int x, y;\n " ) ;
2056
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
2057
let _ = h1;
2058
}
2059
b837d2b5 Andy McCall 1 month ago ↶
2060
# [ test ]
2061
fn split_works_on_non_heads_by_node ( ) {
2062
hermetic ( ) ;
2063
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2064
let root = dir.path ( ) ;
2065
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2066
write ( root, " a.txt" , b " one\n " ) ;
2067
write ( root, " junk/x.lock" , b " lock\n " ) ;
2068
let h1 = repo.commit ( " work plus junk" , & [ ] ) .unwrap ( ) ;
2069
write ( root, " a.txt" , b " one\n two\n " ) ;
2070
let h2 = repo.commit ( " more work" , & [ ] ) .unwrap ( ) ;
2071
assert_eq! ( repo.store ( ) .get_change ( & h2) .unwrap ( ) .deps, vec! [ h1] ) ;
2072
let id1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) .change_id;
2073
2074
let ( hj, h1b) = repo.split ( & h1, & [ " junk" .into ( ) ] ) .unwrap ( ) ;
2075
assert_ne! ( h1b, h1) ;
2076
assert_eq! ( repo.store ( ) .get_change ( & h1b) .unwrap ( ) .change_id, id1, " the rest keeps the ChangeId" ) ;
2077
let junk = repo.store ( ) .get_change ( & hj) .unwrap ( ) ;
2078
assert! ( junk.tree_ops.iter ( ) .all ( | op | matches! ( op, TreeOp:: AddNode { name, .. } if name = = " junk" | | name = = " x.lock" ) ) ) ;
2079
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
2080
assert! ( heads.contains ( & hj) , " the split-off part is a head of its own" ) ;
2081
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
2082
assert! ( matches! ( & snap.files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " one\n two\n " ) , " the dependent was rewritten onto the new hash" ) ;
2083
assert! ( snap.files.contains_key ( " junk/x.lock" ) ) ;
2084
let h2b = heads.iter ( ) .find ( | h | repo.store ( ) .get_change ( h) .unwrap ( ) .message = = " more work" ) .copied ( ) .unwrap ( ) ;
2085
assert_eq! ( repo.store ( ) .get_change ( & h2b) .unwrap ( ) .deps, vec! [ h1b] ) ;
2086
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
2087
2088
repo.abandon ( & hj) .unwrap ( ) ;
2089
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
2090
let snap = repo.load_state ( & heads) .unwrap ( ) .materialize ( ) ;
2091
assert! ( ! snap.files.contains_key ( " junk/x.lock" ) & & ! snap.files.contains_key ( " junk" ) ) ;
2092
assert! ( snap.files.contains_key ( " a.txt" ) ) ;
2093
2094
write ( root, " junk/y.lock" , b " y\n " ) ;
2095
let hb = repo.commit ( " junk base" , & [ ] ) .unwrap ( ) ;
2096
write ( root, " junk/y.lock" , b " yy\n " ) ;
2097
write ( root, " b.txt" , b " b\n " ) ;
2098
repo.commit ( " touches junk" , & [ ] ) .unwrap ( ) ;
2099
let err = repo.split ( & hb, & [ " junk" .into ( ) ] ) .unwrap_err ( ) .to_string ( ) ;
2100
assert! ( err.contains ( " depends on the split-off paths" ) , " {err}" ) ;
2101
}
2102
7b9809e6 Andy McCall 1 month ago ↶
2103
# [ test ]
2104
fn bookmarks_switch_merge_and_push ( ) {
2105
hermetic ( ) ;
2106
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2107
let root = dir.path ( ) ;
2108
let origin = Repo:: init( & root.join ( " origin" ) ) .unwrap ( ) ;
2109
let url = root.join ( " origin" ) .to_string_lossy ( ) .to_string ( ) ;
2110
let mut repo = Repo:: clone( & url, & root.join ( " r" ) ) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2111
let r = root.join ( " r" ) ;
2112
write ( & r, " a.txt" , b " a\n " ) ;
2113
let h_a = repo.commit ( " a" , & [ ] ) .unwrap ( ) ;
2114
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
2115
2116
2117
assert! ( repo.bookmark_create ( " bad name.." , None ) .is_err ( ) ) ;
2118
assert! ( repo.bookmark_create ( " -x" , None ) .is_err ( ) ) ;
2119
assert_eq! ( repo.bookmark_create ( " feature" , None ) .unwrap ( ) , vec! [ h_a] ) ;
2120
assert! ( repo.bookmark_create ( " feature" , None ) .is_err ( ) , " exists" ) ;
2121
repo.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
2122
assert_eq! ( repo.config.bookmark, " feature" ) ;
2123
write ( & r, " a.txt" , b " a\n b\n " ) ;
2124
write ( & r, " b.txt" , b " b\n " ) ;
2125
let h_b = repo.commit ( " b on feature" , & [ ] ) .unwrap ( ) ;
2126
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ h_b] , " b edits a.txt, so it depends on a" ) ;
2127
assert_eq! ( repo.resolve_heads ( " main" ) .unwrap ( ) , vec! [ h_a] , " main is untouched" ) ;
2128
assert_eq! ( repo.resolve_change ( " feature" ) .unwrap ( ) , h_b, " a one-head bookmark names its change" ) ;
2129
2130
let reopened = Repo:: open( & r) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2131
assert_eq! ( reopened.config.bookmark, " feature" ) ;
2132
2133
repo.bookmark_switch ( " main" , false ) .unwrap ( ) ;
2134
assert! ( ! r.join ( " b.txt" ) .exists ( ) ) ;
2135
assert_eq! ( fs:: read( r.join ( " a.txt" ) ) .unwrap ( ) , b " a\n " ) ;
2136
write ( & r, " c.txt" , b " c\n " ) ;
2137
let h_c = repo.commit ( " c on main" , & [ ] ) .unwrap ( ) ;
2138
let list = repo.bookmarks ( ) .unwrap ( ) ;
2139
assert_eq! ( list.iter ( ) .map ( | b | ( b.name.as_str ( ) , b.current) ) .collect:: < Vec< _ > > ( ) , vec! [ ( " feature" , false ) , ( " main" , true ) ] ) ;
2140
assert_eq! ( list.iter ( ) .find ( | b | b.name = = " main" ) .unwrap ( ) .ahead, Some ( 1 ) , " c is not on origin yet" ) ;
2141
2142
let m = repo.merge ( " feature" ) .unwrap ( ) ;
2143
assert! ( m.changed & & m.checked_out) ;
2144
let mut want = vec! [ h_b, h_c] ;
2145
want.sort ( ) ;
2146
assert_eq! ( m.heads, want) ;
2147
assert_eq! ( fs:: read( r.join ( " b.txt" ) ) .unwrap ( ) , b " b\n " ) ;
2148
assert! ( ! repo.merge ( " feature" ) .unwrap ( ) .changed, " already merged" ) ;
2149
assert! ( repo.merge ( " main" ) .is_err ( ) ) ;
2150
2151
assert! ( repo.push_bookmark ( " origin" , " feature" , false ) .unwrap ( ) [ 0 ] .ok) ;
2152
assert_eq! ( origin.resolve_heads ( " feature" ) .unwrap ( ) , vec! [ h_b] ) ;
2153
assert! ( repo.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
2154
assert_eq! ( repo.bookmarks ( ) .unwrap ( ) .iter ( ) .find ( | b | b.name = = " main" ) .unwrap ( ) .ahead, Some ( 0 ) ) ;
2155
2156
let ws = repo.workspace_add ( & root.join ( " ws" ) , None ) .unwrap ( ) ;
2157
assert_eq! ( ws.config.bookmark, " main" ) ;
2158
let mut ws = ws;
2159
ws.bookmark_switch ( " feature" , false ) .unwrap ( ) ;
2160
assert_eq! ( Repo:: open( & r) .unwrap ( ) .config.bookmark, " main" , " the first workspace is unaffected" ) ;
2161
assert! ( repo.bookmark_delete ( " feature" ) .is_err ( ) , " current bookmark of another workspace" ) ;
2162
ws.bookmark_switch ( " main" , false ) .unwrap ( ) ;
2163
2164
assert_eq! ( repo.bookmark_set ( " feature" , & [ h_a] ) .unwrap ( ) , vec! [ h_a] ) ;
2165
assert! ( repo.bookmark_delete ( " main" ) .is_err ( ) , " current here" ) ;
2166
repo.bookmark_delete ( " feature" ) .unwrap ( ) ;
2167
assert! ( repo.resolve_heads ( " feature" ) .is_err ( ) ) ;
2168
assert! ( repo.bookmark_set ( " feature" , & [ h_a] ) .is_err ( ) ) ;
2169
assert! ( repo.checkout ( & [ h_a] , false , " checkout" ) .is_ok ( ) , " changes of a deleted bookmark remain" ) ;
2170
}
2171
4dee277c Andy McCall 1 month ago ↶
2172
# [ test ]
2173
fn oxygen_subrepos_pin_follow_and_update ( ) {
2174
hermetic ( ) ;
2175
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2176
let root = dir.path ( ) ;
2177
2178
let lib_origin = Repo:: init( & root.join ( " lib-origin" ) ) .unwrap ( ) ;
2179
let lib_url = root.join ( " lib-origin" ) .to_string_lossy ( ) .to_string ( ) ;
2180
let lib = Repo:: clone( & lib_url, & root.join ( " lib" ) ) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2181
write ( & root.join ( " lib" ) , " lib.txt" , b " v1\n " ) ;
2182
let l1 = lib.commit ( " lib v1" , & [ ] ) .unwrap ( ) ;
2183
assert! ( lib.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
2184
let _ = lib_origin;
2185
2186
2187
let app_origin = Repo:: init( & root.join ( " app-origin" ) ) .unwrap ( ) ;
2188
let app_url = root.join ( " app-origin" ) .to_string_lossy ( ) .to_string ( ) ;
2189
let _ = app_origin;
2190
let app_dir = root.join ( " app" ) ;
2191
let app = Repo:: clone( & app_url, & app_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2192
write ( & app_dir, " main.txt" , b " app\n " ) ;
2193
let pin = app.submodule_add ( & lib_url, " libs/lib" , None ) .unwrap ( ) ;
2194
assert! ( matches! ( & pin, crate :: subrepo:: Pin:: Oxy { heads, .. } if heads = = & vec! [ l1] ) ) ;
2195
assert! ( app_dir.join ( " .oxymodules" ) .exists ( ) & & app_dir.join ( " libs/lib/.oxy" ) .is_dir ( ) ) ;
2196
let snap = app.snapshot ( " status" ) .unwrap ( ) ;
2197
let paths: Vec< & str > = snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect ( ) ;
2198
assert_eq! ( paths, vec! [ " .oxymodules" , " libs/lib" , " main.txt" ] , " the nested files are not walked" ) ;
2199
let a1 = app.commit ( " app with lib" , & [ ] ) .unwrap ( ) ;
2200
let files = app.load_state ( & [ a1] ) .unwrap ( ) .materialize ( ) .files;
2201
assert_eq! ( files[ " libs/lib" ] .kind, NodeKind:: Subrepo) ;
2202
assert! ( ! files.contains_key ( " libs/lib/lib.txt" ) ) ;
2203
assert! ( ! app.snapshot ( " status" ) .unwrap ( ) .changed) ;
2204
let subs = app.subrepos ( ) .unwrap ( ) ;
2205
assert_eq! ( subs.len ( ) , 1 ) ;
2206
assert_eq! ( subs[ 0 ] .actual.as_ref ( ) , Some ( & subs[ 0 ] .pin) ) ;
2207
2208
2209
let nested = Repo:: open( & app_dir.join ( " libs/lib" ) ) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2210
write ( & app_dir.join ( " libs/lib" ) , " lib.txt" , b " v2\n " ) ;
2211
let l2 = nested.commit ( " lib v2" , & [ ] ) .unwrap ( ) ;
2212
assert! ( nested.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
2213
let snap = app.snapshot ( " status" ) .unwrap ( ) ;
2214
assert_eq! ( snap.statuses.iter ( ) .map ( | s | ( s.path.as_str ( ) , s.status) ) .collect:: < Vec< _ > > ( ) , vec! [ ( " libs/lib" , ChangeStatus:: Modified) ] ) ;
2215
let a2 = app.commit ( " bump lib" , & [ ] ) .unwrap ( ) ;
2216
2217
let r = app.checkout ( & [ a1] , false , " checkout" ) .unwrap ( ) ;
2218
assert! ( r.notes.is_empty ( ) , " {:?}" , r.notes) ;
2219
assert_eq! ( fs:: read( app_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v1\n " ) ;
2220
assert_eq! ( Repo:: open( & app_dir.join ( " libs/lib" ) ) .unwrap ( ) .bookmark_heads ( ) .unwrap ( ) , vec! [ l1] ) ;
2221
assert! ( ! app.snapshot ( " status" ) .unwrap ( ) .changed) ;
2222
2223
write ( & app_dir.join ( " libs/lib" ) , " scratch.txt" , b " wip\n " ) ;
2224
let r = app.checkout ( & [ a2] , false , " checkout" ) .unwrap ( ) ;
2225
assert! ( r.notes.iter ( ) .any ( | n | n.contains ( " uncommitted" ) ) , " {:?}" , r.notes) ;
2226
assert_eq! ( fs:: read( app_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v1\n " ) ;
2227
fs:: remove_file( app_dir.join ( " libs/lib/scratch.txt" ) ) .unwrap ( ) ;
2228
let r = app.submodule_update ( false , & [ ] ) .unwrap ( ) ;
2229
assert_eq! ( r, vec! [ ( " libs/lib" .to_string ( ) , SubrepoSync:: Updated) ] ) ;
2230
assert_eq! ( fs:: read( app_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v2\n " ) ;
2231
let _ = l2;
2232
2233
2234
assert! ( app.push ( " origin" , false ) .unwrap ( ) [ 0 ] .ok) ;
2235
let clone_dir = root.join ( " app-clone" ) ;
2236
let clone = Repo:: clone( & app_url, & clone_dir) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2237
assert! ( ! clone_dir.join ( " libs/lib/.oxy" ) .exists ( ) ) ;
2238
assert_eq! ( clone.submodule_update ( false , & [ ] ) .unwrap ( ) , vec! [ ( " libs/lib" .to_string ( ) , SubrepoSync:: Missing) ] ) ;
2239
assert_eq! ( clone.submodule_update ( true , & [ ] ) .unwrap ( ) , vec! [ ( " libs/lib" .to_string ( ) , SubrepoSync:: Updated) ] ) ;
2240
assert_eq! ( fs:: read( clone_dir.join ( " libs/lib/lib.txt" ) ) .unwrap ( ) , b " v2\n " ) ;
2241
assert! ( ! clone.snapshot ( " status" ) .unwrap ( ) .changed) ;
2242
2243
clone.submodule_remove ( " libs/lib" ) .unwrap ( ) ;
2244
let snap = clone.snapshot ( " status" ) .unwrap ( ) ;
2245
assert! ( snap.statuses.iter ( ) .any ( | s | s.path = = " libs/lib" & & s.status = = ChangeStatus:: Removed ) , " {:?}" , snap.statuses) ;
2246
let c = clone.commit ( " drop lib" , & [ ] ) .unwrap ( ) ;
2247
assert! ( ! clone.load_state ( & [ c] ) .unwrap ( ) .materialize ( ) .files.contains_key ( " libs/lib" ) ) ;
2248
assert! ( clone_dir.join ( " libs/lib/lib.txt" ) .exists ( ) ) ;
2249
assert! ( ! clone.snapshot ( " status" ) .unwrap ( ) .changed, " an unregistered nested repository is ignored" ) ;
2250
}
2251
2252
# [ test ]
2253
fn git_submodules_import_and_export_as_gitlinks ( ) {
2254
hermetic ( ) ;
2255
if std:: process:: Command:: new( " git" ) .arg ( " --version" ) .output ( ) .is_err ( ) {
2256
eprintln! ( " git not installed; skipping" ) ;
2257
return ;
2258
}
2259
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2260
let root = dir.path ( ) ;
2261
let git_in = | d : & Path, args : & [& str ]| -> String {
2262
let out = std:: process:: Command:: new( " git" )
2263
.arg ( " -C" )
2264
.arg ( d)
2265
.args ( [ " -c" , " user.name=Grace" , " -c" , " [email protected] " , " -c" , " commit.gpgsign=false" , " -c" , " protocol.file.allow=always" ] )
2266
.args ( args)
2267
.output ( )
2268
.unwrap ( ) ;
2269
assert! ( out.status.success ( ) , " git {args:?}: {}" , String :: from_utf8_lossy( & out.stderr) ) ;
2270
String :: from_utf8_lossy( & out.stdout) .trim ( ) .to_string ( )
2271
} ;
2272
let sub = root.join ( " sub" ) ;
2273
fs:: create_dir_all( & sub) .unwrap ( ) ;
2274
git_in ( & sub, & [ " init" , " -q" , " -b" , " main" ] ) ;
2275
write ( & sub, " s.txt" , b " sub\n " ) ;
2276
git_in ( & sub, & [ " add" , " ." ] ) ;
2277
git_in ( & sub, & [ " commit" , " -q" , " -m" , " sub initial" ] ) ;
2278
let sub_sha = git_in ( & sub, & [ " rev-parse" , " HEAD" ] ) ;
2279
let g = root.join ( " main.git" ) ;
2280
fs:: create_dir_all( & g) .unwrap ( ) ;
2281
git_in ( & g, & [ " init" , " -q" , " -b" , " main" ] ) ;
2282
write ( & g, " README.md" , b " # app\n " ) ;
2283
git_in ( & g, & [ " add" , " ." ] ) ;
2284
git_in ( & g, & [ " commit" , " -q" , " -m" , " initial" ] ) ;
2285
git_in ( & g, & [ " submodule" , " add" , " -q" , sub.to_str ( ) .unwrap ( ) , " vendor/sub" ] ) ;
2286
git_in ( & g, & [ " commit" , " -q" , " -m" , " add submodule" ] ) ;
2287
2288
let dest = root.join ( " oxy" ) ;
2289
let repo = Repo:: init( & dest) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2290
let rep = repo.import_git ( & g, " main" ) .unwrap ( ) ;
2291
assert_eq! ( ( rep.commits, rep.submodules) , ( 2 , 1 ) ) ;
2292
let files = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) .materialize ( ) .files;
2293
assert_eq! ( files[ " vendor/sub" ] .kind, NodeKind:: Subrepo) ;
2294
let FileContent:: Opaque { blobs } = & files[ " vendor/sub" ] .content else { panic! ( ) } ;
2295
assert_eq! ( crate :: subrepo:: Pin:: decode( & repo.read_blob ( & blobs[ 0 ] ) .unwrap ( ) ) .unwrap ( ) , crate :: subrepo:: Pin:: Git { sha: sub_sha.clone ( ) , branch: " HEAD" .into ( ) } ) ;
2296
let modules = crate :: subrepo:: Modules:: parse( & fs:: read_to_string( dest.join ( " .oxymodules" ) ) .unwrap ( ) ) ;
2297
assert_eq! ( modules.entries[ " vendor/sub" ] .url, sub.to_string_lossy ( ) ) ;
2298
assert! ( modules.entries[ " vendor/sub" ] .git) ;
2299
assert! ( files.contains_key ( " .gitmodules" ) , " the Git file is kept for round trips" ) ;
2300
assert! ( ! dest.join ( " vendor/sub/.git" ) .exists ( ) , " not cloned until update --init" ) ;
2301
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
2302
2303
assert_eq! ( repo.submodule_update ( true , & [ ] ) .unwrap ( ) , vec! [ ( " vendor/sub" .to_string ( ) , SubrepoSync:: Updated) ] ) ;
2304
assert_eq! ( git_in ( & dest.join ( " vendor/sub" ) , & [ " rev-parse" , " HEAD" ] ) , sub_sha) ;
2305
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
2306
2307
let mirror = root.join ( " mirror.git" ) ;
2308
let rep = repo.git_export ( & mirror) .unwrap ( ) ;
2309
assert_eq! ( rep.skipped_subrepos, 0 ) ;
2310
let ls = git_in ( & mirror, & [ " ls-tree" , " main" , " vendor/sub" ] ) ;
2311
assert! ( ls.starts_with ( " 160000 commit " ) & & ls.contains ( & sub_sha) , " {ls}" ) ;
2312
assert_eq! ( git_in ( & mirror, & [ " ls-tree" , " --name-only" , " main" ] ) , " .gitmodules\n README.md\n vendor" ) ;
2313
let orig_tree = git_in ( & g, & [ " rev-parse" , " main^{tree}" ] ) ;
2314
assert_eq! ( git_in ( & mirror, & [ " rev-parse" , " main^{tree}" ] ) , orig_tree, " tree round trip" ) ;
2315
}
2316
957fee30 Andy McCall 1 month ago ↶
2317
# [ test ]
2318
fn partial_commits_by_path_and_by_hunk ( ) {
2319
hermetic ( ) ;
2320
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2321
let root = dir.path ( ) ;
2322
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2323
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
2324
write ( root, " b.txt" , b " b\n " ) ;
2325
let h0 = repo.commit ( " base" , & [ ] ) .unwrap ( ) ;
2326
write ( root, " a.txt" , b " 0\n 1\n 2\n 3\n 4\n " ) ;
2327
write ( root, " b.txt" , b " b\n bb\n " ) ;
2328
write ( root, " c.txt" , b " c\n " ) ;
2329
2330
let h1 = repo.commit_selection ( " a only" , & [ ] , & Selection:: Paths( vec! [ " a.txt" .into ( ) ] ) ) .unwrap ( ) ;
2331
let c1 = repo.store ( ) .get_change ( & h1) .unwrap ( ) ;
2332
assert! ( c1.tree_ops.is_empty ( ) & & c1.edits.iter ( ) .all ( | e | e.node ( ) = = c1.edits[ 0 ] .node ( ) ) , " {:?}" , c1.tree_ops) ;
2333
assert_eq! ( c1.deps, vec! [ h0] ) ;
2334
let files = repo.load_state ( & [ h1] ) .unwrap ( ) .materialize ( ) .files;
2335
assert! ( matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 0\n 1\n 2\n 3\n 4\n " ) ) ;
2336
assert! ( matches! ( & files[ " b.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " b\n " ) ) ;
2337
assert! ( ! files.contains_key ( " c.txt" ) ) ;
2338
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
2339
assert_eq! ( snap.statuses.iter ( ) .map ( | s | ( s.path.as_str ( ) , s.status) ) .collect:: < Vec< _ > > ( ) , vec! [ ( " b.txt" , ChangeStatus:: Modified) , ( " c.txt" , ChangeStatus:: Added) ] ) ;
2340
assert_eq! ( fs:: read( root.join ( " b.txt" ) ) .unwrap ( ) , b " b\n bb\n " , " disk untouched" ) ;
2341
2342
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
2343
let b_hunk = hunks.iter ( ) .find ( | h | h.path = = " b.txt" & & h.text.contains ( " +bb" ) ) .unwrap ( ) .index;
2344
let h2 = repo.commit_selection ( " b only" , & [ ] , & Selection:: Hunks( vec! [ b_hunk] ) ) .unwrap ( ) ;
2345
let files = repo.load_state ( & [ h2] ) .unwrap ( ) .materialize ( ) .files;
2346
assert! ( matches! ( & files[ " b.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " b\n bb\n " ) ) ;
2347
assert! ( ! files.contains_key ( " c.txt" ) ) ;
2348
let snap = repo.snapshot ( " status" ) .unwrap ( ) ;
2349
assert_eq! ( snap.statuses.iter ( ) .map ( | s | s.path.as_str ( ) ) .collect:: < Vec< _ > > ( ) , vec! [ " c.txt" ] ) ;
2350
2351
2352
write ( root, " d/e.txt" , b " e\n " ) ;
2353
let ( _ , hunks) = repo.hunks ( ) .unwrap ( ) ;
2354
let e_insert = hunks.iter ( ) .find ( | h | h.path = = " d/e.txt" & & h.text.contains ( " +e" ) ) .unwrap ( ) .index;
2355
let h3 = repo.commit_selection ( " e only" , & [ ] , & Selection:: Hunks( vec! [ e_insert] ) ) .unwrap ( ) ;
2356
let c3 = repo.store ( ) .get_change ( & h3) .unwrap ( ) ;
2357
assert_eq! ( c3.tree_ops.len ( ) , 2 , " {:?}" , c3.tree_ops) ;
2358
let files = repo.load_state ( & [ h3] ) .unwrap ( ) .materialize ( ) .files;
2359
assert! ( files.contains_key ( " d/e.txt" ) & & ! files.contains_key ( " c.txt" ) ) ;
2360
2361
repo.commit ( " the rest" , & [ ] ) .unwrap ( ) ;
2362
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
2363
let files = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) .materialize ( ) .files;
2364
assert! ( files.contains_key ( " c.txt" ) ) ;
2365
}
2366
2367
# [ test ]
2368
fn blame_attributes_lines_to_introducing_changes_across_renames ( ) {
2369
hermetic ( ) ;
2370
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2371
let root = dir.path ( ) ;
2372
let mut repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2373
repo.create_identity ( " Ada" , " [email protected] " , IdentityScope:: User) .unwrap ( ) ;
2374
write ( root, " a.txt" , b " one\n " ) ;
2375
let h1 = repo.commit ( " first" , & [ ] ) .unwrap ( ) ;
2376
write ( root, " a.txt" , b " one\n two\n " ) ;
2377
let h2 = repo.commit ( " second" , & [ ] ) .unwrap ( ) ;
2378
let b = repo.blame ( " a.txt" ) .unwrap ( ) ;
2379
assert_eq! ( b.iter ( ) .map ( | l | ( l.line, l.hash, l.text.as_slice ( ) ) ) .collect:: < Vec< _ > > ( ) , vec! [ ( 1 , h1, b " one\n " .as_slice ( ) ) , ( 2 , h2, b " two\n " .as_slice ( ) ) ] ) ;
2380
assert! ( b.iter ( ) .all ( | l | l.author = = " Ada" ) ) ;
2381
assert_eq! ( b[ 0 ] .change_id, repo.store ( ) .get_change ( & h1) .unwrap ( ) .change_id) ;
2382
fs:: rename( root.join ( " a.txt" ) , root.join ( " b.txt" ) ) .unwrap ( ) ;
2383
repo.commit ( " rename" , & [ ] ) .unwrap ( ) ;
2384
let b = repo.blame ( " b.txt" ) .unwrap ( ) ;
2385
assert_eq! ( b.iter ( ) .map ( | l | l.hash ) .collect:: < Vec< _ > > ( ) , vec! [ h1, h2] , " the node survives the rename" ) ;
2386
assert! ( repo.blame ( " a.txt" ) .is_err ( ) ) ;
2387
}
2388
2389
# [ test ]
2390
fn bisect_finds_the_first_bad_change ( ) {
2391
hermetic ( ) ;
2392
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2393
let root = dir.path ( ) ;
2394
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2395
let mut content = String :: new( ) ;
2396
let mut hashes = Vec :: new( ) ;
2397
for i in 1 .. = 8 {
2398
content.push_str ( & format! ( " line {i} {} \n " , if i = = 5 { " bug" } else { " " } ) ) ;
2399
write ( root, " f.txt" , content.as_bytes ( ) ) ;
2400
hashes.push ( repo.commit ( & format! ( " step {i} " ) , & [ ] ) .unwrap ( ) ) ;
2401
}
2402
2403
assert_eq! ( repo.bookmark_heads ( ) .unwrap ( ) , vec! [ hashes[ 7 ] ] ) ;
2404
repo.bisect_start ( ) .unwrap ( ) ;
2405
assert! ( matches! ( repo.bisect_mark ( Some ( true ) , & [ hashes[ 0 ] ] ) .unwrap ( ) , BisectStep:: NeedMarks { good: 1 , bad: 0 } ) ) ;
2406
let step = repo.bisect_mark ( Some ( false ) , & [ hashes[ 7 ] ] ) .unwrap ( ) ;
2407
assert! ( matches! ( step, BisectStep:: Testing { remaining: 7 , .. } ) , " {step:?}" ) ;
2408
2409
let step = repo.bisect_run ( & [ " sh" .into ( ) , " -c" .into ( ) , " ! grep -q bug f.txt" .into ( ) ] ) .unwrap ( ) ;
2410
assert_eq! ( step, BisectStep:: Found { culprit: hashes[ 4 ] } ) ;
2411
assert_eq! ( repo.bisect_status ( ) .unwrap ( ) , Some ( BisectStep:: Found { culprit: hashes[ 4 ] } ) ) ;
2412
repo.bisect_reset ( ) .unwrap ( ) ;
2413
assert_eq! ( fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) , content) ;
2414
assert! ( repo.bisect_status ( ) .unwrap ( ) .is_none ( ) ) ;
2415
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
2416
2417
repo.bisect_start ( ) .unwrap ( ) ;
2418
repo.bisect_mark ( Some ( true ) , & [ hashes[ 0 ] ] ) .unwrap ( ) ;
2419
let mut step = repo.bisect_mark ( Some ( false ) , & [ hashes[ 7 ] ] ) .unwrap ( ) ;
2420
let mut skipped_once = false ;
2421
let mut guard = 0 ;
2422
while let BisectStep:: Testing { .. } = step {
2423
guard + = 1 ;
2424
assert! ( guard < 20 ) ;
2425
let text = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
2426
if ! skipped_once & & text.contains ( " line 6" ) {
2427
2428
skipped_once = true ;
2429
step = repo.bisect_mark ( None , & [ ] ) .unwrap ( ) ;
2430
continue ;
2431
}
2432
step = repo.bisect_mark ( Some ( ! text.contains ( " bug" ) ) , & [ ] ) .unwrap ( ) ;
2433
}
2434
assert! ( skipped_once) ;
2435
assert_eq! ( step, BisectStep:: Found { culprit: hashes[ 4 ] } ) ;
2436
repo.bisect_reset ( ) .unwrap ( ) ;
2437
2438
repo.bisect_start ( ) .unwrap ( ) ;
2439
repo.bisect_mark ( Some ( true ) , & [ hashes[ 0 ] ] ) .unwrap ( ) ;
2440
let mut step = repo.bisect_mark ( Some ( false ) , & [ hashes[ 7 ] ] ) .unwrap ( ) ;
2441
let mut guard = 0 ;
2442
while let BisectStep:: Testing { heads, .. } = & step {
2443
guard + = 1 ;
2444
assert! ( guard < 20 ) ;
2445
let text = fs:: read_to_string( root.join ( " f.txt" ) ) .unwrap ( ) ;
2446
let is_culprit_state = heads.contains ( & hashes[ 4 ] ) & & ! heads.iter ( ) .any ( | h | hashes[ 5 .. ] .contains ( h) ) ;
2447
step = if is_culprit_state { repo.bisect_mark ( None , & [ ] ) .unwrap ( ) } else { repo.bisect_mark ( Some ( ! text.contains ( " bug" ) ) , & [ ] ) .unwrap ( ) } ;
2448
}
2449
match step {
2450
BisectStep:: Inconclusive { candidates } => assert! ( candidates.contains ( & hashes[ 4 ] ) & & candidates.len ( ) < = 2 , " {candidates:?}" ) ,
2451
other => panic! ( " {other:?}" ) ,
2452
}
2453
repo.bisect_reset ( ) .unwrap ( ) ;
2454
}
2455
728da0f9 Andy McCall 1 month ago ↶
2456
# [ test ]
2457
fn squash_folds_a_change_into_its_dependency ( ) {
2458
hermetic ( ) ;
2459
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2460
let root = dir.path ( ) ;
2461
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2462
write ( root, " a.txt" , b " 1\n " ) ;
2463
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
2464
write ( root, " a.txt" , b " 1\n 2\n " ) ;
2465
let c = repo.commit ( " C" , & [ ] ) .unwrap ( ) ;
2466
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
2467
let e = repo.commit ( " E" , & [ ] ) .unwrap ( ) ;
2468
write ( root, " b.txt" , b " b\n " ) ;
2469
let b = repo.commit ( " B" , & [ ] ) .unwrap ( ) ;
2470
let id_a = repo.store ( ) .get_change ( & a) .unwrap ( ) .change_id;
2471
let id_e = repo.store ( ) .get_change ( & e) .unwrap ( ) .change_id;
2472
2473
let err = repo.squash ( & e, & a) .unwrap_err ( ) .to_string ( ) ;
2474
assert! ( err.contains ( & c.short ( 12 ) ) , " {err}" ) ;
2475
2476
let a2 = repo.squash ( & c, & a) .unwrap ( ) ;
2477
let a2c = repo.store ( ) .get_change ( & a2) .unwrap ( ) ;
2478
assert_eq! ( a2c.change_id, id_a) ;
2479
assert! ( a2c.deps.is_empty ( ) ) ;
2480
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
2481
let st = repo.load_state ( & heads) .unwrap ( ) ;
2482
assert! ( ! st.contains ( & c) & & ! st.contains ( & a) & & ! st.contains ( & e) ) ;
2483
let e2 = st.changes ( ) .find ( | ( _, m ) | m.change_id = = id_e ) .map ( | ( h , _) | * h) .unwrap ( ) ;
2484
assert_eq! ( repo.store ( ) .get_change ( & e2) .unwrap ( ) .deps, vec! [ a2] , " E now depends on A' alone" ) ;
2485
let files = st.materialize ( ) .files;
2486
assert! ( matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 1\n 2\n 3\n " ) ) ;
2487
assert! ( files.contains_key ( " b.txt" ) ) ;
2488
assert! ( st.contains ( & b) ) ;
2489
assert! ( ! repo.snapshot ( " status" ) .unwrap ( ) .changed) ;
2490
assert_eq! ( repo.log ( ) .unwrap ( ) .len ( ) , 4 , " working copy, B, E', A'" ) ;
2491
2492
repo.undo ( ) .unwrap ( ) ;
2493
repo.undo ( ) .unwrap ( ) ;
2494
let st = repo.load_state ( & repo.bookmark_heads ( ) .unwrap ( ) ) .unwrap ( ) ;
2495
assert! ( st.contains ( & c) & & st.contains ( & a) & & st.contains ( & e) ) ;
2496
}
2497
2498
# [ test ]
2499
fn rebase_re_expresses_a_change_against_another_base ( ) {
2500
hermetic ( ) ;
2501
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2502
let root = dir.path ( ) ;
2503
let repo = Repo:: init( root) .unwrap ( ) .with_home ( root.join ( " home" ) ) ;
2504
write ( root, " a.txt" , b " 1\n 2\n " ) ;
2505
let a = repo.commit ( " A" , & [ ] ) .unwrap ( ) ;
2506
write ( root, " a.txt" , b " 1\n 2\n 3\n " ) ;
2507
let c = repo.commit ( " C: append 3" , & [ ] ) .unwrap ( ) ;
2508
write ( root, " a.txt" , b " 1\n 2\n 3\n 4\n " ) ;
2509
let e = repo.commit ( " E: append 4" , & [ ] ) .unwrap ( ) ;
2510
2511
repo.checkout ( & [ a] , true , " checkout" ) .unwrap ( ) ;
2512
write ( root, " a.txt" , b " 0\n 1\n 2\n " ) ;
2513
let d = repo.commit ( " D: prepend 0" , & [ ] ) .unwrap ( ) ;
2514
let id_c = repo.store ( ) .get_change ( & c) .unwrap ( ) .change_id;
2515
let id_e = repo.store ( ) .get_change ( & e) .unwrap ( ) .change_id;
2516
2517
let c2 = repo.rebase ( & c, & [ d] ) .unwrap ( ) ;
2518
let c2c = repo.store ( ) .get_change ( & c2) .unwrap ( ) ;
2519
assert_eq! ( c2c.change_id, id_c) ;
2520
assert! ( c2c.deps.contains ( & d) , " {:?}" , c2c.deps) ;
2521
let heads = repo.bookmark_heads ( ) .unwrap ( ) ;
2522
let st = repo.load_state ( & heads) .unwrap ( ) ;
2523
assert! ( ! st.contains ( & c) & & st.contains ( & d) ) ;
2524
let e2 = st.changes ( ) .find ( | ( _, m ) | m.change_id = = id_e ) .map ( | ( h , _) | * h) .unwrap ( ) ;
2525
let e2deps = repo.store ( ) .get_change ( & e2) .unwrap ( ) .deps;
2526
assert! ( e2deps.contains ( & c2) & & ! e2deps.contains ( & c) & & e2deps.contains ( & a) , " E follows C by content and still depends on A, which introduced the file: {e2deps:?}" ) ;
2527
let files = st.materialize ( ) .files;
2528
assert! ( matches! ( & files[ " a.txt" ] .content, FileContent:: Text { bytes, .. } if bytes = = b " 0\n 1\n 2\n 3\n 4\n " ) , " {:?}" , files[ " a.txt" ] .content) ;
2529
assert! ( ! files[ " a.txt" ] .is_conflicted ( ) ) ;
2530
2531
assert! ( repo.rebase ( & c2, & [ c2] ) .is_err ( ) ) ;
2532
let err = repo.rebase ( & e2, & [ d] ) .unwrap_err ( ) .to_string ( ) ;
2533
assert! ( err.contains ( " empty" ) | | err.contains ( " already" ) , " {err}" ) ;
2534
}
2535